diff options
Diffstat (limited to 'src/host')
232 files changed, 28740 insertions, 13197 deletions
diff --git a/src/host/fb_tools/bdf_to_c.py b/src/host/fb_tools/bdf_to_c.py index ebeb7f9c..f808f508 100755 --- a/src/host/fb_tools/bdf_to_c.py +++ b/src/host/fb_tools/bdf_to_c.py @@ -19,10 +19,6 @@ selected glyphs in the format defined by the <fb/font.h> header. # 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. from optparse import OptionParser import sys diff --git a/src/host/gprsdecode/Makefile.am b/src/host/gprsdecode/Makefile.am index 542d54b6..b5c8b12a 100644 --- a/src/host/gprsdecode/Makefile.am +++ b/src/host/gprsdecode/Makefile.am @@ -6,8 +6,7 @@ AM_CPPFLAGS = \ $(NULL) AM_CFLAGS = \ - -Wall -O3 \ - -Wno-missing-braces \ + -Wall \ $(LIBOSMOGSM_CFLAGS) \ $(LIBOSMOCORE_CFLAGS) \ $(LIBOSMOCODING_CFLAGS) \ diff --git a/src/host/gprsdecode/configure.ac b/src/host/gprsdecode/configure.ac index 8da68c80..734cbd30 100644 --- a/src/host/gprsdecode/configure.ac +++ b/src/host/gprsdecode/configure.ac @@ -2,6 +2,8 @@ dnl Process this file with autoconf to produce a configure script AC_INIT([gprsdecode], [0.0.0]) AM_INIT_AUTOMAKE +CFLAGS="$CFLAGS -std=gnu11" + dnl kernel style compile messages m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) diff --git a/src/host/gprsdecode/gprs.c b/src/host/gprsdecode/gprs.c index ae59cf90..4ba91ea0 100644 --- a/src/host/gprsdecode/gprs.c +++ b/src/host/gprsdecode/gprs.c @@ -17,10 +17,6 @@ * 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 <stdio.h> diff --git a/src/host/gprsdecode/gsmtap.c b/src/host/gprsdecode/gsmtap.c index 5c124b23..ec816b35 100644 --- a/src/host/gprsdecode/gsmtap.c +++ b/src/host/gprsdecode/gsmtap.c @@ -17,10 +17,6 @@ * 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 <stdio.h> @@ -100,6 +96,6 @@ void gsmtap_send_llc(uint8_t *data, size_t len, bool ul) memcpy(dst, data, len); /* Finally, send to the sink */ - gsmtap_sendmsg(gti, msg); + gsmtap_sendmsg_free(gti, msg); } diff --git a/src/host/gprsdecode/main.c b/src/host/gprsdecode/main.c index 7e094896..49484e29 100644 --- a/src/host/gprsdecode/main.c +++ b/src/host/gprsdecode/main.c @@ -17,10 +17,6 @@ * 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 <stdio.h> diff --git a/src/host/gprsdecode/rlcmac.c b/src/host/gprsdecode/rlcmac.c index d33cb1eb..dc11fd30 100644 --- a/src/host/gprsdecode/rlcmac.c +++ b/src/host/gprsdecode/rlcmac.c @@ -17,10 +17,6 @@ * 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 <stdio.h> diff --git a/src/host/gprsdecode/rlcmac.h b/src/host/gprsdecode/rlcmac.h index 2381d771..41c0decd 100644 --- a/src/host/gprsdecode/rlcmac.h +++ b/src/host/gprsdecode/rlcmac.h @@ -2,6 +2,7 @@ #include <stdint.h> #include <stdbool.h> +#include <osmocom/core/endian.h> #define OLD_TIME 2000 @@ -16,10 +17,16 @@ struct gprs_message { }; struct gprs_lime { +#if OSMO_IS_LITTLE_ENDIAN uint8_t li:6, m:1, e:1; uint8_t used; +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ + uint8_t e:1, m:1, li:6; + uint8_t used; +#endif } __attribute__ ((packed)); struct gprs_frag { diff --git a/src/host/gsmmap/.gitignore b/src/host/gsmmap/.gitignore deleted file mode 100644 index 661fd133..00000000 --- a/src/host/gsmmap/.gitignore +++ /dev/null @@ -1,35 +0,0 @@ -# autoreconf by-products -*.in - -aclocal.m4 -autom4te.cache/ -configure -depcomp -install-sh -missing - -# configure by-products -.deps/ -Makefile - -config.status -version.h - -# build by-products -*.o - -gsmmap - -# various -.version -.tarball-version - -# IDA file -*.id* -*.nam -*.til - -# Other test files -*.dump -*.bin -*.log diff --git a/src/host/gsmmap/Makefile.am b/src/host/gsmmap/Makefile.am deleted file mode 100644 index 29be15c5..00000000 --- a/src/host/gsmmap/Makefile.am +++ /dev/null @@ -1,17 +0,0 @@ -AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 - -# versioning magic -BUILT_SOURCES = $(top_srcdir)/.version -$(top_srcdir)/.version: - echo $(VERSION) > $@-t && mv $@-t $@ -dist-hook: - echo $(VERSION) > $(distdir)/.tarball-version - -INCLUDES = $(all_includes) -I../layer23/include -DHOST_BUILD -AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) - -sbin_PROGRAMS = gsmmap - -gsmmap_SOURCES = gsmmap.c geo.c locate.c log.c ../layer23/src/common/sysinfo.c ../layer23/src/common/networks.c ../layer23/src/common/logging.c -gsmmap_LDADD = $(LIBOSMOGSM_LIBS) $(LIBOSMOCORE_LIBS) -lm - diff --git a/src/host/gsmmap/configure.ac b/src/host/gsmmap/configure.ac deleted file mode 100644 index 3a42d4c3..00000000 --- a/src/host/gsmmap/configure.ac +++ /dev/null @@ -1,26 +0,0 @@ -dnl Process this file with autoconf to produce a configure script -AC_INIT([gsmmap], - m4_esyscmd([./git-version-gen .tarball-version]), - [baseband-devel@lists.osmocom.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 - -dnl checks for libraries -PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore) -PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm) - -dnl checks for header files -AC_HEADER_STDC - -dnl Checks for typedefs, structures and compiler characteristics - -AC_OUTPUT( - Makefile) diff --git a/src/host/gsmmap/geo.c b/src/host/gsmmap/geo.c deleted file mode 100644 index 65633d2c..00000000 --- a/src/host/gsmmap/geo.c +++ /dev/null @@ -1,47 +0,0 @@ -#include <math.h> -#include "geo.h" - -void geo2space(double *x, double *y, double *z, double lon, double lat) -{ - *z = sin(lat / 180.0 * PI) * POLE_RADIUS; - *x = sin(lon / 180.0 * PI) * cos(lat / 180.0 * PI) * EQUATOR_RADIUS; - *y = -cos(lon / 180.0 * PI) * cos(lat / 180.0 * PI) * EQUATOR_RADIUS; -} - -void space2geo(double *lon, double *lat, double x, double y, double z) -{ - double r; - - /* bring geoid to 1m radius */ - z = z / POLE_RADIUS; - x = x / EQUATOR_RADIUS; - y = y / EQUATOR_RADIUS; - - /* normalize */ - r = sqrt(x * x + y * y + z * z); - z = z / r; - x = x / r; - y = y / r; - - *lat = asin(z) / PI * 180; - *lon = atan2(x, -y) / PI * 180; -} - -double distinspace(double x1, double y1, double z1, double x2, double y2, - double z2) -{ - double x = x1 - x2; - double y = y1 - y2; - double z = z1 - z2; - - return sqrt(x * x + y * y + z * z); -} - -double distonplane(double x1, double y1, double x2, double y2) -{ - double x = x1 - x2; - double y = y1 - y2; - - return sqrt(x * x + y * y); -} - diff --git a/src/host/gsmmap/git-version-gen b/src/host/gsmmap/git-version-gen deleted file mode 100755 index 652fac68..00000000 --- a/src/host/gsmmap/git-version-gen +++ /dev/null @@ -1,151 +0,0 @@ -#!/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='osmocon_v*' HEAD 2>/dev/null \ - || git describe --abbrev=4 HEAD 2>/dev/null` \ - && case $v in - osmocon_[0-9]*) ;; - osmocon_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-/;s/^osmocon_//'`; -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/src/host/layer23/.gitignore b/src/host/layer23/.gitignore index 8fb93f73..333c28c5 100644 --- a/src/host/layer23/.gitignore +++ b/src/host/layer23/.gitignore @@ -33,4 +33,6 @@ src/misc/echo_test src/misc/cbch_sniff src/misc/ccch_scan src/misc/layer23 +src/misc/gsmmap src/mobile/mobile +src/modem/modem diff --git a/src/host/layer23/README b/src/host/layer23/README index dd598234..c52e7bc6 100644 --- a/src/host/layer23/README +++ b/src/host/layer23/README @@ -30,7 +30,7 @@ Layer3 calls rslms_recvmsg() with a msgb that has the msgb->l2h pointing to a RSL header (struct abis_rsl_common_hdr). There are utility functions like rslms_tx_rll_req() and rslms_tx_rsll_req_l3() -for creating msgb's with the apropriate RSL/RLL headers. +for creating msgb's with the appropriate RSL/RLL headers. === LAPDm === diff --git a/src/host/layer23/configure.ac b/src/host/layer23/configure.ac index 3e696103..7fb8bf16 100644 --- a/src/host/layer23/configure.ac +++ b/src/host/layer23/configure.ac @@ -2,6 +2,8 @@ dnl Process this file with autoconf to produce a configure script AC_INIT([layer23], [0.0.0]) AM_INIT_AUTOMAKE +CFLAGS="$CFLAGS -std=gnu11" + dnl kernel style compile messages m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) @@ -32,6 +34,7 @@ AC_ARG_ENABLE(werror, if test x"$werror" = x"yes" then WERROR_FLAGS="-Werror" + WERROR_FLAGS+=" -Werror=implicit-int -Werror=int-conversion -Werror=old-style-definition" WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations" WERROR_FLAGS+=" -Wno-error=cpp" # "#warning" CFLAGS="$CFLAGS $WERROR_FLAGS" @@ -39,36 +42,72 @@ then fi dnl checks for libraries -PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore) +PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.5.0) PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.10.0) PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm) +PKG_CHECK_MODULES(LIBOSMOISDN, libosmoisdn) PKG_CHECK_MODULES(LIBOSMOCODEC, libosmocodec) +PKG_CHECK_MODULES(LIBOSMOGPRSRLCMAC, libosmo-gprs-rlcmac) +PKG_CHECK_MODULES(LIBOSMOGPRSLLC, libosmo-gprs-llc) +PKG_CHECK_MODULES(LIBOSMOGPRSSNDCP, libosmo-gprs-sndcp) +PKG_CHECK_MODULES(LIBOSMOGPRSGMM, libosmo-gprs-gmm) +PKG_CHECK_MODULES(LIBOSMOGPRSSM, libosmo-gprs-sm) AC_CHECK_LIB(gps, gps_waiting, LIBGPS_CFLAGS=" -D_HAVE_GPSD" LIBGPS_LIBS=" -lgps ",,) AC_SUBST([LIBGPS_CFLAGS]) AC_SUBST([LIBGPS_LIBS]) dnl optional dependencies -PKG_CHECK_MODULES(LIBLUA, lua53, [ - WITH_LUA=1], [ - WITH_LUA=0]) -AC_SUBST([WITH_LUA]) -AM_CONDITIONAL([BUILD_LUA], test "x$WITH_LUA" = "x1") +AC_ARG_WITH([lua53], [ + AS_HELP_STRING([--with-lua53], + [Enable LUA scripting support @<:@default=check@:>@]) +]) + +AC_ARG_WITH([gapk_io], [ + AS_HELP_STRING([--with-gapk-io], + [Enable GAPK I/O support @<:@default=check@:>@]) +]) + +found_lua53=no +AS_IF([test "x$with_lua53" != "xno"], [ + PKG_CHECK_MODULES(LIBLUA, lua53, [found_lua53=yes], [found_lua53=no]) + AS_IF([test "x$with_lua53" = "xyes" -a "x$found_lua53" = "xno"], [ + AC_MSG_ERROR([lua53 support requested but pkg-config is unable to find it]) + ]) +]) +AM_CONDITIONAL([BUILD_LUA], test "x$found_lua53" = "xyes") + +found_gapk=no +AS_IF([test "x$with_gapk_io" != "xno"], [ + PKG_CHECK_MODULES(LIBOSMOGAPK, libosmogapk, [found_gapk=yes], [found_gapk=no]) + AS_IF([test "x$with_gapk_io" = "xyes" -a "x$found_gapk" = "xno"], [ + AC_MSG_ERROR([GAPK I/O support requested but pkg-config is unable to find it]) + ]) +]) +AM_CONDITIONAL([BUILD_GAPK], test "x$found_gapk" = "xyes") dnl checks for header files AC_HEADER_STDC dnl Checks for typedefs, structures and compiler characteristics +AC_MSG_RESULT([CFLAGS="$CFLAGS"]) +AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"]) + +dnl Generate the output +AM_CONFIG_HEADER(config.h) + AC_OUTPUT( src/Makefile src/common/Makefile src/misc/Makefile src/mobile/Makefile + src/modem/Makefile include/Makefile include/osmocom/Makefile include/osmocom/bb/Makefile include/osmocom/bb/common/Makefile include/osmocom/bb/misc/Makefile include/osmocom/bb/mobile/Makefile + include/osmocom/bb/modem/Makefile Makefile) diff --git a/src/host/layer23/include/osmocom/bb/Makefile.am b/src/host/layer23/include/osmocom/bb/Makefile.am index 58a5f7fb..3b6a4d8b 100644 --- a/src/host/layer23/include/osmocom/bb/Makefile.am +++ b/src/host/layer23/include/osmocom/bb/Makefile.am @@ -1 +1 @@ -SUBDIRS = common misc mobile +SUBDIRS = common misc mobile modem diff --git a/src/host/layer23/include/osmocom/bb/common/Makefile.am b/src/host/layer23/include/osmocom/bb/common/Makefile.am index f9364cd9..7c0fa972 100644 --- a/src/host/layer23/include/osmocom/bb/common/Makefile.am +++ b/src/host/layer23/include/osmocom/bb/common/Makefile.am @@ -1,3 +1,22 @@ -noinst_HEADERS = l1ctl.h l1l2_interface.h l23_app.h logging.h \ - networks.h gps.h sysinfo.h osmocom_data.h utils.h \ - sap_proto.h sap_fsm.h sap_interface.h sim.h +noinst_HEADERS = \ + apn.h \ + apn_fsm.h \ + l1ctl.h \ + l1l2_interface.h \ + l23_app.h \ + logging.h \ + ms.h \ + networks.h \ + gps.h \ + sysinfo.h \ + osmocom_data.h \ + utils.h \ + sap_proto.h \ + sap_fsm.h \ + sap_interface.h \ + settings.h \ + sim.h \ + subscriber.h \ + support.h \ + vty.h \ + $(NULL) diff --git a/src/host/layer23/include/osmocom/bb/common/apn.h b/src/host/layer23/include/osmocom/bb/common/apn.h new file mode 100644 index 00000000..0adb8de7 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/common/apn.h @@ -0,0 +1,82 @@ +/* APN Context + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * 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. + * + */ + +#pragma once + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/select.h> +#include <osmocom/core/tun.h> + +#include <osmocom/gprs/sm/sm.h> + +#include <osmocom/bb/common/apn_fsm.h> + +struct osmocom_ms; + +#define APN_TYPE_IPv4 0x01 /* v4-only */ +#define APN_TYPE_IPv6 0x02 /* v6-only */ +#define APN_TYPE_IPv4v6 0x04 /* v4v6 dual-stack */ + +struct osmobb_pdp_ctx { + uint8_t nsapi; + uint8_t llc_sapi; + uint8_t qos[OSMO_GPRS_SM_QOS_MAXLEN]; + uint8_t qos_len; + uint8_t pco[OSMO_GPRS_SM_PCO_MAXLEN]; + uint8_t pco_len; + enum osmo_gprs_sm_pdp_addr_ietf_type pdp_addr_ietf_type; + struct osmo_sockaddr pdp_addr_v4; + struct osmo_sockaddr pdp_addr_v6; +}; + +struct osmobb_apn { + /* list of APNs inside MS */ + struct llist_head list; + /* back-pointer to MS */ + struct osmocom_ms *ms; + + bool started; + + struct { + /* Primary name */ + char *name; + /* name of the network device */ + char *dev_name; + /* netns name of the network device, NULL = default netns */ + char *dev_netns_name; + /* types supported address types on this APN */ + uint32_t apn_type_mask; + /* administratively shutdown (true) or not (false) */ + bool shutdown; + /* transmit G-PDU sequence numbers (true) or not (false) */ + bool tx_gpdu_seq; + } cfg; + struct osmo_tundev *tun; + struct apn_fsm_ctx fsm; + struct osmobb_pdp_ctx pdp; +}; + +struct osmobb_apn *apn_alloc(struct osmocom_ms *ms, const char *name); +void apn_free(struct osmobb_apn *apn); +int apn_start(struct osmobb_apn *apn); +int apn_stop(struct osmobb_apn *apn); + +#define LOGPAPN(level, apn, fmt, args...) \ + LOGP(DTUN, level, "APN(%s): " fmt, (apn)->cfg.name, ## args) + +#define LOGTUN(level, tun, fmt, args...) \ + LOGP(DTUN, level, "TUN(%s): " fmt, osmo_tundev_get_name(tun), ## args) diff --git a/src/host/layer23/include/osmocom/bb/common/apn_fsm.h b/src/host/layer23/include/osmocom/bb/common/apn_fsm.h new file mode 100644 index 00000000..890267c9 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/common/apn_fsm.h @@ -0,0 +1,29 @@ +#pragma once + +#include <osmocom/core/fsm.h> + +struct osmobb_apn; + +enum apn_fsm_states { + APN_ST_DISABLED, + APN_ST_INACTIVE, + APN_ST_ACTIVATING, + APN_ST_ACTIVE, +}; + +enum apn_fsm_events { + APN_EV_GPRS_ALLOWED, /* data: bool *allowed */ + APN_EV_GMM_ATTACHED, + APN_EV_GMM_DETACHED, + APN_EV_RX_SM_ACT_PDP_CTX_REJ, /* data: enum gsm48_gsm_cause *cause */ + APN_EV_RX_SM_ACT_PDP_CTX_ACC, + APN_EV_RX_SM_DEACT_PDP_CTX_ACC, +}; + +struct apn_fsm_ctx { + struct osmo_fsm_inst *fi; + struct osmobb_apn *apn; +}; + +int apn_fsm_ctx_init(struct apn_fsm_ctx *ctx, struct osmobb_apn *apn); +void apn_fsm_ctx_release(struct apn_fsm_ctx *ctx); diff --git a/src/host/layer23/include/osmocom/bb/common/gps.h b/src/host/layer23/include/osmocom/bb/common/gps.h index 58c0c536..e7ce915c 100644 --- a/src/host/layer23/include/osmocom/bb/common/gps.h +++ b/src/host/layer23/include/osmocom/bb/common/gps.h @@ -13,10 +13,6 @@ * 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. - * */ enum { diff --git a/src/host/layer23/include/osmocom/bb/common/l1ctl.h b/src/host/layer23/include/osmocom/bb/common/l1ctl.h index e4dbdedc..a74c9c37 100644 --- a/src/host/layer23/include/osmocom/bb/common/l1ctl.h +++ b/src/host/layer23/include/osmocom/bb/common/l1ctl.h @@ -2,6 +2,7 @@ #define osmocom_l1ctl_h #include <osmocom/core/msgb.h> +#include <osmocom/core/prim.h> #include <osmocom/bb/common/osmocom_data.h> struct osmocom_ms; @@ -20,15 +21,15 @@ int l1ctl_tx_crypto_req(struct osmocom_ms *ms, uint8_t chan_nr, uint8_t algo, uint8_t *key, uint8_t len); /* Transmit L1CTL_RACH_REQ */ -int l1ctl_tx_rach_req(struct osmocom_ms *ms, uint8_t ra, uint16_t offset, - uint8_t combined); +int l1ctl_tx_rach_req(struct osmocom_ms *ms, + uint8_t chan_nr, uint8_t link_id, + uint8_t ra, uint16_t offset, uint8_t combined, uint8_t uic); /* Transmit L1CTL_DM_EST_REQ */ -int l1ctl_tx_dm_est_req_h0(struct osmocom_ms *ms, uint16_t band_arfcn, - uint8_t chan_nr, uint8_t tsc, uint8_t tch_mode, uint8_t audio_mode); -int l1ctl_tx_dm_est_req_h1(struct osmocom_ms *ms, uint8_t maio, uint8_t hsn, - uint16_t *ma, uint8_t ma_len, uint8_t chan_nr, uint8_t tsc, - uint8_t tch_mode, uint8_t audio_mode); +int l1ctl_tx_dm_est_req_h0(struct osmocom_ms *ms, uint16_t band_arfcn, uint8_t chan_nr, uint8_t tsc, + uint8_t tch_mode, uint8_t audio_mode, uint8_t tch_flags); +int l1ctl_tx_dm_est_req_h1(struct osmocom_ms *ms, uint8_t maio, uint8_t hsn, uint16_t *ma, uint8_t ma_len, + uint8_t chan_nr, uint8_t tsc, uint8_t tch_mode, uint8_t audio_mode, uint8_t tch_flags); /* Transmit L1CTL_DM_FREQ_REQ */ int l1ctl_tx_dm_freq_req_h0(struct osmocom_ms *ms, uint16_t band_arfcn, @@ -48,8 +49,8 @@ int l1ctl_tx_fbsb_req(struct osmocom_ms *ms, uint16_t arfcn, int l1ctl_tx_ccch_mode_req(struct osmocom_ms *ms, uint8_t ccch_mode); /* Transmit TCH_MODE_REQ */ -int l1ctl_tx_tch_mode_req(struct osmocom_ms *ms, uint8_t tch_mode, - uint8_t audio_mode); +int l1ctl_tx_tch_mode_req(struct osmocom_ms *ms, uint8_t tch_mode, uint8_t audio_mode, uint8_t tch_flags, + uint8_t tch_loop_mode); /* Transmit ECHO_REQ */ int l1ctl_tx_echo_req(struct osmocom_ms *ms, unsigned int len); @@ -73,4 +74,17 @@ int l1ctl_ph_prim_cb(struct osmo_prim_hdr *oph, void *ctx); /* Transmit L1CTL_NEIGH_PM_REQ */ int l1ctl_tx_neigh_pm_req(struct osmocom_ms *ms, int num, uint16_t *arfcn); +/* Transmit L1CTL_GPRS_UL_BLOCK_REQ */ +int l1ctl_tx_gprs_ul_block_req(struct osmocom_ms *ms, uint32_t fn, uint8_t tn, + const uint8_t *data, size_t data_len); + +/* Transmit L1CTL_GPRS_UL_TBF_CFG_REQ */ +int l1ctl_tx_gprs_ul_tbf_cfg_req(struct osmocom_ms *ms, uint8_t tbf_ref, + uint8_t slotmask, uint32_t start_fn); + +/* Transmit L1CTL_GPRS_DL_TBF_CFG_REQ */ +int l1ctl_tx_gprs_dl_tbf_cfg_req(struct osmocom_ms *ms, uint8_t tbf_ref, + uint8_t slotmask, uint32_t start_fn, + uint8_t dl_tfi); + #endif diff --git a/src/host/layer23/include/osmocom/bb/common/l1l2_interface.h b/src/host/layer23/include/osmocom/bb/common/l1l2_interface.h index 41403d87..9cb993c8 100644 --- a/src/host/layer23/include/osmocom/bb/common/l1l2_interface.h +++ b/src/host/layer23/include/osmocom/bb/common/l1l2_interface.h @@ -1,6 +1,10 @@ #ifndef _L1L2_INTERFACE_H #define _L1L2_INTERFACE_H +#include <osmocom/core/msgb.h> + +#define L2_DEFAULT_SOCKET_PATH "/tmp/osmocom_l2" + int layer2_open(struct osmocom_ms *ms, const char *socket_path); int layer2_close(struct osmocom_ms *ms); int osmo_send_l1(struct osmocom_ms *ms, struct msgb *msg); diff --git a/src/host/layer23/include/osmocom/bb/common/l23_app.h b/src/host/layer23/include/osmocom/bb/common/l23_app.h index 0b9994c3..6cf0b7e9 100644 --- a/src/host/layer23/include/osmocom/bb/common/l23_app.h +++ b/src/host/layer23/include/osmocom/bb/common/l23_app.h @@ -1,36 +1,77 @@ #ifndef _L23_APP_H #define _L23_APP_H +#include <osmocom/core/tun.h> +#include <osmocom/core/gsmtap.h> + struct option; +struct vty_app_info; /* Options supported by the l23 app */ enum { - L23_OPT_SAP = 1, - L23_OPT_ARFCN = 2, - L23_OPT_TAP = 4, - L23_OPT_VTY = 8, - L23_OPT_DBG = 16, - L23_OPT_VTYIP = 32, + L23_OPT_SAP = 1 << 0, + L23_OPT_ARFCN = 1 << 1, + L23_OPT_TAP = 1 << 2, + L23_OPT_VTY = 1 << 3, + L23_OPT_DBG = 1 << 4, }; -/* initialization, called once when starting the app, before entering - * select loop */ -extern int l23_app_init(struct osmocom_ms *ms); -extern int (*l23_app_work) (struct osmocom_ms *ms); -extern int (*l23_app_exit) (struct osmocom_ms *ms); +/* see (struct l23_global_config)->gsmtap.categ_gprs_mask */ +enum l23_gsmtap_gprs_category { + L23_GSMTAP_GPRS_C_DL_UNKNOWN = 0, /* unknown or undecodable downlink blocks */ + L23_GSMTAP_GPRS_C_DL_DUMMY = 1, /* downlink dummy blocks */ + L23_GSMTAP_GPRS_C_DL_CTRL = 2, /* downlink control blocks */ + L23_GSMTAP_GPRS_C_DL_DATA_GPRS = 3, /* downlink GPRS data blocks */ + L23_GSMTAP_GPRS_C_DL_DATA_EGPRS = 4, /* downlink EGPRS data blocks */ + + L23_GSMTAP_GPRS_C_UL_UNKNOWN = 5, /* unknown or undecodable uplink blocks */ + L23_GSMTAP_GPRS_C_UL_DUMMY = 6, /* uplink dummy blocks */ + L23_GSMTAP_GPRS_C_UL_CTRL = 7, /* uplink control blocks */ + L23_GSMTAP_GPRS_C_UL_DATA_GPRS = 8, /* uplink GPRS data blocks */ + L23_GSMTAP_GPRS_C_UL_DATA_EGPRS = 9, /* uplink EGPRS data blocks */ +}; + +struct l23_global_config { + struct { + char *remote_host; + char *local_host; + uint32_t lchan_mask; /* see l23_gsmtap_gprs_category */ + uint32_t lchan_acch_mask; /* see l23_gsmtap_gprs_category */ + bool lchan_acch; + uint32_t categ_gprs_mask; + struct gsmtap_inst *inst; + } gsmtap; +}; +extern struct l23_global_config l23_cfg; + +extern void *l23_ctx; + +/* initialization, called once when starting the app, before reading VTY config */ +extern int l23_app_init(void); + +/* Start work after reading VTY config and starting layer23 components, + * immediately before entering main select loop */ +extern int (*l23_app_start)(void); + +extern int (*l23_app_work)(void); +extern int (*l23_app_exit)(void); /* configuration options */ struct l23_app_info { const char *copyright; const char *contribution; + struct vty_app_info *vty_info; /* L23_OPT_VTY */ char *getopt_string; - int (*cfg_supported)(); + uint32_t opt_supported; /* mask of L23_OPT_* */ int (*cfg_print_help)(); int (*cfg_getopt_opt)(struct option **options); int (*cfg_handle_opt)(int c,const char *optarg); + int (*vty_init)(void); + osmo_tundev_data_ind_cb_t tun_data_ind_cb; }; -extern struct l23_app_info *l23_app_info(); +/* all l23 apps must define this structure */ +extern const struct l23_app_info l23_app_info; #endif /* _L23_APP_H */ diff --git a/src/host/layer23/include/osmocom/bb/common/logging.h b/src/host/layer23/include/osmocom/bb/common/logging.h index bf6e6aa0..3920d2e0 100644 --- a/src/host/layer23/include/osmocom/bb/common/logging.h +++ b/src/host/layer23/include/osmocom/bb/common/logging.h @@ -12,6 +12,8 @@ enum { DNB, DMM, DCC, + DGCC, + DBCC, DSS, DSMS, DMNCC, @@ -25,6 +27,14 @@ enum { DMOB, DPRIM, DLUA, + DGAPK, + DCSD, + DTUN, + DRLCMAC, + DLLC, + DSNDCP, + DGMM, + DSM }; extern const struct log_info log_info; diff --git a/src/host/layer23/include/osmocom/bb/common/ms.h b/src/host/layer23/include/osmocom/bb/common/ms.h new file mode 100644 index 00000000..36d3e3ba --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/common/ms.h @@ -0,0 +1,116 @@ +/* Mobile Station */ +#pragma once + +#include <osmocom/core/select.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/core/write_queue.h> + +#include <osmocom/gsm/lapdm.h> +#include <osmocom/bb/common/settings.h> +#include <osmocom/bb/common/subscriber.h> +#include <osmocom/bb/common/support.h> +#include <osmocom/bb/common/sap_interface.h> +#include <osmocom/bb/common/sap_proto.h> +#include <osmocom/bb/mobile/gsm48_rr.h> +#include <osmocom/bb/common/sysinfo.h> +#include <osmocom/bb/mobile/gsm322.h> +#include <osmocom/bb/mobile/gsm48_mm.h> +#include <osmocom/bb/mobile/gsm48_cc.h> +#include <osmocom/bb/mobile/mncc_sock.h> +#include <osmocom/bb/common/sim.h> +#include <osmocom/bb/common/l1ctl.h> + +struct osmobb_ms_gmm_layer { + uint8_t ac_ref_nr; + uint8_t key_seq; + uint8_t rand[16]; + uint32_t tlli; +}; + +struct osmosap_entity { + struct osmo_fsm_inst *fi; + uint16_t max_msg_size; + + /* Current state of remote SIM card */ + enum sap_card_status_type card_status; + + /* Optional SAP message call-back */ + sap_msg_cb_t sap_msg_cb; + /* Optional response call-back */ + sap_rsp_cb_t sap_rsp_cb; +}; + +struct osmol1_entity { + int (*l1_traffic_ind)(struct osmocom_ms *ms, struct msgb *msg); + int (*l1_gprs_ul_block_cnf)(struct osmocom_ms *ms, struct msgb *msg); + int (*l1_gprs_dl_block_ind)(struct osmocom_ms *ms, struct msgb *msg); + int (*l1_gprs_rts_ind)(struct osmocom_ms *ms, struct msgb *msg); +}; + +struct osmomncc_entity { + int (*mncc_recv)(struct osmocom_ms *ms, int msg_type, void *arg); + struct mncc_sock_state *sock_state; + uint32_t ref; +}; + +/* RX measurement statistics */ +struct rx_meas_stat { + uint32_t last_fn; + + /* cumulated values of current cell from SACCH dl */ + uint32_t frames; + uint32_t snr; + uint32_t berr; + uint32_t rxlev; + + /* counters loss criterion */ + int16_t dsc, ds_fail; + int16_t s, rl_fail; +}; + +enum osmobb_ms_shutdown_st { + MS_SHUTDOWN_NONE = 0, + MS_SHUTDOWN_IMSI_DETACH = 1, + MS_SHUTDOWN_WAIT_RESET = 2, + MS_SHUTDOWN_COMPL = 3, +}; + +struct osmocom_ms { + struct llist_head entity; + char *name; + struct osmo_wqueue l2_wq, sap_wq; + uint16_t test_arfcn; + struct osmol1_entity l1_entity; + + bool started, deleting; + enum osmobb_ms_shutdown_st shutdown; + struct gsm_support support; + struct gsm_settings settings; + struct gsm_subscriber subscr; + struct gsm_sim sim; + struct lapdm_channel lapdm_channel; + struct osmosap_entity sap_entity; + struct rx_meas_stat meas; + struct gsm48_rrlayer rrlayer; + struct gsm322_plmn plmn; + struct gsm322_cellsel cellsel; + struct gsm48_mmlayer mmlayer; + struct gsm48_cclayer cclayer; + struct osmomncc_entity mncc_entity; + struct llist_head trans_list; + + /* GPRS */ + struct gprs_settings gprs; + struct osmobb_ms_gmm_layer gmmlayer; + struct osmo_fsm_inst *grr_fi; + + struct tch_state *tch_state; + + void *lua_state; + int lua_cb_ref; + char *lua_script; +}; + +struct osmocom_ms *osmocom_ms_alloc(void *ctx, const char *name); + +extern uint16_t cfg_test_arfcn; diff --git a/src/host/layer23/include/osmocom/bb/common/networks.h b/src/host/layer23/include/osmocom/bb/common/networks.h index d34f3162..408e17d0 100644 --- a/src/host/layer23/include/osmocom/bb/common/networks.h +++ b/src/host/layer23/include/osmocom/bb/common/networks.h @@ -1,24 +1,27 @@ #ifndef _NETWORKS_H #define _NETWORKS_H +#include <osmocom/gsm/gsm23003.h> -#define GSM_INPUT_INVALID 0xffff - +/* Instead of handling numerical MCC and MNC, they stored and handled + * hexadecimal. This makes it possible + * to correctly handle 2 and 3 digits MNC. Internally 2 digit MNCs are stored + * as 0xXXf, and 3 digits MNC are stored as 0xXXX, where X is the digit 0..9. +*/ struct gsm_networks { - uint16_t mcc; - int16_t mnc; + uint16_t mcc_hex; + int16_t mnc_hex; const char *name; }; int gsm_match_mcc(uint16_t mcc, char *imsi); -int gsm_match_mnc(uint16_t mcc, uint16_t mnc, char *imsi); -const char *gsm_print_mcc(uint16_t mcc); -const char *gsm_print_mnc(uint16_t mcc); +int gsm_match_mnc(uint16_t mcc, uint16_t mnc, bool mnc_3_digits, char *imsi); const char *gsm_get_mcc(uint16_t mcc); -const char *gsm_get_mnc(uint16_t mcc, uint16_t mnc); +const char *gsm_get_mnc(const struct osmo_plmn_id *plmn); const char *gsm_imsi_mcc(char *imsi); const char *gsm_imsi_mnc(char *imsi); -const uint16_t gsm_input_mcc(char *string); -const uint16_t gsm_input_mnc(char *string); + +uint16_t gsm_mcc_to_hex(uint16_t mcc); +uint16_t gsm_mnc_to_hex(uint16_t mnc, bool mnc_3_digits); #endif /* _NETWORKS_H */ diff --git a/src/host/layer23/include/osmocom/bb/common/osmocom_data.h b/src/host/layer23/include/osmocom/bb/common/osmocom_data.h index 14e594cb..935c02b2 100644 --- a/src/host/layer23/include/osmocom/bb/common/osmocom_data.h +++ b/src/host/layer23/include/osmocom/bb/common/osmocom_data.h @@ -1,107 +1,17 @@ -#ifndef osmocom_data_h -#define osmocom_data_h +#pragma once -#include <osmocom/core/select.h> -#include <osmocom/gsm/gsm_utils.h> -#include <osmocom/core/write_queue.h> +#include <stdint.h> +#include <stdbool.h> struct osmocom_ms; - - /* FIXME no 'mobile' specific stuff should be here */ -#include <osmocom/bb/mobile/support.h> -#include <osmocom/bb/mobile/settings.h> -#include <osmocom/bb/mobile/subscriber.h> -#include <osmocom/gsm/lapdm.h> -#include <osmocom/bb/common/sap_interface.h> -#include <osmocom/bb/common/sap_proto.h> -#include <osmocom/bb/mobile/gsm48_rr.h> -#include <osmocom/bb/common/sysinfo.h> -#include <osmocom/bb/mobile/gsm322.h> -#include <osmocom/bb/mobile/gsm48_mm.h> -#include <osmocom/bb/mobile/gsm48_cc.h> -#include <osmocom/bb/mobile/mncc_sock.h> -#include <osmocom/bb/common/sim.h> -#include <osmocom/bb/common/l1ctl.h> - -struct osmosap_entity { - struct osmo_fsm_inst *fi; - uint16_t max_msg_size; - - /* Current state of remote SIM card */ - enum sap_card_status_type card_status; - - /* Optional SAP message call-back */ - sap_msg_cb_t sap_msg_cb; - /* Optional response call-back */ - sap_rsp_cb_t sap_rsp_cb; -}; - -struct osmol1_entity { - int (*l1_traffic_ind)(struct osmocom_ms *ms, struct msgb *msg); -}; - -struct osmomncc_entity { - int (*mncc_recv)(struct osmocom_ms *ms, int msg_type, void *arg); - struct mncc_sock_state *sock_state; - uint32_t ref; -}; - - -/* RX measurement statistics */ -struct rx_meas_stat { - uint32_t last_fn; - - /* cumulated values of current cell from SACCH dl */ - uint32_t frames; - uint32_t snr; - uint32_t berr; - uint32_t rxlev; - - /* counters loss criterion */ - int16_t dsc, ds_fail; - int16_t s, rl_fail; -}; - -enum { - MS_SHUTDOWN_NONE = 0, - MS_SHUTDOWN_IMSI_DETACH = 1, - MS_SHUTDOWN_WAIT_RESET = 2, - MS_SHUTDOWN_COMPL = 3, -}; - -/* One Mobilestation for osmocom */ -struct osmocom_ms { - struct llist_head entity; - char *name; - struct osmo_wqueue l2_wq, sap_wq; - uint16_t test_arfcn; - struct osmol1_entity l1_entity; - - bool started, deleting; - uint8_t shutdown; - struct gsm_support support; - struct gsm_settings settings; - struct gsm_subscriber subscr; - struct gsm_sim sim; - struct lapdm_channel lapdm_channel; - struct osmosap_entity sap_entity; - struct rx_meas_stat meas; - struct gsm48_rrlayer rrlayer; - struct gsm322_plmn plmn; - struct gsm322_cellsel cellsel; - struct gsm48_mmlayer mmlayer; - struct gsm48_cclayer cclayer; - struct osmomncc_entity mncc_entity; - struct llist_head trans_list; - - void *lua_state; - int lua_cb_ref; - char *lua_script; -}; +struct vty; enum osmobb_sig_subsys { SS_L1CTL, SS_GLOBAL, + SS_L23_VTY, + SS_L23_SUBSCR, + SS_L23_TRANS, }; enum osmobb_l1ctl_sig { @@ -120,6 +30,43 @@ enum osmobb_global_sig { S_GLOBAL_SHUTDOWN, }; +enum osmobb_l23_vty_sig { + S_L23_VTY_MS_START, + S_L23_VTY_MS_STOP, +}; + +enum osmobb_l23_subscriber { + S_L23_SUBSCR_SIM_ATTACHED, + S_L23_SUBSCR_SIM_DETACHED, + S_L23_SUBSCR_SIM_AUTH_RESP, +}; + +enum osmobb_l23_trans_sig { + S_L23_CC_TRANS_ALLOC, /* new transaction has been allocated */ + S_L23_CC_TRANS_FREE, /* transaction is about to be free()d */ + S_L23_CC_TRANS_STATE_CHG, /* transaction state has been changed */ +}; + +struct osmobb_l23_vty_sig_data { + struct vty *vty; + union { + struct { + struct osmocom_ms *ms; + int rc; /* CMD_SUCCESS/CMD_WARNING */ + } ms_start; + struct { + struct osmocom_ms *ms; + bool force; + int rc; /* CMD_SUCCESS/CMD_WARNING */ + } ms_stop; + }; +}; + +struct osmobb_l23_subscr_sim_auth_resp_sig_data { + struct osmocom_ms *ms; + uint8_t sres[4]; +}; + struct osmobb_fbsb_res { struct osmocom_ms *ms; int8_t snr; @@ -142,6 +89,7 @@ struct osmobb_tch_mode_conf { struct osmocom_ms *ms; uint8_t tch_mode; uint8_t audio_mode; + uint8_t tch_flags; }; struct osmobb_neigh_pm_ind { @@ -149,5 +97,3 @@ struct osmobb_neigh_pm_ind { uint16_t band_arfcn; uint8_t rx_lev; }; - -#endif diff --git a/src/host/layer23/include/osmocom/bb/common/settings.h b/src/host/layer23/include/osmocom/bb/common/settings.h new file mode 100644 index 00000000..1d3e53df --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/common/settings.h @@ -0,0 +1,348 @@ +#ifndef _settings_h +#define _settings_h + +#include <stdbool.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/gsm/protocol/gsm_23_003.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/gsm23003.h> +#include <osmocom/gsm/gsm48.h> + +#include <osmocom/bb/common/sim.h> + +struct osmocom_ms; +struct osmobb_apn; + +#define MOB_C7_DEFLT_ANY_TIMEOUT 30 + +/* CC (Call Control) message handling entity */ +enum mncc_handler_t { + /* Built-in mobile's MNCC */ + MNCC_HANDLER_INTERNAL, + /* External MNCC application via UNIX-socket */ + MNCC_HANDLER_EXTERNAL, + /* No call support */ + MNCC_HANDLER_DUMMY, +}; + +/* TCH I/O handler for voice calls */ +enum tch_voice_io_handler { + /* No handler, drop frames */ + TCH_VOICE_IOH_NONE = 0, + /* libosmo-gapk based handler */ + TCH_VOICE_IOH_GAPK, + /* L1 PHY (e.g. Calypso DSP) */ + TCH_VOICE_IOH_L1PHY, + /* External MNCC app (via MNCC socket) */ + TCH_VOICE_IOH_MNCC_SOCK, + /* Return to sender */ + TCH_VOICE_IOH_LOOPBACK, +}; + +extern const struct value_string tch_voice_io_handler_names[]; +static inline const char *tch_voice_io_handler_name(enum tch_voice_io_handler val) +{ return get_value_string(tch_voice_io_handler_names, val); } + +/* TCH I/O handler for data calls */ +enum tch_data_io_handler { + /* No handler, drop frames */ + TCH_DATA_IOH_NONE = 0, + /* UNIX socket */ + TCH_DATA_IOH_UNIX_SOCK, + /* Return to sender */ + TCH_DATA_IOH_LOOPBACK, +}; + +extern const struct value_string tch_data_io_handler_names[]; +static inline const char *tch_data_io_handler_name(enum tch_data_io_handler val) +{ return get_value_string(tch_data_io_handler_names, val); } + +/* TCH I/O format for voice calls */ +enum tch_voice_io_format { + /* RFC3551 for FR/EFR, RFC5993 for HR, RFC4867 for AMR */ + TCH_VOICE_IOF_RTP, + /* Texas Instruments format, used by Calypso based phones (e.g. Motorola C1xx) */ + TCH_VOICE_IOF_TI, +}; + +extern const struct value_string tch_voice_io_format_names[]; +static inline const char *tch_voice_io_format_name(enum tch_voice_io_format val) +{ return get_value_string(tch_voice_io_format_names, val); } + +/* TCH I/O format for data calls */ +enum tch_data_io_format { + /* Osmocom format, used by trxcon and virtphy */ + TCH_DATA_IOF_OSMO, + /* Texas Instruments format, used by Calypso based phones (e.g. Motorola C1xx) */ + TCH_DATA_IOF_TI, +}; + +extern const struct value_string tch_data_io_format_names[]; +static inline const char *tch_data_io_format_name(enum tch_data_io_format val) +{ return get_value_string(tch_data_io_format_names, val); } + +struct tch_voice_settings { + enum tch_voice_io_handler io_handler; + enum tch_voice_io_format io_format; + char alsa_output_dev[128]; + char alsa_input_dev[128]; +}; + +struct tch_data_settings { + enum tch_data_io_handler io_handler; + enum tch_data_io_format io_format; + char unix_socket_path[128]; +}; + +struct test_sim_settings { + char imsi[OSMO_IMSI_BUF_SIZE]; + uint32_t tmsi; + uint8_t ki_type; + uint8_t ki[16]; /* 128 bit max */ + bool barr; + bool rplmn_valid; + struct osmo_plmn_id rplmn; + uint16_t lac; + bool imsi_attached; + bool always_search_hplmn; + struct { + bool valid; + uint32_t ptmsi; /* invalid tmsi: GSM_RESERVED_TMSI */ + uint32_t ptmsi_sig; /* P-TMSI signature, 3 bytes */ + struct gprs_ra_id rai; + enum gsm1111_ef_locigprs_rau_status gu_state; /* GU, TS 24.008 */ + bool imsi_attached; + } locigprs; +}; + +/* Data (CSD) call type and rate, values like in the '<speed>' part of 'AT+CBST'. + * See 3GPP TS 27.007, section 6.7 "Select bearer service type +CBST". */ +enum data_call_type_rate { + DATA_CALL_TR_AUTO = 0, + DATA_CALL_TR_V21_300 = 1, + DATA_CALL_TR_V22_1200 = 2, + DATA_CALL_TR_V23_1200_75 = 3, + DATA_CALL_TR_V22bis_2400 = 4, + DATA_CALL_TR_V26ter_2400 = 5, + DATA_CALL_TR_V32_4800 = 6, + DATA_CALL_TR_V32_9600 = 7, + DATA_CALL_TR_V34_9600 = 12, + DATA_CALL_TR_V34_14400 = 14, + DATA_CALL_TR_V34_19200 = 15, + DATA_CALL_TR_V34_28800 = 16, + DATA_CALL_TR_V34_33600 = 17, + DATA_CALL_TR_V120_1200 = 34, + DATA_CALL_TR_V120_2400 = 36, + DATA_CALL_TR_V120_4800 = 38, + DATA_CALL_TR_V120_9600 = 39, + DATA_CALL_TR_V120_14400 = 43, + DATA_CALL_TR_V120_19200 = 47, + DATA_CALL_TR_V120_28800 = 48, + DATA_CALL_TR_V120_38400 = 49, + DATA_CALL_TR_V120_48000 = 50, + DATA_CALL_TR_V120_56000 = 51, + DATA_CALL_TR_V110_300 = 65, + DATA_CALL_TR_V110_1200 = 66, + DATA_CALL_TR_V110_2400 = 68, + DATA_CALL_TR_V110_4800 = 70, + DATA_CALL_TR_V110_9600 = 71, + DATA_CALL_TR_V110_14400 = 75, + DATA_CALL_TR_V110_19200 = 79, + DATA_CALL_TR_V110_28800 = 80, + DATA_CALL_TR_V110_38400 = 81, + DATA_CALL_TR_V110_48000 = 82, + DATA_CALL_TR_V110_56000 = 83, + DATA_CALL_TR_V110_64000 = 84, + DATA_CALL_TR_BTR_56000 = 115, + DATA_CALL_TR_BTR_64000 = 116, + DATA_CALL_TR_PIAFS32k_32000 = 120, + DATA_CALL_TR_PIAFS64k_64000 = 121, + DATA_CALL_TR_MMEDIA_28800 = 130, + DATA_CALL_TR_MMEDIA_32000 = 131, + DATA_CALL_TR_MMEDIA_33600 = 132, + DATA_CALL_TR_MMEDIA_56000 = 133, + DATA_CALL_TR_MMEDIA_64000 = 134, +}; + +/* Data (CSD) call parameters */ +struct data_call_params { + enum data_call_type_rate type_rate; + enum gsm48_bcap_transp transp; + + /* async call parameters */ + bool is_async; + unsigned int nr_stop_bits; + unsigned int nr_data_bits; + enum gsm48_bcap_parity parity; +}; + +struct gsm_settings { + char layer2_socket_path[128]; + char sap_socket_path[128]; + char mncc_socket_path[128]; + + /* MNCC handler */ + enum mncc_handler_t mncc_handler; + + /* TCH settings */ + struct tch_voice_settings tch_voice; + struct tch_data_settings tch_data; + + /* IMEI */ + char imei[GSM23003_IMEI_NUM_DIGITS + 1]; + char imeisv[GSM23003_IMEISV_NUM_DIGITS + 1]; + char imei_random; + + /* network search */ + int plmn_mode; /* PLMN_MODE_* */ + + /* SIM */ + int sim_type; /* enum gsm_subscriber_sim_type, + * selects card on power on */ + char emergency_imsi[OSMO_IMSI_BUF_SIZE]; + + /* SMS */ + char sms_sca[22]; + bool store_sms; + + /* test card simulator settings */ + struct test_sim_settings test_sim; + + /* call related settings */ + uint8_t cw; /* set if call-waiting is allowed */ + uint8_t auto_answer; + uint8_t clip, clir; + uint8_t half, half_prefer; + + /* changing default behavior */ + uint8_t alter_tx_power; + uint8_t alter_tx_power_value; + int8_t alter_delay; + uint8_t stick; + uint16_t stick_arfcn; + uint8_t skip_max_per_band; + uint8_t no_lupd; + uint8_t no_neighbour; + + /* supported by configuration */ + uint8_t cc_dtmf; + uint8_t sms_ptp; + uint8_t a5_1; + uint8_t a5_2; + uint8_t a5_3; + uint8_t a5_4; + uint8_t a5_5; + uint8_t a5_6; + uint8_t a5_7; + uint8_t p_gsm; + uint8_t e_gsm; + uint8_t r_gsm; + uint8_t dcs; + uint8_t gsm_850; + uint8_t pcs; + uint8_t gsm_480; + uint8_t gsm_450; + uint8_t class_900; + uint8_t class_dcs; + uint8_t class_850; + uint8_t class_pcs; + uint8_t class_400; + uint8_t freq_map[128+38]; + uint8_t full_v1; + uint8_t full_v2; + uint8_t full_v3; + uint8_t half_v1; + uint8_t half_v3; + uint8_t ch_cap; /* channel capability */ + int8_t min_rxlev_dbm; /* min dBm to access */ + + /* CSD modes */ + bool csd_tch_f144; + bool csd_tch_f96; + bool csd_tch_f48; + bool csd_tch_h48; + bool csd_tch_f24; + bool csd_tch_h24; + + /* support for ASCI */ + bool vgcs; /* support of VGCS */ + bool vbs; /* support of VBS */ + + /* radio */ + uint16_t dsc_max; + uint8_t force_rekey; + + /* dialing */ + struct llist_head abbrev; + + /* EDGE / UMTS / CDMA */ + uint8_t edge_ms_sup; + uint8_t edge_psk_sup; + uint8_t edge_psk_uplink; + uint8_t class_900_edge; + uint8_t class_dcs_pcs_edge; + uint8_t umts_fdd; + uint8_t umts_tdd; + uint8_t cdma_2000; + uint8_t dtm; + uint8_t class_dtm; + uint8_t dtm_mac; + uint8_t dtm_egprs; + + /* Timeout for GSM 03.22 C7 state */ + uint8_t any_timeout; + + /* ASCI settings */ + bool uplink_release_local; + bool asci_allow_any; + + /* call parameters */ + struct { + struct data_call_params data; + } call_params; +}; + +struct gsm_settings_abbrev { + struct llist_head list; + char abbrev[4]; + char number[32]; + char name[32]; +}; + +int gsm_settings_arfcn(struct osmocom_ms *ms); +int gsm_settings_init(struct osmocom_ms *ms); +int gsm_settings_exit(struct osmocom_ms *ms); +char *gsm_check_imei(const char *imei, const char *sv); +int gsm_random_imei(struct gsm_settings *set); + +struct gprs_settings { + struct llist_head apn_list; + + /* RFC1144 TCP/IP header compression */ + struct { + int active; + int passive; + int s01; + } pcomp_rfc1144; + + /* V.42vis data compression */ + struct { + int active; + int passive; + int p0; + int p1; + int p2; + } dcomp_v42bis; +}; + +int gprs_settings_init(struct osmocom_ms *ms); +int gprs_settings_fi(struct osmocom_ms *ms); +struct osmobb_apn *ms_find_apn_by_name(struct osmocom_ms *ms, const char *apn_name); +int ms_dispatch_all_apn(struct osmocom_ms *ms, uint32_t event, void *data); + +extern char *layer2_socket_path; + +#endif /* _settings_h */ + diff --git a/src/host/layer23/include/osmocom/bb/common/sim.h b/src/host/layer23/include/osmocom/bb/common/sim.h index 8b1f830c..8aae1fc8 100644 --- a/src/host/layer23/include/osmocom/bb/common/sim.h +++ b/src/host/layer23/include/osmocom/bb/common/sim.h @@ -13,12 +13,14 @@ * 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. - * */ +#pragma once + +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/core/endian.h> + +struct osmocom_ms; /* 9.2 commands */ #define GSM1111_CLASS_GSM 0xa0 @@ -212,6 +214,7 @@ struct gsm1111_response_mfdf { } __attribute__ ((packed)); struct gsm1111_response_mfdf_gsm { +#if OSMO_IS_LITTLE_ENDIAN uint8_t file_char; uint8_t num_df; uint8_t num_ef; @@ -230,10 +233,24 @@ struct gsm1111_response_mfdf_gsm { rfu5:3, unblk2_init:1; uint8_t more_data[0]; +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ + uint8_t file_char; + uint8_t num_df; + uint8_t num_ef; + uint8_t num_codes; + uint8_t rfu1; + uint8_t chv1_init:1, rfu2:3, chv1_remain:4; + uint8_t unblk1_init:1, rfu3:3, unblk1_remain:4; + uint8_t chv2_init:1, rfu4:3, chv2_remain:4; + uint8_t unblk2_init:1, rfu5:3, unblk2_remain:4; + uint8_t more_data[0]; +#endif } __attribute__ ((packed)); /* Section 9.2.1 (response to selecting EF) */ struct gsm1111_response_ef { +#if OSMO_IS_LITTLE_ENDIAN uint16_t rfu1; uint16_t file_size; uint16_t file_id; @@ -251,6 +268,20 @@ struct gsm1111_response_ef { rfu4:5; uint8_t length; uint8_t structure; +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ + uint16_t rfu1; + uint16_t file_size; + uint16_t file_id; + uint8_t tof; + uint8_t inc_allowed; + uint8_t acc_read:4, acc_update:4; + uint8_t acc_inc:4, rfu2:4; + uint8_t acc_reha:4, acc_inval:4; + uint8_t rfu4:5, ru_inval:1, rfu3:1, not_inval:1; + uint8_t length; + uint8_t structure; +#endif } __attribute__ ((packed)); /* Section 10.3.17 */ @@ -258,9 +289,34 @@ struct gsm1111_ef_loci { uint32_t tmsi; struct gsm48_loc_area_id lai; uint8_t tmsi_time; - uint8_t lupd_status; + uint8_t lupd_status; /* enum gsm1111_ef_loci_lupd_status */ } __attribute__ ((packed)); +enum gsm1111_ef_loci_lupd_status { + GSM1111_EF_LOCI_LUPD_ST_UPDATED = 0, + GSM1111_EF_LOCI_LUPD_ST_NOT_UPDATED = 1, + GSM1111_EF_LOCI_LUPD_ST_PLMN_NOT_ALLOWED = 2, + GSM1111_EF_LOCI_LUPD_ST_LA_NOT_ALLOWED = 3, + GSM1111_EF_LOCI_LUPD_ST_RESERVED = 7, +}; + +/* Section 10.3.33 */ +struct gsm1111_ef_locigprs { + uint32_t ptmsi; + uint16_t ptmsi_sig_hi; + uint8_t ptmsi_sig_lo; + struct gsm48_ra_id rai; + uint8_t rau_status; /* enum gsm1111_ef_locigprs_rau_status */ +} __attribute__ ((packed)); + +enum gsm1111_ef_locigprs_rau_status { + GSM1111_EF_LOCIGPRS_RAU_ST_UPDATED = 0, + GSM1111_EF_LOCIGPRS_RAU_ST_NOT_UPDATED = 1, + GSM1111_EF_LOCIGPRS_RAU_ST_PLMN_NOT_ALLOWED = 2, + GSM1111_EF_LOCIGPRS_RAU_ST_RA_NOT_ALLOWED = 3, + GSM1111_EF_LOCIGPRS_RAU_ST_RESERVED = 7, +}; + /* Section 10.5.1 */ struct gsm1111_ef_adn { uint8_t len_bcd; diff --git a/src/host/layer23/include/osmocom/bb/mobile/subscriber.h b/src/host/layer23/include/osmocom/bb/common/subscriber.h index 698b0fdc..d36b4c6c 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/subscriber.h +++ b/src/host/layer23/include/osmocom/bb/common/subscriber.h @@ -1,29 +1,54 @@ #ifndef _SUBSCRIBER_H #define _SUBSCRIBER_H +#include <stdbool.h> + +#include <osmocom/core/utils.h> +#include <osmocom/gsm/protocol/gsm_23_003.h> +#include <osmocom/gsm/gsm23003.h> +#include <osmocom/gsm/gsm48.h> + /* GSM 04.08 4.1.2.2 SIM update status */ -#define GSM_SIM_U0_NULL 0 -#define GSM_SIM_U1_UPDATED 1 -#define GSM_SIM_U2_NOT_UPDATED 2 -#define GSM_SIM_U3_ROAMING_NA 3 +enum gsm_sub_sim_ustate { + GSM_SIM_U0_NULL, + GSM_SIM_U1_UPDATED, + GSM_SIM_U2_NOT_UPDATED, + GSM_SIM_U3_ROAMING_NA, +}; +extern const struct value_string gsm_sub_sim_ustate_names[]; +static inline const char *gsm_sub_sim_ustate_name(enum gsm_sub_sim_ustate val) +{ + return get_value_string(gsm_sub_sim_ustate_names, val); +} + +/* 3GPP TS 24.008 4.1.3.2 GPRS update status */ +enum gsm_sub_sim_gustate { + GSM_SIM_GU0_NULL, + GSM_SIM_GU1_UPDATED, + GSM_SIM_GU2_NOT_UPDATED, + GSM_SIM_GU3_ROAMING_NA, +}; +extern const struct value_string gsm_sub_sim_gustate_names[]; +static inline const char *gsm_sub_sim_gustate_name(enum gsm_sub_sim_gustate val) +{ + return get_value_string(gsm_sub_sim_gustate_names, val); +} struct gsm_sub_plmn_list { struct llist_head entry; - uint16_t mcc, mnc; + struct osmo_plmn_id plmn; }; struct gsm_sub_plmn_na { struct llist_head entry; - uint16_t mcc, mnc; + struct osmo_plmn_id plmn; uint8_t cause; }; -#define GSM_IMSI_LENGTH 16 - #define GSM_SIM_IS_READER(type) \ (type == GSM_SIM_TYPE_L1PHY || type == GSM_SIM_TYPE_SAP) -enum { +enum gsm_subscriber_sim_type { GSM_SIM_TYPE_NONE = 0, GSM_SIM_TYPE_L1PHY, GSM_SIM_TYPE_TEST, @@ -34,19 +59,19 @@ struct gsm_subscriber { struct osmocom_ms *ms; /* status */ - uint8_t sim_type; /* type of sim */ - uint8_t sim_valid; /* sim inserted and valid */ - uint8_t ustate; /* update status */ - uint8_t imsi_attached; /* attached state */ + enum gsm_subscriber_sim_type sim_type; /* type of sim */ + bool sim_valid; /* sim inserted and valid */ + enum gsm_sub_sim_ustate ustate; /* update status */ + bool imsi_attached; /* attached state */ /* IMSI & co */ - char imsi[GSM_IMSI_LENGTH]; + char imsi[OSMO_IMSI_BUF_SIZE]; char msisdn[31]; /* may include access codes */ char iccid[21]; /* 20 + termination */ /* TMSI / LAI */ - uint32_t tmsi; /* invalid tmsi: 0xffffffff */ - uint16_t mcc, mnc, lac; /* invalid lac: 0x0000 */ + uint32_t tmsi; /* invalid tmsi: GSM_RESERVED_TMSI */ + struct osmo_location_area_id lai; /* invalid lac: 0x0000 */ /* key */ @@ -59,7 +84,7 @@ struct gsm_subscriber { uint8_t t6m_hplmn; /* timer for hplmn search */ /* special things */ - uint8_t always_search_hplmn; + bool always_search_hplmn; /* search hplmn in other countries also (for test cards) */ uint8_t any_timeout; /* timer to restart 'any cell selection' */ @@ -67,16 +92,16 @@ struct gsm_subscriber { char sim_spn[17]; /* name of service privider */ /* PLMN last registered */ - uint8_t plmn_valid; - uint16_t plmn_mcc, plmn_mnc; + bool plmn_valid; + struct osmo_plmn_id plmn; /* our access */ - uint8_t acc_barr; /* if we may access, if cell barred */ + bool acc_barr; /* if we may access, if cell barred */ uint16_t acc_class; /* bitmask of what we may access */ /* talk to SIM */ uint8_t sim_state; - uint8_t sim_pin_required; /* state: wait for PIN */ + bool sim_pin_required; /* state: wait for PIN */ uint8_t sim_file_index; uint32_t sim_handle_query; uint32_t sim_handle_update; @@ -84,35 +109,38 @@ struct gsm_subscriber { /* SMS */ char sms_sca[22]; + + struct { + uint8_t gu_state; /* GU, TS 24.008 */ + bool rai_valid; + struct gprs_ra_id rai; + uint32_t ptmsi; /* invalid tmsi: GSM_RESERVED_TMSI */ + uint32_t ptmsi_sig; /* P-TMSI signature, 3 bytes */ + bool imsi_attached; + } gprs; }; int gsm_subscr_init(struct osmocom_ms *ms); int gsm_subscr_exit(struct osmocom_ms *ms); -int gsm_subscr_testcard(struct osmocom_ms *ms, uint16_t mcc, uint16_t mnc, - uint16_t lac, uint32_t tmsi, uint8_t imsi_attached); +int gsm_subscr_insert(struct osmocom_ms *ms); +int gsm_subscr_remove(struct osmocom_ms *ms); + int gsm_subscr_sap_rsp_cb(struct osmocom_ms *ms, int res_code, uint8_t res_type, uint16_t param_len, const uint8_t *param_val); -int gsm_subscr_sapcard(struct osmocom_ms *ms); -int gsm_subscr_remove_sapcard(struct osmocom_ms *ms); -int gsm_subscr_simcard(struct osmocom_ms *ms); -void gsm_subscr_sim_pin(struct osmocom_ms *ms, char *pin1, char *pin2, - int8_t mode); +int gsm_subscr_sim_pin(struct osmocom_ms *ms, const char *pin1, const char *pin2, + int8_t mode); int gsm_subscr_write_loci(struct osmocom_ms *ms); -int gsm_subscr_generate_kc(struct osmocom_ms *ms, uint8_t key_seq, - uint8_t *rand, uint8_t no_sim); -int gsm_subscr_remove(struct osmocom_ms *ms); +int gsm_subscr_write_locigprs(struct osmocom_ms *ms); +int gsm_subscr_generate_kc(struct osmocom_ms *ms, uint8_t key_seq, const uint8_t *rand, + bool no_sim); void new_sim_ustate(struct gsm_subscriber *subscr, int state); -int gsm_subscr_del_forbidden_plmn(struct gsm_subscriber *subscr, uint16_t mcc, - uint16_t mnc); -int gsm_subscr_add_forbidden_plmn(struct gsm_subscriber *subscr, uint16_t mcc, - uint16_t mnc, uint8_t cause); -int gsm_subscr_is_forbidden_plmn(struct gsm_subscriber *subscr, uint16_t mcc, - uint16_t mnc); +int gsm_subscr_del_forbidden_plmn(struct gsm_subscriber *subscr, const struct osmo_plmn_id *plmn); +int gsm_subscr_add_forbidden_plmn(struct gsm_subscriber *subscr, const struct osmo_plmn_id *plmn, uint8_t cause); +int gsm_subscr_is_forbidden_plmn(struct gsm_subscriber *subscr, const struct osmo_plmn_id *plmn); int gsm_subscr_dump_forbidden_plmn(struct osmocom_ms *ms, void (*print)(void *, const char *, ...), void *priv); void gsm_subscr_dump(struct gsm_subscriber *subscr, void (*print)(void *, const char *, ...), void *priv); -char *gsm_check_imsi(const char *imsi); int gsm_subscr_get_key_seq(struct osmocom_ms *ms, struct gsm_subscriber *subscr); #endif /* _SUBSCRIBER_H */ diff --git a/src/host/layer23/include/osmocom/bb/mobile/support.h b/src/host/layer23/include/osmocom/bb/common/support.h index c56c78e8..b0c71f5a 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/support.h +++ b/src/host/layer23/include/osmocom/bb/common/support.h @@ -91,6 +91,14 @@ struct gsm_support { uint8_t half_v1; uint8_t half_v3; + /* CSD modes */ + uint8_t csd_tch_f144; + uint8_t csd_tch_f96; + uint8_t csd_tch_f48; + uint8_t csd_tch_h48; + uint8_t csd_tch_f24; + uint8_t csd_tch_h24; + /* EDGE / UMTS / CDMA */ uint8_t edge_ms_sup; uint8_t edge_psk_sup; @@ -110,7 +118,7 @@ struct gsm_support_scan_max { uint16_t start; uint16_t end; uint16_t max; - uint16_t temp; + uint16_t temp; }; extern struct gsm_support_scan_max gsm_sup_smax[]; diff --git a/src/host/layer23/include/osmocom/bb/common/sysinfo.h b/src/host/layer23/include/osmocom/bb/common/sysinfo.h index f843f271..c31cf9d6 100644 --- a/src/host/layer23/include/osmocom/bb/common/sysinfo.h +++ b/src/host/layer23/include/osmocom/bb/common/sysinfo.h @@ -2,6 +2,7 @@ #define _SYSINFO_H #include <osmocom/gsm/gsm48_ie.h> +#include <osmocom/gsm/gsm23003.h> /* collection of system information of the current cell */ @@ -17,11 +18,27 @@ #define FREQ_TYPE_REP_5bis 0x40 /* sub channel of SI 5bis */ #define FREQ_TYPE_REP_5ter 0x80 /* sub channel of SI 5ter */ -/* structure of all received system informations */ +struct si10_cell_info { + uint8_t index; /* frequency index of the frequencies received in SI5* */ + int16_t arfcn; /* ARFCN or -1 (if not found in SI5*) */ + uint8_t bsic; + bool barred; /* Cell is barred, values below are invalid. */ + bool la_different; /* Location area is different, so CRH is valid. */ + uint8_t cell_resel_hyst_db; + uint8_t ms_txpwr_max_cch; + uint8_t rxlev_acc_min_db; + uint8_t cell_resel_offset; + uint8_t temp_offset; + uint8_t penalty_time; +}; + +/* structure of all received system information */ struct gsm48_sysinfo { /* flags of available information */ uint8_t si1, si2, si2bis, si2ter, si3, - si4, si5, si5bis, si5ter, si6; + si4, si5, si5bis, si5ter, si6, + si13; + bool si10; /* memory maps to simply detect change in system info messages */ uint8_t si1_msg[23]; @@ -34,6 +51,8 @@ struct gsm48_sysinfo { uint8_t si5b_msg[18]; uint8_t si5t_msg[18]; uint8_t si6_msg[18]; + uint8_t si10_msg[21]; + uint8_t si13_msg[23]; struct gsm_sysinfo_freq freq[1024]; /* all frequencies */ uint16_t hopping[64]; /* hopping arfcn */ @@ -42,7 +61,7 @@ struct gsm48_sysinfo { /* serving cell */ uint8_t bsic; uint16_t cell_id; - uint16_t mcc, mnc, lac; /* LAI */ + struct osmo_location_area_id lai; uint8_t max_retrans; /* decoded */ uint8_t tx_integer; /* decoded */ uint8_t reest_denied; /* 1 = denied */ @@ -52,7 +71,7 @@ struct gsm48_sysinfo { /* si1 rest */ uint8_t nch; uint8_t nch_position; - uint8_t band_ind; /* set for DCS */ + bool band_ind; /* set for DCS */ /* si3 rest */ uint8_t sp; @@ -66,9 +85,46 @@ struct gsm48_sysinfo { uint8_t ecsm; uint8_t sched; uint8_t sched_where; - uint8_t gprs; - uint8_t gprs_ra_colour; - uint8_t gprs_si13_pos; + + struct { + /* si3/si4 rest */ + uint8_t supported; + uint8_t ra_colour; + uint8_t si13_pos; + + /* si13 rest */ + uint8_t hopping; + uint8_t hsn; + uint8_t rfl_num_len; + uint8_t rfl_num[4]; + + uint8_t ma_bitlen; + uint8_t ma_bitmap[64 / 8]; + uint8_t arfcn_idx_len; + uint8_t arfcn_idx[16]; + + /* PBCCH is not present */ + uint8_t rac; + uint8_t prio_acc_thresh; + uint8_t nco; + + /* GPRS Cell Options */ + uint8_t nmo; + uint8_t T3168; + uint8_t T3192; + uint8_t ab_type; + uint8_t ctrl_ack_type_use_block; + uint8_t bs_cv_max; + uint8_t pan_params_present; + uint8_t pan_dec; + uint8_t pan_inc; + uint8_t pan_max; + + /* EGPRS Cell Options */ + uint8_t egprs_supported; + uint8_t egprs_pkt_chan_req; + uint8_t egprs_bep_period; + } gprs; /* cell selection */ int8_t ms_txpwr_max_cch; @@ -118,45 +174,52 @@ struct gsm48_sysinfo { uint8_t nb_reest_denied; /* 1 = denied */ uint8_t nb_cell_barr; /* 1 = barred */ uint16_t nb_class_barr; /* bit 10 is emergency */ + + /* SI 10 */ + uint8_t si10_cell_num; /* number neighbor cells found in SI 10 */ + struct si10_cell_info si10_cell[32]; /* 32 neighbor cell descriptions */ }; char *gsm_print_arfcn(uint16_t arfcn); -uint8_t gsm_refer_pcs(uint16_t arfcn, struct gsm48_sysinfo *s); -int gsm48_sysinfo_dump(struct gsm48_sysinfo *s, uint16_t arfcn, - void (*print)(void *, const char *, ...), void *priv, - uint8_t *freq_map); +bool gsm_refer_pcs(uint16_t cell_arfcn, const struct gsm48_sysinfo *cell_s); +uint16_t gsm_arfcn_refer_pcs(uint16_t cell_arfcn, const struct gsm48_sysinfo *cell_s, uint16_t arfcn); +int gsm48_sysinfo_dump(const struct gsm48_sysinfo *s, uint16_t arfcn, + void (*print)(void *, const char *, ...), + void *priv, uint8_t *freq_map); +int gsm48_si10_dump(const struct gsm48_sysinfo *s, void (*print)(void *, const char *, ...), void *priv); int gsm48_decode_lai(struct gsm48_loc_area_id *lai, uint16_t *mcc, uint16_t *mnc, uint16_t *lac); -int gsm48_decode_chan_h0(struct gsm48_chan_desc *cd, uint8_t *tsc, - uint16_t *arfcn); -int gsm48_decode_chan_h1(struct gsm48_chan_desc *cd, uint8_t *tsc, - uint8_t *maio, uint8_t *hsn); +int gsm48_decode_chan_h0(const struct gsm48_chan_desc *cd, + uint8_t *tsc, uint16_t *arfcn); +int gsm48_decode_chan_h1(const struct gsm48_chan_desc *cd, + uint8_t *tsc, uint8_t *maio, uint8_t *hsn); int gsm48_decode_sysinfo1(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_1 *si, int len); + const struct gsm48_system_information_type_1 *si, int len); int gsm48_decode_sysinfo2(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_2 *si, int len); + const struct gsm48_system_information_type_2 *si, int len); int gsm48_decode_sysinfo2bis(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_2bis *si, int len); + const struct gsm48_system_information_type_2bis *si, int len); int gsm48_decode_sysinfo2ter(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_2ter *si, int len); + const struct gsm48_system_information_type_2ter *si, int len); int gsm48_decode_sysinfo3(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_3 *si, int len); + const struct gsm48_system_information_type_3 *si, int len); int gsm48_decode_sysinfo4(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_4 *si, int len); + const struct gsm48_system_information_type_4 *si, int len); int gsm48_decode_sysinfo5(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_5 *si, int len); + const struct gsm48_system_information_type_5 *si, int len); int gsm48_decode_sysinfo5bis(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_5bis *si, int len); + const struct gsm48_system_information_type_5bis *si, int len); int gsm48_decode_sysinfo5ter(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_5ter *si, int len); + const struct gsm48_system_information_type_5ter *si, int len); int gsm48_decode_sysinfo6(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_6 *si, int len); + const struct gsm48_system_information_type_6 *si, int len); +int gsm48_decode_sysinfo10(struct gsm48_sysinfo *s, + const struct gsm48_system_information_type_10 *si, int len); +int gsm48_decode_sysinfo13(struct gsm48_sysinfo *s, + const struct gsm48_system_information_type_13 *si, int len); int gsm48_decode_mobile_alloc(struct gsm_sysinfo_freq *freq, - uint8_t *ma, uint8_t len, uint16_t *hopping, uint8_t *hopp_len, - int si4); -int gsm48_encode_lai_hex(struct gsm48_loc_area_id *lai, uint16_t mcc, - uint16_t mnc, uint16_t lac); -int gsm48_decode_lai_hex(struct gsm48_loc_area_id *lai, uint16_t *mcc, - uint16_t *mnc, uint16_t *lac); + const uint8_t *ma, uint8_t len, + uint16_t *hopping, uint8_t *hopp_len, int si4); +int16_t arfcn_from_freq_index(const struct gsm48_sysinfo *s, uint16_t index); #endif /* _SYSINFO_H */ diff --git a/src/host/layer23/include/osmocom/bb/common/vty.h b/src/host/layer23/include/osmocom/bb/common/vty.h new file mode 100644 index 00000000..b2a52c01 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/common/vty.h @@ -0,0 +1,35 @@ +#pragma once + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/vty/vty.h> +#include <osmocom/vty/buffer.h> +#include <osmocom/vty/command.h> +#include <osmocom/core/signal.h> + +struct osmocom_ms; + +enum l23_vty_node { + MS_NODE = _LAST_OSMOVTY_NODE + 1, + TESTSIM_NODE, + GSMTAP_NODE, + _LAST_L23VTY_NODE, +}; + +int l23_vty_init(int (*config_write_ms_node_cb)(struct vty *), osmo_signal_cbfn *l23_vty_signal_cb); + +struct osmocom_ms *l23_vty_get_ms(const char *name, struct vty *vty); +void l23_ms_dump(struct osmocom_ms *ms, struct vty *vty); +void l23_vty_config_write_ms_node(struct vty *vty, const struct osmocom_ms *ms, const char *prefix); +void l23_vty_config_write_ms_node_contents(struct vty *vty, const struct osmocom_ms *ms, const char *prefix); +void l23_vty_config_write_ms_node_contents_final(struct vty *vty, const struct osmocom_ms *ms, const char *prefix); + +void l23_vty_ms_notify(struct osmocom_ms *ms, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); +void l23_vty_printf(void *priv, const char *fmt, ...); + +extern bool l23_vty_reading; +extern bool l23_vty_hide_default; + +extern struct llist_head ms_list; + +extern struct cmd_element l23_show_ms_cmd; +extern struct cmd_element l23_cfg_ms_cmd; diff --git a/src/host/layer23/include/osmocom/bb/misc/Makefile.am b/src/host/layer23/include/osmocom/bb/misc/Makefile.am index 59760906..14d3af95 100644 --- a/src/host/layer23/include/osmocom/bb/misc/Makefile.am +++ b/src/host/layer23/include/osmocom/bb/misc/Makefile.am @@ -1 +1,8 @@ -noinst_HEADERS = layer3.h rslms.h cell_log.h +noinst_HEADERS = \ + cell_log.h \ + geo.h \ + layer3.h \ + locate.h \ + log.h \ + rslms.h \ + $(NULL) diff --git a/src/host/layer23/include/osmocom/bb/misc/cell_log.h b/src/host/layer23/include/osmocom/bb/misc/cell_log.h index bce066eb..bef6e6ee 100644 --- a/src/host/layer23/include/osmocom/bb/misc/cell_log.h +++ b/src/host/layer23/include/osmocom/bb/misc/cell_log.h @@ -14,10 +14,6 @@ * 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. - * */ int scan_init(struct osmocom_ms *_ms); diff --git a/src/host/gsmmap/geo.h b/src/host/layer23/include/osmocom/bb/misc/geo.h index 25e26cba..25e26cba 100644 --- a/src/host/gsmmap/geo.h +++ b/src/host/layer23/include/osmocom/bb/misc/geo.h diff --git a/src/host/gsmmap/locate.h b/src/host/layer23/include/osmocom/bb/misc/locate.h index 26133452..26133452 100644 --- a/src/host/gsmmap/locate.h +++ b/src/host/layer23/include/osmocom/bb/misc/locate.h diff --git a/src/host/gsmmap/log.h b/src/host/layer23/include/osmocom/bb/misc/log.h index d1520101..3183f876 100644 --- a/src/host/gsmmap/log.h +++ b/src/host/layer23/include/osmocom/bb/misc/log.h @@ -1,3 +1,6 @@ +#pragma once + +#include <osmocom/bb/common/sysinfo.h> enum { LOG_TYPE_NONE = 0, @@ -26,6 +29,7 @@ struct node_mcc { struct node_mnc { struct node_mnc *next; uint16_t mnc; + bool mnc_3_digits; struct node_lac *lac; }; @@ -72,7 +76,7 @@ struct node_meas { }; struct node_mcc *get_node_mcc(uint16_t mcc); -struct node_mnc *get_node_mnc(struct node_mcc *mcc, uint16_t mnc); +struct node_mnc *get_node_mnc(struct node_mcc *mcc, uint16_t mnc, bool mnc_3_digits); struct node_lac *get_node_lac(struct node_mnc *mnc, uint16_t lac); struct node_cell *get_node_cell(struct node_lac *lac, uint16_t cellid); struct node_meas *add_node_meas(struct node_cell *cell); diff --git a/src/host/layer23/include/osmocom/bb/mobile/Makefile.am b/src/host/layer23/include/osmocom/bb/mobile/Makefile.am index 623964fd..5d2eeaf8 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/Makefile.am +++ b/src/host/layer23/include/osmocom/bb/mobile/Makefile.am @@ -1,4 +1,4 @@ noinst_HEADERS = gsm322.h gsm480_ss.h gsm411_sms.h gsm48_cc.h gsm48_mm.h \ - gsm48_rr.h mncc.h settings.h subscriber.h support.h \ - transaction.h vty.h mncc_sock.h mncc_ms.h primitives.h \ - app_mobile.h voice.h + gsm48_rr.h mncc.h gsm44068_gcc_bcc.h \ + tch.h transaction.h vty.h mncc_sock.h mncc_ms.h primitives.h \ + app_mobile.h gapk_io.h diff --git a/src/host/layer23/include/osmocom/bb/mobile/app_mobile.h b/src/host/layer23/include/osmocom/bb/mobile/app_mobile.h index 191f4baf..940dbce7 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/app_mobile.h +++ b/src/host/layer23/include/osmocom/bb/mobile/app_mobile.h @@ -8,10 +8,6 @@ extern char *config_dir; struct osmocom_ms; struct vty; -int l23_app_init(int (*mncc_recv)(struct osmocom_ms *ms, int, void *), - const char *config_file); -int l23_app_exit(void); -int l23_app_work(int *quit); int mobile_delete(struct osmocom_ms *ms, int force); struct osmocom_ms *mobile_new(char *name); int mobile_work(struct osmocom_ms *ms); diff --git a/src/host/layer23/include/osmocom/bb/mobile/gapk_io.h b/src/host/layer23/include/osmocom/bb/mobile/gapk_io.h new file mode 100644 index 00000000..ab8b6579 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/mobile/gapk_io.h @@ -0,0 +1,47 @@ +#pragma once + +#ifdef WITH_GAPK_IO + +#include <stdint.h> +#include <stdbool.h> + +#include <osmocom/gapk/procqueue.h> +#include <osmocom/gapk/codecs.h> + +#define GAPK_ULDL_QUEUE_LIMIT 8 + +/* Forward declarations */ +struct osmocom_ms; +struct msgb; + +struct gapk_io_state { + /* src/alsa -> proc/codec -> sink/tch_fb */ + struct osmo_gapk_pq *pq_source; + /* src/tch_fb -> proc/codec -> sink/alsa */ + struct osmo_gapk_pq *pq_sink; + + /* Description of currently used codec / format */ + const struct osmo_gapk_format_desc *phy_fmt_desc; + const struct osmo_gapk_codec_desc *codec_desc; + + /* DL TCH frame buffer (received, to be played) */ + struct llist_head tch_dl_fb; + unsigned int tch_dl_fb_len; + /* UL TCH frame buffer (captured, to be sent) */ + struct llist_head tch_ul_fb; + unsigned int tch_ul_fb_len; +}; + +struct gapk_io_state * +gapk_io_state_alloc(struct osmocom_ms *ms, + enum osmo_gapk_codec_type codec); +struct gapk_io_state * +gapk_io_state_alloc_mode_rate(struct osmocom_ms *ms, + enum gsm48_chan_mode ch_mode, + bool full_rate); +void gapk_io_state_free(struct gapk_io_state *state); + +void gapk_io_enqueue_dl(struct gapk_io_state *state, struct msgb *msg); +void gapk_io_dequeue_ul(struct osmocom_ms *ms, struct gapk_io_state *state); + +#endif /* WITH_GAPK_IO */ diff --git a/src/host/layer23/include/osmocom/bb/mobile/gsm322.h b/src/host/layer23/include/osmocom/bb/mobile/gsm322.h index d4caac99..4b20a1a1 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/gsm322.h +++ b/src/host/layer23/include/osmocom/bb/mobile/gsm322.h @@ -1,12 +1,13 @@ #ifndef _GSM322_H #define _GSM322_H -#include <osmocom/bb/common/sysinfo.h> - #include <osmocom/core/linuxlist.h> #include <osmocom/core/timer.h> +#include <osmocom/gsm/gsm23003.h> + +#include <osmocom/bb/common/sysinfo.h> -/* 4.3.1.1 List of states for PLMN slection process (automatic mode) */ +/* 4.3.1.1 List of states for PLMN selection process (automatic mode) */ #define GSM322_A0_NULL 0 #define GSM322_A1_TRYING_RPLMN 1 #define GSM322_A2_ON_PLMN 2 @@ -15,7 +16,7 @@ #define GSM322_A5_HPLMN_SEARCH 5 #define GSM322_A6_NO_SIM 6 -/* 4.3.1.2 List of states for PLMN slection process (manual mode) */ +/* 4.3.1.2 List of states for PLMN selection process (manual mode) */ #define GSM322_M0_NULL 0 #define GSM322_M1_TRYING_RPLMN 1 #define GSM322_M2_ON_PLMN 2 @@ -42,7 +43,7 @@ /* GSM 03.22 events */ #define GSM322_EVENT_SWITCH_ON 1 -#define GSM322_EVENT_SWITCH_OFF 2 +#define GSM322_EVENT_SWITCH_OFF 2 #define GSM322_EVENT_SIM_INSERT 3 #define GSM322_EVENT_SIM_REMOVE 4 #define GSM322_EVENT_REG_SUCCESS 5 @@ -74,7 +75,7 @@ enum { /* node for each PLMN */ struct gsm322_plmn_list { struct llist_head entry; - uint16_t mcc, mnc; + struct osmo_plmn_id plmn; uint8_t rxlev; /* rx level in range format */ uint8_t cause; /* cause value, if PLMN is not allowed */ }; @@ -82,14 +83,14 @@ struct gsm322_plmn_list { /* node for each forbidden LA */ struct gsm322_la_list { struct llist_head entry; - uint16_t mcc, mnc, lac; + struct osmo_location_area_id lai; uint8_t cause; }; /* node for each BA-List */ struct gsm322_ba_list { struct llist_head entry; - uint16_t mcc, mnc; + struct osmo_plmn_id plmn; /* Band allocation for 1024+299 frequencies. * First bit of first index is frequency 0. */ @@ -124,14 +125,14 @@ struct gsm322_plmn { struct osmo_timer_list timer; int plmn_curr; /* current index in sorted_plmn */ - uint16_t mcc, mnc; /* current network selected */ + struct osmo_plmn_id plmn; /* current network selected */ }; /* state of CCCH activation */ #define GSM322_CCCH_ST_IDLE 0 /* no connection */ -#define GSM322_CCCH_ST_INIT 1 /* initalized */ +#define GSM322_CCCH_ST_INIT 1 /* initialized */ #define GSM322_CCCH_ST_SYNC 2 /* got sync */ -#define GSM322_CCCH_ST_DATA 3 /* receiveing data */ +#define GSM322_CCCH_ST_DATA 3 /* receiving data */ /* neighbour cell info list entry */ struct gsm322_neighbour { @@ -144,7 +145,7 @@ struct gsm322_neighbour { time_t when; /* when did we sync / read */ int16_t rxlev_sum_dbm; /* sum of received levels */ uint8_t rxlev_count; /* number of received levels */ - int8_t rla_c_dbm; /* average of the reveive level */ + int8_t rla_c_dbm; /* average of the receive level */ uint8_t c12_valid; /* both C1 and C2 are calculated */ int16_t c1, c2, crh; uint8_t checked_for_resel; @@ -171,7 +172,7 @@ struct gsm322_cellsel { /* cell selection list per frequency. */ /* scan and tune state */ struct osmo_timer_list timer; /* cell selection timer */ - uint16_t mcc, mnc; /* current network to search for */ + struct osmo_plmn_id plmn; /* current network to search for */ uint8_t powerscan; /* currently scanning for power */ uint8_t ccch_state; /* special state of current ccch */ uint32_t scan_state; /* special state of current scan */ @@ -188,7 +189,7 @@ struct gsm322_cellsel { uint8_t selected; /* if a cell is selected */ uint16_t sel_arfcn; /* current selected serving cell! */ struct gsm48_sysinfo sel_si; /* copy of selected cell, will update */ - uint16_t sel_mcc, sel_mnc, sel_lac, sel_id; + struct osmo_cell_global_id sel_cgi; /* cell re-selection */ struct llist_head nb_list; /* list of neighbour cells */ @@ -209,7 +210,7 @@ struct gsm322_cellsel { /* GSM 03.22 message */ struct gsm322_msg { int msg_type; - uint16_t mcc, mnc; + struct osmo_plmn_id plmn; uint8_t sysinfo; /* system information type */ uint8_t same_cell; /* select same cell when RET_IDLE */ uint8_t reject; /* location update reject cause */ @@ -229,18 +230,15 @@ int gsm322_cs_sendmsg(struct osmocom_ms *ms, struct msgb *msg); int gsm322_c_event(struct osmocom_ms *ms, struct msgb *msg); int gsm322_plmn_dequeue(struct osmocom_ms *ms); int gsm322_cs_dequeue(struct osmocom_ms *ms); -int gsm322_add_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, - uint16_t mnc, uint16_t lac, uint8_t cause); -int gsm322_del_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, - uint16_t mnc, uint16_t lac); -int gsm322_is_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, uint16_t mnc, - uint16_t lac); +int gsm322_add_forbidden_la(struct osmocom_ms *ms, const struct osmo_location_area_id *lai, uint8_t cause); +int gsm322_del_forbidden_la(struct osmocom_ms *ms, const struct osmo_location_area_id *lai); +int gsm322_is_forbidden_la(struct osmocom_ms *ms, const struct osmo_location_area_id *lai); int gsm322_dump_sorted_plmn(struct osmocom_ms *ms); int gsm322_dump_cs_list(struct gsm322_cellsel *cs, uint8_t flags, void (*print)(void *, const char *, ...), void *priv); int gsm322_dump_forbidden_la(struct osmocom_ms *ms, void (*print)(void *, const char *, ...), void *priv); -int gsm322_dump_ba_list(struct gsm322_cellsel *cs, uint16_t mcc, uint16_t mnc, +int gsm322_dump_ba_list(struct gsm322_cellsel *cs, const struct osmo_plmn_id *plmn, void (*print)(void *, const char *, ...), void *priv); int gsm322_dump_nb_list(struct gsm322_cellsel *cs, void (*print)(void *, const char *, ...), void *priv); diff --git a/src/host/layer23/include/osmocom/bb/mobile/gsm44068_gcc_bcc.h b/src/host/layer23/include/osmocom/bb/mobile/gsm44068_gcc_bcc.h new file mode 100644 index 00000000..e9889b32 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/mobile/gsm44068_gcc_bcc.h @@ -0,0 +1,43 @@ +/* VGCS/VBS call control */ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * Author: Andreas Eversberg + * + * 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/>. + */ +#pragma once + +#define GSM44068_ALLOC_SIZE 2048 +#define GSM44068_ALLOC_HEADROOM 256 + +static inline struct msgb *gsm44068_msgb_alloc_name(const char *name) +{ + return msgb_alloc_headroom(GSM44068_ALLOC_SIZE, GSM44068_ALLOC_HEADROOM, name); +} + +int gsm44068_gcc_init(struct osmocom_ms *ms); +int gsm44068_gcc_exit(struct osmocom_ms *ms); +int gsm44068_rcv_gcc_bcc(struct osmocom_ms *ms, struct msgb *msg); +int gsm44068_rcv_mm_idle(struct osmocom_ms *ms); +struct gsm_trans *trans_find_ongoing_gcc_bcc(struct osmocom_ms *ms); +int gcc_bcc_call(struct osmocom_ms *ms, uint8_t protocol, const char *number); +int gcc_leave(struct osmocom_ms *ms); +int gcc_bcc_hangup(struct osmocom_ms *ms); +int gcc_talk(struct osmocom_ms *ms); +int gcc_listen(struct osmocom_ms *ms); +int gsm44068_dump_calls(struct osmocom_ms *ms, void (*print)(void *, const char *, ...), void *priv); diff --git a/src/host/layer23/include/osmocom/bb/mobile/gsm48_mm.h b/src/host/layer23/include/osmocom/bb/mobile/gsm48_mm.h index 6e9c197c..1f0db785 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/gsm48_mm.h +++ b/src/host/layer23/include/osmocom/bb/mobile/gsm48_mm.h @@ -1,11 +1,16 @@ #ifndef _GSM48_MM_H #define _GSM48_MM_H +struct gsm_settings; + /* GSM 04.07 9.2.2 */ #define GSM48_MMXX_MASK 0xf00 #define GSM48_MMCC_CLASS 0x100 #define GSM48_MMSS_CLASS 0x200 #define GSM48_MMSMS_CLASS 0x300 +#define GSM48_MMGCC_CLASS 0x500 +#define GSM48_MMBCC_CLASS 0x600 +#define GSM48_MMXX_REL_IND 0x022 #define GSM48_MMCC_EST_REQ 0x110 #define GSM48_MMCC_EST_IND 0x112 #define GSM48_MMCC_EST_CNF 0x111 @@ -49,19 +54,67 @@ #define GSM48_MMSMS_ERR_IND 0x372 #define GSM48_MMSMS_PROMPT_IND 0x382 #define GSM48_MMSMS_PROMPT_REJ 0x384 +/* MM messages for Voice Group/Broadcast Calls */ +#define GSM48_MMGCC_EST_REQ 0x510 +#define GSM48_MMGCC_EST_CNF 0x511 +#define GSM48_MMGCC_REL_REQ 0x520 +#define GSM48_MMGCC_REL_IND 0x522 +#define GSM48_MMGCC_DATA_REQ 0x530 +#define GSM48_MMGCC_DATA_IND 0x532 +#define GSM48_MMGCC_UNIT_DATA_REQ 0x540 +#define GSM48_MMGCC_UNIT_DATA_IND 0x542 +#define GSM48_MMGCC_REEST_REQ 0x560 +#define GSM48_MMGCC_REEST_CNF 0x561 +#define GSM48_MMGCC_ERR_IND 0x572 +#define GSM48_MMGCC_NOTIF_IND 0x582 +#define GSM48_MMGCC_GROUP_REQ 0x590 +#define GSM48_MMGCC_GROUP_CNF 0x591 +#define GSM48_MMGCC_UPLINK_REQ 0x5a0 +#define GSM48_MMGCC_UPLINK_CNF 0x5a1 +#define GSM48_MMGCC_UPLINK_REL_REQ 0x5a8 +#define GSM48_MMGCC_UPLINK_REL_IND 0x5aa +#define GSM48_MMGCC_UPLINK_FREE_IND 0x5b2 +#define GSM48_MMGCC_UPLINK_BUSY_IND 0x5b6 +#define GSM48_MMBCC_EST_REQ 0x610 +#define GSM48_MMBCC_EST_CNF 0x611 +#define GSM48_MMBCC_REL_REQ 0x620 +#define GSM48_MMBCC_REL_IND 0x622 +#define GSM48_MMBCC_DATA_REQ 0x630 +#define GSM48_MMBCC_DATA_IND 0x632 +#define GSM48_MMBCC_UNIT_DATA_REQ 0x640 +#define GSM48_MMBCC_UNIT_DATA_IND 0x642 +#define GSM48_MMBCC_REEST_REQ 0x660 +#define GSM48_MMBCC_REEST_CNF 0x661 +#define GSM48_MMBCC_ERR_IND 0x672 +#define GSM48_MMBCC_NOTIF_IND 0x682 +#define GSM48_MMBCC_GROUP_REQ 0x690 +#define GSM48_MMBCC_GROUP_CNF 0x691 +#define GSM48_MMBCC_UPLINK_REQ 0x6a0 +#define GSM48_MMBCC_UPLINK_CNF 0x6a1 +#define GSM48_MMBCC_UPLINK_REL_REQ 0x6a8 +#define GSM48_MMBCC_UPLINK_REL_IND 0x6aa +#define GSM48_MMBCC_UPLINK_FREE_IND 0x6b2 +#define GSM48_MMBCC_UPLINK_BUSY_IND 0x6b6 + #define MMXX_ALLOC_SIZE 256 #define MMXX_ALLOC_HEADROOM 64 +#define MMXX_NOTIFY_SETUP 0 +#define MMXX_NOTIFY_RELEASE 1 + /* MMxx-SAP header */ struct gsm48_mmxx_hdr { - int msg_type; /* MMxx_* primitive */ - uint32_t ref; /* reference to transaction */ - uint32_t transaction_id; /* transaction identifier */ - uint8_t sapi; /* sapi */ - uint8_t emergency; /* emergency type of call */ - uint8_t cause; /* cause used for release */ -}; + uint16_t msg_type; /* MMxx_* primitive */ + uint32_t ref; /* reference to transaction */ + uint32_t transaction_id; /* transaction identifier */ + uint8_t sapi; /* sapi */ + uint8_t emergency; /* emergency type of call */ + uint8_t cause; /* cause used for release */ + uint8_t notify; /* notify ongoing ASCI call */ + bool ch_desc_present; /* notifies channel */ + struct gsm48_chan_desc ch_desc; /* group channel */ +} __attribute__((packed)); /* GSM 6.1.2 */ #define GSM48_MMR_REG_REQ 0x01 @@ -71,10 +124,9 @@ struct gsm48_mmxx_hdr { /* MMR-SAP header */ struct gsm48_mmr { - int msg_type; - + uint8_t msg_type; uint8_t cause; -}; +} __attribute__((packed)); /* GSM 04.07 9.2.1 */ #define GSM48_MMXX_ST_IDLE 0 @@ -134,13 +186,26 @@ struct gsm48_mmr { #define GSM48_MM_EVENT_SYSINFO 14 #define GSM48_MM_EVENT_USER_PLMN_SEL 15 #define GSM48_MM_EVENT_LOST_COVERAGE 16 +#define GSM48_MM_EVENT_NOTIFICATION 17 +#define GSM48_MM_EVENT_UPLINK_FREE 18 +#define GSM48_MM_EVENT_UPLINK_BUSY 19 /* message for MM events */ struct gsm48_mm_event { - uint32_t msg_type; + uint32_t msg_type; - uint8_t sres[4]; -}; + union { + /* GSM48_MM_EVENT_AUTH_RESPONSE */ + uint8_t sres[4]; + /* GSM48_MM_EVENT_NOTIFICATION */ + struct { + uint8_t gcr[5]; + bool ch_desc_present; + struct gsm48_chan_desc ch_desc; + bool gone; + } __attribute__((packed)) notification; + }; +} __attribute__((packed)); /* GSM 04.08 MM timers */ #define GSM_T3210_MS 20, 0 @@ -185,7 +250,7 @@ struct gsm48_mmlayer { uint8_t lupd_rej_cause; /* cause of last reject */ uint8_t lupd_periodic; /* periodic update pending */ uint8_t lupd_retry; /* pending T3211/T3213 to */ - uint16_t lupd_mcc, lupd_mnc, lupd_lac; + struct osmo_location_area_id lupd_lai; /* imsi detach */ uint8_t delay_detach; /* do detach when possible */ @@ -197,6 +262,14 @@ struct gsm48_mmlayer { /* sapi 3 */ int sapi3_link; + + /* VGCS additional states */ + struct { + bool enabled; /* We are in group/broadcast mode. */ + bool group_call; /* This is a group call, not a broadcast call. */ + uint32_t callref; /* Callref of this call. */ + bool normal_service; /* Service state before group transmit mode. */ + } vgcs; }; /* MM connection entry */ @@ -204,7 +277,7 @@ struct gsm48_mm_conn { struct llist_head list; struct gsm48_mmlayer *mm; - /* ref and type form a unique tupple */ + /* ref and protocol form a unique tuple */ uint32_t ref; /* reference to trans */ uint8_t protocol; uint8_t transaction_id; @@ -213,6 +286,8 @@ struct gsm48_mm_conn { int state; }; +int gsm48_encode_mi_lv(struct osmocom_ms *ms, struct msgb *msg, uint8_t mi_type, bool emergency_imsi); +int gsm48_encode_mi_tlv(struct osmocom_ms *ms, struct msgb *msg, uint8_t mi_type, bool emergency_imsi); uint8_t gsm48_current_pwr_lev(struct gsm_settings *set, uint16_t arfcn); int gsm48_mm_init(struct osmocom_ms *ms); int gsm48_mm_exit(struct osmocom_ms *ms); diff --git a/src/host/layer23/include/osmocom/bb/mobile/gsm48_rr.h b/src/host/layer23/include/osmocom/bb/mobile/gsm48_rr.h index 6996ff35..112e85a3 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/gsm48_rr.h +++ b/src/host/layer23/include/osmocom/bb/mobile/gsm48_rr.h @@ -1,6 +1,7 @@ #ifndef _GSM48_RR_H #define _GSM48_RR_H +#include <osmocom/core/timer.h> #include <osmocom/gsm/protocol/gsm_04_08.h> #define GSM_TA_CM 55385 @@ -9,6 +10,10 @@ #define T200_DCCH_SHARED 2 /* SDCCH shares SAPI 0 and 3 */ #define T200_ACCH 2 /* SACCH SAPI 3 */ +/* GSM 04.08 RR timers */ +#define GSM_T3128_MS 1, 0 /* Uplink investigation timer. */ +#define GSM_T3130_MS 5, 0 /* Uplink access timeout. */ + /* GSM 04.07 9.1.2 */ #define GSM48_RR_EST_REQ 0x10 @@ -22,6 +27,15 @@ #define GSM48_RR_ABORT_REQ 0x60 #define GSM48_RR_ABORT_IND 0x62 #define GSM48_RR_ACT_REQ 0x70 +/* These are non-stadard primitives, used for group receive/transmit modes. */ +#define GSM48_RR_GROUP_REQ 0x80 /* Join a group channel in group receive mode. */ +#define GSM48_RR_GROUP_CNF 0x81 /* Group channel has been joined. */ +#define GSM48_RR_GROUP_REL_REQ 0x84 /* Release group channel. */ +#define GSM48_RR_GROUP_REL_IND 0x86 /* Group channel has been released or failed. */ +#define GSM48_RR_UPLINK_REQ 0x90 /* Request uplink for group transmit mode. */ +#define GSM48_RR_UPLINK_CNF 0x91 /* Access granted. */ +#define GSM48_RR_UPLINK_REL_REQ 0x94 /* Release uplink for group receive mode. */ +#define GSM48_RR_UPLINK_REL_IND 0x96 /* Access denied or failed or uplink released. */ #define RR_EST_CAUSE_EMERGENCY 1 #define RR_EST_CAUSE_REESTAB_TCH_F 2 @@ -44,6 +58,8 @@ #define RR_REL_CAUSE_EMERGENCY_ONLY 6 #define RR_REL_CAUSE_LOST_SIGNAL 7 #define RR_REL_CAUSE_LINK_FAILURE 8 +#define RR_REL_CAUSE_UPLINK_BUSY 9 +#define RR_REL_CAUSE_UPLINK_REJECTED 10 #define RR_SYNC_CAUSE_CIPHERING 1 @@ -69,6 +85,13 @@ struct gsm48_rr_hdr { #define GSM48_RR_ST_DEDICATED 2 #define GSM48_RR_ST_REL_PEND 3 +/* group states (VGCS) */ +enum gsm48_rr_gstate { + GSM48_RR_GST_OFF = 0, + GSM48_RR_GST_RECEIVE, + GSM48_RR_GST_TRANSMIT, +}; + /* special states for SAPI 3 link */ #define GSM48_RR_SAPI3ST_IDLE 0 #define GSM48_RR_SAPI3ST_WAIT_EST 1 @@ -100,6 +123,7 @@ struct gsm48_rr_cd { uint8_t start; /* start time available */ struct gsm_time start_tm; /* start time */ uint8_t mode; /* mode of channel */ + uint8_t tch_flags; /* E.g. L1CTL_TCH_FLAG_RXONLY */ uint8_t cipher; /* ciphering of channel */ }; @@ -149,7 +173,7 @@ struct gsm48_rrlayer { /* channel request states */ uint8_t wait_assign; /* waiting for assignment state */ uint8_t n_chan_req; /* number left, incl. current */ - uint8_t chan_req_val; /* current request value */ + uint8_t chan_req_val; /* current request value */ uint8_t chan_req_mask; /* mask of random bits */ /* state of dedicated mdoe */ @@ -157,7 +181,7 @@ struct gsm48_rrlayer { /* cr_hist */ uint8_t cr_ra; /* stores requested ra until confirmed */ - struct gsm48_cr_hist cr_hist[3]; + struct gsm48_cr_hist cr_hist[5]; /* V(SD) sequence numbers */ uint16_t v_sd; /* 16 PD 1-bit sequence numbers packed */ @@ -188,9 +212,27 @@ struct gsm48_rrlayer { /* audio flow */ uint8_t audio_mode; + /* 3GPP TS 44.014 TCH test loop mode (L1CTL specific format) */ + uint8_t tch_loop_mode; + /* sapi 3 */ uint8_t sapi3_state; uint8_t sapi3_link_id; + + /* group call */ + struct { + struct llist_head notif_list; /* list of received call notifications */ + enum gsm48_rr_gstate group_state; /* extension to RR state for group transmit/receive modes */ + struct gsm48_rr_cd cd_group; /* channel description of group call channel */ + bool uplink_free; /* Is set, if uplink is currently free. */ + uint8_t uic; /* UIC to use for access burst (-1 for BSIC) */ + bool uplink_access; /* The network wants us to send listener access bursts. */ + struct osmo_timer_list t_ul_free; /* Uplink free timer. (480ms timer) */ + struct osmo_timer_list t3128; /* Uplink investigation timer. */ + struct osmo_timer_list t3130; /* Uplink access timer. */ + uint8_t uplink_tries; /* Counts number of tries to access the uplink. */ + uint8_t uplink_counter; /* Counts number of access bursts per 'try'. */ + } vgcs; }; const char *get_rr_name(int value); @@ -209,7 +251,7 @@ extern const char *gsm48_rr_state_names[]; int gsm48_rr_start_monitor(struct osmocom_ms *ms); int gsm48_rr_stop_monitor(struct osmocom_ms *ms); int gsm48_rr_alter_delay(struct osmocom_ms *ms); -int gsm48_rr_tx_voice(struct osmocom_ms *ms, struct msgb *msg); +int gsm48_rr_tx_traffic(struct osmocom_ms *ms, struct msgb *msg); int gsm48_rr_audio_mode(struct osmocom_ms *ms, uint8_t mode); #endif /* _GSM48_RR_H */ diff --git a/src/host/layer23/include/osmocom/bb/mobile/mncc.h b/src/host/layer23/include/osmocom/bb/mobile/mncc.h index 8ec9358d..d6facbbd 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/mncc.h +++ b/src/host/layer23/include/osmocom/bb/mobile/mncc.h @@ -1,4 +1,4 @@ -/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface +/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ /* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org> @@ -17,35 +17,12 @@ * 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. - * */ -#ifndef _MNCC_H -#define _MNCC_H +#pragma once -#include <osmocom/core/linuxlist.h> #include <osmocom/gsm/mncc.h> -struct gsm_call { - struct llist_head entry; - - struct osmocom_ms *ms; - - uint32_t callref; - - bool init; /* call initiated, no response yet */ - bool hold; /* call on hold */ - bool ring; /* call ringing/knocking */ - - struct osmo_timer_list dtmf_timer; - uint8_t dtmf_state; - uint8_t dtmf_index; - char dtmf[32]; /* dtmf sequence */ -}; - #define DTMF_ST_IDLE 0 /* no DTMF active */ #define DTMF_ST_START 1 /* DTMF started, waiting for resp. */ #define DTMF_ST_MARK 2 /* wait tone duration */ @@ -58,7 +35,7 @@ struct gsm_call { #define MNCC_SETUP_CNF 0x0104 #define MNCC_SETUP_COMPL_REQ 0x0105 #define MNCC_SETUP_COMPL_IND 0x0106 -/* MNCC_REJ_* is perfomed via MNCC_REL_* */ +/* MNCC_REJ_* is performed via MNCC_REL_* */ #define MNCC_CALL_CONF_IND 0x0107 #define MNCC_CALL_PROC_REQ 0x0108 #define MNCC_PROGRESS_REQ 0x0109 @@ -108,6 +85,9 @@ struct gsm_call { #define GSM_TCHF_FRAME 0x0300 #define GSM_TCHF_FRAME_EFR 0x0301 +#define GSM_TCHH_FRAME 0x0302 +#define GSM_TCH_FRAME_AMR 0x0303 +#define GSM_BAD_FRAME 0x03ff #define GSM_MAX_FACILITY 128 #define GSM_MAX_SSVERSION 128 @@ -128,6 +108,8 @@ struct gsm_call { #define MNCC_F_KEYPAD 0x1000 #define MNCC_F_SIGNAL 0x2000 +struct osmocom_ms; + struct gsm_mncc { /* context based information */ uint32_t msg_type; @@ -136,7 +118,7 @@ struct gsm_mncc { /* which fields are present */ uint32_t fields; - /* data derived informations (MNCC_F_ based) */ + /* data derived information (MNCC_F_ based) */ struct gsm_mncc_bearer_cap bearer_cap; struct gsm_mncc_number called; struct gsm_mncc_number calling; @@ -174,6 +156,3 @@ struct gsm_data_frame { const char *get_mncc_name(int value); int mncc_recv(struct osmocom_ms *ms, int msg_type, void *arg); void mncc_set_cause(struct gsm_mncc *data, int loc, int val); - -#endif - diff --git a/src/host/layer23/include/osmocom/bb/mobile/mncc_ms.h b/src/host/layer23/include/osmocom/bb/mobile/mncc_ms.h index 49ce1a42..05c539b0 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/mncc_ms.h +++ b/src/host/layer23/include/osmocom/bb/mobile/mncc_ms.h @@ -1,6 +1,39 @@ #pragma once -int mncc_call(struct osmocom_ms *ms, char *number); +#include <stdint.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/timer.h> + +struct osmocom_ms; + +enum gsm_call_type { + GSM_CALL_T_UNKNOWN = 0, + GSM_CALL_T_VOICE, + GSM_CALL_T_DATA, /* UDI or 3.1 kHz audio */ + GSM_CALL_T_DATA_FAX, +}; + +struct gsm_call { + struct llist_head entry; + + struct osmocom_ms *ms; + + uint32_t callref; + enum gsm_call_type type; + + bool init; /* call initiated, no response yet */ + bool hold; /* call on hold */ + bool ring; /* call ringing/knocking */ + + struct osmo_timer_list dtmf_timer; + uint8_t dtmf_state; + uint8_t dtmf_index; + char dtmf[32]; /* dtmf sequence */ +}; + +int mncc_call(struct osmocom_ms *ms, const char *number, + enum gsm_call_type call_type); int mncc_hangup(struct osmocom_ms *ms); int mncc_answer(struct osmocom_ms *ms); int mncc_hold(struct osmocom_ms *ms); diff --git a/src/host/layer23/include/osmocom/bb/mobile/settings.h b/src/host/layer23/include/osmocom/bb/mobile/settings.h deleted file mode 100644 index 4e5d5a19..00000000 --- a/src/host/layer23/include/osmocom/bb/mobile/settings.h +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef _settings_h -#define _settings_h - -#define MOB_C7_DEFLT_ANY_TIMEOUT 30 - -struct gsm_settings { - char layer2_socket_path[128]; - char sap_socket_path[128]; - - /* IMEI */ - char imei[16]; - char imeisv[17]; - char imei_random; - - /* network search */ - int plmn_mode; /* PLMN_MODE_* */ - - /* SIM */ - int sim_type; /* selects card on power on */ - char emergency_imsi[16]; - - /* SMS */ - char sms_sca[22]; - bool store_sms; - - /* test card simulator settings */ - char test_imsi[16]; - uint32_t test_tmsi; - uint8_t test_ki_type; - uint8_t test_ki[16]; /* 128 bit max */ - uint8_t test_barr; - uint8_t test_rplmn_valid; - uint16_t test_rplmn_mcc, test_rplmn_mnc; - uint16_t test_lac; - uint8_t test_imsi_attached; - uint8_t test_always; /* ...search hplmn... */ - - /* call related settings */ - uint8_t cw; /* set if call-waiting is allowed */ - uint8_t auto_answer; - uint8_t clip, clir; - uint8_t half, half_prefer; - - /* changing default behavior */ - uint8_t alter_tx_power; - uint8_t alter_tx_power_value; - int8_t alter_delay; - uint8_t stick; - uint16_t stick_arfcn; - uint8_t skip_max_per_band; - uint8_t no_lupd; - uint8_t no_neighbour; - - /* supported by configuration */ - uint8_t cc_dtmf; - uint8_t sms_ptp; - uint8_t a5_1; - uint8_t a5_2; - uint8_t a5_3; - uint8_t a5_4; - uint8_t a5_5; - uint8_t a5_6; - uint8_t a5_7; - uint8_t p_gsm; - uint8_t e_gsm; - uint8_t r_gsm; - uint8_t dcs; - uint8_t gsm_850; - uint8_t pcs; - uint8_t gsm_480; - uint8_t gsm_450; - uint8_t class_900; - uint8_t class_dcs; - uint8_t class_850; - uint8_t class_pcs; - uint8_t class_400; - uint8_t freq_map[128+38]; - uint8_t full_v1; - uint8_t full_v2; - uint8_t full_v3; - uint8_t half_v1; - uint8_t half_v3; - uint8_t ch_cap; /* channel capability */ - int8_t min_rxlev_dbm; /* min dBm to access */ - - /* radio */ - uint16_t dsc_max; - uint8_t force_rekey; - - /* dialing */ - struct llist_head abbrev; - - /* EDGE / UMTS / CDMA */ - uint8_t edge_ms_sup; - uint8_t edge_psk_sup; - uint8_t edge_psk_uplink; - uint8_t class_900_edge; - uint8_t class_dcs_pcs_edge; - uint8_t umts_fdd; - uint8_t umts_tdd; - uint8_t cdma_2000; - uint8_t dtm; - uint8_t class_dtm; - uint8_t dtm_mac; - uint8_t dtm_egprs; - - /* Timeout for GSM 03.22 C7 state */ - uint8_t any_timeout; -}; - -struct gsm_settings_abbrev { - struct llist_head list; - char abbrev[4]; - char number[32]; - char name[32]; -}; - -int gsm_settings_arfcn(struct osmocom_ms *ms); -int gsm_settings_init(struct osmocom_ms *ms); -int gsm_settings_exit(struct osmocom_ms *ms); -char *gsm_check_imei(const char *imei, const char *sv); -int gsm_random_imei(struct gsm_settings *set); - -#endif /* _settings_h */ - diff --git a/src/host/layer23/include/osmocom/bb/mobile/tch.h b/src/host/layer23/include/osmocom/bb/mobile/tch.h new file mode 100644 index 00000000..b026bd5d --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/mobile/tch.h @@ -0,0 +1,28 @@ +#pragma once + +struct osmocom_ms; +struct gsm_data_frame; +struct msgb; + +struct tch_state { + bool rx_only; /* Rx TCH frames, but Tx nothing */ + bool is_voice; /* voice (true) or data (false) */ + union { + struct tch_voice_state { + enum tch_voice_io_handler handler; + struct gapk_io_state *gapk_io; + } voice; + struct tch_data_state { + enum tch_data_io_handler handler; + struct tch_csd_sock_state *sock; + struct osmo_v110_ta *v110_ta; + struct osmo_soft_uart *suart; + unsigned int num_tx; + uint8_t e1e2e3[3]; + } data; + }; +}; + +int tch_init(struct osmocom_ms *ms); +int tch_send_msg(struct osmocom_ms *ms, struct msgb *msg); +int tch_send_mncc_frame(struct osmocom_ms *ms, const struct gsm_data_frame *frame); diff --git a/src/host/layer23/include/osmocom/bb/mobile/transaction.h b/src/host/layer23/include/osmocom/bb/mobile/transaction.h index 8c06d5d9..c1e94298 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/transaction.h +++ b/src/host/layer23/include/osmocom/bb/mobile/transaction.h @@ -27,7 +27,6 @@ struct gsm_trans { union { struct { - /* current call state */ int state; @@ -39,6 +38,7 @@ struct gsm_trans { int T308_second; /* used to send release again */ struct osmo_timer_list timer; struct gsm_mncc msg; /* stores setup/disconnect/release message */ + struct gsm_mncc_bearer_cap *bcap; } cc; struct { /* current supp.serv. state */ @@ -55,6 +55,26 @@ struct gsm_trans { struct gsm_sms *sms; } sms; + struct { + /* VGCS/VBS state machine */ + struct osmo_fsm_inst *fi; + + /* Call State (See Table 9.3 of TS 144.068) */ + uint8_t call_state; + + /* State attributes (See Table 9.7 of TS 144.068) */ + uint8_t d_att, u_att, comm, orig; + + /* Channel description last received via notification */ + bool ch_desc_present; + struct gsm48_chan_desc ch_desc; + + /* Flag to store termination request from upper layer. */ + bool termination; + + /* Flag to tell the state machine that call changes from separate link to group receive mode. */ + bool receive_after_sl; + } gcc; }; }; @@ -62,7 +82,7 @@ struct gsm_trans { struct gsm_trans *trans_find_by_id(struct osmocom_ms *ms, uint8_t proto, uint8_t trans_id); -struct gsm_trans *trans_find_by_callref(struct osmocom_ms *ms, +struct gsm_trans *trans_find_by_callref(struct osmocom_ms *ms, uint8_t protocol, uint32_t callref); struct gsm_trans *trans_alloc(struct osmocom_ms *ms, diff --git a/src/host/layer23/include/osmocom/bb/mobile/voice.h b/src/host/layer23/include/osmocom/bb/mobile/voice.h deleted file mode 100644 index a0524183..00000000 --- a/src/host/layer23/include/osmocom/bb/mobile/voice.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef _voice_h -#define _voice_h - -int gsm_voice_init(struct osmocom_ms *ms); -int gsm_send_voice(struct osmocom_ms *ms, struct gsm_data_frame *data); - -#endif /* _voice_h */ diff --git a/src/host/layer23/include/osmocom/bb/mobile/vty.h b/src/host/layer23/include/osmocom/bb/mobile/vty.h index 3bec1139..f03012e5 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/vty.h +++ b/src/host/layer23/include/osmocom/bb/mobile/vty.h @@ -6,15 +6,16 @@ #include <osmocom/vty/buffer.h> #include <osmocom/vty/command.h> +#include <osmocom/bb/common/vty.h> + enum ms_vty_node { - MS_NODE = _LAST_OSMOVTY_NODE + 1, - TESTSIM_NODE, - SUPPORT_NODE, + SUPPORT_NODE = _LAST_L23VTY_NODE + 1, + TCH_VOICE_NODE, + TCH_DATA_NODE, + VGCS_NODE, + VBS_NODE, }; -int ms_vty_go_parent(struct vty *vty); int ms_vty_init(void); -extern void vty_notify(struct osmocom_ms *ms, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); - #endif diff --git a/src/host/layer23/include/osmocom/bb/modem/Makefile.am b/src/host/layer23/include/osmocom/bb/modem/Makefile.am new file mode 100644 index 00000000..2f69d195 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/modem/Makefile.am @@ -0,0 +1,10 @@ +noinst_HEADERS = \ + modem.h \ + gmm.h \ + grr.h \ + llc.h \ + rlcmac.h \ + sm.h \ + sndcp.h \ + vty.h \ + $(NULL) diff --git a/src/host/layer23/include/osmocom/bb/modem/gmm.h b/src/host/layer23/include/osmocom/bb/modem/gmm.h new file mode 100644 index 00000000..41e1e7e6 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/modem/gmm.h @@ -0,0 +1,13 @@ +#pragma once + +#include <stdbool.h> +#include <stdint.h> + +struct osmocom_ms; + +int modem_gmm_init(struct osmocom_ms *ms); + +int modem_gmm_gmmreg_attach_req(const struct osmocom_ms *ms); +int modem_gmm_gmmreg_detach_req(const struct osmocom_ms *ms); +int modem_gmm_gmmreg_sim_auth_rsp(const struct osmocom_ms *ms, + uint8_t *sres, uint8_t *kc, uint8_t kc_len); diff --git a/src/host/layer23/include/osmocom/bb/modem/grr.h b/src/host/layer23/include/osmocom/bb/modem/grr.h new file mode 100644 index 00000000..a86a089b --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/modem/grr.h @@ -0,0 +1,35 @@ +#pragma once + +#include <stdbool.h> +#include <stdint.h> + +struct msgb; +struct osmocom_ms; +struct lapdm_entity; +struct osmo_fsm; + +enum grr_fsm_state { + GRR_ST_PACKET_NOT_READY, + GRR_ST_PACKET_IDLE, + GRR_ST_PACKET_ACCESS, + GRR_ST_PACKET_TRANSFER, +}; + +enum grr_fsm_event { + GRR_EV_BCCH_BLOCK_IND, + GRR_EV_PCH_AGCH_BLOCK_IND, + GRR_EV_CHAN_ACCESS_REQ, + GRR_EV_CHAN_ACCESS_CNF, + GRR_EV_PDCH_ESTABLISH_REQ, + GRR_EV_PDCH_RELEASE_REQ, + GRR_EV_PDCH_UL_TBF_CFG_REQ, + GRR_EV_PDCH_DL_TBF_CFG_REQ, + GRR_EV_PDCH_BLOCK_REQ, + GRR_EV_PDCH_BLOCK_CNF, + GRR_EV_PDCH_BLOCK_IND, + GRR_EV_PDCH_RTS_IND, +}; + +extern struct osmo_fsm grr_fsm_def; + +int modem_grr_rslms_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx); diff --git a/src/host/layer23/include/osmocom/bb/modem/llc.h b/src/host/layer23/include/osmocom/bb/modem/llc.h new file mode 100644 index 00000000..5fe5d49f --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/modem/llc.h @@ -0,0 +1,8 @@ +#pragma once + +#include <stdbool.h> + +struct osmocom_ms; + +int modem_llc_init(struct osmocom_ms *ms, const char *cipher_plugin_path); + diff --git a/src/host/layer23/include/osmocom/bb/modem/modem.h b/src/host/layer23/include/osmocom/bb/modem/modem.h new file mode 100644 index 00000000..7135bf71 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/modem/modem.h @@ -0,0 +1,19 @@ +#pragma once + +#include <stdbool.h> + +int modem_start(void); +int modem_gprs_attach_if_needed(struct osmocom_ms *ms); +int modem_sync_to_cell(struct osmocom_ms *ms); + +enum modem_state { + MODEM_ST_IDLE, + MODEM_ST_ATTACHING, + MODEM_ST_ATTACHED +}; + +struct modem_app { + struct osmocom_ms *ms; + enum modem_state modem_state; +}; +extern struct modem_app app_data; diff --git a/src/host/layer23/include/osmocom/bb/modem/rlcmac.h b/src/host/layer23/include/osmocom/bb/modem/rlcmac.h new file mode 100644 index 00000000..d1b054f8 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/modem/rlcmac.h @@ -0,0 +1,8 @@ +#pragma once + +#include <stdbool.h> + +struct osmocom_ms; + +int modem_rlcmac_init(struct osmocom_ms *ms); + diff --git a/src/host/layer23/include/osmocom/bb/modem/sm.h b/src/host/layer23/include/osmocom/bb/modem/sm.h new file mode 100644 index 00000000..b1a5df5a --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/modem/sm.h @@ -0,0 +1,8 @@ +#pragma once + +#include <stdbool.h> + +struct osmocom_ms; + +int modem_sm_init(struct osmocom_ms *ms); +int modem_sm_smreg_pdp_act_req(const struct osmocom_ms *ms, const struct osmobb_apn *apn); diff --git a/src/host/layer23/include/osmocom/bb/modem/sndcp.h b/src/host/layer23/include/osmocom/bb/modem/sndcp.h new file mode 100644 index 00000000..b2e6f0fd --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/modem/sndcp.h @@ -0,0 +1,10 @@ +#pragma once + +#include <stdbool.h> + +struct osmocom_ms; +struct osmobb_apn; + +int modem_sndcp_init(struct osmocom_ms *ms); +int modem_sndcp_sn_xid_req(struct osmobb_apn *apn); +int modem_sndcp_sn_unitdata_req(struct osmobb_apn *apn, uint8_t *npdu, size_t npdu_len); diff --git a/src/host/layer23/include/osmocom/bb/modem/vty.h b/src/host/layer23/include/osmocom/bb/modem/vty.h new file mode 100644 index 00000000..5b69af60 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/modem/vty.h @@ -0,0 +1,10 @@ +#pragma once + +#include <osmocom/bb/common/vty.h> + +enum modem_vty_node { + APN_NODE = _LAST_L23VTY_NODE + 1, +}; + +int modem_vty_init(void); +int modem_vty_go_parent(struct vty *vty); diff --git a/src/host/layer23/src/Makefile.am b/src/host/layer23/src/Makefile.am index 58a5f7fb..3b6a4d8b 100644 --- a/src/host/layer23/src/Makefile.am +++ b/src/host/layer23/src/Makefile.am @@ -1 +1 @@ -SUBDIRS = common misc mobile +SUBDIRS = common misc mobile modem diff --git a/src/host/layer23/src/common/Makefile.am b/src/host/layer23/src/common/Makefile.am index a8d9f7e7..8f5aebaa 100644 --- a/src/host/layer23/src/common/Makefile.am +++ b/src/host/layer23/src/common/Makefile.am @@ -1,6 +1,37 @@ -AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBGPS_CFLAGS) +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOGPRSRLCMAC_CFLAGS) \ + $(LIBOSMOGPRSLLC_CFLAGS) \ + $(LIBOSMOGPRSSNDCP_CFLAGS) \ + $(LIBGPS_CFLAGS) \ + $(NULL) noinst_LIBRARIES = liblayer23.a -liblayer23_a_SOURCES = l1ctl.c l1l2_interface.c sap_fsm.c sap_proto.c sap_interface.c \ - logging.c networks.c sim.c sysinfo.c gps.c l1ctl_lapdm_glue.c utils.c +liblayer23_a_SOURCES = \ + apn.c \ + apn_fsm.c \ + gps.c \ + l1ctl.c \ + l1l2_interface.c \ + l1ctl_lapdm_glue.c \ + logging.c \ + ms.c \ + networks.c \ + sap_fsm.c \ + sap_proto.c \ + sap_interface.c \ + settings.c \ + sim.c \ + subscriber.c \ + support.c \ + sysinfo.c \ + utils.c \ + vty.c \ + $(NULL) diff --git a/src/host/layer23/src/common/apn.c b/src/host/layer23/src/common/apn.c new file mode 100644 index 00000000..cefa7641 --- /dev/null +++ b/src/host/layer23/src/common/apn.c @@ -0,0 +1,121 @@ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * 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. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <string.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> + +#include <talloc.h> + +#include <osmocom/gprs/sm/sm.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/apn.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/common/l23_app.h> + +struct osmobb_apn *apn_alloc(struct osmocom_ms *ms, const char *name) +{ + struct osmobb_apn *apn; + apn = talloc_zero(ms, struct osmobb_apn); + if (!apn) + return NULL; + + if (apn_fsm_ctx_init(&apn->fsm, apn) != 0) + goto ret_free; + + talloc_set_name(apn, "apn_%s", name); + apn->cfg.name = talloc_strdup(apn, name); + apn->cfg.shutdown = true; + apn->cfg.tx_gpdu_seq = true; + + apn->tun = osmo_tundev_alloc(apn, name); + if (!apn->tun) + goto ret_free_fsm; + osmo_tundev_set_priv_data(apn->tun, apn); + + apn->ms = ms; + /* FIXME: may want to configure or pick free one in the future: */ + apn->pdp.nsapi = 6; + apn->pdp.llc_sapi = OSMO_GPRS_SM_LLC_SAPI_SAPI3; + + /* QoS zeroed to 14 bytes is a valid QoS seen sent by some phones. Use + * that as default for now. */ + apn->pdp.qos_len = 14; + + llist_add_tail(&apn->list, &ms->gprs.apn_list); + return apn; + +ret_free_fsm: + apn_fsm_ctx_release(&apn->fsm); +ret_free: + talloc_free(apn); + return NULL; +} + +void apn_free(struct osmobb_apn *apn) +{ + apn_fsm_ctx_release(&apn->fsm); + llist_del(&apn->list); + osmo_tundev_free(apn->tun); + talloc_free(apn); +} + +int apn_start(struct osmobb_apn *apn) +{ + int rc; + + if (apn->started) + return 0; + + LOGPAPN(LOGL_INFO, apn, "Opening TUN device %s\n", apn->cfg.dev_name); + /* Set TUN library callback. Must have been configured by the app: */ + OSMO_ASSERT(l23_app_info.tun_data_ind_cb); + osmo_tundev_set_data_ind_cb(apn->tun, l23_app_info.tun_data_ind_cb); + osmo_tundev_set_dev_name(apn->tun, apn->cfg.dev_name); + osmo_tundev_set_netns_name(apn->tun, apn->cfg.dev_netns_name); + + rc = osmo_tundev_open(apn->tun); + if (rc < 0) { + LOGPAPN(LOGL_ERROR, apn, "Failed to configure tun device\n"); + return -1; + } + + LOGPAPN(LOGL_INFO, apn, "Opened TUN device %s\n", osmo_tundev_get_dev_name(apn->tun)); + + LOGPAPN(LOGL_NOTICE, apn, "Successfully started\n"); + apn->started = true; + return 0; +} + +int apn_stop(struct osmobb_apn *apn) +{ + LOGPAPN(LOGL_NOTICE, apn, "Stopping\n"); + + /* shutdown whatever old state might be left */ + if (apn->tun) { + /* release tun device */ + LOGPAPN(LOGL_INFO, apn, "Closing TUN device %s\n", + osmo_tundev_get_dev_name(apn->tun)); + osmo_tundev_close(apn->tun); + } + + apn->started = false; + return 0; +} diff --git a/src/host/layer23/src/common/apn_fsm.c b/src/host/layer23/src/common/apn_fsm.c new file mode 100644 index 00000000..2b523f1d --- /dev/null +++ b/src/host/layer23/src/common/apn_fsm.c @@ -0,0 +1,244 @@ +/* Lifecycle of an APN */ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * 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 <errno.h> +#include <osmocom/core/tdef.h> +#include <osmocom/core/utils.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/apn_fsm.h> +#include <osmocom/bb/common/apn.h> +#define X(s) (1 << (s)) + +static struct osmo_tdef T_defs_apn[] = { + { .T=1, .default_val=65, .desc = "Activating timeout (s)" }, + { 0 } /* empty item at the end */ +}; + +static const struct osmo_tdef_state_timeout apn_fsm_timeouts[32] = { + [APN_ST_DISABLED] = {}, + [APN_ST_INACTIVE] = {}, + [APN_ST_ACTIVATING] = { .T=1 }, + [APN_ST_ACTIVE] = {}, +}; + +#define apn_fsm_state_chg(fi, NEXT_STATE) \ + osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, apn_fsm_timeouts, T_defs_apn, -1) + +static void st_apn_disabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct apn_fsm_ctx *ctx = (struct apn_fsm_ctx *)fi->priv; + + apn_stop(ctx->apn); +} + +static void st_apn_disabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case APN_EV_GPRS_ALLOWED: + if (*((bool *)data) == true) + apn_fsm_state_chg(fi, APN_ST_INACTIVE); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_apn_inactive_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct apn_fsm_ctx *ctx = (struct apn_fsm_ctx *)fi->priv; + + int rc = apn_start(ctx->apn); + if (rc < 0) + apn_fsm_state_chg(fi, APN_ST_DISABLED); + + /* FIXME: Here once we find a way to store whether the ms object is GMM + attached, we can transition directly to ACTIVATING. */ +} + +static void st_apn_inactive(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case APN_EV_GPRS_ALLOWED: + if (*((bool *)data) == false) + apn_fsm_state_chg(fi, APN_ST_DISABLED); + break; + case APN_EV_GMM_ATTACHED: + apn_fsm_state_chg(fi, APN_ST_ACTIVATING); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_apn_activating_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + /* FIXME: We could send SMREG-PDP_ACT.req from here. Right now that's done by the app. */ +} + +static void st_apn_activating(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case APN_EV_GPRS_ALLOWED: + /* TODO: Tx PDP DEACT ACC */ + apn_fsm_state_chg(fi, APN_ST_DISABLED); + break; + case APN_EV_GMM_DETACHED: + apn_fsm_state_chg(fi, APN_ST_INACTIVE); + break; + case APN_EV_RX_SM_ACT_PDP_CTX_REJ: + apn_fsm_state_chg(fi, APN_ST_INACTIVE); + break; + case APN_EV_RX_SM_ACT_PDP_CTX_ACC: + apn_fsm_state_chg(fi, APN_ST_ACTIVE); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_apn_active_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct apn_fsm_ctx *ctx = (struct apn_fsm_ctx *)fi->priv; + struct osmo_netdev *netdev; + + netdev = osmo_tundev_get_netdev(ctx->apn->tun); + osmo_netdev_ifupdown(netdev, true); +} + +static void st_apn_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case APN_EV_GPRS_ALLOWED: + /* TODO: Tx PDP DEACT ACC */ + apn_fsm_state_chg(fi, APN_ST_DISABLED); + break; + case APN_EV_GMM_DETACHED: + apn_fsm_state_chg(fi, APN_ST_INACTIVE); + break; + case APN_EV_RX_SM_DEACT_PDP_CTX_ACC: + apn_fsm_state_chg(fi, APN_ST_INACTIVE); + break; + default: + OSMO_ASSERT(0); + } +} + +static int apn_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + switch (fi->T) { + case 1: + apn_fsm_state_chg(fi, APN_ST_INACTIVE); + break; + default: + OSMO_ASSERT(0); + } + return 0; +} + +static struct osmo_fsm_state apn_fsm_states[] = { + [APN_ST_DISABLED] = { + .in_event_mask = + X(APN_EV_GPRS_ALLOWED), + .out_state_mask = + X(APN_ST_INACTIVE), + .name = "DISABLED", + .onenter = st_apn_disabled_on_enter, + .action = st_apn_disabled, + }, + [APN_ST_INACTIVE] = { + .in_event_mask = + X(APN_EV_GPRS_ALLOWED) | + X(APN_EV_GMM_ATTACHED), + .out_state_mask = + X(APN_ST_ACTIVATING), + .name = "INACTIVE", + .onenter = st_apn_inactive_on_enter, + .action = st_apn_inactive, + }, + [APN_ST_ACTIVATING] = { + .in_event_mask = + X(APN_EV_GPRS_ALLOWED) | + X(APN_EV_GMM_DETACHED) | + X(APN_EV_RX_SM_ACT_PDP_CTX_REJ) | + X(APN_EV_RX_SM_ACT_PDP_CTX_ACC), + .out_state_mask = + X(APN_ST_DISABLED) | + X(APN_ST_INACTIVE) | + X(APN_ST_ACTIVE), + .name = "ACTIVATING", + .onenter = st_apn_activating_on_enter, + .action = st_apn_activating, + }, + [APN_ST_ACTIVE] = { + .in_event_mask = + X(APN_EV_GPRS_ALLOWED) | + X(APN_EV_GMM_DETACHED)| + X(APN_EV_RX_SM_DEACT_PDP_CTX_ACC), + .out_state_mask = + X(APN_ST_DISABLED) | + X(APN_ST_INACTIVE), + .name = "ACTIVE", + .onenter = st_apn_active_on_enter, + .action = st_apn_active, + }, +}; + +const struct value_string apn_fsm_event_names[] = { + { APN_EV_GPRS_ALLOWED, "GPRS_ALLOWED" }, + { APN_EV_GMM_ATTACHED, "GMM_ATTACHED" }, + { APN_EV_GMM_DETACHED, "GMM_DETACHED" }, + { APN_EV_RX_SM_ACT_PDP_CTX_REJ, "ACT_PDP_CTX_REJ" }, + { APN_EV_RX_SM_ACT_PDP_CTX_ACC, "ACT_PDP_CTX_ACC" }, + { APN_EV_RX_SM_DEACT_PDP_CTX_ACC, "DEACT_PDP_CTX_ACC" }, + { 0, NULL } +}; + +struct osmo_fsm apn_fsm = { + .name = "APN", + .states = apn_fsm_states, + .num_states = ARRAY_SIZE(apn_fsm_states), + .timer_cb = apn_fsm_timer_cb, + .event_names = apn_fsm_event_names, + .log_subsys = DTUN, +}; + +int apn_fsm_ctx_init(struct apn_fsm_ctx *ctx, struct osmobb_apn *apn) +{ + ctx->apn = apn; + ctx->fi = osmo_fsm_inst_alloc(&apn_fsm, apn, ctx, LOGL_INFO, NULL); + if (!ctx->fi) + return -ENODATA; + + return 0; +} + +void apn_fsm_ctx_release(struct apn_fsm_ctx *ctx) +{ + osmo_fsm_inst_free(ctx->fi); +} + +static __attribute__((constructor)) void apn_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&apn_fsm) == 0); + osmo_tdefs_reset(T_defs_apn); +} diff --git a/src/host/layer23/src/common/gps.c b/src/host/layer23/src/common/gps.c index 35ee4167..3c69352c 100644 --- a/src/host/layer23/src/common/gps.c +++ b/src/host/layer23/src/common/gps.c @@ -13,10 +13,6 @@ * 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 <stdio.h> @@ -33,6 +29,7 @@ #endif #include <osmocom/core/utils.h> +#include <osmocom/core/select.h> #include <osmocom/bb/common/osmocom_data.h> #include <osmocom/bb/common/logging.h> @@ -82,7 +79,11 @@ int osmo_gpsd_cb(struct osmo_fd *bfd, unsigned int what) g.valid = 0; /* gps is offline */ +#if GPSD_API_MAJOR_VERSION >= 9 && GPSD_API_MINOR_VERSION >= 0 + if (gdata->online.tv_sec || gdata->online.tv_nsec) +#else if (gdata->online) +#endif goto gps_not_ready; #if GPSD_API_MAJOR_VERSION >= 5 @@ -102,7 +103,11 @@ int osmo_gpsd_cb(struct osmo_fd *bfd, unsigned int what) /* data are valid */ if (gdata->set & LATLON_SET) { g.valid = 1; +#if GPSD_API_MAJOR_VERSION >= 9 && GPSD_API_MINOR_VERSION >= 0 + g.gmt = gdata->fix.time.tv_sec; +#else g.gmt = gdata->fix.time; +#endif tm = localtime(&g.gmt); diff = time(NULL) - g.gmt; g.latitude = gdata->fix.latitude; @@ -129,10 +134,6 @@ int osmo_gpsd_open(void) { LOGP(DGPS, LOGL_INFO, "Connecting to gpsd at '%s:%s'\n", g.gpsd_host, g.gpsd_port); - gps_bfd.data = NULL; - gps_bfd.when = BSC_FD_READ; - gps_bfd.cb = osmo_gpsd_cb; - #if GPSD_API_MAJOR_VERSION >= 5 if (gps_open(g.gpsd_host, g.gpsd_port, &_gdata) == -1) gdata = NULL; @@ -145,15 +146,15 @@ int osmo_gpsd_open(void) LOGP(DGPS, LOGL_ERROR, "Can't connect to gpsd\n"); return -1; } - gps_bfd.fd = gdata->gps_fd; - if (gps_bfd.fd < 0) - return gps_bfd.fd; + if (gdata->gps_fd < 0) + return gdata->gps_fd; if (gps_stream(gdata, WATCH_ENABLE, NULL) == -1) { LOGP(DGPS, LOGL_ERROR, "Error in gps_stream()\n"); return -1; } + osmo_fd_setup(&gps_bfd, gdata->gps_fd, OSMO_FD_READ, osmo_gpsd_cb, NULL, 0); osmo_fd_register(&gps_bfd); return 0; @@ -312,18 +313,17 @@ int osmo_serialgps_cb(struct osmo_fd *bfd, unsigned int what) int osmo_serialgps_open(void) { int baud = 0; + int fd; if (gps_bfd.fd > 0) return 0; LOGP(DGPS, LOGL_INFO, "Open GPS device '%s'\n", g.device); - gps_bfd.data = NULL; - gps_bfd.when = BSC_FD_READ; - gps_bfd.cb = osmo_serialgps_cb; - gps_bfd.fd = open(g.device, O_RDONLY); - if (gps_bfd.fd < 0) - return gps_bfd.fd; + fd = open(g.device, O_RDONLY); + if (fd < 0) + return fd; + osmo_fd_setup(&gps_bfd, fd, OSMO_FD_READ, osmo_serialgps_cb, NULL, 0); switch (g.baud) { case 4800: diff --git a/src/host/layer23/src/common/l1ctl.c b/src/host/layer23/src/common/l1ctl.c index 5d6d9c0c..94979103 100644 --- a/src/host/layer23/src/common/l1ctl.c +++ b/src/host/layer23/src/common/l1ctl.c @@ -15,10 +15,6 @@ * 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 <stdio.h> @@ -41,14 +37,63 @@ #include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/gsm/protocol/gsm_08_58.h> #include <osmocom/gsm/rsl.h> +#include <osmocom/gsm/lapdm.h> +#include <osmocom/gsm/gsm0502.h> #include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/l23_app.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/l1l2_interface.h> -#include <osmocom/gsm/lapdm.h> #include <osmocom/bb/common/logging.h> -extern struct gsmtap_inst *gsmtap_inst; +/* determine the CCCH block number based on the frame number */ +static unsigned int fn2ccch_block(uint32_t fn) +{ + int rc = gsm0502_fn2ccch_block(fn); + /* if FN is negative, we were called for something that's not CCCH! */ + OSMO_ASSERT(rc >= 0); + return rc; +} + +static uint8_t chantype_rsl2gsmtap_ext(uint8_t rsl_chantype, uint8_t link_id, uint32_t fn, uint8_t num_agch) +{ + uint8_t ret = chantype_rsl2gsmtap2(rsl_chantype, link_id, false); + if (ret != GSMTAP_CHANNEL_PCH) + return ret; + + if (fn2ccch_block(fn) >= num_agch) + return GSMTAP_CHANNEL_PCH; + return GSMTAP_CHANNEL_AGCH; +} + +static const uint8_t fill_frame[GSM_MACBLOCK_LEN] = { + 0x03, 0x03, 0x01, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, + 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, + 0x2B, 0x2B, 0x2B +}; + +/* Paging Request 1 with "no identity" content, i.e. empty/dummy paging */ +static const uint8_t paging_fill[GSM_MACBLOCK_LEN] = { + 0x15, 0x06, 0x21, 0x00, 0x01, 0xf0, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b }; + +static bool is_fill_frame(uint8_t chan_type, const uint8_t *data) +{ + switch (chan_type) { + case GSMTAP_CHANNEL_AGCH: + if (!memcmp(data, fill_frame, GSM_MACBLOCK_LEN)) + return true; + break; + case GSMTAP_CHANNEL_PCH: + if (!memcmp(data, paging_fill, GSM_MACBLOCK_LEN)) + return true; + break; + /* don't use 'default' case here as the above only conditionally return true */ + } + return false; +} static struct msgb *osmo_l1_alloc(uint8_t msg_type) { @@ -63,7 +108,7 @@ static struct msgb *osmo_l1_alloc(uint8_t msg_type) msg->l1h = msgb_put(msg, sizeof(*l1h)); l1h = (struct l1ctl_hdr *) msg->l1h; l1h->msg_type = msg_type; - + return msg; } @@ -145,6 +190,7 @@ static int rx_ph_data_ind(struct osmocom_ms *ms, struct msgb *msg) struct rx_meas_stat *meas = &ms->meas; uint8_t chan_type, chan_ts, chan_ss; uint8_t gsmtap_chan_type; + uint8_t bs_ag_blks_res; struct gsm_time tm; if (msgb_l1len(msg) < sizeof(*dl)) { @@ -159,7 +205,13 @@ static int rx_ph_data_ind(struct osmocom_ms *ms, struct msgb *msg) ccch = (struct l1ctl_data_ind *) msg->l2h; gsm_fn2gsmtime(&tm, ntohl(dl->frame_nr)); - rsl_dec_chan_nr(dl->chan_nr, &chan_type, &chan_ss, &chan_ts); + if (rsl_dec_chan_nr(dl->chan_nr, &chan_type, &chan_ss, &chan_ts) != 0) { + LOGP(DL1C, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, dl->chan_nr); + return -EINVAL; + } + DEBUGP(DL1C, "%s (%.4u/%.2u/%.2u) %d dBm: %s\n", rsl_chan_nr_str(dl->chan_nr), tm.t1, tm.t2, tm.t3, (int)dl->rx_level-110, @@ -229,11 +281,31 @@ static int rx_ph_data_ind(struct osmocom_ms *ms, struct msgb *msg) return 0; } - /* send CCCH data via GSMTAP */ - gsmtap_chan_type = chantype_rsl2gsmtap(chan_type, dl->link_id); - gsmtap_send(gsmtap_inst, ntohs(dl->band_arfcn), chan_ts, - gsmtap_chan_type, chan_ss, tm.fn, dl->rx_level-110, - dl->snr, ccch->data, sizeof(ccch->data)); + /* May not be initialized in some applications (e.g. ccch_scan) */ + if (ms->cellsel.si != NULL) + bs_ag_blks_res = ms->cellsel.si->bs_ag_blks_res; + else /* fall-back to 1 (this is what OsmoBTS does) */ + bs_ag_blks_res = 1; + + gsmtap_chan_type = chantype_rsl2gsmtap_ext(chan_type, dl->link_id, tm.fn, bs_ag_blks_res); + /* don't log fill frames via GSMTAP; they serve no purpose other than + * to clog up your logs */ + if (!is_fill_frame(gsmtap_chan_type, ccch->data)) { + /* send CCCH data via GSMTAP */ + gsmtap_send(l23_cfg.gsmtap.inst, ntohs(dl->band_arfcn), chan_ts, + gsmtap_chan_type, chan_ss, tm.fn, dl->rx_level-110, + dl->snr, ccch->data, sizeof(ccch->data)); + } + + /* Do not pass PDCH and CBCH frames to LAPDm */ + switch (chan_type) { + case RSL_CHAN_OSMO_PDCH: + case RSL_CHAN_OSMO_CBCH4: + case RSL_CHAN_OSMO_CBCH8: + /* TODO: pass directly to l23 application */ + msgb_free(msg); + return 0; + } /* determine LAPDm entity based on SACCH or not */ if (dl->link_id & 0x40) @@ -245,6 +317,7 @@ static int rx_ph_data_ind(struct osmocom_ms *ms, struct msgb *msg) PRIM_OP_INDICATION, msg); pp.u.data.chan_nr = dl->chan_nr; pp.u.data.link_id = dl->link_id; + pp.u.data.fn = tm.fn; /* send it up into LAPDm */ return lapdm_phsap_up(&pp.oph, le); @@ -284,7 +357,6 @@ int l1ctl_tx_data_req(struct osmocom_ms *ms, struct msgb *msg, struct l1ctl_hdr *l1h; struct l1ctl_info_ul *l1i_ul; uint8_t chan_type, chan_ts, chan_ss; - uint8_t gsmtap_chan_type; DEBUGP(DL1C, "(%s)\n", osmo_hexdump(msg->l2h, msgb_l2len(msg))); @@ -296,10 +368,16 @@ int l1ctl_tx_data_req(struct osmocom_ms *ms, struct msgb *msg, } /* send copy via GSMTAP */ - rsl_dec_chan_nr(chan_nr, &chan_type, &chan_ss, &chan_ts); - gsmtap_chan_type = chantype_rsl2gsmtap(chan_type, link_id); - gsmtap_send(gsmtap_inst, 0|0x4000, chan_ts, gsmtap_chan_type, - chan_ss, 0, 127, 255, msg->l2h, msgb_l2len(msg)); + if (rsl_dec_chan_nr(chan_nr, &chan_type, &chan_ss, &chan_ts) == 0) { + uint8_t gsmtap_chan_type = chantype_rsl2gsmtap2(chan_type, link_id, false); + gsmtap_send(l23_cfg.gsmtap.inst, ms->rrlayer.cd_now.arfcn | GSMTAP_ARFCN_F_UPLINK, + chan_ts, gsmtap_chan_type, chan_ss, 0, 127, 0, + msg->l2h, msgb_l2len(msg)); + } else { + LOGP(DL1C, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, chan_nr); + } /* prepend uplink info header */ l1i_ul = (struct l1ctl_info_ul *) msgb_push(msg, sizeof(*l1i_ul)); @@ -365,8 +443,8 @@ int l1ctl_tx_ccch_mode_req(struct osmocom_ms *ms, uint8_t ccch_mode) } /* Transmit L1CTL_TCH_MODE_REQ */ -int l1ctl_tx_tch_mode_req(struct osmocom_ms *ms, uint8_t tch_mode, - uint8_t audio_mode) +int l1ctl_tx_tch_mode_req(struct osmocom_ms *ms, uint8_t tch_mode, uint8_t audio_mode, uint8_t tch_flags, + uint8_t tch_loop_mode) { struct msgb *msg; struct l1ctl_tch_mode_req *req; @@ -380,6 +458,9 @@ int l1ctl_tx_tch_mode_req(struct osmocom_ms *ms, uint8_t tch_mode, req = (struct l1ctl_tch_mode_req *) msgb_put(msg, sizeof(*req)); req->tch_mode = tch_mode; req->audio_mode = audio_mode; + req->tch_flags = tch_flags; + req->tch_loop_mode = tch_loop_mode; + /* TODO: Set AMR codec in req if req->tch_mode==GSM48_CMODE_SPEECH_AMR */ return osmo_send_l1(ms, msg); } @@ -431,8 +512,9 @@ int l1ctl_tx_crypto_req(struct osmocom_ms *ms, uint8_t chan_nr, } /* Transmit L1CTL_RACH_REQ */ -int l1ctl_tx_rach_req(struct osmocom_ms *ms, uint8_t ra, uint16_t offset, - uint8_t combined) +int l1ctl_tx_rach_req(struct osmocom_ms *ms, + uint8_t chan_nr, uint8_t link_id, + uint8_t ra, uint16_t offset, uint8_t combined, uint8_t uic) { struct msgb *msg; struct l1ctl_info_ul *ul; @@ -442,20 +524,22 @@ int l1ctl_tx_rach_req(struct osmocom_ms *ms, uint8_t ra, uint16_t offset, if (!msg) return -1; - DEBUGP(DL1C, "RACH Req. offset=%d combined=%d\n", offset, combined); + DEBUGP(DL1C, "RACH Req. offset=%d combined=%d uic=0x%02x\n", offset, combined, uic); ul = (struct l1ctl_info_ul *) msgb_put(msg, sizeof(*ul)); + ul->chan_nr = chan_nr; + ul->link_id = link_id; req = (struct l1ctl_rach_req *) msgb_put(msg, sizeof(*req)); req->ra = ra; req->offset = htons(offset); req->combined = combined; + req->uic = uic; return osmo_send_l1(ms, msg); } /* Transmit L1CTL_DM_EST_REQ */ -int l1ctl_tx_dm_est_req_h0(struct osmocom_ms *ms, uint16_t band_arfcn, - uint8_t chan_nr, uint8_t tsc, uint8_t tch_mode, - uint8_t audio_mode) +int l1ctl_tx_dm_est_req_h0(struct osmocom_ms *ms, uint16_t band_arfcn, uint8_t chan_nr, uint8_t tsc, uint8_t tch_mode, + uint8_t audio_mode, uint8_t tch_flags) { struct msgb *msg; struct l1ctl_info_ul *ul; @@ -478,14 +562,13 @@ int l1ctl_tx_dm_est_req_h0(struct osmocom_ms *ms, uint16_t band_arfcn, req->h0.band_arfcn = htons(band_arfcn); req->tch_mode = tch_mode; req->audio_mode = audio_mode; + req->tch_flags = tch_flags; return osmo_send_l1(ms, msg); } -int l1ctl_tx_dm_est_req_h1(struct osmocom_ms *ms, uint8_t maio, uint8_t hsn, - uint16_t *ma, uint8_t ma_len, - uint8_t chan_nr, uint8_t tsc, uint8_t tch_mode, - uint8_t audio_mode) +int l1ctl_tx_dm_est_req_h1(struct osmocom_ms *ms, uint8_t maio, uint8_t hsn, uint16_t *ma, uint8_t ma_len, + uint8_t chan_nr, uint8_t tsc, uint8_t tch_mode, uint8_t audio_mode, uint8_t tch_flags) { struct msgb *msg; struct l1ctl_info_ul *ul; @@ -513,6 +596,7 @@ int l1ctl_tx_dm_est_req_h1(struct osmocom_ms *ms, uint8_t maio, uint8_t hsn, req->h1.ma[i] = htons(ma[i]); req->tch_mode = tch_mode; req->audio_mode = audio_mode; + req->tch_flags = tch_flags; return osmo_send_l1(ms, msg); } @@ -630,13 +714,10 @@ int l1ctl_tx_sim_req(struct osmocom_ms *ms, uint8_t *data, uint16_t length) /* just forward the SIM response to the SIM handler */ static int rx_l1_sim_conf(struct osmocom_ms *ms, struct msgb *msg) { - uint16_t len = msgb_l2len(msg); - uint8_t *data = msg->data; - - LOGP(DL1C, LOGL_INFO, "SIM %s\n", osmo_hexdump(data, len)); + LOGP(DL1C, LOGL_INFO, "SIM %s\n", msgb_hexdump_l1(msg)); sim_apdu_resp(ms, msg); - + return 0; } @@ -751,6 +832,7 @@ static int rx_l1_tch_mode_conf(struct osmocom_ms *ms, struct msgb *msg) mc.tch_mode = conf->tch_mode; mc.audio_mode = conf->audio_mode; + mc.tch_flags = conf->tch_flags; mc.ms = ms; osmo_signal_dispatch(SS_L1CTL, S_L1CTL_TCH_MODE_CONF, &mc); @@ -762,7 +844,6 @@ static int rx_l1_traffic_ind(struct osmocom_ms *ms, struct msgb *msg) { struct l1ctl_info_dl *dl; struct l1ctl_traffic_ind *ti; - size_t frame_len; uint8_t *frame; if (msgb_l1len(msg) < sizeof(*dl)) { @@ -779,11 +860,8 @@ static int rx_l1_traffic_ind(struct osmocom_ms *ms, struct msgb *msg) msg->l2h = dl->payload; msg->l3h = frame; - /* Calculate the frame length */ - frame_len = msgb_l3len(msg); - - DEBUGP(DL1C, "TRAFFIC IND len=%zu (%s)\n", frame_len, - osmo_hexdump(frame, frame_len)); + LOGP(DL1C, LOGL_DEBUG, "Rx TRAFFIC.ind (fn=%u, chan_nr=0x%02x, len=%u): %s\n", + ntohl(dl->frame_nr), dl->chan_nr, msgb_l3len(msg), msgb_hexdump_l3(msg)); /* distribute or drop */ if (ms->l1_entity.l1_traffic_ind) @@ -800,7 +878,6 @@ int l1ctl_tx_traffic_req(struct osmocom_ms *ms, struct msgb *msg, struct l1ctl_hdr *l1h; struct l1ctl_info_ul *l1i_ul; struct l1ctl_traffic_req *tr; - size_t frame_len; uint8_t *frame; /* Header handling */ @@ -808,20 +885,8 @@ int l1ctl_tx_traffic_req(struct osmocom_ms *ms, struct msgb *msg, frame = (uint8_t *) tr->data; msg->l3h = frame; - /* Calculate the frame length */ - frame_len = msgb_l3len(msg); - - DEBUGP(DL1C, "TRAFFIC REQ len=%zu (%s)\n", frame_len, - osmo_hexdump(frame, frame_len)); - - if ((frame[0] >> 4) != 0xd) { - LOGP(DL1C, LOGL_ERROR, "Traffic Request has incorrect magic " - "(%u != 0xd)\n", frame[0] >> 4); - msgb_free(msg); - return -EINVAL; - } - -// printf("TX %s\n", osmo_hexdump(frame, frame_len)); + LOGP(DL1C, LOGL_DEBUG, "Tx TRAFFIC.req (chan_nr=0x%02x, len=%u): %s\n", + chan_nr, msgb_l3len(msg), msgb_hexdump_l3(msg)); /* prepend uplink info header */ l1i_ul = (struct l1ctl_info_ul *) msgb_push(msg, sizeof(*l1i_ul)); @@ -884,6 +949,190 @@ static int rx_l1_neigh_pm_ind(struct osmocom_ms *ms, struct msgb *msg) return 0; } +/* Receive L1CTL_GPRS_UL_BLOCK_CNF */ +static int rx_l1_gprs_ul_block_cnf(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct l1ctl_gprs_ul_block_cnf *cnf = (void *)msg->l1h; + + if (msgb_l1len(msg) < sizeof(*cnf)) { + LOGP(DL1C, LOGL_ERROR, + "Rx malformed GPRS UL BLOCK.cnf (len=%u < %zu)\n", + msgb_l1len(msg), sizeof(*cnf)); + return -EINVAL; + } + if (OSMO_UNLIKELY(cnf->tn >= 8)) { + LOGP(DL1C, LOGL_ERROR, + "Rx malformed GPRS UL BLOCK.cnf (tn=%u)\n", cnf->tn); + return -EINVAL; + } + + msg->l2h = (void *)&cnf->data[0]; + + DEBUGP(DL1C, "Rx GPRS UL BLOCK.cnf (fn=%u, tn=%u, len=%u): %s\n", + ntohl(cnf->fn), cnf->tn, msgb_l2len(msg), msgb_hexdump_l2(msg)); + + /* distribute or drop */ + if (ms->l1_entity.l1_gprs_ul_block_cnf) + return ms->l1_entity.l1_gprs_ul_block_cnf(ms, msg); + + msgb_free(msg); + return 0; +} + +/* Receive L1CTL_GPRS_DL_BLOCK_IND */ +static int rx_l1_gprs_dl_block_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct l1ctl_gprs_dl_block_ind *ind = (void *)msg->l1h; + uint8_t gsmtap_chan; + uint32_t fn; + + if (msgb_l1len(msg) < sizeof(*ind)) { + LOGP(DL1C, LOGL_ERROR, + "Rx malformed GPRS DL BLOCK.ind (len=%u < %zu)\n", + msgb_l1len(msg), sizeof(*ind)); + return -EINVAL; + } + if (OSMO_UNLIKELY(ind->hdr.tn >= 8)) { + LOGP(DL1C, LOGL_ERROR, + "Rx malformed GPRS DL BLOCK.ind (tn=%u)\n", + ind->hdr.tn); + return -EINVAL; + } + + msg->l2h = (void *)&ind->data[0]; + + fn = ntohl(ind->hdr.fn); + if ((fn % 104) == 12) + gsmtap_chan = GSMTAP_CHANNEL_PTCCH; + else + gsmtap_chan = GSMTAP_CHANNEL_PDTCH; + + gsmtap_send(l23_cfg.gsmtap.inst, + ms->rrlayer.cd_now.arfcn, + ind->hdr.tn, gsmtap_chan, 0, fn, + rxlev2dbm(ind->meas.rx_lev), 0, + msgb_l2(msg), msgb_l2len(msg)); + + DEBUGP(DL1C, "Rx GPRS DL BLOCK.ind (fn=%u, tn=%u, len=%u): %s\n", + fn, ind->hdr.tn, msgb_l2len(msg), msgb_hexdump_l2(msg)); + + /* distribute or drop */ + if (ms->l1_entity.l1_gprs_dl_block_ind) + return ms->l1_entity.l1_gprs_dl_block_ind(ms, msg); + + msgb_free(msg); + return 0; +} + +/* Receive L1CTL_GPRS_RTS_IND */ +static int rx_l1_gprs_rts_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct l1ctl_gprs_rts_ind *ind = (void *)msg->l1h; + + if (msgb_l1len(msg) < sizeof(*ind)) { + LOGP(DL1C, LOGL_ERROR, + "Rx malformed GPRS RTS.ind (len=%u < %zu)\n", + msgb_l1len(msg), sizeof(*ind)); + return -EINVAL; + } + if (OSMO_UNLIKELY(ind->tn >= 8)) { + LOGP(DL1C, LOGL_ERROR, + "Rx malformed GPRS RTS.ind (tn=%u)\n", + ind->tn); + return -EINVAL; + } + + DEBUGP(DL1C, "Rx GPRS RTS.ind (fn=%u, tn=%u, usf=%u)\n", + ntohl(ind->fn), ind->tn, ind->usf); + + /* distribute or drop */ + if (ms->l1_entity.l1_gprs_rts_ind) + return ms->l1_entity.l1_gprs_rts_ind(ms, msg); + + msgb_free(msg); + return 0; +} + +/* Transmit L1CTL_GPRS_UL_BLOCK_REQ */ +int l1ctl_tx_gprs_ul_block_req(struct osmocom_ms *ms, uint32_t fn, uint8_t tn, + const uint8_t *data, size_t data_len) +{ + struct l1ctl_gprs_ul_block_req *req; + struct msgb *msg; + + msg = osmo_l1_alloc(L1CTL_GPRS_UL_BLOCK_REQ); + if (!msg) + return -ENOMEM; + + req = (void *)msgb_put(msg, sizeof(*req)); + req->hdr.fn = htonl(fn); + req->hdr.tn = tn; + if (data_len > 0) + memcpy(msgb_put(msg, data_len), data, data_len); + + DEBUGP(DL1C, "Tx GPRS UL block (fn=%u, tn=%u, len=%zu): %s\n", + fn, tn, data_len, osmo_hexdump(data, data_len)); + + gsmtap_send(l23_cfg.gsmtap.inst, + ms->rrlayer.cd_now.arfcn | GSMTAP_ARFCN_F_UPLINK, + tn, GSMTAP_CHANNEL_PDTCH, 0, fn, 127, 0, + data, data_len); + + return osmo_send_l1(ms, msg); +} + +/* Transmit L1CTL_GPRS_UL_TBF_CFG_REQ */ +int l1ctl_tx_gprs_ul_tbf_cfg_req(struct osmocom_ms *ms, uint8_t tbf_ref, + uint8_t slotmask, uint32_t start_fn) +{ + struct l1ctl_gprs_ul_tbf_cfg_req *req; + struct msgb *msg; + + msg = osmo_l1_alloc(L1CTL_GPRS_UL_TBF_CFG_REQ); + if (!msg) + return -ENOMEM; + + req = (void *)msgb_put(msg, sizeof(*req)); + *req = (struct l1ctl_gprs_ul_tbf_cfg_req) { + .tbf_ref = tbf_ref, + .slotmask = slotmask, + .start_fn = htonl(start_fn), + }; + + DEBUGP(DL1C, "Tx GPRS UL TBF CFG: " + "tbf_ref=%u, slotmask=0x%02x, start_fn=%u\n", + tbf_ref, slotmask, start_fn); + + return osmo_send_l1(ms, msg); +} + +/* Transmit L1CTL_GPRS_DL_TBF_CFG_REQ */ +int l1ctl_tx_gprs_dl_tbf_cfg_req(struct osmocom_ms *ms, uint8_t tbf_ref, + uint8_t slotmask, uint32_t start_fn, + uint8_t dl_tfi) +{ + struct l1ctl_gprs_dl_tbf_cfg_req *req; + struct msgb *msg; + + msg = osmo_l1_alloc(L1CTL_GPRS_DL_TBF_CFG_REQ); + if (!msg) + return -ENOMEM; + + req = (void *)msgb_put(msg, sizeof(*req)); + *req = (struct l1ctl_gprs_dl_tbf_cfg_req) { + .tbf_ref = tbf_ref, + .slotmask = slotmask, + .start_fn = htonl(start_fn), + .dl_tfi = dl_tfi, + }; + + DEBUGP(DL1C, "Tx GPRS DL TBF CFG: " + "tbf_ref=%u, slotmask=0x%02x, start_fn=%u, dl_tfi=%u)\n", + tbf_ref, slotmask, start_fn, dl_tfi); + + return osmo_send_l1(ms, msg); +} + /* Receive incoming data from L1 using L1CTL format */ int l1ctl_recv(struct osmocom_ms *ms, struct msgb *msg) { @@ -952,6 +1201,15 @@ int l1ctl_recv(struct osmocom_ms *ms, struct msgb *msg) case L1CTL_TRAFFIC_CONF: msgb_free(msg); break; + case L1CTL_GPRS_UL_BLOCK_CNF: + rc = rx_l1_gprs_ul_block_cnf(ms, msg); + break; + case L1CTL_GPRS_DL_BLOCK_IND: + rc = rx_l1_gprs_dl_block_ind(ms, msg); + break; + case L1CTL_GPRS_RTS_IND: + rc = rx_l1_gprs_rts_ind(ms, msg); + break; default: LOGP(DL1C, LOGL_ERROR, "Unknown MSG: %u\n", hdr->msg_type); msgb_free(msg); diff --git a/src/host/layer23/src/common/l1ctl_lapdm_glue.c b/src/host/layer23/src/common/l1ctl_lapdm_glue.c index 0b2a8ed5..458cc81f 100644 --- a/src/host/layer23/src/common/l1ctl_lapdm_glue.c +++ b/src/host/layer23/src/common/l1ctl_lapdm_glue.c @@ -14,16 +14,14 @@ * 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 <stdint.h> +#include <errno.h> #include <l1ctl_proto.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> #include <osmocom/gsm/prim.h> #include <osmocom/bb/common/l1ctl.h> @@ -50,9 +48,12 @@ int l1ctl_ph_prim_cb(struct osmo_prim_hdr *oph, void *ctx) case PRIM_PH_RACH: l1ctl_tx_param_req(ms, pp->u.rach_req.ta, pp->u.rach_req.tx_power); - rc = l1ctl_tx_rach_req(ms, pp->u.rach_req.ra, + rc = l1ctl_tx_rach_req(ms, + RSL_CHAN_RACH, 0x00, + pp->u.rach_req.ra, pp->u.rach_req.offset, - pp->u.rach_req.is_combined_ccch); + pp->u.rach_req.is_combined_ccch, + 0xff); break; default: rc = -EINVAL; diff --git a/src/host/layer23/src/common/l1l2_interface.c b/src/host/layer23/src/common/l1l2_interface.c index c07b0a1d..8e270522 100644 --- a/src/host/layer23/src/common/l1l2_interface.c +++ b/src/host/layer23/src/common/l1l2_interface.c @@ -15,19 +15,17 @@ * 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 <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/l1ctl.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/l1l2_interface.h> #include <osmocom/core/utils.h> #include <osmocom/core/socket.h> +#include <osmocom/core/select.h> #include <sys/socket.h> #include <sys/un.h> @@ -62,6 +60,7 @@ static int layer2_read(struct osmo_fd *fd) if (rc >= 0) rc = -EIO; layer2_close((struct osmocom_ms *) fd->data); + exit(102); return rc; } @@ -127,9 +126,9 @@ int layer2_close(struct osmocom_ms *ms) if (ms->l2_wq.bfd.fd <= 0) return -EINVAL; + osmo_fd_unregister(&ms->l2_wq.bfd); close(ms->l2_wq.bfd.fd); ms->l2_wq.bfd.fd = -1; - osmo_fd_unregister(&ms->l2_wq.bfd); osmo_wqueue_clear(&ms->l2_wq); return 0; diff --git a/src/host/layer23/src/common/logging.c b/src/host/layer23/src/common/logging.c index ed799913..283b7f1a 100644 --- a/src/host/layer23/src/common/logging.c +++ b/src/host/layer23/src/common/logging.c @@ -14,10 +14,6 @@ * 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. - * */ @@ -36,7 +32,7 @@ static const struct log_info_cat default_categories[] = { .name = "DCS", .description = "Cell selection", .color = "\033[34m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DNB] = { .name = "DNB", @@ -48,54 +44,66 @@ static const struct log_info_cat default_categories[] = { .name = "DPLMN", .description = "PLMN selection", .color = "\033[32m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DRR] = { .name = "DRR", .description = "Radio Resource", .color = "\033[1;34m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DMM] = { .name = "DMM", .description = "Mobility Management", .color = "\033[1;32m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DCC] = { .name = "DCC", .description = "Call Control", .color = "\033[1;33m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DGCC] = { + .name = "DGCC", + .description = "Group Call Control", + .color = "\033[1;33m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DBCC] = { + .name = "DBCC", + .description = "Broadcast Call Control", + .color = "\033[1;33m", + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DSS] = { .name = "DSS", .description = "Supplenmentary Services", .color = "\033[1;35m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DSMS] = { .name = "DSMS", .description = "Short Message Service", .color = "\033[1;37m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DMNCC] = { .name = "DMNCC", .description = "Mobile Network Call Control", .color = "\033[1;37m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DMEAS] = { .name = "DMEAS", .description = "MEasurement Reporting", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DPAG] = { .name = "DPAG", .description = "Paging", .color = "\033[33m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DL1C] = { .name = "DL1C", @@ -107,37 +115,37 @@ static const struct log_info_cat default_categories[] = { .name = "DSAP", .description = "SAP Control", .color = "\033[1;31m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DSUM] = { .name = "DSUM", .description = "Summary of Process", .color = "\033[1;37m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DSIM] = { .name = "DSIM", .description = "SIM client", .color = "\033[0;35m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DGPS] = { .name = "DGPS", .description = "GPS", .color = "\033[1;35m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DMOB] = { .name = "DMOB", .description = "Mobile", .color = "\033[1;35m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DPRIM] = { .name = "DPRIM", .description = "PRIM", .color = "\033[1;32m", - .enabled = 1, .loglevel = LOGL_DEBUG, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DLUA] = { .name = "DLUA", @@ -145,6 +153,54 @@ static const struct log_info_cat default_categories[] = { .color = "\033[1;32m", .enabled = 1, .loglevel = LOGL_DEBUG, }, + [DGAPK] = { + .name = "DGAPK", + .description = "GAPK audio", + .color = "\033[0;36m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DCSD] = { + .name = "DCSD", + .description = "Circuit Switched Data", + .color = "\033[0;36m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DTUN] = { + .name = "DTUN", + .description = "Tunnel interface", + .color = "\033[0;37m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DRLCMAC] = { + .name = "DRLCMAC", + .description = "Radio Link Control / Medium Access Control (RLC/MAC)", + .color = "\033[0;38m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DLLC] = { + .name = "DLLC", + .description = "GPRS Logical Link Control Protocol (LLC)", + .color = "\033[0;39m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DSNDCP] = { + .name = "DSNDCP", + .description = "GPRS Sub-Network Dependent Control Protocol (SNDCP)", + .color = "\033[0;40m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DGMM] = { + .name = "DGMM", + .description = "GPRS Mobility Management (GMM)", + .color = "\033[0;32m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DSM] = { + .name = "DSM", + .description = "GPRS Session Management (SM)", + .color = "\033[0;31m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, }; const struct log_info log_info = { diff --git a/src/host/layer23/src/common/main.c b/src/host/layer23/src/common/main.c index 8abd3f79..919a2315 100644 --- a/src/host/layer23/src/common/main.c +++ b/src/host/layer23/src/common/main.c @@ -15,27 +15,29 @@ * 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 <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/l1ctl.h> #include <osmocom/bb/common/l1l2_interface.h> #include <osmocom/bb/common/sap_interface.h> #include <osmocom/bb/misc/layer3.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/l23_app.h> +#include <osmocom/bb/common/vty.h> #include <osmocom/core/msgb.h> #include <osmocom/core/talloc.h> #include <osmocom/core/select.h> #include <osmocom/core/linuxlist.h> +#include <osmocom/core/application.h> #include <osmocom/core/gsmtap_util.h> -#include <osmocom/core/gsmtap.h> #include <osmocom/core/utils.h> +#include <osmocom/vty/vty.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/ports.h> #include <arpa/inet.h> @@ -47,77 +49,56 @@ #include <fcntl.h> #include <signal.h> -struct log_target *stderr_target; +#include "config.h" void *l23_ctx = NULL; +struct l23_global_config l23_cfg; -static char *layer2_socket_path = "/tmp/osmocom_l2"; static char *sap_socket_path = "/tmp/osmocom_sap"; struct llist_head ms_list; -static struct osmocom_ms *ms = NULL; static char *gsmtap_ip = NULL; -static char *vty_ip = "127.0.0.1"; +static char *config_file = NULL; +static char *log_cat_mask = NULL; -unsigned short vty_port = 4247; -int (*l23_app_work) (struct osmocom_ms *ms) = NULL; -int (*l23_app_exit) (struct osmocom_ms *ms) = NULL; +int (*l23_app_start)(void) = NULL; +int (*l23_app_work)(void) = NULL; +int (*l23_app_exit)(void) = NULL; int quit = 0; -struct gsmtap_inst *gsmtap_inst; - -const char *openbsc_copyright = - "%s" - "%s\n" - "License GPLv2+: GNU GPL version 2 or later " - "<http://gnu.org/licenses/gpl.html>\n" - "This is free software: you are free to change and redistribute it.\n" - "There is NO WARRANTY, to the extent permitted by law.\n\n"; static void print_usage(const char *app) { printf("Usage: %s\n", app); } -static void print_help() +static void print_help(void) { - int options = 0xff; - struct l23_app_info *app = l23_app_info(); - - if (app && app->cfg_supported != 0) - options = app->cfg_supported(); - printf(" Some help...\n"); printf(" -h --help this text\n"); printf(" -s --socket /tmp/osmocom_l2. Path to the unix " "domain socket (l2)\n"); - if (options & L23_OPT_SAP) + if (l23_app_info.opt_supported & L23_OPT_SAP) printf(" -S --sap /tmp/osmocom_sap. Path to the " "unix domain socket (BTSAP)\n"); - if (options & L23_OPT_ARFCN) + if (l23_app_info.opt_supported & L23_OPT_ARFCN) printf(" -a --arfcn NR The ARFCN to be used for layer2.\n"); - if (options & L23_OPT_TAP) + if (l23_app_info.opt_supported & L23_OPT_TAP) printf(" -i --gsmtap-ip The destination IP used for GSMTAP.\n"); - if (options & L23_OPT_VTY) - printf(" -v --vty-port The VTY port number to telnet " - "to. (default %u)\n", vty_port); + if (l23_app_info.opt_supported & L23_OPT_VTY) + printf(" -c --config-file The path to the VTY configuration file.\n"); - if (options & L23_OPT_DBG) + if (l23_app_info.opt_supported & L23_OPT_DBG) printf(" -d --debug Change debug flags.\n"); - if (options & L23_OPT_VTYIP) - printf(" -u --vty-ip The VTY IP to bind telnet to. " - "(default %s)\n", vty_ip); - - if (app && app->cfg_print_help) - app->cfg_print_help(); + if (l23_app_info.cfg_print_help != NULL) + l23_app_info.cfg_print_help(); } static void build_config(char **opt, struct option **option) { - struct l23_app_info *app; struct option *app_opp = NULL; int app_len = 0, len; @@ -127,19 +108,17 @@ static void build_config(char **opt, struct option **option) {"sap", 1, 0, 'S'}, {"arfcn", 1, 0, 'a'}, {"gsmtap-ip", 1, 0, 'i'}, - {"vty-ip", 1, 0, 'u'}, - {"vty-port", 1, 0, 'v'}, + {"config-file", 1, 0, 'c'}, {"debug", 1, 0, 'd'}, }; - app = l23_app_info(); - *opt = talloc_asprintf(l23_ctx, "hs:S:a:i:v:d:u:%s", - app && app->getopt_string ? app->getopt_string : ""); + *opt = talloc_asprintf(l23_ctx, "hs:S:a:i:c:d:%s", + l23_app_info.getopt_string ? l23_app_info.getopt_string : ""); len = ARRAY_SIZE(long_options); - if (app && app->cfg_getopt_opt) - app_len = app->cfg_getopt_opt(&app_opp); + if (l23_app_info.cfg_getopt_opt != NULL) + app_len = l23_app_info.cfg_getopt_opt(&app_opp); *option = talloc_zero_array(l23_ctx, struct option, len + app_len + 1); memcpy(*option, long_options, sizeof(long_options)); @@ -149,7 +128,6 @@ static void build_config(char **opt, struct option **option) static void handle_options(int argc, char **argv) { - struct l23_app_info *app = l23_app_info(); struct option *long_options; char *opt; @@ -170,29 +148,26 @@ static void handle_options(int argc, char **argv) exit(0); break; case 's': - layer2_socket_path = talloc_strdup(l23_ctx, optarg); + layer2_socket_path = optarg; break; case 'S': sap_socket_path = talloc_strdup(l23_ctx, optarg); break; case 'a': - ms->test_arfcn = atoi(optarg); + cfg_test_arfcn = atoi(optarg); break; case 'i': gsmtap_ip = optarg; break; - case 'u': - vty_ip = optarg; - break; - case 'v': - vty_port = atoi(optarg); + case 'c': + config_file = optarg; break; case 'd': - log_parse_category_mask(stderr_target, optarg); + log_cat_mask = optarg; break; default: - if (app && app->cfg_handle_opt) - app->cfg_handle_opt(c, optarg); + if (l23_app_info.cfg_handle_opt != NULL) + l23_app_info.cfg_handle_opt(c, optarg); break; } } @@ -208,21 +183,58 @@ void sighandler(int sigset) if (sigset == SIGHUP || sigset == SIGPIPE) return; - fprintf(stderr, "Signal %d recevied.\n", sigset); + fprintf(stderr, "Signal %d received.\n", sigset); if (l23_app_exit) - rc = l23_app_exit(ms); + rc = l23_app_exit(); + + if (l23_app_info.opt_supported & L23_OPT_VTY) + telnet_exit(); if (rc != -EBUSY) exit (0); } -static void print_copyright() +static void print_copyright(void) { - struct l23_app_info *app; - app = l23_app_info(); - printf(openbsc_copyright, - app && app->copyright ? app->copyright : "", - app && app->contribution ? app->contribution : ""); + printf("%s" + "%s\n" + "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n\n", + l23_app_info.copyright ? l23_app_info.copyright : "", + l23_app_info.contribution ? l23_app_info.contribution : ""); +} + +static int _vty_init(void) +{ + int rc; + + OSMO_ASSERT(l23_app_info.vty_info != NULL); + l23_app_info.vty_info->tall_ctx = l23_ctx; + + vty_init(l23_app_info.vty_info); + logging_vty_add_cmds(); + + if (l23_app_info.vty_init != NULL) + l23_app_info.vty_init(); + if (config_file) { + LOGP(DLGLOBAL, LOGL_INFO, "Using configuration from '%s'\n", config_file); + l23_vty_reading = true; + rc = vty_read_config_file(config_file, NULL); + l23_vty_reading = false; + if (rc < 0) { + LOGP(DLGLOBAL, LOGL_FATAL, + "Failed to parse the configuration file '%s'\n", config_file); + return rc; + } + } + rc = telnet_init_default(l23_ctx, NULL, OSMO_VTY_PORT_BB); + if (rc < 0) { + LOGP(DMOB, LOGL_FATAL, "Cannot init VTY on %s port %u: %s\n", + vty_get_bind_addr(), OSMO_VTY_PORT_BB, strerror(errno)); + return rc; + } + return rc; } int main(int argc, char **argv) @@ -230,52 +242,67 @@ int main(int argc, char **argv) int rc; INIT_LLIST_HEAD(&ms_list); - log_init(&log_info, NULL); - stderr_target = log_target_create_stderr(); - log_add_target(stderr_target); - log_set_all_filter(stderr_target, 1); l23_ctx = talloc_named_const(NULL, 1, "layer2 context"); - ms = talloc_zero(l23_ctx, struct osmocom_ms); - if (!ms) { - fprintf(stderr, "Failed to allocate MS\n"); - exit(1); - } + osmo_init_logging2(l23_ctx, &log_info); - print_copyright(); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + log_set_print_level(osmo_stderr_target, 1); - llist_add_tail(&ms->entity, &ms_list); + log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME); + log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_HEADER_END); - ms->name = talloc_strdup(ms, "1"); - ms->test_arfcn = 871; + print_copyright(); handle_options(argc, argv); - rc = layer2_open(ms, layer2_socket_path); + rc = l23_app_init(); if (rc < 0) { - fprintf(stderr, "Failed during layer2_open()\n"); + fprintf(stderr, "Failed during l23_app_init()\n"); exit(1); } - ms->lapdm_channel.lapdm_dcch.l1_ctx = ms; - ms->lapdm_channel.lapdm_dcch.l3_ctx = ms; - ms->lapdm_channel.lapdm_acch.l1_ctx = ms; - ms->lapdm_channel.lapdm_acch.l3_ctx = ms; - lapdm_channel_init(&ms->lapdm_channel, LAPDM_MODE_MS); - lapdm_channel_set_l1(&ms->lapdm_channel, l1ctl_ph_prim_cb, ms); + if (l23_app_info.opt_supported & L23_OPT_VTY) { + if (_vty_init() < 0) + exit(1); + } - rc = l23_app_init(ms); - if (rc < 0) - exit(1); + if (log_cat_mask != NULL) + log_parse_category_mask(osmo_stderr_target, log_cat_mask); + + if (l23_app_info.opt_supported & L23_OPT_TAP) { + if (gsmtap_ip) { + if (l23_cfg.gsmtap.remote_host != NULL) { + LOGP(DLGLOBAL, LOGL_NOTICE, + "Command line argument '-i %s' overrides node " + "'gsmtap' cmd 'remote-host %s' from the config file\n", + gsmtap_ip, l23_cfg.gsmtap.remote_host); + talloc_free(l23_cfg.gsmtap.remote_host); + } + l23_cfg.gsmtap.remote_host = talloc_strdup(l23_ctx, gsmtap_ip); + } + + if (l23_cfg.gsmtap.remote_host) { + LOGP(DLGLOBAL, LOGL_NOTICE, + "Setting up GSMTAP Um forwarding to '%s:%u'\n", + l23_cfg.gsmtap.remote_host, GSMTAP_UDP_PORT); + l23_cfg.gsmtap.inst = gsmtap_source_init(l23_cfg.gsmtap.remote_host, GSMTAP_UDP_PORT, 1); + if (!l23_cfg.gsmtap.inst) { + fprintf(stderr, "Failed during gsmtap_init()\n"); + exit(1); + } + gsmtap_source_add_sink(l23_cfg.gsmtap.inst); + } + } - if (gsmtap_ip) { - gsmtap_inst = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 1); - if (!gsmtap_inst) { - fprintf(stderr, "Failed during gsmtap_init()\n"); + if (l23_app_start) { + rc = l23_app_start(); + if (rc < 0) { + fprintf(stderr, "Failed during l23_app_start()\n"); exit(1); } - gsmtap_source_add_sink(gsmtap_inst); } signal(SIGINT, sighandler); @@ -285,7 +312,7 @@ int main(int argc, char **argv) while (!quit) { if (l23_app_work) - l23_app_work(ms); + l23_app_work(); osmo_select_main(0); } diff --git a/src/host/layer23/src/common/ms.c b/src/host/layer23/src/common/ms.c new file mode 100644 index 00000000..a14cb1eb --- /dev/null +++ b/src/host/layer23/src/common/ms.c @@ -0,0 +1,80 @@ +/* Mobile Station */ +/* (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * 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. + * + */ +#include <osmocom/gsm/gsm48.h> + +#include <osmocom/bb/common/ms.h> + +extern struct llist_head ms_list; + +/* Default value be configured by cmdline arg: */ +uint16_t cfg_test_arfcn = 871; + +static int osmocom_ms_talloc_destructor(struct osmocom_ms *ms) +{ + + if (ms->sap_wq.bfd.fd > -1) { + sap_close(ms); + ms->sap_wq.bfd.fd = -1; + } + + gprs_settings_fi(ms); + gsm_subscr_exit(ms); + gsm_sim_exit(ms); + return 0; +} + +struct osmocom_ms *osmocom_ms_alloc(void *ctx, const char *name) +{ + struct osmocom_ms *ms; + + ms = talloc_zero(ctx, struct osmocom_ms); + if (!ms) + return NULL; + talloc_set_name(ms, "ms_%s", name); + talloc_set_destructor(ms, osmocom_ms_talloc_destructor); + + ms->name = talloc_strdup(ms, name); + ms->test_arfcn = cfg_test_arfcn; + ms->lapdm_channel.lapdm_dcch.l1_ctx = ms; + ms->lapdm_channel.lapdm_dcch.l3_ctx = ms; + ms->lapdm_channel.lapdm_acch.l1_ctx = ms; + ms->lapdm_channel.lapdm_acch.l3_ctx = ms; + lapdm_channel_init(&ms->lapdm_channel, LAPDM_MODE_MS); + lapdm_channel_set_l1(&ms->lapdm_channel, l1ctl_ph_prim_cb, ms); + + ms->l2_wq.bfd.fd = -1; + ms->sap_wq.bfd.fd = -1; + + ms->gmmlayer.tlli = GSM_RESERVED_TMSI; + + /* Register a new MS */ + llist_add_tail(&ms->entity, &ms_list); + + gsm_support_init(ms); + gsm_settings_init(ms); + gprs_settings_init(ms); + /* init SAP client before SIM card starts up */ + sap_init(ms); + /* SAP response call-back */ + ms->sap_entity.sap_rsp_cb = &gsm_subscr_sap_rsp_cb; + gsm_sim_init(ms); + gsm_subscr_init(ms); + + return ms; +} diff --git a/src/host/layer23/src/common/networks.c b/src/host/layer23/src/common/networks.c index b4757e96..e2429ef8 100644 --- a/src/host/layer23/src/common/networks.c +++ b/src/host/layer23/src/common/networks.c @@ -2,11 +2,13 @@ #include <stdint.h> #include <stdio.h> +#include <osmocom/gsm/gsm23003.h> + #include <osmocom/bb/common/networks.h> /* list of networks */ -struct gsm_networks gsm_networks[] = { +static struct gsm_networks gsm_networks[] = { { 0x001, -1, "Test" }, { 0x001, 0x01f, "Test" }, { 0x412, -1, "Afghanistan" }, @@ -470,7 +472,7 @@ struct gsm_networks gsm_networks[] = { { 0x262, 0x07f, "O2" }, { 0x262, 0x08f, "O2" }, { 0x262, 0x09f, "Vodafone" }, - { 0x262, 0x10f, "DB Systel GSM-R" }, + { 0x262, 0x10f, "DB Netz AG GSM-R" }, { 0x262, 0x11f, "O2" }, { 0x262, 0x12f, "Dolphin Telecom" }, { 0x262, 0x13f, "Mobilcom Multimedia" }, @@ -1774,6 +1776,43 @@ struct gsm_networks gsm_networks[] = { { 0, 0, NULL } }; +/* param: numerically stored mcc as per osmo_plmn_id. */ +uint16_t gsm_mcc_to_hex(uint16_t mcc) +{ + uint8_t buf[3]; + uint16_t in = mcc; + + buf[2] = in % 10; + in = in / 10; + buf[1] = in % 10; + in = in / 10; + buf[0] = in % 10; + + return ((buf[0] << 8) + + (buf[1] << 4) + + buf[2]); +} + +/* param: numerically stored mnc as per osmo_plmn_id. */ +uint16_t gsm_mnc_to_hex(uint16_t mnc, bool mnc_3_digits) +{ + uint8_t buf[3]; + uint16_t in = mnc; + if (mnc_3_digits) { + buf[2] = in % 10; + in = in / 10; + } else { + buf[2] = 0x0f; + } + buf[1] = in % 10; + in = in / 10; + buf[0] = in % 10; + + return ((buf[0] << 8) + + (buf[1] << 4) + + buf[2]); +} + /* GSM 03.22 Annex A */ int gsm_match_mcc(uint16_t mcc, char *imsi) { @@ -1783,30 +1822,33 @@ int gsm_match_mcc(uint16_t mcc, char *imsi) + ((imsi[1] - '0') << 4) + imsi[2] - '0'; - return (mcc == sim_mcc); + return (gsm_mcc_to_hex(mcc) == sim_mcc); } /* GSM 03.22 Annex A */ -int gsm_match_mnc(uint16_t mcc, uint16_t mnc, char *imsi) +int gsm_match_mnc(uint16_t mcc, uint16_t mnc, bool mnc_3_digits, char *imsi) { uint16_t sim_mnc; + uint16_t mnc_hex; /* 1. SIM-MCC = BCCH-MCC */ if (!gsm_match_mcc(mcc, imsi)) return 0; + mnc_hex = gsm_mnc_to_hex(mnc, mnc_3_digits); + /* 2. 3rd digit of BCCH-MNC is not 0xf */ - if ((mnc & 0x00f) != 0x00f) { + if ((mnc_hex & 0x00f) != 0x00f) { /* 3. 3 digit SIM-MNC = BCCH-MNC */ sim_mnc = ((imsi[3] - '0') << 8) + ((imsi[4] - '0') << 4) + imsi[5] - '0'; - return (mnc == sim_mnc); + return (mnc_hex == sim_mnc); } /* 4. BCCH-MCC in the range 310-316 */ - if (mcc >= 310 && mcc <= 316) { + if (gsm_mcc_to_hex(mcc) >= 310 && mnc_hex <= 316) { /* 5. 3rd diit of SIM-MNC is 0 */ if (imsi[5] != 0) return 0; @@ -1817,123 +1859,47 @@ int gsm_match_mnc(uint16_t mcc, uint16_t mnc, char *imsi) + ((imsi[4] - '0') << 4) + 0x00f; - return (mnc == sim_mnc); -} - -const char *gsm_print_mcc(uint16_t mcc) -{ - static char string[6] = "000"; - - snprintf(string, 5, "%03x", mcc); - return string; -} - -const char *gsm_print_mnc(uint16_t mnc) -{ - static char string[8]; - - /* invalid format: return hex value */ - if ((mnc & 0xf000) - || (mnc & 0x0f00) > 0x0900 - || (mnc & 0x00f0) > 0x0090 - || ((mnc & 0x000f) > 0x0009 && (mnc & 0x000f) < 0x000f)) { - snprintf(string, 7, "0x%03x", mnc); - return string; - } - - /* two digits */ - if ((mnc & 0x000f) == 0x000f) { - snprintf(string, 7, "%02x", mnc >> 4); - return string; - } - - /* three digits */ - snprintf(string, 7, "%03x", mnc); - return string; -} - -const uint16_t gsm_input_mcc(char *string) -{ - uint16_t mcc; - - if (strlen(string) != 3) - return GSM_INPUT_INVALID; - if (string[0] < '0' || string [0] > '9' - || string[1] < '0' || string [1] > '9' - || string[2] < '0' || string [2] > '9') - return GSM_INPUT_INVALID; - - mcc = ((string[0] - '0') << 8) - | ((string[1] - '0') << 4) - | ((string[2] - '0')); - - if (mcc == 0x000) - return GSM_INPUT_INVALID; - - return mcc; -} - -const uint16_t gsm_input_mnc(char *string) -{ - uint16_t mnc = 0; - - if (strlen(string) == 2) { - if (string[0] < '0' || string [0] > '9' - || string[1] < '0' || string [1] > '9') - return GSM_INPUT_INVALID; - - mnc = ((string[0] - '0') << 8) - | ((string[1] - '0') << 4) - | 0x00f; - } else - if (strlen(string) == 3) { - if (string[0] < '0' || string [0] > '9' - || string[1] < '0' || string [1] > '9' - || string[2] < '0' || string [2] > '9') - return GSM_INPUT_INVALID; - - mnc = ((string[0] - '0') << 8) - | ((string[1] - '0') << 4) - | ((string[2] - '0')); - } - - return mnc; + return (mnc_hex == sim_mnc); } const char *gsm_get_mcc(uint16_t mcc) { int i; + uint16_t mcc_hex = gsm_mcc_to_hex(mcc); for (i = 0; gsm_networks[i].name; i++) - if (gsm_networks[i].mnc < 0 && gsm_networks[i].mcc == mcc) + if (gsm_networks[i].mnc_hex < 0 && gsm_networks[i].mcc_hex == mcc_hex) return gsm_networks[i].name; - return gsm_print_mcc(mcc); + return osmo_mcc_name(mcc); } -const char *gsm_get_mnc(uint16_t mcc, uint16_t mnc) +const char *gsm_get_mnc(const struct osmo_plmn_id *plmn) { int i; + uint16_t mcc_hex = gsm_mcc_to_hex(plmn->mcc); + uint16_t mnc_hex = gsm_mnc_to_hex(plmn->mnc, plmn->mnc_3_digits); for (i = 0; gsm_networks[i].name; i++) - if (gsm_networks[i].mcc == mcc && gsm_networks[i].mnc == mnc) + if (gsm_networks[i].mcc_hex == mcc_hex && + gsm_networks[i].mnc_hex == mnc_hex) return gsm_networks[i].name; - return gsm_print_mnc(mnc); + return osmo_mnc_name(plmn->mnc, plmn->mnc_3_digits); } /* get MCC from IMSI */ const char *gsm_imsi_mcc(char *imsi) { int i, found = 0; - uint16_t mcc; + uint16_t mcc_hex; - mcc = ((imsi[0] - '0') << 8) + mcc_hex = ((imsi[0] - '0') << 8) | ((imsi[1] - '0') << 4) | ((imsi[2] - '0')); for (i = 0; gsm_networks[i].name; i++) { - if (gsm_networks[i].mcc == mcc) { + if (gsm_networks[i].mcc_hex == mcc_hex) { found = 1; break; } @@ -1961,15 +1927,15 @@ const char *gsm_imsi_mnc(char *imsi) + imsi[5] - '0'; for (i = 0; gsm_networks[i].name; i++) { - if (gsm_networks[i].mcc != mcc) + if (gsm_networks[i].mcc_hex != mcc) continue; - if ((gsm_networks[i].mnc & 0x00f) == 0x00f) { - if (mnc2 == gsm_networks[i].mnc) { + if ((gsm_networks[i].mnc_hex & 0x00f) == 0x00f) { + if (mnc2 == gsm_networks[i].mnc_hex) { found++; position = i; } } else { - if (mnc3 == gsm_networks[i].mnc) { + if (mnc3 == gsm_networks[i].mnc_hex) { found++; position = i; } diff --git a/src/host/layer23/src/common/sap_fsm.c b/src/host/layer23/src/common/sap_fsm.c index 22658917..f189bc8a 100644 --- a/src/host/layer23/src/common/sap_fsm.c +++ b/src/host/layer23/src/common/sap_fsm.c @@ -16,10 +16,6 @@ * 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 <errno.h> @@ -31,8 +27,10 @@ #include <osmocom/core/socket.h> #include <osmocom/core/utils.h> #include <osmocom/core/fsm.h> +#include <osmocom/core/write_queue.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/sap_interface.h> diff --git a/src/host/layer23/src/common/sap_interface.c b/src/host/layer23/src/common/sap_interface.c index 0eac8962..0f9f8c27 100644 --- a/src/host/layer23/src/common/sap_interface.c +++ b/src/host/layer23/src/common/sap_interface.c @@ -18,10 +18,6 @@ * 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 <unistd.h> @@ -38,7 +34,7 @@ #include <osmocom/bb/common/osmocom_data.h> #include <osmocom/bb/common/logging.h> - +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/sap_interface.h> #include <osmocom/bb/common/sap_proto.h> #include <osmocom/bb/common/sap_fsm.h> @@ -325,10 +321,9 @@ int _sap_close_sock(struct osmocom_ms *ms) if (ms->sap_wq.bfd.fd <= 0) return -EINVAL; + osmo_fd_unregister(&ms->sap_wq.bfd); close(ms->sap_wq.bfd.fd); ms->sap_wq.bfd.fd = -1; - - osmo_fd_unregister(&ms->sap_wq.bfd); osmo_wqueue_clear(&ms->sap_wq); return 0; diff --git a/src/host/layer23/src/common/sap_proto.c b/src/host/layer23/src/common/sap_proto.c index c3d202f6..2e891820 100644 --- a/src/host/layer23/src/common/sap_proto.c +++ b/src/host/layer23/src/common/sap_proto.c @@ -17,10 +17,6 @@ * 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 <string.h> @@ -177,7 +173,7 @@ void sap_msgb_add_param(struct msgb *msg, * \param[in] sap_msg pointer to SAP message header * \param[in] param_type parameter type (see sap_param_type enum) * \param[out] param_len parameter length (if found) - * \returns pointer to a given parameter withing the message, NULL otherwise + * \returns pointer to a given parameter within the message, NULL otherwise */ struct sap_param *sap_get_param(const struct sap_message *sap_msg, enum sap_param_type param_type, uint16_t *param_len) diff --git a/src/host/layer23/src/mobile/settings.c b/src/host/layer23/src/common/settings.c index 388c7547..6bc56340 100644 --- a/src/host/layer23/src/mobile/settings.c +++ b/src/host/layer23/src/common/settings.c @@ -13,25 +13,29 @@ * 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 <stdint.h> #include <errno.h> #include <string.h> #include <osmocom/core/talloc.h> +#include <osmocom/gsm/gsm48.h> -#include <osmocom/bb/mobile/app_mobile.h> +#include <osmocom/bb/common/settings.h> #include <osmocom/bb/common/utils.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/osmocom_data.h> -#include <osmocom/bb/common/networks.h> +#include <osmocom/bb/common/apn.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/common/l1l2_interface.h> + +/* Used to set default path globally through cmdline */ +char *layer2_socket_path = L2_DEFAULT_SOCKET_PATH; -static char *layer2_socket_path = "/tmp/osmocom_l2"; static char *sap_socket_path = "/tmp/osmocom_sap"; +static char *mncc_socket_path = "/tmp/ms_mncc"; +static char *data_socket_path = "/tmp/ms_data"; +static char *alsa_dev_default = "default"; int gsm_settings_init(struct osmocom_ms *ms) { @@ -41,6 +45,26 @@ int gsm_settings_init(struct osmocom_ms *ms) strcpy(set->layer2_socket_path, layer2_socket_path); strcpy(set->sap_socket_path, sap_socket_path); + /* Compose MNCC socket path using MS name */ + snprintf(set->mncc_socket_path, sizeof(set->mncc_socket_path) - 1, + "%s_%s", mncc_socket_path, ms->name); + + /* TCH voice: drop frames by default */ + set->tch_voice.io_handler = TCH_VOICE_IOH_NONE; + set->tch_voice.io_format = TCH_VOICE_IOF_RTP; + OSMO_STRLCPY_ARRAY(set->tch_voice.alsa_output_dev, alsa_dev_default); + OSMO_STRLCPY_ARRAY(set->tch_voice.alsa_input_dev, alsa_dev_default); + + /* TCH data: drop frames by default */ + set->tch_data.io_handler = TCH_DATA_IOH_NONE; + set->tch_data.io_format = TCH_DATA_IOF_OSMO; + snprintf(set->tch_data.unix_socket_path, + sizeof(set->tch_data.unix_socket_path) - 1, + "%s_%s", data_socket_path, ms->name); + + /* Built-in MNCC handler */ + set->mncc_handler = MNCC_HANDLER_INTERNAL; + /* network search */ set->plmn_mode = PLMN_MODE_AUTO; @@ -52,10 +76,12 @@ int gsm_settings_init(struct osmocom_ms *ms) set->sim_type = GSM_SIM_TYPE_L1PHY; /* test SIM */ - strcpy(set->test_imsi, "001010000000000"); - set->test_rplmn_mcc = set->test_rplmn_mnc = 1; - set->test_lac = 0x0000; - set->test_tmsi = 0xffffffff; + OSMO_STRLCPY_ARRAY(set->test_sim.imsi, "001010000000000"); + set->test_sim.rplmn.mcc = 1; + set->test_sim.rplmn.mnc = 1; + set->test_sim.rplmn.mnc_3_digits = false; + set->test_sim.lac = 0x0000; + set->test_sim.tmsi = GSM_RESERVED_TMSI; /* set all supported features */ set->sms_ptp = sup->sms_ptp; @@ -84,6 +110,16 @@ int gsm_settings_init(struct osmocom_ms *ms) set->min_rxlev_dbm = sup->min_rxlev_dbm; set->dsc_max = sup->dsc_max; + set->csd_tch_f144 = sup->csd_tch_f144; + set->csd_tch_f96 = sup->csd_tch_f96; + set->csd_tch_f48 = sup->csd_tch_f48; + set->csd_tch_h48 = sup->csd_tch_h48; + set->csd_tch_f24 = sup->csd_tch_f24; + set->csd_tch_h24 = sup->csd_tch_h24; + + set->vgcs = sup->vgcs; + set->vbs = sup->vbs; + if (sup->half_v1 || sup->half_v3) set->half = 1; @@ -97,6 +133,19 @@ int gsm_settings_init(struct osmocom_ms *ms) INIT_LLIST_HEAD(&set->abbrev); + set->uplink_release_local = true; + + set->call_params.data = (struct data_call_params) { + .type_rate = DATA_CALL_TR_V110_9600, + .transp = GSM48_BCAP_TR_TRANSP, + + /* async call parameters (8-N-1) */ + .is_async = true, + .nr_stop_bits = 1, + .nr_data_bits = 8, + .parity = GSM48_BCAP_PAR_NONE, + }; + return 0; } @@ -148,9 +197,6 @@ int gsm_settings_exit(struct osmocom_ms *ms) llist_del(&abbrev->list); talloc_free(abbrev); } - - script_lua_close(ms); - return 0; } @@ -194,3 +240,72 @@ int gsm_random_imei(struct gsm_settings *set) return 0; } +const struct value_string tch_voice_io_handler_names[] = { + { TCH_VOICE_IOH_NONE, "none" }, + { TCH_VOICE_IOH_GAPK, "gapk" }, + { TCH_VOICE_IOH_L1PHY, "l1phy" }, + { TCH_VOICE_IOH_MNCC_SOCK, "mncc-sock" }, + { TCH_VOICE_IOH_LOOPBACK, "loopback" }, + { 0, NULL } +}; + +const struct value_string tch_data_io_handler_names[] = { + { TCH_DATA_IOH_NONE, "none" }, + { TCH_DATA_IOH_UNIX_SOCK, "unix-sock" }, + { TCH_DATA_IOH_LOOPBACK, "loopback" }, + { 0, NULL } +}; + +const struct value_string tch_voice_io_format_names[] = { + { TCH_VOICE_IOF_RTP, "rtp" }, + { TCH_VOICE_IOF_TI, "ti" }, + { 0, NULL } +}; + +const struct value_string tch_data_io_format_names[] = { + { TCH_DATA_IOF_OSMO, "osmo" }, + { TCH_DATA_IOF_TI, "ti" }, + { 0, NULL } +}; + +int gprs_settings_init(struct osmocom_ms *ms) +{ + struct gprs_settings *set = &ms->gprs; + INIT_LLIST_HEAD(&set->apn_list); + + return 0; +} + +int gprs_settings_fi(struct osmocom_ms *ms) +{ + struct gprs_settings *set = &ms->gprs; + struct osmobb_apn *apn; + while ((apn = llist_first_entry_or_null(&set->apn_list, struct osmobb_apn, list))) { + /* free calls llist_del(): */ + apn_free(apn); + } + return 0; +} + +struct osmobb_apn *ms_find_apn_by_name(struct osmocom_ms *ms, const char *apn_name) +{ + struct gprs_settings *set = &ms->gprs; + struct osmobb_apn *apn; + + llist_for_each_entry(apn, &set->apn_list, list) { + if (strcmp(apn->cfg.name, apn_name) == 0) + return apn; + } + return NULL; +} + +int ms_dispatch_all_apn(struct osmocom_ms *ms, uint32_t event, void *data) +{ + struct gprs_settings *set = &ms->gprs; + int rc = 0; + struct osmobb_apn *apn; + + llist_for_each_entry(apn, &set->apn_list, list) + rc |= osmo_fsm_inst_dispatch(apn->fsm.fi, event, data); + return rc; +} diff --git a/src/host/layer23/src/common/sim.c b/src/host/layer23/src/common/sim.c index ed7f54a9..4a4144ab 100644 --- a/src/host/layer23/src/common/sim.c +++ b/src/host/layer23/src/common/sim.c @@ -13,10 +13,6 @@ * 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 <stdint.h> @@ -28,11 +24,11 @@ #include <osmocom/core/gsmtap.h> #include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/l23_app.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/l1ctl.h> -extern struct gsmtap_inst *gsmtap_inst; - static int sim_process_job(struct osmocom_ms *ms); /* @@ -243,7 +239,7 @@ int gsm_sim_job_dequeue(struct osmocom_ms *ms) sim_process_job(ms); return 1; /* work done */ } - + return 0; } @@ -877,7 +873,7 @@ int sim_apdu_resp(struct osmocom_ms *ms, struct msgb *msg) if ((ms->sim.apdu_len + length) <= sizeof(ms->sim.apdu_data)) { memcpy(ms->sim.apdu_data + ms->sim.apdu_len, data, length); ms->sim.apdu_len += length; - gsmtap_send_ex(gsmtap_inst, GSMTAP_TYPE_SIM, + gsmtap_send_ex(l23_cfg.gsmtap.inst, GSMTAP_TYPE_SIM, 0, 0, 0, 0, 0, 0, 0, ms->sim.apdu_data, ms->sim.apdu_len); } } @@ -1132,7 +1128,7 @@ int sim_apdu_resp(struct osmocom_ms *ms, struct msgb *msg) case SIM_JOB_INCREASE: if (length != 4) { LOGP(DSIM, LOGL_ERROR, "expecting uint32_t as " - "value lenght, but got %d bytes\n", + "value length, but got %d bytes\n", length); goto request_error; } diff --git a/src/host/layer23/src/mobile/subscriber.c b/src/host/layer23/src/common/subscriber.c index b2eacc59..f6aa05d5 100644 --- a/src/host/layer23/src/mobile/subscriber.c +++ b/src/host/layer23/src/common/subscriber.c @@ -13,10 +13,6 @@ * 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 <stdint.h> @@ -25,18 +21,59 @@ #include <arpa/inet.h> #include <osmocom/core/talloc.h> #include <osmocom/crypt/auth.h> +#include <osmocom/gsm/gsm23003.h> +#include <osmocom/gsm/gsm48.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/sap_interface.h> #include <osmocom/bb/common/sap_proto.h> #include <osmocom/bb/common/networks.h> -#include <osmocom/bb/mobile/vty.h> +#include <osmocom/bb/common/subscriber.h> +#include <osmocom/bb/common/vty.h> /* enable to get an empty list of forbidden PLMNs, even if stored on SIM. * if list is changed, the result is not written back to SIM */ //#define TEST_EMPTY_FPLMN +const struct value_string gsm_sub_sim_ustate_names[] = { + { GSM_SIM_U0_NULL, "U0_NULL" }, + { GSM_SIM_U1_UPDATED, "U1_UPDATED" }, + { GSM_SIM_U2_NOT_UPDATED, "U2_NOT_UPDATED" }, + { GSM_SIM_U3_ROAMING_NA, "U3_ROAMING_NA" }, + { 0, NULL } +}; + +const struct value_string gsm_sub_sim_gustate_names[] = { + { GSM_SIM_GU0_NULL, "GU0_NULL" }, + { GSM_SIM_GU1_UPDATED, "GU1_UPDATED" }, + { GSM_SIM_GU2_NOT_UPDATED, "GU2_NOT_UPDATED" }, + { GSM_SIM_GU3_ROAMING_NA, "GU3_ROAMING_NA" }, + { 0, NULL } +}; + +static int gsm_subscr_insert_simcard(struct osmocom_ms *ms); +static int gsm_subscr_insert_testcard(struct osmocom_ms *ms); +static int gsm_subscr_insert_sapcard(struct osmocom_ms *ms); + +static int gsm_subscr_remove_sapcard(struct osmocom_ms *ms); + +static int gsm_subscr_generate_kc_simcard(struct osmocom_ms *ms, uint8_t key_seq, + const uint8_t *rand, uint8_t no_sim); +static int gsm_subscr_generate_kc_testcard(struct osmocom_ms *ms, uint8_t key_seq, + const uint8_t *rand, uint8_t no_sim); + +static int gsm_subscr_write_loci_simcard(struct osmocom_ms *ms); + +static int gsm_subscr_write_locigprs_simcard(struct osmocom_ms *ms); +static int gsm_subscr_write_locigprs_testcard(struct osmocom_ms *ms); + +static int gsm_subscr_sim_pin_simcard(struct osmocom_ms *ms, const char *pin1, const char *pin2, + int8_t mode); + +static int subscr_write_plmn_na_simcard(struct osmocom_ms *ms); + static void subscr_sim_query_cb(struct osmocom_ms *ms, struct msgb *msg); static void subscr_sim_update_cb(struct osmocom_ms *ms, struct msgb *msg); static void subscr_sim_key_cb(struct osmocom_ms *ms, struct msgb *msg); @@ -45,21 +82,6 @@ static void subscr_sim_key_cb(struct osmocom_ms *ms, struct msgb *msg); * support */ -char *gsm_check_imsi(const char *imsi) -{ - int i; - - if (!imsi || strlen(imsi) != 15) - return "IMSI must have 15 digits!"; - - for (i = 0; i < strlen(imsi); i++) { - if (imsi[i] < '0' || imsi[i] > '9') - return "IMSI must have digits 0 to 9 only!"; - } - - return NULL; -} - static char *sim_decode_bcd(uint8_t *data, uint8_t length) { int i, j = 0; @@ -81,9 +103,9 @@ static char *sim_decode_bcd(uint8_t *data, uint8_t length) return result; } -/* - * init/exit - */ +/************************************** + * Generic backend-agnostic API + **************************************/ int gsm_subscr_init(struct osmocom_ms *ms) { @@ -93,8 +115,9 @@ int gsm_subscr_init(struct osmocom_ms *ms) subscr->ms = ms; /* set TMSI / LAC invalid */ - subscr->tmsi = 0xffffffff; - subscr->lac = 0x0000; + subscr->tmsi = GSM_RESERVED_TMSI; + subscr->gprs.ptmsi = GSM_RESERVED_TMSI; + subscr->lai.lac = 0x0000; /* set key invalid */ subscr->key_seq = 7; @@ -145,82 +168,554 @@ int gsm_subscr_exit(struct osmocom_ms *ms) return 0; } -/* - * test card - */ - -/* Attach test card, no SIM must be currently attached */ -int gsm_subscr_testcard(struct osmocom_ms *ms, uint16_t mcc, uint16_t mnc, - uint16_t lac, uint32_t tmsi, uint8_t imsi_attached) +/* Insert card */ +int gsm_subscr_insert(struct osmocom_ms *ms) { struct gsm_settings *set = &ms->settings; struct gsm_subscriber *subscr = &ms->subscr; - struct msgb *nmsg; - char *error; + int rc; if (subscr->sim_valid) { - LOGP(DMM, LOGL_ERROR, "Cannot insert card, until current card " - "is detached.\n"); + LOGP(DMM, LOGL_ERROR, "Cannot insert card, until current card is removed.\n"); return -EBUSY; } - error = gsm_check_imsi(set->test_imsi); - if (error) { - LOGP(DMM, LOGL_ERROR, "%s\n", error); - return -EINVAL; - } - /* reset subscriber */ gsm_subscr_exit(ms); gsm_subscr_init(ms); + subscr->sim_valid = true; + + switch (set->sim_type) { + case GSM_SIM_TYPE_L1PHY: + /* trigger sim card reader process */ + rc = gsm_subscr_insert_simcard(ms); + break; + case GSM_SIM_TYPE_TEST: + rc = gsm_subscr_insert_testcard(ms); + break; + case GSM_SIM_TYPE_SAP: + rc = gsm_subscr_insert_sapcard(ms); + break; + default: + return -EINVAL; + } + + if (rc < 0) { + subscr->sim_valid = false; + return rc; + } + return rc; +} + +/* Detach card */ +int gsm_subscr_remove(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + + if (!subscr->sim_valid) { + LOGP(DMM, LOGL_ERROR, "Cannot remove card, no card present\n"); + return -EINVAL; + } + + if (subscr->sim_type == GSM_SIM_TYPE_SAP) + gsm_subscr_remove_sapcard(ms); + + /* remove card */ + osmo_signal_dispatch(SS_L23_SUBSCR, S_L23_SUBSCR_SIM_DETACHED, ms); + + return 0; +} + +/* change to new U state */ +void new_sim_ustate(struct gsm_subscriber *subscr, int state) +{ + LOGP(DMM, LOGL_INFO, "(ms %s) new state %s -> %s\n", subscr->ms->name, + gsm_sub_sim_ustate_name(subscr->ustate), + gsm_sub_sim_ustate_name(state)); + + subscr->ustate = state; +} + +/* enter PIN */ +int gsm_subscr_sim_pin(struct osmocom_ms *ms, const char *pin1, const char *pin2, + int8_t mode) +{ + struct gsm_subscriber *subscr = &ms->subscr; + + /* skip, if no real valid SIM */ + if (subscr->sim_type == GSM_SIM_TYPE_NONE || !subscr->sim_valid) + return 0; + + switch (subscr->sim_type) { + case GSM_SIM_TYPE_L1PHY: + case GSM_SIM_TYPE_SAP: + return gsm_subscr_sim_pin_simcard(ms, pin1, pin2, mode); + case GSM_SIM_TYPE_TEST: + LOGP(DMM, LOGL_NOTICE, "PIN on test SIM: not implemented!\n"); + return 0; /* TODO */ + default: + OSMO_ASSERT(0); + } +} + +int gsm_subscr_generate_kc(struct osmocom_ms *ms, uint8_t key_seq, const uint8_t *rand, + bool no_sim) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct osmobb_l23_subscr_sim_auth_resp_sig_data sd; + int rc; + + if (no_sim || subscr->sim_type == GSM_SIM_TYPE_NONE || !subscr->sim_valid) { + LOGP(DMM, LOGL_INFO, "Sending dummy authentication response\n"); + sd.ms = ms; + sd.sres[0] = 0x12; + sd.sres[1] = 0x34; + sd.sres[2] = 0x56; + sd.sres[3] = 0x78; + osmo_signal_dispatch(SS_L23_SUBSCR, S_L23_SUBSCR_SIM_AUTH_RESP, &sd); + return 0; + } + + switch (subscr->sim_type) { + case GSM_SIM_TYPE_TEST: + rc = gsm_subscr_generate_kc_testcard(ms, key_seq, rand, no_sim); + break; + case GSM_SIM_TYPE_L1PHY: + case GSM_SIM_TYPE_SAP: + /* trigger sim card reader process */ + rc = gsm_subscr_generate_kc_simcard(ms, key_seq, rand, no_sim); + break; + default: + OSMO_ASSERT(0); + } + + return rc; +} + +/* update LOCI on SIM */ +int gsm_subscr_write_loci(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + + /* skip, if no real valid SIM */ + if (subscr->sim_type == GSM_SIM_TYPE_NONE || !subscr->sim_valid) + return 0; + + LOGP(DMM, LOGL_INFO, "Updating LOCI on SIM\n"); + + switch (subscr->sim_type) { + case GSM_SIM_TYPE_L1PHY: + case GSM_SIM_TYPE_SAP: + return gsm_subscr_write_loci_simcard(ms); + case GSM_SIM_TYPE_TEST: + LOGP(DMM, LOGL_NOTICE, "Updating LOCI on test SIM: not implemented!\n"); + return 0; /* TODO */ + default: + OSMO_ASSERT(0); + } +} + +/* update LOCIGPRS on SIM */ +int gsm_subscr_write_locigprs(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + + /* skip, if no real valid SIM */ + if (subscr->sim_type == GSM_SIM_TYPE_NONE || !subscr->sim_valid) + return 0; + + LOGP(DMM, LOGL_INFO, "Updating LOCIGPRS on SIM\n"); + + switch (subscr->sim_type) { + case GSM_SIM_TYPE_L1PHY: + case GSM_SIM_TYPE_SAP: + return gsm_subscr_write_locigprs_simcard(ms); + case GSM_SIM_TYPE_TEST: + return gsm_subscr_write_locigprs_testcard(ms); + default: + OSMO_ASSERT(0); + } +} + +/* update plmn not allowed list on SIM */ +static int subscr_write_plmn_na(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + + /* skip, if no real valid SIM */ + if (subscr->sim_type == GSM_SIM_TYPE_NONE || !subscr->sim_valid) + return 0; + + LOGP(DMM, LOGL_INFO, "Updating FPLMN on SIM\n"); + + switch (subscr->sim_type) { + case GSM_SIM_TYPE_L1PHY: + case GSM_SIM_TYPE_SAP: + return subscr_write_plmn_na_simcard(ms); + case GSM_SIM_TYPE_TEST: + LOGP(DMM, LOGL_NOTICE, "Updating FPLMN on test SIM: not implemented!\n"); + return 0; /* TODO */ + default: + OSMO_ASSERT(0); + } +} + +/* del forbidden PLMN. if PLMN is NULL, flush complete list */ +int gsm_subscr_del_forbidden_plmn(struct gsm_subscriber *subscr, const struct osmo_plmn_id *plmn) +{ + struct gsm_sub_plmn_na *na, *na2; + int deleted = 0; + + llist_for_each_entry_safe(na, na2, &subscr->plmn_na, entry) { + if (!plmn || (osmo_plmn_cmp(&na->plmn, plmn) == 0)) { + LOGP(DPLMN, LOGL_INFO, "Delete from list of forbidden PLMNs (mcc-mnc=%s)\n", + osmo_plmn_name(&na->plmn)); + llist_del(&na->entry); + talloc_free(na); + deleted = 1; + if (plmn) + break; + } + } + + if (deleted) { + /* update plmn not allowed list on SIM */ + subscr_write_plmn_na(subscr->ms); + } + + return -EINVAL; +} + +/* add forbidden PLMN */ +int gsm_subscr_add_forbidden_plmn(struct gsm_subscriber *subscr, const struct osmo_plmn_id *plmn, uint8_t cause) +{ + struct gsm_sub_plmn_na *na; + + /* if already in the list, remove and add to tail */ + gsm_subscr_del_forbidden_plmn(subscr, plmn); + + LOGP(DPLMN, LOGL_INFO, "Add to list of forbidden PLMNs " + "(mcc-mnc=%s)\n", osmo_plmn_name(plmn)); + na = talloc_zero(subscr->ms, struct gsm_sub_plmn_na); + if (!na) + return -ENOMEM; + memcpy(&na->plmn, plmn, sizeof(struct osmo_plmn_id)); + na->cause = cause ? : -1; /* cause 0 is not allowed */ + llist_add_tail(&na->entry, &subscr->plmn_na); + + /* don't add Home PLMN to SIM */ + if (subscr->sim_valid && gsm_match_mnc(plmn->mcc, plmn->mnc, plmn->mnc_3_digits, subscr->imsi)) + return -EINVAL; + + /* update plmn not allowed list on SIM */ + subscr_write_plmn_na(subscr->ms); + + return 0; +} + +/* search forbidden PLMN */ +int gsm_subscr_is_forbidden_plmn(struct gsm_subscriber *subscr, const struct osmo_plmn_id *plmn) +{ + struct gsm_sub_plmn_na *na; + + llist_for_each_entry(na, &subscr->plmn_na, entry) { + if (osmo_plmn_cmp(&na->plmn, plmn) == 0) + return 1; + } + + return 0; +} + +int gsm_subscr_get_key_seq(struct osmocom_ms *ms, struct gsm_subscriber *subscr) +{ + if (ms->settings.force_rekey) + return 7; + else + return subscr->key_seq; +} + +int gsm_subscr_dump_forbidden_plmn(struct osmocom_ms *ms, + void (*print)(void *, const char *, ...), void *priv) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm_sub_plmn_na *temp; + + print(priv, "MCC |MNC |cause\n"); + print(priv, "-------+-------+-------\n"); + llist_for_each_entry(temp, &subscr->plmn_na, entry) + print(priv, "%s |%-3s |#%d\n", + osmo_mcc_name(temp->plmn.mcc), + osmo_mnc_name(temp->plmn.mnc, temp->plmn.mnc_3_digits), + temp->cause); + + return 0; +} + +/* dump subscriber */ +void gsm_subscr_dump(struct gsm_subscriber *subscr, + void (*print)(void *, const char *, ...), void *priv) +{ + int i; + struct gsm_sub_plmn_list *plmn_list; + struct gsm_sub_plmn_na *plmn_na; + + print(priv, "Mobile Subscriber of MS '%s':\n", subscr->ms->name); + + if (!subscr->sim_valid) { + print(priv, " No SIM present.\n"); + return; + } + + print(priv, " IMSI: %s\n", subscr->imsi); + if (subscr->iccid[0]) + print(priv, " ICCID: %s\n", subscr->iccid); + if (subscr->sim_spn[0]) + print(priv, " Service Provider Name: %s\n", subscr->sim_spn); + if (subscr->msisdn[0]) + print(priv, " MSISDN: %s\n", subscr->msisdn); + if (subscr->sms_sca[0]) + print(priv, " SMS Service Center Address: %s\n", + subscr->sms_sca); + + print(priv, " Status: %s IMSI %s", gsm_sub_sim_ustate_name(subscr->ustate), + (subscr->imsi_attached) ? "attached" : "detached"); + if (subscr->tmsi != GSM_RESERVED_TMSI) + print(priv, " TMSI 0x%08x", subscr->tmsi); + if (subscr->lai.lac > 0x0000 && subscr->lai.lac < 0xfffe) { + print(priv, "\n"); + print(priv, " LAI: %s (%s, %s)\n", + osmo_lai_name(&subscr->lai), + gsm_get_mcc(subscr->lai.plmn.mcc), + gsm_get_mnc(&subscr->lai.plmn)); + } else { + print(priv, " LAI: invalid\n"); + } + + print(priv, " GPRS Status: %s IMSI %s", gsm_sub_sim_gustate_name(subscr->gprs.gu_state), + (subscr->gprs.imsi_attached) ? "attached" : "detached"); + if (subscr->gprs.ptmsi != GSM_RESERVED_TMSI) + print(priv, " PTMSI 0x%08x", subscr->tmsi); + if (subscr->gprs.ptmsi_sig != GSM_RESERVED_TMSI) + print(priv, " PTMSI-sig 0x%06x", subscr->gprs.ptmsi_sig); + if (subscr->gprs.rai.lac > 0x0000 && subscr->gprs.rai.lac < 0xfffe) { + struct osmo_plmn_id plmn = { + .mcc = subscr->gprs.rai.mcc, + .mnc = subscr->gprs.rai.mnc, + .mnc_3_digits = subscr->gprs.rai.mnc_3_digits, + }; + print(priv, "\n"); + print(priv, " RAI: %s (%s, %s)\n", + osmo_rai_name(&subscr->gprs.rai), + gsm_get_mcc(plmn.mcc), + gsm_get_mnc(&plmn)); + } else { + print(priv, " RAI: invalid\n"); + } + + if (subscr->gprs.ptmsi != GSM_RESERVED_TMSI) + print(priv, " P-TMSI 0x%08x", subscr->gprs.ptmsi); + if (subscr->key_seq != 7) { + print(priv, " Key: sequence %d ", subscr->key_seq); + for (i = 0; i < sizeof(subscr->key); i++) + print(priv, " %02x", subscr->key[i]); + print(priv, "\n"); + } + if (subscr->plmn_valid) + print(priv, " Registered PLMN: MCC-MNC %s (%s, %s)\n", + osmo_plmn_name(&subscr->plmn), + gsm_get_mcc(subscr->plmn.mcc), + gsm_get_mnc(&subscr->plmn)); + print(priv, " Access barred cells: %s\n", + (subscr->acc_barr) ? "yes" : "no"); + print(priv, " Access classes:"); + for (i = 0; i < 16; i++) + if ((subscr->acc_class & (1 << i))) + print(priv, " C%d", i); + print(priv, "\n"); + if (!llist_empty(&subscr->plmn_list)) { + print(priv, " List of preferred PLMNs:\n"); + print(priv, " MCC |MNC\n"); + print(priv, " -------+-------\n"); + llist_for_each_entry(plmn_list, &subscr->plmn_list, entry) + print(priv, " %s |%s (%s, %s)\n", + osmo_mcc_name(plmn_list->plmn.mcc), + osmo_mnc_name(plmn_list->plmn.mnc, plmn_list->plmn.mnc_3_digits), + gsm_get_mcc(plmn_list->plmn.mcc), + gsm_get_mnc(&plmn_list->plmn)); + } + if (!llist_empty(&subscr->plmn_na)) { + print(priv, " List of forbidden PLMNs:\n"); + print(priv, " MCC |MNC |cause\n"); + print(priv, " -------+-------+-------\n"); + llist_for_each_entry(plmn_na, &subscr->plmn_na, entry) + print(priv, " %s |%-3s |#%d (%s, %s)\n", + osmo_mcc_name(plmn_na->plmn.mcc), + osmo_mnc_name(plmn_na->plmn.mnc, plmn_na->plmn.mnc_3_digits), + plmn_na->cause, gsm_get_mcc(plmn_na->plmn.mcc), + gsm_get_mnc(&plmn_na->plmn)); + } +} + +/******************* + * testcard backend + *******************/ + +/* Attach test card, no SIM must be currently attached */ +int gsm_subscr_insert_testcard(struct osmocom_ms *ms) +{ + struct gsm_settings *set = &ms->settings; + struct gsm_subscriber *subscr = &ms->subscr; + + if (!osmo_imsi_str_valid(set->test_sim.imsi)) { + LOGP(DMM, LOGL_ERROR, "Wrong IMSI format\n"); + return -EINVAL; + } + subscr->sim_type = GSM_SIM_TYPE_TEST; sprintf(subscr->sim_name, "test"); - subscr->sim_valid = 1; - if (imsi_attached && set->test_rplmn_valid) { - subscr->imsi_attached = imsi_attached; - subscr->ustate = GSM_SIM_U1_UPDATED; - } else - subscr->ustate = GSM_SIM_U2_NOT_UPDATED; - subscr->acc_barr = set->test_barr; /* we may access barred cell */ + subscr->imsi_attached = set->test_sim.imsi_attached; + subscr->acc_barr = set->test_sim.barr; /* we may access barred cell */ subscr->acc_class = 0xffff; /* we have any access class */ - subscr->plmn_valid = set->test_rplmn_valid; - subscr->plmn_mcc = mcc; - subscr->plmn_mnc = mnc; - subscr->mcc = mcc; - subscr->mnc = mnc; - subscr->lac = lac; - subscr->tmsi = tmsi; - subscr->always_search_hplmn = set->test_always; + subscr->plmn_valid = set->test_sim.rplmn_valid; + memcpy(&subscr->plmn, &set->test_sim.rplmn, sizeof(struct osmo_plmn_id)); + memcpy(&subscr->lai.plmn, &set->test_sim.rplmn, sizeof(struct osmo_plmn_id)); + subscr->lai.lac = set->test_sim.lac; + subscr->tmsi = set->test_sim.tmsi; + subscr->always_search_hplmn = set->test_sim.always_search_hplmn; subscr->t6m_hplmn = 1; /* try to find home network every 6 min */ - strcpy(subscr->imsi, set->test_imsi); + OSMO_STRLCPY_ARRAY(subscr->imsi, set->test_sim.imsi); + + if (subscr->imsi_attached && subscr->plmn_valid) + subscr->ustate = GSM_SIM_U1_UPDATED; + else + subscr->ustate = GSM_SIM_U2_NOT_UPDATED; + + /* GPRS related: */ + subscr->gprs.ptmsi = set->test_sim.locigprs.ptmsi; + subscr->gprs.ptmsi_sig = set->test_sim.locigprs.ptmsi_sig; + subscr->gprs.imsi_attached = set->test_sim.locigprs.imsi_attached; + subscr->gprs.rai_valid = set->test_sim.locigprs.valid; + memcpy(&subscr->gprs.rai, &set->test_sim.locigprs.rai, sizeof(subscr->gprs.rai)); + + if (subscr->gprs.imsi_attached && subscr->gprs.rai_valid) + subscr->ustate = GSM_SIM_U1_UPDATED; + else + subscr->ustate = GSM_SIM_U2_NOT_UPDATED; - LOGP(DMM, LOGL_INFO, "(ms %s) Inserting test card (IMSI=%s %s, %s)\n", + LOGP(DMM, LOGL_INFO, "(ms %s) Inserting test card (IMSI=%s, %s, %s)\n", ms->name, subscr->imsi, gsm_imsi_mcc(subscr->imsi), gsm_imsi_mnc(subscr->imsi)); if (subscr->plmn_valid) - LOGP(DMM, LOGL_INFO, "-> Test card registered to %s %s 0x%04x" - "(%s, %s)\n", gsm_print_mcc(mcc), - gsm_print_mnc(mnc), lac, gsm_get_mcc(mcc), - gsm_get_mnc(mcc, mnc)); + LOGP(DMM, LOGL_INFO, "-> Test card registered to %s" + " (%s, %s)\n", osmo_lai_name(&subscr->lai), + gsm_get_mcc(subscr->lai.plmn.mcc), + gsm_get_mnc(&subscr->lai.plmn)); else LOGP(DMM, LOGL_INFO, "-> Test card not registered\n"); if (subscr->imsi_attached) LOGP(DMM, LOGL_INFO, "-> Test card attached\n"); + + /* GPRS:*/ + if (subscr->gprs.rai_valid) + LOGP(DMM, LOGL_INFO, "-> Test card GPRS registered to %s\n", + osmo_rai_name(&subscr->gprs.rai)); + else + LOGP(DMM, LOGL_INFO, "-> Test card not GPRS registered\n"); + if (subscr->gprs.imsi_attached) + LOGP(DMM, LOGL_INFO, "-> Test card GPRS attached\n"); + /* insert card */ - nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_REG_REQ); + osmo_signal_dispatch(SS_L23_SUBSCR, S_L23_SUBSCR_SIM_ATTACHED, ms); + return 0; +} + +static int gsm_subscr_generate_kc_testcard(struct osmocom_ms *ms, uint8_t key_seq, + const uint8_t *rand, uint8_t no_sim) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct osmobb_l23_subscr_sim_auth_resp_sig_data sd; + + struct gsm_settings *set = &ms->settings; + static struct osmo_sub_auth_data2 auth = { + .type = OSMO_AUTH_TYPE_GSM + }; + struct osmo_auth_vector _vec; + struct osmo_auth_vector *vec = &_vec; + + auth.algo = set->test_sim.ki_type; + memcpy(auth.u.gsm.ki, set->test_sim.ki, sizeof(auth.u.gsm.ki)); + int ret = osmo_auth_gen_vec2(vec, &auth, rand); + if (ret < 0) + return ret; + + /* store sequence */ + subscr->key_seq = key_seq; + memcpy(subscr->key, vec->kc, 8); + + LOGP(DMM, LOGL_INFO, "Sending authentication response\n"); + sd.ms = ms; + memcpy(sd.sres, vec->sres, 4); + osmo_signal_dispatch(SS_L23_SUBSCR, S_L23_SUBSCR_SIM_AUTH_RESP, &sd); + + return 0; +} + +/* update LOCIGPRS on test SIM */ +int gsm_subscr_write_locigprs_testcard(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + struct sim_hdr *nsh; + struct gsm1111_ef_locigprs *locigprs; + + /* skip, if no real valid SIM */ + if (!GSM_SIM_IS_READER(subscr->sim_type) || !subscr->sim_valid) + return 0; + + LOGP(DMM, LOGL_INFO, "Updating LOCI on SIM\n"); + + /* write to SIM */ + nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_update, + SIM_JOB_UPDATE_BINARY); if (!nmsg) return -ENOMEM; - gsm48_mmr_downmsg(ms, nmsg); + nsh = (struct sim_hdr *) nmsg->data; + nsh->path[0] = 0x7f20; + nsh->path[1] = 0; + nsh->file = 0x6f53; + locigprs = (struct gsm1111_ef_locigprs *)msgb_put(nmsg, sizeof(*locigprs)); + + /* P-TMSI, P-TMSI signature */ + locigprs->ptmsi = htonl(subscr->gprs.ptmsi); + locigprs->ptmsi_sig_hi = htonl(subscr->gprs.ptmsi) >> 8; + locigprs->ptmsi_sig_lo = htonl(subscr->gprs.ptmsi) & 0xff; + + /* RAI */ + gsm48_encode_ra(&locigprs->rai, &subscr->gprs.rai); + + /* location update status */ + switch (subscr->gprs.gu_state) { + case GSM_SIM_GU1_UPDATED: + locigprs->rau_status = GSM1111_EF_LOCIGPRS_RAU_ST_UPDATED; + break; + case GSM_SIM_GU3_ROAMING_NA: + locigprs->rau_status = GSM1111_EF_LOCIGPRS_RAU_ST_RA_NOT_ALLOWED; + break; + default: + locigprs->rau_status = GSM1111_EF_LOCIGPRS_RAU_ST_NOT_UPDATED; + } + + sim_job(ms, nmsg); return 0; } -/* - * sim card - */ +/******************** + * simcard backend + ********************/ static int subscr_sim_iccid(struct osmocom_ms *ms, uint8_t *data, uint8_t length) @@ -251,7 +746,7 @@ static int subscr_sim_imsi(struct osmocom_ms *ms, uint8_t *data, /* decode IMSI, skip first digit (parity) */ imsi = sim_decode_bcd(data + 1, length); - if (strlen(imsi) - 1 > GSM_IMSI_LENGTH - 1 || strlen(imsi) - 1 < 6) { + if (strlen(imsi) >= OSMO_IMSI_BUF_SIZE || strlen(imsi) - 1 < 6) { LOGP(DMM, LOGL_NOTICE, "IMSI invalid length = %zu\n", strlen(imsi) - 1); return -EINVAL; @@ -278,25 +773,60 @@ static int subscr_sim_loci(struct osmocom_ms *ms, uint8_t *data, subscr->tmsi = ntohl(loci->tmsi); /* LAI */ - gsm48_decode_lai_hex(&loci->lai, &subscr->mcc, &subscr->mnc, - &subscr->lac); + gsm48_decode_lai2(&loci->lai, &subscr->lai); /* location update status */ switch (loci->lupd_status & 0x07) { - case 0x00: + case GSM1111_EF_LOCI_LUPD_ST_UPDATED: subscr->ustate = GSM_SIM_U1_UPDATED; break; - case 0x02: - case 0x03: + case GSM1111_EF_LOCI_LUPD_ST_PLMN_NOT_ALLOWED: + case GSM1111_EF_LOCI_LUPD_ST_LA_NOT_ALLOWED: subscr->ustate = GSM_SIM_U3_ROAMING_NA; break; default: subscr->ustate = GSM_SIM_U2_NOT_UPDATED; } - LOGP(DMM, LOGL_INFO, "received LOCI from SIM (mcc=%s mnc=%s lac=0x%04x " - "U%d)\n", gsm_print_mcc(subscr->mcc), - gsm_print_mnc(subscr->mnc), subscr->lac, subscr->ustate); + LOGP(DMM, LOGL_INFO, "received LOCI from SIM (lai=%s U%d)\n", + osmo_lai_name(&subscr->lai), subscr->ustate); + + return 0; +} + +static int subscr_sim_locigprs(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm1111_ef_locigprs *locigprs; + + if (length < 11) + return -EINVAL; + locigprs = (struct gsm1111_ef_locigprs *) data; + + /* P-TMSI, P-TMSI signature */ + subscr->gprs.ptmsi = ntohl(locigprs->ptmsi); + subscr->gprs.ptmsi_sig = (((uint32_t)locigprs->ptmsi_sig_hi) << 8) | locigprs->ptmsi_sig_lo; + + /* RAI */ + subscr->gprs.rai_valid = true; + gsm48_parse_ra(&subscr->gprs.rai, (uint8_t *)&locigprs->rai); + + /* routing area update status */ + switch (locigprs->rau_status & 0x07) { + case GSM1111_EF_LOCIGPRS_RAU_ST_UPDATED: + subscr->gprs.gu_state = GSM_SIM_GU1_UPDATED; /* TODO: use proper enums here */ + break; + case GSM1111_EF_LOCIGPRS_RAU_ST_PLMN_NOT_ALLOWED: + case GSM1111_EF_LOCIGPRS_RAU_ST_RA_NOT_ALLOWED: + subscr->gprs.gu_state = GSM_SIM_GU3_ROAMING_NA; + break; + default: + subscr->gprs.gu_state = GSM_SIM_GU2_NOT_UPDATED; + } + + LOGP(DMM, LOGL_INFO, "received LOCIGPRS from SIM (RAI=%s %s)\n", + osmo_rai_name(&subscr->gprs.rai), gsm_sub_sim_gustate_name(subscr->gprs.gu_state)); return 0; } @@ -384,8 +914,6 @@ static int subscr_sim_plmnsel(struct osmocom_ms *ms, uint8_t *data, struct gsm_subscriber *subscr = &ms->subscr; struct gsm_sub_plmn_list *plmn; struct llist_head *lh, *lh2; - uint8_t lai[5]; - uint16_t dummy_lac; /* flush list */ llist_for_each_safe(lh, lh2, &subscr->plmn_list) { @@ -402,16 +930,11 @@ static int subscr_sim_plmnsel(struct osmocom_ms *ms, uint8_t *data, plmn = talloc_zero(ms, struct gsm_sub_plmn_list); if (!plmn) return -ENOMEM; - lai[0] = data[0]; - lai[1] = data[1]; - lai[2] = data[2]; - gsm48_decode_lai_hex((struct gsm48_loc_area_id *)lai, - &plmn->mcc, &plmn->mnc, &dummy_lac); + osmo_plmn_to_bcd(&data[0], &plmn->plmn); llist_add_tail(&plmn->entry, &subscr->plmn_list); - LOGP(DMM, LOGL_INFO, "received PLMN selector (mcc=%s mnc=%s) " - "from SIM\n", - gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc)); + LOGP(DMM, LOGL_INFO, "received PLMN selector (mcc-mnc=%s) from SIM\n", + osmo_plmn_name(&plmn->plmn)); data += 3; length -= 3; @@ -484,8 +1007,6 @@ static int subscr_sim_fplmn(struct osmocom_ms *ms, uint8_t *data, struct gsm_subscriber *subscr = &ms->subscr; struct gsm_sub_plmn_na *na; struct llist_head *lh, *lh2; - uint8_t lai[5]; - uint16_t dummy_lac; #ifdef TEST_EMPTY_FPLMN return 0; @@ -506,13 +1027,9 @@ static int subscr_sim_fplmn(struct osmocom_ms *ms, uint8_t *data, na = talloc_zero(ms, struct gsm_sub_plmn_na); if (!na) return -ENOMEM; - lai[0] = data[0]; - lai[1] = data[1]; - lai[2] = data[2]; - gsm48_decode_lai_hex((struct gsm48_loc_area_id *)lai, &na->mcc, - &na->mnc, &dummy_lac); - LOGP(DMM, LOGL_INFO, "received Forbidden PLMN %s %s from SIM\n", - gsm_print_mcc(na->mcc), gsm_print_mnc(na->mnc)); + osmo_plmn_to_bcd(&data[0], &na->plmn); + LOGP(DMM, LOGL_INFO, "received Forbidden PLMN %s from SIM\n", + osmo_plmn_name(&na->plmn)); na->cause = -1; /* must have a value, but SIM stores no cause */ llist_add_tail(&na->entry, &subscr->plmn_na); @@ -533,6 +1050,7 @@ static struct subscr_sim_file { { 1, { 0 }, 0x2fe2, SIM_JOB_READ_BINARY, subscr_sim_iccid }, { 1, { 0x7f20, 0 }, 0x6f07, SIM_JOB_READ_BINARY, subscr_sim_imsi }, { 1, { 0x7f20, 0 }, 0x6f7e, SIM_JOB_READ_BINARY, subscr_sim_loci }, + { 1, { 0x7f20, 0 }, 0x6f53, SIM_JOB_READ_BINARY, subscr_sim_locigprs }, { 0, { 0x7f20, 0 }, 0x6f20, SIM_JOB_READ_BINARY, subscr_sim_kc }, { 0, { 0x7f20, 0 }, 0x6f30, SIM_JOB_READ_BINARY, subscr_sim_plmnsel }, { 0, { 0x7f20, 0 }, 0x6f31, SIM_JOB_READ_BINARY, subscr_sim_hpplmn }, @@ -560,25 +1078,18 @@ static int subscr_sim_request(struct osmocom_ms *ms) gsm_imsi_mcc(subscr->imsi), gsm_imsi_mnc(subscr->imsi)); /* if LAI is valid, set RPLMN */ - if (subscr->lac > 0x0000 && subscr->lac < 0xfffe) { - subscr->plmn_valid = 1; - subscr->plmn_mcc = subscr->mcc; - subscr->plmn_mnc = subscr->mnc; - LOGP(DMM, LOGL_INFO, "-> SIM card registered to %s %s " - "(%s, %s)\n", gsm_print_mcc(subscr->plmn_mcc), - gsm_print_mnc(subscr->plmn_mnc), - gsm_get_mcc(subscr->plmn_mcc), - gsm_get_mnc(subscr->plmn_mcc, - subscr->plmn_mnc)); + if (subscr->lai.lac > 0x0000 && subscr->lai.lac < 0xfffe) { + subscr->plmn_valid = true; + memcpy(&subscr->plmn, &subscr->lai.plmn, sizeof(struct osmo_plmn_id)); + LOGP(DMM, LOGL_INFO, "-> SIM card registered to %s (%s, %s)\n", + osmo_plmn_name(&subscr->plmn), + gsm_get_mcc(subscr->plmn.mcc), + gsm_get_mnc(&subscr->plmn)); } else LOGP(DMM, LOGL_INFO, "-> SIM card not registered\n"); /* insert card */ - nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_REG_REQ); - if (!nmsg) - return -ENOMEM; - gsm48_mmr_downmsg(ms, nmsg); - + osmo_signal_dispatch(SS_L23_SUBSCR, S_L23_SUBSCR_SIM_ATTACHED, ms); return 0; } @@ -611,7 +1122,6 @@ static void subscr_sim_query_cb(struct osmocom_ms *ms, struct msgb *msg) uint16_t payload_len = msg->len - sizeof(*sh); int rc; struct subscr_sim_file *sf = &subscr_sim_files[subscr->sim_file_index]; - struct msgb *nmsg; /* error handling */ if (sh->job_type == SIM_JOB_ERROR) { @@ -623,29 +1133,29 @@ static void subscr_sim_query_cb(struct osmocom_ms *ms, struct msgb *msg) LOGP(DMM, LOGL_INFO, "PIN is required, %d tries left\n", payload[1]); - vty_notify(ms, NULL); - vty_notify(ms, "Please give PIN for ICCID %s (you have " + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Please give PIN for ICCID %s (you have " "%d tries left)\n", subscr->iccid, payload[1]); - subscr->sim_pin_required = 1; + subscr->sim_pin_required = true; break; case SIM_CAUSE_PIN1_BLOCKED: LOGP(DMM, LOGL_NOTICE, "PIN is blocked\n"); - vty_notify(ms, NULL); - vty_notify(ms, "PIN is blocked\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "PIN is blocked\n"); if (payload[1]) { - vty_notify(ms, "Please give PUC for ICCID %s " + l23_vty_ms_notify(ms, "Please give PUC for ICCID %s " "(you have %d tries left)\n", subscr->iccid, payload[1]); } - subscr->sim_pin_required = 1; + subscr->sim_pin_required = true; break; case SIM_CAUSE_PUC_BLOCKED: LOGP(DMM, LOGL_NOTICE, "PUC is blocked\n"); - vty_notify(ms, NULL); - vty_notify(ms, "PUC is blocked\n"); - subscr->sim_pin_required = 1; + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "PUC is blocked\n"); + subscr->sim_pin_required = true; break; default: if (sf->func && !sf->mandatory) { @@ -655,15 +1165,12 @@ static void subscr_sim_query_cb(struct osmocom_ms *ms, struct msgb *msg) } LOGP(DMM, LOGL_NOTICE, "SIM reading failed\n"); - vty_notify(ms, NULL); - vty_notify(ms, "SIM failed, replace SIM!\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "SIM failed, replace SIM!\n"); /* detach simcard */ subscr->sim_valid = 0; - nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_NREG_REQ); - if (!nmsg) - return; - gsm48_mmr_downmsg(ms, nmsg); + osmo_signal_dispatch(SS_L23_SUBSCR, S_L23_SUBSCR_SIM_DETACHED, ms); } msgb_free(msg); @@ -672,7 +1179,7 @@ static void subscr_sim_query_cb(struct osmocom_ms *ms, struct msgb *msg) /* if pin was successfully unlocked, then resend request */ if (subscr->sim_pin_required) { - subscr->sim_pin_required = 0; + subscr->sim_pin_required = false; subscr_sim_request(ms); return; } @@ -686,8 +1193,8 @@ static void subscr_sim_query_cb(struct osmocom_ms *ms, struct msgb *msg) if (rc) { LOGP(DMM, LOGL_NOTICE, "SIM reading failed, file invalid\n"); if (subscr_sim_files[subscr->sim_file_index].mandatory) { - vty_notify(ms, NULL); - vty_notify(ms, "SIM failed, data invalid, replace " + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "SIM failed, data invalid, replace " "SIM!\n"); msgb_free(msg); @@ -704,17 +1211,13 @@ ignore: } /* enter PIN */ -void gsm_subscr_sim_pin(struct osmocom_ms *ms, char *pin1, char *pin2, - int8_t mode) +static int gsm_subscr_sim_pin_simcard(struct osmocom_ms *ms, const char *pin1, const char *pin2, + int8_t mode) { struct gsm_subscriber *subscr = &ms->subscr; struct msgb *nmsg; uint8_t job; - /* skip, if no real valid SIM */ - if (!GSM_SIM_IS_READER(subscr->sim_type)) - return; - switch (mode) { case -1: job = SIM_JOB_PIN1_DISABLE; @@ -736,7 +1239,7 @@ void gsm_subscr_sim_pin(struct osmocom_ms *ms, char *pin1, char *pin2, default: if (!subscr->sim_pin_required) { LOGP(DMM, LOGL_ERROR, "No PIN required now\n"); - return; + return 0; } LOGP(DMM, LOGL_INFO, "entering PIN %s\n", pin1); job = SIM_JOB_PIN1_UNLOCK; @@ -744,30 +1247,20 @@ void gsm_subscr_sim_pin(struct osmocom_ms *ms, char *pin1, char *pin2, nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_query, job); if (!nmsg) - return; + return -ENOMEM; memcpy(msgb_put(nmsg, strlen(pin1) + 1), pin1, strlen(pin1) + 1); memcpy(msgb_put(nmsg, strlen(pin2) + 1), pin2, strlen(pin2) + 1); sim_job(ms, nmsg); + return 0; } /* Attach SIM reader, no SIM must be currently attached */ -int gsm_subscr_simcard(struct osmocom_ms *ms) +int gsm_subscr_insert_simcard(struct osmocom_ms *ms) { struct gsm_subscriber *subscr = &ms->subscr; - if (subscr->sim_valid) { - LOGP(DMM, LOGL_ERROR, "Cannot attach card, until current card " - "is detached.\n"); - return -EBUSY; - } - - /* reset subscriber */ - gsm_subscr_exit(ms); - gsm_subscr_init(ms); - subscr->sim_type = GSM_SIM_TYPE_L1PHY; sprintf(subscr->sim_name, "sim"); - subscr->sim_valid = 1; subscr->ustate = GSM_SIM_U2_NOT_UPDATED; /* start with first index */ @@ -776,7 +1269,7 @@ int gsm_subscr_simcard(struct osmocom_ms *ms) } /* update plmn not allowed list on SIM */ -static int subscr_write_plmn_na(struct osmocom_ms *ms) +static int subscr_write_plmn_na_simcard(struct osmocom_ms *ms) { struct gsm_subscriber *subscr = &ms->subscr; struct msgb *nmsg; @@ -784,16 +1277,11 @@ static int subscr_write_plmn_na(struct osmocom_ms *ms) struct gsm_sub_plmn_na *na, *nas[4] = { NULL, NULL, NULL, NULL }; int count = 0, i; uint8_t *data; - uint8_t lai[5]; #ifdef TEST_EMPTY_FPLMN return 0; #endif - /* skip, if no real valid SIM */ - if (!GSM_SIM_IS_READER(subscr->sim_type) || !subscr->sim_valid) - return 0; - /* get tail list from "PLMN not allowed" */ llist_for_each_entry(na, &subscr->plmn_na, entry) { if (count < 4) @@ -820,11 +1308,8 @@ static int subscr_write_plmn_na(struct osmocom_ms *ms) nsh->file = 0x6f7b; for (i = 0; i < 4; i++) { if (nas[i]) { - gsm48_encode_lai_hex((struct gsm48_loc_area_id *)lai, - nas[i]->mcc, nas[i]->mnc, 0); - *data++ = lai[0]; - *data++ = lai[1]; - *data++ = lai[2]; + osmo_plmn_to_bcd(data, &nas[i]->plmn); + data += 3; } else { *data++ = 0xff; *data++ = 0xff; @@ -837,19 +1322,13 @@ static int subscr_write_plmn_na(struct osmocom_ms *ms) } /* update LOCI on SIM */ -int gsm_subscr_write_loci(struct osmocom_ms *ms) +static int gsm_subscr_write_loci_simcard(struct osmocom_ms *ms) { struct gsm_subscriber *subscr = &ms->subscr; struct msgb *nmsg; struct sim_hdr *nsh; struct gsm1111_ef_loci *loci; - /* skip, if no real valid SIM */ - if (!GSM_SIM_IS_READER(subscr->sim_type) || !subscr->sim_valid) - return 0; - - LOGP(DMM, LOGL_INFO, "Updating LOCI on SIM\n"); - /* write to SIM */ nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_update, SIM_JOB_UPDATE_BINARY); @@ -865,7 +1344,7 @@ int gsm_subscr_write_loci(struct osmocom_ms *ms) loci->tmsi = htonl(subscr->tmsi); /* LAI */ - gsm48_encode_lai_hex(&loci->lai, subscr->mcc, subscr->mnc, subscr->lac); + gsm48_generate_lai2(&loci->lai, &subscr->lai); /* TMSI time */ loci->tmsi_time = 0xff; @@ -873,13 +1352,59 @@ int gsm_subscr_write_loci(struct osmocom_ms *ms) /* location update status */ switch (subscr->ustate) { case GSM_SIM_U1_UPDATED: - loci->lupd_status = 0x00; + loci->lupd_status = GSM1111_EF_LOCI_LUPD_ST_UPDATED; break; case GSM_SIM_U3_ROAMING_NA: - loci->lupd_status = 0x03; + loci->lupd_status = GSM1111_EF_LOCI_LUPD_ST_LA_NOT_ALLOWED; + break; + default: + loci->lupd_status = GSM1111_EF_LOCI_LUPD_ST_NOT_UPDATED; + } + + sim_job(ms, nmsg); + + return 0; +} + +/* update LOCIGPRS on SIM */ +int gsm_subscr_write_locigprs_simcard(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + struct sim_hdr *nsh; + struct gsm1111_ef_locigprs *locigprs; + + LOGP(DMM, LOGL_INFO, "Updating LOCI on SIM\n"); + + /* write to SIM */ + nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_update, + SIM_JOB_UPDATE_BINARY); + if (!nmsg) + return -ENOMEM; + nsh = (struct sim_hdr *) nmsg->data; + nsh->path[0] = 0x7f20; + nsh->path[1] = 0; + nsh->file = 0x6f53; + locigprs = (struct gsm1111_ef_locigprs *)msgb_put(nmsg, sizeof(*locigprs)); + + /* P-TMSI, P-TMSI signature */ + locigprs->ptmsi = htonl(subscr->gprs.ptmsi); + locigprs->ptmsi_sig_hi = htonl(subscr->gprs.ptmsi) >> 8; + locigprs->ptmsi_sig_lo = htonl(subscr->gprs.ptmsi) & 0xff; + + /* RAI */ + gsm48_encode_ra(&locigprs->rai, &subscr->gprs.rai); + + /* location update status */ + switch (subscr->gprs.gu_state) { + case GSM_SIM_GU1_UPDATED: + locigprs->rau_status = GSM1111_EF_LOCIGPRS_RAU_ST_UPDATED; + break; + case GSM_SIM_GU3_ROAMING_NA: + locigprs->rau_status = GSM1111_EF_LOCIGPRS_RAU_ST_RA_NOT_ALLOWED; break; default: - loci->lupd_status = 0x01; + locigprs->rau_status = GSM1111_EF_LOCIGPRS_RAU_ST_NOT_UPDATED; } sim_job(ms, nmsg); @@ -900,63 +1425,13 @@ static void subscr_sim_update_cb(struct osmocom_ms *ms, struct msgb *msg) msgb_free(msg); } -int gsm_subscr_generate_kc(struct osmocom_ms *ms, uint8_t key_seq, - uint8_t *rand, uint8_t no_sim) +static int gsm_subscr_generate_kc_simcard(struct osmocom_ms *ms, uint8_t key_seq, + const uint8_t *rand, uint8_t no_sim) { struct gsm_subscriber *subscr = &ms->subscr; struct msgb *nmsg; struct sim_hdr *nsh; - /* not a SIM */ - if (!GSM_SIM_IS_READER(subscr->sim_type) - || !subscr->sim_valid || no_sim) { - struct gsm48_mm_event *nmme; - - LOGP(DMM, LOGL_INFO, "Sending dummy authentication response\n"); - nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_AUTH_RESPONSE); - if (!nmsg) - return -ENOMEM; - nmme = (struct gsm48_mm_event *) nmsg->data; - nmme->sres[0] = 0x12; - nmme->sres[1] = 0x34; - nmme->sres[2] = 0x56; - nmme->sres[3] = 0x78; - gsm48_mmevent_msg(ms, nmsg); - - return 0; - } - - /* test SIM */ - if (subscr->sim_type == GSM_SIM_TYPE_TEST) { - struct gsm48_mm_event *nmme; - struct gsm_settings *set = &ms->settings; - static struct osmo_sub_auth_data auth = { - .type = OSMO_AUTH_TYPE_GSM - }; - struct osmo_auth_vector _vec; - struct osmo_auth_vector *vec = &_vec; - - auth.algo = set->test_ki_type; - memcpy(auth.u.gsm.ki, set->test_ki, sizeof(auth.u.gsm.ki)); - int ret = osmo_auth_gen_vec(vec, &auth, rand); - if (ret < 0) - return ret; - - /* store sequence */ - subscr->key_seq = key_seq; - memcpy(subscr->key, vec->kc, 8); - - LOGP(DMM, LOGL_INFO, "Sending authentication response\n"); - nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_AUTH_RESPONSE); - if (!nmsg) - return -ENOMEM; - nmme = (struct gsm48_mm_event *) nmsg->data; - memcpy(nmme->sres, vec->sres, 4); - gsm48_mmevent_msg(ms, nmsg); - - return 0; - } - LOGP(DMM, LOGL_INFO, "Generating KEY at SIM\n"); /* command to SIM */ @@ -986,8 +1461,8 @@ static void subscr_sim_key_cb(struct osmocom_ms *ms, struct msgb *msg) uint16_t payload_len = msg->len - sizeof(*sh); struct msgb *nmsg; struct sim_hdr *nsh; - struct gsm48_mm_event *nmme; uint8_t *data; + struct osmobb_l23_subscr_sim_auth_resp_sig_data sd; /* error handling */ if (sh->job_type == SIM_JOB_ERROR) { @@ -1023,278 +1498,38 @@ static void subscr_sim_key_cb(struct osmocom_ms *ms, struct msgb *msg) sim_job(ms, nmsg); /* return signed response */ - nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_AUTH_RESPONSE); - if (!nmsg) - return; - nmme = (struct gsm48_mm_event *) nmsg->data; - memcpy(nmme->sres, payload, 4); - gsm48_mmevent_msg(ms, nmsg); - + sd.ms = ms; + memcpy(sd.sres, payload, 4); + osmo_signal_dispatch(SS_L23_SUBSCR, S_L23_SUBSCR_SIM_AUTH_RESP, &sd); msgb_free(msg); } -/* - * detach - */ - -/* Detach card */ -int gsm_subscr_remove(struct osmocom_ms *ms) -{ - struct gsm_subscriber *subscr = &ms->subscr; - struct msgb *nmsg; - - if (!subscr->sim_valid) { - LOGP(DMM, LOGL_ERROR, "Cannot remove card, no card present\n"); - return -EINVAL; - } - - /* remove card */ - nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_NREG_REQ); - if (!nmsg) - return -ENOMEM; - gsm48_mmr_downmsg(ms, nmsg); - - return 0; -} - -/* - * state and lists - */ - -static const char *subscr_ustate_names[] = { - "U0_NULL", - "U1_UPDATED", - "U2_NOT_UPDATED", - "U3_ROAMING_NA" -}; - -/* change to new U state */ -void new_sim_ustate(struct gsm_subscriber *subscr, int state) -{ - LOGP(DMM, LOGL_INFO, "(ms %s) new state %s -> %s\n", subscr->ms->name, - subscr_ustate_names[subscr->ustate], - subscr_ustate_names[state]); - - subscr->ustate = state; -} - -/* del forbidden PLMN. if MCC==0, flush complete list */ -int gsm_subscr_del_forbidden_plmn(struct gsm_subscriber *subscr, uint16_t mcc, - uint16_t mnc) -{ - struct gsm_sub_plmn_na *na, *na2; - int deleted = 0; - - llist_for_each_entry_safe(na, na2, &subscr->plmn_na, entry) { - if (!mcc || (na->mcc == mcc && na->mnc == mnc)) { - LOGP(DPLMN, LOGL_INFO, "Delete from list of forbidden " - "PLMNs (mcc=%s, mnc=%s)\n", - gsm_print_mcc(mcc), gsm_print_mnc(mnc)); - llist_del(&na->entry); - talloc_free(na); - deleted = 1; - if (mcc) - break; - } - } - - if (deleted) { - /* update plmn not allowed list on SIM */ - subscr_write_plmn_na(subscr->ms); - } - - return -EINVAL; -} - -/* add forbidden PLMN */ -int gsm_subscr_add_forbidden_plmn(struct gsm_subscriber *subscr, uint16_t mcc, - uint16_t mnc, uint8_t cause) -{ - struct gsm_sub_plmn_na *na; - - /* if already in the list, remove and add to tail */ - gsm_subscr_del_forbidden_plmn(subscr, mcc, mnc); - - LOGP(DPLMN, LOGL_INFO, "Add to list of forbidden PLMNs " - "(mcc=%s, mnc=%s)\n", gsm_print_mcc(mcc), gsm_print_mnc(mnc)); - na = talloc_zero(subscr->ms, struct gsm_sub_plmn_na); - if (!na) - return -ENOMEM; - na->mcc = mcc; - na->mnc = mnc; - na->cause = cause ? : -1; /* cause 0 is not allowed */ - llist_add_tail(&na->entry, &subscr->plmn_na); - - /* don't add Home PLMN to SIM */ - if (subscr->sim_valid && gsm_match_mnc(mcc, mnc, subscr->imsi)) - return -EINVAL; - - /* update plmn not allowed list on SIM */ - subscr_write_plmn_na(subscr->ms); - - return 0; -} - -/* search forbidden PLMN */ -int gsm_subscr_is_forbidden_plmn(struct gsm_subscriber *subscr, uint16_t mcc, - uint16_t mnc) -{ - struct gsm_sub_plmn_na *na; - - llist_for_each_entry(na, &subscr->plmn_na, entry) { - if (na->mcc == mcc && na->mnc == mnc) - return 1; - } - - return 0; -} - -int gsm_subscr_get_key_seq(struct osmocom_ms *ms, struct gsm_subscriber *subscr) -{ - if (ms->settings.force_rekey) - return 7; - else - return subscr->key_seq; -} - -int gsm_subscr_dump_forbidden_plmn(struct osmocom_ms *ms, - void (*print)(void *, const char *, ...), void *priv) -{ - struct gsm_subscriber *subscr = &ms->subscr; - struct gsm_sub_plmn_na *temp; - - print(priv, "MCC |MNC |cause\n"); - print(priv, "-------+-------+-------\n"); - llist_for_each_entry(temp, &subscr->plmn_na, entry) - print(priv, "%s |%s%s |#%d\n", - gsm_print_mcc(temp->mcc), gsm_print_mnc(temp->mnc), - ((temp->mnc & 0x00f) == 0x00f) ? " ":"", temp->cause); - - return 0; -} - -/* dump subscriber */ -void gsm_subscr_dump(struct gsm_subscriber *subscr, - void (*print)(void *, const char *, ...), void *priv) -{ - int i; - struct gsm_sub_plmn_list *plmn_list; - struct gsm_sub_plmn_na *plmn_na; - - print(priv, "Mobile Subscriber of MS '%s':\n", subscr->ms->name); - - if (!subscr->sim_valid) { - print(priv, " No SIM present.\n"); - return; - } - - print(priv, " IMSI: %s\n", subscr->imsi); - if (subscr->iccid[0]) - print(priv, " ICCID: %s\n", subscr->iccid); - if (subscr->sim_spn[0]) - print(priv, " Service Provider Name: %s\n", subscr->sim_spn); - if (subscr->msisdn[0]) - print(priv, " MSISDN: %s\n", subscr->msisdn); - if (subscr->sms_sca[0]) - print(priv, " SMS Service Center Address: %s\n", - subscr->sms_sca); - print(priv, " Status: %s IMSI %s", subscr_ustate_names[subscr->ustate], - (subscr->imsi_attached) ? "attached" : "detached"); - if (subscr->tmsi != 0xffffffff) - print(priv, " TMSI 0x%08x", subscr->tmsi); - if (subscr->lac > 0x0000 && subscr->lac < 0xfffe) { - print(priv, "\n"); - print(priv, " LAI: MCC %s MNC %s LAC 0x%04x " - "(%s, %s)\n", gsm_print_mcc(subscr->mcc), - gsm_print_mnc(subscr->mnc), subscr->lac, - gsm_get_mcc(subscr->mcc), - gsm_get_mnc(subscr->mcc, subscr->mnc)); - } else - print(priv, " LAI: invalid\n"); - if (subscr->key_seq != 7) { - print(priv, " Key: sequence %d ", subscr->key_seq); - for (i = 0; i < sizeof(subscr->key); i++) - print(priv, " %02x", subscr->key[i]); - print(priv, "\n"); - } - if (subscr->plmn_valid) - print(priv, " Registered PLMN: MCC %s MNC %s (%s, %s)\n", - gsm_print_mcc(subscr->plmn_mcc), - gsm_print_mnc(subscr->plmn_mnc), - gsm_get_mcc(subscr->plmn_mcc), - gsm_get_mnc(subscr->plmn_mcc, subscr->plmn_mnc)); - print(priv, " Access barred cells: %s\n", - (subscr->acc_barr) ? "yes" : "no"); - print(priv, " Access classes:"); - for (i = 0; i < 16; i++) - if ((subscr->acc_class & (1 << i))) - print(priv, " C%d", i); - print(priv, "\n"); - if (!llist_empty(&subscr->plmn_list)) { - print(priv, " List of preferred PLMNs:\n"); - print(priv, " MCC |MNC\n"); - print(priv, " -------+-------\n"); - llist_for_each_entry(plmn_list, &subscr->plmn_list, entry) - print(priv, " %s |%s (%s, %s)\n", - gsm_print_mcc(plmn_list->mcc), - gsm_print_mnc(plmn_list->mnc), - gsm_get_mcc(plmn_list->mcc), - gsm_get_mnc(plmn_list->mcc, plmn_list->mnc)); - } - if (!llist_empty(&subscr->plmn_na)) { - print(priv, " List of forbidden PLMNs:\n"); - print(priv, " MCC |MNC |cause\n"); - print(priv, " -------+-------+-------\n"); - llist_for_each_entry(plmn_na, &subscr->plmn_na, entry) - print(priv, " %s |%s%s |#%d " - "(%s, %s)\n", gsm_print_mcc(plmn_na->mcc), - gsm_print_mnc(plmn_na->mnc), - ((plmn_na->mnc & 0x00f) == 0x00f) ? " ":"", - plmn_na->cause, gsm_get_mcc(plmn_na->mcc), - gsm_get_mnc(plmn_na->mcc, plmn_na->mnc)); - } -} - -/* - * SAP interface integration - */ +/*********************************************** + * sapcard backend + * (SAP interface integration, reuses some parts of simcard backend) + ***********************************************/ /* Attach SIM card over SAP */ -int gsm_subscr_sapcard(struct osmocom_ms *ms) +int gsm_subscr_insert_sapcard(struct osmocom_ms *ms) { struct gsm_subscriber *subscr = &ms->subscr; - struct msgb *nmsg; int rc; - if (subscr->sim_valid) { - LOGP(DMM, LOGL_ERROR, "Cannot insert card, until current card " - "is detached.\n"); - return -EBUSY; - } - - /* reset subscriber */ - gsm_subscr_exit(ms); - gsm_subscr_init(ms); - subscr->sim_type = GSM_SIM_TYPE_SAP; sprintf(subscr->sim_name, "sap"); - subscr->sim_valid = 1; /* Try to connect to the SAP interface */ - vty_notify(ms, NULL); - vty_notify(ms, "Connecting to the SAP interface...\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Connecting to the SAP interface...\n"); rc = sap_open(ms); if (rc < 0) { LOGP(DSAP, LOGL_ERROR, "Failed during sap_open(), no SAP based SIM reader\n"); - vty_notify(ms, "SAP connection error!\n"); + l23_vty_ms_notify(ms, "SAP connection error!\n"); ms->sap_wq.bfd.fd = -1; /* Detach SIM */ subscr->sim_valid = 0; - nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_NREG_REQ); - if (!nmsg) - return -ENOMEM; - gsm48_mmr_downmsg(ms, nmsg); + osmo_signal_dispatch(SS_L23_SUBSCR, S_L23_SUBSCR_SIM_DETACHED, ms); return rc; } @@ -1303,7 +1538,7 @@ int gsm_subscr_sapcard(struct osmocom_ms *ms) } /* Deattach sapcard */ -int gsm_subscr_remove_sapcard(struct osmocom_ms *ms) +static int gsm_subscr_remove_sapcard(struct osmocom_ms *ms) { return sap_close(ms); } diff --git a/src/host/layer23/src/mobile/support.c b/src/host/layer23/src/common/support.c index e9361a35..a31b456c 100644 --- a/src/host/layer23/src/mobile/support.c +++ b/src/host/layer23/src/common/support.c @@ -13,10 +13,6 @@ * 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 <stdio.h> @@ -24,6 +20,7 @@ #include <string.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> void gsm_support_init(struct osmocom_ms *ms) { @@ -37,9 +34,9 @@ void gsm_support_init(struct osmocom_ms *ms) /* revision level */ sup->rev_lev = 1; /* phase 2 mobile station */ /* support of VGCS */ - sup->vgcs = 0; /* no */ + sup->vgcs = true; /* yes */ /* support of VBS */ - sup->vbs = 0; /* no */ + sup->vbs = true; /* yes */ /* support of SMS */ sup->sms_ptp = 1; /* no */ /* screening indicator */ @@ -100,6 +97,14 @@ void gsm_support_init(struct osmocom_ms *ms) sup->full_v3 = 0; sup->half_v1 = 1; sup->half_v3 = 0; + + /* CSD modes */ + sup->csd_tch_f144 = 0; + sup->csd_tch_f96 = 1; + sup->csd_tch_f48 = 1; + sup->csd_tch_h48 = 1; + sup->csd_tch_f24 = 1; + sup->csd_tch_h24 = 1; } /* (3.2.1) maximum channels to scan within each band */ @@ -176,6 +181,14 @@ void gsm_support_dump(struct osmocom_ms *ms, print(priv, " Full-Rate V3 : %s\n", SUP_SET(full_v3)); print(priv, " Half-Rate V1 : %s\n", SUP_SET(half_v1)); print(priv, " Half-Rate V3 : %s\n", SUP_SET(half_v3)); + + print(priv, " CSD TCH/F14.4: %s\n", SUP_SET(csd_tch_f144)); + print(priv, " CSD TCH/F9.6 : %s\n", SUP_SET(csd_tch_f96)); + print(priv, " CSD TCH/F4.8 : %s\n", SUP_SET(csd_tch_f48)); + print(priv, " CSD TCH/H4.8 : %s\n", SUP_SET(csd_tch_h48)); + print(priv, " CSD TCH/F2.4 : %s\n", SUP_SET(csd_tch_f24)); + print(priv, " CSD TCH/H2.4 : %s\n", SUP_SET(csd_tch_h24)); + print(priv, " Min RXLEV : %d\n", set->min_rxlev_dbm); } diff --git a/src/host/layer23/src/common/sysinfo.c b/src/host/layer23/src/common/sysinfo.c index b42bd653..efbc5198 100644 --- a/src/host/layer23/src/common/sysinfo.c +++ b/src/host/layer23/src/common/sysinfo.c @@ -13,10 +13,6 @@ * 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 <stdio.h> @@ -24,15 +20,18 @@ #include <string.h> #include <arpa/inet.h> +#include <osmocom/core/utils.h> #include <osmocom/core/bitvec.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/gsm/gsm48_rest_octets.h> + +#include <osmocom/gprs/rlcmac/csn1_defs.h> #include <osmocom/bb/common/osmocom_data.h> #include <osmocom/bb/common/networks.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/sysinfo.h> -#define MIN(a, b) ((a < b) ? a : b) - /* * dumping */ @@ -51,34 +50,51 @@ char *gsm_print_arfcn(uint16_t arfcn) return text; } -/* check if the cell 'talks' about DCS (0) or PCS (1) */ -uint8_t gsm_refer_pcs(uint16_t arfcn, struct gsm48_sysinfo *s) +/* Check if the cell 'talks' about DCS (false) or PCS (true) */ +bool gsm_refer_pcs(uint16_t cell_arfcn, const struct gsm48_sysinfo *cell_s) { /* If ARFCN is PCS band, the cell refers to PCS */ - if ((arfcn & ARFCN_PCS)) - return 1; + if ((cell_arfcn & ARFCN_PCS)) + return true; /* If no SI1 is available, we assume DCS. Be sure to call this * function only if SI 1 is available. */ - if (!s->si1) + if (!cell_s->si1) return 0; /* If band indicator indicates PCS band, the cell refers to PCSThe */ - return s->band_ind; + return cell_s->band_ind; +} + +/* Change the given ARFCN to PCS ARFCN, if it is in the PCS channel range and the cell refers to PCS band. */ +uint16_t gsm_arfcn_refer_pcs(uint16_t cell_arfcn, const struct gsm48_sysinfo *cell_s, uint16_t arfcn) +{ + /* If ARFCN is not one of the overlapping channel of PCS and DCS. */ + if (arfcn < 512 || arfcn > 810) + return arfcn; + + /* If the 'cell' does not refer to PCS. */ + if (!gsm_refer_pcs(cell_arfcn, cell_s)) + return arfcn; + + /* The ARFCN is PCS, because the ARFCN is in the PCS range and the cell refers to it. */ + return arfcn | ARFCN_PCS; } -int gsm48_sysinfo_dump(struct gsm48_sysinfo *s, uint16_t arfcn, - void (*print)(void *, const char *, ...), void *priv, uint8_t *freq_map) +int gsm48_sysinfo_dump(const struct gsm48_sysinfo *s, uint16_t arfcn, + void (*print)(void *, const char *, ...), + void *priv, uint8_t *freq_map) { - char buffer[81]; + char buffer[82]; int i, j, k, index; int refer_pcs = gsm_refer_pcs(arfcn, s); + int rc; /* available sysinfos */ print(priv, "ARFCN = %s channels 512+ refer to %s\n", gsm_print_arfcn(arfcn), (refer_pcs) ? "PCS (1900)" : "DCS (1800)"); - print(priv, "Available SYSTEM INFORMATIONS ="); + print(priv, "Available SYSTEM INFORMATION ="); if (s->si1) print(priv, " 1"); if (s->si2) @@ -175,7 +191,7 @@ int gsm48_sysinfo_dump(struct gsm48_sysinfo *s, uint16_t arfcn, /* frequency map */ for (i = 0; i < 1024; i += 64) { - sprintf(buffer, " %3d ", i); + snprintf(buffer, sizeof(buffer), " %3d ", i); for (j = 0; j < 64; j++) { index = i+j; if (refer_pcs && index >= 512 && index <= 885) @@ -197,7 +213,7 @@ int gsm48_sysinfo_dump(struct gsm48_sysinfo *s, uint16_t arfcn, } for (; j < 64; j++) buffer[j + 5] = ' '; - sprintf(buffer + 69, " %d", i + 63); + snprintf(buffer + 69, sizeof(buffer) - 69, " %d", i + 63); print(priv, "%s\n", buffer); } print(priv, " 'S' = serv. cell 'n' = SI2 (neigh.) 'r' = SI5 (rep.) " @@ -205,12 +221,10 @@ int gsm48_sysinfo_dump(struct gsm48_sysinfo *s, uint16_t arfcn, /* serving cell */ print(priv, "Serving Cell:\n"); - print(priv, " BSIC = %d,%d MCC = %s MNC = %s LAC = 0x%04x Cell ID " - "= 0x%04x\n", s->bsic >> 3, s->bsic & 0x7, - gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc), s->lac, - s->cell_id); - print(priv, " Country = %s Network Name = %s\n", gsm_get_mcc(s->mcc), - gsm_get_mnc(s->mcc, s->mnc)); + print(priv, " BSIC = %d,%d LAI = %s Cell ID = 0x%04x\n", + s->bsic >> 3, s->bsic & 0x7, osmo_lai_name(&s->lai), s->cell_id); + print(priv, " Country = %s Network Name = %s\n", + gsm_get_mcc(s->lai.plmn.mcc), gsm_get_mnc(&s->lai.plmn)); print(priv, " MAX_RETRANS = %d TX_INTEGER = %d re-establish = %s\n", s->max_retrans, s->tx_integer, (s->reest_denied) ? "denied" : "allowed"); @@ -256,7 +270,7 @@ int gsm48_sysinfo_dump(struct gsm48_sysinfo *s, uint16_t arfcn, print(priv, "\n"); /* cell selection */ - print(priv, "MX_TXPWR_MAX_CCCH = %d CRH = %d RXLEV_MIN = %d " + print(priv, "MS_TXPWR_MAX_CCCH = %d CRH = %d RXLEV_MIN = %d " "NECI = %d ACS = %d\n", s->ms_txpwr_max_cch, s->cell_resel_hyst_db, s->rxlev_acc_min_db, s->neci, s->acs); @@ -285,6 +299,16 @@ int gsm48_sysinfo_dump(struct gsm48_sysinfo *s, uint16_t arfcn, print(priv, " BS-PA-MFMS = %d Attachment = %s\n", s->pag_mf_periods, (s->att_allowed) ? "allowed" : "denied"); print(priv, "BS-AG_BLKS_RES = %d ", s->bs_ag_blks_res); + if (!s->nch) + print(priv, "NCH not available "); + else { + uint8_t num_blocks, first_block; + rc = osmo_gsm48_si1ro_nch_pos_decode(s->nch_position, &num_blocks, &first_block); + if (rc < 0) + print(priv, "NCH Position invalid "); + else + print(priv, "NCH Position %u / %u blocks ", first_block, num_blocks); + } if (s->t3212) print(priv, "T3212 = %d sec.\n", s->t3212); else @@ -302,12 +326,54 @@ int gsm48_sysinfo_dump(struct gsm48_sysinfo *s, uint16_t arfcn, return 0; } +int gsm48_si10_dump(const struct gsm48_sysinfo *s, void (*print)(void *, const char *, ...), void *priv) +{ + const struct si10_cell_info *c; + int i; + + if (!s || !s->si10) { + print(priv, "No group channel neighbor information available.\n"); + return 0; + } + + if (!s->si10_cell_num) { + print(priv, "No group channel neighbors exist.\n"); + return 0; + } + + /* Group call neighbor cells. */ + print(priv, "Group channel neighbor cells (current or last call):\n"); + for (i = 0; i < s->si10_cell_num; i++) { + c = &s->si10_cell[i]; + print(priv, " index = %d", c->index); + if (c->arfcn >= 0) + print(priv, " ARFCN = %d", c->arfcn); + else + print(priv, " ARFCN = not in SI5*"); + print(priv, " BSIC = %d,%d", c->bsic >> 3, c->bsic & 0x7); + if (c->barred) { + print(priv, " barred"); + continue; + } + if (c->la_different) + print(priv, " CRH = %d", c->cell_resel_hyst_db); + print(priv, " MS_TXPWR_MAX_CCCH = %d\n", c->ms_txpwr_max_cch); + print(priv, " RXLEV_MIN = %d", c->rxlev_acc_min_db); + print(priv, " CRO = %d", c->cell_resel_offset); + print(priv, " TEMP_OFFSET = %d", c->temp_offset); + print(priv, " PENALTY_TIME = %d", c->penalty_time); + } + print(priv, "\n"); + + return 0; +} + /* * decoding */ -int gsm48_decode_chan_h0(struct gsm48_chan_desc *cd, uint8_t *tsc, - uint16_t *arfcn) +int gsm48_decode_chan_h0(const struct gsm48_chan_desc *cd, + uint8_t *tsc, uint16_t *arfcn) { *tsc = cd->h0.tsc; *arfcn = cd->h0.arfcn_low | (cd->h0.arfcn_high << 8); @@ -315,8 +381,8 @@ int gsm48_decode_chan_h0(struct gsm48_chan_desc *cd, uint8_t *tsc, return 0; } -int gsm48_decode_chan_h1(struct gsm48_chan_desc *cd, uint8_t *tsc, - uint8_t *maio, uint8_t *hsn) +int gsm48_decode_chan_h1(const struct gsm48_chan_desc *cd, + uint8_t *tsc, uint8_t *maio, uint8_t *hsn) { *tsc = cd->h1.tsc; *maio = cd->h1.maio_low | (cd->h1.maio_high << 2); @@ -326,8 +392,9 @@ int gsm48_decode_chan_h1(struct gsm48_chan_desc *cd, uint8_t *tsc, } /* decode "Cell Channel Description" (10.5.2.1b) and other frequency lists */ -static int decode_freq_list(struct gsm_sysinfo_freq *f, uint8_t *cd, - uint8_t len, uint8_t mask, uint8_t frqt) +static int decode_freq_list(struct gsm_sysinfo_freq *f, + const uint8_t *cd, uint8_t len, + uint8_t mask, uint8_t frqt) { #if 0 /* only Bit map 0 format for P-GSM */ @@ -341,7 +408,7 @@ static int decode_freq_list(struct gsm_sysinfo_freq *f, uint8_t *cd, /* decode "Cell Selection Parameters" (10.5.2.4) */ static int gsm48_decode_cell_sel_param(struct gsm48_sysinfo *s, - struct gsm48_cell_sel_par *cs) + const struct gsm48_cell_sel_par *cs) { s->ms_txpwr_max_cch = cs->ms_txpwr_max_ccch; s->cell_resel_hyst_db = cs->cell_resel_hyst * 2; @@ -354,7 +421,7 @@ static int gsm48_decode_cell_sel_param(struct gsm48_sysinfo *s, /* decode "Cell Options (BCCH)" (10.5.2.3) */ static int gsm48_decode_cellopt_bcch(struct gsm48_sysinfo *s, - struct gsm48_cell_options *co) + const struct gsm48_cell_options *co) { s->bcch_radio_link_timeout = (co->radio_link_timeout + 1) * 4; s->bcch_dtx = co->dtx; @@ -365,7 +432,7 @@ static int gsm48_decode_cellopt_bcch(struct gsm48_sysinfo *s, /* decode "Cell Options (SACCH)" (10.5.2.3a) */ static int gsm48_decode_cellopt_sacch(struct gsm48_sysinfo *s, - struct gsm48_cell_options *co) + const struct gsm48_cell_options *co) { s->sacch_radio_link_timeout = (co->radio_link_timeout + 1) * 4; s->sacch_dtx = co->dtx; @@ -376,7 +443,7 @@ static int gsm48_decode_cellopt_sacch(struct gsm48_sysinfo *s, /* decode "Control Channel Description" (10.5.2.11) */ static int gsm48_decode_ccd(struct gsm48_sysinfo *s, - struct gsm48_control_channel_descr *cc) + const struct gsm48_control_channel_descr *cc) { s->ccch_conf = cc->ccch_conf; s->bs_ag_blks_res = cc->bs_ag_blks_res; @@ -389,7 +456,8 @@ static int gsm48_decode_ccd(struct gsm48_sysinfo *s, /* decode "Mobile Allocation" (10.5.2.21) */ int gsm48_decode_mobile_alloc(struct gsm_sysinfo_freq *freq, - uint8_t *ma, uint8_t len, uint16_t *hopping, uint8_t *hopp_len, int si4) + const uint8_t *ma, uint8_t len, + uint16_t *hopping, uint8_t *hopp_len, int si4) { int i, j = 0; uint16_t f[len << 3]; @@ -442,16 +510,16 @@ int gsm48_decode_mobile_alloc(struct gsm_sysinfo_freq *freq, } /* Rach Control decode tables */ -static uint8_t gsm48_max_retrans[4] = { +static const uint8_t gsm48_max_retrans[4] = { 1, 2, 4, 7 }; -static uint8_t gsm48_tx_integer[16] = { +static const uint8_t gsm48_tx_integer[16] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 25, 32, 50 }; /* decode "RACH Control Parameter" (10.5.2.29) */ static int gsm48_decode_rach_ctl_param(struct gsm48_sysinfo *s, - struct gsm48_rach_control *rc) + const struct gsm48_rach_control *rc) { s->reest_denied = rc->re; s->cell_barr = rc->cell_bar; @@ -462,7 +530,7 @@ static int gsm48_decode_rach_ctl_param(struct gsm48_sysinfo *s, return 0; } static int gsm48_decode_rach_ctl_neigh(struct gsm48_sysinfo *s, - struct gsm48_rach_control *rc) + const struct gsm48_rach_control *rc) { s->nb_reest_denied = rc->re; s->nb_cell_barr = rc->cell_bar; @@ -474,14 +542,13 @@ static int gsm48_decode_rach_ctl_neigh(struct gsm48_sysinfo *s, } /* decode "SI 1 Rest Octets" (10.5.2.32) */ -static int gsm48_decode_si1_rest(struct gsm48_sysinfo *s, uint8_t *si, - uint8_t len) +static int gsm48_decode_si1_rest(struct gsm48_sysinfo *s, + const uint8_t *si, uint8_t len) { - struct bitvec bv; - - memset(&bv, 0, sizeof(bv)); - bv.data_len = len; - bv.data = si; + struct bitvec bv = { + .data_len = len, + .data = (uint8_t *)si, + }; /* Optional Selection Parameters */ if (bitvec_get_bit_high(&bv) == H) { @@ -489,23 +556,19 @@ static int gsm48_decode_si1_rest(struct gsm48_sysinfo *s, uint8_t *si, s->nch_position = bitvec_get_uint(&bv, 5); } else s->nch = 0; - if (bitvec_get_bit_high(&bv) == H) - s->band_ind = 1; - else - s->band_ind = 0; + s->band_ind = (bitvec_get_bit_high(&bv) == H); return 0; } /* decode "SI 3 Rest Octets" (10.5.2.34) */ -static int gsm48_decode_si3_rest(struct gsm48_sysinfo *s, uint8_t *si, - uint8_t len) +static int gsm48_decode_si3_rest(struct gsm48_sysinfo *s, + const uint8_t *si, uint8_t len) { - struct bitvec bv; - - memset(&bv, 0, sizeof(bv)); - bv.data_len = len; - bv.data = si; + struct bitvec bv = { + .data_len = len, + .data = (uint8_t *)si, + }; /* Optional Selection Parameters */ if (bitvec_get_bit_high(&bv) == H) { @@ -540,24 +603,23 @@ static int gsm48_decode_si3_rest(struct gsm48_sysinfo *s, uint8_t *si, s->sched = 0; /* GPRS Indicator */ if (bitvec_get_bit_high(&bv) == H) { - s->gprs = 1; - s->gprs_ra_colour = bitvec_get_uint(&bv, 3); - s->gprs_si13_pos = bitvec_get_uint(&bv, 1); + s->gprs.supported = 1; + s->gprs.ra_colour = bitvec_get_uint(&bv, 3); + s->gprs.si13_pos = bitvec_get_uint(&bv, 1); } else - s->gprs = 0; + s->gprs.supported = 0; return 0; } /* decode "SI 4 Rest Octets" (10.5.2.35) */ -static int gsm48_decode_si4_rest(struct gsm48_sysinfo *s, uint8_t *si, - uint8_t len) +static int gsm48_decode_si4_rest(struct gsm48_sysinfo *s, + const uint8_t *si, uint8_t len) { - struct bitvec bv; - - memset(&bv, 0, sizeof(bv)); - bv.data_len = len; - bv.data = si; + struct bitvec bv = { + .data_len = len, + .data = (uint8_t *)si, + }; /* Optional Selection Parameters */ if (bitvec_get_bit_high(&bv) == H) { @@ -576,33 +638,215 @@ static int gsm48_decode_si4_rest(struct gsm48_sysinfo *s, uint8_t *si, s->po = 0; /* GPRS Indicator */ if (bitvec_get_bit_high(&bv) == H) { - s->gprs = 1; - s->gprs_ra_colour = bitvec_get_uint(&bv, 3); - s->gprs_si13_pos = bitvec_get_uint(&bv, 1); + s->gprs.supported = 1; + s->gprs.ra_colour = bitvec_get_uint(&bv, 3); + s->gprs.si13_pos = bitvec_get_uint(&bv, 1); } else - s->gprs = 0; + s->gprs.supported = 0; // todo: more rest octet bits return 0; } -/* decode "SI 6 Rest Octets" (10.5.2.35a) */ -static int gsm48_decode_si6_rest(struct gsm48_sysinfo *s, uint8_t *si, - uint8_t len) +/* TODO: decode "SI 6 Rest Octets" (10.5.2.35a) */ +static int gsm48_decode_si6_rest(struct gsm48_sysinfo *s, + const uint8_t *si, uint8_t len) { return 0; } +/* Decode "SI 10 Rest Octets" (10.5.2.44) */ +static int gsm48_decode_si10_rest_first(struct gsm48_sysinfo *s, struct bitvec *bv, + struct si10_cell_info *c) +{ + uint8_t ba_ind; + + /* <BA ind : bit(1)> */ + ba_ind = bitvec_get_uint(bv, 1); + if (ba_ind != s->nb_ba_ind_si5) { + LOGP(DRR, LOGL_NOTICE, "SI10: BA_IND %u != BA_IND %u of SI5!\n", ba_ind, s->nb_ba_ind_si5); + return EOF; + } + + /* { L <spare padding> | H <neighbour information> } */ + if (bitvec_get_bit_high(bv) != H) { + LOGP(DRR, LOGL_INFO, "SI10: No neighbor cell defined.\n"); + return EOF; + } + + /* <first frequency: bit(5)> */ + c->index = bitvec_get_uint(bv, 5); + + /* <bsic : bit(6)> */ + c->bsic = bitvec_get_uint(bv, 6); + + /* { H <cell parameters> | L } */ + if (bitvec_get_bit_high(bv) != H) { + LOGP(DRR, LOGL_NOTICE, "SI10: No cell parameters for first cell, cannot continue to decode!\n"); + return EOF; + } + + /* <cell barred (H)> | L <further cell info> */ + if (bitvec_get_bit_high(bv) == H) { + c->barred = true; + return 0; + } + + /* { H <cell reselect hysteresis : bit(3)> | L } */ + if (bitvec_get_bit_high(bv) == H) { + c->la_different = true; + c->cell_resel_hyst_db = bitvec_get_uint(bv, 3) * 2; + } + + /* <ms txpwr max cch : bit(5)> */ + c->ms_txpwr_max_cch = bitvec_get_uint(bv, 5); + /* <rxlev access min : bit(6)> */ + c->rxlev_acc_min_db = rxlev2dbm(bitvec_get_uint(bv, 6)); + /* <cell reselect offset : bit(6)> */ + c->cell_resel_offset = bitvec_get_uint(bv, 6); + /* <temporary offset : bit(3)> */ + c->temp_offset = bitvec_get_uint(bv, 3); + /* <penalty time : bit(5)> */ + c->penalty_time = bitvec_get_uint(bv, 5); + + return 0; +} + +static int gsm48_decode_si10_rest_other(struct gsm48_sysinfo *s, struct bitvec *bv, + struct si10_cell_info *c) +{ + int rc; + + /* { H <info field> }** L <spare padding> */ + if (bitvec_get_bit_high(bv) != H) + return EOF; + + c->index = (c->index + 1) & 0x1f; + /* <next frequency (H)>** L <differential cell info> */ + /* Increment frequency number for every <info field> and every <next frequency> occurrence. */ + while ((rc = bitvec_get_bit_high(bv)) == H) + c->index = (c->index + 1) & 0x1f; + if (rc < 0) + goto short_read; + + /* { H <BCC : bit(3)> | L <bsic : bit(6)> } */ + rc = bitvec_get_bit_high(bv); + if (rc < 0) + goto short_read; + if (rc == H) { + rc = bitvec_get_uint(bv, 3); + if (rc < 0) + goto short_read; + c->bsic = (c->bsic & 0x07) | rc; + } else { + rc = bitvec_get_uint(bv, 6); + if (rc < 0) + goto short_read; + c->bsic = rc; + } + + /* { H <diff cell pars> | L } */ + rc = bitvec_get_bit_high(bv); + if (rc < 0) + goto short_read; + if (rc != H) + return 0; + + /* <cell barred (H)> | L <further cell info> */ + rc = bitvec_get_bit_high(bv); + if (rc < 0) + goto short_read; + if (rc == H) { + c->barred = true; + return 0; + } + + /* { H <cell reselect hysteresis : bit(3)> | L } */ + rc = bitvec_get_bit_high(bv); + if (rc < 0) + goto short_read; + if (rc == H) { + c->la_different = true; + rc = bitvec_get_uint(bv, 3); + if (rc < 0) + goto short_read; + c->cell_resel_hyst_db = bitvec_get_uint(bv, 3) * 2; + } + + /* { H <ms txpwr max cch : bit(5)> | L } */ + rc = bitvec_get_bit_high(bv); + if (rc < 0) + goto short_read; + if (rc == H) { + rc = bitvec_get_uint(bv, 5); + if (rc < 0) + goto short_read; + c->ms_txpwr_max_cch = rc; + } + + /* { H <rxlev access min : bit(6)> | L } */ + rc = bitvec_get_bit_high(bv); + if (rc < 0) + goto short_read; + if (rc == H) { + rc = bitvec_get_uint(bv, 6); + if (rc < 0) + goto short_read; + c->rxlev_acc_min_db = rxlev2dbm(rc); + } else + c->rxlev_acc_min_db = -110; + + /* { H <cell reselect offset : bit(6)> | L } */ + rc = bitvec_get_bit_high(bv); + if (rc < 0) + goto short_read; + if (rc == H) { + rc = bitvec_get_uint(bv, 6); + if (rc < 0) + goto short_read; + c->cell_resel_offset = rc; + } + + /* { H <temporary offset : bit(3)> | L } */ + rc = bitvec_get_bit_high(bv); + if (rc < 0) + goto short_read; + if (rc == H) { + rc = bitvec_get_uint(bv, 3); + if (rc < 0) + goto short_read; + c->temp_offset = rc; + } + + /* { H <penalty time : bit(5)> | L } */ + rc = bitvec_get_bit_high(bv); + if (rc < 0) + goto short_read; + if (rc == H) { + rc = bitvec_get_uint(bv, 5); + if (rc < 0) + goto short_read; + c->penalty_time = rc; + } + + return 0; + +short_read: + LOGP(DRR, LOGL_NOTICE, "SI10: Short read of differential cell info.\n"); + return -EINVAL; +} + int gsm48_decode_sysinfo1(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_1 *si, int len) + const struct gsm48_system_information_type_1 *si, int len) { int payload_len = len - sizeof(*si); - memcpy(s->si1_msg, si, MIN(len, sizeof(s->si1_msg))); + memcpy(s->si1_msg, si, OSMO_MIN(len, sizeof(s->si1_msg))); /* Cell Channel Description */ decode_freq_list(s->freq, si->cell_channel_description, - sizeof(si->cell_channel_description), 0xce, FREQ_TYPE_SERV); + sizeof(si->cell_channel_description), + 0xce, FREQ_TYPE_SERV); /* RACH Control Parameter */ gsm48_decode_rach_ctl_param(s, &si->rach_control); /* SI 1 Rest Octets */ @@ -612,26 +856,26 @@ int gsm48_decode_sysinfo1(struct gsm48_sysinfo *s, s->si1 = 1; if (s->si4) { - LOGP(DRR, LOGL_NOTICE, "Now updating previously received " - "SYSTEM INFORMATION 4\n"); - gsm48_decode_sysinfo4(s, - (struct gsm48_system_information_type_4 *) s->si4_msg, - sizeof(s->si4_msg)); + const struct gsm48_system_information_type_4 *si4 = (void *)s->si4_msg; + LOGP(DRR, LOGL_NOTICE, + "Now updating previously received SYSTEM INFORMATION 4\n"); + gsm48_decode_sysinfo4(s, si4, sizeof(s->si4_msg)); } return 0; } int gsm48_decode_sysinfo2(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_2 *si, int len) + const struct gsm48_system_information_type_2 *si, int len) { - memcpy(s->si2_msg, si, MIN(len, sizeof(s->si2_msg))); + memcpy(s->si2_msg, si, OSMO_MIN(len, sizeof(s->si2_msg))); /* Neighbor Cell Description */ - s->nb_ext_ind_si2 = (si->bcch_frequency_list[0] >> 6) & 1; - s->nb_ba_ind_si2 = (si->bcch_frequency_list[0] >> 5) & 1; + s->nb_ext_ind_si2 = (si->bcch_frequency_list[0] >> 5) & 1; + s->nb_ba_ind_si2 = (si->bcch_frequency_list[0] >> 4) & 1; decode_freq_list(s->freq, si->bcch_frequency_list, - sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_NCELL_2); + sizeof(si->bcch_frequency_list), + 0xce, FREQ_TYPE_NCELL_2); /* NCC Permitted */ s->nb_ncc_permitted_si2 = si->ncc_permitted; /* RACH Control Parameter */ @@ -643,13 +887,13 @@ int gsm48_decode_sysinfo2(struct gsm48_sysinfo *s, } int gsm48_decode_sysinfo2bis(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_2bis *si, int len) + const struct gsm48_system_information_type_2bis *si, int len) { - memcpy(s->si2b_msg, si, MIN(len, sizeof(s->si2b_msg))); + memcpy(s->si2b_msg, si, OSMO_MIN(len, sizeof(s->si2b_msg))); /* Neighbor Cell Description */ - s->nb_ext_ind_si2bis = (si->bcch_frequency_list[0] >> 6) & 1; - s->nb_ba_ind_si2bis = (si->bcch_frequency_list[0] >> 5) & 1; + s->nb_ext_ind_si2bis = (si->bcch_frequency_list[0] >> 5) & 1; + s->nb_ba_ind_si2bis = (si->bcch_frequency_list[0] >> 4) & 1; decode_freq_list(s->freq, si->bcch_frequency_list, sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_NCELL_2bis); /* RACH Control Parameter */ @@ -661,13 +905,13 @@ int gsm48_decode_sysinfo2bis(struct gsm48_sysinfo *s, } int gsm48_decode_sysinfo2ter(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_2ter *si, int len) + const struct gsm48_system_information_type_2ter *si, int len) { - memcpy(s->si2t_msg, si, MIN(len, sizeof(s->si2t_msg))); + memcpy(s->si2t_msg, si, OSMO_MIN(len, sizeof(s->si2t_msg))); /* Neighbor Cell Description 2 */ - s->nb_multi_rep_si2ter = (si->ext_bcch_frequency_list[0] >> 6) & 3; - s->nb_ba_ind_si2ter = (si->ext_bcch_frequency_list[0] >> 5) & 1; + s->nb_multi_rep_si2ter = (si->ext_bcch_frequency_list[0] >> 5) & 3; + s->nb_ba_ind_si2ter = (si->ext_bcch_frequency_list[0] >> 4) & 1; decode_freq_list(s->freq, si->ext_bcch_frequency_list, sizeof(si->ext_bcch_frequency_list), 0x8e, FREQ_TYPE_NCELL_2ter); @@ -678,16 +922,16 @@ int gsm48_decode_sysinfo2ter(struct gsm48_sysinfo *s, } int gsm48_decode_sysinfo3(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_3 *si, int len) + const struct gsm48_system_information_type_3 *si, int len) { int payload_len = len - sizeof(*si); - memcpy(s->si3_msg, si, MIN(len, sizeof(s->si3_msg))); + memcpy(s->si3_msg, si, OSMO_MIN(len, sizeof(s->si3_msg))); /* Cell Identity */ s->cell_id = ntohs(si->cell_identity); /* LAI */ - gsm48_decode_lai_hex(&si->lai, &s->mcc, &s->mnc, &s->lac); + gsm48_decode_lai2(&si->lai, &s->lai); /* Control Channel Description */ gsm48_decode_ccd(s, &si->control_channel_desc); /* Cell Options (BCCH) */ @@ -700,9 +944,8 @@ int gsm48_decode_sysinfo3(struct gsm48_sysinfo *s, if (payload_len >= 4) gsm48_decode_si3_rest(s, si->rest_octets, payload_len); - LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 3 (mcc %s mnc %s " - "lac 0x%04x)\n", gsm_print_mcc(s->mcc), - gsm_print_mnc(s->mnc), s->lac); + LOGP(DRR, LOGL_INFO, + "New SYSTEM INFORMATION 3 (lai=%s)\n", osmo_lai_name(&s->lai)); s->si3 = 1; @@ -710,17 +953,17 @@ int gsm48_decode_sysinfo3(struct gsm48_sysinfo *s, } int gsm48_decode_sysinfo4(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_4 *si, int len) + const struct gsm48_system_information_type_4 *si, int len) { int payload_len = len - sizeof(*si); - uint8_t *data = si->data; - struct gsm48_chan_desc *cd; + const uint8_t *data = si->data; + const struct gsm48_chan_desc *cd; - memcpy(s->si4_msg, si, MIN(len, sizeof(s->si4_msg))); + memcpy(s->si4_msg, si, OSMO_MIN(len, sizeof(s->si4_msg))); /* LAI */ - gsm48_decode_lai_hex(&si->lai, &s->mcc, &s->mnc, &s->lac); + gsm48_decode_lai2(&si->lai, &s->lai); /* Cell Selection Parameters */ gsm48_decode_cell_sel_param(s, &si->cell_sel_par); /* RACH Control Parameter */ @@ -733,15 +976,13 @@ short_read: LOGP(DRR, LOGL_NOTICE, "Short read!\n"); return -EIO; } - cd = (struct gsm48_chan_desc *) (data + 1); + cd = (const struct gsm48_chan_desc *)(data + 1); s->chan_nr = cd->chan_nr; - if (cd->h0.h) { - s->h = 1; + s->h = cd->h0.h; + if (s->h) gsm48_decode_chan_h1(cd, &s->tsc, &s->maio, &s->hsn); - } else { - s->h = 0; + else gsm48_decode_chan_h0(cd, &s->tsc, &s->arfcn); - } payload_len -= 4; data += 4; } @@ -751,11 +992,10 @@ short_read: goto short_read; if (!s->si1) { LOGP(DRR, LOGL_NOTICE, "Ignoring CBCH allocation of " - "SYSTEM INFORMATION 4 until SI 1 is " - "received.\n"); + "SYSTEM INFORMATION 4 until SI 1 is received.\n"); } else { gsm48_decode_mobile_alloc(s->freq, data + 2, data[1], - s->hopping, &s->hopp_len, 1); + s->hopping, &s->hopp_len, 1); } payload_len -= 2 + data[1]; data += 2 + data[1]; @@ -770,59 +1010,65 @@ short_read: } int gsm48_decode_sysinfo5(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_5 *si, int len) + const struct gsm48_system_information_type_5 *si, int len) { - memcpy(s->si5_msg, si, MIN(len, sizeof(s->si5_msg))); + memcpy(s->si5_msg, si, OSMO_MIN(len, sizeof(s->si5_msg))); /* Neighbor Cell Description */ - s->nb_ext_ind_si5 = (si->bcch_frequency_list[0] >> 6) & 1; - s->nb_ba_ind_si5 = (si->bcch_frequency_list[0] >> 5) & 1; + s->nb_ext_ind_si5 = (si->bcch_frequency_list[0] >> 5) & 1; + s->nb_ba_ind_si5 = (si->bcch_frequency_list[0] >> 4) & 1; decode_freq_list(s->freq, si->bcch_frequency_list, - sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_REP_5); + sizeof(si->bcch_frequency_list), + 0xce, FREQ_TYPE_REP_5); s->si5 = 1; + s->si10 = false; return 0; } int gsm48_decode_sysinfo5bis(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_5bis *si, int len) + const struct gsm48_system_information_type_5bis *si, int len) { - memcpy(s->si5b_msg, si, MIN(len, sizeof(s->si5b_msg))); + memcpy(s->si5b_msg, si, OSMO_MIN(len, sizeof(s->si5b_msg))); /* Neighbor Cell Description */ - s->nb_ext_ind_si5bis = (si->bcch_frequency_list[0] >> 6) & 1; - s->nb_ba_ind_si5bis = (si->bcch_frequency_list[0] >> 5) & 1; + s->nb_ext_ind_si5bis = (si->bcch_frequency_list[0] >> 5) & 1; + s->nb_ba_ind_si5bis = (si->bcch_frequency_list[0] >> 4) & 1; decode_freq_list(s->freq, si->bcch_frequency_list, - sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_REP_5bis); + sizeof(si->bcch_frequency_list), + 0xce, FREQ_TYPE_REP_5bis); s->si5bis = 1; + s->si10 = false; return 0; } int gsm48_decode_sysinfo5ter(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_5ter *si, int len) + const struct gsm48_system_information_type_5ter *si, int len) { - memcpy(s->si5t_msg, si, MIN(len, sizeof(s->si5t_msg))); + memcpy(s->si5t_msg, si, OSMO_MIN(len, sizeof(s->si5t_msg))); /* Neighbor Cell Description */ - s->nb_multi_rep_si5ter = (si->bcch_frequency_list[0] >> 6) & 3; - s->nb_ba_ind_si5ter = (si->bcch_frequency_list[0] >> 5) & 1; + s->nb_multi_rep_si5ter = (si->bcch_frequency_list[0] >> 5) & 3; + s->nb_ba_ind_si5ter = (si->bcch_frequency_list[0] >> 4) & 1; decode_freq_list(s->freq, si->bcch_frequency_list, - sizeof(si->bcch_frequency_list), 0x8e, FREQ_TYPE_REP_5ter); + sizeof(si->bcch_frequency_list), + 0x8e, FREQ_TYPE_REP_5ter); s->si5ter = 1; + s->si10 = false; return 0; } int gsm48_decode_sysinfo6(struct gsm48_sysinfo *s, - struct gsm48_system_information_type_6 *si, int len) + const struct gsm48_system_information_type_6 *si, int len) { int payload_len = len - sizeof(*si); - memcpy(s->si6_msg, si, MIN(len, sizeof(s->si6_msg))); + memcpy(s->si6_msg, si, OSMO_MIN(len, sizeof(s->si6_msg))); /* Cell Identity */ if (s->si6 && s->cell_id != ntohs(si->cell_identity)) @@ -830,7 +1076,7 @@ int gsm48_decode_sysinfo6(struct gsm48_sysinfo *s, "read.\n"); s->cell_id = ntohs(si->cell_identity); /* LAI */ - gsm48_decode_lai_hex(&si->lai, &s->mcc, &s->mnc, &s->lac); + gsm48_decode_lai2(&si->lai, &s->lai); /* Cell Options (SACCH) */ gsm48_decode_cellopt_sacch(s, &si->cell_options); /* NCC Permitted */ @@ -844,28 +1090,156 @@ int gsm48_decode_sysinfo6(struct gsm48_sysinfo *s, return 0; } -int gsm48_encode_lai_hex(struct gsm48_loc_area_id *lai, uint16_t mcc, - uint16_t mnc, uint16_t lac) +/* Get ARFCN from BCCH allocation found in SI5/SI5bis an SI5ter. See TS 44.018 §10.5.2.20. */ +int16_t arfcn_from_freq_index(const struct gsm48_sysinfo *s, uint16_t index) { - lai->digits[0] = (mcc >> 8) | (mcc & 0xf0); - lai->digits[1] = (mcc & 0x0f) | (mnc << 4); - lai->digits[2] = (mnc >> 8) | (mnc & 0xf0); - lai->lac = htons(lac); + uint16_t arfcn, i = 0; + + /* Search for ARFCN found in SI5 or SI5bis. (first sub list) */ + for (arfcn = 1; arfcn <= 1024; arfcn++) { + if (!(s->freq[arfcn & 1023].mask & (FREQ_TYPE_REP_5 | FREQ_TYPE_REP_5bis))) + continue; + if (index == i++) + return arfcn & 1023; + } - return 0; + /* Search for ARFCN found in SI5ter. (second sub list) */ + for (arfcn = 1; arfcn <= 1024; arfcn++) { + if (!(s->freq[arfcn & 1023].mask & FREQ_TYPE_REP_5ter)) + continue; + if (index == i++) + return arfcn & 1023; + } + + /* If not found, return EOF (-1) as idicator. */ + return EOF; } - int gsm48_decode_lai_hex(struct gsm48_loc_area_id *lai, uint16_t *mcc, - uint16_t *mnc, uint16_t *lac) +int gsm48_decode_sysinfo10(struct gsm48_sysinfo *s, + const struct gsm48_system_information_type_10 *si, int len) { - *mcc = ((lai->digits[0] & 0x0f) << 8) - | (lai->digits[0] & 0xf0) - | (lai->digits[1] & 0x0f); - *mnc = ((lai->digits[2] & 0x0f) << 8) - | (lai->digits[2] & 0xf0) - | ((lai->digits[1] & 0xf0) >> 4); - *lac = ntohs(lai->lac); - + int payload_len = len - sizeof(*si); + struct bitvec bv; + int i; + int rc; + + bv = (struct bitvec) { + .data_len = payload_len, + .data = (uint8_t *)si->rest_octets, + }; + + memcpy(s->si10_msg, si, OSMO_MIN(len, sizeof(s->si10_msg))); + + /* Clear cell list. */ + s->si10_cell_num = 0; + memset(s->si10_cell, 0, sizeof(s->si10_cell)); + + /* SI 10 Rest Octets of first neighbor cell, if included. */ + rc = gsm48_decode_si10_rest_first(s, &bv, &s->si10_cell[0]); + if (rc == EOF) { + s->si10 = true; + return 0; + } + if (rc < 0) + return rc; + s->si10_cell[0].arfcn = arfcn_from_freq_index(s, s->si10_cell[0].index); + s->si10_cell_num++; + + for (i = 1; i < ARRAY_SIZE(s->si10_cell); i++) { + /* Clone last cell info and then store differential elements. */ + memcpy(&s->si10_cell[i], &s->si10_cell[i - 1], sizeof(s->si10_cell[i])); + /* SI 10 Rest Octets of other neighbor cell, if included. */ + rc = gsm48_decode_si10_rest_other(s, &bv, &s->si10_cell[i]); + if (rc == EOF) + break; + if (rc < 0) + return rc; + s->si10_cell[i].arfcn = arfcn_from_freq_index(s, s->si10_cell[i].index); + s->si10_cell_num++; + } + + s->si10 = true; return 0; } +int gsm48_decode_sysinfo13(struct gsm48_sysinfo *s, + const struct gsm48_system_information_type_13 *si, int len) +{ + SI13_RestOctets_t si13ro; + int rc; + + memcpy(s->si13_msg, si, OSMO_MIN(len, sizeof(s->si13_msg))); + + rc = osmo_gprs_rlcmac_decode_si13ro(&si13ro, si->rest_octets, sizeof(s->si13_msg)); + if (rc != 0) { + LOGP(DRR, LOGL_ERROR, "Failed to parse SI13 Rest Octets\n"); + return rc; + } + + if (si13ro.UnionType != 0) { + LOGP(DRR, LOGL_NOTICE, "PBCCH is deprecated and not supported\n"); + return -ENOTSUP; + } + + s->gprs.hopping = si13ro.Exist_MA; + if (s->gprs.hopping) { + const GPRS_Mobile_Allocation_t *gma = &si13ro.GPRS_Mobile_Allocation; + + s->gprs.hsn = gma->HSN; + s->gprs.rfl_num_len = gma->ElementsOf_RFL_NUMBER; + memcpy(&s->gprs.rfl_num[0], &gma->RFL_NUMBER[0], sizeof(gma->RFL_NUMBER)); + + if (gma->UnionType == 0) { /* MA Bitmap */ + const MobileAllocation_t *ma = &gma->u.MA; + s->gprs.ma_bitlen = ma->MA_BitLength; + memcpy(&s->gprs.ma_bitmap[0], &ma->MA_BITMAP[0], sizeof(ma->MA_BITMAP)); + } else { /* ARFCN Index List */ + const ARFCN_index_list_t *ai = &gma->u.ARFCN_index_list; + s->gprs.arfcn_idx_len = ai->ElementsOf_ARFCN_INDEX; + memcpy(&s->gprs.arfcn_idx[0], &ai->ARFCN_INDEX[0], sizeof(ai->ARFCN_INDEX)); + } + } + + const PBCCH_Not_present_t *np = &si13ro.u.PBCCH_Not_present; + + s->gprs.rac = np->RAC; + s->gprs.prio_acc_thresh = np->PRIORITY_ACCESS_THR; + s->gprs.nco = np->NETWORK_CONTROL_ORDER; + + const GPRS_Cell_Options_t *gco = &np->GPRS_Cell_Options; + + s->gprs.nmo = gco->NMO; + s->gprs.T3168 = gco->T3168; + s->gprs.T3192 = gco->T3192; + s->gprs.ab_type = gco->ACCESS_BURST_TYPE; + s->gprs.ctrl_ack_type_use_block = !gco->CONTROL_ACK_TYPE; /* inverted */ + s->gprs.bs_cv_max = gco->BS_CV_MAX; + + s->gprs.pan_params_present = gco->Exist_PAN; + if (s->gprs.pan_params_present) { + s->gprs.pan_dec = gco->PAN_DEC; + s->gprs.pan_inc = gco->PAN_INC; + s->gprs.pan_max = gco->PAN_MAX; + } + + s->gprs.egprs_supported = 0; + if (gco->Exist_Extension_Bits) { + /* CSN.1 codec is not powerful enough (yet?) to decode this part :( */ + unsigned int ext_len = gco->Extension_Bits.extension_length; + const uint8_t *ext = &gco->Extension_Bits.Extension_Info[0]; + + s->gprs.egprs_supported = (ext[0] >> 7); + if (s->gprs.egprs_supported) { + if (ext_len < 6) + return -EINVAL; + s->gprs.egprs_pkt_chan_req = ~ext[0] & (1 << 6); /* inverted */ + s->gprs.egprs_bep_period = (ext[0] >> 2) & 0x0f; + } + } + + /* TODO: GPRS_Power_Control_Parameters */ + + s->si13 = 1; + + return 0; +} diff --git a/src/host/layer23/src/common/utils.c b/src/host/layer23/src/common/utils.c index 4ecb134b..417519b0 100644 --- a/src/host/layer23/src/common/utils.c +++ b/src/host/layer23/src/common/utils.c @@ -14,10 +14,6 @@ * 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 <osmocom/bb/common/utils.h> diff --git a/src/host/layer23/src/common/vty.c b/src/host/layer23/src/common/vty.c new file mode 100644 index 00000000..dd80a14b --- /dev/null +++ b/src/host/layer23/src/common/vty.c @@ -0,0 +1,1499 @@ +/* + * (C) 2010 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * 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. + * + */ + +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <sys/types.h> + +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/signal.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/crypt/auth.h> +#include <osmocom/vty/cpu_sched_vty.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/stats.h> +#include <osmocom/vty/misc.h> + +#include <osmocom/bb/common/vty.h> +#include <osmocom/bb/common/l23_app.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/common/networks.h> +#include <osmocom/bb/common/gps.h> +#include <osmocom/bb/common/l1l2_interface.h> + +extern struct llist_head active_connections; /* libosmocore */ + +bool l23_vty_reading = false; + +bool l23_vty_hide_default = false; + +static struct cmd_node ms_node = { + MS_NODE, + "%s(ms)# ", + 1 +}; + +static struct cmd_node gsmtap_node = { + GSMTAP_NODE, + "%s(gsmtap)# ", + 1 +}; + +struct cmd_node testsim_node = { + TESTSIM_NODE, + "%s(test-sim)# ", + 1 +}; + +static void l23_vty_restart_required_warn(struct vty *vty, struct osmocom_ms *ms) +{ + if (l23_vty_reading) + return; + if (ms->shutdown != MS_SHUTDOWN_NONE) + return; + vty_out(vty, "You must restart MS '%s' ('shutdown / no shutdown') for " + "change to take effect!%s", ms->name, VTY_NEWLINE); +} + +struct osmocom_ms *l23_vty_get_ms(const char *name, struct vty *vty) +{ + struct osmocom_ms *ms; + + llist_for_each_entry(ms, &ms_list, entity) { + if (!strcmp(ms->name, name)) { + if (ms->shutdown != MS_SHUTDOWN_NONE) { + vty_out(vty, "MS '%s' is admin down.%s", name, + VTY_NEWLINE); + return NULL; + } + return ms; + } + } + vty_out(vty, "MS name '%s' does not exist.%s", name, VTY_NEWLINE); + + return NULL; +} + +void l23_vty_ms_notify(struct osmocom_ms *ms, const char *fmt, ...) +{ + struct telnet_connection *connection; + char buffer[1000]; + va_list args; + struct vty *vty; + + if (fmt) { + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer) - 1, fmt, args); + buffer[sizeof(buffer) - 1] = '\0'; + va_end(args); + + if (!buffer[0]) + return; + } + + llist_for_each_entry(connection, &active_connections, entry) { + vty = connection->vty; + if (!vty) + continue; + if (!fmt) { + vty_out(vty, "%s%% (MS %s)%s", VTY_NEWLINE, ms->name, + VTY_NEWLINE); + continue; + } + if (buffer[strlen(buffer) - 1] == '\n') { + buffer[strlen(buffer) - 1] = '\0'; + vty_out(vty, "%% %s%s", buffer, VTY_NEWLINE); + buffer[strlen(buffer)] = '\n'; + } else + vty_out(vty, "%% %s", buffer); + } +} + +void l23_vty_printf(void *priv, const char *fmt, ...) +{ + char buffer[1000]; + struct vty *vty = priv; + va_list args; + + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer) - 1, fmt, args); + buffer[sizeof(buffer) - 1] = '\0'; + va_end(args); + + if (buffer[0]) { + if (buffer[strlen(buffer) - 1] == '\n') { + buffer[strlen(buffer) - 1] = '\0'; + vty_out(vty, "%s%s", buffer, VTY_NEWLINE); + } else + vty_out(vty, "%s", buffer); + } +} + +/* placeholder for layer23 shared MS info to be dumped */ +void l23_ms_dump(struct osmocom_ms *ms, struct vty *vty) +{ + struct gsm_settings *set = &ms->settings; + char *service = ""; + + if (!ms->started) + service = ", radio is not started"; + else if (ms->mmlayer.state == GSM48_MM_ST_NULL) { + service = ", MM connection not yet set up"; + } else if (ms->mmlayer.state == GSM48_MM_ST_MM_IDLE) { + /* current MM idle state */ + switch (ms->mmlayer.substate) { + case GSM48_MM_SST_NORMAL_SERVICE: + case GSM48_MM_SST_PLMN_SEARCH_NORMAL: + service = ", service is normal"; + break; + case GSM48_MM_SST_LOC_UPD_NEEDED: + case GSM48_MM_SST_ATTEMPT_UPDATE: + service = ", service is limited (pending)"; + break; + case GSM48_MM_SST_NO_CELL_AVAIL: + service = ", service is unavailable"; + break; + default: + if (ms->subscr.sim_valid) + service = ", service is limited"; + else + service = ", service is limited " + "(IMSI detached)"; + break; + } + } else + service = ", MM connection active"; + + vty_out(vty, "MS '%s' is %s%s%s%s", ms->name, + (ms->shutdown != MS_SHUTDOWN_NONE) ? "administratively " : "", + (ms->shutdown != MS_SHUTDOWN_NONE || !ms->started) ? "down" : "up", + (ms->shutdown == MS_SHUTDOWN_NONE) ? service : "", + VTY_NEWLINE); + + vty_out(vty, " IMEI: %s%s", set->imei, VTY_NEWLINE); + vty_out(vty, " IMEISV: %s%s", set->imeisv, VTY_NEWLINE); + if (set->imei_random) + vty_out(vty, " IMEI generation: random (%d trailing " + "digits)%s", set->imei_random, VTY_NEWLINE); + else + vty_out(vty, " IMEI generation: fixed%s", VTY_NEWLINE); +} + +/* CONFIG NODE: */ +DEFUN(cfg_hide_default, cfg_hide_default_cmd, "hide-default", + "Hide most default values in config to make it more compact") +{ + l23_vty_hide_default = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_hide_default, cfg_no_hide_default_cmd, "no hide-default", + NO_STR "Show default values in config") +{ + l23_vty_hide_default = 0; + + return CMD_SUCCESS; +} + +DEFUN(show_support, show_support_cmd, "show support [MS_NAME]", + SHOW_STR "Display information about MS support\n" + "Name of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + if (argc) { + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + gsm_support_dump(ms, l23_vty_printf, vty); + } else { + llist_for_each_entry(ms, &ms_list, entity) { + gsm_support_dump(ms, l23_vty_printf, vty); + vty_out(vty, "%s", VTY_NEWLINE); + } + } + + return CMD_SUCCESS; +} + +DEFUN(show_subscr, show_subscr_cmd, "show subscriber [MS_NAME]", + SHOW_STR "Display information about subscriber\n" + "Name of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + if (argc) { + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + gsm_subscr_dump(&ms->subscr, l23_vty_printf, vty); + } else { + llist_for_each_entry(ms, &ms_list, entity) { + if (ms->shutdown == MS_SHUTDOWN_NONE) { + gsm_subscr_dump(&ms->subscr, l23_vty_printf, vty); + vty_out(vty, "%s", VTY_NEWLINE); + } + } + } + + return CMD_SUCCESS; +} + +/* "gsmtap" config */ +gDEFUN(l23_cfg_gsmtap, l23_cfg_gsmtap_cmd, "gsmtap", + "Configure GSMTAP\n") +{ + vty->node = GSMTAP_NODE; + return CMD_SUCCESS; +} + +static const struct value_string gsmtap_categ_gprs_names[] = { + { L23_GSMTAP_GPRS_C_DL_UNKNOWN, "dl-unknown" }, + { L23_GSMTAP_GPRS_C_DL_DUMMY, "dl-dummy" }, + { L23_GSMTAP_GPRS_C_DL_CTRL, "dl-ctrl" }, + { L23_GSMTAP_GPRS_C_DL_DATA_GPRS, "dl-data-gprs" }, + { L23_GSMTAP_GPRS_C_DL_DATA_EGPRS, "dl-data-egprs" }, + { L23_GSMTAP_GPRS_C_UL_UNKNOWN, "ul-unknown" }, + { L23_GSMTAP_GPRS_C_UL_DUMMY, "ul-dummy" }, + { L23_GSMTAP_GPRS_C_UL_CTRL, "ul-ctrl" }, + { L23_GSMTAP_GPRS_C_UL_DATA_GPRS, "ul-data-gprs" }, + { L23_GSMTAP_GPRS_C_UL_DATA_EGPRS, "ul-data-egprs" }, + { 0, NULL } +}; + +static const struct value_string gsmtap_categ_gprs_help[] = { + { L23_GSMTAP_GPRS_C_DL_UNKNOWN, "Unknown / Unparseable / Erroneous Downlink Blocks" }, + { L23_GSMTAP_GPRS_C_DL_DUMMY, "Downlink Dummy Blocks" }, + { L23_GSMTAP_GPRS_C_DL_CTRL, "Downlink Control Blocks" }, + { L23_GSMTAP_GPRS_C_DL_DATA_GPRS, "Downlink Data Blocks (GPRS)" }, + { L23_GSMTAP_GPRS_C_DL_DATA_EGPRS, "Downlink Data Blocks (EGPRS)" }, + { L23_GSMTAP_GPRS_C_UL_UNKNOWN, "Unknown / Unparseable / Erroneous Downlink Blocks" }, + { L23_GSMTAP_GPRS_C_UL_DUMMY, "Uplink Dummy Blocks" }, + { L23_GSMTAP_GPRS_C_UL_CTRL, "Uplink Control Blocks" }, + { L23_GSMTAP_GPRS_C_UL_DATA_GPRS, "Uplink Data Blocks (GPRS)" }, + { L23_GSMTAP_GPRS_C_UL_DATA_EGPRS, "Uplink Data Blocks (EGPRS)" }, + { 0, NULL } +}; + +DEFUN(cfg_gsmtap_gsmtap_remote_host, + cfg_gsmtap_gsmtap_remote_host_cmd, + "remote-host [HOSTNAME]", + "Enable GSMTAP Um logging\n" + "Remote IP address or hostname ('localhost' if omitted)\n") +{ + osmo_talloc_replace_string(l23_ctx, &l23_cfg.gsmtap.remote_host, + argc > 0 ? argv[0] : "localhost"); + + if (vty->type != VTY_FILE) + vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_gsmtap_no_gsmtap_remote_host, + cfg_gsmtap_no_gsmtap_remote_host_cmd, + "no remote-host", + NO_STR "Disable GSMTAP Um logging\n") +{ + TALLOC_FREE(l23_cfg.gsmtap.remote_host); + if (vty->type != VTY_FILE) + vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_gsmtap_gsmtap_local_host, + cfg_gsmtap_gsmtap_local_host_cmd, + "local-host " VTY_IPV46_CMD, + "Set source for GSMTAP Um logging\n" + "Local IPv4 address\n" "Local IPv6 address\n") +{ + osmo_talloc_replace_string(l23_ctx, &l23_cfg.gsmtap.local_host, argv[0]); + + if (vty->type != VTY_FILE) + vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_gsmtap_no_gsmtap_local_host, + cfg_gsmtap_no_gsmtap_local_host_cmd, + "no local-host", + NO_STR "Disable explicit source for GSMTAP Um logging\n") +{ + TALLOC_FREE(l23_cfg.gsmtap.local_host); + if (vty->type != VTY_FILE) + vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_gsmtap_gsmtap_lchan_all, cfg_gsmtap_gsmtap_lchan_all_cmd, + "lchan (enable-all|disable-all)", + "Enable/disable sending of UL/DL messages over GSMTAP\n" + "Enable all kinds of messages (all LCHAN)\n" + "Disable all kinds of messages (all LCHAN)\n") +{ + if (argv[0][0] == 'e') { + l23_cfg.gsmtap.lchan_mask = UINT32_MAX; + l23_cfg.gsmtap.lchan_acch_mask = UINT32_MAX; + l23_cfg.gsmtap.lchan_acch = true; + } else { + l23_cfg.gsmtap.lchan_mask = 0x00; + l23_cfg.gsmtap.lchan_acch_mask = 0x00; + l23_cfg.gsmtap.lchan_acch = false; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_gsmtap_gsmtap_lchan, cfg_gsmtap_gsmtap_lchan_cmd, + "HIDDEN", "HIDDEN") +{ + unsigned int channel; + + if (osmo_str_startswith(argv[0], "sacch")) { + if (strcmp(argv[0], "sacch") == 0) { + l23_cfg.gsmtap.lchan_acch = true; + } else { + channel = get_string_value(gsmtap_gsm_channel_names, argv[0]); + channel &= ~GSMTAP_CHANNEL_ACCH; + l23_cfg.gsmtap.lchan_acch_mask |= (1 << channel); + } + } else { + channel = get_string_value(gsmtap_gsm_channel_names, argv[0]); + l23_cfg.gsmtap.lchan_mask |= (1 << channel); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_gsmtap_no_gsmtap_lchan, cfg_gsmtap_no_gsmtap_lchan_cmd, + "HIDDEN", "HIDDEN") +{ + unsigned int channel; + + if (osmo_str_startswith(argv[0], "sacch")) { + if (strcmp(argv[0], "sacch") == 0) { + l23_cfg.gsmtap.lchan_acch = false; + } else { + channel = get_string_value(gsmtap_gsm_channel_names, argv[0]); + channel &= ~GSMTAP_CHANNEL_ACCH; + l23_cfg.gsmtap.lchan_acch_mask &= ~(1 << channel); + } + } else { + channel = get_string_value(gsmtap_gsm_channel_names, argv[0]); + l23_cfg.gsmtap.lchan_mask &= ~(1 << channel); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_gsmtap_gsmtap_categ_gprs_all, cfg_gsmtap_gsmtap_categ_gprs_all_cmd, + "category gprs (enable-all|disable-all)", + "Enable/disable sending of UL/DL messages over GSMTAP\n" + "Enable all kinds of messages (all categories)\n" + "Disable all kinds of messages (all categories)\n") +{ + + if (strcmp(argv[0], "enable-all") == 0) + l23_cfg.gsmtap.categ_gprs_mask = UINT32_MAX; + else + l23_cfg.gsmtap.categ_gprs_mask = 0x00; + + return CMD_SUCCESS; +} + +DEFUN(cfg_gsmtap_gsmtap_categ_gprs, cfg_gsmtap_gsmtap_categ_gprs_cmd, "HIDDEN", "HIDDEN") +{ + int categ; + + categ = get_string_value(gsmtap_categ_gprs_names, argv[0]); + if (categ < 0) + return CMD_WARNING; + + l23_cfg.gsmtap.categ_gprs_mask |= (1 << categ); + + return CMD_SUCCESS; +} + +DEFUN(cfg_gsmtap_no_gsmtap_categ_gprs, cfg_gsmtap_no_gsmtap_categ_gprs_cmd, "HIDDEN", "HIDDEN") +{ + int categ; + + categ = get_string_value(gsmtap_categ_gprs_names, argv[0]); + if (categ < 0) + return CMD_WARNING; + + l23_cfg.gsmtap.categ_gprs_mask &= ~(1 << categ); + + return CMD_SUCCESS; +} + + +gDEFUN(l23_show_ms, l23_show_ms_cmd, "show ms [MS_NAME]", + SHOW_STR "Display available MS entities\n") +{ + struct osmocom_ms *ms; + + if (argc) { + llist_for_each_entry(ms, &ms_list, entity) { + if (!strcmp(ms->name, argv[0])) { + l23_ms_dump(ms, vty); + return CMD_SUCCESS; + } + } + vty_out(vty, "MS name '%s' does not exist.%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + + llist_for_each_entry(ms, &ms_list, entity) { + l23_ms_dump(ms, vty); + vty_out(vty, "%s", VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +static int _sim_testcard_cmd(struct vty *vty, int argc, const char *argv[], + int attached) +{ + struct osmocom_ms *ms; + struct gsm_settings *set; + int rc; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (ms->subscr.sim_valid) { + vty_out(vty, "SIM already attached, remove first!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + set = &ms->settings; + set->sim_type = GSM_SIM_TYPE_TEST; + + if (argc == 2) { + vty_out(vty, "Give MNC together with MCC%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (argc >= 3) { + struct osmo_plmn_id plmn; + if (osmo_mcc_from_str(argv[1], &plmn.mcc) < 0) { + vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (osmo_mnc_from_str(argv[2], &plmn.mnc, &plmn.mnc_3_digits) < 0) { + vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + memcpy(&set->test_sim.rplmn, &plmn, sizeof(plmn)); + set->test_sim.rplmn_valid = true; + } else { + set->test_sim.rplmn_valid = false; + } + + if (argc >= 4) + set->test_sim.lac = strtoul(argv[3], NULL, 16); + + if (argc >= 5) + set->test_sim.tmsi = strtoul(argv[4], NULL, 16); + + set->test_sim.imsi_attached = attached; + + rc = gsm_subscr_insert(ms); + if (rc < 0) { + vty_out(vty, "Attach test SIM card failed: %d%s", rc, VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(sim_testcard, sim_testcard_cmd, + "sim testcard MS_NAME [MCC] [MNC] [LAC] [TMSI]", + "SIM actions\nAttach built in test SIM\nName of MS (see \"show ms\")\n" + "Optionally set mobile Country Code of RPLMN\n" + "Optionally set mobile Network Code of RPLMN\n" + "Optionally set location area code of RPLMN\n" + "Optionally set current assigned TMSI") +{ + return _sim_testcard_cmd(vty, argc, argv, 0); +} + +DEFUN(sim_testcard_att, sim_testcard_att_cmd, + "sim testcard MS_NAME MCC MNC LAC TMSI attached", + "SIM actions\nAttach built in test SIM\nName of MS (see \"show ms\")\n" + "Set mobile Country Code of RPLMN\nSet mobile Network Code of RPLMN\n" + "Set location area code\nSet current assigned TMSI\n" + "Indicate to MM that card is already attached") +{ + return _sim_testcard_cmd(vty, argc, argv, 1); +} + +DEFUN(sim_sap, sim_sap_cmd, "sim sap MS_NAME", + "SIM actions\nAttach SIM over SAP interface\n" + "Name of MS (see \"show ms\")\n") +{ + struct osmocom_ms *ms; + struct gsm_settings *set; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (ms->subscr.sim_valid) { + vty_out(vty, "SIM already attached, remove first!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + set = &ms->settings; + set->sim_type = GSM_SIM_TYPE_SAP; + if (gsm_subscr_insert(ms) != 0) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN(sim_reader, sim_reader_cmd, "sim reader MS_NAME", + "SIM actions\nAttach SIM from reader\nName of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + struct gsm_settings *set; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (ms->subscr.sim_valid) { + vty_out(vty, "SIM already attached, remove first!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + set = &ms->settings; + set->sim_type = GSM_SIM_TYPE_L1PHY; + gsm_subscr_insert(ms); + + return CMD_SUCCESS; +} + +DEFUN(sim_remove, sim_remove_cmd, "sim remove MS_NAME", + "SIM actions\nDetach SIM card\nName of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (!ms->subscr.sim_valid) { + vty_out(vty, "No SIM attached!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_remove(ms); + return CMD_SUCCESS; +} + +DEFUN(sim_pin, sim_pin_cmd, "sim pin MS_NAME PIN", + "SIM actions\nEnter PIN for SIM card\nName of MS (see \"show ms\")\n" + "PIN number") +{ + struct osmocom_ms *ms; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (strlen(argv[1]) < 4 || strlen(argv[1]) > 8) { + vty_out(vty, "PIN must be in range 4..8!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!ms->subscr.sim_pin_required) { + vty_out(vty, "No PIN is required at this time!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_sim_pin(ms, (char *)argv[1], "", 0); + + return CMD_SUCCESS; +} + +DEFUN(sim_disable_pin, sim_disable_pin_cmd, "sim disable-pin MS_NAME PIN", + "SIM actions\nDisable PIN of SIM card\nName of MS (see \"show ms\")\n" + "PIN number") +{ + struct osmocom_ms *ms; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (strlen(argv[1]) < 4 || strlen(argv[1]) > 8) { + vty_out(vty, "PIN must be in range 4..8!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_sim_pin(ms, (char *)argv[1], "", -1); + + return CMD_SUCCESS; +} + +DEFUN(sim_enable_pin, sim_enable_pin_cmd, "sim enable-pin MS_NAME PIN", + "SIM actions\nEnable PIN of SIM card\nName of MS (see \"show ms\")\n" + "PIN number") +{ + struct osmocom_ms *ms; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (strlen(argv[1]) < 4 || strlen(argv[1]) > 8) { + vty_out(vty, "PIN must be in range 4..8!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_sim_pin(ms, (char *)argv[1], "", 1); + + return CMD_SUCCESS; +} + +DEFUN(sim_change_pin, sim_change_pin_cmd, "sim change-pin MS_NAME OLD NEW", + "SIM actions\nChange PIN of SIM card\nName of MS (see \"show ms\")\n" + "Old PIN number\nNew PIN number") +{ + struct osmocom_ms *ms; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (strlen(argv[1]) < 4 || strlen(argv[1]) > 8) { + vty_out(vty, "Old PIN must be in range 4..8!%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (strlen(argv[2]) < 4 || strlen(argv[2]) > 8) { + vty_out(vty, "New PIN must be in range 4..8!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_sim_pin(ms, (char *)argv[1], (char *)argv[2], 2); + + return CMD_SUCCESS; +} + +DEFUN(sim_unblock_pin, sim_unblock_pin_cmd, "sim unblock-pin MS_NAME PUC NEW", + "SIM actions\nChange PIN of SIM card\nName of MS (see \"show ms\")\n" + "Personal Unblock Key\nNew PIN number") +{ + struct osmocom_ms *ms; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (strlen(argv[1]) != 8) { + vty_out(vty, "PUC must be 8 digits!%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (strlen(argv[2]) < 4 || strlen(argv[2]) > 8) { + vty_out(vty, "PIN must be in range 4..8!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_sim_pin(ms, (char *)argv[1], (char *)argv[2], 99); + + return CMD_SUCCESS; +} + +DEFUN(sim_lai, sim_lai_cmd, "sim lai MS_NAME MCC MNC LAC", + "SIM actions\nChange LAI of SIM card\nName of MS (see \"show ms\")\n" + "Mobile Country Code\nMobile Network Code\nLocation Area Code " + " (use 0000 to remove LAI)") +{ + struct osmocom_ms *ms; + struct osmo_plmn_id plmn; + uint16_t lac; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (osmo_mcc_from_str(argv[1], &plmn.mcc) < 0) { + vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (osmo_mnc_from_str(argv[2], &plmn.mnc, &plmn.mnc_3_digits) < 0) { + vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + + lac = strtoul(argv[3], NULL, 0); + + memcpy(&ms->subscr.lai.plmn, &plmn, sizeof(plmn)); + ms->subscr.lai.lac = lac; + ms->subscr.tmsi = GSM_RESERVED_TMSI; + + gsm_subscr_write_loci(ms); + + return CMD_SUCCESS; +} + +/* per MS config */ +gDEFUN(l23_cfg_ms, l23_cfg_ms_cmd, "ms MS_NAME", + "Select a mobile station to configure\nName of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + llist_for_each_entry(ms, &ms_list, entity) { + if (!strcmp(ms->name, argv[0])) { + vty->index = ms; + vty->node = MS_NODE; + return CMD_SUCCESS; + } + } + + vty_out(vty, "MS name '%s' does not exits%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; +} + +DEFUN(cfg_ms_layer2, cfg_ms_layer2_cmd, "layer2-socket PATH", + "Define socket path to connect between layer 2 and layer 1\n" + "Unix socket, default '" L2_DEFAULT_SOCKET_PATH "'") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + OSMO_STRLCPY_ARRAY(set->layer2_socket_path, argv[0]); + + l23_vty_restart_required_warn(vty, ms); + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_imei, cfg_ms_imei_cmd, "imei IMEI [SV]", + "Set IMEI (enter without control digit)\n15 Digits IMEI\n" + "Software version digit") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + char *error, *sv = "0"; + + if (argc >= 2) + sv = (char *)argv[1]; + + error = gsm_check_imei(argv[0], sv); + if (error) { + vty_out(vty, "%s%s", error, VTY_NEWLINE); + return CMD_WARNING; + } + + OSMO_STRLCPY_ARRAY(set->imei, argv[0]); + OSMO_STRLCPY_ARRAY(set->imeisv, argv[0]); + osmo_strlcpy(set->imeisv + GSM23003_IMEI_NUM_DIGITS, sv, + sizeof(set->imeisv) - GSM23003_IMEI_NUM_DIGITS); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_imei_fixed, cfg_ms_imei_fixed_cmd, "imei-fixed", + "Use fixed IMEI on every power on") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->imei_random = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_imei_random, cfg_ms_imei_random_cmd, "imei-random <0-15>", + "Use random IMEI on every power on\n" + "Number of trailing digits to randomize") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->imei_random = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_sim, cfg_ms_sim_cmd, "sim (none|reader|test|sap)", + "Set SIM card to attach when powering on\nAttach no SIM\n" + "Attach SIM from reader\nAttach build in test SIM\n" + "Attach SIM over SAP interface") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + switch (argv[0][0]) { + case 'n': + set->sim_type = GSM_SIM_TYPE_NONE; + break; + case 'r': + set->sim_type = GSM_SIM_TYPE_L1PHY; + break; + case 't': + set->sim_type = GSM_SIM_TYPE_TEST; + break; + case 's': + set->sim_type = GSM_SIM_TYPE_SAP; + break; + default: + vty_out(vty, "unknown SIM type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + l23_vty_restart_required_warn(vty, ms); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_no_shutdown, cfg_ms_no_shutdown_cmd, "no shutdown", + NO_STR "Activate and run MS") +{ + struct osmocom_ms *ms = vty->index; + + struct osmobb_l23_vty_sig_data data; + memset(&data, 0, sizeof(data)); + + data.vty = vty; + data.ms_start.ms = ms; + data.ms_start.rc = CMD_SUCCESS; + osmo_signal_dispatch(SS_L23_VTY, S_L23_VTY_MS_START, &data); + + return data.ms_start.rc; +} + +DEFUN(cfg_ms_shutdown, cfg_ms_shutdown_cmd, "shutdown", + "Shut down and deactivate MS") +{ + struct osmocom_ms *ms = vty->index; + + struct osmobb_l23_vty_sig_data data; + memset(&data, 0, sizeof(data)); + + data.vty = vty; + data.ms_stop.ms = ms; + data.ms_stop.force = false; + data.ms_stop.rc = CMD_SUCCESS; + osmo_signal_dispatch(SS_L23_VTY, S_L23_VTY_MS_STOP, &data); + + return data.ms_stop.rc; +} + +DEFUN(cfg_ms_shutdown_force, cfg_ms_shutdown_force_cmd, "shutdown force", + "Shut down and deactivate MS\nDo not perform IMSI detach") +{ + struct osmocom_ms *ms = vty->index; + + struct osmobb_l23_vty_sig_data data; + memset(&data, 0, sizeof(data)); + + data.vty = vty; + data.ms_stop.ms = ms; + data.ms_stop.force = true; + data.ms_stop.rc = CMD_SUCCESS; + osmo_signal_dispatch(SS_L23_VTY, S_L23_VTY_MS_STOP, &data); + + return data.ms_stop.rc; +} + +/* per testsim config */ +DEFUN(cfg_ms_testsim, cfg_ms_testsim_cmd, "test-sim", + "Configure test SIM emulation") +{ + vty->node = TESTSIM_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_testsim_imsi, cfg_testsim_imsi_cmd, "imsi IMSI", + "Set IMSI on test card\n15 digits IMSI") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + if (!osmo_imsi_str_valid(argv[0])) { + vty_out(vty, "Wrong IMSI format%s", VTY_NEWLINE); + return CMD_WARNING; + } + + OSMO_STRLCPY_ARRAY(set->test_sim.imsi, argv[0]); + + l23_vty_restart_required_warn(vty, ms); + + return CMD_SUCCESS; +} + +#define HEX_STR "\nByte as two digits hexadecimal" +DEFUN(cfg_testsim_ki_xor, cfg_testsim_ki_xor_cmd, "ki xor HEX HEX HEX HEX HEX HEX " + "HEX HEX HEX HEX HEX HEX", + "Set Key (Ki) on test card\nUse XOR algorithm" HEX_STR HEX_STR HEX_STR + HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR) +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + uint8_t ki[12]; + const char *p; + int i; + + for (i = 0; i < 12; i++) { + p = argv[i]; + if (!strncmp(p, "0x", 2)) + p += 2; + if (strlen(p) != 2) { + vty_out(vty, "Expecting two digits hex value (with or " + "without 0x in front)%s", VTY_NEWLINE); + return CMD_WARNING; + } + ki[i] = strtoul(p, NULL, 16); + } + + set->test_sim.ki_type = OSMO_AUTH_ALG_XOR; + memcpy(set->test_sim.ki, ki, 12); + return CMD_SUCCESS; +} + +DEFUN(cfg_testsim_ki_comp128, cfg_testsim_ki_comp128_cmd, "ki comp128 HEX HEX HEX " + "HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX", + "Set Key (Ki) on test card\nUse XOR algorithm" HEX_STR HEX_STR HEX_STR + HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR + HEX_STR HEX_STR HEX_STR HEX_STR) +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + uint8_t ki[16]; + const char *p; + int i; + + for (i = 0; i < 16; i++) { + p = argv[i]; + if (!strncmp(p, "0x", 2)) + p += 2; + if (strlen(p) != 2) { + vty_out(vty, "Expecting two digits hex value (with or " + "without 0x in front)%s", VTY_NEWLINE); + return CMD_WARNING; + } + ki[i] = strtoul(p, NULL, 16); + } + + set->test_sim.ki_type = OSMO_AUTH_ALG_COMP128v1; + memcpy(set->test_sim.ki, ki, 16); + return CMD_SUCCESS; +} + +DEFUN(cfg_testsim_barr, cfg_testsim_barr_cmd, "barred-access", + "Allow access to barred cells") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->test_sim.barr = true; + + return CMD_SUCCESS; +} + +DEFUN(cfg_testsim_no_barr, cfg_testsim_no_barr_cmd, "no barred-access", + NO_STR "Deny access to barred cells") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->test_sim.barr = false; + + return CMD_SUCCESS; +} + +DEFUN(cfg_testsim_no_rplmn, cfg_testsim_no_rplmn_cmd, "no rplmn", + NO_STR "Unset Registered PLMN") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->test_sim.rplmn_valid = false; + set->test_sim.rplmn.mcc = 1; + set->test_sim.rplmn.mnc = 1; + set->test_sim.rplmn.mnc_3_digits = false; + set->test_sim.lac = 0x0000; + set->test_sim.tmsi = GSM_RESERVED_TMSI; + + l23_vty_restart_required_warn(vty, ms); + + return CMD_SUCCESS; +} + +static int _testsim_rplmn_cmd(struct vty *vty, int argc, const char *argv[], bool attached) +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + struct osmo_plmn_id plmn; + + if (osmo_mcc_from_str(argv[0], &plmn.mcc) < 0) { + vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (osmo_mnc_from_str(argv[1], &plmn.mnc, &plmn.mnc_3_digits) < 0) { + vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + set->test_sim.rplmn_valid = true; + memcpy(&set->test_sim.rplmn, &plmn, sizeof(plmn)); + + if (argc >= 3) + set->test_sim.lac = strtoul(argv[2], NULL, 16); + else + set->test_sim.lac = 0xfffe; + + if (argc >= 4) + set->test_sim.tmsi = strtoul(argv[3], NULL, 16); + else + set->test_sim.tmsi = GSM_RESERVED_TMSI; + + set->test_sim.imsi_attached = attached; + + l23_vty_restart_required_warn(vty, ms); + + return CMD_SUCCESS; +} + +DEFUN(cfg_testsim_rplmn, cfg_testsim_rplmn_cmd, + "rplmn MCC MNC [LAC] [TMSI]", + "Set Registered PLMN\nMobile Country Code\nMobile Network Code\n" + "Optionally set location area code\n" + "Optionally set current assigned TMSI") +{ + return _testsim_rplmn_cmd(vty, argc, argv, false); +} + +DEFUN(cfg_testsim_rplmn_att, cfg_testsim_rplmn_att_cmd, + "rplmn MCC MNC LAC TMSI attached", + "Set Registered PLMN\nMobile Country Code\nMobile Network Code\n" + "Set location area code\nSet current assigned TMSI\n" + "Indicate to MM that card is already attached") +{ + return _testsim_rplmn_cmd(vty, argc, argv, true); +} + +DEFUN(cfg_testsim_hplmn, cfg_testsim_hplmn_cmd, "hplmn-search (everywhere|foreign-country)", + "Set Home PLMN search mode\n" + "Search for HPLMN when on any other network\n" + "Search for HPLMN when in a different country") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + switch (argv[0][0]) { + case 'e': + set->test_sim.always_search_hplmn = true; + break; + case 'f': + set->test_sim.always_search_hplmn = false; + break; + } + + l23_vty_restart_required_warn(vty, ms); + + return CMD_SUCCESS; +} + +static int _testsim_locigprs_cmd(struct vty *vty, int argc, const char *argv[], bool attached) +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + struct osmo_plmn_id plmn; + + if (osmo_mcc_from_str(argv[0], &plmn.mcc) < 0) { + vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (osmo_mnc_from_str(argv[1], &plmn.mnc, &plmn.mnc_3_digits) < 0) { + vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + set->test_sim.locigprs.valid = true; + set->test_sim.locigprs.rai.mcc = plmn.mcc; + set->test_sim.locigprs.rai.mnc = plmn.mnc; + set->test_sim.locigprs.rai.mnc_3_digits = plmn.mnc_3_digits; + + if (argc >= 3) + set->test_sim.locigprs.rai.lac = strtoul(argv[2], NULL, 16); + else + set->test_sim.locigprs.rai.lac = 0xfffe; + + if (argc >= 4) + set->test_sim.locigprs.rai.rac = strtoul(argv[3], NULL, 16); + else + set->test_sim.locigprs.rai.rac = 0xff; + + if (argc >= 5) + set->test_sim.locigprs.ptmsi = strtoul(argv[4], NULL, 16); + else + set->test_sim.locigprs.ptmsi = GSM_RESERVED_TMSI; + + if (argc >= 6) + set->test_sim.locigprs.ptmsi_sig = strtoul(argv[5], NULL, 16); + else + set->test_sim.locigprs.ptmsi_sig = GSM_RESERVED_TMSI; + + set->test_sim.locigprs.imsi_attached = attached; + + l23_vty_restart_required_warn(vty, ms); + + return CMD_SUCCESS; +} + +DEFUN(cfg_testsim_no_locigprs, cfg_testsim_no_locigprs_cmd, "no locigprs", + NO_STR "Unset EF LOCIgprs\n") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->test_sim.locigprs.valid = false; + set->test_sim.locigprs.ptmsi = GSM_RESERVED_TMSI; + set->test_sim.locigprs.ptmsi_sig = GSM_RESERVED_TMSI; + set->test_sim.locigprs.rai.mcc = 1; + set->test_sim.locigprs.rai.mnc = 1; + set->test_sim.locigprs.rai.mnc_3_digits = false; + set->test_sim.locigprs.rai.lac = 0x0000; + set->test_sim.locigprs.rai.rac = 0x0000; + + l23_vty_restart_required_warn(vty, ms); + + return CMD_SUCCESS; +} + +DEFUN(cfg_testsim_locigprs, cfg_testsim_locigprs_cmd, + "locigprs MCC MNC [LAC] [RAC] [PTMSI] [PTMSISIG]", + "Set EF LOCIgprs\nMobile Country Code\nMobile Network Code\n" + "Optionally set location area code\n" + "Optionally set routing area code\n" + "Optionally set current assigned P-TMSI\n" + "Optionally set current assigned P-TMSI signature\n") +{ + return _testsim_locigprs_cmd(vty, argc, argv, false); +} + +DEFUN(cfg_testsim_locigprs_att, cfg_testsim_locigprs_att_cmd, + "locigprs MCC MNC LAC RAC PTMSI PTMSISIG attached", + "Set EF LOCIgprs\nMobile Country Code\nMobile Network Code\n" + "Set location area code\n" + "Set routing area code\n" + "Set current assigned P-TMSI\n" + "Set current assigned P-TMSI signature\n" + "Indicate to MM that card is already attached\n") +{ + return _testsim_locigprs_cmd(vty, argc, argv, true); +} + +static int l23_vty_config_write_gsmtap_node(struct vty *vty) +{ + const char *chan_buf; + unsigned int i; + + vty_out(vty, "gsmtap%s", VTY_NEWLINE); + + if (l23_cfg.gsmtap.remote_host) + vty_out(vty, " remote-host %s%s", l23_cfg.gsmtap.remote_host, VTY_NEWLINE); + else + vty_out(vty, " no remote-host%s", VTY_NEWLINE); + + if (l23_cfg.gsmtap.local_host) + vty_out(vty, " local-host %s%s", l23_cfg.gsmtap.local_host, VTY_NEWLINE); + else + vty_out(vty, " no local-host%s", VTY_NEWLINE); + + if (l23_cfg.gsmtap.lchan_acch) + vty_out(vty, " lchan sacch%s", VTY_NEWLINE); + + for (i = 0; i < sizeof(uint32_t) * 8; i++) { + if (l23_cfg.gsmtap.lchan_acch_mask & ((uint32_t) 1 << i)) { + chan_buf = get_value_string_or_null(gsmtap_gsm_channel_names, GSMTAP_CHANNEL_ACCH | i); + if (chan_buf == NULL) + continue; + chan_buf = osmo_str_tolower(chan_buf); + vty_out(vty, " lchan %s%s", chan_buf, VTY_NEWLINE); + } + } + + for (i = 0; i < sizeof(uint32_t) * 8; i++) { + if (l23_cfg.gsmtap.lchan_mask & ((uint32_t) 1 << i)) { + chan_buf = get_value_string_or_null(gsmtap_gsm_channel_names, i); + if (chan_buf == NULL) + continue; + chan_buf = osmo_str_tolower(chan_buf); + vty_out(vty, " lchan %s%s", chan_buf, VTY_NEWLINE); + } + } + + for (i = 0; i < 32; i++) { + if (l23_cfg.gsmtap.categ_gprs_mask & ((uint32_t)1 << i)) { + const char *category_buf; + if (!(category_buf = get_value_string_or_null(gsmtap_categ_gprs_names, i))) + continue; + vty_out(vty, " category gprs %s%s", category_buf, VTY_NEWLINE); + } + } + + return CMD_SUCCESS; +} + +static int l23_vty_config_write_testsim_node(struct vty *vty, const struct osmocom_ms *ms, const char *prefix) +{ + const struct gsm_settings *set = &ms->settings; + vty_out(vty, "%stest-sim%s", prefix, VTY_NEWLINE); + vty_out(vty, "%s imsi %s%s", prefix, set->test_sim.imsi, VTY_NEWLINE); + switch (set->test_sim.ki_type) { + case OSMO_AUTH_ALG_XOR: + vty_out(vty, "%s ki xor %s%s", + prefix, osmo_hexdump(set->test_sim.ki, 12), VTY_NEWLINE); + break; + case OSMO_AUTH_ALG_COMP128v1: + vty_out(vty, "%s ki comp128 %s%s", + prefix, osmo_hexdump(set->test_sim.ki, 16), VTY_NEWLINE); + break; + } + if (!l23_vty_hide_default || set->test_sim.barr) + vty_out(vty, "%s %sbarred-access%s", prefix, + (set->test_sim.barr) ? "" : "no ", VTY_NEWLINE); + if (set->test_sim.rplmn_valid) { + vty_out(vty, "%s rplmn %s %s", prefix, + osmo_mcc_name(set->test_sim.rplmn.mcc), + osmo_mnc_name(set->test_sim.rplmn.mnc, set->test_sim.rplmn.mnc_3_digits)); + if (set->test_sim.lac > 0x0000 && set->test_sim.lac < 0xfffe) { + vty_out(vty, " 0x%04x", set->test_sim.lac); + if (set->test_sim.tmsi != GSM_RESERVED_TMSI) { + vty_out(vty, " 0x%08x", set->test_sim.tmsi); + if (set->test_sim.imsi_attached) + vty_out(vty, " attached"); + } + } + vty_out(vty, "%s", VTY_NEWLINE); + } else + if (!l23_vty_hide_default) + vty_out(vty, "%s no rplmn%s", prefix, VTY_NEWLINE); + + if (!l23_vty_hide_default || set->test_sim.always_search_hplmn) + vty_out(vty, "%s hplmn-search %s%s", prefix, + set->test_sim.always_search_hplmn ? "everywhere" : "foreign-country", + VTY_NEWLINE); + + if (set->test_sim.locigprs.valid) { + vty_out(vty, "%s locigprs %s %s", prefix, + osmo_mcc_name(set->test_sim.locigprs.rai.mcc), + osmo_mnc_name(set->test_sim.locigprs.rai.mnc, + set->test_sim.locigprs.rai.mnc_3_digits)); + if (set->test_sim.locigprs.rai.lac > 0x0000 && set->test_sim.locigprs.rai.lac < 0xfffe) { + vty_out(vty, " 0x%04x", set->test_sim.locigprs.rai.lac); + if (set->test_sim.locigprs.rai.rac < 0xff) { + vty_out(vty, " 0x%02x", set->test_sim.locigprs.rai.rac); + if (set->test_sim.locigprs.ptmsi != GSM_RESERVED_TMSI) { + vty_out(vty, " 0x%08x 0x%06x", + set->test_sim.locigprs.ptmsi, + set->test_sim.locigprs.ptmsi_sig); + if (set->test_sim.locigprs.imsi_attached) + vty_out(vty, " attached"); + } + } + } + vty_out(vty, "%s", VTY_NEWLINE); + } else + if (!l23_vty_hide_default) + vty_out(vty, "%s no locigprs%s", prefix, VTY_NEWLINE); + return CMD_SUCCESS; +} + +void l23_vty_config_write_ms_node(struct vty *vty, const struct osmocom_ms *ms, const char *prefix) +{ + size_t prefix_len = strlen(prefix); + char *prefix_content = alloca(prefix_len + 1 + 1); + + memcpy(prefix_content, prefix, prefix_len); + prefix_content[prefix_len] = ' '; + prefix_content[prefix_len + 1] = '\0'; + + vty_out(vty, "%sms %s%s", prefix, ms->name, VTY_NEWLINE); + l23_vty_config_write_ms_node_contents(vty, ms, prefix_content); + l23_vty_config_write_ms_node_contents_final(vty, ms, prefix_content); +} + +/* placeholder for shared VTY commands */ +void l23_vty_config_write_ms_node_contents(struct vty *vty, const struct osmocom_ms *ms, const char *prefix) +{ + const struct gsm_settings *set = &ms->settings; + + vty_out(vty, "%slayer2-socket %s%s", prefix, set->layer2_socket_path, + VTY_NEWLINE); + + vty_out(vty, "%simei %s %s%s", prefix, set->imei, + set->imeisv + strlen(set->imei), VTY_NEWLINE); + if (set->imei_random) + vty_out(vty, "%simei-random %d%s", prefix, set->imei_random, VTY_NEWLINE); + else if (!l23_vty_hide_default) + vty_out(vty, "%simei-fixed%s", prefix, VTY_NEWLINE); + + switch (set->sim_type) { + case GSM_SIM_TYPE_NONE: + vty_out(vty, "%ssim none%s", prefix, VTY_NEWLINE); + break; + case GSM_SIM_TYPE_L1PHY: + vty_out(vty, "%ssim reader%s", prefix, VTY_NEWLINE); + break; + case GSM_SIM_TYPE_TEST: + vty_out(vty, "%ssim test%s", prefix, VTY_NEWLINE); + break; + case GSM_SIM_TYPE_SAP: + vty_out(vty, "%ssim sap%s", prefix, VTY_NEWLINE); + break; + default: + OSMO_ASSERT(0); + } + + l23_vty_config_write_testsim_node(vty, ms, prefix); +} + +/* placeholder for shared VTY commands. Must be put at the end of the node: */ +void l23_vty_config_write_ms_node_contents_final(struct vty *vty, const struct osmocom_ms *ms, const char *prefix) +{ + /* no shutdown must be written to config, because shutdown is default */ + vty_out(vty, "%s%sshutdown%s", prefix, (ms->shutdown != MS_SHUTDOWN_NONE) ? "" : "no ", + VTY_NEWLINE); + vty_out(vty, "!%s", VTY_NEWLINE); +} + +static void l23_vty_init_gsmtap(void) +{ + cfg_gsmtap_gsmtap_lchan_cmd.string = vty_cmd_string_from_valstr(l23_ctx, gsmtap_gsm_channel_names, + "lchan (sacch|", + "|", ")", VTY_DO_LOWER); + cfg_gsmtap_gsmtap_lchan_cmd.doc = vty_cmd_string_from_valstr(l23_ctx, gsmtap_gsm_channel_names, + "Enable sending of UL/DL messages over GSMTAP\n" "SACCH\n", + "\n", "", 0); + + cfg_gsmtap_no_gsmtap_lchan_cmd.string = vty_cmd_string_from_valstr(l23_ctx, gsmtap_gsm_channel_names, + "no lchan (sacch|", + "|", ")", VTY_DO_LOWER); + cfg_gsmtap_no_gsmtap_lchan_cmd.doc = vty_cmd_string_from_valstr(l23_ctx, gsmtap_gsm_channel_names, + NO_STR "Disable sending of UL/DL messages over GSMTAP\n" "SACCH\n", + "\n", "", 0); + + + cfg_gsmtap_gsmtap_categ_gprs_cmd.string = vty_cmd_string_from_valstr(l23_ctx, gsmtap_categ_gprs_names, + "category gprs (", + "|", ")", VTY_DO_LOWER); + cfg_gsmtap_gsmtap_categ_gprs_cmd.doc = vty_cmd_string_from_valstr(l23_ctx, gsmtap_categ_gprs_help, + "GSMTAP Category\n" "GPRS\n", + "\n", "", 0); + cfg_gsmtap_no_gsmtap_categ_gprs_cmd.string = vty_cmd_string_from_valstr(l23_ctx, gsmtap_categ_gprs_names, + "no category gprs (", + "|", ")", VTY_DO_LOWER); + cfg_gsmtap_no_gsmtap_categ_gprs_cmd.doc = vty_cmd_string_from_valstr(l23_ctx, gsmtap_categ_gprs_help, + NO_STR "GSMTAP Category\n" "GPRS\n", + "\n", "", 0); + + install_element(CONFIG_NODE, &l23_cfg_gsmtap_cmd); + + install_node(&gsmtap_node, l23_vty_config_write_gsmtap_node); + install_element(GSMTAP_NODE, &cfg_gsmtap_gsmtap_remote_host_cmd); + install_element(GSMTAP_NODE, &cfg_gsmtap_no_gsmtap_remote_host_cmd); + install_element(GSMTAP_NODE, &cfg_gsmtap_gsmtap_local_host_cmd); + install_element(GSMTAP_NODE, &cfg_gsmtap_no_gsmtap_local_host_cmd); + install_element(GSMTAP_NODE, &cfg_gsmtap_gsmtap_lchan_all_cmd); + install_element(GSMTAP_NODE, &cfg_gsmtap_gsmtap_lchan_cmd); + install_element(GSMTAP_NODE, &cfg_gsmtap_no_gsmtap_lchan_cmd); + install_element(GSMTAP_NODE, &cfg_gsmtap_gsmtap_categ_gprs_all_cmd); + install_element(GSMTAP_NODE, &cfg_gsmtap_gsmtap_categ_gprs_cmd); + install_element(GSMTAP_NODE, &cfg_gsmtap_no_gsmtap_categ_gprs_cmd); +} + +int l23_vty_init(int (*config_write_ms_node_cb)(struct vty *), osmo_signal_cbfn *l23_vty_signal_cb) +{ + int rc = 0; + + if (l23_app_info.opt_supported & L23_OPT_TAP) + l23_vty_init_gsmtap(); + + if (l23_app_info.opt_supported & L23_OPT_VTY) + osmo_stats_vty_add_cmds(); + + install_element_ve(&show_subscr_cmd); + install_element_ve(&show_support_cmd); + + install_element(ENABLE_NODE, &sim_testcard_cmd); + install_element(ENABLE_NODE, &sim_testcard_att_cmd); + install_element(ENABLE_NODE, &sim_sap_cmd); + install_element(ENABLE_NODE, &sim_reader_cmd); + install_element(ENABLE_NODE, &sim_remove_cmd); + install_element(ENABLE_NODE, &sim_pin_cmd); + install_element(ENABLE_NODE, &sim_disable_pin_cmd); + install_element(ENABLE_NODE, &sim_enable_pin_cmd); + install_element(ENABLE_NODE, &sim_change_pin_cmd); + install_element(ENABLE_NODE, &sim_unblock_pin_cmd); + install_element(ENABLE_NODE, &sim_lai_cmd); + + install_element(CONFIG_NODE, &cfg_hide_default_cmd); + install_element(CONFIG_NODE, &cfg_no_hide_default_cmd); + + install_node(&ms_node, config_write_ms_node_cb); + install_element(MS_NODE, &cfg_ms_layer2_cmd); + install_element(MS_NODE, &cfg_ms_imei_cmd); + install_element(MS_NODE, &cfg_ms_imei_fixed_cmd); + install_element(MS_NODE, &cfg_ms_imei_random_cmd); + install_element(MS_NODE, &cfg_ms_sim_cmd); + install_element(MS_NODE, &cfg_ms_testsim_cmd); + install_node(&testsim_node, NULL); + install_element(TESTSIM_NODE, &cfg_testsim_imsi_cmd); + install_element(TESTSIM_NODE, &cfg_testsim_ki_xor_cmd); + install_element(TESTSIM_NODE, &cfg_testsim_ki_comp128_cmd); + install_element(TESTSIM_NODE, &cfg_testsim_barr_cmd); + install_element(TESTSIM_NODE, &cfg_testsim_no_barr_cmd); + install_element(TESTSIM_NODE, &cfg_testsim_no_rplmn_cmd); + install_element(TESTSIM_NODE, &cfg_testsim_rplmn_cmd); + install_element(TESTSIM_NODE, &cfg_testsim_rplmn_att_cmd); + install_element(TESTSIM_NODE, &cfg_testsim_hplmn_cmd); + install_element(TESTSIM_NODE, &cfg_testsim_no_locigprs_cmd); + install_element(TESTSIM_NODE, &cfg_testsim_locigprs_cmd); + install_element(TESTSIM_NODE, &cfg_testsim_locigprs_att_cmd); + install_element(MS_NODE, &cfg_ms_shutdown_cmd); + install_element(MS_NODE, &cfg_ms_shutdown_force_cmd); + install_element(MS_NODE, &cfg_ms_no_shutdown_cmd); + + /* Register the talloc context introspection command */ + osmo_talloc_vty_add_cmds(); + osmo_cpu_sched_vty_init(l23_ctx); + if (l23_vty_signal_cb) + rc = osmo_signal_register_handler(SS_L23_VTY, l23_vty_signal_cb, NULL); + return rc; +} + diff --git a/src/host/layer23/src/misc/Makefile.am b/src/host/layer23/src/misc/Makefile.am index 78ab962f..05995c3f 100644 --- a/src/host/layer23/src/misc/Makefile.am +++ b/src/host/layer23/src/misc/Makefile.am @@ -1,14 +1,78 @@ -AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBGPS_CFLAGS) -LDADD = ../common/liblayer23.a $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBGPS_LIBS) +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) -bin_PROGRAMS = bcch_scan ccch_scan echo_test cell_log cbch_sniff +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOGPRSRLCMAC_CFLAGS) \ + $(LIBOSMOGPRSLLC_CFLAGS) \ + $(LIBOSMOGPRSSNDCP_CFLAGS) \ + $(LIBGPS_CFLAGS) \ + $(NULL) -noinst_HEADERS = bcch_scan.h geo.h +LDADD = \ + $(top_builddir)/src/common/liblayer23.a \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOCODEC_LIBS) \ + $(LIBOSMOGPRSRLCMAC_LIBS) \ + $(LIBOSMOGPRSLLC_LIBS) \ + $(LIBOSMOGPRSSNDCP_LIBS) \ + $(LIBGPS_LIBS) \ + $(NULL) + +bin_PROGRAMS = \ + bcch_scan \ + ccch_scan \ + echo_test \ + cell_log \ + cbch_sniff \ + gsmmap \ + $(NULL) + +noinst_HEADERS = \ + bcch_scan.h \ + $(NULL) + +bcch_scan_SOURCES = \ + $(top_srcdir)/src/common/main.c \ + app_bcch_scan.c \ + bcch_scan.c \ + $(NULL) + +ccch_scan_SOURCES = \ + $(top_srcdir)/src/common/main.c \ + app_ccch_scan.c \ + rslms.c \ + $(NULL) + +echo_test_SOURCES = \ + $(top_srcdir)/src/common/main.c \ + app_echo_test.c \ + $(NULL) -bcch_scan_SOURCES = ../common/main.c app_bcch_scan.c bcch_scan.c -ccch_scan_SOURCES = ../common/main.c app_ccch_scan.c rslms.c -echo_test_SOURCES = ../common/main.c app_echo_test.c cell_log_LDADD = $(LDADD) -lm -cell_log_SOURCES = ../common/main.c app_cell_log.c cell_log.c geo.c -cbch_sniff_SOURCES = ../common/main.c app_cbch_sniff.c +cell_log_SOURCES = \ + $(top_srcdir)/src/common/main.c \ + app_cell_log.c \ + cell_log.c \ + geo.c \ + $(NULL) + +cbch_sniff_SOURCES = \ + $(top_srcdir)/src/common/main.c \ + app_cbch_sniff.c \ + $(NULL) + +gsmmap_LDADD = $(LDADD) -lm +gsmmap_SOURCES = \ + gsmmap.c \ + geo.c \ + locate.c \ + log.c \ + $(NULL) diff --git a/src/host/layer23/src/misc/app_bcch_scan.c b/src/host/layer23/src/misc/app_bcch_scan.c index 22e6f1b9..768920d0 100644 --- a/src/host/layer23/src/misc/app_bcch_scan.c +++ b/src/host/layer23/src/misc/app_bcch_scan.c @@ -15,16 +15,14 @@ * 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 <osmocom/bb/common/osmocom_data.h> #include <osmocom/bb/common/l1ctl.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/l23_app.h> +#include <osmocom/bb/common/l1l2_interface.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/misc/layer3.h> #include <osmocom/core/msgb.h> @@ -35,6 +33,8 @@ #include <l1ctl_proto.h> #include "bcch_scan.h" +static struct osmocom_ms *g_ms; + static int signal_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) { @@ -51,20 +51,32 @@ static int signal_cb(unsigned int subsys, unsigned int signal, return 0; } -int l23_app_init(struct osmocom_ms *ms) +static int _bcch_scan_start(void) +{ + int rc; + + rc = layer2_open(g_ms, g_ms->settings.layer2_socket_path); + if (rc < 0) { + fprintf(stderr, "Failed during layer2_open()\n"); + return rc; + } + + l1ctl_tx_reset_req(g_ms, L1CTL_RES_T_FULL); + return 0; +} + +int l23_app_init(void) { - /* don't do layer3_init() as we don't want an actualy L3 */ + g_ms = osmocom_ms_alloc(l23_ctx, "1"); + OSMO_ASSERT(g_ms); + /* don't do layer3_init() as we don't want an actual L3 */ fps_init(); - l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL); + l23_app_start = _bcch_scan_start; return osmo_signal_register_handler(SS_L1CTL, &signal_cb, NULL); } -static struct l23_app_info info = { +const struct l23_app_info l23_app_info = { .copyright = "Copyright (C) 2010 Harald Welte <laforge@gnumonks.org>\n", .contribution = "Contributions by Holger Hans Peter Freyther\n", + .opt_supported = L23_OPT_ARFCN | L23_OPT_TAP | L23_OPT_DBG, }; - -struct l23_app_info *l23_app_info() -{ - return &info; -} diff --git a/src/host/layer23/src/misc/app_cbch_sniff.c b/src/host/layer23/src/misc/app_cbch_sniff.c index 8256eaf6..f83815ef 100644 --- a/src/host/layer23/src/misc/app_cbch_sniff.c +++ b/src/host/layer23/src/misc/app_cbch_sniff.c @@ -16,31 +16,35 @@ * 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 <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/l1ctl.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/l23_app.h> #include <osmocom/bb/misc/layer3.h> +#include <osmocom/bb/common/sysinfo.h> +#include <osmocom/bb/common/l1l2_interface.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/core/msgb.h> #include <osmocom/core/talloc.h> #include <osmocom/core/select.h> #include <osmocom/core/signal.h> #include <osmocom/gsm/rsl.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/gsm/lapdm.h> #include <l1ctl_proto.h> -struct osmocom_ms *g_ms; +static struct osmocom_ms *g_ms; struct gsm48_sysinfo g_sysinfo = {}; static int try_cbch(struct osmocom_ms *ms, struct gsm48_sysinfo *s) { + uint8_t chan_nr; + if (!s->si1 || !s->si4) return 0; if (!s->chan_nr) { @@ -48,6 +52,13 @@ static int try_cbch(struct osmocom_ms *ms, struct gsm48_sysinfo *s) return 0; } + /* Convert received channel number to Osmocom specific one; + * this way the layer1 can activate proper CBCH task. */ + if (s->chan_nr != RSL_CHAN_SDCCH4_ACCH) + chan_nr = RSL_CHAN_OSMO_CBCH8 | (s->chan_nr & 0x07); + else + chan_nr = RSL_CHAN_OSMO_CBCH4; + if (s->h) { LOGP(DRR, LOGL_INFO, "chan_nr = 0x%02x TSC = %d MAIO = %d " "HSN = %d hseq (%d): %s\n", @@ -56,13 +67,13 @@ static int try_cbch(struct osmocom_ms *ms, struct gsm48_sysinfo *s) osmo_hexdump((unsigned char *) s->hopping, s->hopp_len * 2)); return l1ctl_tx_dm_est_req_h1(ms, s->maio, s->hsn, s->hopping, s->hopp_len, - s->chan_nr, s->tsc, - GSM48_CMODE_SIGN, 0); + chan_nr, s->tsc, + GSM48_CMODE_SIGN, 0, 0); } else { LOGP(DRR, LOGL_INFO, "chan_nr = 0x%02x TSC = %d ARFCN = %d\n", s->chan_nr, s->tsc, s->arfcn); return l1ctl_tx_dm_est_req_h0(ms, s->arfcn, - s->chan_nr, s->tsc, GSM48_CMODE_SIGN, 0); + chan_nr, s->tsc, GSM48_CMODE_SIGN, 0, 0); } } @@ -104,14 +115,24 @@ static int unit_data_ind(struct osmocom_ms *ms, struct msgb *msg) DEBUGP(DRSL, "RSLms UNIT DATA IND chan_nr=0x%02x link_id=0x%02x\n", rllh->chan_nr, rllh->link_id); - rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh)); + if (rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg) - sizeof(*rllh)) < 0) { + LOGP(DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return -EINVAL; + } + if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { DEBUGP(DRSL, "UNIT_DATA_IND without L3 INFO ?!?\n"); return -EIO; } msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO); - rsl_dec_chan_nr(rllh->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(rllh->chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRSL, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, rllh->chan_nr); + return -EINVAL; + } + switch (ch_type) { case RSL_CHAN_BCCH: return bcch(ms, msg); @@ -177,26 +198,37 @@ static int signal_cb(unsigned int subsys, unsigned int signal, return 0; } -int l23_app_init(struct osmocom_ms *ms) +static int _cbch_sniff_start(void) { - /* don't do layer3_init() as we don't want an actualy L3 */ + int rc; - g_ms = ms; - lapdm_channel_set_l3(&ms->lapdm_channel, &rcv_rsl, ms); + rc = layer2_open(g_ms, g_ms->settings.layer2_socket_path); + if (rc < 0) { + fprintf(stderr, "Failed during layer2_open()\n"); + return rc; + } - l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL); + l1ctl_tx_reset_req(g_ms, L1CTL_RES_T_FULL); /* FIXME: L1CTL_RES_T_FULL doesn't reset dedicated mode * (if previously set), so we release it here. */ - l1ctl_tx_dm_rel_req(ms); + l1ctl_tx_dm_rel_req(g_ms); + return 0; +} + +int l23_app_init(void) +{ + /* don't do layer3_init() as we don't want an actual L3 */ + l23_app_start = _cbch_sniff_start; + + g_ms = osmocom_ms_alloc(l23_ctx, "1"); + OSMO_ASSERT(g_ms); + + lapdm_channel_set_l3(&g_ms->lapdm_channel, &rcv_rsl, g_ms); return osmo_signal_register_handler(SS_L1CTL, &signal_cb, NULL); } -static struct l23_app_info info = { +const struct l23_app_info l23_app_info = { .copyright = "Copyright (C) 2010 Harald Welte <laforge@gnumonks.org>\n", .contribution = "Contributions by Holger Hans Peter Freyther\n", + .opt_supported = L23_OPT_ARFCN | L23_OPT_TAP | L23_OPT_DBG, }; - -struct l23_app_info *l23_app_info() -{ - return &info; -} diff --git a/src/host/layer23/src/misc/app_ccch_scan.c b/src/host/layer23/src/misc/app_ccch_scan.c index 88a2bef3..d2b6c242 100644 --- a/src/host/layer23/src/misc/app_ccch_scan.c +++ b/src/host/layer23/src/misc/app_ccch_scan.c @@ -14,10 +14,6 @@ * 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 <stdint.h> @@ -36,12 +32,16 @@ #include <osmocom/bb/misc/rslms.h> #include <osmocom/bb/misc/layer3.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/l1ctl.h> #include <osmocom/bb/common/l23_app.h> +#include <osmocom/bb/common/l1l2_interface.h> +#include <osmocom/bb/common/ms.h> #include <l1ctl_proto.h> static struct { + struct osmocom_ms *ms; int ccch_mode; } app_state; @@ -166,11 +166,16 @@ static int gsm48_rx_imm_ass(struct msgb *msg, struct osmocom_ms *ms) struct gsm48_imm_ass *ia = msgb_l3(msg); uint8_t ch_type, ch_subch, ch_ts; - /* Discard packet TBF assignement */ + /* Discard packet TBF assignment */ if (ia->page_mode & 0xf0) return 0; - rsl_dec_chan_nr(ia->chan_desc.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(ia->chan_desc.chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, ia->chan_desc.chan_nr); + return -EINVAL; + } if (!ia->chan_desc.h0.h) { /* Non-hopping */ @@ -231,24 +236,6 @@ static char *chan_need(int need) } } -static char *mi_type_to_string(int type) -{ - switch (type) { - case GSM_MI_TYPE_NONE: - return "none"; - case GSM_MI_TYPE_IMSI: - return "imsi"; - case GSM_MI_TYPE_IMEI: - return "imei"; - case GSM_MI_TYPE_IMEISV: - return "imeisv"; - case GSM_MI_TYPE_TMSI: - return "tmsi"; - default: - return "invalid"; - } -} - /** * This can contain two MIs. The size checking is a bit of a mess. */ @@ -256,6 +243,7 @@ static int gsm48_rx_paging_p1(struct msgb *msg, struct osmocom_ms *ms) { struct gsm48_paging1 *pag; int len1, len2, mi_type, tag; + struct osmo_mobile_identity mi; char mi_string[GSM48_MI_SIZE]; /* is there enough room for the header + LV? */ @@ -274,11 +262,11 @@ static int gsm48_rx_paging_p1(struct msgb *msg, struct osmocom_ms *ms) } if (mi_type != GSM_MI_TYPE_NONE) { - gsm48_mi_to_string(mi_string, sizeof(mi_string), &pag->data[1], len1); - LOGP(DRR, LOGL_NOTICE, "Paging1: %s chan %s to %s M(%s) \n", + osmo_mobile_identity_decode(&mi, &pag->data[1], len1, false); + osmo_mobile_identity_to_str_buf(mi_string, sizeof(mi_string), &mi); + LOGP(DRR, LOGL_NOTICE, "Paging1: %s chan %s to M(%s)\n", pag_print_mode(pag->pag_mode), chan_need(pag->cneed1), - mi_type_to_string(mi_type), mi_string); } @@ -295,11 +283,11 @@ static int gsm48_rx_paging_p1(struct msgb *msg, struct osmocom_ms *ms) return -1; } - gsm48_mi_to_string(mi_string, sizeof(mi_string), &pag->data[2 + len1 + 2], len2); - LOGP(DRR, LOGL_NOTICE, "Paging2: %s chan %s to %s M(%s) \n", + osmo_mobile_identity_decode(&mi, &pag->data[2 + len1 + 2], len2, false); + osmo_mobile_identity_to_str_buf(mi_string, sizeof(mi_string), &mi); + LOGP(DRR, LOGL_NOTICE, "Paging2: %s chan %s to M(%s)\n", pag_print_mode(pag->pag_mode), chan_need(pag->cneed2), - mi_type_to_string(mi_type), mi_string); } return 0; @@ -308,8 +296,9 @@ static int gsm48_rx_paging_p1(struct msgb *msg, struct osmocom_ms *ms) static int gsm48_rx_paging_p2(struct msgb *msg, struct osmocom_ms *ms) { struct gsm48_paging2 *pag; - int tag, len, mi_type; + struct osmo_mobile_identity mi; char mi_string[GSM48_MI_SIZE]; + int tag, len; if (msgb_l3len(msg) < sizeof(*pag)) { LOGP(DRR, LOGL_ERROR, "Paging2 message is too small.\n"); @@ -317,12 +306,14 @@ static int gsm48_rx_paging_p2(struct msgb *msg, struct osmocom_ms *ms) } pag = msgb_l3(msg); - LOGP(DRR, LOGL_NOTICE, "Paging1: %s chan %s to TMSI M(0x%x) \n", - pag_print_mode(pag->pag_mode), - chan_need(pag->cneed1), pag->tmsi1); - LOGP(DRR, LOGL_NOTICE, "Paging2: %s chan %s to TMSI M(0x%x) \n", - pag_print_mode(pag->pag_mode), - chan_need(pag->cneed2), pag->tmsi2); + LOGP(DRR, LOGL_NOTICE, "Paging1: %s chan %s to M(TMSI-0x%08x)\n", + pag_print_mode(pag->pag_mode), + chan_need(pag->cneed1), + osmo_load32be(&pag->tmsi1)); + LOGP(DRR, LOGL_NOTICE, "Paging2: %s chan %s to M(TMSI-0x%08x)\n", + pag_print_mode(pag->pag_mode), + chan_need(pag->cneed2), + osmo_load32be(&pag->tmsi2)); /* no optional element */ if (msgb_l3len(msg) < sizeof(*pag) + 3) @@ -330,7 +321,6 @@ static int gsm48_rx_paging_p2(struct msgb *msg, struct osmocom_ms *ms) tag = pag->data[0]; len = pag->data[1]; - mi_type = pag->data[2] & GSM_MI_TYPE_MASK; if (tag != GSM48_IE_MOBILE_ID) return 0; @@ -340,11 +330,10 @@ static int gsm48_rx_paging_p2(struct msgb *msg, struct osmocom_ms *ms) return -1; } - gsm48_mi_to_string(mi_string, sizeof(mi_string), &pag->data[2], len); - LOGP(DRR, LOGL_NOTICE, "Paging3: %s chan %s to %s M(%s) \n", + osmo_mobile_identity_decode(&mi, &pag->data[2], len, false); + osmo_mobile_identity_to_str_buf(mi_string, sizeof(mi_string), &mi); + LOGP(DRR, LOGL_NOTICE, "Paging3: %s chan n/a to M(%s)\n", pag_print_mode(pag->pag_mode), - "n/a ", - mi_type_to_string(mi_type), mi_string); return 0; @@ -360,18 +349,20 @@ static int gsm48_rx_paging_p3(struct msgb *msg, struct osmocom_ms *ms) } pag = msgb_l3(msg); - LOGP(DRR, LOGL_NOTICE, "Paging1: %s chan %s to TMSI M(0x%x) \n", - pag_print_mode(pag->pag_mode), - chan_need(pag->cneed1), pag->tmsi1); - LOGP(DRR, LOGL_NOTICE, "Paging2: %s chan %s to TMSI M(0x%x) \n", - pag_print_mode(pag->pag_mode), - chan_need(pag->cneed2), pag->tmsi2); - LOGP(DRR, LOGL_NOTICE, "Paging3: %s chan %s to TMSI M(0x%x) \n", - pag_print_mode(pag->pag_mode), - "n/a ", pag->tmsi3); - LOGP(DRR, LOGL_NOTICE, "Paging4: %s chan %s to TMSI M(0x%x) \n", - pag_print_mode(pag->pag_mode), - "n/a ", pag->tmsi4); + LOGP(DRR, LOGL_NOTICE, "Paging1: %s chan %s to M(TMSI-0x%08x)\n", + pag_print_mode(pag->pag_mode), + chan_need(pag->cneed1), + osmo_load32be(&pag->tmsi1)); + LOGP(DRR, LOGL_NOTICE, "Paging2: %s chan %s to M(TMSI-0x%08x)\n", + pag_print_mode(pag->pag_mode), + chan_need(pag->cneed2), + osmo_load32be(&pag->tmsi2)); + LOGP(DRR, LOGL_NOTICE, "Paging3: %s chan n/a to M(TMSI-0x%08x)\n", + pag_print_mode(pag->pag_mode), + osmo_load32be(&pag->tmsi3)); + LOGP(DRR, LOGL_NOTICE, "Paging4: %s chan n/a to M(TMSI-0x%08x)\n", + pag_print_mode(pag->pag_mode), + osmo_load32be(&pag->tmsi4)); return 0; } @@ -409,6 +400,13 @@ int gsm48_rx_ccch(struct msgb *msg, struct osmocom_ms *ms) struct gsm48_system_information_type_header *sih = msgb_l3(msg); int rc = 0; + /* Skip frames with wrong length */ + if (msgb_l3len(msg) != GSM_MACBLOCK_LEN) { + LOGP(DRR, LOGL_ERROR, "Rx CCCH message with odd length=%u: %s\n", + msgb_l3len(msg), msgb_hexdump_l3(msg)); + return -EINVAL; + } + /* Skip dummy (fill) frames */ if (is_fill_frame(msg)) return 0; @@ -482,20 +480,33 @@ static int signal_cb(unsigned int subsys, unsigned int signal, return 0; } +static int _ccch_scan_start(void) +{ + int rc; + + rc = layer2_open(app_state.ms, app_state.ms->settings.layer2_socket_path); + if (rc < 0) { + fprintf(stderr, "Failed during layer2_open()\n"); + return rc; + } -int l23_app_init(struct osmocom_ms *ms) + l1ctl_tx_reset_req(app_state.ms, L1CTL_RES_T_FULL); + return 0; +} + +int l23_app_init(void) { + l23_app_start = _ccch_scan_start; + + app_state.ms = osmocom_ms_alloc(l23_ctx, "1"); + OSMO_ASSERT(app_state.ms); + osmo_signal_register_handler(SS_L1CTL, &signal_cb, NULL); - l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL); - return layer3_init(ms); + return layer3_init(app_state.ms); } -static struct l23_app_info info = { +const struct l23_app_info l23_app_info = { .copyright = "Copyright (C) 2010 Harald Welte <laforge@gnumonks.org>\n", .contribution = "Contributions by Holger Hans Peter Freyther\n", + .opt_supported = L23_OPT_ARFCN | L23_OPT_TAP | L23_OPT_DBG, }; - -struct l23_app_info *l23_app_info() -{ - return &info; -} diff --git a/src/host/layer23/src/misc/app_cell_log.c b/src/host/layer23/src/misc/app_cell_log.c index 5b7f7b40..9c498006 100644 --- a/src/host/layer23/src/misc/app_cell_log.c +++ b/src/host/layer23/src/misc/app_cell_log.c @@ -14,10 +14,6 @@ * 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 <signal.h> @@ -30,28 +26,44 @@ #include <osmocom/bb/common/l23_app.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/gps.h> +#include <osmocom/bb/common/l1l2_interface.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/misc/cell_log.h> +#include <osmocom/core/application.h> #include <osmocom/core/talloc.h> #include <osmocom/core/utils.h> #include <l1ctl_proto.h> -extern struct log_target *stderr_target; -extern void *l23_ctx; - extern uint16_t basic_band_range[][2]; extern uint16_t (*band_range)[][2]; char *logname = "/dev/null"; int RACH_MAX = 2; +static struct osmocom_ms *g_ms; + -int _scan_work(struct osmocom_ms *ms) +int _scan_start(void) { + int rc; + + rc = layer2_open(g_ms, g_ms->settings.layer2_socket_path); + if (rc < 0) { + fprintf(stderr, "Failed during layer2_open()\n"); + return rc; + } + + l1ctl_tx_reset_req(g_ms, L1CTL_RES_T_FULL); return 0; } -int _scan_exit(struct osmocom_ms *ms) +int _scan_work(void) +{ + return 0; +} + +int _scan_exit(void) { /* in case there is a lockup during exit */ signal(SIGINT, SIG_DFL); @@ -64,33 +76,31 @@ int _scan_exit(struct osmocom_ms *ms) return 0; } -int l23_app_init(struct osmocom_ms *ms) +int l23_app_init(void) { int rc; srand(time(NULL)); -// log_parse_category_mask(stderr_target, "DL1C:DRSL:DRR:DGPS:DSUM"); - log_parse_category_mask(stderr_target, "DSUM"); - log_set_log_level(stderr_target, LOGL_INFO); +// log_parse_category_mask(osmo_stderr_target, "DL1C:DRSL:DRR:DGPS:DSUM"); + log_parse_category_mask(osmo_stderr_target, "DSUM"); + log_set_log_level(osmo_stderr_target, LOGL_INFO); + l23_app_start = _scan_start; l23_app_work = _scan_work; l23_app_exit = _scan_exit; - rc = scan_init(ms); + g_ms = osmocom_ms_alloc(l23_ctx, "1"); + OSMO_ASSERT(g_ms); + + rc = scan_init(g_ms); if (rc) return rc; - l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL); printf("Mobile initialized, please start phone now!\n"); return 0; } -static int l23_cfg_supported() -{ - return L23_OPT_TAP | L23_OPT_DBG; -} - static int l23_getopt_options(struct option **options) { static struct option opts [] = { @@ -151,7 +161,7 @@ static void parse_band_range(char* s) (*band_range)[i][1] = 0; } -static int l23_cfg_print_help() +static int l23_cfg_print_help(void) { printf("\nApplication specific\n"); printf(" -l --logfile LOGFILE Logfile for the cell log.\n"); @@ -232,16 +242,11 @@ cmd_line_error: exit(1); } -static struct l23_app_info info = { +const struct l23_app_info l23_app_info = { .copyright = "Copyright (C) 2010 Andreas Eversberg\n", .getopt_string = "g:p:l:r:nf:b:A:", - .cfg_supported = l23_cfg_supported, + .opt_supported = L23_OPT_TAP | L23_OPT_DBG, .cfg_getopt_opt = l23_getopt_options, .cfg_handle_opt = l23_cfg_handle, .cfg_print_help = l23_cfg_print_help, }; - -struct l23_app_info *l23_app_info() -{ - return &info; -} diff --git a/src/host/layer23/src/misc/app_echo_test.c b/src/host/layer23/src/misc/app_echo_test.c index 3a0ee0f4..f5b3136b 100644 --- a/src/host/layer23/src/misc/app_echo_test.c +++ b/src/host/layer23/src/misc/app_echo_test.c @@ -15,22 +15,19 @@ * 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 <osmocom/bb/common/osmocom_data.h> #include <osmocom/bb/common/l1ctl.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/l23_app.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/misc/layer3.h> #include <osmocom/core/msgb.h> #include <osmocom/core/talloc.h> #include <osmocom/core/select.h> - +#include <osmocom/core/timer.h> static struct { struct osmo_timer_list timer; @@ -44,8 +41,11 @@ static void test_tmr_cb(void *data) osmo_timer_schedule(&test_data.timer, 1, 0); } -int l23_app_init(struct osmocom_ms *ms) +int l23_app_init(void) { + struct osmocom_ms *ms = osmocom_ms_alloc(l23_ctx, "1"); + OSMO_ASSERT(ms); + test_data.timer.cb = &test_tmr_cb; test_data.timer.data = ms; @@ -54,12 +54,7 @@ int l23_app_init(struct osmocom_ms *ms) return 0; } -static struct l23_app_info info = { +const struct l23_app_info l23_app_info = { .copyright = "Copyright (C) 2010 Harald Welte <laforge@gnumonks.org>\n", .contribution = "Contributions by Holger Hans Peter Freyther\n", }; - -struct l23_app_info *l23_app_info() -{ - return &info; -} diff --git a/src/host/layer23/src/misc/bcch_scan.c b/src/host/layer23/src/misc/bcch_scan.c index 5dc0bc3b..69df043d 100644 --- a/src/host/layer23/src/misc/bcch_scan.c +++ b/src/host/layer23/src/misc/bcch_scan.c @@ -14,10 +14,6 @@ * 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 <stdio.h> diff --git a/src/host/layer23/src/misc/cell_log.c b/src/host/layer23/src/misc/cell_log.c index 7340dcb9..ed4d74d1 100644 --- a/src/host/layer23/src/misc/cell_log.c +++ b/src/host/layer23/src/misc/cell_log.c @@ -14,10 +14,6 @@ * 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 <stdio.h> @@ -36,14 +32,19 @@ #include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/gsm/rsl.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/lapdm.h> #include <osmocom/bb/common/l1ctl.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/networks.h> #include <osmocom/bb/common/gps.h> +#include <osmocom/bb/common/sysinfo.h> +#include <osmocom/bb/mobile/gsm48_rr.h> #include <osmocom/bb/misc/cell_log.h> -#include "../../../gsmmap/geo.h" +#include <osmocom/bb/misc/geo.h> #define READ_WAIT 2, 0 #define RACH_WAIT 0, 900000 @@ -97,7 +98,7 @@ static struct log_si { uint16_t flags; uint8_t bsic; int8_t rxlev_dbm; - uint16_t mcc, mnc, lac, cellid; + struct osmo_cell_global_id cgi; uint8_t ta; double latitude, longitude; } log_si; @@ -186,9 +187,10 @@ static void log_sysinfo(void) if (log_si.ta != 0xff) sprintf(ta_str, " TA=%d", log_si.ta); - LOGP(DSUM, LOGL_INFO, "Cell: ARFCN=%d MCC=%s MNC=%s (%s, %s)%s\n", - arfcn, gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc), - gsm_get_mcc(s->mcc), gsm_get_mnc(s->mcc, s->mnc), ta_str); + LOGP(DSUM, LOGL_INFO, "Cell: ARFCN=%d MCC-MNC=%s (%s, %s)%s\n", + arfcn, osmo_plmn_name(&s->lai.plmn), + gsm_get_mcc(s->lai.plmn.mcc), + gsm_get_mnc(&s->lai.plmn), ta_str); LOGFILE("[sysinfo]\n"); LOGFILE("arfcn %d\n", s->arfcn); @@ -291,7 +293,7 @@ static void start_rach(void) ncch->data[2] = (s->ccch_conf == 1) << 7; ncch->data[3] = 0; ncch->data[4] = RSL_IE_ACCESS_DELAY; - ncch->data[5] = 0; /* no delay */ + ncch->data[5] = 0; /* no delay */ ncch->data[6] = RSL_IE_MS_POWER; ncch->data[7] = 0; /* full power */ @@ -438,7 +440,7 @@ static int ta_result(uint8_t ta) return 0; } -/* match request reference agains request */ +/* match request reference against request */ static int match_ra(struct osmocom_ms *ms, struct gsm48_req_ref *ref) { uint8_t ia_t1, ia_t2, ia_t3; @@ -669,11 +671,15 @@ static int unit_data_ind(struct osmocom_ms *ms, struct msgb *msg) struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); struct tlv_parsed tv; uint8_t ch_type, ch_subch, ch_ts; - + DEBUGP(DRSL, "RSLms UNIT DATA IND chan_nr=0x%02x link_id=0x%02x\n", rllh->chan_nr, rllh->link_id); - rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh)); + if (rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg) - sizeof(*rllh)) < 0) { + LOGP(DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return -EINVAL; + } + if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { DEBUGP(DRSL, "UNIT_DATA_IND without L3 INFO ?!?\n"); return -EIO; @@ -684,7 +690,13 @@ static int unit_data_ind(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } - rsl_dec_chan_nr(rllh->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(rllh->chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRSL, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, rllh->chan_nr); + return -EINVAL; + } + switch (ch_type) { case RSL_CHAN_PCH_AGCH: return pch_agch(ms, msg); diff --git a/src/host/layer23/src/misc/geo.c b/src/host/layer23/src/misc/geo.c index 65633d2c..e02a2391 100644 --- a/src/host/layer23/src/misc/geo.c +++ b/src/host/layer23/src/misc/geo.c @@ -1,5 +1,6 @@ #include <math.h> -#include "geo.h" + +#include <osmocom/bb/misc/geo.h> void geo2space(double *x, double *y, double *z, double lon, double lat) { diff --git a/src/host/layer23/src/misc/geo.h b/src/host/layer23/src/misc/geo.h deleted file mode 100644 index 25e26cba..00000000 --- a/src/host/layer23/src/misc/geo.h +++ /dev/null @@ -1,12 +0,0 @@ -/* WGS 84 */ -#define EQUATOR_RADIUS 6378137.0 -#define POLE_RADIUS 6356752.314 - -#define PI 3.1415926536 - -void geo2space(double *x, double *y, double *z, double lat, double lon); -void space2geo(double *lat, double *lon, double x, double y, double z); -double distinspace(double x1, double y1, double z1, double x2, double y2, - double z2); -double distonplane(double x1, double y1, double x2, double y2); - diff --git a/src/host/gsmmap/gsmmap.c b/src/host/layer23/src/misc/gsmmap.c index 83f0d01c..378b358b 100644 --- a/src/host/gsmmap/gsmmap.c +++ b/src/host/layer23/src/misc/gsmmap.c @@ -14,10 +14,6 @@ * 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. - * */ #warning todo bsic #include <stdio.h> @@ -34,9 +30,9 @@ #include <osmocom/bb/common/networks.h> #include <osmocom/bb/common/logging.h> -#include "log.h" -#include "geo.h" -#include "locate.h" +#include <osmocom/bb/misc/log.h> +#include <osmocom/bb/misc/geo.h> +#include <osmocom/bb/misc/locate.h> /* * structure of power and cell infos @@ -56,7 +52,7 @@ static void nomem(void) exit(-ENOMEM); } -static void add_power() +static void add_power(void) { struct node_power *node_power; @@ -85,7 +81,7 @@ static void print_si(void *priv, const char *fmt, ...) fprintf(outfp, "%s", buffer); } -static void add_sysinfo() +static void add_sysinfo(void) { struct gsm48_sysinfo s; struct node_mcc *mcc; @@ -125,13 +121,13 @@ static void add_sysinfo() 23); printf("--------------------------------------------------------------------------\n"); gsm48_sysinfo_dump(&s, sysinfo.arfcn, print_si, stdout, NULL); - mcc = get_node_mcc(s.mcc); + mcc = get_node_mcc(s.lai.plmn.mcc); if (!mcc) nomem(); - mnc = get_node_mnc(mcc, s.mnc); + mnc = get_node_mnc(mcc, s.lai.plmn.mnc, s.lai.plmn.mnc_3_digits); if (!mnc) nomem(); - lac = get_node_lac(mnc, s.lac); + lac = get_node_lac(mnc, s.lai.lac); if (!lac) nomem(); cell = get_node_cell(lac, s.cell_id); @@ -300,8 +296,7 @@ void kml_footer(FILE *outfp) } -void kml_meas(FILE *outfp, struct node_meas *meas, int n, uint16_t mcc, - uint16_t mnc, uint16_t lac, uint16_t cellid) +static void kml_meas(FILE *outfp, struct node_meas *meas, int n, const struct osmo_cell_global_id *cgi) { struct tm *tm = localtime(&meas->gmt); @@ -309,8 +304,11 @@ void kml_meas(FILE *outfp, struct node_meas *meas, int n, uint16_t mcc, fprintf(outfp, "\t\t\t\t\t\t<name>%d: %d</name>\n", n, meas->rxlev); fprintf(outfp, "\t\t\t\t\t\t<description>\n"); fprintf(outfp, "MCC=%s MNC=%s\nLAC=%04x CELL-ID=%04x\n(%s %s)\n", - gsm_print_mcc(mcc), gsm_print_mnc(mnc), lac, cellid, - gsm_get_mcc(mcc), gsm_get_mnc(mcc, mnc)); + osmo_mcc_name(cgi->lai.plmn.mcc), + osmo_mnc_name(cgi->lai.plmn.mnc, cgi->lai.plmn.mnc_3_digits), + cgi->lai.lac, cgi->cell_identity, + gsm_get_mcc(cgi->lai.plmn.mcc), + gsm_get_mnc(&cgi->lai.plmn)); fprintf(outfp, "\n%s", asctime(tm)); fprintf(outfp, "RX-LEV %d dBm\n", meas->rxlev); if (meas->ta_valid) @@ -427,11 +425,11 @@ void kml_cell(FILE *outfp, struct node_cell *cell) return; fprintf(outfp, "\t\t\t\t\t<Placemark>\n"); - fprintf(outfp, "\t\t\t\t\t\t<name>MCC=%s MNC=%s\nLAC=%04x " - "CELL-ID=%04x\n(%s %s)</name>\n", gsm_print_mcc(cell->s.mcc), - gsm_print_mnc(cell->s.mnc), cell->s.lac, cell->s.cell_id, - gsm_get_mcc(cell->s.mcc), - gsm_get_mnc(cell->s.mcc, cell->s.mnc)); + fprintf(outfp, "\t\t\t\t\t\t<name>LAI=%s " + "CELL-ID=%04x\n(%s %s)</name>\n", + osmo_lai_name(&cell->s.lai), cell->s.cell_id, + gsm_get_mcc(cell->s.lai.plmn.mcc), + gsm_get_mnc(&cell->s.lai.plmn)); fprintf(outfp, "\t\t\t\t\t\t<description>\n"); gsm48_sysinfo_dump(&cell->s, cell->sysinfo.arfcn, print_si, outfp, NULL); @@ -580,15 +578,20 @@ usage: /* folder open */ fprintf(outfp, "\t<Folder>\n"); fprintf(outfp, "\t\t<name>MCC %s (%s)</name>\n", - gsm_print_mcc(mcc->mcc), gsm_get_mcc(mcc->mcc)); + osmo_mcc_name(mcc->mcc), gsm_get_mcc(mcc->mcc)); fprintf(outfp, "\t\t<open>0</open>\n"); mnc = mcc->mnc; while (mnc) { + struct osmo_plmn_id plmn = { + .mcc = mcc->mcc, + .mnc = mnc->mnc, + .mnc_3_digits = mnc->mnc_3_digits, + }; printf(" MNC: %02x\n", mnc->mnc); /* folder open */ fprintf(outfp, "\t\t<Folder>\n"); fprintf(outfp, "\t\t\t<name>MNC %s (%s)</name>\n", - gsm_print_mnc(mnc->mnc), gsm_get_mnc(mcc->mcc, mnc->mnc)); + osmo_mnc_name(mnc->mnc, mnc->mnc_3_digits), gsm_get_mnc(&plmn)); fprintf(outfp, "\t\t\t<open>0</open>\n"); lac = mnc->lac; while (lac) { @@ -608,9 +611,20 @@ usage: while (meas) { if (meas->ta_valid) printf(" TA: %d\n", meas->ta); - if (meas->gps_valid) - kml_meas(outfp, meas, ++n, mcc->mcc, mnc->mnc, - lac->lac, cell->cellid); + if (meas->gps_valid) { + struct osmo_cell_global_id cgi = { + .lai = { + .plmn = { + .mcc = mcc->mcc, + .mnc = mnc->mnc, + .mnc_3_digits = mnc->mnc_3_digits, + }, + .lac = lac->lac, + }, + .cell_identity = cell->cellid, + }; + kml_meas(outfp, meas, ++n, &cgi); + } meas = meas->next; } kml_cell(outfp, cell); diff --git a/src/host/gsmmap/locate.c b/src/host/layer23/src/misc/locate.c index ed0ac931..eb37285e 100644 --- a/src/host/gsmmap/locate.c +++ b/src/host/layer23/src/misc/locate.c @@ -5,8 +5,8 @@ #include <errno.h> #include <math.h> -#include "geo.h" -#include "locate.h" +#include <osmocom/bb/misc/geo.h> +#include <osmocom/bb/misc/locate.h> #define CIRCLE_PROBE 30.0 #define FINETUNE_RADIUS 5.0 diff --git a/src/host/gsmmap/log.c b/src/host/layer23/src/misc/log.c index c2db801e..577e1268 100644 --- a/src/host/gsmmap/log.c +++ b/src/host/layer23/src/misc/log.c @@ -14,18 +14,13 @@ * 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 <stdio.h> #include <stdlib.h> #include <osmocom/bb/common/osmocom_data.h> - -#include "log.h" +#include <osmocom/bb/misc/log.h> extern struct power power; extern struct sysinfo sysinfo; @@ -60,18 +55,22 @@ struct node_mcc *get_node_mcc(uint16_t mcc) return node_mcc; } -struct node_mnc *get_node_mnc(struct node_mcc *mcc, uint16_t mnc) +struct node_mnc *get_node_mnc(struct node_mcc *mcc, uint16_t mnc, bool mnc_3_digits) { struct node_mnc *node_mnc; struct node_mnc **node_mnc_p = &mcc->mnc; while (*node_mnc_p) { /* found in list */ - if ((*node_mnc_p)->mnc == mnc) + if ((*node_mnc_p)->mnc == mnc && + (*node_mnc_p)->mnc_3_digits == mnc_3_digits) return *node_mnc_p; /* insert into list */ - if ((*node_mnc_p)->mnc > mnc) + if (((*node_mnc_p)->mnc > mnc) || + ((*node_mnc_p)->mnc == mnc && + (*node_mnc_p)->mnc_3_digits > mnc_3_digits)) { break; + } node_mnc_p = &((*node_mnc_p)->next); } @@ -80,6 +79,7 @@ struct node_mnc *get_node_mnc(struct node_mcc *mcc, uint16_t mnc) if (!node_mnc) return NULL; node_mnc->mnc = mnc; + node_mnc->mnc_3_digits = mnc_3_digits; node_mnc->next = *node_mnc_p; *node_mnc_p = node_mnc; return node_mnc; diff --git a/src/host/layer23/src/misc/rslms.c b/src/host/layer23/src/misc/rslms.c index 455e6631..c071113b 100644 --- a/src/host/layer23/src/misc/rslms.c +++ b/src/host/layer23/src/misc/rslms.c @@ -14,10 +14,6 @@ * 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 <stdint.h> @@ -34,6 +30,7 @@ #include <osmocom/bb/misc/rslms.h> #include <osmocom/bb/misc/layer3.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/l1ctl.h> /* Send a 'simple' RLL request to L2 */ @@ -61,11 +58,15 @@ static int rslms_rx_udata_ind(struct msgb *msg, struct osmocom_ms *ms) struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); struct tlv_parsed tv; int rc = 0; - + DEBUGP(DRSL, "RSLms UNIT DATA IND chan_nr=0x%02x link_id=0x%02x\n", rllh->chan_nr, rllh->link_id); - rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh)); + if (rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg) - sizeof(*rllh)) < 0) { + LOGP(DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return -EINVAL; + } + if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { DEBUGP(DRSL, "UNIT_DATA_IND without L3 INFO ?!?\n"); return -EIO; diff --git a/src/host/layer23/src/mobile/Makefile.am b/src/host/layer23/src/mobile/Makefile.am index 4e80e4ea..d00e45ea 100644 --- a/src/host/layer23/src/mobile/Makefile.am +++ b/src/host/layer23/src/mobile/Makefile.am @@ -1,16 +1,62 @@ -AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBGPS_CFLAGS) $(LIBLUA_CFLAGS) -LDADD = ../common/liblayer23.a $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBGPS_LIBS) $(LIBLUA_LIBS) +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOISDN_CFLAGS) \ + $(LIBOSMOGPRSRLCMAC_CFLAGS) \ + $(LIBOSMOGPRSLLC_CFLAGS) \ + $(LIBOSMOGPRSSNDCP_CFLAGS) \ + $(LIBOSMOCODEC_CFLAGS) \ + $(LIBOSMOGAPK_CFLAGS) \ + $(LIBGPS_CFLAGS) \ + $(LIBLUA_CFLAGS) \ + $(NULL) noinst_LIBRARIES = libmobile.a -libmobile_a_SOURCES = gsm322.c gsm480_ss.c gsm411_sms.c gsm48_cc.c gsm48_mm.c \ - gsm48_rr.c mnccms.c settings.c subscriber.c support.c \ - transaction.c vty_interface.c voice.c mncc_sock.c primitives.c +libmobile_a_SOURCES = \ + gsm322.c \ + gsm480_ss.c \ + gsm411_sms.c \ + gsm48_cc.c \ + gsm44068_gcc_bcc.c \ + gsm48_mm.c \ + gsm48_rr.c \ + gsm414.c \ + mnccms.c \ + mncc_sock.c \ + primitives.c \ + tch.c \ + tch_data.c \ + tch_data_sock.c \ + tch_voice.c \ + transaction.c \ + vty_interface.c \ + $(NULL) bin_PROGRAMS = mobile mobile_SOURCES = main.c app_mobile.c -mobile_LDADD = libmobile.a $(LDADD) +mobile_LDADD = \ + libmobile.a \ + $(top_builddir)/src/common/liblayer23.a \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOISDN_LIBS) \ + $(LIBOSMOGPRSRLCMAC_LIBS) \ + $(LIBOSMOGPRSLLC_LIBS) \ + $(LIBOSMOGPRSSNDCP_LIBS) \ + $(LIBOSMOCODEC_LIBS) \ + $(LIBOSMOGAPK_LIBS) \ + $(LIBGPS_LIBS) \ + $(LIBLUA_LIBS) \ + $(NULL) # lua support if BUILD_LUA @@ -19,3 +65,9 @@ libmobile_a_SOURCES += script_lua.c else libmobile_a_SOURCES += script_nolua.c endif + +# GAPK I/O support +if BUILD_GAPK +AM_CPPFLAGS += -DWITH_GAPK_IO=1 +libmobile_a_SOURCES += gapk_io.c +endif diff --git a/src/host/layer23/src/mobile/app_mobile.c b/src/host/layer23/src/mobile/app_mobile.c index f0f118b6..477c4fa5 100644 --- a/src/host/layer23/src/mobile/app_mobile.c +++ b/src/host/layer23/src/mobile/app_mobile.c @@ -16,32 +16,35 @@ * 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 <errno.h> #include <signal.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/l1l2_interface.h> #include <osmocom/bb/common/l1ctl.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/gps.h> +#include <osmocom/bb/common/sap_interface.h> +#include <osmocom/bb/common/sim.h> +#include <osmocom/bb/common/l23_app.h> + #include <osmocom/bb/mobile/gsm48_rr.h> #include <osmocom/bb/mobile/gsm480_ss.h> +#include <osmocom/bb/mobile/gsm48_mm.h> +#include <osmocom/bb/mobile/gsm48_cc.h> +#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h> #include <osmocom/bb/mobile/gsm411_sms.h> +#include <osmocom/bb/mobile/gsm322.h> #include <osmocom/bb/mobile/vty.h> #include <osmocom/bb/mobile/app_mobile.h> #include <osmocom/bb/mobile/mncc.h> -#include <osmocom/bb/mobile/voice.h> +#include <osmocom/bb/mobile/tch.h> #include <osmocom/bb/mobile/primitives.h> -#include <osmocom/bb/common/sap_interface.h> -#include <osmocom/vty/ports.h> -#include <osmocom/vty/logging.h> +#include <osmocom/vty/vty.h> #include <osmocom/vty/telnet_interface.h> #include <osmocom/core/msgb.h> @@ -51,14 +54,16 @@ #include <l1ctl_proto.h> +#include "config.h" + extern void *l23_ctx; extern struct llist_head ms_list; -extern int vty_reading; -int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg); +int mncc_recv_internal(struct osmocom_ms *ms, int msg_type, void *arg); +int mncc_recv_external(struct osmocom_ms *ms, int msg_type, void *arg); int mncc_recv_dummy(struct osmocom_ms *ms, int msg_type, void *arg); -int (*mncc_recv_app)(struct osmocom_ms *ms, int, void *); -static int quit; +static int _quit; +extern int quit; /* l23 main */ /* handle ms instance */ int mobile_work(struct osmocom_ms *ms) @@ -82,12 +87,54 @@ int mobile_work(struct osmocom_ms *ms) return work; } -/* run ms instance, if layer1 is available */ -int mobile_signal_cb(unsigned int subsys, unsigned int signal, +/* SIM becomes ATTACHED/DETACHED, or answers a request */ +static int mobile_l23_subscr_signal_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) { + struct msgb *nmsg; + struct gsm48_mm_event *nmme; + struct osmocom_ms *ms; + struct osmobb_l23_subscr_sim_auth_resp_sig_data *sim_auth_resp; + + OSMO_ASSERT(subsys == SS_L23_SUBSCR); + + switch (signal) { + case S_L23_SUBSCR_SIM_ATTACHED: + ms = signal_data; + nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_REG_REQ); + if (!nmsg) + return -ENOMEM; + gsm48_mmr_downmsg(ms, nmsg); + break; + case S_L23_SUBSCR_SIM_DETACHED: + ms = signal_data; + nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_NREG_REQ); + if (!nmsg) + return 0; + gsm48_mmr_downmsg(ms, nmsg); + break; + case S_L23_SUBSCR_SIM_AUTH_RESP: + sim_auth_resp = signal_data; + ms = sim_auth_resp->ms; + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_AUTH_RESPONSE); + if (!nmsg) + return 0; + nmme = (struct gsm48_mm_event *) nmsg->data; + memcpy(nmme->sres, sim_auth_resp->sres, 4); + gsm48_mmevent_msg(ms, nmsg); + break; + default: + OSMO_ASSERT(0); + } + + return 0; +} + +/* run ms instance, if layer1 is available */ +static int mobile_signal_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ struct osmocom_ms *ms; - struct gsm_settings *set; struct msgb *nmsg; if (subsys != SS_L1CTL) @@ -96,11 +143,10 @@ int mobile_signal_cb(unsigned int subsys, unsigned int signal, switch (signal) { case S_L1CTL_RESET: ms = signal_data; - set = &ms->settings; /* waiting for reset after shutdown */ if (ms->shutdown == MS_SHUTDOWN_WAIT_RESET) { - LOGP(DMOB, LOGL_NOTICE, "MS '%s' has been resetted\n", ms->name); + LOGP(DMOB, LOGL_NOTICE, "MS '%s' has been reset\n", ms->name); ms->shutdown = MS_SHUTDOWN_COMPL; break; } @@ -108,21 +154,10 @@ int mobile_signal_cb(unsigned int subsys, unsigned int signal, if (ms->started) break; - /* insert test card, if enabled */ - switch (set->sim_type) { - case GSM_SIM_TYPE_L1PHY: - /* trigger sim card reader process */ - gsm_subscr_simcard(ms); - break; - case GSM_SIM_TYPE_TEST: - gsm_subscr_testcard(ms, set->test_rplmn_mcc, - set->test_rplmn_mnc, set->test_lac, - set->test_tmsi, set->test_imsi_attached); - break; - case GSM_SIM_TYPE_SAP: - gsm_subscr_sapcard(ms); - break; - default: + if (ms->settings.sim_type != GSM_SIM_TYPE_NONE) { + /* insert sim card */ + gsm_subscr_insert(ms); + } else { /* no SIM, trigger PLMN selection process */ nmsg = gsm322_msgb_alloc(GSM322_EVENT_SWITCH_ON); if (!nmsg) @@ -167,6 +202,7 @@ int mobile_exit(struct osmocom_ms *ms, int force) gsm48_cc_exit(ms); gsm480_ss_exit(ms); gsm411_sms_exit(ms); + gsm44068_gcc_exit(ms); gsm_sim_exit(ms); lapdm_channel_exit(&ms->lapdm_channel); @@ -176,8 +212,8 @@ int mobile_exit(struct osmocom_ms *ms, int force) } else { mobile_set_shutdown(ms, MS_SHUTDOWN_COMPL); /* being down */ } - vty_notify(ms, NULL); - vty_notify(ms, "Power off!\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Power off!\n"); LOGP(DMOB, LOGL_NOTICE, "Power off! (MS %s)\n", ms->name); return 0; @@ -190,26 +226,27 @@ static int mobile_init(struct osmocom_ms *ms) gsm_settings_arfcn(ms); - lapdm_channel_init(&ms->lapdm_channel, LAPDM_MODE_MS); - ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI3].dl.t200_sec = - T200_DCCH_SHARED; - ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI3].dl.t200_usec = 0; - ms->lapdm_channel.lapdm_acch.datalink[DL_SAPI3].dl.t200_sec = - T200_ACCH; - ms->lapdm_channel.lapdm_acch.datalink[DL_SAPI3].dl.t200_usec = 0; + const int t200_ms_dcch[_NR_DL_SAPI] = { + [DL_SAPI0] = 1000, + [DL_SAPI3] = 1000 * T200_DCCH_SHARED + }; + const int t200_ms_acch[_NR_DL_SAPI] = { + [DL_SAPI0] = 2000, + [DL_SAPI3] = 1000 * T200_ACCH + }; + + lapdm_channel_init3(&ms->lapdm_channel, LAPDM_MODE_MS, + t200_ms_dcch, t200_ms_acch, + GSM_LCHAN_SDCCH, NULL); + lapdm_channel_set_flags(&ms->lapdm_channel, LAPDM_ENT_F_DROP_2ND_REJ); lapdm_channel_set_l1(&ms->lapdm_channel, l1ctl_ph_prim_cb, ms); - /* init SAP client before SIM card starts up */ - sap_init(ms); - - /* SAP response call-back */ - ms->sap_entity.sap_rsp_cb = &gsm_subscr_sap_rsp_cb; - gsm_sim_init(ms); gsm48_cc_init(ms); gsm480_ss_init(ms); gsm411_sms_init(ms); - gsm_voice_init(ms); + gsm44068_gcc_init(ms); + tch_init(ms); gsm_subscr_init(ms); gsm48_rr_init(ms); gsm48_mm_init(ms); @@ -236,6 +273,24 @@ static int mobile_init(struct osmocom_ms *ms) "default IMEI.\n***\n"); } + switch (ms->settings.mncc_handler) { + case MNCC_HANDLER_INTERNAL: + LOGP(DMOB, LOGL_INFO, "Using the built-in MNCC-handler for MS '%s'\n", ms->name); + ms->mncc_entity.mncc_recv = &mncc_recv_internal; + break; + case MNCC_HANDLER_EXTERNAL: + LOGP(DMOB, LOGL_INFO, "Using external MNCC-handler (socket '%s') for MS '%s'\n", + ms->settings.mncc_socket_path, ms->name); + ms->mncc_entity.mncc_recv = &mncc_recv_external; + ms->mncc_entity.sock_state = mncc_sock_init(ms, ms->settings.mncc_socket_path); + break; + case MNCC_HANDLER_DUMMY: + default: + LOGP(DMOB, LOGL_INFO, "Using dummy MNCC-handler (no call support) " + "for MS '%s'\n", ms->name); + ms->mncc_entity.mncc_recv = &mncc_recv_dummy; + } + l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL); LOGP(DMOB, LOGL_NOTICE, "Mobile '%s' initialized, please start phone now!\n", ms->name); return 0; @@ -255,7 +310,7 @@ int mobile_start(struct osmocom_ms *ms, char **other_name) if (!strcmp(ms->settings.layer2_socket_path, tmp->settings.layer2_socket_path)) { LOGP(DMOB, LOGL_ERROR, "Cannot start MS '%s', because MS '%s' " - "use the same layer2-socket.\nPlease shutdown " + "is using the same layer2-socket.\nPlease shutdown " "MS '%s' first.\n", ms->name, tmp->name, tmp->name); *other_name = tmp->name; return -1; @@ -263,11 +318,19 @@ int mobile_start(struct osmocom_ms *ms, char **other_name) if (!strcmp(ms->settings.sap_socket_path, tmp->settings.sap_socket_path)) { LOGP(DMOB, LOGL_ERROR, "Cannot start MS '%s', because MS '%s' " - "use the same sap-socket.\nPlease shutdown " + "is using the same sap-socket.\nPlease shutdown " "MS '%s' first.\n", ms->name, tmp->name, tmp->name); *other_name = tmp->name; return -2; } + if (!strcmp(ms->settings.mncc_socket_path, + tmp->settings.mncc_socket_path)) { + LOGP(DMOB, LOGL_ERROR, "Cannot start MS '%s', because MS '%s' " + "is using the same mncc-socket.\nPlease shutdown " + "MS '%s' first.\n", ms->name, tmp->name, tmp->name); + *other_name = tmp->name; + return -3; + } } rc = mobile_init(ms); @@ -290,40 +353,14 @@ int mobile_stop(struct osmocom_ms *ms, int force) struct osmocom_ms *mobile_new(char *name) { static struct osmocom_ms *ms; - char *mncc_name; - ms = talloc_zero(l23_ctx, struct osmocom_ms); + ms = osmocom_ms_alloc(l23_ctx, name); if (!ms) { LOGP(DMOB, LOGL_ERROR, "Failed to allocate MS: %s\n", name); return NULL; } - talloc_set_name(ms, "ms_%s", name); - ms->name = talloc_strdup(ms, name); - ms->l2_wq.bfd.fd = -1; - ms->sap_wq.bfd.fd = -1; - - /* Register a new MS */ - llist_add_tail(&ms->entity, &ms_list); - - gsm_support_init(ms); - gsm_settings_init(ms); - mobile_set_shutdown(ms, MS_SHUTDOWN_COMPL); - - if (mncc_recv_app) { - mncc_name = talloc_asprintf(ms, "/tmp/ms_mncc_%s", ms->name); - - ms->mncc_entity.mncc_recv = mncc_recv_app; - ms->mncc_entity.sock_state = mncc_sock_init(ms, mncc_name); - - talloc_free(mncc_name); - } else if (ms->settings.ch_cap == GSM_CAP_SDCCH) - ms->mncc_entity.mncc_recv = mncc_recv_dummy; - else - ms->mncc_entity.mncc_recv = mncc_recv_mobile; - - return ms; } @@ -334,7 +371,7 @@ int mobile_delete(struct osmocom_ms *ms, int force) ms->deleting = true; - if (mncc_recv_app) { + if (ms->settings.mncc_handler == MNCC_HANDLER_EXTERNAL) { mncc_sock_exit(ms->mncc_entity.sock_state); ms->mncc_entity.sock_state = NULL; } @@ -349,8 +386,8 @@ int mobile_delete(struct osmocom_ms *ms, int force) } /* handle global shutdown */ -int global_signal_cb(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) +static int global_signal_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) { struct osmocom_ms *ms, *ms2; @@ -361,20 +398,20 @@ int global_signal_cb(unsigned int subsys, unsigned int signal, case S_GLOBAL_SHUTDOWN: /* force to exit, if signalled */ if (signal_data && *((uint8_t *)signal_data)) - quit = 1; + _quit = 1; llist_for_each_entry_safe(ms, ms2, &ms_list, entity) - mobile_delete(ms, quit); + mobile_delete(ms, _quit); /* quit, after all MS processes are gone */ - quit = 1; + _quit = 1; break; } return 0; } /* global work handler */ -int l23_app_work(int *_quit) +static int _mobile_app_work(void) { struct osmocom_ms *ms, *ms2; int work = 0; @@ -387,14 +424,9 @@ int l23_app_work(int *_quit) layer2_close(ms); ms->l2_wq.bfd.fd = -1; } - - if (ms->sap_wq.bfd.fd > -1) { - sap_close(ms); - ms->sap_wq.bfd.fd = -1; - } - if (ms->deleting) { gsm_settings_exit(ms); + script_lua_close(ms); llist_del(&ms->entity); talloc_free(ms); work = 1; @@ -403,70 +435,27 @@ int l23_app_work(int *_quit) } /* return, if a shutdown was scheduled (quit = 1) */ - *_quit = quit; + quit = _quit; return work; } /* global exit */ -int l23_app_exit(void) +static int _mobile_app_exit(void) { + osmo_signal_unregister_handler(SS_L23_SUBSCR, &mobile_l23_subscr_signal_cb, NULL); osmo_signal_unregister_handler(SS_L1CTL, &gsm322_l1_signal, NULL); osmo_signal_unregister_handler(SS_L1CTL, &mobile_signal_cb, NULL); osmo_signal_unregister_handler(SS_GLOBAL, &global_signal_cb, NULL); osmo_gps_close(); - telnet_exit(); - return 0; } -static struct vty_app_info vty_info = { - .name = "OsmocomBB", - .version = PACKAGE_VERSION, - .go_parent_cb = ms_vty_go_parent, -}; -/* global init */ -int l23_app_init(int (*mncc_recv)(struct osmocom_ms *ms, int, void *), - const char *config_file) +static int _mobile_app_start(void) { - struct telnet_connection dummy_conn; - int rc = 0; - - mncc_recv_app = mncc_recv; - - osmo_gps_init(); - - vty_info.tall_ctx = l23_ctx; - vty_init(&vty_info); - logging_vty_add_cmds(); - ms_vty_init(); - dummy_conn.priv = NULL; - vty_reading = 1; - if (config_file != NULL) { - rc = vty_read_config_file(config_file, &dummy_conn); - if (rc < 0) { - LOGP(DMOB, LOGL_FATAL, "Failed to parse the configuration " - "file '%s'\n", config_file); - LOGP(DMOB, LOGL_FATAL, "Please make sure the file " - "'%s' exists, or use an example from " - "'doc/examples/mobile/'\n", config_file); - return rc; - } - LOGP(DMOB, LOGL_INFO, "Using configuration from '%s'\n", config_file); - } - vty_reading = 0; - rc = telnet_init_default(l23_ctx, NULL, OSMO_VTY_PORT_BB); - if (rc < 0) { - LOGP(DMOB, LOGL_FATAL, "Cannot init VTY on %s port %u: %s\n", - vty_get_bind_addr(), OSMO_VTY_PORT_BB, strerror(errno)); - return rc; - } - - osmo_signal_register_handler(SS_GLOBAL, &global_signal_cb, NULL); - osmo_signal_register_handler(SS_L1CTL, &mobile_signal_cb, NULL); - osmo_signal_register_handler(SS_L1CTL, &gsm322_l1_signal, NULL); + int rc; if (llist_empty(&ms_list)) { struct osmocom_ms *ms; @@ -481,11 +470,45 @@ int l23_app_init(int (*mncc_recv)(struct osmocom_ms *ms, int, void *), return rc; } - quit = 0; + _quit = 0; + + return 0; +} + +/* global init */ +int l23_app_init(void) +{ + l23_app_start = _mobile_app_start; + l23_app_work = _mobile_app_work; + l23_app_exit = _mobile_app_exit; + osmo_gps_init(); + + osmo_signal_register_handler(SS_GLOBAL, &global_signal_cb, NULL); + osmo_signal_register_handler(SS_L1CTL, &mobile_signal_cb, NULL); + osmo_signal_register_handler(SS_L1CTL, &gsm322_l1_signal, NULL); + osmo_signal_register_handler(SS_L23_SUBSCR, &mobile_l23_subscr_signal_cb, NULL); return 0; } +static int _mobile_vty_init(void) +{ + return ms_vty_init(); +} + +static struct vty_app_info _mobile_vty_info = { + .name = "OsmocomBB(mobile)", + .version = PACKAGE_VERSION, +}; + +const struct l23_app_info l23_app_info = { + .copyright = "Copyright (C) 2010-2015 Andreas Eversberg, Sylvain Munaut, Holger Freyther, Harald Welte\n", + .contribution = "Contributions by Alex Badea, Pablo Neira, Steve Markgraf and others\n", + .opt_supported = L23_OPT_TAP | L23_OPT_VTY | L23_OPT_DBG, + .vty_info = &_mobile_vty_info, + .vty_init = _mobile_vty_init, +}; + void mobile_set_started(struct osmocom_ms *ms, bool state) { ms->started = state; diff --git a/src/host/layer23/src/mobile/gapk_io.c b/src/host/layer23/src/mobile/gapk_io.c new file mode 100644 index 00000000..cc756b06 --- /dev/null +++ b/src/host/layer23/src/mobile/gapk_io.c @@ -0,0 +1,547 @@ +/* + * GAPK (GSM Audio Pocket Knife) based audio I/O + * + * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * Contributions by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * 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. + * + */ + +#include <string.h> +#include <errno.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +#include <osmocom/gapk/procqueue.h> +#include <osmocom/gapk/formats.h> +#include <osmocom/gapk/codecs.h> +#include <osmocom/gapk/common.h> + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/common/logging.h> + +#include <osmocom/bb/mobile/tch.h> +#include <osmocom/bb/mobile/gapk_io.h> + +/* The RAW PCM format is common for both audio source and sink */ +static const struct osmo_gapk_format_desc *rawpcm_fmt; + +static int pq_queue_tch_fb_recv(void *_state, uint8_t *out, + const uint8_t *in, unsigned int in_len) +{ + struct gapk_io_state *state = (struct gapk_io_state *)_state; + struct msgb *tch_msg; + size_t frame_len; + + /* Obtain one TCH frame from the DL buffer */ + tch_msg = msgb_dequeue_count(&state->tch_dl_fb, + &state->tch_dl_fb_len); + if (tch_msg == NULL) + return -EIO; + + /* Calculate received frame length */ + frame_len = msgb_l3len(tch_msg); + if (frame_len == 0) { + msgb_free(tch_msg); + return -EIO; + } + + /* Copy the frame bytes from message */ + memcpy(out, tch_msg->l3h, frame_len); + + /* Release memory */ + msgb_free(tch_msg); + + return frame_len; +} + +static int pq_queue_tch_fb_send(void *_state, uint8_t *out, + const uint8_t *in, unsigned int in_len) +{ + struct gapk_io_state *state = (struct gapk_io_state *)_state; + struct msgb *tch_msg; + + if (state->tch_ul_fb_len >= GAPK_ULDL_QUEUE_LIMIT) { + LOGP(DGAPK, LOGL_ERROR, "UL TCH frame buffer overflow, dropping msg\n"); + return -EOVERFLOW; + } + + /* Allocate a new message for the lower layers */ + tch_msg = msgb_alloc_headroom(in_len + 64, 64, "TCH frame"); + if (tch_msg == NULL) + return -ENOMEM; + + /* Copy the frame bytes to a new message */ + tch_msg->l2h = msgb_put(tch_msg, in_len); + memcpy(tch_msg->l2h, in, in_len); + + /* Put encoded TCH frame to the UL buffer */ + msgb_enqueue_count(&state->tch_ul_fb, tch_msg, + &state->tch_ul_fb_len); + + return 0; +} + +/** + * A custom TCH frame buffer block, which actually + * handles incoming frames from DL buffer and puts + * outgoing frames to UL buffer... + */ +static int pq_queue_tch_fb(struct osmo_gapk_pq *pq, + struct gapk_io_state *state, + bool is_src) +{ + struct osmo_gapk_pq_item *item; + unsigned int frame_len; + + LOGP(DGAPK, LOGL_DEBUG, "PQ '%s': Adding TCH frame buffer %s\n", + pq->name, is_src ? "input" : "output"); + + /* Allocate and add a new queue item */ + item = osmo_gapk_pq_add_item(pq); + if (item == NULL) + return -ENOMEM; + + /* General item type and description */ + item->type = is_src ? OSMO_GAPK_ITEM_TYPE_SOURCE : OSMO_GAPK_ITEM_TYPE_SINK; + item->cat_name = is_src ? "source" : "sink"; + item->sub_name = "tch_fb"; + + /* I/O length */ + frame_len = state->phy_fmt_desc->frame_len; + item->len_in = is_src ? 0 : frame_len; + item->len_out = is_src ? frame_len : 0; + + /* Handler and it's state */ + item->proc = is_src ? &pq_queue_tch_fb_recv : &pq_queue_tch_fb_send; + item->state = state; + + return 0; +} + +/** + * Auxiliary wrapper around format conversion block. + * Is used to perform either a conversion from the format, + * produced by encoder, to canonical, or a conversion + * from canonical format to the format expected by decoder. + */ +static int pq_queue_codec_fmt_conv(struct osmo_gapk_pq *pq, + const struct osmo_gapk_codec_desc *codec, + bool is_src) +{ + const struct osmo_gapk_format_desc *codec_fmt_desc; + + /* Get format description */ + codec_fmt_desc = osmo_gapk_fmt_get_from_type(is_src ? + codec->codec_enc_format_type : codec->codec_dec_format_type); + if (codec_fmt_desc == NULL) + return -ENOTSUP; + + /* Put format conversion block */ + return osmo_gapk_pq_queue_fmt_convert(pq, codec_fmt_desc, !is_src); +} + +/** + * Prepares the following queue (source is mic): + * + * source/alsa -> proc/codec -> proc/format -> + * -> proc/format -> sink/tch_fb + * + * The two format conversion blocks are aimed to + * convert an encoder specific format + * to a PHY specific format. + */ +static int prepare_audio_source(struct gapk_io_state *state, + const char *alsa_input_dev) +{ + struct osmo_gapk_pq *pq; + char *pq_desc; + int rc; + + LOGP(DGAPK, LOGL_DEBUG, "Prepare audio input (capture) chain\n"); + + /* Allocate a processing queue */ + pq = osmo_gapk_pq_create("pq_audio_source"); + if (pq == NULL) + return -ENOMEM; + + /* ALSA audio source */ + rc = osmo_gapk_pq_queue_alsa_input(pq, alsa_input_dev, rawpcm_fmt->frame_len); + if (rc) + goto error; + + /* Frame encoder */ + rc = osmo_gapk_pq_queue_codec(pq, state->codec_desc, 1); + if (rc) + goto error; + + /* Encoder specific format -> canonical */ + rc = pq_queue_codec_fmt_conv(pq, state->codec_desc, true); + if (rc) + goto error; + + /* Canonical -> PHY specific format */ + rc = osmo_gapk_pq_queue_fmt_convert(pq, state->phy_fmt_desc, 1); + if (rc) + goto error; + + /* TCH frame buffer sink */ + rc = pq_queue_tch_fb(pq, state, false); + if (rc) + goto error; + + /* Check composed queue in strict mode */ + rc = osmo_gapk_pq_check(pq, 1); + if (rc) + goto error; + + /* Prepare queue (allocate buffers, etc.) */ + rc = osmo_gapk_pq_prepare(pq); + if (rc) + goto error; + + /* Save pointer within MS GAPK state */ + state->pq_source = pq; + + /* Describe prepared chain */ + pq_desc = osmo_gapk_pq_describe(pq); + LOGP(DGAPK, LOGL_DEBUG, "PQ '%s': chain '%s' prepared\n", pq->name, pq_desc); + talloc_free(pq_desc); + + return 0; + +error: + talloc_free(pq); + return rc; +} + +/** + * Prepares the following queue (sink is speaker): + * + * src/tch_fb -> proc/format -> [proc/ecu] -> + * proc/format -> proc/codec -> sink/alsa + * + * The two format conversion blocks (proc/format) + * are aimed to convert a PHY specific format + * to an encoder specific format. + * + * A ECU (Error Concealment Unit) block is optionally + * added if implemented for a given codec. + */ +static int prepare_audio_sink(struct gapk_io_state *state, + const char *alsa_output_dev) +{ + struct osmo_gapk_pq *pq; + char *pq_desc; + int rc; + + LOGP(DGAPK, LOGL_DEBUG, "Prepare audio output (playback) chain\n"); + + /* Allocate a processing queue */ + pq = osmo_gapk_pq_create("pq_audio_sink"); + if (pq == NULL) + return -ENOMEM; + + /* TCH frame buffer source */ + rc = pq_queue_tch_fb(pq, state, true); + if (rc) + goto error; + + /* PHY specific format -> canonical */ + rc = osmo_gapk_pq_queue_fmt_convert(pq, state->phy_fmt_desc, 0); + if (rc) + goto error; + + /* Optional ECU (Error Concealment Unit) */ + osmo_gapk_pq_queue_ecu(pq, state->codec_desc); + + /* Canonical -> decoder specific format */ + rc = pq_queue_codec_fmt_conv(pq, state->codec_desc, false); + if (rc) + goto error; + + /* Frame decoder */ + rc = osmo_gapk_pq_queue_codec(pq, state->codec_desc, 0); + if (rc) + goto error; + + /* ALSA audio sink */ + rc = osmo_gapk_pq_queue_alsa_output(pq, alsa_output_dev, rawpcm_fmt->frame_len); + if (rc) + goto error; + + /* Check composed queue in strict mode */ + rc = osmo_gapk_pq_check(pq, 1); + if (rc) + goto error; + + /* Prepare queue (allocate buffers, etc.) */ + rc = osmo_gapk_pq_prepare(pq); + if (rc) + goto error; + + /* Save pointer within MS GAPK state */ + state->pq_sink = pq; + + /* Describe prepared chain */ + pq_desc = osmo_gapk_pq_describe(pq); + LOGP(DGAPK, LOGL_DEBUG, "PQ '%s': chain '%s' prepared\n", pq->name, pq_desc); + talloc_free(pq_desc); + + return 0; + +error: + talloc_free(pq); + return rc; +} + +/** + * Cleans up both TCH frame I/O buffers, destroys both + * processing queues (chains), and deallocates the memory. + * Should be called when a voice call is finished... + */ +void gapk_io_state_free(struct gapk_io_state *state) +{ + struct msgb *msg; + + if (state == NULL) + return; + + /* Flush TCH frame I/O buffers */ + while ((msg = msgb_dequeue(&state->tch_dl_fb))) + msgb_free(msg); + while ((msg = msgb_dequeue(&state->tch_ul_fb))) + msgb_free(msg); + + /* Destroy both audio I/O chains */ + if (state->pq_source != NULL) + osmo_gapk_pq_destroy(state->pq_source); + if (state->pq_sink != NULL) + osmo_gapk_pq_destroy(state->pq_sink); + + talloc_free(state); +} + +/** + * Picks the corresponding PHY's frame format for a given codec. + * To be used with PHYs that produce audio frames in RTP format, + * such as trxcon (GSM 05.03 libosmocoding API). + */ +static enum osmo_gapk_format_type phy_fmt_pick_rtp(enum osmo_gapk_codec_type codec) +{ + switch (codec) { + case CODEC_HR: + return FMT_RTP_HR_IETF; + case CODEC_FR: + return FMT_GSM; + case CODEC_EFR: + return FMT_RTP_EFR; + case CODEC_AMR: + return FMT_RTP_AMR; + default: + return FMT_INVALID; + } +} + +/** + * Picks the corresponding PHY's frame format for a given codec. + * To be used with PHYs that produce audio in TI Calypso format. + */ +static enum osmo_gapk_format_type phy_fmt_pick_ti(enum osmo_gapk_codec_type codec) +{ + switch (codec) { + case CODEC_HR: + return FMT_TI_HR; + case CODEC_FR: + return FMT_TI_FR; + case CODEC_EFR: + return FMT_TI_EFR; + case CODEC_AMR: /* not supported */ + default: + return FMT_INVALID; + } +} + +/** + * Allocates both TCH frame I/O buffers + * and prepares both processing queues (chains). + * Should be called when a voice call is initiated... + */ +struct gapk_io_state * +gapk_io_state_alloc(struct osmocom_ms *ms, + enum osmo_gapk_codec_type codec) +{ + const struct osmo_gapk_format_desc *phy_fmt_desc; + const struct osmo_gapk_codec_desc *codec_desc; + const struct gsm_settings *set = &ms->settings; + enum osmo_gapk_format_type phy_fmt; + struct gapk_io_state *state; + int rc = 0; + + LOGP(DGAPK, LOGL_NOTICE, "Initialize GAPK I/O\n"); + + /* Make sure that the chosen codec has description */ + codec_desc = osmo_gapk_codec_get_from_type(codec); + if (codec_desc == NULL) { + LOGP(DGAPK, LOGL_ERROR, "Invalid codec type 0x%02x\n", codec); + return NULL; + } + + /* Make sure that the chosen codec is supported */ + if (codec_desc->codec_encode == NULL || codec_desc->codec_decode == NULL) { + LOGP(DGAPK, LOGL_ERROR, + "Codec '%s' is not supported by GAPK\n", codec_desc->name); + return NULL; + } + + switch (set->tch_voice.io_format) { + case TCH_VOICE_IOF_RTP: + phy_fmt = phy_fmt_pick_rtp(codec); + break; + case TCH_VOICE_IOF_TI: + phy_fmt = phy_fmt_pick_ti(codec); + break; + default: + LOGP(DGAPK, LOGL_ERROR, "Unhandled I/O format %s\n", + tch_voice_io_format_name(set->tch_voice.io_format)); + return NULL; + } + + phy_fmt_desc = osmo_gapk_fmt_get_from_type(phy_fmt); + if (phy_fmt_desc == NULL) { + LOGP(DGAPK, LOGL_ERROR, "Failed to pick the PHY specific " + "frame format for codec '%s'\n", codec_desc->name); + return NULL; + } + + state = talloc_zero(ms, struct gapk_io_state); + if (state == NULL) { + LOGP(DGAPK, LOGL_ERROR, "Failed to allocate memory\n"); + return NULL; + } + + /* Init TCH frame I/O buffers */ + INIT_LLIST_HEAD(&state->tch_dl_fb); + INIT_LLIST_HEAD(&state->tch_ul_fb); + + /* Store the codec / format description */ + state->codec_desc = codec_desc; + state->phy_fmt_desc = phy_fmt_desc; + + /* Use gapk_io_state as talloc context for both chains */ + osmo_gapk_set_talloc_ctx(state); + + /* Prepare both source and sink chains */ + rc |= prepare_audio_source(state, set->tch_voice.alsa_input_dev); + rc |= prepare_audio_sink(state, set->tch_voice.alsa_output_dev); + + /* Fall back to ms instance */ + osmo_gapk_set_talloc_ctx(ms); + + /* If at lease one chain constructor failed */ + if (rc) { + /* Destroy both audio I/O chains */ + if (state->pq_source) + osmo_gapk_pq_destroy(state->pq_source); + if (state->pq_sink) + osmo_gapk_pq_destroy(state->pq_sink); + + /* Release the memory and return */ + talloc_free(state); + + LOGP(DGAPK, LOGL_ERROR, "Failed to initialize GAPK I/O\n"); + return NULL; + } + + LOGP(DGAPK, LOGL_NOTICE, + "GAPK I/O initialized for MS '%s', codec '%s'\n", + ms->name, codec_desc->name); + + return state; +} + +/* gapk_io_init_ms() wrapper, selecting a codec based on channel mode and rate */ +struct gapk_io_state * +gapk_io_state_alloc_mode_rate(struct osmocom_ms *ms, + enum gsm48_chan_mode ch_mode, + bool full_rate) +{ + enum osmo_gapk_codec_type codec; + + switch (ch_mode) { + case GSM48_CMODE_SPEECH_V1: /* FR or HR */ + codec = full_rate ? CODEC_FR : CODEC_HR; + break; + case GSM48_CMODE_SPEECH_EFR: + codec = CODEC_EFR; + break; + case GSM48_CMODE_SPEECH_AMR: + codec = CODEC_AMR; + break; + default: + LOGP(DGAPK, LOGL_ERROR, "Unhandled channel mode 0x%02x (%s)\n", + ch_mode, get_value_string(gsm48_chan_mode_names, ch_mode)); + return NULL; + } + + return gapk_io_state_alloc(ms, codec); +} + +/* Enqueue a Downlink TCH frame */ +void gapk_io_enqueue_dl(struct gapk_io_state *state, struct msgb *msg) +{ + if (state->tch_dl_fb_len >= GAPK_ULDL_QUEUE_LIMIT) { + LOGP(DGAPK, LOGL_ERROR, "DL TCH frame buffer overflow, dropping msg\n"); + msgb_free(msg); + return; + } + + msgb_enqueue_count(&state->tch_dl_fb, msg, + &state->tch_dl_fb_len); + + /* Decode and play a received DL TCH frame */ + osmo_gapk_pq_execute(state->pq_sink); +} + +/* Dequeue an Uplink TCH frame */ +void gapk_io_dequeue_ul(struct osmocom_ms *ms, struct gapk_io_state *state) +{ + struct msgb *msg; + + /* Record and encode an UL TCH frame */ + osmo_gapk_pq_execute(state->pq_source); + + /* Obtain one TCH frame from the UL buffer */ + msg = msgb_dequeue_count(&state->tch_ul_fb, &state->tch_ul_fb_len); + if (msg != NULL) + tch_send_msg(ms, msg); +} + +/** + * Performs basic initialization of GAPK library, + * setting the talloc root context and a logging category. + */ +static __attribute__((constructor)) void gapk_io_init(void) +{ + /* Init logging subsystem */ + osmo_gapk_log_init(DGAPK); + + /* Make RAWPCM format info easy to access */ + rawpcm_fmt = osmo_gapk_fmt_get_from_type(FMT_RAWPCM_S16LE); +} diff --git a/src/host/layer23/src/mobile/gsm322.c b/src/host/layer23/src/mobile/gsm322.c index 56dd236e..3d0cc77d 100644 --- a/src/host/layer23/src/mobile/gsm322.c +++ b/src/host/layer23/src/mobile/gsm322.c @@ -13,10 +13,6 @@ * 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 <stdint.h> @@ -36,18 +32,21 @@ #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/l1ctl.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/networks.h> +#include <osmocom/bb/common/utils.h> +#include <osmocom/bb/common/settings.h> #include <osmocom/bb/mobile/vty.h> #include <osmocom/bb/mobile/app_mobile.h> -#include <osmocom/bb/common/utils.h> +#include <osmocom/bb/mobile/gsm322.h> +#include <osmocom/bb/mobile/gsm48_mm.h> #include <l1ctl_proto.h> const char *ba_version = "osmocom BA V1\n"; static void gsm322_cs_timeout(void *arg); -static int gsm322_cs_select(struct osmocom_ms *ms, int index, uint16_t mcc, - uint16_t mnc, int any); +static int gsm322_cs_select(struct osmocom_ms *ms, int index, const struct osmo_plmn_id *plmn, int any); static int gsm322_m_switch_on(struct osmocom_ms *ms, struct msgb *msg); static void gsm322_any_timeout(void *arg); static int gsm322_nb_scan(struct osmocom_ms *ms); @@ -156,7 +155,7 @@ static int gsm322_nb_meas_ind(struct osmocom_ms *ms, uint16_t arfcn, * * * subscr->plmn_list * - * The "PLMN Selector list" stores prefered networks to select during PLMN + * The "PLMN Selector list" stores preferred networks to select during PLMN * search process. This list is also stored in the SIM. * * * subscr->plmn_na @@ -172,7 +171,7 @@ static int gsm322_nb_meas_ind(struct osmocom_ms *ms, uint16_t arfcn, * * * cs->list[1024+299] * - * This list stores measurements and cell informations during cell selection + * This list stores measurements and cell information during cell selection * process. It can be used to speed up repeated cell selection. * * * cs->ba_list @@ -201,7 +200,7 @@ static int gsm322_nb_meas_ind(struct osmocom_ms *ms, uint16_t arfcn, * the BCCH data after 5 minutes. This timer is also used if sync or read * fails. * - * The C1 and C2 criterion is calculated for the currently monitored neigbour + * The C1 and C2 criterion is calculated for the currently monitored neighbour * cells. During this process, a better neighbour cell will trigger cell * re-selection. * @@ -427,14 +426,14 @@ static int16_t calculate_c2(int16_t c1, int serving, int last_serving, return c2; } - /* penatly time reached */ + /* penalty time reached */ if (t >= (penalty_time + 1) * 20) { LOGP(DNB, LOGL_INFO, "C2 = C1 + CELL_RESELECT_OFFSET (%d) = %d " "(PENALTY_TIME reached)\n", cell_resel_off, c2); return c2; } - /* penalty time not reached, substract temporary offset */ + /* penalty time not reached, subtract temporary offset */ if (temp_offset < 7) c2 -= temp_offset * 10; else @@ -513,7 +512,7 @@ static void gsm322_unselect_cell(struct gsm322_cellsel *cs) cs->si->si5 = 0; /* unset SI5* */ cs->si = NULL; memset(&cs->sel_si, 0, sizeof(cs->sel_si)); - cs->sel_mcc = cs->sel_mnc = cs->sel_lac = cs->sel_id = 0; + memset(&cs->sel_cgi, 0, sizeof(cs->sel_cgi)); } /* print to DCS logging */ @@ -535,17 +534,15 @@ static void print_dcs(void *priv, const char *fmt, ...) } /* del forbidden LA */ -int gsm322_del_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, - uint16_t mnc, uint16_t lac) +int gsm322_del_forbidden_la(struct osmocom_ms *ms, const struct osmo_location_area_id *lai) { struct gsm322_plmn *plmn = &ms->plmn; struct gsm322_la_list *la; llist_for_each_entry(la, &plmn->forbidden_la, entry) { - if (la->mcc == mcc && la->mnc == mnc && la->lac == lac) { - LOGP(DPLMN, LOGL_INFO, "Delete from list of forbidden " - "LAs (mcc=%s, mnc=%s, lac=%04x)\n", - gsm_print_mcc(mcc), gsm_print_mnc(mnc), lac); + if (osmo_lai_cmp(&la->lai, lai) == 0) { + LOGP(DPLMN, LOGL_INFO, "Delete from list of forbidden LAs (LAI=%s)\n", + osmo_lai_name(lai)); llist_del(&la->entry); talloc_free(la); return 0; @@ -556,21 +553,16 @@ int gsm322_del_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, } /* add forbidden LA */ -int gsm322_add_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, - uint16_t mnc, uint16_t lac, uint8_t cause) +int gsm322_add_forbidden_la(struct osmocom_ms *ms, const struct osmo_location_area_id *lai, uint8_t cause) { struct gsm322_plmn *plmn = &ms->plmn; struct gsm322_la_list *la; - LOGP(DPLMN, LOGL_INFO, "Add to list of forbidden LAs " - "(mcc=%s, mnc=%s, lac=%04x)\n", gsm_print_mcc(mcc), - gsm_print_mnc(mnc), lac); + LOGP(DPLMN, LOGL_INFO, "Add to list of forbidden LAs (LAI=%s)\n", osmo_lai_name(lai)); la = talloc_zero(ms, struct gsm322_la_list); if (!la) return -ENOMEM; - la->mcc = mcc; - la->mnc = mnc; - la->lac = lac; + la->lai = *lai; la->cause = cause; llist_add_tail(&la->entry, &plmn->forbidden_la); @@ -578,14 +570,13 @@ int gsm322_add_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, } /* search forbidden LA */ -int gsm322_is_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, uint16_t mnc, - uint16_t lac) +int gsm322_is_forbidden_la(struct osmocom_ms *ms, const struct osmo_location_area_id *lai) { struct gsm322_plmn *plmn = &ms->plmn; struct gsm322_la_list *la; llist_for_each_entry(la, &plmn->forbidden_la, entry) { - if (la->mcc == mcc && la->mnc == mnc && la->lac == lac) + if (osmo_lai_cmp(&la->lai, lai) == 0) return 1; } @@ -593,15 +584,13 @@ int gsm322_is_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, uint16_t mnc, } /* search for PLMN in all BA lists */ -static struct gsm322_ba_list *gsm322_find_ba_list(struct gsm322_cellsel *cs, - uint16_t mcc, uint16_t mnc) +static struct gsm322_ba_list *gsm322_find_ba_list(struct gsm322_cellsel *cs, const struct osmo_plmn_id *plmn) { struct gsm322_ba_list *ba, *ba_found = NULL; /* search for BA list */ llist_for_each_entry(ba, &cs->ba_list, entry) { - if (ba->mcc == mcc - && ba->mnc == mnc) { + if (osmo_plmn_cmp(&ba->plmn, plmn) == 0) { ba_found = ba; break; } @@ -611,16 +600,14 @@ static struct gsm322_ba_list *gsm322_find_ba_list(struct gsm322_cellsel *cs, } /* search available PLMN */ -int gsm322_is_plmn_avail_and_allow(struct gsm322_cellsel *cs, uint16_t mcc, - uint16_t mnc) +int gsm322_is_plmn_avail_and_allow(struct gsm322_cellsel *cs, const struct osmo_plmn_id *plmn) { int i; for (i = 0; i <= 1023+299; i++) { if ((cs->list[i].flags & GSM322_CS_FLAG_TEMP_AA) && cs->list[i].sysinfo - && cs->list[i].sysinfo->mcc == mcc - && cs->list[i].sysinfo->mnc == mnc) + && (osmo_plmn_cmp(&cs->list[i].sysinfo->lai.plmn, plmn) == 0)) return 1; } @@ -635,9 +622,12 @@ int gsm322_is_hplmn_avail(struct gsm322_cellsel *cs, char *imsi) for (i = 0; i <= 1023+299; i++) { if ((cs->list[i].flags & GSM322_CS_FLAG_SYSINFO) && cs->list[i].sysinfo - && gsm_match_mnc(cs->list[i].sysinfo->mcc, - cs->list[i].sysinfo->mnc, imsi)) + && gsm_match_mnc(cs->list[i].sysinfo->lai.plmn.mcc, + cs->list[i].sysinfo->lai.plmn.mnc, + cs->list[i].sysinfo->lai.plmn.mnc_3_digits, + imsi)) return 1; + /* TODO: take into account mnc_3_digits, probably use osmo_mnc_cmp()*/ } return 0; @@ -899,8 +889,7 @@ static int gsm322_sort_list(struct osmocom_ms *ms) /* search if network has multiple cells */ found = NULL; llist_for_each_entry(temp, &temp_list, entry) { - if (temp->mcc == cs->list[i].sysinfo->mcc - && temp->mnc == cs->list[i].sysinfo->mnc) { + if (osmo_plmn_cmp(&temp->plmn, &cs->list[i].sysinfo->lai.plmn) == 0) { found = temp; break; } @@ -913,8 +902,7 @@ static int gsm322_sort_list(struct osmocom_ms *ms) temp = talloc_zero(ms, struct gsm322_plmn_list); if (!temp) return -ENOMEM; - temp->mcc = cs->list[i].sysinfo->mcc; - temp->mnc = cs->list[i].sysinfo->mnc; + memcpy(&temp->plmn, &cs->list[i].sysinfo->lai.plmn, sizeof(temp->plmn)); temp->rxlev = cs->list[i].rxlev; llist_add_tail(&temp->entry, &temp_list); } @@ -924,7 +912,7 @@ static int gsm322_sort_list(struct osmocom_ms *ms) if (subscr->sim_valid) { found = NULL; llist_for_each_entry(temp, &temp_list, entry) { - if (gsm_match_mnc(temp->mcc, temp->mnc, subscr->imsi)) { + if (gsm_match_mnc(temp->plmn.mcc, temp->plmn.mnc, temp->plmn.mnc_3_digits, subscr->imsi)) { found = temp; break; } @@ -940,8 +928,7 @@ static int gsm322_sort_list(struct osmocom_ms *ms) llist_for_each_entry(sim_entry, &subscr->plmn_list, entry) { found = NULL; llist_for_each_entry(temp, &temp_list, entry) { - if (temp->mcc == sim_entry->mcc - && temp->mnc == sim_entry->mnc) { + if (osmo_plmn_cmp(&temp->plmn, &sim_entry->plmn) == 0) { found = temp; break; } @@ -975,7 +962,7 @@ static int gsm322_sort_list(struct osmocom_ms *ms) entries--; } - /* move ohter PLMN in decreasing order */ + /* move other PLMN in decreasing order */ while(1) { found = NULL; llist_for_each_entry(temp, &temp_list, entry) { @@ -995,16 +982,15 @@ static int gsm322_sort_list(struct osmocom_ms *ms) i = 0; llist_for_each_entry(temp, &plmn->sorted_plmn, entry) { llist_for_each_entry(na_entry, &subscr->plmn_na, entry) { - if (temp->mcc == na_entry->mcc - && temp->mnc == na_entry->mnc) { + if (osmo_plmn_cmp(&temp->plmn, &na_entry->plmn) == 0) { temp->cause = na_entry->cause; break; } } LOGP(DPLMN, LOGL_INFO, "Creating Sorted PLMN list. " - "(%02d: mcc %s mnc %s allowed %s rx-lev %s)\n", - i, gsm_print_mcc(temp->mcc), - gsm_print_mnc(temp->mnc), (temp->cause) ? "no ":"yes", + "(%02d: mcc-mnc %s allowed %s rx-lev %s)\n", + i, osmo_plmn_name(&temp->plmn), + (temp->cause) ? "no ":"yes", gsm_print_rxlev(temp->rxlev)); i++; } @@ -1027,9 +1013,9 @@ static int gsm322_a_go_on_plmn(struct osmocom_ms *ms, struct msgb *msg) new_a_state(plmn, GSM322_A2_ON_PLMN); /* start timer, if on VPLMN of home country OR special case */ - if (!gsm_match_mnc(plmn->mcc, plmn->mnc, subscr->imsi) + if (!gsm_match_mnc(plmn->plmn.mcc, plmn->plmn.mnc, plmn->plmn.mnc_3_digits, subscr->imsi) && (subscr->always_search_hplmn - || gsm_match_mcc(plmn->mcc, subscr->imsi)) + || gsm_match_mcc(plmn->plmn.mcc, subscr->imsi)) && subscr->sim_valid && subscr->t6m_hplmn) start_plmn_timer(plmn, subscr->t6m_hplmn * 360); else @@ -1047,7 +1033,7 @@ static int gsm322_a_go_wait_for_plmns(struct osmocom_ms *ms, struct msgb *msg) new_a_state(plmn, GSM322_A4_WAIT_FOR_PLMN); - /* we must forward this, otherwhise "Any cell selection" + /* we must forward this, otherwise "Any cell selection" * will not start automatically. */ nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN); @@ -1070,41 +1056,37 @@ static int gsm322_a_no_more_plmn(struct osmocom_ms *ms, struct msgb *msg) int found; /* any allowable PLMN available? */ - found = gsm322_cs_select(ms, -1, 0, 0, 0); + found = gsm322_cs_select(ms, -1, NULL, 0); /* if no PLMN in list: * this means that we are at a point where we camp on any cell or - * no cell ist available. */ + * no cell is available. */ if (found < 0) { if (subscr->plmn_valid) { LOGP(DPLMN, LOGL_INFO, "Not any PLMN allowable. " "Do limited search with RPLMN.\n"); - plmn->mcc = subscr->plmn_mcc; - plmn->mnc = subscr->plmn_mnc; + memcpy(&plmn->plmn, &subscr->plmn, sizeof(struct osmo_plmn_id)); } else if (subscr->sim_valid) { LOGP(DPLMN, LOGL_INFO, "Not any PLMN allowable. " "Do limited search with HPLMN.\n"); - plmn->mcc = subscr->mcc; - plmn->mnc = subscr->mnc; + memcpy(&plmn->plmn, &subscr->plmn, sizeof(struct osmo_plmn_id)); } else { LOGP(DPLMN, LOGL_INFO, "Not any PLMN allowable. " "Do limited search with no PLMN.\n"); - plmn->mcc = 0; - plmn->mnc = 0; + memset(&plmn->plmn, 0, sizeof(struct osmo_plmn_id)); } return gsm322_a_go_wait_for_plmns(ms, msg); } /* select first PLMN in list */ - plmn->mcc = cs->list[found].sysinfo->mcc; - plmn->mnc = cs->list[found].sysinfo->mnc; + memcpy(&plmn->plmn, &cs->list[found].sysinfo->lai.plmn, sizeof(struct osmo_plmn_id)); LOGP(DPLMN, LOGL_INFO, "PLMN available after searching PLMN list " - "(mcc=%s mnc=%s %s, %s)\n", - gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc), - gsm_get_mcc(plmn->mcc), gsm_get_mnc(plmn->mcc, plmn->mnc)); + "(mcc=-mnc=%s %s, %s)\n", + osmo_plmn_name(&plmn->plmn), + gsm_get_mcc(plmn->plmn.mcc), gsm_get_mnc(&plmn->plmn)); /* indicate New PLMN */ nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN); @@ -1133,10 +1115,9 @@ static int gsm322_a_sel_first_plmn(struct osmocom_ms *ms, struct msgb *msg) i = 0; llist_for_each_entry(plmn_entry, &plmn->sorted_plmn, entry) { /* if last selected PLMN was HPLMN, we skip that */ - if (gsm_match_mnc(plmn_entry->mcc, plmn_entry->mnc, - subscr->imsi) - && plmn_entry->mcc == plmn->mcc - && plmn_entry->mnc == plmn->mnc) { + if (gsm_match_mnc(plmn_entry->plmn.mcc, plmn_entry->plmn.mnc, + plmn_entry->plmn.mnc_3_digits, subscr->imsi) + && (osmo_plmn_cmp(&plmn_entry->plmn, &plmn->plmn) == 0)) { LOGP(DPLMN, LOGL_INFO, "Skip HPLMN, because it was " "previously selected.\n"); i++; @@ -1147,10 +1128,9 @@ static int gsm322_a_sel_first_plmn(struct osmocom_ms *ms, struct msgb *msg) plmn_first = plmn_entry; break; } - LOGP(DPLMN, LOGL_INFO, "Skip PLMN (%02d: mcc=%s, mnc=%s), " + LOGP(DPLMN, LOGL_INFO, "Skip PLMN (%02d: mcc-mnc=%s), " "because it is not allowed (cause %d).\n", i, - gsm_print_mcc(plmn_entry->mcc), - gsm_print_mnc(plmn_entry->mnc), + osmo_plmn_name(&plmn_entry->plmn), plmn_entry->cause); i++; } @@ -1164,15 +1144,14 @@ static int gsm322_a_sel_first_plmn(struct osmocom_ms *ms, struct msgb *msg) return 0; } - LOGP(DPLMN, LOGL_INFO, "Selecting PLMN from list. (%02d: mcc=%s " - "mnc=%s %s, %s)\n", plmn->plmn_curr, - gsm_print_mcc(plmn_first->mcc), gsm_print_mnc(plmn_first->mnc), - gsm_get_mcc(plmn_first->mcc), - gsm_get_mnc(plmn_first->mcc, plmn_first->mnc)); + LOGP(DPLMN, LOGL_INFO, "Selecting PLMN from list. (%02d: mcc-mnc=%s %s, %s)\n", + plmn->plmn_curr, + osmo_plmn_name(&plmn_first->plmn), + gsm_get_mcc(plmn_first->plmn.mcc), + gsm_get_mnc(&plmn_first->plmn)); /* set current network */ - plmn->mcc = plmn_first->mcc; - plmn->mnc = plmn_first->mnc; + memcpy(&plmn->plmn, &plmn_first->plmn, sizeof(struct osmo_plmn_id)); new_a_state(plmn, GSM322_A3_TRYING_PLMN); @@ -1208,10 +1187,9 @@ static int gsm322_a_sel_next_plmn(struct osmocom_ms *ms, struct msgb *msg) plmn_next = plmn_entry; break; } - LOGP(DPLMN, LOGL_INFO, "Skip PLMN (%02d: mcc=%s, mnc=%s), " + LOGP(DPLMN, LOGL_INFO, "Skip PLMN (%02d: mcc-mnc=%s), " "because it is not allowed (cause %d).\n", i, - gsm_print_mcc(plmn_entry->mcc), - gsm_print_mnc(plmn_entry->mnc), + osmo_plmn_name(&plmn_entry->plmn), plmn_entry->cause); i++; } @@ -1225,13 +1203,13 @@ static int gsm322_a_sel_next_plmn(struct osmocom_ms *ms, struct msgb *msg) } /* set next network */ - plmn->mcc = plmn_next->mcc; - plmn->mnc = plmn_next->mnc; + memcpy(&plmn->plmn, &plmn_next->plmn, sizeof(struct osmo_plmn_id)); - LOGP(DPLMN, LOGL_INFO, "Selecting PLMN from list. (%02d: mcc=%s " - "mnc=%s %s, %s)\n", plmn->plmn_curr, - gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc), - gsm_get_mcc(plmn->mcc), gsm_get_mnc(plmn->mcc, plmn->mnc)); + LOGP(DPLMN, LOGL_INFO, "Selecting PLMN from list. (%02d: mcc-mnc=%s %s, %s)\n", + plmn->plmn_curr, + osmo_plmn_name(&plmn->plmn), + gsm_get_mcc(plmn->plmn.mcc), + gsm_get_mnc(&plmn->plmn)); new_a_state(plmn, GSM322_A3_TRYING_PLMN); @@ -1268,8 +1246,7 @@ static int gsm322_a_user_resel(struct osmocom_ms *ms, struct msgb *msg) /* search current PLMN in list */ llist_for_each_entry(plmn_entry, &plmn->sorted_plmn, entry) { - if (plmn_entry->mcc == plmn->mcc - && plmn_entry->mnc == plmn->mnc) { + if (osmo_plmn_cmp(&plmn_entry->plmn, &plmn->plmn) == 0) { plmn_found = plmn_entry; break; } @@ -1305,8 +1282,8 @@ static int gsm322_a_plmn_avail(struct osmocom_ms *ms, struct msgb *msg) struct gsm_subscriber *subscr = &ms->subscr; struct gsm322_msg *gm = (struct gsm322_msg *) msg->data; - if (subscr->plmn_valid && plmn->mcc == gm->mcc - && plmn->mnc == gm->mnc) { + if (subscr->plmn_valid && + (osmo_plmn_cmp(&plmn->plmn, &gm->plmn) == 0)) { struct msgb *nmsg; new_m_state(plmn, GSM322_A1_TRYING_RPLMN); @@ -1337,17 +1314,14 @@ static int gsm322_a_loss_of_radio(struct osmocom_ms *ms, struct msgb *msg) int found; /* any allowable PLMN available */ - found = gsm322_cs_select(ms, -1, 0, 0, 0); + found = gsm322_cs_select(ms, -1, NULL, 0); /* if PLMN in list */ if (found >= 0) { - LOGP(DPLMN, LOGL_INFO, "PLMN available (mcc=%s mnc=%s " - "%s, %s)\n", gsm_print_mcc( - cs->list[found].sysinfo->mcc), - gsm_print_mnc(cs->list[found].sysinfo->mnc), - gsm_get_mcc(cs->list[found].sysinfo->mcc), - gsm_get_mnc(cs->list[found].sysinfo->mcc, - cs->list[found].sysinfo->mnc)); + LOGP(DPLMN, LOGL_INFO, "PLMN available (mcc-mnc=%s %s, %s)\n", + osmo_plmn_name(&cs->list[found].sysinfo->lai.plmn), + gsm_get_mcc(cs->list[found].sysinfo->lai.plmn.mcc), + gsm_get_mnc(&cs->list[found].sysinfo->lai.plmn)); return gsm322_a_sel_first_plmn(ms, msg); } @@ -1374,17 +1348,17 @@ static int gsm322_a_switch_on(struct osmocom_ms *ms, struct msgb *msg) /* if there is a registered PLMN */ if (subscr->plmn_valid) { /* select the registered PLMN */ - plmn->mcc = subscr->plmn_mcc; - plmn->mnc = subscr->plmn_mnc; + memcpy(&plmn->plmn, &subscr->plmn, sizeof(struct osmo_plmn_id)); LOGP(DSUM, LOGL_INFO, "Start search of last registered PLMN " - "(mcc=%s mnc=%s %s, %s)\n", gsm_print_mcc(plmn->mcc), - gsm_print_mnc(plmn->mnc), gsm_get_mcc(plmn->mcc), - gsm_get_mnc(plmn->mcc, plmn->mnc)); - LOGP(DPLMN, LOGL_INFO, "Use RPLMN (mcc=%s mnc=%s " - "%s, %s)\n", gsm_print_mcc(plmn->mcc), - gsm_print_mnc(plmn->mnc), gsm_get_mcc(plmn->mcc), - gsm_get_mnc(plmn->mcc, plmn->mnc)); + "(mcc-%s %s, %s)\n", + osmo_plmn_name(&plmn->plmn), + gsm_get_mcc(plmn->plmn.mcc), + gsm_get_mnc(&plmn->plmn)); + LOGP(DPLMN, LOGL_INFO, "Use RPLMN (mcc-mnc=%s %s, %s)\n", + osmo_plmn_name(&plmn->plmn), + gsm_get_mcc(plmn->plmn.mcc), + gsm_get_mnc(&plmn->plmn)); new_a_state(plmn, GSM322_A1_TRYING_RPLMN); @@ -1397,7 +1371,7 @@ static int gsm322_a_switch_on(struct osmocom_ms *ms, struct msgb *msg) return 0; } - plmn->mcc = plmn->mnc = 0; + memset(&plmn->plmn, 0, sizeof(plmn->plmn)); /* initiate search at cell selection */ LOGP(DSUM, LOGL_INFO, "Search for network\n"); @@ -1440,7 +1414,7 @@ static int gsm322_a_sim_removed(struct osmocom_ms *ms, struct msgb *msg) gsm322_cs_sendmsg(ms, nmsg); /* flush list of PLMNs */ - gsm_subscr_del_forbidden_plmn(&ms->subscr, 0, 0); + gsm_subscr_del_forbidden_plmn(&ms->subscr, NULL); return gsm322_a_switch_on(ms, msg); } @@ -1515,47 +1489,43 @@ static int gsm322_m_display_plmns(struct osmocom_ms *ms, struct msgb *msg) /* generate list */ gsm322_sort_list(ms); - vty_notify(ms, NULL); + l23_vty_ms_notify(ms, NULL); switch (msg_type) { case GSM322_EVENT_REG_FAILED: - vty_notify(ms, "Failed to register to network %s, %s " - "(%s, %s)\n", - gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc), - gsm_get_mcc(plmn->mcc), - gsm_get_mnc(plmn->mcc, plmn->mnc)); + l23_vty_ms_notify(ms, "Failed to register to network %s (%s, %s)\n", + osmo_plmn_name(&plmn->plmn), + gsm_get_mcc(plmn->plmn.mcc), + gsm_get_mnc(&plmn->plmn)); break; case GSM322_EVENT_NO_CELL_FOUND: - vty_notify(ms, "No cell found for network %s, %s " - "(%s, %s)\n", - gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc), - gsm_get_mcc(plmn->mcc), - gsm_get_mnc(plmn->mcc, plmn->mnc)); + l23_vty_ms_notify(ms, "No cell found for network %s (%s, %s)\n", + osmo_plmn_name(&plmn->plmn), + gsm_get_mcc(plmn->plmn.mcc), + gsm_get_mnc(&plmn->plmn)); break; case GSM322_EVENT_ROAMING_NA: - vty_notify(ms, "Roaming not allowed to network %s, %s " - "(%s, %s)\n", - gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc), - gsm_get_mcc(plmn->mcc), - gsm_get_mnc(plmn->mcc, plmn->mnc)); + l23_vty_ms_notify(ms, "Roaming not allowed to network %s (%s, %s)\n", + osmo_plmn_name(&plmn->plmn), + gsm_get_mcc(plmn->plmn.mcc), + gsm_get_mnc(&plmn->plmn)); break; } if (llist_empty(&plmn->sorted_plmn)) - vty_notify(ms, "Search network!\n"); + l23_vty_ms_notify(ms, "Search network!\n"); else { - vty_notify(ms, "Search or select from network:\n"); + l23_vty_ms_notify(ms, "Search or select from network:\n"); llist_for_each_entry(temp, &plmn->sorted_plmn, entry) - vty_notify(ms, " Network %s, %s (%s, %s)\n", - gsm_print_mcc(temp->mcc), - gsm_print_mnc(temp->mnc), - gsm_get_mcc(temp->mcc), - gsm_get_mnc(temp->mcc, temp->mnc)); + l23_vty_ms_notify(ms, " Network mcc-mnc=%s (%s, %s)\n", + osmo_plmn_name(&temp->plmn), + gsm_get_mcc(temp->plmn.mcc), + gsm_get_mnc(&temp->plmn)); } /* go Not on PLMN state */ new_m_state(plmn, GSM322_M3_NOT_ON_PLMN); - /* we must forward this, otherwhise "Any cell selection" + /* we must forward this, otherwise "Any cell selection" * will not start automatically. * this way we get back to the last PLMN, in case we gained * our coverage back. @@ -1582,7 +1552,7 @@ static int gsm322_m_user_resel(struct osmocom_ms *ms, struct msgb *msg) * selected by the user. this prevents from switching back to the * last selected PLMN and destroying the list of scanned networks. */ - plmn->mcc = plmn->mnc = 0; + memset(&plmn->plmn, 0, sizeof(plmn->plmn)); if (!subscr->sim_valid) { return 0; @@ -1634,17 +1604,16 @@ static int gsm322_m_switch_on(struct osmocom_ms *ms, struct msgb *msg) struct msgb *nmsg; /* select the registered PLMN */ - plmn->mcc = subscr->plmn_mcc; - plmn->mnc = subscr->plmn_mnc; + memcpy(&plmn->plmn, &subscr->plmn, sizeof(struct osmo_plmn_id)); - LOGP(DSUM, LOGL_INFO, "Start search of last registered PLMN " - "(mcc=%s mnc=%s %s, %s)\n", gsm_print_mcc(plmn->mcc), - gsm_print_mnc(plmn->mnc), gsm_get_mcc(plmn->mcc), - gsm_get_mnc(plmn->mcc, plmn->mnc)); - LOGP(DPLMN, LOGL_INFO, "Use RPLMN (mcc=%s mnc=%s " - "%s, %s)\n", gsm_print_mcc(plmn->mcc), - gsm_print_mnc(plmn->mnc), gsm_get_mcc(plmn->mcc), - gsm_get_mnc(plmn->mcc, plmn->mnc)); + LOGP(DSUM, LOGL_INFO, "Start search of last registered PLMN (mcc-mnc=%s %s, %s)\n", + osmo_plmn_name(&plmn->plmn), + gsm_get_mcc(plmn->plmn.mcc), + gsm_get_mnc(&plmn->plmn)); + LOGP(DPLMN, LOGL_INFO, "Use RPLMN (mcc-mnc=%s %s, %s)\n", + osmo_plmn_name(&plmn->plmn), + gsm_get_mcc(plmn->plmn.mcc), + gsm_get_mnc(&plmn->plmn)); new_m_state(plmn, GSM322_M1_TRYING_RPLMN); @@ -1657,7 +1626,7 @@ static int gsm322_m_switch_on(struct osmocom_ms *ms, struct msgb *msg) return 0; } - plmn->mcc = plmn->mnc = 0; + memset(&plmn->plmn, 0, sizeof(plmn->plmn)); /* initiate search at cell selection */ LOGP(DSUM, LOGL_INFO, "Search for network\n"); @@ -1704,7 +1673,7 @@ static int gsm322_m_sim_removed(struct osmocom_ms *ms, struct msgb *msg) gsm322_cs_sendmsg(ms, nmsg); /* flush list of PLMNs */ - gsm_subscr_del_forbidden_plmn(&ms->subscr, 0, 0); + gsm_subscr_del_forbidden_plmn(&ms->subscr, NULL); return gsm322_m_switch_on(ms, msg); } @@ -1716,9 +1685,8 @@ static int gsm322_m_go_on_plmn(struct osmocom_ms *ms, struct msgb *msg) struct gsm_subscriber *subscr = &ms->subscr; /* set last registered PLMN */ - subscr->plmn_valid = 1; - subscr->plmn_mcc = plmn->mcc; - subscr->plmn_mnc = plmn->mnc; + subscr->plmn_valid = true; + memcpy(&subscr->plmn, &plmn->plmn, sizeof(struct osmo_plmn_id)); new_m_state(plmn, GSM322_M2_ON_PLMN); @@ -1753,15 +1721,15 @@ static int gsm322_m_choose_plmn(struct osmocom_ms *ms, struct msgb *msg) struct msgb *nmsg; /* use user selection */ - plmn->mcc = gm->mcc; - plmn->mnc = gm->mnc; + memcpy(&plmn->plmn, &gm->plmn, sizeof(struct osmo_plmn_id)); - LOGP(DPLMN, LOGL_INFO, "User selects PLMN. (mcc=%s mnc=%s " - "%s, %s)\n", gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc), - gsm_get_mcc(plmn->mcc), gsm_get_mnc(plmn->mcc, plmn->mnc)); + LOGP(DPLMN, LOGL_INFO, "User selects PLMN. (mcc-mnc=%s %s, %s)\n", + osmo_plmn_name(&plmn->plmn), + gsm_get_mcc(plmn->plmn.mcc), + gsm_get_mnc(&plmn->plmn)); /* if selected PLMN is in list of forbidden PLMNs */ - gsm_subscr_del_forbidden_plmn(subscr, plmn->mcc, plmn->mnc); + gsm_subscr_del_forbidden_plmn(subscr, &plmn->plmn); new_m_state(plmn, GSM322_M4_TRYING_PLMN); @@ -1817,8 +1785,7 @@ static int gsm322_am_no_cell_found(struct osmocom_ms *ms, struct msgb *msg) */ /* select a suitable and allowable cell */ -static int gsm322_cs_select(struct osmocom_ms *ms, int index, uint16_t mcc, - uint16_t mnc, int any) +static int gsm322_cs_select(struct osmocom_ms *ms, int index, const struct osmo_plmn_id *plmn, int any) { struct gsm322_cellsel *cs = &ms->cellsel; struct gsm_settings *set = &ms->settings; @@ -1847,7 +1814,7 @@ static int gsm322_cs_select(struct osmocom_ms *ms, int index, uint16_t mcc, if (cs->state == GSM322_C2_STORED_CELL_SEL || cs->state == GSM322_C5_CHOOSE_CELL) mask |= GSM322_CS_FLAG_BA; - flags = mask; /* all masked flags are requied */ + flags = mask; /* all masked flags are required */ /* loop through all scanned frequencies and select cell. * if an index is given (arfci), we just check this cell only */ @@ -1860,12 +1827,12 @@ static int gsm322_cs_select(struct osmocom_ms *ms, int index, uint16_t mcc, cs->list[i].flags &= ~GSM322_CS_FLAG_TEMP_AA; s = cs->list[i].sysinfo; - /* channel has no informations for us */ + /* channel has no information for us */ if (!s || (cs->list[i].flags & mask) != flags) { continue; } - /* check C1 criteria not fullfilled */ + /* check C1 criteria not fulfilled */ // TODO: class 3 DCS mobile gsm_arfcn2band_rc(index2arfcn(i), &band); class = class_of_band(ms, band); @@ -1906,58 +1873,51 @@ static int gsm322_cs_select(struct osmocom_ms *ms, int index, uint16_t mcc, if ((cs->list[i].flags & GSM322_CS_FLAG_FORBIDD)) { if (!any) { LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is " - "in list of forbidden LAs. (mcc=%s " - "mnc=%s lai=%04x)\n", + "in list of forbidden LAs. (lai=%s)\n", gsm_print_arfcn(index2arfcn(i)), - gsm_print_mcc(s->mcc), - gsm_print_mnc(s->mnc), s->lac); + osmo_lai_name(&s->lai)); continue; } LOGP(DCS, LOGL_INFO, "Accept ARFCN %s: Cell is in " "list of forbidden LAs, but we search for any " - "cell. (mcc=%s mnc=%s lai=%04x)\n", + "cell. (lai=%s)\n", gsm_print_arfcn(index2arfcn(i)), - gsm_print_mcc(s->mcc), - gsm_print_mnc(s->mnc), s->lac); + osmo_lai_name(&s->lai)); cs->list[i].flags &= ~GSM322_CS_FLAG_TEMP_AA; } /* if cell is in list of forbidden PLMNs */ - if (gsm_subscr_is_forbidden_plmn(subscr, s->mcc, s->mnc)) { + if (gsm_subscr_is_forbidden_plmn(subscr, &s->lai.plmn)) { if (!any) { LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is " - "in list of forbidden PLMNs. (mcc=%s " - "mnc=%s)\n", + "in list of forbidden PLMNs. (mcc-mnc=%s)\n", gsm_print_arfcn(index2arfcn(i)), - gsm_print_mcc(s->mcc), - gsm_print_mnc(s->mnc)); + osmo_plmn_name(&s->lai.plmn)); continue; } LOGP(DCS, LOGL_INFO, "Accept ARFCN %s: Cell is in list " "of forbidden PLMNs, but we search for any " - "cell. (mcc=%s mnc=%s)\n", + "cell. (mcc-mnc=%s)\n", gsm_print_arfcn(index2arfcn(i)), - gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc)); + osmo_plmn_name(&s->lai.plmn)); cs->list[i].flags &= ~GSM322_CS_FLAG_TEMP_AA; } /* if we search a specific PLMN, but it does not match */ - if (!any && mcc && (mcc != s->mcc - || mnc != s->mnc)) { + if (!any && plmn && (osmo_plmn_cmp(&s->lai.plmn, plmn) != 0)) { LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: PLMN of cell " - "does not match target PLMN. (mcc=%s " - "mnc=%s)\n", gsm_print_arfcn(index2arfcn(i)), - gsm_print_mcc(s->mcc), - gsm_print_mnc(s->mnc)); + "does not match target PLMN. (mcc-mnc=%s)\n", + gsm_print_arfcn(index2arfcn(i)), + osmo_plmn_name(&s->lai.plmn)); continue; } LOGP(DCS, LOGL_INFO, "Cell ARFCN %s: Cell found, (rxlev=%s " - "mcc=%s mnc=%s lac=%04x %s, %s)\n", - gsm_print_arfcn(index2arfcn(i)), + "lai=%s %s, %s)\n", + gsm_print_arfcn(index2arfcn(i)), gsm_print_rxlev(cs->list[i].rxlev), - gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc), s->lac, - gsm_get_mcc(s->mcc), gsm_get_mnc(s->mcc, s->mnc)); + osmo_lai_name(&s->lai), + gsm_get_mcc(s->lai.plmn.mcc), gsm_get_mnc(&s->lai.plmn)); /* find highest power cell */ if (found < 0 || cs->list[i].rxlev > power) { @@ -1974,8 +1934,7 @@ static int gsm322_cs_select(struct osmocom_ms *ms, int index, uint16_t mcc, } /* re-select a suitable and allowable cell */ -static int gsm322_cs_reselect(struct osmocom_ms *ms, uint16_t mcc, - uint16_t mnc, int any) +static int gsm322_cs_reselect(struct osmocom_ms *ms, const struct osmo_plmn_id *plmn, int any) { struct gsm322_cellsel *cs = &ms->cellsel; struct gsm_subscriber *subscr = &ms->subscr; @@ -2004,18 +1963,18 @@ static int gsm322_cs_reselect(struct osmocom_ms *ms, uint16_t mcc, /* if cell is in list of forbidden LAs */ if (!any && (cs->list[i].flags & GSM322_CS_FLAG_FORBIDD)) { LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is in list of " - "forbidden LAs. (mcc=%s mnc=%s lai=%04x)\n", - gsm_print_arfcn(index2arfcn(i)), gsm_print_mcc(s->mcc), - gsm_print_mnc(s->mnc), s->lac); + "forbidden LAs. (lai=%s)\n", + gsm_print_arfcn(index2arfcn(i)), + osmo_lai_name(&s->lai)); return -1; } /* if cell is in list of forbidden PLMNs */ - if (!any && gsm_subscr_is_forbidden_plmn(subscr, s->mcc, s->mnc)) { + if (!any && gsm_subscr_is_forbidden_plmn(subscr, &s->lai.plmn)) { LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is in " - "list of forbidden PLMNs. (mcc=%s mnc=%s)\n", + "list of forbidden PLMNs. (mcc-mnc=%s)\n", gsm_print_arfcn(index2arfcn(i)), - gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc)); + osmo_plmn_name(&s->lai.plmn)); return -1; } @@ -2030,21 +1989,21 @@ static int gsm322_cs_reselect(struct osmocom_ms *ms, uint16_t mcc, } /* if we search a specific PLMN, but it does not match */ - if (!any && mcc && (mcc != s->mcc - || mnc != s->mnc)) { + if (!any && plmn && (osmo_plmn_cmp(plmn, &s->lai.plmn) != 0)) { LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: PLMN of cell " - "does not match target PLMN. (mcc=%s mnc=%s)\n", - gsm_print_arfcn(index2arfcn(i)), gsm_print_mcc(s->mcc), - gsm_print_mnc(s->mnc)); + "does not match target PLMN. (mcc-mnc=%s)\n", + gsm_print_arfcn(index2arfcn(i)), + osmo_plmn_name(&s->lai.plmn)); return -1; } LOGP(DCS, LOGL_INFO, "Cell ARFCN %s: Neighbour cell accepted, " - "(rxlev=%s mcc=%s mnc=%s lac=%04x %s, %s)\n", + "(rxlev=%s lai=%s %s, %s)\n", gsm_print_arfcn(index2arfcn(i)), gsm_print_rxlev(cs->list[i].rxlev), - gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc), s->lac, - gsm_get_mcc(s->mcc), gsm_get_mnc(s->mcc, s->mnc)); + osmo_lai_name(&s->lai), + gsm_get_mcc(s->lai.plmn.mcc), + gsm_get_mnc(&s->lai.plmn)); return i; } @@ -2053,12 +2012,13 @@ static int gsm322_cs_reselect(struct osmocom_ms *ms, uint16_t mcc, static int gsm322_search_end(struct osmocom_ms *ms) { struct gsm322_cellsel *cs = &ms->cellsel; - struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_plmn *plmn322 = &ms->plmn; struct msgb *nmsg; struct gsm322_msg *ngm; int msg_type = -1; /* no message to be sent */ - int tune_back = 0, mcc = 0, mnc = 0; + int tune_back = 0; int found; + struct osmo_plmn_id plmn = {}; switch (cs->state) { case GSM322_ANY_SEARCH: @@ -2066,12 +2026,12 @@ static int gsm322_search_end(struct osmocom_ms *ms) LOGP(DCS, LOGL_INFO, "Any cell search finished.\n"); /* create AA flag */ - found = gsm322_cs_select(ms, -1, 0, 0, 0); + found = gsm322_cs_select(ms, -1, NULL, 0); /* if no cell is found, or if we don't wait for any available * and allowable PLMN to appear, we just continue to camp */ if (ms->settings.plmn_mode != PLMN_MODE_AUTO - || plmn->state != GSM322_A4_WAIT_FOR_PLMN + || plmn322->state != GSM322_A4_WAIT_FOR_PLMN || found < 0) { tune_back = 1; gsm322_c_camp_any_cell(ms, NULL); @@ -2080,10 +2040,9 @@ static int gsm322_search_end(struct osmocom_ms *ms) /* indicate available PLMN, include selected PLMN, if found */ msg_type = GSM322_EVENT_PLMN_AVAIL; - if (gsm322_is_plmn_avail_and_allow(cs, plmn->mcc, plmn->mnc)) { + if (gsm322_is_plmn_avail_and_allow(cs, &plmn322->plmn)) { /* set what PLMN becomes available */ - mcc = plmn->mcc; - mnc = plmn->mnc; + memcpy(&plmn, &plmn322->plmn, sizeof(struct osmo_plmn_id)); } new_c_state(cs, GSM322_C0_NULL); @@ -2096,7 +2055,7 @@ static int gsm322_search_end(struct osmocom_ms *ms) LOGP(DCS, LOGL_INFO, "PLMN search finished.\n"); /* create AA flag */ - gsm322_cs_select(ms, -1, 0, 0, 0); + gsm322_cs_select(ms, -1, NULL, 0); new_c_state(cs, GSM322_C0_NULL); @@ -2148,8 +2107,7 @@ static int gsm322_search_end(struct osmocom_ms *ms) if (!nmsg) return -ENOMEM; ngm = (struct gsm322_msg *) nmsg->data; - ngm->mcc = mcc; - ngm->mnc = mnc; + memcpy(&ngm->plmn, &plmn, sizeof(struct osmo_plmn_id)); gsm322_plmn_sendmsg(ms, nmsg); } @@ -2166,10 +2124,8 @@ static int gsm322_search_end(struct osmocom_ms *ms) memcpy(cs->list[cs->arfci].sysinfo, &cs->sel_si, sizeof(struct gsm48_sysinfo)); cs->si = cs->list[cs->arfci].sysinfo; - cs->sel_mcc = cs->si->mcc; - cs->sel_mnc = cs->si->mnc; - cs->sel_lac = cs->si->lac; - cs->sel_id = cs->si->cell_id; + cs->sel_cgi.lai = cs->si->lai; + cs->sel_cgi.cell_identity = cs->si->cell_id; LOGP(DCS, LOGL_INFO, "Tuning back to frequency %s after full " "search.\n", gsm_print_arfcn(cs->arfcn)); cs->sync_retries = SYNC_RETRIES; @@ -2195,7 +2151,7 @@ static int gsm322_cs_scan(struct osmocom_ms *ms) if (cs->state == GSM322_C2_STORED_CELL_SEL || cs->state == GSM322_C5_CHOOSE_CELL) mask |= GSM322_CS_FLAG_BA; - flags = mask; /* all masked flags are requied */ + flags = mask; /* all masked flags are required */ for (i = 0; i <= 1023+299; i++) { j = 0; /* make gcc happy */ if (!ms->settings.skip_max_per_band) { @@ -2249,7 +2205,7 @@ static int gsm322_cs_scan(struct osmocom_ms *ms) } /* NOTE: We might already have system information from previous - * scan. But we need recent informations, so we scan again! + * scan. But we need recent information, so we scan again! */ /* Tune to frequency for a while, to receive broadcasts. */ @@ -2317,17 +2273,17 @@ static int gsm322_cs_store(struct osmocom_ms *ms) cs->list[cs->arfci].flags &= ~GSM322_CS_FLAG_BARRED; /* store selected network */ - if (s->mcc) { - if (gsm322_is_forbidden_la(ms, s->mcc, s->mnc, s->lac)) + if (s->lai.plmn.mcc) { + if (gsm322_is_forbidden_la(ms, &s->lai)) cs->list[cs->arfci].flags |= GSM322_CS_FLAG_FORBIDD; else cs->list[cs->arfci].flags &= ~GSM322_CS_FLAG_FORBIDD; } - LOGP(DCS, LOGL_DEBUG, "Scan frequency %s: Cell found. (rxlev %s " - "mcc %s mnc %s lac %04x)\n", gsm_print_arfcn(cs->arfcn), + LOGP(DCS, LOGL_DEBUG, "Scan frequency %s: Cell found. (rxlev %s lai %s)\n", + gsm_print_arfcn(cs->arfcn), gsm_print_rxlev(cs->list[cs->arfci].rxlev), - gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc), s->lac); + osmo_lai_name(&s->lai)); /* selected PLMN (auto) becomes available during "any search" */ if (ms->settings.plmn_mode == PLMN_MODE_AUTO @@ -2336,10 +2292,10 @@ static int gsm322_cs_store(struct osmocom_ms *ms) || cs->state == GSM322_C8_ANY_CELL_RESEL || cs->state == GSM322_C9_CHOOSE_ANY_CELL) && plmn->state == GSM322_A4_WAIT_FOR_PLMN - && s->mcc == plmn->mcc && s->mnc == plmn->mnc) { + && (osmo_plmn_cmp(&s->lai.plmn, &plmn->plmn) == 0)) { LOGP(DCS, LOGL_INFO, "Candidate network to become available " "again\n"); - found = gsm322_cs_select(ms, cs->arfci, s->mcc, s->mnc, 0); + found = gsm322_cs_select(ms, cs->arfci, &s->lai.plmn, 0); if (found >= 0) { LOGP(DCS, LOGL_INFO, "Selected PLMN in \"A4_WAIT_F" "OR_PLMN\" state becomes available.\n"); @@ -2350,8 +2306,7 @@ indicate_plmn_avail: return -ENOMEM; /* set what PLMN becomes available */ ngm = (struct gsm322_msg *) nmsg->data; - ngm->mcc = plmn->mcc; - ngm->mnc = plmn->mcc; + memcpy(&ngm->plmn, &plmn->plmn, sizeof(struct osmo_plmn_id)); gsm322_plmn_sendmsg(ms, nmsg); new_c_state(cs, GSM322_C0_NULL); @@ -2367,10 +2322,10 @@ indicate_plmn_avail: || cs->state == GSM322_C8_ANY_CELL_RESEL || cs->state == GSM322_C9_CHOOSE_ANY_CELL) && plmn->state == GSM322_M3_NOT_ON_PLMN - && s->mcc == plmn->mcc && s->mnc == plmn->mnc) { + && (osmo_plmn_cmp(&s->lai.plmn, &plmn->plmn) == 0)) { LOGP(DCS, LOGL_INFO, "Candidate network to become available " "again\n"); - found = gsm322_cs_select(ms, cs->arfci, s->mcc, s->mnc, 0); + found = gsm322_cs_select(ms, cs->arfci, &s->lai.plmn, 0); if (found >= 0) { LOGP(DCS, LOGL_INFO, "Current selected PLMN in \"M3_N" "OT_ON_PLMN\" state becomes available.\n"); @@ -2410,9 +2365,9 @@ indicate_plmn_avail: if (cs->state == GSM322_C4_NORMAL_CELL_RESEL || cs->state == GSM322_C8_ANY_CELL_RESEL) - found = gsm322_cs_reselect(ms, cs->mcc, cs->mnc, any); + found = gsm322_cs_reselect(ms, &cs->plmn, any); else - found = gsm322_cs_select(ms, -1, cs->mcc, cs->mnc, any); + found = gsm322_cs_select(ms, -1, &cs->plmn, any); /* if not found */ if (found < 0) { @@ -2437,18 +2392,14 @@ indicate_plmn_avail: cs->selected = 1; cs->sel_arfcn = cs->arfcn; memcpy(&cs->sel_si, cs->si, sizeof(cs->sel_si)); - cs->sel_mcc = cs->si->mcc; - cs->sel_mnc = cs->si->mnc; - cs->sel_lac = cs->si->lac; - cs->sel_id = cs->si->cell_id; + cs->sel_cgi.lai = cs->si->lai; + cs->sel_cgi.cell_identity = cs->si->cell_id; if (ms->rrlayer.monitor) { - vty_notify(ms, "MON: %scell selected ARFCN=%s MCC=%s MNC=%s " - "LAC=0x%04x cellid=0x%04x (%s %s)\n", + l23_vty_ms_notify(ms, "MON: %scell selected ARFCN=%s CGI=%s (%s %s)\n", (any) ? "any " : "", gsm_print_arfcn(cs->sel_arfcn), - gsm_print_mcc(cs->sel_mcc), gsm_print_mnc(cs->sel_mnc), - cs->sel_lac, cs->sel_id, - gsm_get_mcc(cs->sel_mcc), - gsm_get_mnc(cs->sel_mcc, cs->sel_mnc)); + osmo_cgi_name(&cs->sel_cgi), + gsm_get_mcc(cs->sel_cgi.lai.plmn.mcc), + gsm_get_mnc(&cs->sel_cgi.lai.plmn)); } /* tell CS process about available cell */ @@ -2462,13 +2413,14 @@ indicate_plmn_avail: return 0; } -/* process system information when returing to idle mode */ +/* process system information when returning to idle mode */ struct gsm322_ba_list *gsm322_cs_sysinfo_sacch(struct osmocom_ms *ms) { struct gsm322_cellsel *cs = &ms->cellsel; struct gsm48_sysinfo *s; struct gsm322_ba_list *ba = NULL; - int i, refer_pcs; + int i; + bool refer_pcs; uint8_t freq[128+38]; if (!cs) { @@ -2484,13 +2436,12 @@ struct gsm322_ba_list *gsm322_cs_sysinfo_sacch(struct osmocom_ms *ms) /* collect system information received during dedicated mode */ if (s->si5 && (!s->nb_ext_ind_si5 || s->si5bis)) { /* find or create ba list */ - ba = gsm322_find_ba_list(cs, s->mcc, s->mnc); + ba = gsm322_find_ba_list(cs, &s->lai.plmn); if (!ba) { ba = talloc_zero(ms, struct gsm322_ba_list); if (!ba) return NULL; - ba->mcc = s->mcc; - ba->mnc = s->mnc; + memcpy(&ba->plmn, &s->lai.plmn, sizeof(struct osmo_plmn_id)); llist_add_tail(&ba->entry, &cs->ba_list); } /* update (add) ba list */ @@ -2506,10 +2457,10 @@ struct gsm322_ba_list *gsm322_cs_sysinfo_sacch(struct osmocom_ms *ms) } } if (!!memcmp(freq, ba->freq, sizeof(freq))) { - LOGP(DCS, LOGL_INFO, "New BA list (mcc=%s mnc=%s " - "%s, %s).\n", gsm_print_mcc(ba->mcc), - gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc), - gsm_get_mnc(ba->mcc, ba->mnc)); + LOGP(DCS, LOGL_INFO, "New BA list (mcc-mnc=%s %s, %s).\n", + osmo_plmn_name(&ba->plmn), + gsm_get_mcc(ba->plmn.mcc), + gsm_get_mnc(&ba->plmn)); memcpy(ba->freq, freq, sizeof(freq)); } } @@ -2517,22 +2468,22 @@ struct gsm322_ba_list *gsm322_cs_sysinfo_sacch(struct osmocom_ms *ms) return ba; } -/* store BA whenever a system informations changes */ +/* store BA whenever a system information changes */ static int gsm322_store_ba_list(struct gsm322_cellsel *cs, struct gsm48_sysinfo *s) { struct gsm322_ba_list *ba; - int i, refer_pcs; + int i; + bool refer_pcs; uint8_t freq[128+38]; /* find or create ba list */ - ba = gsm322_find_ba_list(cs, s->mcc, s->mnc); + ba = gsm322_find_ba_list(cs, &s->lai.plmn); if (!ba) { ba = talloc_zero(cs->ms, struct gsm322_ba_list); if (!ba) return -ENOMEM; - ba->mcc = s->mcc; - ba->mnc = s->mnc; + memcpy(&ba->plmn, &s->lai.plmn, sizeof(struct osmo_plmn_id)); llist_add_tail(&ba->entry, &cs->ba_list); } /* update ba list */ @@ -2549,10 +2500,10 @@ static int gsm322_store_ba_list(struct gsm322_cellsel *cs, } } if (!!memcmp(freq, ba->freq, sizeof(freq))) { - LOGP(DCS, LOGL_INFO, "New BA list (mcc=%s mnc=%s " - "%s, %s).\n", gsm_print_mcc(ba->mcc), - gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc), - gsm_get_mnc(ba->mcc, ba->mnc)); + LOGP(DCS, LOGL_INFO, "New BA list (mcc-mnc=%s %s, %s).\n", + osmo_plmn_name(&ba->plmn), + gsm_get_mcc(ba->plmn.mcc), + gsm_get_mnc(&ba->plmn)); memcpy(ba->freq, freq, sizeof(freq)); } @@ -2582,12 +2533,12 @@ static int gsm322_c_camp_sysinfo_bcch(struct osmocom_ms *ms, struct msgb *msg) return 0; } - /* Store BA if we have full system info about cells and neigbor cells. + /* Store BA if we have full system info about cells and neighbor cells. * Depending on the extended bit in the channel description, - * we require more or less system informations about neighbor cells + * we require more or less system information about neighbor cells */ - if (s->mcc - && s->mnc + if (s->lai.plmn.mcc + && s->lai.plmn.mnc && (gm->sysinfo == GSM48_MT_RR_SYSINFO_1 || gm->sysinfo == GSM48_MT_RR_SYSINFO_2 || gm->sysinfo == GSM48_MT_RR_SYSINFO_2bis @@ -2600,7 +2551,7 @@ static int gsm322_c_camp_sysinfo_bcch(struct osmocom_ms *ms, struct msgb *msg) && s->nb_ext_ind_si2bis))) gsm322_store_ba_list(cs, s); - /* update sel_si, if all relevant system informations received */ + /* update sel_si, if all relevant system information received */ if (s->si1 && s->si2 && s->si3 && (!s->nb_ext_ind_si2 || (s->si2bis && s->nb_ext_ind_si2 && !s->nb_ext_ind_si2bis) @@ -2627,7 +2578,7 @@ static int gsm322_c_camp_sysinfo_bcch(struct osmocom_ms *ms, struct msgb *msg) && cs->list[cs->arfci].sysinfo->sp_cbq)) { LOGP(DCS, LOGL_INFO, "Cell becomes barred.\n"); if (ms->rrlayer.monitor) - vty_notify(ms, "MON: trigger cell re-selection" + l23_vty_ms_notify(ms, "MON: trigger cell re-selection" ": cell becomes barred\n"); trigger_resel: /* mark cell as unscanned */ @@ -2635,6 +2586,8 @@ static int gsm322_c_camp_sysinfo_bcch(struct osmocom_ms *ms, struct msgb *msg) if (cs->list[cs->arfci].sysinfo) { LOGP(DCS, LOGL_DEBUG, "free sysinfo arfcn=%s\n", gsm_print_arfcn(cs->arfcn)); + if (cs->si == cs->list[cs->arfci].sysinfo) + cs->si = NULL; talloc_free(cs->list[cs->arfci].sysinfo); cs->list[cs->arfci].sysinfo = NULL; } @@ -2655,27 +2608,26 @@ static int gsm322_c_camp_sysinfo_bcch(struct osmocom_ms *ms, struct msgb *msg) & (s->class_barr ^ 0xffff))) { LOGP(DCS, LOGL_INFO, "Cell access becomes barred.\n"); if (ms->rrlayer.monitor) - vty_notify(ms, "MON: trigger cell re-selection" + l23_vty_ms_notify(ms, "MON: trigger cell re-selection" ": access to cell becomes barred\n"); goto trigger_resel; } } /* check if MCC, MNC, LAC, cell ID changes */ - if (cs->sel_mcc != s->mcc || cs->sel_mnc != s->mnc - || cs->sel_lac != s->lac) { + if (osmo_lai_cmp(&cs->sel_cgi.lai, &s->lai) != 0) { LOGP(DCS, LOGL_NOTICE, "Cell changes location area. " "This is not good!\n"); if (ms->rrlayer.monitor) - vty_notify(ms, "MON: trigger cell re-selection: " + l23_vty_ms_notify(ms, "MON: trigger cell re-selection: " "cell changes LAI\n"); goto trigger_resel; } - if (cs->sel_id != s->cell_id) { + if (cs->sel_cgi.cell_identity != s->cell_id) { LOGP(DCS, LOGL_NOTICE, "Cell changes cell ID. " "This is not good!\n"); if (ms->rrlayer.monitor) - vty_notify(ms, "MON: trigger cell re-selection: " + l23_vty_ms_notify(ms, "MON: trigger cell re-selection: " "cell changes cell ID\n"); goto trigger_resel; } @@ -2696,12 +2648,12 @@ static int gsm322_c_scan_sysinfo_bcch(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } - /* Store BA if we have full system info about cells and neigbor cells. + /* Store BA if we have full system info about cells and neighbor cells. * Depending on the extended bit in the channel description, - * we require more or less system informations about neighbor cells + * we require more or less system information about neighbor cells */ - if (s->mcc - && s->mnc + if (s->lai.plmn.mcc + && s->lai.plmn.mnc && (gm->sysinfo == GSM48_MT_RR_SYSINFO_1 || gm->sysinfo == GSM48_MT_RR_SYSINFO_2 || gm->sysinfo == GSM48_MT_RR_SYSINFO_2bis @@ -2712,7 +2664,7 @@ static int gsm322_c_scan_sysinfo_bcch(struct osmocom_ms *ms, struct msgb *msg) && (!s->si2ter_ind || s->si2ter)) gsm322_store_ba_list(cs, s); - /* all relevant system informations received */ + /* all relevant system information received */ if (s->si1 && s->si2 && s->si3 && (!s->nb_ext_ind_si2 || s->si2bis) && (!s->si2ter_ind || s->si2ter)) { @@ -2752,6 +2704,8 @@ static void gsm322_cs_timeout(void *arg) if (cs->list[cs->arfci].sysinfo) { LOGP(DCS, LOGL_DEBUG, "free sysinfo arfcn=%s\n", gsm_print_arfcn(cs->arfcn)); + if (cs->si == cs->list[cs->arfci].sysinfo) + cs->si = NULL; talloc_free(cs->list[cs->arfci].sysinfo); cs->list[cs->arfci].sysinfo = NULL; } @@ -2919,6 +2873,8 @@ int gsm322_l1_signal(unsigned int subsys, unsigned int signal, cs->list[i].flags &= ~GSM322_CS_FLAG_SYSINFO; LOGP(DCS, LOGL_DEBUG, "free sysinfo ARFCN=%s\n", gsm_print_arfcn(index2arfcn(i))); + if (cs->si == cs->list[i].sysinfo) + cs->si = NULL; talloc_free(cs->list[i].sysinfo); cs->list[i].sysinfo = NULL; } @@ -3089,7 +3045,7 @@ static void gsm322_cs_loss(void *arg) LOGP(DCS, LOGL_INFO, "Loss of CCCH, Trigger " "re-selection.\n"); if (ms->rrlayer.monitor) - vty_notify(ms, "MON: trigger cell " + l23_vty_ms_notify(ms, "MON: trigger cell " "re-selection: loss of signal\n"); nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_RESEL); @@ -3108,7 +3064,7 @@ static void gsm322_cs_loss(void *arg) /* keep cell info for re-selection */ gsm48_rr_los(ms); - /* be shure that nothing else is done after here + /* be sure that nothing else is done after here * because the function call above may cause * to return from idle state and trigger cell re-sel. */ @@ -3220,7 +3176,7 @@ static int gsm322_c_stored_cell_sel(struct osmocom_ms *ms, return gsm322_cs_powerscan(ms); } -/* start noraml cell selection */ +/* start normal cell selection */ static int gsm322_c_normal_cell_sel(struct osmocom_ms *ms, struct msgb *msg) { struct gsm322_cellsel *cs = &ms->cellsel; @@ -3265,7 +3221,7 @@ static int gsm322_c_any_cell_sel(struct osmocom_ms *ms, struct msgb *msg) /* indicate to MM that we lost coverage. * this is the only case where we really have no coverage. - * we tell MM, so it will enter the "No Cell Avaiable" state. */ + * we tell MM, so it will enter the "No Cell Available" state. */ if (msg_type == GSM322_EVENT_NO_CELL_FOUND) { struct msgb *nmsg; @@ -3281,7 +3237,7 @@ static int gsm322_c_any_cell_sel(struct osmocom_ms *ms, struct msgb *msg) new_c_state(cs, GSM322_C6_ANY_CELL_SEL); - cs->mcc = cs->mnc = 0; + memset(&cs->plmn, 0, sizeof(cs->plmn)); /* unset selected cell */ gsm322_unselect_cell(cs); @@ -3325,7 +3281,7 @@ static int gsm322_c_sim_remove(struct osmocom_ms *ms, struct msgb *msg) return gsm322_c_any_cell_sel(ms, msg); } -/* start noraml cell re-selection */ +/* start normal cell re-selection */ static int gsm322_c_normal_cell_resel(struct osmocom_ms *ms, struct msgb *msg) { struct gsm322_cellsel *cs = &ms->cellsel; @@ -3383,11 +3339,10 @@ static int gsm322_c_camp_normally(struct osmocom_ms *ms, struct msgb *msg) struct gsm322_cellsel *cs = &ms->cellsel; struct msgb *nmsg; - LOGP(DSUM, LOGL_INFO, "Camping normally on cell (ARFCN=%s mcc=%s " - "mnc=%s %s, %s)\n", gsm_print_arfcn(cs->sel_arfcn), - gsm_print_mcc(cs->sel_mcc), - gsm_print_mnc(cs->sel_mnc), gsm_get_mcc(cs->sel_mcc), - gsm_get_mnc(cs->sel_mcc, cs->sel_mnc)); + LOGP(DSUM, LOGL_INFO, "Camping normally on cell (ARFCN=%s mcc-mnc=%s %s, %s)\n", gsm_print_arfcn(cs->sel_arfcn), + osmo_plmn_name(&cs->sel_cgi.lai.plmn), + gsm_get_mcc(cs->sel_cgi.lai.plmn.mcc), + gsm_get_mnc(&cs->sel_cgi.lai.plmn)); /* if we did cell reselection, we have a valid last serving cell */ if (cs->state != GSM322_C4_NORMAL_CELL_RESEL) @@ -3410,11 +3365,11 @@ static int gsm322_c_camp_any_cell(struct osmocom_ms *ms, struct msgb *msg) struct gsm322_cellsel *cs = &ms->cellsel; struct msgb *nmsg; - LOGP(DSUM, LOGL_INFO, "Camping on any cell (ARFCN=%s mcc=%s " - "mnc=%s %s, %s)\n", gsm_print_arfcn(cs->sel_arfcn), - gsm_print_mcc(cs->sel_mcc), - gsm_print_mnc(cs->sel_mnc), gsm_get_mcc(cs->sel_mcc), - gsm_get_mnc(cs->sel_mcc, cs->sel_mnc)); + LOGP(DSUM, LOGL_INFO, "Camping on any cell (ARFCN=%s mcc-mnc=%s %s, %s)\n", + gsm_print_arfcn(cs->sel_arfcn), + osmo_plmn_name(&cs->sel_cgi.lai.plmn), + gsm_get_mcc(cs->sel_cgi.lai.plmn.mcc), + gsm_get_mnc(&cs->sel_cgi.lai.plmn)); /* (re-)starting 'any cell selection' timer to look for coverage of * allowed PLMNs. @@ -3423,13 +3378,12 @@ static int gsm322_c_camp_any_cell(struct osmocom_ms *ms, struct msgb *msg) if (ms->subscr.sim_valid && (cs->state != GSM322_C8_ANY_CELL_RESEL || !osmo_timer_pending(&cs->any_timer))) { - struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_plmn *plmn322 = &ms->plmn; stop_any_timer(cs); if (ms->settings.plmn_mode == PLMN_MODE_MANUAL - && (!plmn->mcc - || gsm_subscr_is_forbidden_plmn(&ms->subscr, plmn->mcc, - plmn->mnc))) { + && (!plmn322->plmn.mcc + || gsm_subscr_is_forbidden_plmn(&ms->subscr, &plmn322->plmn))) { LOGP(DCS, LOGL_INFO, "Not starting 'any search' timer, " "because no selected PLMN or forbidden\n"); } else @@ -3456,8 +3410,7 @@ static int gsm322_c_camp_any_cell(struct osmocom_ms *ms, struct msgb *msg) } /* create temporary ba range with given frequency ranges */ -struct gsm322_ba_list *gsm322_cs_ba_range(struct osmocom_ms *ms, - uint32_t *range, uint8_t ranges, uint8_t refer_pcs) +static struct gsm322_ba_list *gsm322_cs_ba_range(struct osmocom_ms *ms, uint32_t *range, uint8_t ranges, bool refer_pcs) { static struct gsm322_ba_list ba; int lower, higher; @@ -3490,7 +3443,7 @@ struct gsm322_ba_list *gsm322_cs_ba_range(struct osmocom_ms *ms, if (lower == higher) break; lower++; - /* wrap arround, only if not PCS */ + /* wrap around, only if not PCS */ if (lower == 1024) lower = 0; } @@ -3520,8 +3473,7 @@ static int gsm322_cs_choose(struct osmocom_ms *ms) if (!ba) { LOGP(DCS, LOGL_INFO, "No BA on sysinfo, try stored " "BA list.\n"); - ba = gsm322_find_ba_list(cs, cs->sel_si.mcc, - cs->sel_si.mnc); + ba = gsm322_find_ba_list(cs, &cs->sel_si.lai.plmn); } } @@ -3622,23 +3574,22 @@ static int gsm322_c_new_plmn(struct osmocom_ms *ms, struct msgb *msg) { struct gsm322_msg *gm = (struct gsm322_msg *) msg->data; struct gsm322_cellsel *cs = &ms->cellsel; - struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_plmn *plmn322 = &ms->plmn; struct gsm322_ba_list *ba; - cs->mcc = plmn->mcc; - cs->mnc = plmn->mnc; + memcpy(&cs->plmn, &plmn322->plmn, sizeof(struct osmo_plmn_id)); if (gm->limited) { LOGP(DCS, LOGL_INFO, "Selected PLMN with limited service.\n"); return gsm322_c_any_cell_sel(ms, msg); } - LOGP(DSUM, LOGL_INFO, "Selecting PLMN (mcc=%s mnc=%s %s, %s)\n", - gsm_print_mcc(cs->mcc), gsm_print_mnc(cs->mnc), - gsm_get_mcc(cs->mcc), gsm_get_mnc(cs->mcc, cs->mnc)); + LOGP(DSUM, LOGL_INFO, "Selecting PLMN (mcc-mnc=%s %s, %s)\n", + osmo_plmn_name(&cs->plmn), + gsm_get_mcc(cs->plmn.mcc), gsm_get_mnc(&cs->plmn)); /* search for BA list */ - ba = gsm322_find_ba_list(cs, plmn->mcc, plmn->mnc); + ba = gsm322_find_ba_list(cs, &plmn322->plmn); if (ba) { LOGP(DCS, LOGL_INFO, "Start stored cell selection.\n"); @@ -3829,7 +3780,8 @@ static int gsm322_a_event(struct osmocom_ms *ms, struct msgb *msg) && ((1 << plmn->state) & plmnastatelist[i].states)) break; if (i == PLMNASLLEN) { - LOGP(DPLMN, LOGL_NOTICE, "Event unhandled at this state.\n"); + LOGP(DPLMN, LOGL_NOTICE, "Event %s unhandled in state %s.\n", + get_event_name(msg_type), get_a_state_name(plmn->state)); return 0; } @@ -4148,13 +4100,13 @@ static int gsm322_nb_check(struct osmocom_ms *ms, int any) } if (ms->rrlayer.monitor) { - vty_notify(ms, "MON: cell ARFCN LAC C1 C2 CRH RLA_C " + l23_vty_ms_notify(ms, "MON: cell ARFCN LAC C1 C2 CRH RLA_C " "bargraph\n"); snprintf(arfcn_text, 10, "%s ", gsm_print_arfcn(cs->sel_arfcn)); arfcn_text[9] = '\0'; - vty_notify(ms, "MON: serving %s 0x%04x %3d %3d %4d " - "%s\n", arfcn_text, cs->sel_lac, cs->c1, cs->c2, + l23_vty_ms_notify(ms, "MON: serving %s 0x%04x %3d %3d %4d " + "%s\n", arfcn_text, cs->sel_cgi.lai.lac, cs->c1, cs->c2, cs->rla_c_dbm, bargraph(cs->rla_c_dbm / 2, -55, -24)); } @@ -4175,7 +4127,7 @@ static int gsm322_nb_check(struct osmocom_ms *ms, int any) snprintf(arfcn_text, 10, "%s ", gsm_print_arfcn(nb->arfcn)); arfcn_text[9] = '\0'; - vty_notify(ms, "MON: nb %2d %s ARFCN not " + l23_vty_ms_notify(ms, "MON: nb %2d %s ARFCN not " "supported\n", i + 1, arfcn_text); } goto cont; @@ -4183,12 +4135,12 @@ static int gsm322_nb_check(struct osmocom_ms *ms, int any) /* check if we have successfully read BCCH */ if (!s || nb->state != GSM322_NB_SYSINFO) { LOGP(DNB, LOGL_INFO, "Skip cell: There are no system " - "informations available.\n"); + "information available.\n"); if (ms->rrlayer.monitor) { snprintf(arfcn_text, 10, "%s ", gsm_print_arfcn(nb->arfcn)); arfcn_text[9] = '\0'; - vty_notify(ms, "MON: nb %2d %s " + l23_vty_ms_notify(ms, "MON: nb %2d %s " " %4d %s\n", i + 1, arfcn_text, nb->rla_c_dbm, bargraph(nb->rla_c_dbm / 2, -55, -24)); @@ -4201,7 +4153,10 @@ static int gsm322_nb_check(struct osmocom_ms *ms, int any) nb->prio_low = 1; /* get C1 & C2 */ - gsm_arfcn2band_rc(nb->arfcn, &band); + if (gsm_arfcn2band_rc(nb->arfcn, &band) != 0) { + LOGP(DNB, LOGL_ERROR, "gsm_arfcn2band_rc() failed\n"); + goto cont; + } class = class_of_band(ms, band); nb->c1 = calculate_c1(DNB, nb->rla_c_dbm, s->rxlev_acc_min_db, ms_pwr_dbm(band, s->ms_txpwr_max_cch), @@ -4214,8 +4169,7 @@ static int gsm322_nb_check(struct osmocom_ms *ms, int any) nb->c12_valid = 1; /* calculate CRH depending on LAI */ - if (cs->sel_mcc == s->mcc && cs->sel_mnc == s->mnc - && cs->sel_lac == s->lac) { + if (osmo_lai_cmp(&cs->sel_cgi.lai, &s->lai) == 0) { LOGP(DNB, LOGL_INFO, "-> Cell of is in the same LA, " "so CRH = 0\n"); nb->crh = 0; @@ -4234,8 +4188,8 @@ static int gsm322_nb_check(struct osmocom_ms *ms, int any) snprintf(arfcn_text, 10, "%s ", gsm_print_arfcn(nb->arfcn)); arfcn_text[9] = '\0'; - vty_notify(ms, "MON: nb %2d %s 0x%04x %3d %3d %2d" - " %4d %s\n", i + 1, arfcn_text, s->lac, + l23_vty_ms_notify(ms, "MON: nb %2d %s 0x%04x %3d %3d %2d" + " %4d %s\n", i + 1, arfcn_text, s->lai.lac, nb->c1, nb->c2, nb->crh, nb->rla_c_dbm, bargraph(nb->rla_c_dbm / 2, -55, -24)); } @@ -4256,18 +4210,17 @@ static int gsm322_nb_check(struct osmocom_ms *ms, int any) } /* check if LA is forbidden */ - if (any && gsm322_is_forbidden_la(ms, s->mcc, s->mnc, s->lac)) { + if (any && gsm322_is_forbidden_la(ms, &s->lai)) { LOGP(DNB, LOGL_INFO, "Skip cell: Cell has " "forbidden LA.\n"); goto cont; } /* check if we have same PLMN */ - if (!any && (cs->sel_mcc != s->mcc || cs->sel_mnc != s->mnc)) { + if (!any && (osmo_plmn_cmp(&cs->sel_cgi.lai.plmn, &s->lai.plmn) != 0)) { LOGP(DNB, LOGL_INFO, "Skip cell: PLMN of cell " - "does not match target PLMN. (cell: mcc=%s " - "mnc=%s)\n", gsm_print_mcc(s->mcc), - gsm_print_mnc(s->mnc)); + "does not match target PLMN. (cell: mcc-mnc=%s)\n", + osmo_plmn_name(&s->lai.plmn)); goto cont; } @@ -4310,7 +4263,7 @@ cont: if (!i) { if (ms->rrlayer.monitor) - vty_notify(ms, "MON: no neighbour cells\n"); + l23_vty_ms_notify(ms, "MON: no neighbour cells\n"); } if (cs->resel_when + GSM58_RESEL_THRESHOLD >= now) { @@ -4356,7 +4309,7 @@ static int gsm322_nb_scan(struct osmocom_ms *ms) nb->c2); /* track which cells have been checked do far */ if (nb->checked_for_resel) { - LOGP(DCS, LOGL_INFO, "Skip cell: alredy tried to " + LOGP(DCS, LOGL_INFO, "Skip cell: already tried to " "select.\n"); goto cont; } @@ -4417,7 +4370,7 @@ no_cell_found: nb->checked_for_resel = 1; /* NOTE: We might already have system information from previous - * scan. But we need recent informations, so we scan again! + * scan. But we need recent information, so we scan again! */ /* Tune to frequency for a while, to receive broadcasts. */ @@ -4451,7 +4404,8 @@ static int gsm322_nb_start(struct osmocom_ms *ms, int synced) uint8_t map[128]; uint16_t nc[32]; uint8_t changed = 0; - int refer_pcs, index; + bool refer_pcs; + int index; uint16_t arfcn; if (cs->ms->settings.no_neighbour) @@ -4523,7 +4477,7 @@ static int gsm322_nb_start(struct osmocom_ms *ms, int synced) if (!changed && cs->nb_meas_set) return 0; - /* start neigbour cell measurement task */ + /* start neighbour cell measurement task */ num = 0; llist_for_each_entry(nb, &cs->nb_list, entry) { if (nb->state == GSM322_NB_NOT_SUP) @@ -4619,7 +4573,7 @@ printf("%d time to sync again: %u\n", nb->arfcn, now + GSM58_READ_AGAIN - nb->wh "reselection.\n"); if (ms->rrlayer.monitor) - vty_notify(ms, "MON: trigger cell re-selection: " + l23_vty_ms_notify(ms, "MON: trigger cell re-selection: " "better cell\n"); cs->resel_when = now; @@ -4701,7 +4655,11 @@ static int gsm322_nb_new_rxlev(struct gsm322_cellsel *cs) enum gsm_band band; int class; - gsm_arfcn2band_rc(cs->arfcn, &band); + if (gsm_arfcn2band_rc(cs->arfcn, &band) != 0) { + LOGP(DNB, LOGL_ERROR, "gsm_arfcn2band_rc() failed\n"); + return -EINVAL; + } + class = class_of_band(cs->ms, band); /* calculate the RAL_C of serving cell */ @@ -4847,9 +4805,9 @@ int gsm322_dump_sorted_plmn(struct osmocom_ms *ms) LOGP(DPLMN, LOGL_INFO, "MCC |MNC |allowed|rx-lev\n"); LOGP(DPLMN, LOGL_INFO, "-------+-------+-------+-------\n"); llist_for_each_entry(temp, &plmn->sorted_plmn, entry) { - LOGP(DPLMN, LOGL_INFO, "%s |%s%s |%s |%s\n", - gsm_print_mcc(temp->mcc), gsm_print_mnc(temp->mnc), - ((temp->mnc & 0x00f) == 0x00f) ? " ":"", + LOGP(DPLMN, LOGL_INFO, "%s |%-3s |%s |%s\n", + osmo_mcc_name(temp->plmn.mcc), + osmo_mnc_name(temp->plmn.mnc, temp->plmn.mnc_3_digits), (temp->cause) ? "no ":"yes", gsm_print_rxlev(temp->rxlev)); } @@ -4877,11 +4835,11 @@ int gsm322_dump_cs_list(struct gsm322_cellsel *cs, uint8_t flags, print(priv, "%4dDCS|", i); else print(priv, "%4d |", i); - if (s->mcc) { - print(priv, "%s |%s%s |", gsm_print_mcc(s->mcc), - gsm_print_mnc(s->mnc), - ((s->mnc & 0x00f) == 0x00f) ? " ":""); - print(priv, "0x%04x |0x%04x |", s->lac, s->cell_id); + if (s->lai.plmn.mcc) { + print(priv, "%s |%-3s |", + osmo_mcc_name(s->lai.plmn.mcc), + osmo_mnc_name(s->lai.plmn.mnc, s->lai.plmn.mnc_3_digits)); + print(priv, "0x%04x |0x%04x |", s->lai.lac, s->cell_id); } else print(priv, "n/a |n/a |n/a |n/a |"); if ((cs->list[i].flags & GSM322_CS_FLAG_SYSINFO)) { @@ -4920,27 +4878,27 @@ int gsm322_dump_forbidden_la(struct osmocom_ms *ms, print(priv, "MCC |MNC |LAC |cause\n"); print(priv, "-------+-------+-------+-------\n"); llist_for_each_entry(temp, &plmn->forbidden_la, entry) - print(priv, "%s |%s%s |0x%04x |#%d\n", - gsm_print_mcc(temp->mcc), gsm_print_mnc(temp->mnc), - ((temp->mnc & 0x00f) == 0x00f) ? " ":"", - temp->lac, temp->cause); + print(priv, "%s |%-3s |0x%04x |#%d\n", + osmo_mcc_name(temp->lai.plmn.mcc), + osmo_mnc_name(temp->lai.plmn.mnc, temp->lai.plmn.mnc_3_digits), + temp->lai.lac, temp->cause); return 0; } -int gsm322_dump_ba_list(struct gsm322_cellsel *cs, uint16_t mcc, uint16_t mnc, +int gsm322_dump_ba_list(struct gsm322_cellsel *cs, const struct osmo_plmn_id *plmn, void (*print)(void *, const char *, ...), void *priv) { struct gsm322_ba_list *ba; int i; llist_for_each_entry(ba, &cs->ba_list, entry) { - if (mcc && mnc && (mcc != ba->mcc || mnc != ba->mnc)) + if (plmn && (osmo_plmn_cmp(&ba->plmn, plmn) != 0)) continue; - print(priv, "Band Allocation of network: MCC %s MNC %s " - "(%s, %s)\n", gsm_print_mcc(ba->mcc), - gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc), - gsm_get_mnc(ba->mcc, ba->mnc)); + print(priv, "Band Allocation of network: MCC-MNC %s (%s, %s)\n", + osmo_plmn_name(&ba->plmn), + gsm_get_mcc(ba->plmn.mcc), + gsm_get_mnc(&ba->plmn)); for (i = 0; i <= 1023+299; i++) { if ((ba->freq[i >> 3] & (1 << (i & 7)))) print(priv, " %s", @@ -4970,7 +4928,7 @@ int gsm322_dump_nb_list(struct gsm322_cellsel *cs, print(priv, "C1=%d C2=%d ", cs->c1, cs->c1); else print(priv, "C1 - C2 - "); - print(priv, "LAC=0x%04x\n\n", (cs->selected) ? cs->sel_si.lac : 0); + print(priv, "LAC=0x%04x\n\n", (cs->selected) ? cs->sel_si.lai.lac : 0); print(priv, "Neighbour cells:\n\n"); llist_for_each_entry(nb, &cs->nb_list, entry) { @@ -5013,7 +4971,7 @@ int gsm322_dump_nb_list(struct gsm322_cellsel *cs, s = cs->list[arfcn2index(nb->arfcn)].sysinfo; if (nb->state == GSM322_NB_SYSINFO && s) { print(priv, "%s |0x%04x |0x%04x |", - (nb->prio_low) ? "low ":"normal", s->lac, + (nb->prio_low) ? "low ":"normal", s->lai.lac, s->cell_id); } else print(priv, "- |- |- |"); @@ -5080,10 +5038,11 @@ int gsm322_init(struct osmocom_ms *ms) s_rc = fgets(version, sizeof(version), fp); version[sizeof(version) - 1] = '\0'; if (!s_rc || !!strcmp(ba_version, version)) { - LOGP(DCS, LOGL_NOTICE, "BA version missmatch, " + LOGP(DCS, LOGL_NOTICE, "BA version mismatch, " "stored BA list becomes obsolete.\n"); } else while(!feof(fp)) { + uint16_t mcc_hex, mnc_hex; ba = talloc_zero(ms, struct gsm322_ba_list); if (!ba) { fclose(fp); @@ -5094,18 +5053,30 @@ int gsm322_init(struct osmocom_ms *ms) talloc_free(ba); break; } - ba->mcc = (buf[0] << 8) | buf[1]; - ba->mnc = (buf[2] << 8) | buf[3]; + mcc_hex = (buf[0] << 8) | buf[1]; + mnc_hex = (buf[2] << 8) | buf[3]; + ba->plmn.mcc = (((mcc_hex & 0x0f00) >> 8) * 100) + + (((mcc_hex & 0x00f0) >> 4) * 10) + + (mcc_hex & 0x000f); + ba->plmn.mnc_3_digits = ((mnc_hex & 0x00f) != 0x00f); + if (ba->plmn.mnc_3_digits) + ba->plmn.mnc = (((mnc_hex & 0x0f00) >> 8) * 100) + + (((mnc_hex & 0x00f0) >> 4) * 10) + + (mnc_hex & 0x000f); + else + ba->plmn.mnc = (((mnc_hex & 0x0f00) >> 8) * 10) + + (((mnc_hex & 0x00f0) >> 4)); + rc = fread(ba->freq, sizeof(ba->freq), 1, fp); if (!rc) { talloc_free(ba); break; } llist_add_tail(&ba->entry, &cs->ba_list); - LOGP(DCS, LOGL_INFO, "Read stored BA list (mcc=%s " - "mnc=%s %s, %s)\n", gsm_print_mcc(ba->mcc), - gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc), - gsm_get_mnc(ba->mcc, ba->mnc)); + LOGP(DCS, LOGL_INFO, "Read stored BA list (mcc-mnc=%s %s, %s)\n", + osmo_plmn_name(&ba->plmn), + gsm_get_mcc(ba->plmn.mcc), + gsm_get_mnc(&ba->plmn)); } fclose(fp); } else @@ -5114,17 +5085,63 @@ int gsm322_init(struct osmocom_ms *ms) return 0; } +static void gsm322_write_ba(struct osmocom_ms *ms) +{ + const struct gsm322_ba_list *ba; + char *ba_filename; + FILE *fp; + + ba_filename = talloc_asprintf(ms, "%s/%s.ba", config_dir, ms->name); + OSMO_ASSERT(ba_filename != NULL); + + LOGP(DCS, LOGL_INFO, "Writing stored BA list to '%s'\n", ba_filename); + + fp = fopen(ba_filename, "w"); + talloc_free(ba_filename); + if (fp == NULL) { + LOGP(DCS, LOGL_ERROR, + "Failed to open '%s' for writing: %s\n", + ba_filename, strerror(errno)); + return; + } + + fputs(ba_version, fp); + + llist_for_each_entry(ba, &ms->cellsel.ba_list, entry) { + size_t rc = 0; + uint16_t mcc_hex = gsm_mcc_to_hex(ba->plmn.mcc); + uint16_t mnc_hex = gsm_mnc_to_hex(ba->plmn.mnc, ba->plmn.mnc_3_digits); + uint8_t buf[] = { + mcc_hex >> 8, mcc_hex & 0xff, + mnc_hex >> 8, mnc_hex & 0xff, + }; + + LOGP(DCS, LOGL_INFO, + "Writing stored BA list entry (mcc-mnc=%s %s, %s)\n", + osmo_plmn_name(&ba->plmn), + gsm_get_mcc(ba->plmn.mcc), + gsm_get_mnc(&ba->plmn)); + + rc += fwrite(buf, sizeof(buf), 1, fp); + rc += fwrite(ba->freq, sizeof(ba->freq), 1, fp); + + /* fwrite() returns count of written items, should be 2 */ + if (rc != 2) { + LOGP(DCS, LOGL_ERROR, + "Writing stored BA list: fwrite() failed (rc=%zu)\n", rc); + break; + } + } + + fclose(fp); +} + int gsm322_exit(struct osmocom_ms *ms) { struct gsm322_plmn *plmn = &ms->plmn; struct gsm322_cellsel *cs = &ms->cellsel; struct llist_head *lh, *lh2; struct msgb *msg; - FILE *fp; - char *ba_filename; - struct gsm322_ba_list *ba; - uint8_t buf[4]; - int rc = 0; int i; LOGP(DPLMN, LOGL_INFO, "exit PLMN process\n"); @@ -5145,38 +5162,14 @@ int gsm322_exit(struct osmocom_ms *ms) gsm_print_arfcn(index2arfcn(i))); talloc_free(cs->list[i].sysinfo); cs->list[i].sysinfo = NULL; + cs->si = NULL; } cs->list[i].flags = 0; } cs->si = NULL; /* store BA list */ - ba_filename = talloc_asprintf(ms, "%s/%s.ba", config_dir, ms->name); - if (ba_filename) { - fp = fopen(ba_filename, "w"); - talloc_free(ba_filename); - if (fp) { - fputs(ba_version, fp); - llist_for_each_entry(ba, &cs->ba_list, entry) { - buf[0] = ba->mcc >> 8; - buf[1] = ba->mcc & 0xff; - buf[2] = ba->mnc >> 8; - buf[3] = ba->mnc & 0xff; - - LOGP(DCS, LOGL_INFO, "Write stored BA list (mcc=%s " - "mnc=%s %s, %s)\n", gsm_print_mcc(ba->mcc), - gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc), - gsm_get_mnc(ba->mcc, ba->mnc)); - - rc += fwrite(buf, 4, 1, fp); - rc += fwrite(ba->freq, sizeof(ba->freq), 1, fp); - } - fclose(fp); - } - } - - if (rc != 2) - LOGP(DCS, LOGL_ERROR, "Failed to write BA list\n"); + gsm322_write_ba(ms); /* free lists */ while ((msg = msgb_dequeue(&plmn->event_queue))) diff --git a/src/host/layer23/src/mobile/gsm411_sms.c b/src/host/layer23/src/mobile/gsm411_sms.c index 45decfd9..a21133b9 100644 --- a/src/host/layer23/src/mobile/gsm411_sms.c +++ b/src/host/layer23/src/mobile/gsm411_sms.c @@ -19,10 +19,6 @@ * 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 <stdint.h> @@ -34,9 +30,11 @@ #include <osmocom/core/msgb.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/mobile/mncc.h> #include <osmocom/bb/mobile/transaction.h> #include <osmocom/bb/mobile/gsm411_sms.h> +#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h> #include <osmocom/gsm/gsm0411_utils.h> #include <osmocom/core/talloc.h> #include <osmocom/bb/mobile/vty.h> @@ -123,11 +121,11 @@ struct gsm_sms *sms_from_text(const char *receiver, int dcs, const char *text) static int gsm411_sms_report(struct osmocom_ms *ms, struct gsm_sms *sms, uint8_t cause) { - vty_notify(ms, NULL); + l23_vty_ms_notify(ms, NULL); if (!cause) - vty_notify(ms, "SMS to %s successfull\n", sms->address); + l23_vty_ms_notify(ms, "SMS to %s successful\n", sms->address); else - vty_notify(ms, "SMS to %s failed: %s\n", sms->address, + l23_vty_ms_notify(ms, "SMS to %s failed: %s\n", sms->address, get_value_string(gsm411_rp_cause_strs, cause)); mobile_prim_ntfy_sms_status(ms, sms, cause); @@ -193,8 +191,8 @@ static int sms_store(struct osmocom_ms *ms, struct msgb *msg, if (*p == '\n' || *p == '\r') *p = ' '; } - vty_notify(ms, NULL); - vty_notify(ms, "SMS from %s: '%s'\n", gsms->address, vty_text); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "SMS from %s: '%s'\n", gsms->address, vty_text); home = getenv("HOME"); if (!home) { @@ -654,6 +652,14 @@ int gsm411_tx_sms_submit(struct osmocom_ms *ms, const char *sms_sca, return -EIO; } + /* ASCI call does not allow other transactions */ + if (trans_find_ongoing_gcc_bcc(ms)) { + LOGP(DLSMS, LOGL_ERROR, "Phone is busy doing ASCI call\n"); + gsm411_sms_report(ms, sms, GSM411_RP_CAUSE_MO_TEMP_FAIL); + sms_free(sms); + return -EIO; + } + /* allocate transaction with dummy reference */ transaction_id = trans_assign_trans_id(ms, GSM48_PDISC_SMS, 0); if (transaction_id < 0) { @@ -917,7 +923,7 @@ int gsm411_rcv_sms(struct osmocom_ms *ms, struct msgb *msg) struct gsm_trans *trans; int rc = 0; - trans = trans_find_by_callref(ms, mmh->ref); + trans = trans_find_by_callref(ms, GSM48_PDISC_SMS, mmh->ref); if (!trans) { LOGP(DLSMS, LOGL_INFO, " -> (new transaction sapi=%d)\n", sapi); trans = trans_alloc(ms, GSM48_PDISC_SMS, mmh->transaction_id, diff --git a/src/host/layer23/src/mobile/gsm414.c b/src/host/layer23/src/mobile/gsm414.c new file mode 100644 index 00000000..90fc2dfd --- /dev/null +++ b/src/host/layer23/src/mobile/gsm414.c @@ -0,0 +1,218 @@ +/* + * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * 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. + * + */ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> + +#include <osmocom/gsm/gsm48.h> +#include <osmocom/gsm/rsl.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_04_14.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/mobile/gsm48_rr.h> + +#include <l1ctl_proto.h> + +int gsm48_rr_tx_rr_status(struct osmocom_ms *ms, uint8_t cause); +int gsm48_send_rsl(struct osmocom_ms *ms, uint8_t msg_type, + struct msgb *msg, uint8_t link_id); +struct msgb *gsm48_l3_msgb_alloc(void); + +#define loop_mode_name(mode) \ + get_value_string(loop_mode_names, mode) + +static const struct value_string loop_mode_names[] = { + { L1CTL_TCH_LOOP_OPEN, "(OPEN)" }, + { L1CTL_TCH_LOOP_A, "A" }, + { L1CTL_TCH_LOOP_B, "B" }, + { L1CTL_TCH_LOOP_C, "C" }, + { L1CTL_TCH_LOOP_D, "D" }, + { L1CTL_TCH_LOOP_E, "E" }, + { L1CTL_TCH_LOOP_F, "F" }, + { L1CTL_TCH_LOOP_I, "I" }, + { 0, NULL } +}; + +static struct msgb *alloc_gsm414_msg(uint8_t msg_type) +{ + struct gsm48_hdr *ngh; + struct msgb *nmsg; + + nmsg = gsm48_l3_msgb_alloc(); + if (nmsg == NULL) + return NULL; + + ngh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*ngh)); + ngh->proto_discr = GSM48_PDISC_TEST; + ngh->msg_type = msg_type; + + return nmsg; +} + +static int handle_close_tch_loop(struct osmocom_ms *ms, const struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + const struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int msg_len = msgb_l3len(msg); + struct msgb *nmsg; + + /* Make sure that we have an active connection */ + if (rr->state != GSM48_RR_ST_DEDICATED) { + LOGP(DMM, LOGL_NOTICE, "TCH loop requires an active connection\n"); + gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_MSG_TYPE_N_COMPAT); + return -EINVAL; + } + + /* Make sure that the established channel is either TCH/F or TCH/H */ + if ((rr->cd_now.chan_nr & 0xf8) != RSL_CHAN_Bm_ACCHs + && (rr->cd_now.chan_nr & 0xf0) != RSL_CHAN_Lm_ACCHs) { + LOGP(DMM, LOGL_NOTICE, "TCH loop requires a TCH/F or TCH/H connection\n"); + gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_MSG_TYPE_N_COMPAT); + return -EINVAL; + } + + /* Check if a loop is already closed */ + if (rr->tch_loop_mode != L1CTL_TCH_LOOP_OPEN) { + LOGP(DMM, LOGL_NOTICE, "TCH loop has already been closed\n"); + gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_MSG_TYPE_N_COMPAT); + return -EINVAL; + } + + if ((msg_len - sizeof(*gh)) < 1) + return -EINVAL; + + /* Parse type of the TCH test loop, convert to L1CTL format */ + uint8_t gsm414_loop_mode = (gh->data[0] >> 1) & 0x1f; + + /* NOTE: some bits are not specified, so they can be 0 or 1 */ + if (gsm414_loop_mode == GSM414_LOOP_A) + rr->tch_loop_mode = L1CTL_TCH_LOOP_A; + else if (gsm414_loop_mode == GSM414_LOOP_B) + rr->tch_loop_mode = L1CTL_TCH_LOOP_B; + else if ((gsm414_loop_mode & 0x1e) == GSM414_LOOP_C) + rr->tch_loop_mode = L1CTL_TCH_LOOP_C; + else if ((gsm414_loop_mode & 0x1c) == GSM414_LOOP_D) + rr->tch_loop_mode = L1CTL_TCH_LOOP_D; + else if ((gsm414_loop_mode & 0x1c) == GSM414_LOOP_E) + rr->tch_loop_mode = L1CTL_TCH_LOOP_E; + else if ((gsm414_loop_mode & 0x1c) == GSM414_LOOP_F) + rr->tch_loop_mode = L1CTL_TCH_LOOP_F; + else if ((gsm414_loop_mode & 0x1c) == GSM414_LOOP_I) + rr->tch_loop_mode = L1CTL_TCH_LOOP_I; + else { + LOGP(DMM, LOGL_NOTICE, "Unhandled 3GPP TS 44.014 TCH loop " + "mode=0x%02x => rejecting\n", gsm414_loop_mode); + gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_MSG_TYPE_N); + return -ENOTSUP; + } + + LOGP(DMM, LOGL_NOTICE, "(%s) Closing 3GPP TS 44.014 TCH loop mode '%s'\n", + rsl_chan_nr_str(rr->cd_now.chan_nr), loop_mode_name(rr->tch_loop_mode)); + + /* Instruct the L1 to enable received TCH loopback mode + * FIXME: delay applying this mode, so we can send the ACK first */ + l1ctl_tx_tch_mode_req(ms, rr->cd_now.mode, rr->audio_mode, rr->cd_now.tch_flags, rr->tch_loop_mode); + + /* Craft and send the ACKnowledgement */ + nmsg = alloc_gsm414_msg(GSM414_MT_CLOSE_TCH_LOOP_ACK); + if (nmsg == NULL) + return -ENOMEM; + + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg, 0); +} + +static int handle_open_tch_loop(struct osmocom_ms *ms, const struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *nmsg; + + /* Make sure that we have an active connection */ + if (rr->state != GSM48_RR_ST_DEDICATED) { + LOGP(DMM, LOGL_NOTICE, "TCH loop requires an active connection\n"); + gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_MSG_TYPE_N_COMPAT); + return -EINVAL; + } + + /* Make sure that the established channel is either TCH/F or TCH/H */ + if ((rr->cd_now.chan_nr & 0xf8) != RSL_CHAN_Bm_ACCHs + && (rr->cd_now.chan_nr & 0xf0) != RSL_CHAN_Lm_ACCHs) { + LOGP(DMM, LOGL_NOTICE, "TCH loop requires a TCH/F or TCH/H connection\n"); + gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_MSG_TYPE_N_COMPAT); + return -EINVAL; + } + + /* Check if a loop actually needs to be closed */ + if (rr->tch_loop_mode == L1CTL_TCH_LOOP_OPEN) { + LOGP(DMM, LOGL_NOTICE, "TCH loop has not been closed (already open)\n"); + gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_MSG_TYPE_N_COMPAT); + return -EINVAL; + } + + LOGP(DMM, LOGL_NOTICE, "(%s) Opening 3GPP TS 44.014 TCH loop mode '%s'\n", + rsl_chan_nr_str(rr->cd_now.chan_nr), loop_mode_name(rr->tch_loop_mode)); + + /* Instruct the L1 to disable the TCH loopback mode */ + l1ctl_tx_tch_mode_req(ms, rr->cd_now.mode, rr->audio_mode, rr->cd_now.tch_flags, L1CTL_TCH_LOOP_OPEN); + + /* Only the loop mode C needs to be ACKnowledged */ + bool needs_ack = rr->tch_loop_mode == L1CTL_TCH_LOOP_C; + rr->tch_loop_mode = L1CTL_TCH_LOOP_OPEN; + if (!needs_ack) + return 0; + + /* Craft and send the ACKnowledgement */ + nmsg = alloc_gsm414_msg(GSM414_MT_OPEN_LOOP_CMD); + if (nmsg == NULL) + return -ENOMEM; + + msgb_put_u8(nmsg, GSM414_OPEN_LOOP_ACK_IE); + + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg, 0); +} + +int gsm414_rcv_test(struct osmocom_ms *ms, const struct msgb *msg) +{ + const struct gsm48_hdr *gh = msgb_l3(msg); + + LOGP(DMM, LOGL_INFO, "Received 3GPP TS 44.014 message '%s' (0x%02x)\n", + get_value_string(gsm414_msgt_names, gh->msg_type), gh->msg_type); + + /* TODO: check if the test SIM (special EF.ADM) is inserted */ + switch (gh->msg_type) { + case GSM414_MT_CLOSE_TCH_LOOP_CMD: + return handle_close_tch_loop(ms, msg); + case GSM414_MT_OPEN_LOOP_CMD: + return handle_open_tch_loop(ms, msg); + default: + LOGP(DMM, LOGL_NOTICE, "Unhandled 3GPP TS 44.014 message '%s' (0x%02x)\n", + get_value_string(gsm414_msgt_names, gh->msg_type), gh->msg_type); + gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_MSG_TYPE_N); + return -ENOTSUP; + } +} diff --git a/src/host/layer23/src/mobile/gsm44068_gcc_bcc.c b/src/host/layer23/src/mobile/gsm44068_gcc_bcc.c new file mode 100644 index 00000000..3a4db3a6 --- /dev/null +++ b/src/host/layer23/src/mobile/gsm44068_gcc_bcc.c @@ -0,0 +1,1967 @@ +/* Handle VGCS/VBCS calls. (Voice Group/Broadcast Call Service). */ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * Author: Andreas Eversberg + * + * 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/>. + */ + +/* Notes on the state machine: + * + * The state machine is different from the diagram depicted in the specs. + * This is because there are some messages missing and some state transitions + * are different or not shown. + * + * A call that has no channel is answered without joining the group channel. + * If it comes available, the establishment is performed and the U4 is entered. + * + * Uplink control is not described in the diagram. Talking/listening is + * requested by user and can be rejected by MM layer, if talking is not + * allowed. + * + * We can be sure that there is no other MM connection while doing VGCS call + * establishment: MMxx-EST-REQ is only accpepted, if there is no MM connection. + * We block calls from user, if there is some other transaction, which is not + * in state U3. Also we accept incoming indications any time and create + * transactions that go to state U3. + * + * If the upper layer or lower layer requests another call/SMS/SS while VGCS + * call is ongoing, this may cause undefined behaviour. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/gsm/protocol/gsm_44_068.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/fsm.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/transaction.h> +#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h> +#include <osmocom/bb/mobile/tch.h> +#include <osmocom/bb/mobile/vty.h> +#include <l1ctl_proto.h> + +#define S(x) (1 << (x)) + +#define LOG_GCC(trans, level, fmt, args...) \ + LOGP(((trans)->protocol == GSM48_PDISC_GROUP_CC) ? DGCC : DBCC, level, \ + ((trans)->protocol == GSM48_PDISC_GROUP_CC) ? ("VGCS callref %u: " fmt) : ("VBS callref %u: " fmt), \ + (trans)->callref, ##args) +#define LOG_GCC_PR(protocol, ref, level, fmt, args...) \ + LOGP((protocol == GSM48_PDISC_GROUP_CC) ? DGCC : DBCC, level, \ + (protocol == GSM48_PDISC_GROUP_CC) ? ("VGCS callref %u: " fmt) : ("VBS callref %u: " fmt), \ + ref, ##args) + +/* + * init + */ + +int gsm44068_gcc_init(struct osmocom_ms *ms) +{ + LOGP(DGCC, LOGL_INFO, "init GCC/BCC\n"); + + return 0; +} + +int gsm44068_gcc_exit(struct osmocom_ms *ms) +{ + struct gsm_trans *trans, *trans2; + + LOGP(DGCC, LOGL_INFO, "exit GCC/BCC processes for %s\n", ms->name); + + llist_for_each_entry_safe(trans, trans2, &ms->trans_list, entry) { + if (trans->protocol == GSM48_PDISC_GROUP_CC || trans->protocol == GSM48_PDISC_BCAST_CC) { + LOG_GCC(trans, LOGL_NOTICE, "Free pendig CC-transaction.\n"); + trans_free(trans); + } + } + + return 0; +} + + +/* + * messages + */ + +/* TS 44.068 Chapter 6.1.2.1 */ +enum vgcs_gcc_fsm_states { + VGCS_GCC_ST_U0_NULL = 0, + VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING, + VGCS_GCC_ST_U1_GROUP_CALL_INITIATED, + VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE, /* sepeate link */ + VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE, /* wait for receive mode */ + VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, /* receive mode / U6 @ BCC */ + VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE, /* wait for send and receive mode */ + VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE, /* send and receive mode */ + VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE, /* no channel */ + VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, + VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST, + VGCS_GCC_ST_U5_TERMINATION_REQUESTED, +}; + +/* TS 44.068 Figure 6.1 (additional events added) */ +enum vgcs_gcc_fsm_event { + VGCS_GCC_EV_SETUP_REQ, /* calling user initiates call */ + VGCS_GCC_EV_TERM_REQ, /* calling user requests termination */ + VGCS_GCC_EV_MM_EST_CNF, /* MM connection established */ + VGCS_GCC_EV_MM_EST_REJ, /* MM connection failed */ + VGCS_GCC_EV_DI_TERMINATION, /* network acknowledges termination */ + VGCS_GCC_EV_DI_TERM_REJECT, /* network rejects termination */ + VGCS_GCC_EV_DI_CONNECT, /* network indicates connect */ + VGCS_GCC_EV_TIMEOUT, /* several timeout events */ + VGCS_GCC_EV_SETUP_IND, /* notification of ongoing call received */ + VGCS_GCC_EV_REL_IND, /* notification of call being gone */ + VGCS_GCC_EV_JOIN_GC_REQ, /* user wants to join ongoing call */ + VGCS_GCC_EV_JOIN_GC_CNF, /* MM confirms joining ongoing call */ + VGCS_GCC_EV_ABORT_REQ, /* user rejects or leaves call */ + VGCS_GCC_EV_ABORT_IND, /* MM indicates channel released or failed */ + VGCS_GCC_EV_TALK_REQ, /* user wants to talk */ + VGCS_GCC_EV_TALK_CNF, /* MM confirms talk */ + VGCS_GCC_EV_TALK_REJ, /* MM rejects talk */ + VGCS_GCC_EV_LISTEN_REQ, /* user wants to listen */ + VGCS_GCC_EV_LISTEN_CNF, /* MM confirms listen */ + VGCS_GCC_EV_MM_IDLE, /* MM layer becomes ready for new channel */ + VGCS_GCC_EV_UPLINK_FREE, /* MM layer indicates free uplink in group receive mode */ + VGCS_GCC_EV_UPLINK_BUSY, /* MM layer indicates busy uplink in group receive mode */ +}; + +static const struct value_string vgcs_gcc_fsm_event_names[] = { + OSMO_VALUE_STRING(VGCS_GCC_EV_SETUP_REQ), + OSMO_VALUE_STRING(VGCS_GCC_EV_TERM_REQ), + OSMO_VALUE_STRING(VGCS_GCC_EV_MM_EST_CNF), + OSMO_VALUE_STRING(VGCS_GCC_EV_MM_EST_REJ), + OSMO_VALUE_STRING(VGCS_GCC_EV_DI_TERMINATION), + OSMO_VALUE_STRING(VGCS_GCC_EV_DI_TERM_REJECT), + OSMO_VALUE_STRING(VGCS_GCC_EV_DI_CONNECT), + OSMO_VALUE_STRING(VGCS_GCC_EV_TIMEOUT), + OSMO_VALUE_STRING(VGCS_GCC_EV_SETUP_IND), + OSMO_VALUE_STRING(VGCS_GCC_EV_REL_IND), + OSMO_VALUE_STRING(VGCS_GCC_EV_JOIN_GC_REQ), + OSMO_VALUE_STRING(VGCS_GCC_EV_JOIN_GC_CNF), + OSMO_VALUE_STRING(VGCS_GCC_EV_ABORT_REQ), + OSMO_VALUE_STRING(VGCS_GCC_EV_ABORT_IND), + OSMO_VALUE_STRING(VGCS_GCC_EV_TALK_REQ), + OSMO_VALUE_STRING(VGCS_GCC_EV_TALK_CNF), + OSMO_VALUE_STRING(VGCS_GCC_EV_TALK_REJ), + OSMO_VALUE_STRING(VGCS_GCC_EV_LISTEN_REQ), + OSMO_VALUE_STRING(VGCS_GCC_EV_LISTEN_CNF), + OSMO_VALUE_STRING(VGCS_GCC_EV_MM_IDLE), + OSMO_VALUE_STRING(VGCS_GCC_EV_UPLINK_FREE), + OSMO_VALUE_STRING(VGCS_GCC_EV_UPLINK_BUSY), + { } +}; + +/*! return string representation of GCC/BCC Message Type */ +static const char *gsm44068_gcc_msg_name(uint8_t msg_type) +{ + return get_value_string(osmo_gsm44068_msg_type_names, msg_type); +} + +#define TFU(param) ((param < 0) ? "unchanged" : ((param) ? "T" : "F")) + +/* Set state attributes and check if they are consistent with the current state. */ +static int set_state_attributes(struct gsm_trans *trans, int d_att, int u_att, int comm, int orig, int call_state) +{ + bool orig_t = false, comm_t = false; + + LOG_GCC(trans, LOGL_DEBUG, "Setting state attributes: D-ATT = %s, U-ATT = %s, COMM = %s, ORIG = %s.\n", + TFU(d_att), TFU(u_att), TFU(comm), TFU(orig)); + + /* Control Speaker. */ + if (d_att >= 0 && trans->gcc.d_att != d_att) { + LOG_GCC(trans, LOGL_DEBUG, "Switching Speaker to %d\n", d_att); + gsm48_rr_audio_mode(trans->ms, AUDIO_TX_MICROPHONE | (d_att * AUDIO_RX_SPEAKER)); + } + + if (d_att >= 0) + trans->gcc.d_att = d_att; + if (u_att >= 0) + trans->gcc.u_att = u_att; + if (comm >= 0) + trans->gcc.comm = comm; + if (orig >= 0) + trans->gcc.orig = orig; + if (call_state >= 0) + trans->gcc.call_state = call_state; + + switch (trans->gcc.fi->state) { + case VGCS_GCC_ST_U3_GROUP_CALL_PRESENT: + case VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST: + orig_t = orig; + comm_t = comm; + break; + case VGCS_GCC_ST_U0_NULL: + case VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE: + case VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE: + comm_t = comm; + break; + } + + if (orig_t) + LOG_GCC(trans, LOGL_ERROR, "ORIG = T is inconsistent with states U3 and U4. Please fix!"); + + if (comm_t) + LOG_GCC(trans, LOGL_ERROR, + "COMM = T is inconsistent with states U0, U3, U4, U2nc and U2r. Please fix!"); + + return (orig_t || comm_t) ? -EINVAL : 0; +} + +static void vgcs_vty_notify(struct gsm_trans *trans, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); + +static void vgcs_vty_notify(struct gsm_trans *trans, const char *fmt, ...) +{ + struct osmocom_ms *ms = trans->ms; + char buffer[1000]; + va_list args; + + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer) - 1, fmt, args); + buffer[sizeof(buffer) - 1] = '\0'; + va_end(args); + + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "%s call %d: %s", (trans->protocol == GSM48_PDISC_GROUP_CC) ? "Group" : "Broadcast", + trans->callref, buffer); +} + +/* + * messages + */ + +/* Send MMxx-GROUP-REQ to MM. */ +static int vgcs_group_req(struct gsm_trans *trans) +{ + struct msgb *nmsg; + struct gsm48_mmxx_hdr *nmmh; + uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_GROUP_REQ : GSM48_MMBCC_GROUP_REQ; + + LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type)); + + OSMO_ASSERT(trans->gcc.ch_desc_present); + + nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + nmmh = (struct gsm48_mmxx_hdr *) nmsg->data; + nmmh->ch_desc_present = trans->gcc.ch_desc_present; + memcpy(&nmmh->ch_desc, &trans->gcc.ch_desc, sizeof(nmmh->ch_desc)); + + return gsm48_mmxx_downmsg(trans->ms, nmsg); +} + +/* Send MMxx-EST-REQ to MM. */ +static int vgcs_est_req(struct gsm_trans *trans) +{ + struct msgb *nmsg; + uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_EST_REQ : GSM48_MMBCC_EST_REQ; + + LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type)); + + nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + + return gsm48_mmxx_downmsg(trans->ms, nmsg); +} + +/* Push message and send MMxx-DATA-REQ to MM. */ +static int vgcs_data_req(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_mmxx_hdr *mmh; + uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_DATA_REQ : GSM48_MMBCC_DATA_REQ; + + /* push RR header */ + msg->l3h = msg->data; + msgb_push(msg, sizeof(struct gsm48_mmxx_hdr)); + mmh = (struct gsm48_mmxx_hdr *)msg->data; + mmh->msg_type = msg_type; + mmh->ref = trans->callref; + mmh->transaction_id = trans->transaction_id; + mmh->sapi = 0; + + /* send message to MM */ + return gsm48_mmxx_downmsg(trans->ms, msg); +} + +/* Send MMxx-REL-REQ to MM. */ +static int vgcs_rel_req(struct gsm_trans *trans) +{ + struct msgb *nmsg; + uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_REL_REQ : GSM48_MMBCC_REL_REQ; + + LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type)); + + nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + + return gsm48_mmxx_downmsg(trans->ms, nmsg); +} + +/* Send MMxx-UPLINK-REQ to MM. */ +static int vgcs_uplink_req(struct gsm_trans *trans) +{ + struct msgb *nmsg; + uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_UPLINK_REQ : GSM48_MMBCC_UPLINK_REQ; + + LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type)); + + nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + + return gsm48_mmxx_downmsg(trans->ms, nmsg); +} + +/* Send MMxx-UPLINK-REL-REQ to MM. */ +static int vgcs_uplink_rel_req(struct gsm_trans *trans) +{ + struct msgb *nmsg; + uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_UPLINK_REL_REQ + : GSM48_MMBCC_UPLINK_REL_REQ; + + LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type)); + + nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + + return gsm48_mmxx_downmsg(trans->ms, nmsg); +} + +static void _add_callref_ie(struct msgb *msg, uint32_t callref, bool with_prio, uint8_t prio) +{ + uint32_t ie; + + ie = callref << 5; + if (with_prio) + ie |= 0x10 | (prio << 1); + msgb_put_u32(msg, ie); +} + +static void _add_user_user_ie(struct msgb *msg, uint8_t user_pdisc, uint8_t *user, uint8_t user_len) +{ + uint8_t *ie; + + ie = msgb_put(msg, user_len + 2); + *ie++ = GSM48_IE_USER_USER; + *ie++ = user_len; + memcpy(ie, user, user_len); +} + +#define GSM44068_IE_CALL_STATE 0xA0 +#define GSM44068_IE_STATE_ATTRS 0xB0 +#define GSM44068_IE_TALKER_PRIO 0xc0 + +static void _add_call_state_ie(struct msgb *msg, uint8_t call_state) +{ + msgb_put_u8(msg, GSM44068_IE_CALL_STATE | call_state); +} + +static void _add_state_attrs_ie(struct msgb *msg, uint8_t da, uint8_t ua, uint8_t comm, uint8_t oi) +{ + msgb_put_u8(msg, GSM44068_IE_STATE_ATTRS | (da << 3) | (ua << 2) | (comm << 1) | oi); +} + +static void _add_talker_prio_ie(struct msgb *msg, uint8_t talker_prio) +{ + msgb_put_u8(msg, GSM44068_IE_TALKER_PRIO | talker_prio); +} + +static void _add_cause_ie(struct msgb *msg, uint8_t cause, uint8_t *diag, uint8_t diag_len) +{ + uint8_t *ie; + + ie = msgb_put(msg, diag_len + 2); + *ie++ = diag_len + 1; + *ie++ = 0x80 | cause; + if (diag_len && diag) + memcpy(ie, diag, diag_len); +} + +static int _msg_too_short(struct gsm_trans *trans) +{ + LOG_GCC(trans, LOGL_ERROR, "MSG too short.\n"); + return -EINVAL; +} + +static int _ie_invalid(struct gsm_trans *trans) +{ + LOG_GCC(trans, LOGL_ERROR, "IE invalid.\n"); + return -EINVAL; +} + +/* 3GPP TS 44.068 Clause 8.4 */ +static int gsm44068_rx_set_parameter(struct gsm_trans *trans, struct msgb *msg, + uint8_t *da, uint8_t *ua, uint8_t *comm, uint8_t *oi) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh); + uint8_t *ie = gh->data; + + /* State attributes */ + if (remaining_len < 1) + return _msg_too_short(trans); + if (da) + *da = (ie[0] >> 3) & 0x1; + if (ua) + *ua = (ie[0] >> 2) & 0x1; + if (comm) + *comm = (ie[0] >> 1) & 0x1; + if (oi) + *oi = ie[0] & 0x1; + ie += 1; + + return 0; +} + +/* 3GPP TS 44.068 Clause 8.5 */ +static int gsm44068_tx_setup(struct gsm_trans *trans, uint32_t callref, bool with_prio, uint8_t prio, + uint8_t user_pdisc, uint8_t *user, uint8_t user_len, + bool with_talker_prio, uint8_t talker_prio) +{ + struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX SETUP"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + LOG_GCC(trans, LOGL_INFO, "Sending SETUP.\n"); + + gh->proto_discr = trans->protocol | (trans->transaction_id << 4); + gh->msg_type = OSMO_GSM44068_MSGT_SETUP; + _add_callref_ie(msg, callref, with_prio, prio); + if (user_len && user) + _add_user_user_ie(msg, user_pdisc, user, user_len); + if (with_talker_prio) + _add_talker_prio_ie(msg, talker_prio); + + return vgcs_data_req(trans, msg); +} + +/* 3GPP TS 44.068 Clause 8.6 */ +static int gsm44068_tx_status(struct gsm_trans *trans, uint8_t cause, uint8_t *diag, uint8_t diag_len, + bool with_call_state, uint8_t call_state, bool with_state_attrs, + uint8_t da, uint8_t ua, uint8_t comm, uint8_t oi) +{ + struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX STATUS"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + LOG_GCC(trans, LOGL_INFO, "Sending STATUS.\n"); + + gh->proto_discr = trans->protocol | (trans->transaction_id << 4); + gh->msg_type = OSMO_GSM44068_MSGT_STATUS; + _add_cause_ie(msg, cause, diag, diag_len); + if (with_call_state) + _add_call_state_ie(msg, call_state); + if (with_state_attrs) + _add_state_attrs_ie(msg, da, ua, comm, oi); + + return vgcs_data_req(trans, msg); +} + +/* 3GPP TS 44.068 Clause 8.7 and 8.8 */ +static int gsm44068_rx_termination(struct gsm_trans *trans, struct msgb *msg, uint8_t *cause, uint8_t *diag, uint8_t *diag_len) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh); + uint8_t *ie = gh->data; + uint8_t ie_len; + + /* Cause */ + if (remaining_len < 2 || ie[0] < remaining_len - 2) + return _msg_too_short(trans); + ie_len = ie[0]; + if (remaining_len < ie_len + 1) + return _msg_too_short(trans); + if (ie_len < 1) + return _ie_invalid(trans); + if (cause) + *cause = ie[1] & 0x7f; + if (diag && diag_len) { + *diag_len = ie_len - 1; + if (*diag_len) + memcpy(diag, ie + 2, ie_len - 1); + } + remaining_len -= ie_len + 1; + ie += ie_len + 1; + + return 0; +} + +/* 3GPP TS 44.068 Clause 8.9 */ +static int gsm44068_tx_termination_request(struct gsm_trans *trans, uint32_t callref, bool with_prio, uint8_t prio, + bool with_talker_prio, uint8_t talker_prio) +{ + struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX TERMINATION REQUEST"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + LOG_GCC(trans, LOGL_INFO, "Sending TERMINATION REQUEST.\n"); + + gh->proto_discr = trans->protocol | (trans->transaction_id << 4); + gh->msg_type = OSMO_GSM44068_MSGT_TERMINATION_REQUEST; + _add_callref_ie(msg, callref, with_prio, prio); + if (with_talker_prio) + _add_talker_prio_ie(msg, talker_prio); + + return vgcs_data_req(trans, msg); +} + +/* 3GPP TS 44.018 Clause 9.1.48 */ +static int gsm44068_tx_uplink_release(struct gsm_trans *trans, uint8_t cause) +{ + struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX UPLINK RELEASE"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + struct gsm48_uplink_release *ur = (struct gsm48_uplink_release *) msgb_put(msg, sizeof(*ur)); + + LOG_GCC(trans, LOGL_INFO, "UPLINK RELEASE (cause #%d)\n", cause); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_UPLINK_RELEASE; + ur->rr_cause = cause; + + return vgcs_data_req(trans, msg); +} + +/* + * GCC/BCC state machine + * + * For reference see Figure 6.1 of TS 44.068. + * + * Note: There are some events that are not depicted in the state diagram: + * + * "L: ABORT-IND" indicates closing/failing of radio channel. + * "H: TALK-REQ" request talking on the channel. + * "L: TALK-CNF" confirms talker. + * "L: TALK-REJ" rejects talker. + * "H: LISTEN-REQ" request listening. + * "L: LISTEN-CNF" confirms listening. + * + */ + +/* Table 6.1 of TS 44.068 */ +#define T_NO_CHANNEL 3 +#define T_MM_EST 7 +#define T_TERM 10 +#define T_CONN_REQ 10 + +static void vgcs_gcc_fsm_u0_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 0, 0, 0, 0, OSMO_GSM44068_CSTATE_U0); +} + +static void vgcs_gcc_fsm_u0_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + + switch (event) { + case VGCS_GCC_EV_SETUP_REQ: + /* The calling user initiates a new call. */ + LOG_GCC(trans, LOGL_INFO, "Received call from user.\n"); + /* Change to MM CONNECTION PENDING state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING, 0, 0); + /* Send EST-REQ to MM layer. */ + vgcs_est_req(trans); + break; + case VGCS_GCC_EV_SETUP_IND: + /* New call notification. */ + LOG_GCC(trans, LOGL_INFO, "Received call from network.\n"); + /* Change to GROUP CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0); + /* Notify call at VTY. */ + vgcs_vty_notify(trans, "Incoming call\n"); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u0p_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 0, 0, 0, 1, OSMO_GSM44068_CSTATE_U0p); + + /* Start timer */ + osmo_timer_schedule(&fi->timer, T_MM_EST, 0); +} + +static void vgcs_gcc_fsm_u0p_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + int rc; + + switch (event) { + case VGCS_GCC_EV_TERM_REQ: + /* The user terminates the call. */ + LOG_GCC(trans, LOGL_INFO, "User terminates the call.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Free transaction. MM confirmation/rejection is handled without transaction also. */ + trans_free(trans); + break; + case VGCS_GCC_EV_MM_EST_REJ: + /* The MM layer rejects the call. */ + LOG_GCC(trans, LOGL_INFO, "Call was rejected by MM layer.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify reject at VTY. */ + vgcs_vty_notify(trans, "Rejected (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_MM_EST_CNF: + /* The MM connection was confirmed. */ + LOG_GCC(trans, LOGL_INFO, "Call was confirmed by MM layer.\n"); + /* Change to GROUP CALL INITIATED state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U1_GROUP_CALL_INITIATED, 0, 0); + /* Choose transaction ID. */ + rc = trans_assign_trans_id(trans->ms, trans->protocol, 0); + if (rc < 0) { + /* No free transaction ID. */ + trans_free(trans); + return; + } + trans->transaction_id = rc; + /* Send SETUP towards network. */ + gsm44068_tx_setup(trans, trans->callref, false, 0, false, NULL, 0, false, 0); + break; + case VGCS_GCC_EV_TIMEOUT: + /* Establishment of MM layer timed out. */ + LOG_GCC(trans, LOGL_INFO, "MM layer timed out.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Free transaction. MM confirmation/rejection is handled without transaction also. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u1_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 0, 0, 1, 1, OSMO_GSM44068_CSTATE_U1); +} + +static void vgcs_gcc_fsm_u1_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_DI_CONNECT: + /* Received CONNECT from network. */ + LOG_GCC(trans, LOGL_INFO, "Call was accepted by network.\n"); + /* Change to GROUP CALL ACTIVE (separate link) state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE, 0, 0); + /* Notify connect at VTY. */ + vgcs_vty_notify(trans, "Connect\n"); + break; + case VGCS_GCC_EV_DI_TERMINATION: + /* Received TERMINATION from network. */ + LOG_GCC(trans, LOGL_INFO, "Call was terminated by network (cause %d).\n", *cause); + /* Chane to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Terminated (cause %d)\n", *cause); + /* Release MM connection. */ + vgcs_rel_req(trans); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_TERM_REQ: + /* The user terminates the call. */ + LOG_GCC(trans, LOGL_INFO, "User terminates the call.\n"); + /* Change to TERMINATION REQUESTED state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U5_TERMINATION_REQUESTED, 0, 0); + /* Send TERMINATION REQUEST towards network. */ + gsm44068_tx_termination_request(trans, trans->callref, false, 0, false, 0); + break; + case VGCS_GCC_EV_ABORT_IND: + /* Radio link was released or failed. */ + LOG_GCC(trans, LOGL_INFO, "Got release from MM layer.\n"); + /* Chane to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Release MM connection. */ + vgcs_rel_req(trans); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u2sl_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 1, 1, 1, 1, OSMO_GSM44068_CSTATE_U2sl_U2); +} + +static void vgcs_gcc_fsm_u2sl_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_DI_TERMINATION: + /* Received TERMINATION from network. */ + LOG_GCC(trans, LOGL_INFO, "Call was terminated by network (cause %d).\n", *cause); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Terminated (cause %d)\n", *cause); + /* Don't release MM connection, this is done from network side using CHANNEL RELEASE. */ + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_TERM_REQ: + /* The user terminates the call. */ + LOG_GCC(trans, LOGL_INFO, "User terminates the call.\n"); + /* Change to TERMINATION REQUESTED state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U5_TERMINATION_REQUESTED, 0, 0); + /* Send TERMINATION REQUEST towards network. */ + gsm44068_tx_termination_request(trans, trans->callref, false, 0, false, 0); + break; + case VGCS_GCC_EV_ABORT_IND: + /* Radio link was released or failed. */ + LOG_GCC(trans, LOGL_INFO, "Got release from MM layer.\n"); + /* Chane to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_LISTEN_REQ: + /* The user wants to release dedicated link and join the group channel as listener. */ + LOG_GCC(trans, LOGL_INFO, "User releases uplink on dedicated channel.\n"); + /* Change state to ACTIVE (wait receive). */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE, 0, 0); + /* Set flag that we change to group receive mode after separate link. */ + trans->gcc.receive_after_sl = true; + /* Request release of the uplink. */ + gsm44068_tx_uplink_release(trans, GSM48_RR_CAUSE_LEAVE_GROUP_CA); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u2wr_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 1, 0, 1, -1, OSMO_GSM44068_CSTATE_U2wr_U6); +} + +static void vgcs_gcc_fsm_u2wr_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_LISTEN_CNF: + /* The MM layer confirms uplink release. */ + LOG_GCC(trans, LOGL_INFO, "Uplink is now released.\n"); + /* Change to CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0); + /* Notify answer at VTY. */ + vgcs_vty_notify(trans, "Listening\n"); + break; + case VGCS_GCC_EV_TERM_REQ: + /* The calling subscriber wants to terminate the call. */ + LOG_GCC(trans, LOGL_INFO, "User wants to terminate. Setting termination flag.\n"); + trans->gcc.termination = true; + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User aborts group channel. */ + LOG_GCC(trans, LOGL_INFO, "User leaves the group channel.\n"); + /* Change to GROUP CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0); + /* Reset flag after we changed to group receive mode after separate link. */ + trans->gcc.receive_after_sl = false; + /* Notify leaving at VTY. */ + vgcs_vty_notify(trans, "Call left\n"); + /* Release MM connection. (Leave call.) */ + vgcs_rel_req(trans); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The MM layer released the group channel. */ + LOG_GCC(trans, LOGL_INFO, "Release/failure received from MM layer.\n"); + if (trans->gcc.receive_after_sl) { + LOG_GCC(trans, LOGL_INFO, "Ignoring release, because we released separate link.\n"); + break; + } + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_MM_IDLE: + if (!trans->gcc.receive_after_sl) + break; + /* If no channel is available (no notification received), enter the U2nc state. */ + if (!trans->gcc.ch_desc_present) { + /* Change state to ACTIVE (no channel). */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE, 0, 0); + /* Notify answer at VTY. */ + vgcs_vty_notify(trans, "Listen (no channel yet)\n"); + break; + } + /* The MM layer indicates that the phone is ready to request group channel. */ + LOG_GCC(trans, LOGL_INFO, "MM is now idle, we can request group channel.\n"); + /* Send GROUP-REQ to MM layer. */ + vgcs_group_req(trans); + break; + case VGCS_GCC_EV_JOIN_GC_CNF: + /* Reset flag after we changed to group receive mode after separate link. */ + trans->gcc.receive_after_sl = false; + /* The MM layer confirms group channel. */ + LOG_GCC(trans, LOGL_INFO, "Joined group call after releasing separate link.\n"); + /* Change to CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0); + /* Notify answer at VTY. */ + vgcs_vty_notify(trans, "Listening\n"); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u2r_u6_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 1, 0, 0, -1, (trans->protocol == GSM48_PDISC_GROUP_CC) ? + OSMO_GSM44068_CSTATE_U2r : OSMO_GSM44068_CSTATE_U2wr_U6); + + /* There is a pending termination request, request uplink. */ + if (trans->gcc.termination) + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TALK_REQ, NULL); +} + +static void vgcs_gcc_fsm_u2r_u6_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_TERM_REQ: + /* The calling subscriber wants to terminate the call. */ + LOG_GCC(trans, LOGL_INFO, "User wants to terminate. Setting termination flag.\n"); + trans->gcc.termination = true; + /* fall-thru */ + case VGCS_GCC_EV_TALK_REQ: + /* The user wants to talk. */ + LOG_GCC(trans, LOGL_INFO, "User wants to talk on the uplink.\n"); + /* Change to GROUP CALL ACTIVE (wait send) state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE, 0, 0); + /* Request group transmit mode from MM layer. */ + vgcs_uplink_req(trans); + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User aborts group channel. */ + LOG_GCC(trans, LOGL_INFO, "User leaves the group channel.\n"); + /* Change to GROUP CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0); + /* Notify leaving at VTY. */ + vgcs_vty_notify(trans, "Call left\n"); + /* Release MM connection. (Leave call.) */ + vgcs_rel_req(trans); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The MM layer released the group channel. */ + LOG_GCC(trans, LOGL_INFO, "Release/failure received from MM layer.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_UPLINK_FREE: + /* The MM layer indicates that the uplink is free. */ + LOG_GCC(trans, LOGL_INFO, "Uplink free indication received from MM layer.\n"); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Uplink free => You may talk\n"); + break; + case VGCS_GCC_EV_UPLINK_BUSY: + /* The MM layer indicates that the uplink is busy. */ + LOG_GCC(trans, LOGL_INFO, "Uplink busy indication received from MM layer.\n"); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Uplink busy => You cannot talk\n"); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u2ws_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 1, 1, 0, -1, OSMO_GSM44068_CSTATE_U2ws); +} + +static void vgcs_gcc_fsm_u2ws_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_TALK_CNF: + /* Uplink was granted. */ + LOG_GCC(trans, LOGL_INFO, "Uplink established, user can talk now.\n"); + /* Change to GROUP CALL ACTIVE (send and receive) state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Talking\n"); + break; + case VGCS_GCC_EV_TALK_REJ: + /* Uplink was rejected. */ + LOG_GCC(trans, LOGL_INFO, "Uplink rejected, user cannot talk.\n"); + /* Clear termination flag, if set. */ + trans->gcc.termination = false; + /* Change to GROUP CALL ACTIVE (receive) state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Talking rejected (cause %d)\n", *cause); + break; + case VGCS_GCC_EV_TERM_REQ: + /* The calling subscriber wants to terminate the call. */ + LOG_GCC(trans, LOGL_INFO, "User wants to terminate. Setting termination flag.\n"); + trans->gcc.termination = true; + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User aborts group channel. */ + LOG_GCC(trans, LOGL_INFO, "User leaves the group channel.\n"); + /* Change to GROUP CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0); + /* Notify leaving at VTY. */ + vgcs_vty_notify(trans, "Call left\n"); + /* Release MM connection. (Leave call.) */ + vgcs_rel_req(trans); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The MM layer released the group channel. */ + LOG_GCC(trans, LOGL_INFO, "Release/failure received from MM layer.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u2sr_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 1, 1, 1, -1, OSMO_GSM44068_CSTATE_U2sr); + + /* There is a pending termination request, request uplink. */ + if (trans->gcc.termination) + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TERM_REQ, NULL); +} + +static void vgcs_gcc_fsm_u2sr_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_DI_TERMINATION: + /* Received TERMINATION from network. */ + LOG_GCC(trans, LOGL_INFO, "Call was terminated by network (cause %d).\n", *cause); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Terminated (cause %d)\n", *cause); + /* Don't release MM connection, this is done from network side using CHANNEL RELEASE. */ + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_TALK_REJ: + /* Uplink was rejected by network. (Reject after granting access.) */ + LOG_GCC(trans, LOGL_INFO, "Uplink rejected, user cannot talk.\n"); + /* Change to GROUP CALL ACTIVE (receive) state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Talking rejected (cause %d)\n", *cause); + break; + case VGCS_GCC_EV_LISTEN_REQ: + /* The user wants to release the uplink and become a listener. */ + LOG_GCC(trans, LOGL_INFO, "User wants to release the uplink.\n"); + /* Change to GROUP CALL ACTIVE (wait receive) state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE, 0, 0); + /* Request group receive mode from MM layer. */ + vgcs_uplink_rel_req(trans); + break; + case VGCS_GCC_EV_TERM_REQ: + /* The user terminates the call. */ + LOG_GCC(trans, LOGL_INFO, "User terminates the call.\n"); + /* Change to TERMINATION REQUESTED state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U5_TERMINATION_REQUESTED, 0, 0); + /* Send TERMINATION REQUEST towards network. */ + gsm44068_tx_termination_request(trans, trans->callref, false, 0, false, 0); + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User aborts group channel. */ + LOG_GCC(trans, LOGL_INFO, "User leaves the group channel.\n"); + /* Change to GROUP CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0); + /* Notify leaving at VTY. */ + vgcs_vty_notify(trans, "Call left\n"); + /* Release MM connection. (Leave call.) */ + vgcs_rel_req(trans); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The MM layer released the group channel. */ + LOG_GCC(trans, LOGL_INFO, "Release/failure received from MM layer.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u2nc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 1, 1, 0, -1, OSMO_GSM44068_CSTATE_U2nc); + + /* Start timer */ + osmo_timer_schedule(&fi->timer, T_NO_CHANNEL, 0); +} + +static void vgcs_gcc_fsm_u2nc_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + + switch (event) { + case VGCS_GCC_EV_SETUP_IND: + /* Channel becomes available, now join the group call. */ + LOG_GCC(trans, LOGL_INFO, "Radio channel becomes available.\n"); + /* Change to CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST, 0, 0); + /* Send GROUP-REQ to MM layer. */ + vgcs_group_req(trans); + break; + case VGCS_GCC_EV_TERM_REQ: + /* The calling subscriber wants to terminate the call. */ + LOG_GCC(trans, LOGL_INFO, "User wants to terminate. Setting termination flag.\n"); + trans->gcc.termination = true; + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User aborts group channel. */ + LOG_GCC(trans, LOGL_INFO, "User leaves the group channel, that is not yet established.\n"); + /* Change to GROUP CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0); + break; + case VGCS_GCC_EV_REL_IND: + /* The MM layer indicates that group channel is gone. */ + LOG_GCC(trans, LOGL_INFO, "Group call notification is gone.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released\n"); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_TIMEOUT: + /* Group channel did not become availblet. */ + LOG_GCC(trans, LOGL_INFO, "Timeout waiting for group channel.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Timeout\n"); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u3_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 0, 0, 0, 0, OSMO_GSM44068_CSTATE_U3); +} + +static void vgcs_gcc_fsm_u3_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + + switch (event) { + case VGCS_GCC_EV_JOIN_GC_REQ: + /* Join (answer) incoming group call. */ + LOG_GCC(trans, LOGL_INFO, "Join call.\n"); + /* If no channel is available, enter the U2nc state. */ + if (!trans->gcc.ch_desc_present) { + /* Change state to ACTIVE (no channel). */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE, 0, 0); + /* Notify answer at VTY. */ + vgcs_vty_notify(trans, "Answer (no channel yet)\n"); + break; + } + /* Change to CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST, 0, 0); + /* Send GROUP-REQ to MM layer. */ + vgcs_group_req(trans); + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User rejects group call. */ + LOG_GCC(trans, LOGL_INFO, "User rejects group call.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_REL_IND: + /* The notified call is gone. */ + LOG_GCC(trans, LOGL_INFO, "Received call from network is gone.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released\n"); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u4_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 0, 0, 0, 0, OSMO_GSM44068_CSTATE_U4); + + /* Start timer */ + osmo_timer_schedule(&fi->timer, T_CONN_REQ, 0); +} + +static void vgcs_gcc_fsm_u4_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_JOIN_GC_CNF: + /* The MM layer confirms the group receive mode. (We have a channel.) */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0); + /* Notify answer at VTY. */ + vgcs_vty_notify(trans, "Answer\n"); + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User aborts group channel. */ + LOG_GCC(trans, LOGL_INFO, "User aborts group channel.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_REL_IND: + /* The notified call is gone. */ + LOG_GCC(trans, LOGL_INFO, "Received call from network is gone.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released\n"); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The notified call is gone. */ + LOG_GCC(trans, LOGL_INFO, "Call rejected.\n"); + /* Change back to U3 state, so that call may be joined later. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + break; + case VGCS_GCC_EV_TIMEOUT: + /* Group channel timed out. */ + LOG_GCC(trans, LOGL_INFO, "Timeout waiting for group channel.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Timeout\n"); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u5_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, -1, -1, 1, 1, OSMO_GSM44068_CSTATE_U5); + + /* Start timer */ + osmo_timer_schedule(&fi->timer, T_TERM, 0); +} + +static void vgcs_gcc_fsm_u5_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_DI_TERMINATION: + /* The network confirm the termination. */ + LOG_GCC(trans, LOGL_INFO, "Termination confirmend by network.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Terminated (cause %d)\n", *cause); + /* Don't release MM connection, this is done from network side using CHANNEL RELEASE. */ + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_DI_TERM_REJECT: + /* Termination was rejected. */ + LOG_GCC(trans, LOGL_INFO, "Termination rejected (cause %d), releasing MM connection.\n", *cause); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Termination rejected (cause %d), Call left\n", *cause); + /* Release MM connection. (Leave call.) + * We must release here, because we can have a dedicated MM connection or joined a group channel. + * We cannot go back, because we don't know what state we had before U5 state was entered. + * Do we really need to go back or just leave the call? */ + vgcs_rel_req(trans); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The notified call is gone. */ + LOG_GCC(trans, LOGL_INFO, "Received call from network is gone.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_TIMEOUT: + /* Termination timed out. */ + LOG_GCC(trans, LOGL_INFO, "Timeout waiting for termination.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Timeout\n"); + /* Release MM connection. (Leave call.) */ + vgcs_rel_req(trans); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static int vgcs_gcc_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + return osmo_fsm_inst_dispatch(fi, VGCS_GCC_EV_TIMEOUT, NULL); +} + +static const struct osmo_fsm_state vgcs_gcc_fsm_states[] = { + [VGCS_GCC_ST_U0_NULL] = { + .name = "NULL (U0)", + .in_event_mask = S(VGCS_GCC_EV_SETUP_REQ) | + S(VGCS_GCC_EV_SETUP_IND), + .out_state_mask = S(VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING) | + S(VGCS_GCC_ST_U1_GROUP_CALL_INITIATED) | + S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT), + .onenter = vgcs_gcc_fsm_u0_onenter, + .action = vgcs_gcc_fsm_u0_action, + }, + [VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING] = { + .name = "MM CONNECTION PENDING (U0.p)", + .in_event_mask = S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_MM_EST_REJ) | + S(VGCS_GCC_EV_MM_EST_CNF) | + S(VGCS_GCC_EV_TIMEOUT), + .out_state_mask = S(VGCS_GCC_ST_U0_NULL) | + S(VGCS_GCC_ST_U1_GROUP_CALL_INITIATED), + .onenter = vgcs_gcc_fsm_u0p_onenter, + .action = vgcs_gcc_fsm_u0p_action, + }, + [VGCS_GCC_ST_U1_GROUP_CALL_INITIATED] = { + .name = "GROUP CALL INITIATED (U1)", + .in_event_mask = S(VGCS_GCC_EV_DI_CONNECT) | + S(VGCS_GCC_EV_DI_TERMINATION) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_IND), + .out_state_mask = S(VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U5_TERMINATION_REQUESTED) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u1_onenter, + .action = vgcs_gcc_fsm_u1_action, + }, + [VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE] = { + .name = "GROUP CALL ACTIVE separate link (U2sl)", + .in_event_mask = S(VGCS_GCC_EV_DI_TERMINATION) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_IND) | + S(VGCS_GCC_EV_LISTEN_REQ), + .out_state_mask = S(VGCS_GCC_ST_U0_NULL) | + S(VGCS_GCC_ST_U5_TERMINATION_REQUESTED) | + S(VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE), + .onenter = vgcs_gcc_fsm_u2sl_onenter, + .action = vgcs_gcc_fsm_u2sl_action, + }, + [VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE] = { + .name = "GROUP CALL ACTIVE wait for receive mode (U2wr)", + .in_event_mask = S(VGCS_GCC_EV_LISTEN_CNF) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_ABORT_IND) | + S(VGCS_GCC_EV_MM_IDLE) | + S(VGCS_GCC_EV_JOIN_GC_CNF), + .out_state_mask = S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u2wr_onenter, + .action = vgcs_gcc_fsm_u2wr_action, + }, + [VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE] = { + .name = "GROUP CALL ACTIVE receive mode (U2r or U6 @ BCC)", + .in_event_mask = S(VGCS_GCC_EV_TALK_REQ) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_ABORT_IND) | + S(VGCS_GCC_EV_UPLINK_FREE) | + S(VGCS_GCC_EV_UPLINK_BUSY), + .out_state_mask = S(VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u2r_u6_onenter, + .action = vgcs_gcc_fsm_u2r_u6_action, + }, + [VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE] = { + .name = "GROUP CALL ACTIVE wait for send+receive mode (U2ws)", + .in_event_mask = S(VGCS_GCC_EV_TALK_CNF) | + S(VGCS_GCC_EV_TALK_REJ) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_ABORT_IND), + .out_state_mask = S(VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u2ws_onenter, + .action = vgcs_gcc_fsm_u2ws_action, + }, + [VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE] = { + .name = "GROUP CALL ACTIVE send+receive mode (U2sr)", + .in_event_mask = S(VGCS_GCC_EV_DI_TERMINATION) | + S(VGCS_GCC_EV_LISTEN_REQ) | + S(VGCS_GCC_EV_TALK_REJ) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_ABORT_IND), + .out_state_mask = S(VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u2sr_onenter, + .action = vgcs_gcc_fsm_u2sr_action, + }, + [VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE] = { + .name = "GROUP CALL ACTIVE no channel (U2nc)", + .in_event_mask = S(VGCS_GCC_EV_SETUP_IND) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_REL_IND) | + S(VGCS_GCC_EV_TIMEOUT), + .out_state_mask = S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST) | + S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u2nc_onenter, + .action = vgcs_gcc_fsm_u2nc_action, + }, + [VGCS_GCC_ST_U3_GROUP_CALL_PRESENT] = { + .name = "GROUP CALL PRESENT (U3)", + .in_event_mask = S(VGCS_GCC_EV_JOIN_GC_REQ) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_REL_IND), + .out_state_mask = S(VGCS_GCC_ST_U0_NULL) | + S(VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST) | + S(VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE), + .onenter = vgcs_gcc_fsm_u3_onenter, + .action = vgcs_gcc_fsm_u3_action, + }, + [VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST] = { + .name = "GROUP CALL CONNECTION REQUEST (U4)", + .in_event_mask = S(VGCS_GCC_EV_JOIN_GC_CNF) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_REL_IND) | + S(VGCS_GCC_EV_ABORT_IND) | + S(VGCS_GCC_EV_TIMEOUT), + .out_state_mask = S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u4_onenter, + .action = vgcs_gcc_fsm_u4_action, + }, + [VGCS_GCC_ST_U5_TERMINATION_REQUESTED] = { + .name = "TERMINATION REQUESTED (U5)", + .in_event_mask = S(VGCS_GCC_EV_DI_TERMINATION) | + S(VGCS_GCC_EV_DI_TERM_REJECT) | + S(VGCS_GCC_EV_ABORT_IND) | + S(VGCS_GCC_EV_TIMEOUT), + .out_state_mask = S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u5_onenter, + .action = vgcs_gcc_fsm_u5_action, + }, +}; + +static struct osmo_fsm vgcs_gcc_fsm = { + .name = "gcc", + .states = vgcs_gcc_fsm_states, + .num_states = ARRAY_SIZE(vgcs_gcc_fsm_states), + .log_subsys = DGCC, + .event_names = vgcs_gcc_fsm_event_names, + .timer_cb = vgcs_gcc_fsm_timer_cb, +}; + +static struct osmo_fsm vgcs_bcc_fsm = { + .name = "bcc", + .states = vgcs_gcc_fsm_states, + .num_states = ARRAY_SIZE(vgcs_gcc_fsm_states), + .log_subsys = DBCC, + .event_names = vgcs_gcc_fsm_event_names, + .timer_cb = vgcs_gcc_fsm_timer_cb, +}; + +static __attribute__((constructor)) void on_dso_load(void) +{ + OSMO_ASSERT(osmo_fsm_register(&vgcs_gcc_fsm) == 0); + OSMO_ASSERT(osmo_fsm_register(&vgcs_bcc_fsm) == 0); +} + +static const char *gsm44068_gcc_state_name(struct osmo_fsm_inst *fi) +{ + return vgcs_gcc_fsm_states[fi->state].name; +} + +/* + * transaction + */ + +/* Create transaction together with state machine and set initail states. */ +static struct gsm_trans *trans_alloc_vgcs(struct osmocom_ms *ms, uint8_t pdisc, uint8_t transaction_id, + uint32_t callref) +{ + struct gsm_trans *trans; + + trans = trans_alloc(ms, pdisc, 0xff, callref); + if (!trans) + return NULL; + trans->gcc.fi = osmo_fsm_inst_alloc((pdisc == GSM48_PDISC_GROUP_CC) ? &vgcs_gcc_fsm : &vgcs_bcc_fsm, + trans, trans, LOGL_DEBUG, NULL); + if (!trans->gcc.fi) { + trans_free(trans); + return NULL; + } + + LOG_GCC(trans, LOGL_DEBUG, "Created transaction for callref %d, pdisc %d, transaction_id 0x%x\n", + callref, pdisc, transaction_id); + + set_state_attributes(trans, 0, 0, 0, 0, OSMO_GSM44068_CSTATE_U0); + + return trans; +} + +/* Call Control Specific transaction release. + * gets called by trans_free, DO NOT CALL YOURSELF! + */ +void _gsm44068_gcc_bcc_trans_free(struct gsm_trans *trans) +{ + if (!trans->gcc.fi) + return; + + /* Free state machine. */ + osmo_fsm_inst_free(trans->gcc.fi); +} + +/* Find ongoing call. (The call must not be present.) */ +struct gsm_trans *trans_find_ongoing_gcc_bcc(struct osmocom_ms *ms) +{ + struct gsm_trans *trans; + + llist_for_each_entry(trans, &ms->trans_list, entry) { + if (trans->protocol != GSM48_PDISC_GROUP_CC && trans->protocol != GSM48_PDISC_BCAST_CC) + continue; + if (trans->gcc.fi->state != VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) + return trans; + } + + return NULL; +} + +/* + * messages from upper/lower layers + */ + +static int gsm44068_gcc_data_ind(struct gsm_trans *trans, struct msgb *msg, uint8_t transaction_id) +{ + struct osmocom_ms *ms = trans->ms; + struct gsm48_hdr *gh = msgb_l3(msg); + int msg_type = gh->msg_type & 0xbf; + int rc = 0; + uint8_t cause = 0; + uint8_t d_att, u_att, comm, orig; + + /* pull the MMCC header */ + msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr)); + + /* Use transaction ID from message. If we join a group call, we don't have it until now. */ + trans->transaction_id = transaction_id; + + LOG_GCC(trans, LOGL_INFO, "(ms %s) Received '%s' in state %s\n", ms->name, gsm44068_gcc_msg_name(msg_type), + gsm44068_gcc_state_name(trans->gcc.fi)); + + switch (msg_type) { + case OSMO_GSM44068_MSGT_CONNECT: + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_DI_CONNECT, NULL); + break; + case OSMO_GSM44068_MSGT_TERMINATION: + rc = gsm44068_rx_termination(trans, msg, &cause, NULL, NULL); + if (rc < 0) + break; + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_DI_TERMINATION, &cause); + break; + case OSMO_GSM44068_MSGT_TERMINATION_REJECT: + rc = gsm44068_rx_termination(trans, msg, &cause, NULL, NULL); + if (rc < 0) + break; + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_DI_TERM_REJECT, &cause); + break; + case OSMO_GSM44068_MSGT_GET_STATUS: + /* Note: The message via UNIT DATA is not supported. */ + gsm44068_tx_status(trans, OSMO_GSM44068_CAUSE_RESPONSE_TO_GET_STATUS, NULL, 0, + true, trans->gcc.call_state, + true, trans->gcc.d_att, trans->gcc.u_att, trans->gcc.comm, trans->gcc.orig); + break; + case OSMO_GSM44068_MSGT_SET_PARAMETER: + rc = gsm44068_rx_set_parameter(trans, msg, &d_att, &u_att, &comm, &orig); + if (rc >= 0) + set_state_attributes(trans, d_att, u_att, comm, orig, -1); + break; + default: + LOG_GCC(trans, LOGL_NOTICE, "Message '%s' not supported.\n", gsm44068_gcc_msg_name(msg_type)); + rc = -EINVAL; + } + + return rc; +} + +/* receive message from MM layer */ +int gsm44068_rcv_gcc_bcc(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_settings *set = &ms->settings; + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + int msg_type = mmh->msg_type; + uint8_t pdisc; + struct gsm_trans *trans; + int rc = 0; + + /* Check for message class and get protocol type. */ + switch ((msg_type & GSM48_MMXX_MASK)) { + case GSM48_MMGCC_CLASS: + pdisc = GSM48_PDISC_GROUP_CC; + if (!set->vgcs) { + LOGP(DGCC, LOGL_ERROR, "Ignoring message '%s', because VGCS is not supported!\n", + get_mmxx_name(msg_type)); + return -ENOTSUP; + } + break; + case GSM48_MMBCC_CLASS: + pdisc = GSM48_PDISC_BCAST_CC; + if (!set->vbs) { + LOGP(DGCC, LOGL_ERROR, "Ignoring message '%s', because VBS is not supported!\n", + get_mmxx_name(msg_type)); + return -ENOTSUP; + } + break; + default: + LOGP(DGCC, LOGL_ERROR, "Message class not allowed, please fix!\n"); + return -EINVAL; + } + + trans = trans_find_by_callref(ms, pdisc, mmh->ref); + if (!trans) { + LOG_GCC_PR(pdisc, mmh->ref, LOGL_INFO, "(ms %s) Received '%s' without transaction.\n", + ms->name, get_mmxx_name(msg_type)); + + if (msg_type == GSM48_MMGCC_REL_IND || msg_type == GSM48_MMBCC_REL_IND) { + /* If we got the MMxx-EST-REJ after we aborted the call, we ignore. */ + return 0; + } + if (msg_type == GSM48_MMGCC_EST_CNF || msg_type == GSM48_MMBCC_EST_CNF || + msg_type == GSM48_MMGCC_GROUP_CNF || msg_type == GSM48_MMBCC_GROUP_CNF) { + struct msgb *nmsg; + LOG_GCC_PR(pdisc, mmh->ref, LOGL_ERROR, "Received confirm for GCC/BCC call, " + "but there is no transaction, releasing.\n"); + /* If we got the MMxx-EST-CNF after we aborted the call, we release. */ + msg_type = (pdisc == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_REL_REQ : GSM48_MMBCC_REL_REQ; + nmsg = gsm48_mmxx_msgb_alloc(msg_type, mmh->ref, mmh->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + LOG_GCC_PR(pdisc, mmh->ref, LOGL_INFO, "Sending %s\n", get_mmxx_name(msg_type)); + gsm48_mmxx_downmsg(ms, nmsg); + return 0; + + } else if (msg_type == GSM48_MMGCC_NOTIF_IND || msg_type == GSM48_MMBCC_NOTIF_IND) { + /* Notification gone but no transaction. */ + if (mmh->notify != MMXX_NOTIFY_SETUP) + return 0; + /* Incoming notification, creation transaction. */ + trans = trans_alloc_vgcs(ms, pdisc, 0xff, mmh->ref); + if (!trans) + return -ENOMEM; + } else { + LOG_GCC_PR(pdisc, mmh->ref, LOGL_ERROR, "Received GCC/BCC message for unknown transaction.\n"); + return -ENOENT; + } + } + + LOG_GCC(trans, LOGL_INFO, "(ms %s) Received '%s' in state %s\n", ms->name, get_mmxx_name(msg_type), + gsm44068_gcc_state_name(trans->gcc.fi)); + + switch (msg_type) { + case GSM48_MMGCC_EST_CNF: + case GSM48_MMBCC_EST_CNF: + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_MM_EST_CNF, NULL); + break; + case GSM48_MMGCC_ERR_IND: + case GSM48_MMBCC_ERR_IND: + case GSM48_MMGCC_REL_IND: + case GSM48_MMBCC_REL_IND: + /* Ignore release confirm or release collision. */ + if (trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) + break; + /* If MM fails or is rejected during U0.p state, this is a MM-EST-REJ. */ + if (trans->gcc.fi->state == VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING) + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_MM_EST_REJ, &mmh->cause); + else + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_ABORT_IND, &mmh->cause); + break; + case GSM48_MMGCC_DATA_IND: + case GSM48_MMBCC_DATA_IND: + rc = gsm44068_gcc_data_ind(trans, msg, mmh->transaction_id); + break; + case GSM48_MMGCC_NOTIF_IND: + case GSM48_MMBCC_NOTIF_IND: + switch (mmh->notify) { + case MMXX_NOTIFY_SETUP: + /* Store channel description. */ + trans->gcc.ch_desc_present = mmh->ch_desc_present; + memcpy(&trans->gcc.ch_desc, &mmh->ch_desc, sizeof(trans->gcc.ch_desc)); + if (trans->gcc.fi->state == VGCS_GCC_ST_U0_NULL) { + /* In null state this indication is a SETUP-IND. */ + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_SETUP_IND, NULL); + } else if (trans->gcc.fi->state == VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE && mmh->ch_desc_present) { + /* In U2nc state with a channel info, is a SETUP-IND (with updated mode). */ + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_SETUP_IND, NULL); + } + break; + case MMXX_NOTIFY_RELEASE: + if (trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT || + trans->gcc.fi->state == VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST || + trans->gcc.fi->state == VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE) { + /* If notification is gone, release pending received call. */ + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_REL_IND, NULL); + } + break; + } + break; + case GSM48_MMGCC_GROUP_CNF: + case GSM48_MMBCC_GROUP_CNF: + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_JOIN_GC_CNF, NULL); + break; + case GSM48_MMGCC_UPLINK_CNF: + case GSM48_MMBCC_UPLINK_CNF: + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TALK_CNF, NULL); + break; + case GSM48_MMGCC_UPLINK_REL_IND: + case GSM48_MMBCC_UPLINK_REL_IND: + if (trans->gcc.fi->state == VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE || + trans->gcc.fi->state == VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE) { + /* We are waiting to send, so this is a reject. */ + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TALK_REJ, &mmh->cause); + } else if (trans->gcc.fi->state == VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE) { + /* We are waiting to receive, so this is a confirm. */ + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_LISTEN_CNF, &mmh->cause); + } + break; + case GSM48_MMGCC_UPLINK_FREE_IND: + case GSM48_MMBCC_UPLINK_FREE_IND: + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_UPLINK_FREE, NULL); + break; + case GSM48_MMGCC_UPLINK_BUSY_IND: + case GSM48_MMBCC_UPLINK_BUSY_IND: + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_UPLINK_BUSY, NULL); + break; + default: + LOG_GCC(trans, LOGL_NOTICE, "Message '%s' unhandled.\n", get_mmxx_name(msg_type)); + rc = -ENOTSUP; + } + + return rc; +} + +/* Special function to receive IDLE state of MM layer. This is required to request a channel after dedicated mode. */ +int gsm44068_rcv_mm_idle(struct osmocom_ms *ms) +{ + struct gsm_trans *trans; + + trans = trans_find_ongoing_gcc_bcc(ms); + if (!trans) + return -ENOENT; + if (trans->gcc.fi->state == VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE && trans->gcc.receive_after_sl) { + /* Finally the MM layer is IDLE. */ + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_MM_IDLE, NULL); + return 0; + } + return -EINVAL; +} + +/* Setup or join a VGC/VBC. */ +int gcc_bcc_call(struct osmocom_ms *ms, uint8_t protocol, const char *number) +{ + uint32_t callref = strtoul(number, NULL, 0); + struct gsm_trans *trans; + int i; + + if (strlen(number) > 8) { +inval: + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Invalid group '%s'\n", number); + return -EINVAL; + } + + for (i = 0; i < strlen(number); i++) { + if (number[i] < '0' || number[i] > '9') + goto inval; + } + + if (callref == 0) + goto inval; + + /* Reject if there is already an ongoing call. */ + trans = trans_find_ongoing_gcc_bcc(ms); + if (trans) { + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Cannot call/join, we are busy\n"); + return -EBUSY; + } + + /* Find call that matches the given protocol+cellref. */ + trans = trans_find_by_callref(ms, protocol, callref); + if (trans) { + /* Answer incoming call. */ + if (trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) { + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_JOIN_GC_REQ, NULL); + return 0; + } + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Call already established\n"); + return -EEXIST; + } + + /* Create new transaction. ORIG will be set when entering U2sl state. */ + trans = trans_alloc_vgcs(ms, protocol, 0xff, callref); + if (!trans) + return -ENOMEM; + + /* Setup new call. */ + return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_SETUP_REQ, NULL); +} + +/* Leave a VGC. (If we are the originator, we do not terminate.) */ +int gcc_leave(struct osmocom_ms *ms) +{ + struct gsm_trans *trans; + + /* Reject if there is no call. */ + trans = trans_find_ongoing_gcc_bcc(ms); + if (!trans) { + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No Call\n"); + return -EINVAL; + } + + if (trans->gcc.fi->state == VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING || + trans->gcc.fi->state == VGCS_GCC_ST_U1_GROUP_CALL_INITIATED || + trans->gcc.fi->state == VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE || + trans->gcc.fi->state == VGCS_GCC_ST_U5_TERMINATION_REQUESTED) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot leave (abort), in this state.\n"); + if (trans->gcc.fi->state == VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE) + vgcs_vty_notify(trans, "Cannot leave while talking\n"); + return -EINVAL; + } + + /* Send ABORT-REQ to leave the call. */ + return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_ABORT_REQ, NULL); +} + +/* Hangup VGC/VBC. (If we are the originator, we terminate the call.) */ +int gcc_bcc_hangup(struct osmocom_ms *ms) +{ + struct gsm_trans *trans; + + /* Reject if there is no call. */ + trans = trans_find_ongoing_gcc_bcc(ms); + if (!trans) { + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No Call\n"); + return -EINVAL; + } + + if (trans->gcc.orig) { + if (trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT || + trans->gcc.fi->state == VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST || + trans->gcc.fi->state == VGCS_GCC_ST_U5_TERMINATION_REQUESTED) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot hangup (terminate) in this state.\n"); + return -EINVAL; + } + /* Send TERM-REQ to leave the call. */ + return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TERM_REQ, NULL); + } + + if (trans->gcc.fi->state == VGCS_GCC_ST_U5_TERMINATION_REQUESTED) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot hangup (abort) in this state.\n"); + return -EINVAL; + } + /* Send ABORT-REQ to leave the call. */ + return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_ABORT_REQ, NULL); +} + +int gcc_talk(struct osmocom_ms *ms) +{ + struct gsm_trans *trans; + + /* Reject if there is no call. */ + trans = trans_find_ongoing_gcc_bcc(ms); + if (!trans) { + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No Call\n"); + return -EINVAL; + } + + if (trans->protocol != GSM48_PDISC_GROUP_CC) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot talk, the ongoing call is not a group call.\n"); + vgcs_vty_notify(trans, "Not a group call\n"); + return -EIO; + } + + if (trans->gcc.fi->state != VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot talk, we are not listening.\n"); + return -EINVAL; + } + + /* Send TALK-REQ to become talker. */ + return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TALK_REQ, NULL); +} + +int gcc_listen(struct osmocom_ms *ms) +{ + struct gsm_trans *trans; + + /* Reject if there is no call. */ + trans = trans_find_ongoing_gcc_bcc(ms); + if (!trans) { + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No Call\n"); + return -EINVAL; + } + + if (trans->protocol != GSM48_PDISC_GROUP_CC) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot become listener, the ongoing call is not a group call.\n"); + vgcs_vty_notify(trans, "Not a group call\n"); + return -EIO; + } + + if (trans->gcc.fi->state != VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE && + trans->gcc.fi->state != VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot listen, we are not talking.\n"); + return -EINVAL; + } + + /* Send LISTEN-REQ to become listener. */ + return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_LISTEN_REQ, NULL); +} + +int gsm44068_dump_calls(struct osmocom_ms *ms, void (*print)(void *, const char *, ...), void *priv) +{ + struct gsm_trans *trans; + + llist_for_each_entry(trans, &ms->trans_list, entry) { + if (trans->protocol != GSM48_PDISC_GROUP_CC && trans->protocol != GSM48_PDISC_BCAST_CC) + continue; + print(priv, "Voice %s call '%d' is %s.\n", + (trans->protocol == GSM48_PDISC_GROUP_CC) ? "group" : "broadcast", trans->callref, + (trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) ? "incoming" : "active"); + } + + return 0; +} diff --git a/src/host/layer23/src/mobile/gsm480_ss.c b/src/host/layer23/src/mobile/gsm480_ss.c index 52e43b9b..0acd18d7 100644 --- a/src/host/layer23/src/mobile/gsm480_ss.c +++ b/src/host/layer23/src/mobile/gsm480_ss.c @@ -13,10 +13,6 @@ * 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 <stdint.h> @@ -26,15 +22,20 @@ #include <stdlib.h> #include <osmocom/core/msgb.h> +#include <osmocom/core/signal.h> +#include <osmocom/core/talloc.h> + +#include <osmocom/gsm/protocol/gsm_04_80.h> +#include <osmocom/gsm/gsm48.h> + #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/mobile/mncc.h> #include <osmocom/bb/mobile/transaction.h> #include <osmocom/bb/mobile/gsm480_ss.h> -#include <osmocom/core/talloc.h> +#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h> #include <osmocom/bb/mobile/vty.h> -#include <osmocom/gsm/protocol/gsm_04_80.h> -#include <osmocom/gsm/gsm48.h> static uint32_t new_callref = 0x80000001; @@ -196,7 +197,7 @@ static const struct value_string Bearerservice_vals[] = { static int gsm480_ss_result(struct osmocom_ms *ms, const char *response, uint8_t error) { - vty_notify(ms, NULL); + l23_vty_ms_notify(ms, NULL); if (response) { char text[256], *t = text, *s; @@ -204,18 +205,18 @@ static int gsm480_ss_result(struct osmocom_ms *ms, const char *response, while ((s = strchr(text, '\r'))) *s = '\n'; while ((s = strsep(&t, "\n"))) { - vty_notify(ms, "Service response: %s\n", s); + l23_vty_ms_notify(ms, "Service response: %s\n", s); } } else if (error) - vty_notify(ms, "Service request failed: %s\n", + l23_vty_ms_notify(ms, "Service request failed: %s\n", get_value_string(gsm480_err_names, error)); else - vty_notify(ms, "Service request failed.\n"); + l23_vty_ms_notify(ms, "Service request failed.\n"); return 0; } -enum { +enum gsm480_ss_state { GSM480_SS_ST_IDLE = 0, GSM480_SS_ST_REGISTER, GSM480_SS_ST_ACTIVE, @@ -263,8 +264,8 @@ void _gsm480_ss_trans_free(struct gsm_trans *trans) msgb_free(trans->ss.msg); trans->ss.msg = NULL; } - vty_notify(trans->ms, NULL); - vty_notify(trans->ms, "Service connection terminated.\n"); + l23_vty_ms_notify(trans->ms, NULL); + l23_vty_ms_notify(trans->ms, "Service connection terminated.\n"); } /* release MM connection, free transaction */ @@ -286,8 +287,15 @@ static int gsm480_trans_free(struct gsm_trans *trans) return 0; } +static void gsm480_trans_state_chg(struct gsm_trans *trans, + enum gsm480_ss_state state) +{ + trans->ss.state = state; + osmo_signal_dispatch(SS_L23_TRANS, S_L23_CC_TRANS_STATE_CHG, trans); +} + /* - * endcoding + * encoding */ #define GSM480_ALLOC_SIZE 512+128 @@ -364,7 +372,7 @@ static const char *ss_code_by_char(const char *code, uint8_t *ss_code) static const char *decode_ss_code(uint8_t ss_code) { static char unknown[16]; - + switch (ss_code) { case 33: return "CFU"; @@ -602,6 +610,12 @@ int ss_send(struct osmocom_ms *ms, const char *code, int new_trans) return -EIO; } + /* ASCI call does not allow other transactions */ + if (trans_find_ongoing_gcc_bcc(ms)) { + gsm480_ss_result(ms, "<ongoing ASCI call>", 0); + return -EBUSY; + } + /* allocate transaction with dummy reference */ transaction_id = trans_assign_trans_id(ms, GSM48_PDISC_NC_SS, 0); @@ -620,8 +634,7 @@ int ss_send(struct osmocom_ms *ms, const char *code, int new_trans) return -ENOMEM; } - /* go register sent state */ - trans->ss.state = GSM480_SS_ST_REGISTER; + gsm480_trans_state_chg(trans, GSM480_SS_ST_REGISTER); /* FIXME: generate invoke ID */ trans->ss.invoke_id = 5; @@ -646,7 +659,7 @@ int ss_send(struct osmocom_ms *ms, const char *code, int new_trans) code++; to = ss_code_by_char(code + 1, &ss_code); - + /* register */ if (ss_code && to && to[0] == '*') { OSMO_STRLCPY_ARRAY(dest, to + 1); @@ -664,7 +677,7 @@ int ss_send(struct osmocom_ms *ms, const char *code, int new_trans) uint8_t ss_code = 0; ss_code_by_char(code + 2, &ss_code); - + if (ss_code) return gsm480_tx_cf(trans, GSM0480_MTYPE_REGISTER, GSM0480_OP_CODE_ERASE_SS, ss_code, NULL); @@ -674,7 +687,7 @@ int ss_send(struct osmocom_ms *ms, const char *code, int new_trans) uint8_t ss_code = 0; ss_code_by_char(code + 1, &ss_code); - + if (ss_code) return gsm480_tx_cf(trans, GSM0480_MTYPE_REGISTER, GSM0480_OP_CODE_DEACTIVATE_SS, ss_code, NULL); @@ -712,7 +725,7 @@ static int parse_tag_asn1(const uint8_t *data, int len, /* check for buffer overflow */ if (len < *tag_len) return -1; - + /* return length */ return len; } @@ -789,7 +802,7 @@ static int gsm480_rx_cf(struct gsm_trans *trans, const uint8_t *data, LOGP(DSS, LOGL_INFO, "call forwarding reply: len %d data %s\n", len, osmo_hexdump(data, len)); - vty_notify(ms, NULL); + l23_vty_ms_notify(ms, NULL); /* forwarding feature list */ if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { @@ -798,9 +811,9 @@ static int gsm480_rx_cf(struct gsm_trans *trans, const uint8_t *data, } if (data[0] == 0x80) { if ((tag_data[0] & 0x01)) - vty_notify(ms, "Status: activated\n"); + l23_vty_ms_notify(ms, "Status: activated\n"); else - vty_notify(ms, "Status: deactivated\n"); + l23_vty_ms_notify(ms, "Status: deactivated\n"); return 0; } @@ -819,7 +832,7 @@ static int gsm480_rx_cf(struct gsm_trans *trans, const uint8_t *data, /* check for SS code */ if (data[0] != 0x04) break; - vty_notify(ms, "Reply for %s\n", decode_ss_code(tag_data[0])); + l23_vty_ms_notify(ms, "Reply for %s\n", decode_ss_code(tag_data[0])); len -= tag_data - data + tag_len; data = tag_data + tag_len; /* sequence tag */ @@ -835,10 +848,10 @@ static int gsm480_rx_cf(struct gsm_trans *trans, const uint8_t *data, data = tag_data; break; default: - vty_notify(ms, "Call Forwarding reply unsupported.\n"); + l23_vty_ms_notify(ms, "Call Forwarding reply unsupported.\n"); return 0; } - + while (len) { /* sequence tag */ if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { @@ -867,20 +880,20 @@ static int gsm480_rx_cf(struct gsm_trans *trans, const uint8_t *data, osmo_hexdump(tag_data, tag_len)); switch (data2[0]) { case 0x82: - vty_notify(ms, "Bearer Service: %s\n", + l23_vty_ms_notify(ms, "Bearer Service: %s\n", get_value_string(Bearerservice_vals, tag_data[0])); break; case 0x83: - vty_notify(ms, "Teleservice: %s\n", + l23_vty_ms_notify(ms, "Teleservice: %s\n", get_value_string(Teleservice_vals, tag_data[0])); break; case 0x84: if ((tag_data[0] & 0x01)) - vty_notify(ms, "Status: activated\n"); + l23_vty_ms_notify(ms, "Status: activated\n"); else - vty_notify(ms, "Status: deactivated\n"); + l23_vty_ms_notify(ms, "Status: deactivated\n"); break; case 0x85: if (((tag_data[0] & 0x70) >> 4) == 1) @@ -892,7 +905,7 @@ static int gsm480_rx_cf(struct gsm_trans *trans, const uint8_t *data, gsm48_decode_bcd_number2(number + strlen(number), sizeof(number) - strlen(number), tag_data - 1, tag_len + 1, 1); - vty_notify(ms, "Destination: %s\n", number); + l23_vty_ms_notify(ms, "Destination: %s\n", number); break; } len2 -= tag_data - data2 + tag_len; @@ -933,7 +946,7 @@ static int gsm480_rx_result(struct gsm_trans *trans, const uint8_t *data, LOGP(DSS, LOGL_NOTICE, "Invoke ID mismatch\n"); } } - /* Store invoke ID, in case we wan't to send a result. */ + /* Store invoke ID, in case we want to send a result. */ trans->ss.invoke_id = tag_data[0]; len -= tag_data - data + tag_len; data = tag_data + tag_len; @@ -1058,7 +1071,12 @@ static int gsm480_rx_release_comp(struct gsm_trans *trans, struct msgb *msg) struct tlv_parsed tp; int rc = 0; - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0) < 0) { + LOGP(DSS, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + gsm480_trans_free(trans); + return -EINVAL; + } + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { rc = gsm480_rx_fac_ie(trans, TLVP_VAL(&tp, GSM48_IE_FACILITY), *(TLVP_VAL(&tp, GSM48_IE_FACILITY)-1)); @@ -1091,11 +1109,15 @@ static int gsm480_rx_facility(struct gsm_trans *trans, struct msgb *msg) struct tlv_parsed tp; int rc = 0; - /* go register state */ - trans->ss.state = GSM480_SS_ST_ACTIVE; + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, + GSM48_IE_FACILITY, 0) < 0) { + LOGP(DSS, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + /* XXX: indicate an error somehow */ + return -EINVAL; + } + + gsm480_trans_state_chg(trans, GSM480_SS_ST_ACTIVE); - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, - GSM48_IE_FACILITY, 0); if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { rc = gsm480_rx_fac_ie(trans, TLVP_VAL(&tp, GSM48_IE_FACILITY), *(TLVP_VAL(&tp, GSM48_IE_FACILITY)-1)); @@ -1123,10 +1145,14 @@ static int gsm480_rx_register(struct gsm_trans *trans, struct msgb *msg) struct tlv_parsed tp; int rc = 0; - /* go register state */ - trans->ss.state = GSM480_SS_ST_ACTIVE; + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0) < 0) { + LOGP(DSS, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + /* XXX: indicate an error somehow */ + return -EINVAL; + } + + gsm480_trans_state_chg(trans, GSM480_SS_ST_ACTIVE); - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { rc = gsm480_rx_fac_ie(trans, TLVP_VAL(&tp, GSM48_IE_FACILITY), *(TLVP_VAL(&tp, GSM48_IE_FACILITY)-1)); @@ -1243,7 +1269,7 @@ int gsm480_rcv_ss(struct osmocom_ms *ms, struct msgb *msg) struct gsm_trans *trans; int rc = 0; - trans = trans_find_by_callref(ms, mmh->ref); + trans = trans_find_by_callref(ms, GSM48_PDISC_NC_SS, mmh->ref); if (!trans) { LOGP(DSS, LOGL_INFO, " -> (new transaction)\n"); trans = trans_alloc(ms, GSM48_PDISC_NC_SS, mmh->transaction_id, diff --git a/src/host/layer23/src/mobile/gsm48_cc.c b/src/host/layer23/src/mobile/gsm48_cc.c index f1e81098..94b81cb0 100644 --- a/src/host/layer23/src/mobile/gsm48_cc.c +++ b/src/host/layer23/src/mobile/gsm48_cc.c @@ -13,10 +13,6 @@ * 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 <stdint.h> @@ -29,13 +25,16 @@ #include <osmocom/core/utils.h> #include <osmocom/gsm/gsm48.h> #include <osmocom/core/talloc.h> +#include <osmocom/core/signal.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/mobile/mncc.h> #include <osmocom/bb/mobile/transaction.h> #include <osmocom/bb/mobile/gsm48_cc.h> -#include <osmocom/bb/mobile/voice.h> +#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h> +#include <osmocom/bb/mobile/tch.h> #include <l1ctl_proto.h> static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg); @@ -44,6 +43,8 @@ int mncc_release_ind(struct osmocom_ms *ms, struct gsm_trans *trans, uint32_t callref, int location, int value); static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg); static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg); +static void gsm48_cc_trans_bcap_update(struct gsm_trans *trans, + const struct gsm_mncc *mncc); /* * init @@ -55,7 +56,7 @@ int gsm48_cc_init(struct osmocom_ms *ms) cc->ms = ms; - if (!cc->mncc_upqueue.next == 0) + if (cc->mncc_upqueue.next != NULL) return 0; LOGP(DCC, LOGL_INFO, "init Call Control\n"); @@ -183,11 +184,13 @@ static int gsm48_cc_to_mm(struct msgb *msg, struct gsm_trans *trans, /* enqueue message to application (MNCC-SAP) */ static int mncc_recvmsg(struct osmocom_ms *ms, struct gsm_trans *trans, - int msg_type, struct gsm_mncc *mncc) + uint32_t msg_type, struct gsm_mncc *mncc) { struct gsm48_cclayer *cc = &ms->cclayer; struct msgb *msg; + gsm48_cc_trans_bcap_update(trans, mncc); + if (trans) LOGP(DCC, LOGL_INFO, "(ms %s ti %x) Sending '%s' to MNCC.\n", ms->name, trans->transaction_id, @@ -241,6 +244,8 @@ static void new_cc_state(struct gsm_trans *trans, int state) gsm48_cc_state_name(state)); trans->cc.state = state; + + osmo_signal_dispatch(SS_L23_TRANS, S_L23_CC_TRANS_STATE_CHG, trans); } /* @@ -369,6 +374,9 @@ void _gsm48_cc_trans_free(struct gsm_trans *trans) { gsm48_stop_cc_timer(trans); + talloc_free(trans->cc.bcap); + trans->cc.bcap = NULL; + /* disable audio distribution */ if (trans->ms->mncc_entity.ref == trans->callref) trans->ms->mncc_entity.ref = 0; @@ -384,6 +392,16 @@ void _gsm48_cc_trans_free(struct gsm_trans *trans) new_cc_state(trans, GSM_CSTATE_NULL); } +static void gsm48_cc_trans_bcap_update(struct gsm_trans *trans, + const struct gsm_mncc *mncc) +{ + if (~mncc->fields & MNCC_F_BEARER_CAP) + return; + if (trans->cc.bcap == NULL) + trans->cc.bcap = talloc(trans, struct gsm_mncc_bearer_cap); + memcpy(trans->cc.bcap, &mncc->bearer_cap, sizeof(mncc->bearer_cap)); +} + /* release MM connection, go NULL state, free transaction */ static int gsm48_rel_null_free(struct gsm_trans *trans) { @@ -625,10 +643,14 @@ static int gsm48_cc_rx_progress(struct gsm_trans *trans, struct msgb *msg) LOGP(DCC, LOGL_INFO, "received PROGRESS\n"); + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, + GSM48_IE_PROGR_IND, 0) < 0) { + LOGP(DCC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -EINVAL; + } + memset(&progress, 0, sizeof(struct gsm_mncc)); progress.callref = trans->callref; - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, - GSM48_IE_PROGR_IND, 0); /* progress */ if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) { progress.fields |= MNCC_F_PROGRESS; @@ -656,13 +678,17 @@ static int gsm48_cc_rx_call_proceeding(struct gsm_trans *trans, struct tlv_parsed tp; struct gsm_mncc call_proc; - LOGP(DCC, LOGL_INFO, "sending CALL PROCEEDING\n"); + LOGP(DCC, LOGL_INFO, "received CALL PROCEEDING\n"); + + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0) < 0) { + LOGP(DCC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -EINVAL; + } gsm48_stop_cc_timer(trans); memset(&call_proc, 0, sizeof(struct gsm_mncc)); call_proc.callref = trans->callref; - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); #if 0 /* repeat */ if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_CIR)) @@ -714,12 +740,16 @@ static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg) LOGP(DCC, LOGL_INFO, "received ALERTING\n"); + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0) < 0) { + LOGP(DCC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -EINVAL; + } + gsm48_stop_cc_timer(trans); /* no T301 in MS call control */ memset(&alerting, 0, sizeof(struct gsm_mncc)); alerting.callref = trans->callref; - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); /* facility */ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { alerting.fields |= MNCC_F_FACILITY; @@ -755,11 +785,15 @@ static int gsm48_cc_rx_connect(struct gsm_trans *trans, struct msgb *msg) LOGP(DCC, LOGL_INFO, "received CONNECT\n"); + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0) < 0) { + LOGP(DCC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -EINVAL; + } + gsm48_stop_cc_timer(trans); memset(&connect, 0, sizeof(struct gsm_mncc)); connect.callref = trans->callref; - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); /* facility */ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { connect.fields |= MNCC_F_FACILITY; @@ -825,10 +859,13 @@ static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg) LOGP(DCC, LOGL_INFO, "received SETUP\n"); + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0) < 0) { + LOGP(DCC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -EINVAL; + } + memset(&setup, 0, sizeof(struct gsm_mncc)); setup.callref = trans->callref; - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); - /* bearer capability */ if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { setup.fields |= MNCC_F_BEARER_CAP; @@ -1078,9 +1115,13 @@ static int gsm48_cc_rx_start_dtmf_ack(struct gsm_trans *trans, struct msgb *msg) LOGP(DCC, LOGL_INFO, "received START DTMF ACKNOWLEDGE\n"); + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0) < 0) { + LOGP(DCC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -EINVAL; + } + memset(&dtmf, 0, sizeof(struct gsm_mncc)); dtmf.callref = trans->callref; - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); /* keypad facility */ if (TLVP_PRESENT(&tp, GSM48_IE_KPD_FACILITY)) { dtmf.fields |= MNCC_F_KEYPAD; @@ -1141,9 +1182,13 @@ static int gsm48_cc_rx_stop_dtmf_ack(struct gsm_trans *trans, struct msgb *msg) LOGP(DCC, LOGL_INFO, "received STOP DTMF ACKNOWLEDGE\n"); + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0) < 0) { + LOGP(DCC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -EINVAL; + } + memset(&dtmf, 0, sizeof(struct gsm_mncc)); dtmf.callref = trans->callref; - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); return mncc_recvmsg(trans->ms, trans, MNCC_STOP_DTMF_RSP, &dtmf); } @@ -1337,15 +1382,18 @@ static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg) LOGP(DCC, LOGL_INFO, "received USERINFO\n"); - memset(&user, 0, sizeof(struct gsm_mncc)); - user.callref = trans->callref; if (payload_len < 1) { - LOGP(DCC, LOGL_NOTICE, "Short read of userinfo message " - "error.\n"); + LOGP(DCC, LOGL_NOTICE, "Short read of USERINFO message\n"); return -EINVAL; } - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, - GSM48_IE_USER_USER, 0); + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, + GSM48_IE_USER_USER, 0) < 0) { + LOGP(DCC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -EINVAL; + } + + memset(&user, 0, sizeof(struct gsm_mncc)); + user.callref = trans->callref; /* user-user */ gsm48_decode_useruser(&user.useruser, TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); @@ -1419,17 +1467,20 @@ static int gsm48_cc_rx_modify_reject(struct gsm_trans *trans, struct msgb *msg) LOGP(DCC, LOGL_INFO, "received MODIFY REJECT\n"); + if (payload_len < 1) { + LOGP(DCC, LOGL_NOTICE, "Short read of MODIFY REJECT message\n"); + return -EINVAL; + } + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, + GSM48_IE_BEARER_CAP, GSM48_IE_CAUSE) < 0) { + LOGP(DCC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -EINVAL; + } + gsm48_stop_cc_timer(trans); memset(&modify, 0, sizeof(struct gsm_mncc)); modify.callref = trans->callref; - if (payload_len < 1) { - LOGP(DCC, LOGL_NOTICE, "Short read of modify reject message " - "error.\n"); - return -EINVAL; - } - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, - GSM48_IE_BEARER_CAP, GSM48_IE_CAUSE); /* bearer capability */ if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { modify.fields |= MNCC_F_BEARER_CAP; @@ -1677,14 +1728,18 @@ static int gsm48_cc_rx_disconnect(struct gsm_trans *trans, struct msgb *msg) LOGP(DCC, LOGL_INFO, "received DISCONNECT\n"); + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, + GSM48_IE_CAUSE, 0) < 0) { + LOGP(DCC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -EINVAL; + } + gsm48_stop_cc_timer(trans); new_cc_state(trans, GSM_CSTATE_DISCONNECT_IND); memset(&disc, 0, sizeof(struct gsm_mncc)); disc.callref = trans->callref; - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, - GSM48_IE_CAUSE, 0); /* cause */ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { disc.fields |= MNCC_F_CAUSE; @@ -1726,11 +1781,15 @@ static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg) LOGP(DCC, LOGL_INFO, "received RELEASE\n"); + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0) < 0) { + LOGP(DCC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -EINVAL; + } + gsm48_stop_cc_timer(trans); memset(&rel, 0, sizeof(struct gsm_mncc)); rel.callref = trans->callref; - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); /* cause */ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { rel.fields |= MNCC_F_CAUSE; @@ -1750,7 +1809,7 @@ static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg) TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); } - /* in case we receive a relase, when we are already in NULL state */ + /* in case we receive a release, when we are already in NULL state */ if (trans->cc.state == GSM_CSTATE_NULL) { LOGP(DCC, LOGL_INFO, "ignoring RELEASE in NULL state\n"); /* release MM conn, free trans */ @@ -1795,11 +1854,15 @@ static int gsm48_cc_rx_release_compl(struct gsm_trans *trans, struct msgb *msg) LOGP(DCC, LOGL_INFO, "received RELEASE COMPLETE\n"); + if (tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0) < 0) { + LOGP(DCC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -EINVAL; + } + gsm48_stop_cc_timer(trans); memset(&rel, 0, sizeof(struct gsm_mncc)); rel.callref = trans->callref; - tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); /* cause */ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { rel.fields |= MNCC_F_CAUSE; @@ -1937,10 +2000,16 @@ int mncc_tx_to_cc(void *inst, int msg_type, void *arg) return -EBUSY; } + /* ASCI call does not allow other transactions */ + if (trans_find_ongoing_gcc_bcc(ms)) { + LOGP(DCC, LOGL_NOTICE, "Phone is busy doing ASCI call\n"); + return -EBUSY; + } + data->msg_type = msg_type; /* Find callref */ - trans = trans_find_by_callref(ms, data->callref); + trans = trans_find_by_callref(ms, GSM48_PDISC_CC, data->callref); if (!trans) { /* check for SETUP message */ @@ -1968,9 +2037,15 @@ int mncc_tx_to_cc(void *inst, int msg_type, void *arg) } } + gsm48_cc_trans_bcap_update(trans, data); + switch (msg_type) { case GSM_TCHF_FRAME: - return gsm_send_voice(ms, arg); + case GSM_TCHF_FRAME_EFR: + case GSM_TCHH_FRAME: + case GSM_TCH_FRAME_AMR: + case GSM_BAD_FRAME: + return tch_send_mncc_frame(ms, arg); case MNCC_LCHAN_MODIFY: return 0; case MNCC_FRAME_RECV: @@ -2095,15 +2170,21 @@ static struct datastate { static int gsm48_cc_data_ind(struct gsm_trans *trans, struct msgb *msg) { struct osmocom_ms *ms = trans->ms; - struct gsm48_hdr *gh = msgb_l3(msg); - int msg_type = gh->msg_type & 0xbf; - uint8_t transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4; - /* flip */ + const struct gsm48_hdr *gh = msgb_l3(msg); int msg_supported = 0; /* determine, if message is supported at all */ + uint8_t msg_type; int i, rc; - /* set transaction ID, if not already */ - trans->transaction_id = transaction_id; + if (msgb_l3len(msg) < sizeof(*gh)) { + LOGP(DCC, LOGL_INFO, "%s(): short read of msgb: %s\n", + __func__, msgb_hexdump(msg)); + return -EINVAL; + } + + msg_type = gh->msg_type & 0xbf; + + /* set transaction ID (flip), if not already */ + trans->transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4; /* pull the MMCC header */ msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr)); @@ -2146,7 +2227,7 @@ int gsm48_rcv_cc(struct osmocom_ms *ms, struct msgb *msg) struct gsm_trans *trans; int rc = 0; - trans = trans_find_by_callref(ms, mmh->ref); + trans = trans_find_by_callref(ms, GSM48_PDISC_CC, mmh->ref); if (!trans) { trans = trans_alloc(ms, GSM48_PDISC_CC, mmh->transaction_id, mmh->ref); diff --git a/src/host/layer23/src/mobile/gsm48_mm.c b/src/host/layer23/src/mobile/gsm48_mm.c index 872dce55..fad475e2 100644 --- a/src/host/layer23/src/mobile/gsm48_mm.c +++ b/src/host/layer23/src/mobile/gsm48_mm.c @@ -13,10 +13,6 @@ * 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 <stdint.h> @@ -30,18 +26,24 @@ #include <osmocom/core/utils.h> #include <osmocom/gsm/gsm48.h> #include <osmocom/core/talloc.h> +#include <osmocom/core/timer.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/networks.h> #include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/utils.h> +#include <osmocom/bb/common/subscriber.h> #include <osmocom/bb/mobile/gsm48_cc.h> #include <osmocom/bb/mobile/gsm480_ss.h> #include <osmocom/bb/mobile/gsm411_sms.h> +#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h> #include <osmocom/bb/mobile/app_mobile.h> #include <osmocom/bb/mobile/primitives.h> #include <osmocom/bb/mobile/vty.h> -#include <osmocom/bb/common/utils.h> +#include <osmocom/bb/mobile/gsm48_rr.h> +#include <osmocom/bb/mobile/gsm322.h> extern void *l23_ctx; @@ -59,6 +61,9 @@ static void new_mm_state(struct gsm48_mmlayer *mm, int state, int substate); static int gsm48_mm_loc_upd_normal(struct osmocom_ms *ms, struct msgb *msg); static int gsm48_mm_loc_upd_periodic(struct osmocom_ms *ms, struct msgb *msg); static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg); +static int gsm48_mm_group_reject(struct osmocom_ms *ms, struct msgb *msg); +static int gsm48_mm_group_rel_req(struct osmocom_ms *ms, struct msgb *msg); +static int gsm48_mm_uplink_reject(struct osmocom_ms *ms, struct msgb *msg); /* * notes @@ -109,7 +114,7 @@ static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg); * - cell selected * - cell == SIM LAI * - * Otherwhise PLMN SEARCH is entered. + * Otherwise PLMN SEARCH is entered. * * During PLMN SEARCH NORMAL state: (4.2.2.5) * - on expirery of T3212: Perform periodic location update, when back @@ -143,6 +148,9 @@ static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg); * During LIMITED SERVICE state: (4.2.2.3) * - reject MM connection except for emergency calls * - perform location update, if new LAI is entered + * - indicate GCC/BCC calls with channel description only + * - reject joining to GCC/BCC calls without channel description + * - accept joining to GCC/BCC calls with channel description * * * The LOCATION UPDATE NEEDED state is entered if: @@ -175,6 +183,9 @@ static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg); * - accept MM connection for emergency calls * - trigger location update on any other MM connection * - respond to paging (with IMSI only, because in U2 TMSI is not valid) + * - indicate GCC/BCC calls with channel description only + * - reject joining to GCC/BCC calls without channel description + * - accept joining to GCC/BCC calls with channel description * * * The NORMAL SERVICE state is entered if: @@ -184,16 +195,56 @@ static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg); * - and SIM LAI == cell * * During NORMAL SERVICE state: (4.2.2.1) - * - on expirery of T3211 or T3213: Perform location updated - * - on expirery of T3212: Perform location updated + * - on expirery of T3211 or T3213: Perform location update + * - on expirery of T3212: Perform location update * - on change of LAI: Perform location update * - perform IMSI detach * - perform MM connections * - respond to paging + * - indicate GCC/BCC calls with and without channel description + * - accept joining to GCC/BCC calls without channel description + * -> The GCC/BCC layer waits for channel description before joining. + * - accept joining to GCC/BCC calls with channel description + * + * + * The RECEIVING GROUP CALL (NORMAL SERVICE) is entered if: + * - the upper layer requests to join to GCC/BCC call + * - and service state is NORMAL SERVICE + * + * During RECEIVING GROUP CALL (NORMAL SERVICE) state: (4.2.2.7) + * - reject all MM connections + * - indicate notifications and paging to GCC or BCC layer + * -> If supported by RR layer. + * The following events are not be supported here: + * - perform IMSI detach (This is delayed until the call has ended.) + * - on expirery of T3211 or T3213: Perform location update + * - on expirery of T3212: Perform location update + * - accept MM connections + * - on change of LAI: Perform location update + * - accept joining to GCC/BCC calls without channel description + * - accept joining to GCC/BCC calls with channel description + * + * + * The RECEIVING GROUP CALL (LIMITED SERVICE) is entered if: + * - the upper layer requests to join to GCC/BCC call + * - and service state is LIMITED SERVICE or ATTEMPTING TO UPDATE + * + * During RECEIVING GROUP CALL (LIMITED SERVICE) state: (4.2.2.8) + * - reject all MM connections + * - indicate notifications and paging to GCC or BCC layer + * -> If supported by RR layer. + * The following events are not be supported here: + * - reject MM connection except for emergency calls + * - on expirery of T3212: Perform location updated + * - reject joining to GCC/BCC calls without channel description + * - accept joining to GCC/BCC calls with channel description + * + * + * A group call is only accepted, if there is no other MM connection ongoing. * * * gsm48_mm_set_plmn_search() is used enter PLMN SEARCH or PLMN SEARCH NORMAL - * state. Depending on the conditions above, the appropiate state is selected. + * state. Depending on the conditions above, the appropriate state is selected. * * * gsm48_mm_return_idle() is used to select the Service state when returning @@ -222,6 +273,33 @@ static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg); * * gsm48_mm_cell_selected() is used to select the Service state. * + * + * New primitives are invented for group/broadcast calls. They are not + * specified in any recommendation. They are: + * + * - MMxCC_NOTIF_IND: The MM layer indicates new/updated/ceased calls. This is + * completely independent from the state of the MM layer. + * - MMxCC_GROUP_REQ: The GCC/BCC layer requests group channel in receive mode. + * This mode has no MM connection. Speical flags (mm->vgcs*) are used to + * define that mode and store the reference (callref + group|broadcast). This + * reference is used for messages towards GCC/BCC layer. The state is IDLE + * and the sub-state defines group receive mode at normal service or at + * limited service state. + * - MMxCC_GROUP_CNF: The MM layer confirms group channel. + * - MMxCC_UPLINK_REQ: The GCC/BCC layer requests uplink (group transmit mode). + * The MM layer changes to group transmit mode. + * - MMxCC_UPLINK_CNF: The MM layer confirms uplink. (Uplink was granted.) + * - MMxCC_UPLINK_REL_REQ: The GCC/BCC layer requests release of uplink. + * - MMxCC_UPLINK_REL_IND: The MM layer indicates/confirms release of uplink + * + * Existing primitives are used with group calls: + * + * MMxCC_REL_IND: Failed to establish group receive mode. + * MMxCC_ERR_IND: Abort received from RR layer in group receive or transmit mode. + * MMxCC_REL_REQ: Leave group call (receive mode or transmit mode). + * + * The group call is released at MM layer, if one of the primitives above are + * received or transmitted. */ /* @@ -271,46 +349,68 @@ static int decode_network_name(char *name, int name_len, return length; } -/* encode 'mobile identity' */ -int gsm48_encode_mi(uint8_t *buf, struct msgb *msg, struct osmocom_ms *ms, - uint8_t mi_type) +/* Encode and append 'mobile identity' of given type to message, based on current settings. */ +int gsm48_encode_mi_lv(struct osmocom_ms *ms, struct msgb *msg, uint8_t mi_type, bool emergency_imsi) { struct gsm_subscriber *subscr = &ms->subscr; struct gsm_settings *set = &ms->settings; - uint8_t *ie; + struct osmo_mobile_identity mi = { }; + int rc; + uint8_t *l; - switch(mi_type) { + /* Copy MI values according to their types. */ + switch (mi_type) { case GSM_MI_TYPE_TMSI: - gsm48_generate_mid_from_tmsi(buf, subscr->tmsi); + mi.tmsi = subscr->tmsi; break; case GSM_MI_TYPE_IMSI: - gsm48_generate_mid_from_imsi(buf, subscr->imsi); + if (emergency_imsi) + OSMO_STRLCPY_ARRAY(mi.imsi, set->emergency_imsi); + else + OSMO_STRLCPY_ARRAY(mi.imsi, subscr->imsi); break; case GSM_MI_TYPE_IMEI: - gsm48_generate_mid_from_imsi(buf, set->imei); + OSMO_STRLCPY_ARRAY(mi.imei, set->imei); break; case GSM_MI_TYPE_IMEISV: - gsm48_generate_mid_from_imsi(buf, set->imeisv); - break; - case GSM_MI_TYPE_NONE: - default: - buf[0] = GSM48_IE_MOBILE_ID; - buf[1] = 1; - buf[2] = 0xf0; + OSMO_STRLCPY_ARRAY(mi.imeisv, set->imeisv); break; } - /* alter MI type */ - buf[2] = (buf[2] & ~GSM_MI_TYPE_MASK) | mi_type; - if (msg) { - /* MI as LV */ - ie = msgb_put(msg, 1 + buf[1]); - memcpy(ie, buf + 1, 1 + buf[1]); + /* Generate MI or 'NONE', if not available. */ + switch (mi_type) { + case GSM_MI_TYPE_TMSI: + case GSM_MI_TYPE_IMSI: + case GSM_MI_TYPE_IMEI: + case GSM_MI_TYPE_IMEISV: + mi.type = mi_type; + l = msgb_put(msg, 1); + rc = osmo_mobile_identity_encode_msgb(msg, &mi, true); + if (rc < 0) { + LOGP(DMM, LOGL_ERROR, "Failed to encode mobile identity type %d. Please fix!\n", mi_type); + *l = 1; + msgb_put_u8(msg, 0xf0 | GSM_MI_TYPE_NONE); + break; + } + *l = rc; + break; + case GSM_MI_TYPE_NONE: + default: + msgb_put_u8(msg, 1); + msgb_put_u8(msg, 0xf0 | mi_type); } return 0; } +int gsm48_encode_mi_tlv(struct osmocom_ms *ms, struct msgb *msg, uint8_t mi_type, bool emergency_imsi) +{ + /* Append IE type. */ + msgb_put_u8(msg, GSM48_IE_MOBILE_ID); + + return gsm48_encode_mi_lv(ms, msg, mi_type, emergency_imsi); +} + /* encode 'classmark 1' */ int gsm48_encode_classmark1(struct gsm48_classmark1 *cm, uint8_t rev_lev, uint8_t es_ind, uint8_t a5_1, uint8_t pwr_lev) @@ -554,6 +654,9 @@ static const struct value_string gsm48_mmevent_names[] = { { GSM48_MM_EVENT_SYSINFO, "MM_EVENT_SYSINFO" }, { GSM48_MM_EVENT_USER_PLMN_SEL, "MM_EVENT_USER_PLMN_SEL" }, { GSM48_MM_EVENT_LOST_COVERAGE, "MM_EVENT_LOST_COVERAGE" }, + { GSM48_MM_EVENT_NOTIFICATION, "MM_EVENT_NOTIFICATION" }, + { GSM48_MM_EVENT_UPLINK_FREE, "MM_EVENT_UPLINK_FREE" }, + { GSM48_MM_EVENT_UPLINK_BUSY, "MM_EVENT_UPLINK_BUSY" }, { 0, NULL } }; @@ -638,6 +741,46 @@ static const struct value_string gsm48_mmxx_msg_names[] = { { GSM48_MMSMS_ERR_IND, "MMSMS_ERR_IND" }, { GSM48_MMSMS_PROMPT_IND, "MMSMS_PROMPT_IND" }, { GSM48_MMSMS_PROMPT_REJ, "MMSMS_PROMPT_REJ" }, + { GSM48_MMGCC_EST_REQ, "MMGCC_EST_REQ" }, + { GSM48_MMGCC_EST_CNF, "MMGCC_EST_CNF" }, + { GSM48_MMGCC_REL_REQ, "MMGCC_REL_REQ" }, + { GSM48_MMGCC_REL_IND, "MMGCC_REL_IND" }, + { GSM48_MMGCC_DATA_REQ, "MMGCC_DATA_REQ" }, + { GSM48_MMGCC_DATA_IND, "MMGCC_DATA_IND" }, + { GSM48_MMGCC_UNIT_DATA_REQ, "MMGCC_UNIT_DATA_REQ" }, + { GSM48_MMGCC_UNIT_DATA_IND, "MMGCC_UNIT_DATA_IND" }, + { GSM48_MMGCC_REEST_REQ, "MMBCC_REEST_REQ" }, + { GSM48_MMGCC_REEST_CNF, "MMBCC_REEST_CNF" }, + { GSM48_MMGCC_ERR_IND, "MMGCC_ERR_IND" }, + { GSM48_MMGCC_NOTIF_IND, "MMGCC_NOTIF_IND" }, + { GSM48_MMGCC_GROUP_REQ, "MMGCC_GROUP_REQ" }, + { GSM48_MMGCC_GROUP_CNF, "MMGCC_GROUP_CNF" }, + { GSM48_MMGCC_UPLINK_REQ, "MMGCC_UPLINK_REQ" }, + { GSM48_MMGCC_UPLINK_CNF, "MMGCC_UPLINK_CNF" }, + { GSM48_MMGCC_UPLINK_REL_REQ, "MMGCC_UPLINK_REL_REQ" }, + { GSM48_MMGCC_UPLINK_REL_IND, "MMGCC_UPLINK_REL_CNF" }, + { GSM48_MMGCC_UPLINK_FREE_IND, "MMGCC_UPLINK_FREE_IND" }, + { GSM48_MMGCC_UPLINK_BUSY_IND, "MMGCC_UPLINK_BUSY_IND" }, + { GSM48_MMBCC_EST_REQ, "MMBCC_EST_REQ" }, + { GSM48_MMBCC_EST_CNF, "MMBCC_EST_CNF" }, + { GSM48_MMBCC_REL_REQ, "MMBCC_REL_REQ" }, + { GSM48_MMBCC_REL_IND, "MMBCC_REL_IND" }, + { GSM48_MMBCC_DATA_REQ, "MMBCC_DATA_REQ" }, + { GSM48_MMBCC_DATA_IND, "MMBCC_DATA_IND" }, + { GSM48_MMBCC_UNIT_DATA_REQ, "MMBCC_UNIT_DATA_REQ" }, + { GSM48_MMBCC_UNIT_DATA_IND, "MMBCC_UNIT_DATA_IND" }, + { GSM48_MMBCC_REEST_REQ, "MMBCC_REEST_REQ" }, + { GSM48_MMBCC_REEST_CNF, "MMBCC_REEST_CNF" }, + { GSM48_MMBCC_ERR_IND, "MMBCC_ERR_IND" }, + { GSM48_MMBCC_NOTIF_IND, "MMBCC_NOTIF_IND" }, + { GSM48_MMBCC_GROUP_REQ, "MMBCC_GROUP_REQ" }, + { GSM48_MMBCC_GROUP_CNF, "MMBCC_GROUP_CNF" }, + { GSM48_MMBCC_UPLINK_REQ, "MMBCC_UPLINK_REQ" }, + { GSM48_MMBCC_UPLINK_CNF, "MMBCC_UPLINK_CNF" }, + { GSM48_MMBCC_UPLINK_REL_REQ, "MMBCC_UPLINK_REL_REQ" }, + { GSM48_MMBCC_UPLINK_REL_IND, "MMBCC_UPLINK_REL_CNF" }, + { GSM48_MMBCC_UPLINK_FREE_IND, "MMBCC_UPLINK_FREE_IND" }, + { GSM48_MMBCC_UPLINK_BUSY_IND, "MMBCC_UPLINK_BUSY_IND" }, { 0, NULL } }; @@ -763,6 +906,10 @@ int gsm48_mmxx_dequeue(struct osmocom_ms *ms) case GSM48_MMSMS_CLASS: gsm411_rcv_sms(ms, msg); break; + case GSM48_MMGCC_CLASS: + case GSM48_MMBCC_CLASS: + gsm44068_rcv_gcc_bcc(ms, msg); + break; } msgb_free(msg); work = 1; /* work done */ @@ -864,8 +1011,8 @@ const char *gsm48_mm_state_names[] = { "wait for RR connection active", "MM idle", "wait for additional outgoing MM connection", - "MM_CONN_ACTIVE_VGCS", - "WAIT_RR_CONN_VGCS", + "MM connection active (group transmit mode)", + "wait for RR connection (group transmit mode)", "location updating pending", "IMSI detach pending", "RR connection release not allowed" @@ -881,8 +1028,8 @@ const char *gsm48_mm_substate_names[] = { "location updating needed", "PLMN search", "PLMN search (normal)", - "RX_VGCS_NORMAL", - "RX_VGCS_LIMITED" + "receiving group call (normal service)", + "receiving group call (limiteed service)" }; /* change state from LOCATION UPDATE NEEDED to ATTEMPTING TO UPDATE */ @@ -925,34 +1072,39 @@ static void new_mm_state(struct gsm48_mmlayer *mm, int state, int substate) && (mm->state != GSM48_MM_ST_MM_IDLE || mm->substate != substate)) { switch (substate) { case GSM48_MM_SST_NORMAL_SERVICE: - vty_notify(ms, NULL); - vty_notify(ms, "On Network, normal service: %s, %s\n", - gsm_get_mcc(plmn->mcc), - gsm_get_mnc(plmn->mcc, plmn->mnc)); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "On Network, normal service: %s, %s\n", + gsm_get_mcc(plmn->plmn.mcc), + gsm_get_mnc(&plmn->plmn)); break; case GSM48_MM_SST_LIMITED_SERVICE: - vty_notify(ms, NULL); - vty_notify(ms, "Limited service, emergency calls are " + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Limited service, emergency calls are " "possible.\n"); break; case GSM48_MM_SST_PLMN_SEARCH_NORMAL: case GSM48_MM_SST_PLMN_SEARCH: - vty_notify(ms, NULL); - vty_notify(ms, "Searching network...\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Searching network...\n"); break; case GSM48_MM_SST_NO_IMSI: - vty_notify(ms, NULL); - vty_notify(ms, "No SIM, emergency calls are " + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No SIM, emergency calls are " "possible.\n"); break; case GSM48_MM_SST_NO_CELL_AVAIL: - vty_notify(ms, NULL); - vty_notify(ms, "No service.\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No service.\n"); break; case GSM48_MM_SST_ATTEMPT_UPDATE: - vty_notify(ms, NULL); - vty_notify(ms, "Trying to registering with " - "network...\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Trying to register with network %s, %s...\n", + gsm_get_mcc(plmn->plmn.mcc), gsm_get_mnc(&plmn->plmn)); + break; + case GSM48_MM_SST_RX_VGCS_NORMAL: + case GSM48_MM_SST_RX_VGCS_LIMITED: + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Listening to %s call.\n", (mm->vgcs.group_call) ? "group" : "broadcast"); break; } } @@ -975,6 +1127,8 @@ static void new_mm_state(struct gsm48_mmlayer *mm, int state, int substate) if (!nmsg) return; gsm48_mmevent_msg(ms, nmsg); + /* Stop here and wait for the IMSI_DETACH event being handled. */ + return; } /* 4.4.2 start T3212 in MM IDLE mode if not started or has expired */ @@ -1006,7 +1160,7 @@ static void new_mm_state(struct gsm48_mmlayer *mm, int state, int substate) if (s->t3212) /* still required? */ gsm48_mm_loc_upd_periodic(ms, NULL); else - LOGP(DMM, LOGL_INFO, "but not requred\n"); + LOGP(DMM, LOGL_INFO, "but not required\n"); /* must exit, because this function can be called * recursively */ @@ -1021,6 +1175,15 @@ static void new_mm_state(struct gsm48_mmlayer *mm, int state, int substate) /* must exit, because this function can be called recursively */ return; } + + /* Tell the group call that we are ready for a new channel activation. */ + if (state == GSM48_MM_ST_MM_IDLE + && (substate == GSM48_MM_SST_NORMAL_SERVICE + || substate == GSM48_MM_SST_ATTEMPT_UPDATE)) { + gsm44068_rcv_mm_idle(ms); + /* must exit, because this function can be called recursively */ + return; + } } /* return PLMN SEARCH or PLMN SEARCH NORMAL state */ @@ -1042,7 +1205,7 @@ static int gsm48_mm_set_plmn_search(struct osmocom_ms *ms) "SIM not updated.\n"); return GSM48_MM_SST_PLMN_SEARCH; } - if (subscr->lac == 0x0000 || subscr->lac >= 0xfffe) { + if (subscr->lai.lac == 0x0000 || subscr->lai.lac >= 0xfffe) { LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH state, because " "LAI in SIM not valid.\n"); return GSM48_MM_SST_PLMN_SEARCH; @@ -1056,15 +1219,12 @@ static int gsm48_mm_set_plmn_search(struct osmocom_ms *ms) } /* selected cell's LAI not equal to LAI stored on the sim */ - if (cs->sel_mcc != subscr->mcc - || cs->sel_mnc != subscr->mnc - || cs->sel_lac != subscr->lac) { + if (osmo_lai_cmp(&cs->sel_cgi.lai, &subscr->lai) != 0) { LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH state, because " - "LAI of selected cell (MCC %s MNC %s LAC 0x%04x) " - "!= LAI in SIM (MCC %s MNC %s LAC 0x%04x).\n", - gsm_print_mcc(cs->sel_mcc), gsm_print_mnc(cs->sel_mnc), - cs->sel_lac, gsm_print_mcc(subscr->mcc), - gsm_print_mnc(subscr->mnc), subscr->lac); + "LAI of selected cell (MCC-MNC %s LAC 0x%04x) " + "!= LAI in SIM (MCC-MNC %s LAC 0x%04x).\n", + osmo_plmn_name(&cs->sel_cgi.lai.plmn), cs->sel_cgi.lai.lac, + osmo_plmn_name2(&subscr->lai.plmn), subscr->lai.lac); return GSM48_MM_SST_PLMN_SEARCH; } @@ -1081,6 +1241,14 @@ static int gsm48_mm_return_idle(struct osmocom_ms *ms, struct msgb *msg) struct gsm322_cellsel *cs = &ms->cellsel; struct gsm48_sysinfo *s = &cs->sel_si; + /* When we are in group transmit mode, we return to group receive mode. */ + if (mm->vgcs.enabled) { + LOGP(DMM, LOGL_INFO, "Return to group receive mode.\n"); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, (mm->vgcs.normal_service) ? GSM48_MM_SST_RX_VGCS_NORMAL + : GSM48_MM_SST_RX_VGCS_LIMITED); + return 0; + } + if (cs->state != GSM322_C3_CAMPED_NORMALLY && cs->state != GSM322_C7_CAMPED_ANY_CELL) { LOGP(DMM, LOGL_INFO, "Not camping, wait for CS process to " @@ -1120,10 +1288,8 @@ static int gsm48_mm_return_idle(struct osmocom_ms *ms, struct msgb *msg) /* if we are attached and selected cell equals the registered LAI */ if (subscr->imsi_attached - && subscr->lac /* valid */ - && cs->sel_mcc == subscr->mcc - && cs->sel_mnc == subscr->mnc - && cs->sel_lac == subscr->lac) { + && subscr->lai.lac /* valid */ + && (osmo_lai_cmp(&cs->sel_cgi.lai, &subscr->lai) == 0)) { LOGP(DMM, LOGL_INFO, "We are in registered LAI as returning " "to MM IDLE\n"); /* if SIM not updated (abnormal case as described in 4.4.4.9) */ @@ -1131,8 +1297,7 @@ static int gsm48_mm_return_idle(struct osmocom_ms *ms, struct msgb *msg) new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_ATTEMPT_UPDATE); else - new_mm_state(mm, GSM48_MM_ST_MM_IDLE, - GSM48_MM_SST_NORMAL_SERVICE); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_NORMAL_SERVICE); return 0; } @@ -1140,27 +1305,22 @@ static int gsm48_mm_return_idle(struct osmocom_ms *ms, struct msgb *msg) if (cs->state == GSM322_C3_CAMPED_NORMALLY) { LOGP(DMM, LOGL_INFO, "We are camping normally as returning to " "MM IDLE\n"); - if (gsm_subscr_is_forbidden_plmn(subscr, cs->sel_mcc, - cs->sel_mnc)) { + if (gsm_subscr_is_forbidden_plmn(subscr, &cs->sel_cgi.lai.plmn)) { /* location update not allowed */ LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed PLMN.\n"); - new_mm_state(mm, GSM48_MM_ST_MM_IDLE, - GSM48_MM_SST_LIMITED_SERVICE); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_LIMITED_SERVICE); } else - if (gsm322_is_forbidden_la(ms, cs->sel_mcc, cs->sel_mnc, - cs->sel_lac)) { + if (gsm322_is_forbidden_la(ms, &cs->sel_cgi.lai)) { /* location update not allowed */ LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed LA.\n"); - new_mm_state(mm, GSM48_MM_ST_MM_IDLE, - GSM48_MM_SST_LIMITED_SERVICE); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_LIMITED_SERVICE); } else /* 4.4.4.9 if cell is barred, don't start */ if ((!subscr->acc_barr && s->cell_barr) || (!subscr->acc_barr && !((subscr->acc_class & 0xfbff) & (s->class_barr ^ 0xffff)))) { LOGP(DMM, LOGL_INFO, "Loc. upd. no access.\n"); - new_mm_state(mm, GSM48_MM_ST_MM_IDLE, - GSM48_MM_SST_LIMITED_SERVICE); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_LIMITED_SERVICE); } else { /* location update allowed */ LOGP(DMM, LOGL_INFO, "Loc. upd. allowed.\n"); @@ -1171,8 +1331,7 @@ static int gsm48_mm_return_idle(struct osmocom_ms *ms, struct msgb *msg) /* location update not allowed */ LOGP(DMM, LOGL_INFO, "We are camping on any cell as returning " "to MM IDLE\n"); - new_mm_state(mm, GSM48_MM_ST_MM_IDLE, - GSM48_MM_SST_LIMITED_SERVICE); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_LIMITED_SERVICE); } return 0; @@ -1200,6 +1359,13 @@ static int gsm48_mm_cell_selected(struct osmocom_ms *ms, struct msgb *msg) struct gsm48_sysinfo *s = &cs->sel_si; struct gsm_settings *set = &ms->settings; + if (mm->vgcs.enabled) { + LOGP(DMM, LOGL_ERROR, "Cell selection in group receive mode, this should not happen.\n"); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, (mm->vgcs.normal_service) ? GSM48_MM_SST_RX_VGCS_NORMAL + : GSM48_MM_SST_RX_VGCS_LIMITED); + return 0; + } + /* no SIM is inserted */ if (!subscr->sim_valid) { LOGP(DMM, LOGL_INFO, "SIM invalid as cell is selected.\n"); @@ -1210,17 +1376,14 @@ static int gsm48_mm_cell_selected(struct osmocom_ms *ms, struct msgb *msg) /* SIM not updated in this LA */ if (subscr->ustate == GSM_SIM_U1_UPDATED - && subscr->lac /* valid */ - && cs->sel_mcc == subscr->mcc - && cs->sel_mnc == subscr->mnc - && cs->sel_lac == subscr->lac + && subscr->lai.lac /* valid */ + && (osmo_lai_cmp(&cs->sel_cgi.lai, &subscr->lai) == 0) && !mm->lupd_periodic) { if (subscr->imsi_attached) { struct msgb *nmsg; LOGP(DMM, LOGL_INFO, "Valid in location area.\n"); - new_mm_state(mm, GSM48_MM_ST_MM_IDLE, - GSM48_MM_SST_NORMAL_SERVICE); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_NORMAL_SERVICE); /* send message to PLMN search process */ nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_SUCCESS); @@ -1234,8 +1397,7 @@ static int gsm48_mm_cell_selected(struct osmocom_ms *ms, struct msgb *msg) struct msgb *nmsg; LOGP(DMM, LOGL_INFO, "Attachment not required.\n"); - new_mm_state(mm, GSM48_MM_ST_MM_IDLE, - GSM48_MM_SST_NORMAL_SERVICE); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_NORMAL_SERVICE); /* send message to PLMN search process */ nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_SUCCESS); @@ -1251,14 +1413,12 @@ static int gsm48_mm_cell_selected(struct osmocom_ms *ms, struct msgb *msg) /* PLMN mode auto and selected cell is forbidden */ if (set->plmn_mode == PLMN_MODE_AUTO && (!cs->selected - || gsm_subscr_is_forbidden_plmn(subscr, cs->sel_mcc, cs->sel_mnc) - || gsm322_is_forbidden_la(ms, cs->sel_mcc, cs->sel_mnc, - cs->sel_lac))) { + || gsm_subscr_is_forbidden_plmn(subscr, &cs->sel_cgi.lai.plmn) + || gsm322_is_forbidden_la(ms, &cs->sel_cgi.lai))) { struct msgb *nmsg; LOGP(DMM, LOGL_INFO, "Selected cell is forbidden.\n"); - new_mm_state(mm, GSM48_MM_ST_MM_IDLE, - GSM48_MM_SST_LIMITED_SERVICE); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_LIMITED_SERVICE); /* send message to PLMN search process */ nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_FAILED); @@ -1273,14 +1433,12 @@ static int gsm48_mm_cell_selected(struct osmocom_ms *ms, struct msgb *msg) * in M3 state the PLMN is not selected for registration. */ if (set->plmn_mode == PLMN_MODE_MANUAL && (!cs->selected - || plmn->mcc != cs->sel_mcc - || plmn->mnc != cs->sel_mnc + || (osmo_plmn_cmp(&plmn->plmn, &cs->sel_cgi.lai.plmn) != 0) || plmn->state == GSM322_M3_NOT_ON_PLMN)) { struct msgb *nmsg; LOGP(DMM, LOGL_INFO, "Selected cell not found.\n"); - new_mm_state(mm, GSM48_MM_ST_MM_IDLE, - GSM48_MM_SST_LIMITED_SERVICE); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_LIMITED_SERVICE); /* send message to PLMN search process */ nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_FAILED); @@ -1388,9 +1546,8 @@ uint32_t mm_conn_new_ref = 0x80000001; /* new MM connection state */ static void new_conn_state(struct gsm48_mm_conn *conn, int state) { - LOGP(DMM, LOGL_INFO, "(ref %x) new state %s -> %s\n", conn->ref, - gsm48_mmxx_state_names[conn->state], - gsm48_mmxx_state_names[state]); + LOGP(DMM, LOGL_INFO, "(ref 0x%x proto %d) new state %s -> %s\n", conn->ref, conn->protocol, + gsm48_mmxx_state_names[conn->state], gsm48_mmxx_state_names[state]); conn->state = state; } @@ -1409,13 +1566,35 @@ struct gsm48_mm_conn *mm_conn_by_id(struct gsm48_mmlayer *mm, } /* find MM connection by reference */ -struct gsm48_mm_conn *mm_conn_by_ref(struct gsm48_mmlayer *mm, - uint32_t ref) +struct gsm48_mm_conn *mm_conn_by_ref_and_class(struct gsm48_mmlayer *mm, + uint32_t ref, uint16_t cls) { struct gsm48_mm_conn *conn; + uint8_t protocol; + + switch (cls) { + case GSM48_MMCC_CLASS: + protocol = GSM48_PDISC_CC; + break; + case GSM48_MMSS_CLASS: + protocol = GSM48_PDISC_NC_SS; + break; + case GSM48_MMSMS_CLASS: + protocol = GSM48_PDISC_SMS; + break; + case GSM48_MMGCC_CLASS: + protocol = GSM48_PDISC_GROUP_CC; + break; + case GSM48_MMBCC_CLASS: + protocol = GSM48_PDISC_BCAST_CC; + break; + default: + LOGP(DMM, LOGL_ERROR, "Invalid message class 0x%03x. Please fix!", cls); + return NULL; + } llist_for_each_entry(conn, &mm->mm_conn, list) { - if (conn->ref == ref) + if (conn->ref == ref && conn->protocol == protocol) return conn; } return NULL; @@ -1465,6 +1644,7 @@ static int gsm48_mm_release_mm_conn(struct osmocom_ms *ms, int abort_any, struct gsm48_mm_conn *conn, *conn2; struct msgb *nmsg; struct gsm48_mmxx_hdr *nmmh; + int msg_type; /* Note: For SAPI 0 all connections are released */ @@ -1480,35 +1660,44 @@ static int gsm48_mm_release_mm_conn(struct osmocom_ms *ms, int abort_any, /* abort any OR the pending connection */ if ((abort_any || conn->state == GSM48_MMXX_ST_CONN_PEND) && (sapi == conn->sapi || sapi == 0)) { - /* send MMxx-REL-IND */ - nmsg = NULL; + /* send MMXX-REL-IND or MMXX-ERR-IND */ switch(conn->protocol) { case GSM48_PDISC_CC: - nmsg = gsm48_mmxx_msgb_alloc( - error ? GSM48_MMCC_ERR_IND - : GSM48_MMCC_REL_IND, conn->ref, - conn->transaction_id, - conn->sapi); + msg_type = (error) ? GSM48_MMCC_ERR_IND + : GSM48_MMCC_REL_IND; break; case GSM48_PDISC_NC_SS: - nmsg = gsm48_mmxx_msgb_alloc( - error ? GSM48_MMSS_ERR_IND - : GSM48_MMSS_REL_IND, conn->ref, - conn->transaction_id, - conn->sapi); + msg_type = (error) ? GSM48_MMSS_ERR_IND + : GSM48_MMSS_REL_IND; break; case GSM48_PDISC_SMS: - nmsg = gsm48_mmxx_msgb_alloc( - error ? GSM48_MMSMS_ERR_IND - : GSM48_MMSMS_REL_IND, conn->ref, - conn->transaction_id, - conn->sapi); + msg_type = (error) ? GSM48_MMSMS_ERR_IND + : GSM48_MMSMS_REL_IND; + break; + case GSM48_PDISC_GROUP_CC: + msg_type = (error) ? GSM48_MMGCC_ERR_IND + : GSM48_MMGCC_REL_IND; break; + case GSM48_PDISC_BCAST_CC: + msg_type = (error) ? GSM48_MMBCC_ERR_IND + : GSM48_MMBCC_REL_IND; + break; + default: + msg_type = -1; } - if (!nmsg) { + if (msg_type == -1) { /* this should not happen */ + LOGP(DMM, LOGL_ERROR, "MM connection of " + "unsupported protocol? Please fix!\n"); + mm_conn_free(conn); + continue; + } + nmsg = gsm48_mmxx_msgb_alloc(msg_type, conn->ref, + conn->transaction_id, + conn->sapi); + if (!nmsg) { mm_conn_free(conn); - continue; /* skip if not of CC type */ + continue; } nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; nmmh->cause = cause; @@ -1584,7 +1773,7 @@ static int gsm48_mm_rx_tmsi_realloc_cmd(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } /* LAI */ - gsm48_decode_lai_hex(lai, &subscr->mcc, &subscr->mnc, &subscr->lac); + gsm48_decode_lai2(lai, &subscr->lai); /* MI */ mi = gh->data + sizeof(struct gsm48_loc_area_id); mi_type = mi[1] & GSM_MI_TYPE_MASK; @@ -1600,12 +1789,12 @@ static int gsm48_mm_rx_tmsi_realloc_cmd(struct osmocom_ms *ms, struct msgb *msg) gsm48_mm_tx_tmsi_reall_cpl(ms); break; case GSM_MI_TYPE_IMSI: - subscr->tmsi = 0xffffffff; + subscr->tmsi = GSM_RESERVED_TMSI; LOGP(DMM, LOGL_INFO, "TMSI removed.\n"); gsm48_mm_tx_tmsi_reall_cpl(ms); break; default: - subscr->tmsi = 0xffffffff; + subscr->tmsi = GSM_RESERVED_TMSI; LOGP(DMM, LOGL_NOTICE, "TMSI reallocation with unknown MI " "type %d.\n", mi_type); gsm48_mm_tx_mm_status(ms, GSM48_REJECT_INCORRECT_MESSAGE); @@ -1697,8 +1886,8 @@ static int gsm48_mm_rx_auth_rej(struct osmocom_ms *ms, struct msgb *msg) subscr->sim_valid = 0; /* TMSI and LAI invalid */ - subscr->tmsi = 0xffffffff; - subscr->lac = 0x0000; + subscr->tmsi = GSM_RESERVED_TMSI; + subscr->lai.lac = 0x0000; /* key is invalid */ subscr->key_seq = 7; @@ -1754,7 +1943,7 @@ static int gsm48_mm_rx_id_req(struct osmocom_ms *ms, struct msgb *msg) return gsm48_mm_tx_mm_status(ms, GSM48_REJECT_MSG_NOT_COMPATIBLE); } - if (mi_type == GSM_MI_TYPE_TMSI && subscr->tmsi == 0xffffffff) { + if (mi_type == GSM_MI_TYPE_TMSI && subscr->tmsi == GSM_RESERVED_TMSI) { LOGP(DMM, LOGL_INFO, "IDENTITY REQUEST of TMSI, but we have no " "TMSI\n"); return gsm48_mm_tx_mm_status(ms, @@ -1769,7 +1958,6 @@ static int gsm48_mm_tx_id_rsp(struct osmocom_ms *ms, uint8_t mi_type) { struct msgb *nmsg; struct gsm48_hdr *ngh; - uint8_t buf[11]; LOGP(DMM, LOGL_INFO, "IDENTITY RESPONSE\n"); @@ -1781,8 +1969,8 @@ static int gsm48_mm_tx_id_rsp(struct osmocom_ms *ms, uint8_t mi_type) ngh->proto_discr = GSM48_PDISC_MM; ngh->msg_type = GSM48_MT_MM_ID_RESP; - /* MI */ - gsm48_encode_mi(buf, nmsg, ms, mi_type); + /* MI (LV) */ + gsm48_encode_mi_lv(ms, nmsg, mi_type, false); /* push RR header and send down */ return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_DATA_REQ, 0, 0); @@ -1800,7 +1988,6 @@ static int gsm48_mm_tx_imsi_detach(struct osmocom_ms *ms, int rr_prim) struct msgb *nmsg; struct gsm48_hdr *ngh; uint8_t pwr_lev; - uint8_t buf[11]; struct gsm48_classmark1 cm; @@ -1821,13 +2008,13 @@ static int gsm48_mm_tx_imsi_detach(struct osmocom_ms *ms, int rr_prim) pwr_lev = gsm48_current_pwr_lev(set, rr->cd_now.arfcn); gsm48_encode_classmark1(&cm, sup->rev_lev, sup->es_ind, set->a5_1, pwr_lev); - msgb_v_put(nmsg, *((uint8_t *)&cm)); - /* MI */ - if (subscr->tmsi != 0xffffffff) { /* have TMSI ? */ - gsm48_encode_mi(buf, nmsg, ms, GSM_MI_TYPE_TMSI); + msgb_v_put(nmsg, *((uint8_t *)&cm)); + /* MI (LV) */ + if (subscr->tmsi != GSM_RESERVED_TMSI) { /* have TMSI ? */ + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_TMSI, false); LOGP(DMM, LOGL_INFO, " using TMSI 0x%08x\n", subscr->tmsi); } else { - gsm48_encode_mi(buf, nmsg, ms, GSM_MI_TYPE_IMSI); + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_IMSI, false); LOGP(DMM, LOGL_INFO, " using IMSI %s\n", subscr->imsi); } @@ -1962,6 +2149,34 @@ static int gsm48_mm_imsi_detach_release(struct osmocom_ms *ms, struct msgb *msg) return gsm48_mm_imsi_detach_sent(ms, msg); } +/* Detach during VGCS. Queue and return idle. */ +static int gsm48_mm_imsi_detach_vgcs(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct msgb *nmsg; + struct gsm48_mmxx_hdr *nmmh; + int msg_type; + + LOGP(DMM, LOGL_INFO, "IMSI detach delayed until group receive/transmit mode is left.\n"); + + /* remember to detach later */ + mm->delay_detach = 1; + + /* Release group call. */ + gsm48_mm_group_rel_req(ms, msg); + + /* Release message to GCC/BCC layer */ + msg_type = (mm->vgcs.group_call) ? GSM48_MMGCC_REL_IND : GSM48_MMBCC_REL_IND; + nmsg = gsm48_mmxx_msgb_alloc(msg_type, mm->vgcs.callref, 0xff, 0); + if (!nmsg) + return -ENOMEM; + nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; + nmmh->cause = GSM48_CC_CAUSE_NORMAL_UNSPEC; + + gsm48_mmxx_upmsg(ms, nmsg); + return 0; +} + /* ignore ongoing IMSI detach */ static int gsm48_mm_imsi_detach_ignore(struct osmocom_ms *ms, struct msgb *msg) { @@ -2016,8 +2231,8 @@ static int gsm48_mm_rx_abort(struct osmocom_ms *ms, struct msgb *msg) subscr->sim_valid = 0; /* TMSI and LAI invalid */ - subscr->tmsi = 0xffffffff; - subscr->lac = 0x0000; + subscr->tmsi = GSM_RESERVED_TMSI; + subscr->lai.lac = 0x0000; /* key is invalid */ subscr->key_seq = 7; @@ -2044,11 +2259,13 @@ static int gsm48_mm_rx_info(struct osmocom_ms *ms, struct msgb *msg) struct tlv_parsed tp; if (payload_len < 0) { - LOGP(DMM, LOGL_NOTICE, "Short read of MM INFORMATION message " - "error.\n"); + LOGP(DMM, LOGL_ERROR, "Short read of MM INFORMATION message\n"); + return -EINVAL; + } + if (tlv_parse(&tp, &gsm48_mm_att_tlvdef, gh->data, payload_len, 0, 0) < 0) { + LOGP(DMM, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); return -EINVAL; } - tlv_parse(&tp, &gsm48_mm_att_tlvdef, gh->data, payload_len, 0, 0); /* long name */ if (TLVP_PRESENT(&tp, GSM48_IE_NAME_LONG)) { @@ -2078,6 +2295,8 @@ static int gsm48_mm_sysinfo(struct osmocom_ms *ms, struct msgb *msg) if (mm->state == GSM48_MM_ST_MM_IDLE && (mm->substate == GSM48_MM_SST_NO_CELL_AVAIL || mm->substate == GSM48_MM_SST_LIMITED_SERVICE + || mm->substate == GSM48_MM_SST_RX_VGCS_NORMAL + || mm->substate == GSM48_MM_SST_RX_VGCS_LIMITED || mm->substate == GSM48_MM_SST_PLMN_SEARCH || mm->substate == GSM48_MM_SST_PLMN_SEARCH_NORMAL)) return 0; @@ -2135,7 +2354,7 @@ static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg) /* (re)start only if we still require location update */ if (!mm->lupd_pending) { LOGP(DMM, LOGL_INFO, "No loc. upd. pending.\n"); - /* use MM IDLE to selecte the idle state */ + /* use MM IDLE to select the idle state */ return gsm48_mm_return_idle(ms, NULL); } @@ -2159,7 +2378,7 @@ static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg) if (!nmsg) return -ENOMEM; gsm322_plmn_sendmsg(ms, nmsg); - /* use MM IDLE to selecte the idle state */ + /* use MM IDLE to select the idle state */ return gsm48_mm_return_idle(ms, NULL); } @@ -2169,20 +2388,18 @@ static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg) "forbidden PLMN.\n"); LOGP(DSUM, LOGL_INFO, "Location updating is disabled by " "configuration\n"); - gsm_subscr_add_forbidden_plmn(subscr, cs->sel_mcc, - cs->sel_mnc, GSM48_REJECT_PLMN_NOT_ALLOWED); + gsm_subscr_add_forbidden_plmn(subscr, &cs->sel_cgi.lai.plmn, GSM48_REJECT_PLMN_NOT_ALLOWED); msg_type = GSM322_EVENT_REG_FAILED; goto _stop; } /* if LAI is forbidden, don't start */ - if (gsm_subscr_is_forbidden_plmn(subscr, cs->sel_mcc, cs->sel_mnc)) { + if (gsm_subscr_is_forbidden_plmn(subscr, &cs->sel_cgi.lai.plmn)) { LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed PLMN.\n"); msg_type = GSM322_EVENT_REG_FAILED; goto stop; } - if (gsm322_is_forbidden_la(ms, cs->sel_mcc, - cs->sel_mnc, cs->sel_lac)) { + if (gsm322_is_forbidden_la(ms, &cs->sel_cgi.lai)) { LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed LA.\n"); msg_type = GSM322_EVENT_REG_FAILED; goto stop; @@ -2197,13 +2414,9 @@ static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg) goto stop; } - mm->lupd_mcc = cs->sel_mcc; - mm->lupd_mnc = cs->sel_mnc; - mm->lupd_lac = cs->sel_lac; + memcpy(&mm->lupd_lai, &cs->sel_cgi.lai, sizeof(mm->lupd_lai)); - LOGP(DSUM, LOGL_INFO, "Perform location update (MCC %s, MNC %s " - "LAC 0x%04x)\n", gsm_print_mcc(mm->lupd_mcc), - gsm_print_mnc(mm->lupd_mnc), mm->lupd_lac); + LOGP(DSUM, LOGL_INFO, "Perform location update (LAI=%s)\n", osmo_lai_name(&mm->lupd_lai)); return gsm48_mm_tx_loc_upd_req(ms); } @@ -2215,6 +2428,7 @@ static int gsm48_mm_loc_upd_normal(struct osmocom_ms *ms, struct msgb *msg) struct gsm_subscriber *subscr = &ms->subscr; struct gsm322_cellsel *cs = &ms->cellsel; struct gsm48_sysinfo *s = &cs->sel_si; + bool vgcs = mm->vgcs.enabled; struct msgb *nmsg; /* in case we already have a location update going on */ @@ -2246,17 +2460,16 @@ static int gsm48_mm_loc_upd_normal(struct osmocom_ms *ms, struct msgb *msg) /* if location update is not required */ if (subscr->ustate == GSM_SIM_U1_UPDATED && cs->selected - && cs->sel_mcc == subscr->mcc - && cs->sel_mnc == subscr->mnc - && cs->sel_lac == subscr->lac + && (osmo_lai_cmp(&cs->sel_cgi.lai, &subscr->lai) == 0) && (subscr->imsi_attached || !s->att_allowed)) { LOGP(DMM, LOGL_INFO, "Loc. upd. not required.\n"); - subscr->imsi_attached = 1; + subscr->imsi_attached = true; /* go straight to normal service state */ new_mm_state(mm, GSM48_MM_ST_MM_IDLE, - GSM48_MM_SST_NORMAL_SERVICE); + (vgcs) ? GSM48_MM_SST_RX_VGCS_NORMAL + : GSM48_MM_SST_NORMAL_SERVICE); #if 0 /* don't send message, if we got not triggered by PLMN search */ @@ -2276,9 +2489,7 @@ static int gsm48_mm_loc_upd_normal(struct osmocom_ms *ms, struct msgb *msg) /* 4.4.3 is attachment required? */ if (subscr->ustate == GSM_SIM_U1_UPDATED && cs->selected - && cs->sel_mcc == subscr->mcc - && cs->sel_mnc == subscr->mnc - && cs->sel_lac == subscr->lac + && (osmo_lai_cmp(&cs->sel_cgi.lai, &subscr->lai) == 0) && !subscr->imsi_attached && s->att_allowed) { /* do location update for IMSI attach */ @@ -2335,7 +2546,6 @@ static int gsm48_mm_tx_loc_upd_req(struct osmocom_ms *ms) struct gsm48_hdr *ngh; struct gsm48_loc_upd_req *nlu; /* NOTE: mi_len is part of struct */ uint8_t pwr_lev; - uint8_t buf[11]; LOGP(DMM, LOGL_INFO, "LOCATION UPDATING REQUEST\n"); @@ -2343,7 +2553,8 @@ static int gsm48_mm_tx_loc_upd_req(struct osmocom_ms *ms) if (!nmsg) return -ENOMEM; ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh)); - nlu = (struct gsm48_loc_upd_req *)msgb_put(nmsg, sizeof(*nlu)); + /* Do not add mi_len to the message, this is done at gsm48_encode_mi_lv(). */ + nlu = (struct gsm48_loc_upd_req *)msgb_put(nmsg, sizeof(*nlu) - 1); ngh->proto_discr = GSM48_PDISC_MM; ngh->msg_type = GSM48_MT_MM_LOC_UPD_REQUEST; @@ -2356,24 +2567,20 @@ static int gsm48_mm_tx_loc_upd_req(struct osmocom_ms *ms) * * NOTE: The TMSI is only valid within a LAI! */ - gsm48_encode_lai_hex(&nlu->lai, subscr->mcc, subscr->mnc, subscr->lac); - LOGP(DMM, LOGL_INFO, " using LAI (mcc %s mnc %s " "lac 0x%04x)\n", - gsm_print_mcc(subscr->mcc), - gsm_print_mnc(subscr->mnc), subscr->lac); + gsm48_generate_lai2(&nlu->lai, &subscr->lai); + LOGP(DMM, LOGL_INFO, " using LAI=%s\n", osmo_lai_name(&subscr->lai)); /* classmark 1 */ pwr_lev = gsm48_current_pwr_lev(set, cs->sel_arfcn); gsm48_encode_classmark1(&nlu->classmark1, sup->rev_lev, sup->es_ind, set->a5_1, pwr_lev); - /* MI */ - if (subscr->tmsi != 0xffffffff) { /* have TMSI ? */ - gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_TMSI); + /* MI (LV) */ + if (subscr->tmsi != GSM_RESERVED_TMSI) { /* have TMSI ? */ + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_TMSI, false); LOGP(DMM, LOGL_INFO, " using TMSI 0x%08x\n", subscr->tmsi); } else { - gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_IMSI); + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_IMSI, false); LOGP(DMM, LOGL_INFO, " using IMSI %s\n", subscr->imsi); } - msgb_put(nmsg, buf[1]); /* length is part of nlu */ - memcpy(&nlu->mi_len, buf + 1, 1 + buf[1]); new_mm_state(mm, GSM48_MM_ST_WAIT_RR_CONN_LUPD, 0); @@ -2406,27 +2613,29 @@ static int gsm48_mm_rx_loc_upd_acc(struct osmocom_ms *ms, struct msgb *msg) struct tlv_parsed tp; struct msgb *nmsg; - if (payload_len < sizeof(struct gsm48_loc_area_id)) { - short_read: - LOGP(DMM, LOGL_NOTICE, "Short read of LOCATION UPDATING ACCEPT " - "message error.\n"); + if (payload_len < sizeof(*lai)) { +short_read: + LOGP(DMM, LOGL_ERROR, "Short read of LOCATION UPDATING ACCEPT message\n"); + return -EINVAL; + } + if (tlv_parse(&tp, &gsm48_mm_att_tlvdef, + gh->data + sizeof(*lai), + payload_len - sizeof(*lai), 0, 0) < 0) { + LOGP(DMM, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); return -EINVAL; } - tlv_parse(&tp, &gsm48_mm_att_tlvdef, - gh->data + sizeof(struct gsm48_loc_area_id), - payload_len - sizeof(struct gsm48_loc_area_id), 0, 0); /* update has finished */ mm->lupd_pending = 0; - /* RA was successfull */ + /* RA was successful */ mm->lupd_ra_failure = 0; /* stop periodic location updating timer */ stop_mm_t3212(mm); /* 4.4.2 */ /* LAI */ - gsm48_decode_lai_hex(lai, &subscr->mcc, &subscr->mnc, &subscr->lac); + gsm48_decode_lai2(lai, &subscr->lai); /* stop location update timer */ stop_mm_t3210(mm); @@ -2435,7 +2644,7 @@ static int gsm48_mm_rx_loc_upd_acc(struct osmocom_ms *ms, struct msgb *msg) mm->lupd_attempt = 0; /* mark SIM as attached */ - subscr->imsi_attached = 1; + subscr->imsi_attached = true; /* set the status in the sim to updated */ new_sim_ustate(subscr, GSM_SIM_U1_UPDATED); @@ -2444,19 +2653,17 @@ static int gsm48_mm_rx_loc_upd_acc(struct osmocom_ms *ms, struct msgb *msg) gsm_subscr_write_loci(ms); /* set last registered PLMN */ - if (subscr->lac > 0x0000 && subscr->lac < 0xfffe) { + if (subscr->lai.lac > 0x0000 && subscr->lai.lac < 0xfffe) { subscr->plmn_valid = 1; - subscr->plmn_mcc = subscr->mcc; - subscr->plmn_mnc = subscr->mnc; + memcpy(&subscr->plmn, &subscr->lai.plmn, sizeof(struct osmo_plmn_id)); } LOGP(DSUM, LOGL_INFO, "Location update accepted\n"); - LOGP(DMM, LOGL_INFO, "LOCATION UPDATING ACCEPT (mcc %s mnc %s " - "lac 0x%04x)\n", gsm_print_mcc(subscr->mcc), - gsm_print_mnc(subscr->mnc), subscr->lac); + LOGP(DMM, LOGL_INFO, "LOCATION UPDATING ACCEPT (lai=%s)\n", + osmo_lai_name(&subscr->lai)); /* remove LA from forbidden list */ - gsm322_del_forbidden_la(ms, subscr->mcc, subscr->mnc, subscr->lac); + gsm322_del_forbidden_la(ms, &subscr->lai); /* MI */ if (TLVP_PRESENT(&tp, GSM48_IE_MOBILE_ID)) { @@ -2486,7 +2693,7 @@ static int gsm48_mm_rx_loc_upd_acc(struct osmocom_ms *ms, struct msgb *msg) break; case GSM_MI_TYPE_IMSI: LOGP(DMM, LOGL_INFO, "TMSI removed\n"); - subscr->tmsi = 0xffffffff; + subscr->tmsi = GSM_RESERVED_TMSI; /* store LOCI on sim */ gsm_subscr_write_loci(ms); @@ -2507,7 +2714,7 @@ static int gsm48_mm_rx_loc_upd_acc(struct osmocom_ms *ms, struct msgb *msg) gsm322_plmn_sendmsg(ms, nmsg); /* follow on proceed */ - if (TLVP_PRESENT(&tp, GSM48_IE_MOBILE_ID)) + if (TLVP_PRESENT(&tp, GSM48_IE_FOLLOW_ON_PROC)) LOGP(DMM, LOGL_NOTICE, "follow-on proceed not supported.\n"); /* start RR release timer */ @@ -2531,7 +2738,7 @@ static int gsm48_mm_rx_loc_upd_rej(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } - /* RA was successfull */ + /* RA was successful */ mm->lupd_ra_failure = 0; /* stop periodic location updating timer */ @@ -2581,8 +2788,8 @@ static int gsm48_mm_rel_loc_upd_rej(struct osmocom_ms *ms, struct msgb *msg) case GSM48_REJECT_LOC_NOT_ALLOWED: case GSM48_REJECT_ROAMING_NOT_ALLOWED: /* TMSI and LAI invalid */ - subscr->tmsi = 0xffffffff; - subscr->lac = 0x0000; + subscr->tmsi = GSM_RESERVED_TMSI; + subscr->lai.lac = 0x0000; /* key is invalid */ subscr->key_seq = 7; @@ -2631,15 +2838,13 @@ static int gsm48_mm_rel_loc_upd_rej(struct osmocom_ms *ms, struct msgb *msg) LOGP(DSUM, LOGL_INFO, "Location update failed (Illegal ME)\n"); break; case GSM48_REJECT_PLMN_NOT_ALLOWED: - gsm_subscr_add_forbidden_plmn(subscr, mm->lupd_mcc, - mm->lupd_mnc, mm->lupd_rej_cause); + gsm_subscr_add_forbidden_plmn(subscr, &mm->lupd_lai.plmn, mm->lupd_rej_cause); LOGP(DSUM, LOGL_INFO, "Location update failed (PLMN not " "allowed)\n"); break; case GSM48_REJECT_LOC_NOT_ALLOWED: case GSM48_REJECT_ROAMING_NOT_ALLOWED: - gsm322_add_forbidden_la(ms, mm->lupd_mcc, mm->lupd_mnc, - mm->lupd_lac, mm->lupd_rej_cause); + gsm322_add_forbidden_la(ms, &mm->lupd_lai, mm->lupd_rej_cause); LOGP(DSUM, LOGL_INFO, "Location update failed (LAI not " "allowed)\n"); break; @@ -2674,7 +2879,7 @@ static int gsm48_mm_loc_upd_delay_retry(struct osmocom_ms *ms, struct msgb *msg) return 0; } -/* process failues as described in the lower part of 4.4.4.9 */ +/* process failures as described in the lower part of 4.4.4.9 */ static int gsm48_mm_loc_upd_failed(struct osmocom_ms *ms, struct msgb *msg) { struct gsm48_mmlayer *mm = &ms->mmlayer; @@ -2686,9 +2891,7 @@ static int gsm48_mm_loc_upd_failed(struct osmocom_ms *ms, struct msgb *msg) stop_mm_t3210(mm); if (subscr->ustate == GSM_SIM_U1_UPDATED - && mm->lupd_mcc == subscr->mcc - && mm->lupd_mnc == subscr->mnc - && mm->lupd_lac == subscr->lac) { + && (osmo_lai_cmp(&mm->lupd_lai, &subscr->lai) == 0)) { if (mm->lupd_attempt < 4) { LOGP(DSUM, LOGL_INFO, "Try location update later\n"); LOGP(DMM, LOGL_INFO, "Loc. upd. failed, retry #%d\n", @@ -2704,8 +2907,8 @@ static int gsm48_mm_loc_upd_failed(struct osmocom_ms *ms, struct msgb *msg) } /* TMSI and LAI invalid */ - subscr->tmsi = 0xffffffff; - subscr->lac = 0x0000; + subscr->tmsi = GSM_RESERVED_TMSI; + subscr->lai.lac = 0x0000; /* key is invalid */ subscr->key_seq = 7; @@ -2755,7 +2958,7 @@ static int gsm48_mm_rel_loc_upd_abort(struct osmocom_ms *ms, struct msgb *msg) return 0; } - /* RA was successfull or sent twice */ + /* RA was successful or sent twice */ mm->lupd_ra_failure = 0; /* continue with failure handling */ @@ -2797,7 +3000,6 @@ static int gsm48_mm_tx_cm_serv_req(struct osmocom_ms *ms, int rr_prim, struct gsm48_hdr *ngh; struct gsm48_service_request *nsr; /* NOTE: includes MI length */ uint8_t *cm2lv; - uint8_t buf[11]; LOGP(DMM, LOGL_INFO, "CM SERVICE REQUEST (cause %d)\n", mm->est_cause); @@ -2805,7 +3007,8 @@ static int gsm48_mm_tx_cm_serv_req(struct osmocom_ms *ms, int rr_prim, if (!nmsg) return -ENOMEM; ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh)); - nsr = (struct gsm48_service_request *)msgb_put(nmsg, sizeof(*nsr)); + /* Do not add mi_len to the message, this is done at gsm48_encode_mi_lv(). */ + nsr = (struct gsm48_service_request *)msgb_put(nmsg, sizeof(*nsr) - 1); cm2lv = (uint8_t *)&nsr->classmark; ngh->proto_discr = GSM48_PDISC_MM; @@ -2823,23 +3026,21 @@ static int gsm48_mm_tx_cm_serv_req(struct osmocom_ms *ms, int rr_prim, if (mm->est_cause == RR_EST_CAUSE_EMERGENCY && set->emergency_imsi[0]) { LOGP(DMM, LOGL_INFO, "-> Using IMSI %s for emergency\n", set->emergency_imsi); - gsm48_generate_mid_from_imsi(buf, set->emergency_imsi); + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_IMSI, true); } else if (!subscr->sim_valid) { /* have no SIM ? */ LOGP(DMM, LOGL_INFO, "-> Using IMEI %s\n", set->imei); - gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_IMEI); + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_IMEI, false); } else - if (subscr->tmsi != 0xffffffff) { /* have TMSI ? */ - gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_TMSI); + if (subscr->tmsi != GSM_RESERVED_TMSI) { /* have TMSI ? */ + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_TMSI, false); LOGP(DMM, LOGL_INFO, "-> Using TMSI\n"); } else { - gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_IMSI); + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_IMSI, false); LOGP(DMM, LOGL_INFO, "-> Using IMSI %s\n", subscr->imsi); } - msgb_put(nmsg, buf[1]); /* length is part of nsr */ - memcpy(&nsr->mi_len, buf + 1, 1 + buf[1]); /* prio is optional for eMLPP */ /* push RR header and send down */ @@ -2912,8 +3113,8 @@ static int gsm48_mm_rx_cm_service_rej(struct osmocom_ms *ms, struct msgb *msg) abort_any = 1; /* TMSI and LAI invalid */ - subscr->tmsi = 0xffffffff; - subscr->lac = 0x0000; + subscr->tmsi = GSM_RESERVED_TMSI; + subscr->lai.lac = 0x0000; /* key is invalid */ subscr->key_seq = 7; @@ -2938,7 +3139,7 @@ static int gsm48_mm_rx_cm_service_rej(struct osmocom_ms *ms, struct msgb *msg) /* release MM connection(s) */ gsm48_mm_release_mm_conn(ms, abort_any, 16, 0, 0); - /* state depends on the existance of remaining MM connections */ + /* state depends on the existence of remaining MM connections */ if (llist_empty(&mm->mm_conn)) new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0); else @@ -2989,21 +3190,10 @@ static int gsm48_mm_init_mm(struct osmocom_ms *ms, struct msgb *msg, */ sapi = conn_found->sapi; reject: - nmsg = NULL; - switch(msg_type) { - case GSM48_MMCC_EST_REQ: - nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_REL_IND, - mmh->ref, mmh->transaction_id, sapi); - break; - case GSM48_MMSS_EST_REQ: - nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSS_REL_IND, - mmh->ref, mmh->transaction_id, sapi); - break; - case GSM48_MMSMS_EST_REQ: - nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSMS_REL_IND, - mmh->ref, mmh->transaction_id, sapi); - break; - } + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMXX_REL_IND | + (msg_type & GSM48_MMXX_MASK), + mmh->ref, mmh->transaction_id, + sapi); if (!nmsg) return -ENOMEM; nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; @@ -3077,6 +3267,16 @@ static int gsm48_mm_init_mm(struct osmocom_ms *ms, struct msgb *msg, cm_serv = GSM48_CMSERV_SMS; proto = GSM48_PDISC_SMS; break; + case GSM48_MMGCC_EST_REQ: + cause = RR_EST_CAUSE_OTHER_SDCCH; + cm_serv = GSM48_CMSERV_VGCS; + proto = GSM48_PDISC_GROUP_CC; + break; + case GSM48_MMBCC_EST_REQ: + cause = RR_EST_CAUSE_OTHER_SDCCH; + cm_serv = GSM48_CMSERV_VBS; + proto = GSM48_PDISC_BCAST_CC; + break; } /* create MM connection instance */ @@ -3212,21 +3412,10 @@ static int gsm48_mm_init_mm_reject(struct osmocom_ms *ms, struct msgb *msg) struct gsm48_mmxx_hdr *nmmh; /* reject */ - nmsg = NULL; - switch(msg_type) { - case GSM48_MMCC_EST_REQ: - nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_REL_IND, mmh->ref, - mmh->transaction_id, sapi); - break; - case GSM48_MMSS_EST_REQ: - nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSS_REL_IND, mmh->ref, - mmh->transaction_id, sapi); - break; - case GSM48_MMSMS_EST_REQ: - nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSMS_REL_IND, mmh->ref, - mmh->transaction_id, sapi); - break; - } + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMXX_REL_IND | + (msg_type & GSM48_MMXX_MASK), + mmh->ref, mmh->transaction_id, + sapi); if (!nmsg) return -ENOMEM; nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; @@ -3292,6 +3481,16 @@ static int gsm48_mm_conn_go_dedic(struct osmocom_ms *ms) conn_found->ref, conn_found->transaction_id, conn_found->sapi); break; + case GSM48_PDISC_GROUP_CC: + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMGCC_EST_CNF, + conn_found->ref, conn_found->transaction_id, + conn_found->sapi); + break; + case GSM48_PDISC_BCAST_CC: + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMBCC_EST_CNF, + conn_found->ref, conn_found->transaction_id, + conn_found->sapi); + break; } if (!nmsg) return -ENOMEM; @@ -3358,6 +3557,7 @@ static int gsm48_mm_abort_mm_con(struct osmocom_ms *ms, struct msgb *msg) { struct gsm48_mmlayer *mm = &ms->mmlayer; struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data; + uint32_t msg_type = rrh->msg_type; int cause; /* stop RR release timer */ @@ -3377,11 +3577,13 @@ static int gsm48_mm_abort_mm_con(struct osmocom_ms *ms, struct msgb *msg) cause = 47; } + LOGP(DMM, LOGL_INFO, "Aborting connection with cause %d\n", cause); + /* stop MM connection timer */ stop_mm_t3230(mm); /* release all connections */ - gsm48_mm_release_mm_conn(ms, 1, cause, 1, 0); + gsm48_mm_release_mm_conn(ms, 1, cause, (msg_type == GSM48_RR_ABORT_IND), 0); /* return to MM IDLE */ return gsm48_mm_return_idle(ms, NULL); @@ -3395,7 +3597,7 @@ static int gsm48_mm_timeout_mm_con(struct osmocom_ms *ms, struct msgb *msg) /* release pending connection */ gsm48_mm_release_mm_conn(ms, 0, 102, 0, 0); - /* state depends on the existance of remaining MM connections */ + /* state depends on the existence of remaining MM connections */ if (llist_empty(&mm->mm_conn)) { /* start RR release timer */ start_mm_t3240(mm); @@ -3425,37 +3627,32 @@ static int gsm48_mm_data(struct osmocom_ms *ms, struct msgb *msg) struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; struct gsm48_mm_conn *conn; int msg_type = mmh->msg_type; + uint8_t sapi; - /* get connection, if not exist (anymore), release */ - conn = mm_conn_by_ref(mm, mmh->ref); - if (!conn) { - LOGP(DMM, LOGL_INFO, "MMXX_DATA_REQ with unknown (already " - "released) ref=%x, sending MMXX_REL_IND\n", mmh->ref); - switch(msg_type & GSM48_MMXX_MASK) { - case GSM48_MMCC_CLASS: - mmh->msg_type = GSM48_MMCC_REL_IND; - break; - case GSM48_MMSS_CLASS: - mmh->msg_type = GSM48_MMSS_REL_IND; - break; - case GSM48_MMSMS_CLASS: - mmh->msg_type = GSM48_MMSMS_REL_IND; - break; - } - mmh->cause = 31; + if (mm->state == GSM48_MM_ST_MM_CONN_ACTIVE_VGCS) { + /* Group transmit mode has no MM connection. */ + sapi = 0; + } else { + /* get connection, if not exist (anymore), release */ + conn = mm_conn_by_ref_and_class(mm, mmh->ref, (mmh->msg_type & GSM48_MMXX_MASK)); + if (!conn) { + LOGP(DMM, LOGL_INFO, "MMXX_DATA_REQ with unknown (already " + "released) ref=%x, sending MMXX_REL_IND\n", mmh->ref); + mmh->msg_type = GSM48_MMXX_REL_IND | (msg_type & GSM48_MMXX_MASK); + mmh->cause = 31; - /* mirror message with REL_IND + cause */ - return gsm48_mmxx_upmsg(ms, msg); + /* mirror message with REL_IND + cause */ + return gsm48_mmxx_upmsg(ms, msg); + } + /* set SAPI, if upper layer does not do it correctly */ + sapi = conn->sapi; } - /* set SAPI, if upper layer does not do it correctly */ - mmh->sapi = conn->sapi; - /* pull MM header */ msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr)); /* push RR header and send down */ - return gsm48_mm_to_rr(ms, msg, GSM48_RR_DATA_REQ, conn->sapi, 0); + return gsm48_mm_to_rr(ms, msg, GSM48_RR_DATA_REQ, sapi, 0); } /* release of MM connection (active state) */ @@ -3466,11 +3663,11 @@ static int gsm48_mm_release_active(struct osmocom_ms *ms, struct msgb *msg) struct gsm48_mm_conn *conn; /* get connection, if not exist (anymore), release */ - conn = mm_conn_by_ref(mm, mmh->ref); + conn = mm_conn_by_ref_and_class(mm, mmh->ref, (mmh->msg_type & GSM48_MMXX_MASK)); if (conn) mm_conn_free(conn); - /* state depends on the existance of remaining MM connections */ + /* state depends on the existence of remaining MM connections */ if (llist_empty(&mm->mm_conn)) { /* start RR release timer */ start_mm_t3240(mm); @@ -3490,7 +3687,7 @@ static int gsm48_mm_release_wait_add(struct osmocom_ms *ms, struct msgb *msg) struct gsm48_mm_conn *conn; /* get connection, if not exist (anymore), release */ - conn = mm_conn_by_ref(mm, mmh->ref); + conn = mm_conn_by_ref_and_class(mm, mmh->ref, (mmh->msg_type & GSM48_MMXX_MASK)); if (conn) mm_conn_free(conn); @@ -3505,7 +3702,7 @@ static int gsm48_mm_release_wait_active(struct osmocom_ms *ms, struct msgb *msg) struct gsm48_mm_conn *conn; /* get connection, if not exist (anymore), release */ - conn = mm_conn_by_ref(mm, mmh->ref); + conn = mm_conn_by_ref_and_class(mm, mmh->ref, (mmh->msg_type & GSM48_MMXX_MASK)); if (conn) mm_conn_free(conn); @@ -3534,7 +3731,7 @@ static int gsm48_mm_release_wait_rr(struct osmocom_ms *ms, struct msgb *msg) struct gsm48_mm_conn *conn; /* get connection, if not exist (anymore), release */ - conn = mm_conn_by_ref(mm, mmh->ref); + conn = mm_conn_by_ref_and_class(mm, mmh->ref, (mmh->msg_type & GSM48_MMXX_MASK)); if (conn) mm_conn_free(conn); @@ -3562,6 +3759,351 @@ static int gsm48_mm_abort_rr(struct osmocom_ms *ms, struct msgb *msg) return gsm48_mm_return_idle(ms, NULL); } +/* The RR indicates notification of ongoing VGCS/VBS calls. */ +static int gsm48_mm_notification(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mm_event *mme = (struct gsm48_mm_event *)msg->data; + uint32_t ref = osmo_load32be(mme->notification.gcr) >> 5; + uint16_t msg_type = (mme->notification.gcr[3] & 0x10) ? GSM48_MMGCC_NOTIF_IND : GSM48_MMBCC_NOTIF_IND; + struct gsm48_mmxx_hdr *mmh; + struct msgb *nmsg; + + /* Notification message to GCC/BCC layer */ + nmsg = gsm48_mmxx_msgb_alloc(msg_type, ref, 0xff, 0); + if (!nmsg) + return -ENOMEM; + mmh = (struct gsm48_mmxx_hdr *)nmsg->data; + mmh->notify = (mme->notification.gone) ? MMXX_NOTIFY_RELEASE : MMXX_NOTIFY_SETUP; + mmh->ch_desc_present = mme->notification.ch_desc_present; + memcpy(&mmh->ch_desc, &mme->notification.ch_desc, sizeof(mmh->ch_desc)); + + gsm48_mmxx_upmsg(ms, nmsg); + return 0; +} + +/* The RR indicates uplink busy. */ +static int gsm48_mm_uplink_free(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_mm_event *mme = (struct gsm48_mm_event *)msg->data; + struct msgb *nmsg; + uint16_t msg_type; + + if (mm->vgcs.group_call) + msg_type = (mme->msg_type == GSM48_MM_EVENT_UPLINK_BUSY) ? GSM48_MMGCC_UPLINK_BUSY_IND + : GSM48_MMGCC_UPLINK_FREE_IND; + else + msg_type = (mme->msg_type == GSM48_MM_EVENT_UPLINK_BUSY) ? GSM48_MMBCC_UPLINK_BUSY_IND + : GSM48_MMBCC_UPLINK_FREE_IND; + + LOGP(DMM, LOGL_INFO, "Update uplink free/busy state in group receive mode.\n"); + + /* Notification message to GCC/BCC layer */ + nmsg = gsm48_mmxx_msgb_alloc(msg_type, mm->vgcs.callref, 0xff, 0); + if (!nmsg) + return -ENOMEM; + + gsm48_mmxx_upmsg(ms, nmsg); + return 0; +} + +/* Join VGCS/VBS call as listener. */ +static int gsm48_mm_group_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_settings *set = &ms->settings; + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + struct msgb *nmsg; + + if (mm->substate == GSM48_MM_SST_NO_IMSI && !set->asci_allow_any) + return gsm48_mm_group_reject(ms, msg); + + LOGP(DMM, LOGL_INFO, "Request for joining a group call, trying to establish group receive mode.\n"); + + /* Store infos about group/broadcast call. */ + mm->vgcs.enabled = true; + mm->vgcs.group_call = (mmh->msg_type == GSM48_MMGCC_GROUP_REQ); + mm->vgcs.callref = mmh->ref; + mm->vgcs.normal_service = (mm->substate == GSM48_MM_SST_NORMAL_SERVICE || + mm->substate == GSM48_MM_SST_PLMN_SEARCH_NORMAL); + + /* Change to VGCS substate. */ + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, (mm->vgcs.normal_service) + ? GSM48_MM_SST_RX_VGCS_NORMAL : GSM48_MM_SST_RX_VGCS_LIMITED); + + /* Group recevie mode request to RR layer */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + + /* Add channel description. */ + memcpy(msgb_put(nmsg, sizeof(mmh->ch_desc)), &mmh->ch_desc, sizeof(mmh->ch_desc)); + + /* Push RR header and send to RR layer. */ + return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_GROUP_REQ, 0, 0); +} + +/* Joining VGCS/VBS call is not allowed in other states. */ +static int gsm48_mm_group_reject(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + uint16_t msg_type; + struct msgb *nmsg; + struct gsm48_mmxx_hdr *nmmh; + + LOGP(DMM, LOGL_NOTICE, "Joining group call rejected in current state.\n"); + + msg_type = (mmh->msg_type == GSM48_MMGCC_GROUP_REQ) ? GSM48_MMGCC_REL_IND : GSM48_MMBCC_REL_IND; + + /* Release message to GCC/BCC layer */ + nmsg = gsm48_mmxx_msgb_alloc(msg_type, mmh->ref, mmh->transaction_id, mmh->sapi); + if (!nmsg) + return -ENOMEM; + nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; + nmmh->cause = GSM48_CC_CAUSE_CALL_REJECTED; + + gsm48_mmxx_upmsg(ms, nmsg); + return 0; +} + +/* RR layer confirms group call. */ +static int gsm48_mm_group_cnf(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + uint16_t msg_type; + struct msgb *nmsg; + + LOGP(DMM, LOGL_NOTICE, "RR confirms group call.\n"); + + msg_type = (mm->vgcs.group_call) ? GSM48_MMGCC_GROUP_CNF : GSM48_MMBCC_GROUP_CNF; + + /* Uplink confirm message to GCC/BCC layer */ + nmsg = gsm48_mmxx_msgb_alloc(msg_type, mm->vgcs.callref, 0xff, 0); + if (!nmsg) + return -ENOMEM; + + + gsm48_mmxx_upmsg(ms, nmsg); + return 0; +} + +/* RR layer releases group call channel. */ +static int gsm48_mm_group_rel_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data; + uint16_t msg_type; + struct msgb *nmsg; + struct gsm48_mmxx_hdr *nmmh; + + LOGP(DMM, LOGL_NOTICE, "RR released or rejected group call channel.\n"); + + /* Disable group mode. */ + mm->vgcs.enabled = false; + + /* Change mode back to normal or limited service. */ + if (mm->substate == GSM48_MM_SST_RX_VGCS_LIMITED) { + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, (subscr->sim_valid) ? GSM48_MM_SST_LIMITED_SERVICE + : GSM48_MM_SST_NO_IMSI); + } + if (mm->substate == GSM48_MM_SST_RX_VGCS_NORMAL) + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_NORMAL_SERVICE); + + /* Return IDLE, if not already. Also select the sub-state to use. */ + gsm48_mm_return_idle(ms, NULL); + + /* Release message to GCC/BCC layer */ + msg_type = (mm->vgcs.group_call) ? GSM48_MMGCC_REL_IND : GSM48_MMBCC_REL_IND; + nmsg = gsm48_mmxx_msgb_alloc(msg_type, mm->vgcs.callref, 0xff, 0); + if (!nmsg) + return -ENOMEM; + nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; + switch (rrh->cause) { + case RR_REL_CAUSE_TRY_LATER: + /* Joining not yet possible */ + nmmh->cause = GSM48_CC_CAUSE_TEMP_FAILURE; + break; + case RR_REL_CAUSE_LOST_SIGNAL: + /* Lower layer failed. */ + nmmh->cause = GSM48_CC_CAUSE_TEMP_FAILURE; + break; + case RR_REL_CAUSE_NORMAL: + /* Channel was released by network. */ + nmmh->cause = GSM48_CC_CAUSE_NORM_CALL_CLEAR; + break; + default: + nmmh->cause = GSM48_CC_CAUSE_NORMAL_UNSPEC; + break; + } + + gsm48_mmxx_upmsg(ms, nmsg); + return 0; +} + +/* Upper layer releases group call. */ +static int gsm48_mm_group_rel_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + + LOGP(DMM, LOGL_INFO, "Request to release group call in receive or transmit mode.\n"); + + /* Disable group mode. */ + mm->vgcs.enabled = false; + + /* Change mode back to normal or limited service. */ + if (mm->substate == GSM48_MM_SST_RX_VGCS_LIMITED) { + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, (subscr->sim_valid) ? GSM48_MM_SST_LIMITED_SERVICE + : GSM48_MM_SST_NO_IMSI); + } + if (mm->substate == GSM48_MM_SST_RX_VGCS_NORMAL) + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_NORMAL_SERVICE); + + /* We are already IDLE. Also select the sub-state to use. */ + gsm48_mm_return_idle(ms, NULL); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + + /* Push RR header and send to RR layer. */ + return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_GROUP_REL_REQ, 0, 0); +} + +/* Upper layer requests uplink. */ +static int gsm48_mm_uplink_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_settings *set = &ms->settings; + struct msgb *nmsg; + + if (mm->substate != GSM48_MM_SST_RX_VGCS_NORMAL && !set->asci_allow_any) + return gsm48_mm_uplink_reject(ms, msg); + + LOGP(DMM, LOGL_INFO, "Request for uplink, trying to establish group transmit mode.\n"); + + /* Go into uplink pending state. */ + new_mm_state(mm, GSM48_MM_ST_WAIT_RR_CONN_VGCS, 0); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + + /* Push RR header and send to RR layer. */ + return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_UPLINK_REQ, 0, 0); +} + +/* Uplink not allowed in this state. */ +static int gsm48_mm_uplink_reject(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + uint16_t msg_type; + struct msgb *nmsg; + struct gsm48_mmxx_hdr *nmmh; + + LOGP(DMM, LOGL_NOTICE, "Request for uplink rejected in current state.\n"); + + msg_type = (mmh->msg_type == GSM48_MMGCC_UPLINK_REQ) ? GSM48_MMGCC_UPLINK_REL_IND : GSM48_MMBCC_UPLINK_REL_IND; + + /* Uplink release message to GCC/BCC layer */ + nmsg = gsm48_mmxx_msgb_alloc(msg_type, mmh->ref, mmh->transaction_id, mmh->sapi); + if (!nmsg) + return -ENOMEM; + nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; + nmmh->cause = GSM48_CC_CAUSE_CALL_REJECTED; + + gsm48_mmxx_upmsg(ms, nmsg); + return 0; +} + +/* RR layer confirms uplink. */ +static int gsm48_mm_uplink_cnf(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + uint16_t msg_type; + struct msgb *nmsg; + + LOGP(DMM, LOGL_NOTICE, "RR confirms uplink.\n"); + + /* Go into group transmit state. */ + new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE_VGCS, 0); + + msg_type = (mm->vgcs.group_call) ? GSM48_MMGCC_UPLINK_CNF : GSM48_MMBCC_UPLINK_CNF; + + /* Uplink confirm message to GCC/BCC layer */ + nmsg = gsm48_mmxx_msgb_alloc(msg_type, mm->vgcs.callref, 0xff, 0); + if (!nmsg) + return -ENOMEM; + + gsm48_mmxx_upmsg(ms, nmsg); + return 0; +} + +/* RR layer releases/rejects uplink. */ +static int gsm48_mm_uplink_rel_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data; + uint16_t msg_type; + struct msgb *nmsg; + struct gsm48_mmxx_hdr *nmmh; + + LOGP(DMM, LOGL_NOTICE, "RR released or rejected uplink.\n"); + + /* Change to VGCS substate. */ + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, (mm->vgcs.normal_service) ? GSM48_MM_SST_RX_VGCS_NORMAL + : GSM48_MM_SST_RX_VGCS_LIMITED); + + msg_type = (mm->vgcs.group_call) ? GSM48_MMGCC_UPLINK_REL_IND : GSM48_MMBCC_UPLINK_REL_IND; + + /* Uplink reject message to GCC/BCC layer */ + nmsg = gsm48_mmxx_msgb_alloc(msg_type, mm->vgcs.callref, 0xff, 0); + if (!nmsg) + return -ENOMEM; + nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; + switch (rrh->cause) { + case RR_REL_CAUSE_UPLINK_REJECTED: + /* Access to uplink was rejected by network or when not in group receive mode. */ + nmmh->cause = GSM48_CC_CAUSE_CALL_REJECTED; + break; + case RR_REL_CAUSE_UPLINK_BUSY: + /* Uplink was busy and did not become free. */ + nmmh->cause = GSM48_CC_CAUSE_USER_BUSY; + break; + case RR_REL_CAUSE_LINK_FAILURE: + /* Access to uplink failed. */ + nmmh->cause = GSM48_CC_CAUSE_TEMP_FAILURE; + break; + case RR_REL_CAUSE_NORMAL: + /* Uplink was released by message. */ + nmmh->cause = GSM48_CC_CAUSE_NORM_CALL_CLEAR; + break; + default: + nmmh->cause = GSM48_CC_CAUSE_NORMAL_UNSPEC; + break; + } + + gsm48_mmxx_upmsg(ms, nmsg); + return 0; +} + +/* Upper layer releases uplink. */ +static int gsm48_mm_uplink_rel_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct msgb *nmsg; + + LOGP(DMM, LOGL_INFO, "Request to release uplink, leaving group transmit mode.\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + + /* Push RR header and send to RR layer. */ + return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_UPLINK_REL_REQ, 0, 0); +} + /* * other processes */ @@ -3640,19 +4182,51 @@ static struct downstate { {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_no_rr}, + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MMGCC_EST_REQ, gsm48_mm_init_mm_no_rr}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MMBCC_EST_REQ, gsm48_mm_init_mm_no_rr}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MMBCC_GROUP_REQ, gsm48_mm_group_req}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MMGCC_GROUP_REQ, gsm48_mm_group_req}, + /* 4.2.2.2 Attempt to update / Loc. Upd. needed */ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) | SBIT(GSM48_MM_SST_LOC_UPD_NEEDED), GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr}, /* emergency only */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) | + SBIT(GSM48_MM_SST_LOC_UPD_NEEDED), + GSM48_MMBCC_GROUP_REQ, gsm48_mm_group_req}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) | + SBIT(GSM48_MM_SST_LOC_UPD_NEEDED), + GSM48_MMGCC_GROUP_REQ, gsm48_mm_group_req}, + /* 4.2.2.3 Limited service */ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE), GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr}, + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE), + GSM48_MMBCC_GROUP_REQ, gsm48_mm_group_req}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE), + GSM48_MMGCC_GROUP_REQ, gsm48_mm_group_req}, + /* 4.2.2.4 No IMSI */ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NO_IMSI), GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr}, + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NO_IMSI), + GSM48_MMBCC_GROUP_REQ, gsm48_mm_group_req}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NO_IMSI), + GSM48_MMGCC_GROUP_REQ, gsm48_mm_group_req}, + /* 4.2.2.5 PLMN search, normal service */ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr}, @@ -3663,10 +4237,54 @@ static struct downstate { {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_no_rr}, + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), + GSM48_MMGCC_EST_REQ, gsm48_mm_init_mm_no_rr}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), + GSM48_MMBCC_EST_REQ, gsm48_mm_init_mm_no_rr}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), + GSM48_MMBCC_GROUP_REQ, gsm48_mm_group_req}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), + GSM48_MMGCC_GROUP_REQ, gsm48_mm_group_req}, + /* 4.2.2.6 PLMN search */ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH), GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr}, + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH), + GSM48_MMBCC_GROUP_REQ, gsm48_mm_group_req}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH), + GSM48_MMGCC_GROUP_REQ, gsm48_mm_group_req}, + + /* 4.2.2.7 Receiving group call, normal service */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL), + GSM48_MMGCC_REL_REQ, gsm48_mm_group_rel_req}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL), + GSM48_MMBCC_REL_REQ, gsm48_mm_group_rel_req}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL), + GSM48_MMGCC_UPLINK_REQ, gsm48_mm_uplink_req}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL), + GSM48_MMBCC_UPLINK_REQ, gsm48_mm_uplink_req}, + + /* 4.2.2.8 Receiving group call, limited service */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_LIMITED), + GSM48_MMGCC_REL_REQ, gsm48_mm_group_rel_req}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_LIMITED), + GSM48_MMBCC_REL_REQ, gsm48_mm_group_rel_req}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_LIMITED), + GSM48_MMGCC_UPLINK_REQ, gsm48_mm_uplink_req}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_LIMITED), + GSM48_MMBCC_UPLINK_REQ, gsm48_mm_uplink_req}, + /* 4.5.1.1 MM Connection (EST) */ {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES, GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_first}, @@ -3677,6 +4295,12 @@ static struct downstate { {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES, GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_first}, + {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES, + GSM48_MMGCC_EST_REQ, gsm48_mm_init_mm_first}, + + {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES, + GSM48_MMBCC_EST_REQ, gsm48_mm_init_mm_first}, + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES, GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_more}, @@ -3695,6 +4319,7 @@ static struct downstate { {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES, GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_wait}, + /* Reject call in other states. */ {ALL_STATES, ALL_STATES, GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_reject}, @@ -3704,6 +4329,18 @@ static struct downstate { {ALL_STATES, ALL_STATES, GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_reject}, + {ALL_STATES, ALL_STATES, + GSM48_MMGCC_EST_REQ, gsm48_mm_init_mm_reject}, + + {ALL_STATES, ALL_STATES, + GSM48_MMBCC_EST_REQ, gsm48_mm_init_mm_reject}, + + {ALL_STATES, ALL_STATES, + GSM48_MMGCC_GROUP_REQ, gsm48_mm_group_reject}, + + {ALL_STATES, ALL_STATES, + GSM48_MMBCC_GROUP_REQ, gsm48_mm_group_reject}, + /* 4.5.2.1 MM Connection (DATA) */ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) | SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, @@ -3717,6 +4354,14 @@ static struct downstate { SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, GSM48_MMSMS_DATA_REQ, gsm48_mm_data}, + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) | + SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, + GSM48_MMGCC_DATA_REQ, gsm48_mm_data}, + + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) | + SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, + GSM48_MMBCC_DATA_REQ, gsm48_mm_data}, + /* 4.5.2.1 MM Connection (REL) */ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES, GSM48_MMCC_REL_REQ, gsm48_mm_release_active}, @@ -3727,6 +4372,12 @@ static struct downstate { {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES, GSM48_MMSMS_REL_REQ, gsm48_mm_release_active}, + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES, + GSM48_MMGCC_REL_REQ, gsm48_mm_release_active}, + + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES, + GSM48_MMBCC_REL_REQ, gsm48_mm_release_active}, + {SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, GSM48_MMCC_REL_REQ, gsm48_mm_release_wait_add}, @@ -3736,6 +4387,12 @@ static struct downstate { {SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, GSM48_MMSMS_REL_REQ, gsm48_mm_release_wait_add}, + {SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, + GSM48_MMGCC_REL_REQ, gsm48_mm_release_wait_add}, + + {SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, + GSM48_MMBCC_REL_REQ, gsm48_mm_release_wait_add}, + {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN), ALL_STATES, GSM48_MMCC_REL_REQ, gsm48_mm_release_wait_active}, @@ -3753,6 +4410,35 @@ static struct downstate { {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), ALL_STATES, GSM48_MMSMS_REL_REQ, gsm48_mm_release_wait_rr}, + + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), ALL_STATES, + GSM48_MMGCC_REL_REQ, gsm48_mm_release_wait_rr}, + + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), ALL_STATES, + GSM48_MMBCC_REL_REQ, gsm48_mm_release_wait_rr}, + + /* Group transmit mode */ + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_VGCS) | + SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS), ALL_STATES, + GSM48_MMGCC_UPLINK_REL_REQ, gsm48_mm_uplink_rel_req}, + + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_VGCS) | + SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS), ALL_STATES, + GSM48_MMBCC_UPLINK_REL_REQ, gsm48_mm_uplink_rel_req}, + + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_VGCS) | + SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS), ALL_STATES, + GSM48_MMGCC_REL_REQ, gsm48_mm_group_rel_req}, + + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_VGCS) | + SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS), ALL_STATES, + GSM48_MMBCC_REL_REQ, gsm48_mm_group_rel_req}, + + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS), ALL_STATES, + GSM48_MMGCC_DATA_REQ, gsm48_mm_data}, + + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS), ALL_STATES, + GSM48_MMBCC_DATA_REQ, gsm48_mm_data}, }; #define DOWNSLLEN \ @@ -3767,7 +4453,7 @@ int gsm48_mmxx_downmsg(struct osmocom_ms *ms, struct msgb *msg) int i, rc; /* keep up to date with the transaction ID */ - conn = mm_conn_by_ref(mm, mmh->ref); + conn = mm_conn_by_ref_and_class(mm, mmh->ref, (mmh->msg_type & GSM48_MMXX_MASK)); if (conn) conn->transaction_id = mmh->transaction_id; @@ -3800,7 +4486,7 @@ int gsm48_mmxx_downmsg(struct osmocom_ms *ms, struct msgb *msg) return rc; } -/* state trasitions for radio ressource messages (lower layer) */ +/* state trasitions for radio resource messages (lower layer) */ static struct rrdatastate { uint32_t states; int type; @@ -3880,6 +4566,23 @@ static struct rrdatastate { SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), /* not supported */ GSM48_RR_ABORT_IND, gsm48_mm_abort_mm_con}, + /* Group call */ + {ALL_STATES, + GSM48_RR_GROUP_CNF, gsm48_mm_group_cnf}, + + {ALL_STATES, + GSM48_RR_UPLINK_CNF, gsm48_mm_uplink_cnf}, + + {ALL_STATES, + GSM48_RR_GROUP_REL_IND, gsm48_mm_group_rel_ind}, + + {ALL_STATES, + GSM48_RR_UPLINK_REL_IND, gsm48_mm_uplink_rel_ind}, + + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_VGCS) | + SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS), + GSM48_RR_REL_IND, gsm48_mm_group_rel_ind}, + /* other (also wait for network command) */ {ALL_STATES, GSM48_RR_REL_IND, gsm48_mm_rel_other}, @@ -3965,20 +4668,84 @@ static struct mmdatastate { #define MMDATASLLEN \ (sizeof(mmdatastatelist) / sizeof(struct mmdatastate)) +static int create_conn_and_push_mm_hdr(struct gsm48_mmlayer *mm, struct msgb *msg, int rr_est, int rr_prim, + uint8_t sapi) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t pdisc = gh->proto_discr & 0x0f; + uint8_t transaction_id; + uint32_t callref; + struct gsm48_mm_conn *conn; + struct gsm48_mmxx_hdr *mmh; + + transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4; /* flip */ + + if (mm->vgcs.enabled) { + /* Ongoing group call. */ + callref = mm->vgcs.callref; + } else { + /* find transaction, if any */ + conn = mm_conn_by_id(mm, pdisc, transaction_id); + + /* create MM connection instance */ + if (!conn) { + /* if MT calls are not supported with protocol */ + if (rr_est == -1) { + LOGP(DMM, LOGL_ERROR, "No MO connection for pdisc=%d, transaction_id=%d\n", + pdisc, transaction_id); + return -EINVAL; + } + conn = mm_conn_new(mm, pdisc, transaction_id, sapi, mm_conn_new_ref++); + rr_prim = rr_est; + } + if (!conn) + return -ENOMEM; + callref = conn->ref; + } + + /* push new header */ + msgb_push(msg, sizeof(struct gsm48_mmxx_hdr)); + mmh = (struct gsm48_mmxx_hdr *)msg->data; + mmh->msg_type = rr_prim; + mmh->ref = callref; + mmh->transaction_id = transaction_id; + mmh->sapi = sapi; + + /* go MM CONN ACTIVE state */ + if (mm->state == GSM48_MM_ST_WAIT_NETWORK_CMD || + mm->state == GSM48_MM_ST_RR_CONN_RELEASE_NA) { + /* stop RR release timer */ + stop_mm_t3240(mm); + + /* stop "RR connection release not allowed" timer */ + stop_mm_t3241(mm); + + new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE, 0); + } + + return 0; +} + static int gsm48_mm_data_ind(struct osmocom_ms *ms, struct msgb *msg) { struct gsm48_mmlayer *mm = &ms->mmlayer; struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data; uint8_t sapi = rrh->sapi; - struct gsm48_hdr *gh = msgb_l3(msg); - uint8_t pdisc = gh->proto_discr & 0x0f; - uint8_t msg_type = gh->msg_type & 0xbf; - struct gsm48_mmxx_hdr *mmh; + const struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t pdisc, msg_type; int msg_supported = 0; /* determine, if message is supported at all */ - int rr_prim = -1, rr_est = -1; /* no prim set */ uint8_t skip_ind; int i, rc; + if (msgb_l3len(msg) < sizeof(*gh)) { + LOGP(DMM, LOGL_INFO, "%s(): short read of msgb: %s\n", + __func__, msgb_hexdump(msg)); + return -EINVAL; + } + + pdisc = gh->proto_discr & 0x0f; + msg_type = gh->msg_type & 0xbf; + /* 9.2.19 */ if (msg_type == GSM48_MT_MM_NULL) { msgb_free(msg); @@ -3994,61 +4761,6 @@ static int gsm48_mm_data_ind(struct osmocom_ms *ms, struct msgb *msg) /* pull the RR header */ msgb_pull(msg, sizeof(struct gsm48_rr_hdr)); - /* create transaction (if not exists) and push message */ - switch (pdisc) { - case GSM48_PDISC_CC: - rr_prim = GSM48_MMCC_DATA_IND; - rr_est = GSM48_MMCC_EST_IND; - break; - case GSM48_PDISC_NC_SS: - rr_prim = GSM48_MMSS_DATA_IND; - rr_est = GSM48_MMSS_EST_IND; - break; - case GSM48_PDISC_SMS: - rr_prim = GSM48_MMSMS_DATA_IND; - rr_est = GSM48_MMSMS_EST_IND; - break; - } - if (rr_prim != -1) { - uint8_t transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4; - /* flip */ - struct gsm48_mm_conn *conn; - - /* find transaction, if any */ - conn = mm_conn_by_id(mm, pdisc, transaction_id); - - /* create MM connection instance */ - if (!conn) { - conn = mm_conn_new(mm, pdisc, transaction_id, sapi, - mm_conn_new_ref++); - rr_prim = rr_est; - } - if (!conn) { - msgb_free(msg); - return -ENOMEM; - } - - /* push new header */ - msgb_push(msg, sizeof(struct gsm48_mmxx_hdr)); - mmh = (struct gsm48_mmxx_hdr *)msg->data; - mmh->msg_type = rr_prim; - mmh->ref = conn->ref; - mmh->transaction_id = conn->transaction_id; - mmh->sapi = conn->sapi; - - /* go MM CONN ACTIVE state */ - if (mm->state == GSM48_MM_ST_WAIT_NETWORK_CMD - || mm->state == GSM48_MM_ST_RR_CONN_RELEASE_NA) { - /* stop RR release timer */ - stop_mm_t3240(mm); - - /* stop "RR connection release not allowed" timer */ - stop_mm_t3241(mm); - - new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE, 0); - } - } - /* forward message */ switch (pdisc) { case GSM48_PDISC_MM: @@ -4059,31 +4771,45 @@ static int gsm48_mm_data_ind(struct osmocom_ms *ms, struct msgb *msg) msgb_free(msg); return 0; } - break; /* follow the selection proceedure below */ + break; /* follow the selection procedure below */ case GSM48_PDISC_CC: - rc = gsm48_rcv_cc(ms, msg); + rc = create_conn_and_push_mm_hdr(mm, msg, GSM48_MMCC_EST_IND, GSM48_MMCC_DATA_IND, sapi); + if (rc == 0) + rc = gsm48_rcv_cc(ms, msg); msgb_free(msg); return rc; case GSM48_PDISC_NC_SS: - rc = gsm480_rcv_ss(ms, msg); + rc = create_conn_and_push_mm_hdr(mm, msg, GSM48_MMSS_EST_IND, GSM48_MMSS_DATA_IND, sapi); + if (rc == 0) + rc = gsm480_rcv_ss(ms, msg); msgb_free(msg); return rc; case GSM48_PDISC_SMS: - rc = gsm411_rcv_sms(ms, msg); + rc = create_conn_and_push_mm_hdr(mm, msg, GSM48_MMSMS_EST_IND, GSM48_MMSMS_DATA_IND, sapi); + if (rc == 0) + rc = gsm411_rcv_sms(ms, msg); + msgb_free(msg); + return rc; + + case GSM48_PDISC_GROUP_CC: + rc = create_conn_and_push_mm_hdr(mm, msg, -1, GSM48_MMGCC_DATA_IND, sapi); + if (rc == 0) + rc = gsm44068_rcv_gcc_bcc(ms, msg); + msgb_free(msg); + return rc; + case GSM48_PDISC_BCAST_CC: + rc = create_conn_and_push_mm_hdr(mm, msg, -1, GSM48_MMBCC_DATA_IND, sapi); + if (rc == 0) + rc = gsm44068_rcv_gcc_bcc(ms, msg); msgb_free(msg); return rc; - case 0x0f: /* test TS 04.14 */ - LOGP(DMM, LOGL_NOTICE, "Test protocol 0x%02x according to " - "TS 04.14 is not supported.\n", pdisc); - goto status; default: LOGP(DMM, LOGL_NOTICE, "Protocol type 0x%02x unsupported.\n", - pdisc); -status: + pdisc); msgb_free(msg); return gsm48_mm_tx_mm_status(ms, GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED); @@ -4165,6 +4891,10 @@ static struct eventstate { {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_start}, + /* 4.2.2.7 Receiving Group Call (Normal service) */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL), + GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_vgcs}, + /* 4.2.2.2 Attempt to update / Loc. upd. needed */ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) | SBIT(GSM48_MM_SST_LOC_UPD_NEEDED), @@ -4200,6 +4930,10 @@ static struct eventstate { {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE), GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay_per}, /* 4.4.2 */ + /* 4.2.2.8 Receiving Group Call (Limited service) */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_LIMITED), + GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_vgcs}, + /* 4.2.2.4 No IMSI */ /* 4.2.2.5 PLMN search, normal service */ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), @@ -4235,12 +4969,15 @@ static struct eventstate { {SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES, /* silently detach */ GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_end}, + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS) | + SBIT(GSM48_MM_ST_WAIT_RR_CONN_VGCS), ALL_STATES, /* uplink access */ + GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_vgcs}, + {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN) | SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) | SBIT(GSM48_MM_ST_PROCESS_CM_SERV_P) | SBIT(GSM48_MM_ST_WAIT_REEST) | SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON) | - SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS) | SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES, /* we can release */ GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_release}, @@ -4295,6 +5032,17 @@ static struct eventstate { {ALL_STATES, ALL_STATES, GSM48_MM_EVENT_CLASSMARK_CHG, gsm48_mm_classm_chg}, #endif + + /* Group call notification event */ + {ALL_STATES, ALL_STATES, + GSM48_MM_EVENT_NOTIFICATION, gsm48_mm_notification}, + + /* Uplink free/busy while in group receive mode */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL) | SBIT(GSM48_MM_SST_RX_VGCS_LIMITED), + GSM48_MM_EVENT_UPLINK_BUSY, gsm48_mm_uplink_free}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL) | SBIT(GSM48_MM_SST_RX_VGCS_LIMITED), + GSM48_MM_EVENT_UPLINK_FREE, gsm48_mm_uplink_free}, }; #define EVENTSLLEN \ @@ -4323,7 +5071,8 @@ static int gsm48_mm_ev(struct osmocom_ms *ms, int msg_type, struct msgb *msg) && ((1 << mm->substate) & eventstatelist[i].substates)) break; if (i == EVENTSLLEN) { - LOGP(DMM, LOGL_NOTICE, "Message unhandled at this state.\n"); + LOGP(DMM, LOGL_NOTICE, "Message %s unhandled in state %s.\n", + get_mmevent_name(msg_type), gsm48_mm_state_names[mm->state]); return 0; } diff --git a/src/host/layer23/src/mobile/gsm48_rr.c b/src/host/layer23/src/mobile/gsm48_rr.c index a94fc99c..1750c57f 100644 --- a/src/host/layer23/src/mobile/gsm48_rr.c +++ b/src/host/layer23/src/mobile/gsm48_rr.c @@ -13,10 +13,6 @@ * 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. - * */ /* Very short description of some of the procedures: @@ -38,9 +34,56 @@ * When the assignment or handover fails, the old channel is activate and the * link is established again. Also pending messages are sent. * + * Group Channel: + * + * A group receive/transmit mode is not documented at GSM 04.07. The group + * receive mode is similar to the IDLE mode, except that the MS is listening + * to a TCH. The group transmit mode is similar to the DEDICATED mode. Special + * (undocumented) commands are used to enter group receive and transmit mode. + * + * There is a substate that indicates group receive/transmit mode. If the + * substate is set to group receive, the IDLE mode becomes the group receive + * mode. If the substate is set to group transmit, the dedicated mode becomes + * the group transmit mode. The substate set to group receive allows to enter + * regular dedicated mode and return back to group receive mode afterwards. + * + * new_rr_state(rr, GSM48_RR_ST_IDLE) is used to return to IDLE mode or to + * group receive mode, depending on the substate. + * + * The Uplink access on group channel: + * + * If the uplink is busy, wait until it becomes free (Uplink investigation + * procedure). Abort, if the procedure times out, if the VGCS UPLINK GRANT + * message is recived for a different talker. + * + * Request uplink using access bursts on TCH until an VGCS UPLINK GRANT is + * received. Abort, if it the procedure times out, if the uplink becomes busy, + * if the VGCS UPLINK GRANT message references a different frame numer. + * + * Establish layer 2 with TALKER INDICATION. Abort, if content resolution + * mismatches (RR_REL_IND), if link fails (MDL_ERROR), if uplink becomes free. + * + * Release uplink and wait until uplink becomes free. Abort, if UPLINK RELEASE + * is received or if uplink fails. + * + * New primitives are invented for group/broadcast calls. They are not + * specified in any recommendation. They are: + * + * GSM48_MM_EVENT_NOTIFICATION: Notify MM layer about received/ceased call. + * GSM48_MM_EVENT_UPLINK_BUSY: Notify MM layer about uplink becoming busy. + * GSM48_MM_EVENT_UPLINK_FREE: Notify MM layer about uplink becoming free. + * + * RR_GROUP_REQ: The MM layer requests group channel in receive mode. + * RR_GROUP_CNF: The RR confirms group channel. + * RR_GROUP_REL_REQ: The MM layer releases group channel. + * RR_GROUP_REL_IND: The RR indicates/confirms release of group channel. + * RR_UPLINK_REQ: The MM layer requests uplink (group transmit mode). + * RR_UPLINK_CNF: The RR layer confirms uplink. (Uplink was granted.) + * RR_UPLINK_REL_REQ: The MM layer requests release of uplink. + * RR_UPLINK_REL_IND: The RR layer indicates/confirms release of uplink */ -/* Testing delayed (immediate) assigment / handover +/* Testing delayed (immediate) assignment / handover * * When enabled, the starting time will be set by given frames in the future. * If a starting time is given by the network, this time is ignored. @@ -73,32 +116,49 @@ #include <osmocom/core/bitvec.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/l1l2_interface.h> #include <osmocom/bb/common/l23_app.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/networks.h> #include <osmocom/bb/common/l1ctl.h> -#include <osmocom/bb/mobile/vty.h> #include <osmocom/bb/common/utils.h> +#include <osmocom/bb/common/settings.h> + +#include <osmocom/bb/mobile/vty.h> +#include <osmocom/bb/mobile/gsm48_rr.h> #include <l1ctl_proto.h> +/* Check response for the last 3 channel requests only. See TS 44.018 §3.3.1.1.3.1 and §3.3.1.1.3.2. */ +#define IMM_ASS_HISTORY 3 +/* Check response for up to 5 uplink requests. See TS 44.018 §3.3.1.2.1.2. */ +#define UL_GRANT_HISTROY 5 + +static int gsm48_rr_render_ma(struct osmocom_ms *ms, struct gsm48_rr_cd *cd, uint16_t *ma, uint8_t *ma_len); +static int gsm48_rr_activate_channel(struct osmocom_ms *ms, struct gsm48_rr_cd *cd, uint16_t *ma, uint8_t ma_len); static void start_rr_t_meas(struct gsm48_rrlayer *rr, int sec, int micro); static void stop_rr_t_starting(struct gsm48_rrlayer *rr); static void stop_rr_t3124(struct gsm48_rrlayer *rr); static int gsm48_rcv_rsl(struct osmocom_ms *ms, struct msgb *msg); static int gsm48_rr_dl_est(struct osmocom_ms *ms); static int gsm48_rr_tx_meas_rep(struct osmocom_ms *ms); -static int gsm48_rr_set_mode(struct osmocom_ms *ms, uint8_t chan_nr, - uint8_t mode); +static int gsm48_rr_set_mode(struct osmocom_ms *ms, uint8_t chan_nr, uint8_t mode, uint8_t tch_flags); static int gsm48_rr_rel_cnf(struct osmocom_ms *ms, struct msgb *msg); +int gsm414_rcv_test(struct osmocom_ms *ms, const struct msgb *msg); +static int gsm48_rr_group_rel(struct osmocom_ms *ms, int cause); +static int gsm48_rr_uplink_access(struct osmocom_ms *ms, struct msgb *msg); +static int gsm48_rr_uplink_access_abort(struct osmocom_ms *ms, uint8_t cause); +static int gsm48_rr_uplink_status(struct osmocom_ms *ms, uint32_t event); +static int gsm48_match_ra(struct osmocom_ms *ms, struct gsm48_req_ref *ref, uint8_t hist_num); +static int gsm48_rr_tx_talker_indication(struct osmocom_ms *ms); +static int gsm48_rr_tx_rand_acc_dedicated(struct osmocom_ms *ms, uint8_t ref, uint16_t offset, int8_t uic); +static int gsm48_rr_uplink_rel_req(struct osmocom_ms *ms, struct msgb *msg); /* * support */ -#define MIN(a, b) ((a < b) ? a : b) - /* decode "Power Command" (10.5.2.28) and (10.5.2.28a) */ static int gsm48_decode_power_cmd_acc(struct gsm48_power_cmd *pc, uint8_t *power_level, uint8_t *atc) @@ -210,7 +270,7 @@ static int gsm48_apply_v_sd(struct gsm48_rrlayer *rr, struct msgb *msg) case GSM48_PDISC_MM: case GSM48_PDISC_CC: case GSM48_PDISC_NC_SS: - /* all thre pdiscs share the same V(SD) */ + /* all three pdiscs share the same V(SD) */ pdisc = GSM48_PDISC_MM; // fall through case GSM48_PDISC_GROUP_CC: @@ -249,8 +309,14 @@ static uint8_t gsm48_rr_check_mode(struct osmocom_ms *ms, uint8_t chan_nr, struct gsm_settings *set = &ms->settings; uint8_t ch_type, ch_subch, ch_ts; + if (rsl_dec_chan_nr(chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, chan_nr); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + /* only complain if we use TCH/F or TCH/H */ - rsl_dec_chan_nr(chan_nr, &ch_type, &ch_subch, &ch_ts); if (ch_type != RSL_CHAN_Bm_ACCHs && ch_type != RSL_CHAN_Lm_ACCHs) return 0; @@ -307,6 +373,56 @@ static uint8_t gsm48_rr_check_mode(struct osmocom_ms *ms, uint8_t chan_nr, LOGP(DRR, LOGL_INFO, "Mode: half-rate speech V3\n"); } break; + case GSM48_CMODE_DATA_14k5: + if (ch_type != RSL_CHAN_Bm_ACCHs) { + LOGP(DRR, LOGL_ERROR, "TCH/F is expected for mode %s\n", + gsm48_chan_mode_name(mode)); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } else if (!set->csd_tch_f144) { + LOGP(DRR, LOGL_ERROR, "Not supporting TCH/F14.4 data (%s)\n", + gsm48_chan_mode_name(mode)); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + LOGP(DRR, LOGL_INFO, "Mode: TCH/F14.4 data (%s)\n", + gsm48_chan_mode_name(mode)); + break; + case GSM48_CMODE_DATA_12k0: + if (ch_type != RSL_CHAN_Bm_ACCHs) { + LOGP(DRR, LOGL_ERROR, "TCH/F is expected for mode %s\n", + gsm48_chan_mode_name(mode)); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } else if (!set->csd_tch_f96) { + LOGP(DRR, LOGL_ERROR, "Not supporting TCH/F9.6 data (%s)\n", + gsm48_chan_mode_name(mode)); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + LOGP(DRR, LOGL_INFO, "Mode: TCH/F9.6 data (%s)\n", + gsm48_chan_mode_name(mode)); + break; + case GSM48_CMODE_DATA_6k0: + if ((ch_type == RSL_CHAN_Bm_ACCHs && !set->csd_tch_f48) + || (ch_type == RSL_CHAN_Lm_ACCHs && !set->csd_tch_h48)) { + LOGP(DRR, LOGL_ERROR, "Not supporting TCH/%c4.8 data (%s)\n", + ch_type == RSL_CHAN_Bm_ACCHs ? 'F' : 'H', + gsm48_chan_mode_name(mode)); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + LOGP(DRR, LOGL_INFO, "Mode: TCH/%c4.8 data (%s)\n", + ch_type == RSL_CHAN_Bm_ACCHs ? 'F' : 'H', + gsm48_chan_mode_name(mode)); + break; + case GSM48_CMODE_DATA_3k6: + if ((ch_type == RSL_CHAN_Bm_ACCHs && !set->csd_tch_f24) + || (ch_type == RSL_CHAN_Lm_ACCHs && !set->csd_tch_h24)) { + LOGP(DRR, LOGL_ERROR, "Not supporting TCH/%c2.4 data (%s)\n", + ch_type == RSL_CHAN_Bm_ACCHs ? 'F' : 'H', + gsm48_chan_mode_name(mode)); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + LOGP(DRR, LOGL_INFO, "Mode: TCH/%c2.4 data (%s)\n", + ch_type == RSL_CHAN_Bm_ACCHs ? 'F' : 'H', + gsm48_chan_mode_name(mode)); + break; default: LOGP(DRR, LOGL_ERROR, "Mode 0x%02x not supported!\n", mode); return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; @@ -319,7 +435,7 @@ static uint8_t gsm48_rr_check_mode(struct osmocom_ms *ms, uint8_t chan_nr, int gsm48_rr_alter_delay(struct osmocom_ms *ms) { struct gsm48_rrlayer *rr = &ms->rrlayer; - struct gsm_settings *set = &rr->ms->settings; + struct gsm_settings *set = &ms->settings; if (rr->state != GSM48_RR_ST_DEDICATED) return -EINVAL; @@ -343,19 +459,21 @@ const char *gsm48_rr_state_names[] = { static void new_rr_state(struct gsm48_rrlayer *rr, int state) { + struct osmocom_ms *ms = rr->ms; + if (state < 0 || state >= (sizeof(gsm48_rr_state_names) / sizeof(char *))) return; - /* must check against equal state */ - if (rr->state == state) { + /* Check against equal state or IDLE state. */ + if (rr->state == state && state != GSM48_RR_ST_IDLE) { LOGP(DRR, LOGL_INFO, "equal state ? %s\n", gsm48_rr_state_names[rr->state]); return; } LOGP(DRR, LOGL_INFO, "new state %s -> %s\n", - gsm48_rr_state_names[rr->state], gsm48_rr_state_names[state]); + gsm48_rr_state_names[rr->state], gsm48_rr_state_names[state]); /* abort handover, in case of release of dedicated mode */ if (rr->state == GSM48_RR_ST_DEDICATED) { @@ -369,10 +487,15 @@ static void new_rr_state(struct gsm48_rrlayer *rr, int state) rr->state = state; - if (state == GSM48_RR_ST_IDLE) { + if (state != GSM48_RR_ST_IDLE) + return; + + /* Return from dedicated/group receive/transmit mode to idle mode. (Trigger cell reselection.) */ + if (rr->vgcs.group_state == GSM48_RR_GST_OFF) { struct msgb *msg, *nmsg; struct gsm322_msg *em; + LOGP(DRR, LOGL_INFO, "Returning to IDLE mode.\n"); /* release dedicated mode, if any */ l1ctl_tx_dm_rel_req(rr->ms); rr->ms->meas.rl_fail = 0; @@ -411,6 +534,41 @@ static void new_rr_state(struct gsm48_rrlayer *rr, int state) msgb_free(nmsg); /* reset any BA range */ rr->ba_ranges = 0; + return; + } + + /* Return from dedicated mode to group receive mode. + * This is not used, because we never enter dedicated mode during group receive/transmit mode. + * It may be used later, if we support it in MM layer. */ + if (rr->vgcs.group_state == GSM48_RR_GST_RECEIVE) { + uint16_t ma[64]; + uint8_t ma_len; + struct msgb *msg; + + LOGP(DRR, LOGL_INFO, "Returning to GROUP RECEIVE mode.\n"); + /* release dedicated mode, if any */ + l1ctl_tx_dm_rel_req(rr->ms); + rr->ms->meas.rl_fail = 0; + rr->dm_est = 0; + l1ctl_tx_reset_req(rr->ms, L1CTL_RES_T_SCHED); + /* free establish message, if any */ + rr->rr_est_req = 0; + if (rr->rr_est_msg) { + msgb_free(rr->rr_est_msg); + rr->rr_est_msg = NULL; + } + /* free all pending messages */ + while ((msg = msgb_dequeue(&rr->downqueue))) + msgb_free(msg); + /* reset ciphering */ + rr->cipher_on = 0; + /* copy channel description "group mode" */ + memcpy(&rr->cd_now, &rr->vgcs.cd_group, sizeof(rr->cd_now)); + /* render channel "group mode" */ + gsm48_rr_render_ma(ms, &rr->cd_now, ma, &ma_len); + /* activate channel */ + gsm48_rr_activate_channel(ms, &rr->cd_now, ma, ma_len); + return; } } @@ -451,6 +609,14 @@ static const struct value_string gsm48_rr_msg_names[] = { { GSM48_RR_ABORT_REQ, "RR_ABORT_REQ" }, { GSM48_RR_ABORT_IND, "RR_ABORT_IND" }, { GSM48_RR_ACT_REQ, "RR_ACT_REQ" }, + { GSM48_RR_GROUP_REQ, "RR_GROUP_REQ" }, + { GSM48_RR_GROUP_CNF, "RR_GROUP_CNF" }, + { GSM48_RR_GROUP_REL_REQ, "RR_GROUP_REL_REQ" }, + { GSM48_RR_GROUP_REL_IND, "RR_GROUP_REL_IND" }, + { GSM48_RR_UPLINK_REQ, "RR_UPLINK_REQ" }, + { GSM48_RR_UPLINK_CNF, "RR_UPLINK_CNF" }, + { GSM48_RR_UPLINK_REL_REQ, "RR_UPLINK_REL_REQ" }, + { GSM48_RR_UPLINK_REL_IND, "RR_UPLINK_REL_IND" }, { 0, NULL } }; @@ -515,8 +681,8 @@ int gsm48_rr_upmsg(struct osmocom_ms *ms, struct msgb *msg) } /* push rsl header and send (RSL-SAP) */ -static int gsm48_send_rsl(struct osmocom_ms *ms, uint8_t msg_type, - struct msgb *msg, uint8_t link_id) +int gsm48_send_rsl(struct osmocom_ms *ms, uint8_t msg_type, + struct msgb *msg, uint8_t link_id) { struct gsm48_rrlayer *rr = &ms->rrlayer; @@ -632,39 +798,43 @@ static void timeout_rr_meas(void *arg) struct gsm_settings *set = &rr->ms->settings; int rxlev, berr, snr; uint8_t ch_type, ch_subch, ch_ts; + struct osmo_strbuf sb; char text[256]; - /* don't monitor if no cell is selcted or if we scan neighbour cells */ + sb = (struct osmo_strbuf) { .buf = text, .len = sizeof(text) }; + + /* don't monitor if no cell is selected or if we scan neighbour cells */ if (!cs->selected || cs->neighbour) { - sprintf(text, "MON: not camping on serving cell"); + OSMO_STRBUF_PRINTF(sb, "MON: not camping on serving cell"); goto restart; } else if (!meas->frames) { - sprintf(text, "MON: no cell info"); + OSMO_STRBUF_PRINTF(sb, "MON: no cell info"); } else { rxlev = (meas->rxlev + meas->frames / 2) / meas->frames; berr = (meas->berr + meas->frames / 2) / meas->frames; snr = (meas->snr + meas->frames / 2) / meas->frames; - sprintf(text, "MON: f=%d lev=%s snr=%2d ber=%3d " - "LAI=%s %s %04x ID=%04x", cs->sel_arfcn, - gsm_print_rxlev(rxlev), berr, snr, - gsm_print_mcc(cs->sel_mcc), - gsm_print_mnc(cs->sel_mnc), cs->sel_lac, cs->sel_id); + OSMO_STRBUF_PRINTF(sb, "MON: f=%d lev=%s snr=%2d ber=%3d " + "CGI=%s", cs->sel_arfcn, + gsm_print_rxlev(rxlev), snr, berr, + osmo_cgi_name(&cs->sel_cgi)); if (rr->state == GSM48_RR_ST_DEDICATED) { - rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, - &ch_subch, &ch_ts); - sprintf(text + strlen(text), " TA=%d pwr=%d TS=%d", - rr->cd_now.ind_ta - set->alter_delay, - (set->alter_tx_power) ? set->alter_tx_power_value - : rr->cd_now.ind_tx_power, ch_ts); - if (ch_type == RSL_CHAN_SDCCH8_ACCH - || ch_type == RSL_CHAN_SDCCH4_ACCH) - sprintf(text + strlen(text), "/%d", ch_subch); + OSMO_STRBUF_PRINTF(sb, " TA=%d pwr=%d", + rr->cd_now.ind_ta - set->alter_delay, + (set->alter_tx_power) ? set->alter_tx_power_value + : rr->cd_now.ind_tx_power); + if (rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, &ch_subch, &ch_ts) == 0) { + OSMO_STRBUF_PRINTF(sb, " TS=%d", ch_ts); + if (ch_type == RSL_CHAN_SDCCH8_ACCH + || ch_type == RSL_CHAN_SDCCH4_ACCH + || ch_type == RSL_CHAN_Lm_ACCHs) + OSMO_STRBUF_PRINTF(sb, "/%d", ch_subch); + } } else gsm322_meas(rr->ms, rxlev); } LOGP(DRR, LOGL_INFO, "%s\n", text); if (rr->monitor) - vty_notify(rr->ms, "%s\n", text); + l23_vty_ms_notify(rr->ms, "%s\n", text); if (rr->dm_est) gsm48_rr_tx_meas_rep(rr->ms); @@ -765,6 +935,36 @@ static void timeout_rr_t3126(void *arg) new_rr_state(rr, GSM48_RR_ST_IDLE); } +static void timeout_rr_t3128(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + struct osmocom_ms *ms = rr->ms; + + LOGP(DRR, LOGL_INFO, "timer T3128 has fired\n"); + + LOGP(DRR, LOGL_NOTICE, "Failed to access uplink, uplink did not become free.\n"); + gsm48_rr_uplink_access_abort(ms, RR_REL_CAUSE_UPLINK_BUSY); +} + +static void timeout_rr_t3130(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + struct osmocom_ms *ms = rr->ms; + + LOGP(DRR, LOGL_INFO, "timer T3130 has fired\n"); + + gsm48_rr_uplink_access(ms, NULL); +} + +static void timeout_rr_t_ul_free(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + + LOGP(DRR, LOGL_INFO, "uplink free timer has fired\n"); + rr->vgcs.uplink_free = false; + gsm48_rr_uplink_status(rr->ms, GSM48_MM_EVENT_UPLINK_BUSY); +} + static void start_rr_t_meas(struct gsm48_rrlayer *rr, int sec, int micro) { rr->t_meas.cb = timeout_rr_meas; @@ -817,6 +1017,33 @@ static void start_rr_t3126(struct gsm48_rrlayer *rr, int sec, int micro) osmo_timer_schedule(&rr->t3126, sec, micro); } +static void start_rr_t3128(struct gsm48_rrlayer *rr, int sec, int micro) +{ + LOGP(DRR, LOGL_INFO, "starting T3128 with %d.%03d seconds\n", sec, + micro / 1000); + rr->vgcs.t3128.cb = timeout_rr_t3128; + rr->vgcs.t3128.data = rr; + osmo_timer_schedule(&rr->vgcs.t3128, sec, micro); +} + +static void start_rr_t3130(struct gsm48_rrlayer *rr, int sec, int micro) +{ + LOGP(DRR, LOGL_INFO, "starting T3130 with %d.%03d seconds\n", sec, + micro / 1000); + rr->vgcs.t3130.cb = timeout_rr_t3130; + rr->vgcs.t3130.data = rr; + osmo_timer_schedule(&rr->vgcs.t3130, sec, micro); +} + +static void start_rr_t_ul_free(struct gsm48_rrlayer *rr) +{ + if (!osmo_timer_pending(&rr->vgcs.t_ul_free)) + LOGP(DRR, LOGL_INFO, "starting uplink free timer\n"); + rr->vgcs.t_ul_free.cb = timeout_rr_t_ul_free; + rr->vgcs.t_ul_free.data = rr; + osmo_timer_schedule(&rr->vgcs.t_ul_free, 0, 480000); +} + static void stop_rr_t_meas(struct gsm48_rrlayer *rr) { if (osmo_timer_pending(&rr->t_meas)) { @@ -873,12 +1100,36 @@ static void stop_rr_t3126(struct gsm48_rrlayer *rr) } } +static void stop_rr_t3128(struct gsm48_rrlayer *rr) +{ + if (osmo_timer_pending(&rr->vgcs.t3128)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T3128\n"); + osmo_timer_del(&rr->vgcs.t3128); + } +} + +static void stop_rr_t3130(struct gsm48_rrlayer *rr) +{ + if (osmo_timer_pending(&rr->vgcs.t3130)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T3130\n"); + osmo_timer_del(&rr->vgcs.t3130); + } +} + +static void stop_rr_t_ul_free(struct gsm48_rrlayer *rr) +{ + if (osmo_timer_pending(&rr->vgcs.t_ul_free)) { + LOGP(DRR, LOGL_INFO, "stopping pending uplink free timer\n"); + osmo_timer_del(&rr->vgcs.t_ul_free); + } +} + /* * status */ /* send rr status request */ -static int gsm48_rr_tx_rr_status(struct osmocom_ms *ms, uint8_t cause) +int gsm48_rr_tx_rr_status(struct osmocom_ms *ms, uint8_t cause) { struct msgb *nmsg; struct gsm48_hdr *gh; @@ -893,7 +1144,7 @@ static int gsm48_rr_tx_rr_status(struct osmocom_ms *ms, uint8_t cause) st = (struct gsm48_rr_status *) msgb_put(nmsg, sizeof(*st)); gh->proto_discr = GSM48_PDISC_RR; - gh->msg_type = GSM48_MT_RR_CIPH_M_COMPL; + gh->msg_type = GSM48_MT_RR_STATUS; /* rr cause */ st->rr_cause = cause; @@ -908,11 +1159,9 @@ static int gsm48_rr_tx_rr_status(struct osmocom_ms *ms, uint8_t cause) /* send chiperhing mode complete */ static int gsm48_rr_tx_cip_mode_cpl(struct osmocom_ms *ms, uint8_t cr) { - struct gsm_settings *set = &ms->settings; struct msgb *nmsg; struct gsm48_hdr *gh; struct gsm48_rr_hdr *nrrh; - uint8_t buf[11], *tlv; LOGP(DRR, LOGL_INFO, "CIPHERING MODE COMPLETE (cr %d)\n", cr); @@ -925,13 +1174,8 @@ static int gsm48_rr_tx_cip_mode_cpl(struct osmocom_ms *ms, uint8_t cr) gh->msg_type = GSM48_MT_RR_CIPH_M_COMPL; /* MI */ - if (cr) { - gsm48_generate_mid_from_imsi(buf, set->imeisv); - /* alter MI type */ - buf[2] = (buf[2] & ~GSM_MI_TYPE_MASK) | GSM_MI_TYPE_IMEISV; - tlv = msgb_put(nmsg, 2 + buf[1]); - memcpy(tlv, buf, 2 + buf[1]); - } + if (cr) + gsm48_encode_mi_tlv(ms, nmsg, GSM_MI_TYPE_IMEISV, false); gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg, 0); @@ -1278,6 +1522,648 @@ static int gsm48_rr_rx_cm_enq(struct osmocom_ms *ms, struct msgb *msg) } /* + * ASCI notification + */ + +struct asci_notif { + struct llist_head entry; + struct gsm48_rrlayer *rr; + uint8_t gcr[5]; + bool ch_desc_present; + struct gsm48_chan_desc ch_desc; + struct osmo_timer_list timer; +}; + +/* When does a notification received from NCH expires. */ +#define NOTIFICATION_TIMEOUT 5 + +static void asci_notif_timeout(void *arg); + +/* Add new notification to list. */ +static struct asci_notif *asci_notif_alloc(struct gsm48_rrlayer *rr, const uint8_t *gcr) +{ + struct asci_notif *notif; + + notif = talloc_zero(rr->ms, struct asci_notif); + if (!notif) + return NULL; + notif->rr = rr; + memcpy(notif->gcr, gcr, sizeof(notif->gcr)); + llist_add_tail(¬if->entry, &rr->vgcs.notif_list); + + notif->timer.cb = asci_notif_timeout; + notif->timer.data = notif; + + return notif; +} + +/* Remove notification from list. */ +static void asci_notif_free(struct asci_notif *notif) +{ + osmo_timer_del(¬if->timer); + llist_del(¬if->entry); + talloc_free(notif); +} + +/* Remove all ASCI notifications from list. */ +static void asci_notif_list_free(struct gsm48_rrlayer *rr) +{ + struct asci_notif *notif, *notif2; + + llist_for_each_entry_safe(notif, notif2, &rr->vgcs.notif_list, entry) + asci_notif_free(notif); +} + +/* Notification timed out. */ +static void asci_notif_timeout(void *arg) +{ + struct asci_notif *notif = arg; + struct gsm48_rrlayer *rr = notif->rr; + struct msgb *nmsg; + struct gsm48_mm_event *mme; + + /* Send notification of ceased call to MM layer. */ + LOGP(DRR, LOGL_INFO, "Notify MM layer about ceased group call.\n"); + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_NOTIFICATION); + if (!nmsg) + return; + mme = (struct gsm48_mm_event *) nmsg->data; + memcpy(mme->notification.gcr, notif->gcr, sizeof(mme->notification.gcr)); + mme->notification.gone = true; + gsm48_mmevent_msg(rr->ms, nmsg); + + asci_notif_free(notif); +} + +/* Find notification in list. */ +static struct asci_notif *asci_notif_find(struct gsm48_rrlayer *rr, const uint8_t *gcr) +{ + struct asci_notif *notif; + + llist_for_each_entry(notif, &rr->vgcs.notif_list, entry) { + if (!memcmp(¬if->gcr, gcr, sizeof(notif->gcr))) + return notif; + } + + return NULL; +} + +static int gsm48_rr_rx_group_call(struct osmocom_ms *ms, const uint8_t *gcr, const uint8_t *ch_desc, bool nch) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct asci_notif *notif; + bool update_call = false; + struct msgb *nmsg; + struct gsm48_mm_event *mme; + + /* Find or create notification entry. Only create entries for notifications on NCH */ + notif = asci_notif_find(rr, gcr); + if (!notif) { + update_call = true; + if (nch) { + notif = asci_notif_alloc(rr, gcr); + if (!notif) + return -ENOMEM; + } + } + + /* Update channel description. */ + if (notif) { + if (ch_desc) { + if (!notif->ch_desc_present) + update_call = true; + notif->ch_desc_present = true; + memcpy(¬if->ch_desc, ch_desc, sizeof(notif->ch_desc)); + } else { + notif->ch_desc_present = false; + if (!notif->ch_desc_present) + update_call = true; + } + /* (Re-)Start timer. */ + osmo_timer_schedule(¬if->timer, NOTIFICATION_TIMEOUT, 0); + } else + update_call = true; + + /* Send notification of new or updated call to MM layer. */ + if (update_call) { + LOGP(DRR, LOGL_INFO, "Notify MM layer about new/updated group call.\n"); + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_NOTIFICATION); + if (!nmsg) + return -ENOMEM; + mme = (struct gsm48_mm_event *) nmsg->data; + memcpy(mme->notification.gcr, gcr, sizeof(mme->notification.gcr)); + if (ch_desc) { + mme->notification.ch_desc_present = true; + memcpy(&mme->notification.ch_desc, ch_desc, sizeof(mme->notification.ch_desc)); + } + gsm48_mmevent_msg(ms, nmsg); + } + + return 0; +} + +/* Common function to decode Group Call Information */ +static int gsm48_rr_decode_group_call_info(struct osmocom_ms *ms, struct bitvec *bv, bool nch) +{ + struct bitvec *gcr_bv = NULL, *chd_bv = NULL; + int rc = 0; + int i; + + /* <Group Call Reference : bit(36)> */ + gcr_bv = bitvec_alloc(OSMO_BYTES_FOR_BITS(36), NULL); + OSMO_ASSERT(gcr_bv); + for (i = 0; i < 36; i++) + bitvec_set_bit(gcr_bv, bitvec_get_bit_pos(bv, bv->cur_bit++)); + /* Group Channel Description */ + if (bitvec_get_bit_pos(bv, bv->cur_bit++) == 1) { + chd_bv = bitvec_alloc(OSMO_BYTES_FOR_BITS(24), NULL); + OSMO_ASSERT(chd_bv); + for (i = 0; i < 24; i++) + bitvec_set_bit(chd_bv, bitvec_get_bit_pos(bv, bv->cur_bit++)); + /* FIXME: hopping */ + if (bitvec_get_bit_pos(bv, bv->cur_bit++) == 1) { + LOGP(DRR, LOGL_ERROR, "Hopping not supported on VGCS/VBS channel, please fix!\n"); + rc = -ENOTSUP; + goto out; + } + } + + rc = gsm48_rr_rx_group_call(ms, gcr_bv->data, (chd_bv) ? chd_bv->data : NULL, nch); + +out: + bitvec_free(chd_bv); + bitvec_free(gcr_bv); + + return rc; +} + +/* Notification/FACCH (9.1.21a) */ +static int gsm48_rr_rx_notif_facch(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_hdr_sh *sgh = msgb_l3(msg); + int payload_len = msgb_l3len(msg) - sizeof(*sgh); + struct bitvec bv; + int rc; + + LOGP(DRR, LOGL_INFO, "NOTIFICATION/FACCH\n"); + + bv = (struct bitvec) { + .data = sgh->data, + .data_len = payload_len, + }; + + /* Group Call Information */ + if (bitvec_get_bit_pos(&bv, bv.cur_bit++) == 0) { + rc = gsm48_rr_decode_group_call_info(ms, &bv, false); + return rc; + } + + /* Note: Other information are not used. */ + return -ENOTSUP; +} + +/* Notification/NCH (9.1.21b) */ +static int gsm48_rr_rx_notif_nch(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_notification_nch *nn = msgb_l3(msg); + int payload_len = msgb_l3len(msg) - sizeof(*nn); + struct bitvec bv; + int rc; + + LOGP(DRR, LOGL_INFO, "NOTIFICATION/NCH\n"); + + bv = (struct bitvec) { + .data = nn->data, + .data_len = payload_len, + }; + + /* 0 | 1 <NLN(NCH) : bit (2) > */ + if (bitvec_get_bit_pos(&bv, bv.cur_bit++) == 1) { + /* NLN not used + nln = bitvec_get_uint(&bv, 2); + nln_present = true; + */ + bv.cur_bit += 2; + } + + /* < list of Group Call NCH information > */ + while (bitvec_get_bit_pos(&bv, bv.cur_bit++) == 1) { + rc = gsm48_rr_decode_group_call_info(ms, &bv, true); + if (rc < 0) + break; + } + + return 0; +} + +/* + * VGCS uplink control + */ + +/* Send uplink status to upper layer. */ +static int gsm48_rr_uplink_status(struct osmocom_ms *ms, uint32_t event) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *nmsg; + + if (rr->vgcs.group_state != GSM48_RR_GST_RECEIVE) + return -EINVAL; + + /* Send notification of uplink state to MM layer. */ + LOGP(DRR, LOGL_INFO, "Notify MM layer about uplink state.\n"); + nmsg = gsm48_mmevent_msgb_alloc(event); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(rr->ms, nmsg); + + return 0; +} + +/* UPLINK BUSY (9.1.46) */ +static int gsm48_rr_rx_uplink_busy(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + LOGP(DRR, LOGL_INFO, "UPLINK BUSY\n"); + + /* Only allow when in some group mode. */ + if (rr->vgcs.group_state == GSM48_RR_GST_OFF) + return 0; + + /* Uplink is busy now. */ + if (rr->vgcs.uplink_free) { + rr->vgcs.uplink_free = false; + gsm48_rr_uplink_status(ms, GSM48_MM_EVENT_UPLINK_BUSY); + } + stop_rr_t_ul_free(rr); + + /* Abort during uplink investigation or access procedure. */ + if (osmo_timer_pending(&rr->vgcs.t3128) || osmo_timer_pending(&rr->vgcs.t3130)) { + LOGP(DRR, LOGL_NOTICE, "Abort uplink access, due to busy uplink.\n"); + return gsm48_rr_uplink_access_abort(ms, RR_REL_CAUSE_UPLINK_BUSY); + } + + return 0; +} + +/* UPLINK FREE (9.1.47) */ +static int gsm48_rr_rx_uplink_free(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct bitvec bv; + bool uplink_access = false; + uint8_t uic = 0xff; + uint8_t *mode; + + bv = (struct bitvec) { + .data_len = msgb_l3len(msg), + .data = msgb_l3(msg), + .cur_bit = 8, + }; + + /* Uplink Access */ + if (bitvec_get_bit_high(&bv) == H) + uplink_access = true; + + /* UIC */ + if (bitvec_get_bit_high(&bv) == H) + uic = bitvec_get_uint(&bv, 6); + + /* Note: Emergency Indicator not used. */ + + /* Do not flood the logging with UPLINK FREE messages. Log only on the fist received message. */ + if (!osmo_timer_pending(&rr->vgcs.t_ul_free)) + LOGP(DRR, LOGL_INFO, "UPLINK FREE (uplink access=%s, uic=0x%02x)\n", (uplink_access) ? "true" : "false", + uic); + + /* Uplink is free now. */ + if (!rr->vgcs.uplink_free) { + rr->vgcs.uplink_free = true; + gsm48_rr_uplink_status(ms, GSM48_MM_EVENT_UPLINK_FREE); + } + rr->vgcs.uic = uic; + rr->vgcs.uplink_access = uplink_access; + start_rr_t_ul_free(rr); + + /* We can be in group mode or in dedicated mode. When we are in dedicated mode and we receive UPLINK FREE, + * we know that we are actually on a group channel. This is the actual confirm to the UPLINK RELEASE message + * on a group channel. We must then release layer 2 locally and indicate channel release toward upper layer. + * + * When we are in group transmit mode, we return to group receive mode and also release layer 2 locally and + * indicate uplink release towards upper layer. + * + * When we are waiting for a free uplink (T3128 is running), we start uplink access. While accessing the + * uplink, we ignore further UPLINK FREE messages. + */ + if (rr->vgcs.group_state != GSM48_RR_GST_OFF) { + /* Start uplink access. */ + if (osmo_timer_pending(&rr->vgcs.t3128)) { + /* Stop timer, because uplink is now free. */ + stop_rr_t3128(rr); + rr->vgcs.uplink_tries = 3; + return gsm48_rr_uplink_access(ms, NULL); + } + + /* Ignore uplink free messages while accessing uplink. */ + if (osmo_timer_pending(&rr->vgcs.t3130)) + return 0; + } + + /* Any time in group transmit mode or dedicated mode, release on uplink free. */ + if (rr->state == GSM48_RR_ST_DEDICATED || rr->state == GSM48_RR_ST_REL_PEND) { + struct msgb *nmsg; + + LOGP(DRR, LOGL_INFO, "Uplink becomes free, send (local) release to layer 2.\n"); + + /* Go into pending release state if not already. + * We are already in pending relese state when we release uplink normally. + * If we receive UPLINK FREE while we release normally, we abort layer 2. */ + if (rr->state != GSM48_RR_ST_REL_PEND) + new_rr_state(rr, GSM48_RR_ST_REL_PEND); + + /* release message */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = RSL_REL_LOCAL_END; + gsm48_send_rsl_nol3(ms, RSL_MT_REL_REQ, nmsg, 0); + + return 0; + } + + return 0; +} + +/* UPLINK RELEASE (9.1.48) */ +static int gsm48_rr_rx_uplink_release(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_uplink_release *ur = (struct gsm48_uplink_release *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*ur); + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short UPLINK RELEASE message.\n"); + return -EINVAL; + } + + LOGP(DRR, LOGL_INFO, "UPLINK RELEASE with cause 0x%02x\n", ur->rr_cause); + + /* Only allow when in some group mode. */ + if (rr->vgcs.group_state == GSM48_RR_GST_OFF) + return 0; + + if (rr->state == GSM48_RR_ST_DEDICATED && rr->vgcs.group_state == GSM48_RR_GST_TRANSMIT) + return gsm48_rr_uplink_access_abort(ms, RR_REL_CAUSE_NORMAL); + + return 0; +} + +/* VGCS UPLINK GRANT (9.1.49) */ +static int gsm48_rr_rx_vgcs_uplink_grant(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_sysinfo *s = &ms->cellsel.sel_si; + struct gsm_settings *set = &ms->settings; + struct gsm0408_vgcs_ul_grant *ug = msgb_l3(msg); + int ug_len = msgb_l3len(msg) - sizeof(*ug); + + LOGP(DRR, LOGL_INFO, "VGCS UPLINK GRANT\n"); + + if (ug_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of VGCS UPLINK GRANT message.\n"); + return -EINVAL; + } + + /* Only allow when in some group mode. */ + if (rr->vgcs.group_state == GSM48_RR_GST_OFF) + return 0; + + /* Uplink is busy now. */ + if (rr->vgcs.uplink_free) { + rr->vgcs.uplink_free = false; + gsm48_rr_uplink_status(rr->ms, GSM48_MM_EVENT_UPLINK_BUSY); + } + stop_rr_t_ul_free(rr); + + /* Abort during uplink investigation or access procedure. */ + if (osmo_timer_pending(&rr->vgcs.t3128)) { + LOGP(DRR, LOGL_NOTICE, "Abort uplink access, other phone accessing the uplink.\n"); + return gsm48_rr_uplink_access_abort(ms, RR_REL_CAUSE_UPLINK_BUSY); + } + + /* We are not waiting for the uplink to be granted. */ + if (!osmo_timer_pending(&rr->vgcs.t3130)) + return 0; + + /* Stop timer. */ + stop_rr_t3130(rr); + + /* Check if message is for our request. */ + if (!gsm48_match_ra(ms, &ug->req_ref, 5)) { + LOGP(DRR, LOGL_NOTICE, "Abort uplink access, other phone gets uplink access granted.\n"); + return gsm48_rr_uplink_access_abort(ms, RR_REL_CAUSE_UPLINK_BUSY); + } + + LOGP(DRR, LOGL_INFO, "Access to uplink has been granted.\n"); + + /* Set initial power and timing advance. */ + rr->cd_now.ind_tx_power = s->ms_txpwr_max_cch; + rr->cd_now.ind_ta = ug->ta; + LOGP(DRR, LOGL_INFO, "Applying initial ta and tx_power\n"); + l1ctl_tx_param_req(ms, rr->cd_now.ind_ta - set->alter_delay, + (set->alter_tx_power) ? set->alter_tx_power_value : s->ms_txpwr_max_cch); + + /* Turn on transmitter. */ + rr->cd_now.tch_flags &= ~(L1CTL_TCH_FLAG_RXONLY); + gsm48_rr_set_mode(ms, rr->cd_now.chan_nr, rr->cd_now.mode, rr->cd_now.tch_flags); + + /* Complete group transmit mode. */ + new_rr_state(rr, GSM48_RR_ST_DEDICATED); + + /* Establish layer 2 connection. */ + return gsm48_rr_tx_talker_indication(ms); +} + +/* send rr uplink release */ +static int gsm48_rr_tx_uplink_release(struct osmocom_ms *ms, uint8_t cause) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_uplink_release *ur; + + LOGP(DRR, LOGL_INFO, "UPLINK RELEASE (cause #%d)\n", cause); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + ur = (struct gsm48_uplink_release *) msgb_put(nmsg, sizeof(*ur)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_UPLINK_RELEASE; + ur->rr_cause = cause; + + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg, 0); +} + +/* Start uplink access procedure. (3.3.1.2.1.2) */ +static int gsm48_rr_uplink_access(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + /* store frame number */ + if (msg) { + struct abis_rsl_cchan_hdr *ch = msgb_l2(msg); + struct gsm48_req_ref *ref = + (struct gsm48_req_ref *) (ch->data + 1); + + if (msgb_l2len(msg) < sizeof(*ch) + sizeof(*ref)) { + LOGP(DRR, LOGL_ERROR, "CHAN_CNF too slort\n"); + return -EINVAL; + } + + /* Store to history buffer. */ + /* shift history and store */ + memcpy(&(rr->cr_hist[4]), &(rr->cr_hist[3]), + sizeof(struct gsm48_cr_hist)); + memcpy(&(rr->cr_hist[3]), &(rr->cr_hist[2]), + sizeof(struct gsm48_cr_hist)); + memcpy(&(rr->cr_hist[2]), &(rr->cr_hist[1]), + sizeof(struct gsm48_cr_hist)); + memcpy(&(rr->cr_hist[1]), &(rr->cr_hist[0]), + sizeof(struct gsm48_cr_hist)); + rr->cr_hist[0].valid = 1; + rr->cr_hist[0].ref.ra = rr->cr_ra; + rr->cr_hist[0].ref.t1 = ref->t1; + rr->cr_hist[0].ref.t2 = ref->t2; + rr->cr_hist[0].ref.t3_low = ref->t3_low; + rr->cr_hist[0].ref.t3_high = ref->t3_high; + } + + if (!osmo_timer_pending(&rr->vgcs.t3130)) { + uint8_t uplink_ref; + + /* Only try up to 3 times. */ + if (!rr->vgcs.uplink_tries) { + LOGP(DRR, LOGL_NOTICE, "Abort uplink access, due uplink access timeout.\n"); + return gsm48_rr_uplink_access_abort(ms, RR_REL_CAUSE_LINK_FAILURE); + } + + LOGP(DRR, LOGL_INFO, "Trying to access uplink.\n"); + + rr->vgcs.uplink_tries--; + + /* See Table 9.1.45.1 */ + uplink_ref = layer23_random(); + uplink_ref &= 0x1f; + uplink_ref |= 0xc0; + + /* store value, mask and history */ + rr->cr_ra = uplink_ref; + rr->cr_hist[4].valid = 0; + rr->cr_hist[3].valid = 0; + rr->cr_hist[2].valid = 0; + rr->cr_hist[1].valid = 0; + rr->cr_hist[0].valid = 0; + + /* Reset counter. */ + rr->vgcs.uplink_counter = 0; + + /* Start T3130. */ + start_rr_t3130(rr, GSM_T3130_MS); + } + + /* Send random access bursts up to 5 times. */ + if (rr->vgcs.uplink_counter < 5) { + int delay_ms; + + /* The first UPLINK ACCESS message shall be delayed between 0..20ms. + * Subsequent UPLINK ACCESS messages shall be delayed 100ms + 0..20ms. */ + delay_ms = (layer23_random() & 0xffff) / 3277; + if (rr->vgcs.uplink_counter) + delay_ms += 100; + + gsm48_rr_tx_rand_acc_dedicated(ms, rr->cr_ra, delay_ms * 26 / 120, rr->vgcs.uic); + rr->vgcs.uplink_counter++; + } + + return 0; +} + +/* Whenever uplink access is released or failed for some reason. */ +static int gsm48_rr_uplink_access_abort(struct osmocom_ms *ms, uint8_t cause) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + /* Stop group transmit mode timers. */ + stop_rr_t3128(rr); + stop_rr_t3130(rr); + + /* Turn off transmitter. */ + rr->cd_now.tch_flags |= L1CTL_TCH_FLAG_RXONLY; + gsm48_rr_set_mode(ms, rr->cd_now.chan_nr, rr->cd_now.mode, rr->cd_now.tch_flags); + + /* Only return IDLE without changing channel, because we are still in group transmit mode. */ + new_rr_state(rr, GSM48_RR_ST_IDLE); + + /* Set group state to receive mode. */ + rr->vgcs.group_state = GSM48_RR_GST_RECEIVE; + + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_UPLINK_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = cause; + return gsm48_rr_upmsg(ms, nmsg); +} + +/* send talker indication */ +static int gsm48_rr_tx_talker_indication(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm322_cellsel *cs = &ms->cellsel; + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_talker_indication *ti; + + LOGP(DRR, LOGL_INFO, "TALKER INDICATION\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + ti = (struct gsm48_talker_indication *) msgb_put(nmsg, sizeof(*ti)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_TALKER_IND; + + /* classmark 2 */ + ti->cm2_len = sizeof(ti->cm2); + gsm48_rr_enc_cm2(ms, &ti->cm2, rr->cd_now.arfcn); + + /* mobile identity */ + if (ms->subscr.tmsi != GSM_RESERVED_TMSI && (osmo_lai_cmp(&subscr->lai, &cs->sel_cgi.lai) == 0)) { + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_TMSI, false); + LOGP(DRR, LOGL_INFO, "Sending TALKER INDICATION with TMSI.\n"); + } else if (subscr->imsi[0]) { + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_IMSI, false); + LOGP(DRR, LOGL_INFO, "Sending TALKER INDICATION with IMSI.\n"); + } else { + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_NONE, false); + LOGP(DRR, LOGL_INFO, "Sending TALKER INDICATION without TMSI/IMSI.\n"); + } + + /* start establishmnet */ + return gsm48_send_rsl(ms, RSL_MT_EST_REQ, nmsg, 0); +} + +/* * random access */ @@ -1302,7 +2188,7 @@ static int gsm48_rr_chan_req(struct osmocom_ms *ms, int cause, int paging, && (!cs->selected || (cs->state != GSM322_C3_CAMPED_NORMALLY && cs->state != GSM322_C7_CAMPED_ANY_CELL))) { LOGP(DRR, LOGL_INFO, "Paging, but not camping, ignore.\n"); - return -EINVAL; + return -EINVAL; } /* ignore channel request while not camping on a cell */ @@ -1454,7 +2340,7 @@ static int gsm48_rr_chan_req(struct osmocom_ms *ms, int cause, int paging, if (s->neci) { chan_req_mask = 0x0f; chan_req_val = 0x10; - LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (OHTER " + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (OTHER " "with NECI)\n", chan_req_val); } else { chan_req_mask = 0x1f; @@ -1483,12 +2369,10 @@ rel_ind: return -EINVAL; } - /* store value, mask and history */ + /* store value, mask and clear history */ rr->chan_req_val = chan_req_val; rr->chan_req_mask = chan_req_mask; - rr->cr_hist[2].valid = 0; - rr->cr_hist[1].valid = 0; - rr->cr_hist[0].valid = 0; + memset(rr->cr_hist, 0, sizeof(rr->cr_hist)); /* store establishment cause, so 'choose cell' selects the last cell * after location updating */ @@ -1518,7 +2402,10 @@ int gsm48_rr_tx_rand_acc(struct osmocom_ms *ms, struct msgb *msg) uint8_t tx_power; enum gsm_band band; - gsm_arfcn2band_rc(cs->arfcn, &band); + if (gsm_arfcn2band_rc(cs->arfcn, &band) != 0) { + LOGP(DRR, LOGL_ERROR, "gsm_arfcn2band_rc() failed\n"); + return -EINVAL; + } /* already assigned */ if (rr->wait_assign == 2) @@ -1536,10 +2423,7 @@ int gsm48_rr_tx_rand_acc(struct osmocom_ms *ms, struct msgb *msg) } /* shift history and store */ - memcpy(&(rr->cr_hist[2]), &(rr->cr_hist[1]), - sizeof(struct gsm48_cr_hist)); - memcpy(&(rr->cr_hist[1]), &(rr->cr_hist[0]), - sizeof(struct gsm48_cr_hist)); + memmove(rr->cr_hist + 1, rr->cr_hist, sizeof(rr->cr_hist) - sizeof(rr->cr_hist[0])); rr->cr_hist[0].valid = 1; rr->cr_hist[0].ref.ra = rr->cr_ra; rr->cr_hist[0].ref.t1 = ref->t1; @@ -1593,11 +2477,11 @@ fail: rr->n_chan_req--; if (rr->wait_assign == 0) { - /* first random acces, without delay of slots */ + /* first random access, without delay of slots */ slots = 0; rr->wait_assign = 1; } else { - /* subsequent random acces, with slots from table 3.1 */ + /* subsequent random access, with slots from table 3.1 */ switch(s->tx_integer) { case 3: case 8: case 14: case 50: if (s->ccch_conf != 1) /* not combined CCCH */ @@ -1707,32 +2591,31 @@ static int gsm48_new_sysinfo(struct osmocom_ms *ms, uint8_t type) && s->si5 && (!s->nb_ext_ind_si5 || s->si5bis)) { struct gsm48_rr_meas *rrmeas = &ms->rrlayer.meas; - int n = 0, i, refer_pcs; + int i; + bool refer_pcs; + int16_t arfcn; LOGP(DRR, LOGL_NOTICE, "Complete set of SI5* for BA(%d)\n", s->nb_ba_ind_si5); rrmeas->nc_num = 0; refer_pcs = gsm_refer_pcs(cs->arfcn, s); - /* collect channels from freq list (1..1023,0) */ - for (i = 1; i <= 1024; i++) { - if ((s->freq[i & 1023].mask & FREQ_TYPE_REP)) { - if (n == 32) { - LOGP(DRR, LOGL_NOTICE, "SI5* report " - "exceeds 32 BCCHs\n"); - break; - } - if (refer_pcs && i >= 512 && i <= 810) - rrmeas->nc_arfcn[n] = i | ARFCN_PCS; - else - rrmeas->nc_arfcn[n] = i & 1023; - rrmeas->nc_rxlev_dbm[n] = -128; - LOGP(DRR, LOGL_NOTICE, "SI5* report arfcn %s\n", - gsm_print_arfcn(rrmeas->nc_arfcn[n])); - n++; - } + /* Collect channels from freq list in correct order. */ + for (i = 0; i < 32; i++) { + arfcn = arfcn_from_freq_index(s, i); + if (arfcn < 0) + break; + if (refer_pcs && arfcn >= 512 && arfcn <= 810) + rrmeas->nc_arfcn[i] = arfcn | ARFCN_PCS; + else + rrmeas->nc_arfcn[i] = arfcn; + rrmeas->nc_rxlev_dbm[i] = -128; + LOGP(DRR, LOGL_NOTICE, "SI5/SI5bis report arfcn %s (index %d)\n", + gsm_print_arfcn(rrmeas->nc_arfcn[i]), i); } - rrmeas->nc_num = n; + rrmeas->nc_num = i; + if (i == 32 && arfcn_from_freq_index(s, i) >= 0) + LOGP(DRR, LOGL_NOTICE, "SI5/SI5bis/SI5ter define more than 32 channels.\n"); } /* send sysinfo event to other layers */ @@ -1777,7 +2660,7 @@ static int gsm48_rr_rx_sysinfo1(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } - if (!memcmp(si, s->si1_msg, MIN(msgb_l3len(msg), sizeof(s->si1_msg)))) + if (!memcmp(si, s->si1_msg, OSMO_MIN(msgb_l3len(msg), sizeof(s->si1_msg)))) return 0; gsm48_decode_sysinfo1(s, si, msgb_l3len(msg)); @@ -1806,7 +2689,7 @@ static int gsm48_rr_rx_sysinfo2(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } - if (!memcmp(si, s->si2_msg, MIN(msgb_l3len(msg), sizeof(s->si2_msg)))) + if (!memcmp(si, s->si2_msg, OSMO_MIN(msgb_l3len(msg), sizeof(s->si2_msg)))) return 0; gsm48_decode_sysinfo2(s, si, msgb_l3len(msg)); @@ -1835,7 +2718,7 @@ static int gsm48_rr_rx_sysinfo2bis(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } - if (!memcmp(si, s->si2b_msg, MIN(msgb_l3len(msg), sizeof(s->si2b_msg)))) + if (!memcmp(si, s->si2b_msg, OSMO_MIN(msgb_l3len(msg), sizeof(s->si2b_msg)))) return 0; gsm48_decode_sysinfo2bis(s, si, msgb_l3len(msg)); @@ -1864,7 +2747,7 @@ static int gsm48_rr_rx_sysinfo2ter(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } - if (!memcmp(si, s->si2t_msg, MIN(msgb_l3len(msg), sizeof(s->si2t_msg)))) + if (!memcmp(si, s->si2t_msg, OSMO_MIN(msgb_l3len(msg), sizeof(s->si2t_msg)))) return 0; gsm48_decode_sysinfo2ter(s, si, msgb_l3len(msg)); @@ -1894,7 +2777,7 @@ static int gsm48_rr_rx_sysinfo3(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } - if (!memcmp(si, s->si3_msg, MIN(msgb_l3len(msg), sizeof(s->si3_msg)))) + if (!memcmp(si, s->si3_msg, OSMO_MIN(msgb_l3len(msg), sizeof(s->si3_msg)))) return 0; gsm48_decode_sysinfo3(s, si, msgb_l3len(msg)); @@ -1929,14 +2812,12 @@ static int gsm48_rr_rx_sysinfo4(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } - if (!memcmp(si, s->si4_msg, MIN(msgb_l3len(msg), sizeof(s->si4_msg)))) + if (!memcmp(si, s->si4_msg, OSMO_MIN(msgb_l3len(msg), sizeof(s->si4_msg)))) return 0; gsm48_decode_sysinfo4(s, si, msgb_l3len(msg)); - LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 4 (mcc %s mnc %s " - "lac 0x%04x)\n", gsm_print_mcc(s->mcc), - gsm_print_mnc(s->mnc), s->lac); + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 4 (lai=%s)\n", osmo_lai_name(&s->lai)); return gsm48_new_sysinfo(ms, si->header.system_information); } @@ -1944,9 +2825,10 @@ static int gsm48_rr_rx_sysinfo4(struct osmocom_ms *ms, struct msgb *msg) /* receive "SYSTEM INFORMATION 5" message (9.1.37) */ static int gsm48_rr_rx_sysinfo5(struct osmocom_ms *ms, struct msgb *msg) { - struct gsm48_system_information_type_5 *si = msgb_l3(msg); + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_5 *si = msgb_l3(msg) + 1; struct gsm48_sysinfo *s = ms->cellsel.si; - int payload_len = msgb_l3len(msg) - sizeof(*si); + int payload_len = msgb_l3len(msg) - sizeof(*si) - 1; if (!s) { LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 5 " @@ -1960,7 +2842,7 @@ static int gsm48_rr_rx_sysinfo5(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } - if (!memcmp(si, s->si5_msg, MIN(msgb_l3len(msg), sizeof(s->si5_msg)))) + if (!memcmp(si, s->si5_msg, OSMO_MIN(msgb_l3len(msg), sizeof(s->si5_msg)))) return 0; gsm48_decode_sysinfo5(s, si, msgb_l3len(msg)); @@ -1973,9 +2855,10 @@ static int gsm48_rr_rx_sysinfo5(struct osmocom_ms *ms, struct msgb *msg) /* receive "SYSTEM INFORMATION 5bis" message (9.1.38) */ static int gsm48_rr_rx_sysinfo5bis(struct osmocom_ms *ms, struct msgb *msg) { - struct gsm48_system_information_type_5bis *si = msgb_l3(msg); + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_5bis *si = msgb_l3(msg) + 1; struct gsm48_sysinfo *s = ms->cellsel.si; - int payload_len = msgb_l3len(msg) - sizeof(*si); + int payload_len = msgb_l3len(msg) - sizeof(*si) - 1; if (!s) { LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 5bis" @@ -1989,7 +2872,7 @@ static int gsm48_rr_rx_sysinfo5bis(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } - if (!memcmp(si, s->si5b_msg, MIN(msgb_l3len(msg), + if (!memcmp(si, s->si5b_msg, OSMO_MIN(msgb_l3len(msg), sizeof(s->si5b_msg)))) return 0; @@ -2003,9 +2886,10 @@ static int gsm48_rr_rx_sysinfo5bis(struct osmocom_ms *ms, struct msgb *msg) /* receive "SYSTEM INFORMATION 5ter" message (9.1.39) */ static int gsm48_rr_rx_sysinfo5ter(struct osmocom_ms *ms, struct msgb *msg) { - struct gsm48_system_information_type_5ter *si = msgb_l3(msg); + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_5ter *si = msgb_l3(msg) + 1; struct gsm48_sysinfo *s = ms->cellsel.si; - int payload_len = msgb_l3len(msg) - sizeof(*si); + int payload_len = msgb_l3len(msg) - sizeof(*si) - 1; if (!s) { LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 5ter" @@ -2019,7 +2903,7 @@ static int gsm48_rr_rx_sysinfo5ter(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } - if (!memcmp(si, s->si5t_msg, MIN(msgb_l3len(msg), + if (!memcmp(si, s->si5t_msg, OSMO_MIN(msgb_l3len(msg), sizeof(s->si5t_msg)))) return 0; @@ -2033,10 +2917,11 @@ static int gsm48_rr_rx_sysinfo5ter(struct osmocom_ms *ms, struct msgb *msg) /* receive "SYSTEM INFORMATION 6" message (9.1.39) */ static int gsm48_rr_rx_sysinfo6(struct osmocom_ms *ms, struct msgb *msg) { - struct gsm48_system_information_type_6 *si = msgb_l3(msg); + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_6 *si = msgb_l3(msg) + 1; struct gsm48_sysinfo *s = ms->cellsel.si; struct rx_meas_stat *meas = &ms->meas; - int payload_len = msgb_l3len(msg) - sizeof(*si); + int payload_len = msgb_l3len(msg) - sizeof(*si) - 1; if (!s) { LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 6 " @@ -2050,14 +2935,13 @@ static int gsm48_rr_rx_sysinfo6(struct osmocom_ms *ms, struct msgb *msg) return -EINVAL; } - if (!memcmp(si, s->si6_msg, MIN(msgb_l3len(msg), sizeof(s->si6_msg)))) + if (!memcmp(si, s->si6_msg, OSMO_MIN(msgb_l3len(msg), sizeof(s->si6_msg)))) return 0; gsm48_decode_sysinfo6(s, si, msgb_l3len(msg)); - LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 6 (mcc %s mnc %s " - "lac 0x%04x SACCH-timeout %d)\n", gsm_print_mcc(s->mcc), - gsm_print_mnc(s->mnc), s->lac, s->sacch_radio_link_timeout); + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 6 (lai=%s SACCH-timeout %d)\n", + osmo_lai_name(&s->lai), s->sacch_radio_link_timeout); meas->rl_fail = meas->s = s->sacch_radio_link_timeout; LOGP(DRR, LOGL_INFO, "using (new) SACCH timeout %d\n", meas->rl_fail); @@ -2065,6 +2949,72 @@ static int gsm48_rr_rx_sysinfo6(struct osmocom_ms *ms, struct msgb *msg) return gsm48_new_sysinfo(ms, si->system_information); } +/* Receive "SYSTEM INFORMATION 10" message (9.1.50). */ +static int gsm48_rr_rx_sysinfo_10(struct osmocom_ms *ms, struct msgb *msg) +{ + /* NOTE: Short L2 header is included in this structure */ + struct gsm48_system_information_type_10 *si = msgb_l3(msg); + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si); + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 10 ignored.\n"); + return -EINVAL; + } + + if (payload_len < 20) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 10 message.\n"); + return -EINVAL; + } + + /* No complete SI5, cannot decode yet. */ + if (!s->si5 || !(s->si5bis || !s->nb_ext_ind_si5)) + return 0; + + /* We decode when changed or when SI10 could not decoded, due to missing neighbor cell infos. */ + if (!memcmp(si, s->si10_msg, OSMO_MIN(msgb_l3len(msg), sizeof(s->si10_msg))) && s->si10) + return 0; + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 10\n"); + + gsm48_decode_sysinfo10(s, si, msgb_l3len(msg)); + + /* We cannot call gsm48_new_sysinfo, because it requires regular message types. */ + return 0; +} + +/* receive "SYSTEM INFORMATION 13" message (9.1.43a) */ +static int gsm48_rr_rx_sysinfo13(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct gsm48_system_information_type_13 *si = msgb_l3(msg); + int rest_octets_len = msgb_l3len(msg) - sizeof(si->header); + struct gsm48_sysinfo *s = ms->cellsel.si; + + if (!s) { + LOGP(DRR, LOGL_INFO, + "No cell selected, SYSTEM INFORMATION 13 ignored\n"); + return -EINVAL; + } + + if (rest_octets_len < 0) { + LOGP(DRR, LOGL_NOTICE, + "Short read of SYSTEM INFORMATION 13 message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si13_msg, OSMO_MIN(msgb_l3len(msg), sizeof(s->si6_msg)))) + return 0; + + gsm48_decode_sysinfo13(s, si, msgb_l3len(msg)); + + LOGP(DRR, LOGL_INFO, + "New SYSTEM INFORMATION 13 (%s, RAC 0x%02x, NCO %u, MNO %u)\n", + s->gprs.egprs_supported ? "EGPRS" : "GPRS only", + s->gprs.rac, s->gprs.nco, s->gprs.nmo); + + return gsm48_new_sysinfo(ms, si->header.system_information); +} + /* * paging */ @@ -2077,46 +3027,38 @@ static int gsm48_rr_chan2cause[4] = { RR_EST_CAUSE_ANS_PAG_TCH_ANY }; -/* given LV of mobile identity is checked agains ms */ -static uint8_t gsm_match_mi(struct osmocom_ms *ms, uint8_t *mi) +/* given LV of mobile identity is checked against ms */ +static uint8_t gsm_match_mi(struct osmocom_ms *ms, const uint8_t *mi_lv) { struct gsm322_cellsel *cs = &ms->cellsel; - char imsi[16]; - uint32_t tmsi; - uint8_t mi_type; + struct osmo_mobile_identity mi; + char buf[32]; + int rc; - if (mi[0] < 1) - return 0; - mi_type = mi[1] & GSM_MI_TYPE_MASK; - switch (mi_type) { + rc = osmo_mobile_identity_decode(&mi, mi_lv+1, mi_lv[0], false); + if (rc < 0) + return rc; + osmo_mobile_identity_to_str_buf(buf, sizeof(buf), &mi); + + switch (mi.type) { case GSM_MI_TYPE_TMSI: - if (mi[0] < 5) - return 0; - memcpy(&tmsi, mi+2, 4); - if (ms->subscr.tmsi == ntohl(tmsi) - && ms->subscr.mcc == cs->sel_mcc - && ms->subscr.mnc == cs->sel_mnc - && ms->subscr.lac == cs->sel_lac) { - LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", - ntohl(tmsi)); - - return mi_type; + if ((ms->subscr.tmsi == mi.tmsi) + && (osmo_lai_cmp(&ms->subscr.lai, &cs->sel_cgi.lai) == 0)) { + LOGP(DPAG, LOGL_INFO, " %s matches\n", buf); + return mi.type; } else - LOGP(DPAG, LOGL_INFO, " TMSI %08x (not for us)\n", - ntohl(tmsi)); + LOGP(DPAG, LOGL_INFO, " %s (not for us)\n", buf); break; case GSM_MI_TYPE_IMSI: - gsm48_mi_to_string(imsi, sizeof(imsi), mi + 1, mi[0]); - if (!strcmp(imsi, ms->subscr.imsi)) { - LOGP(DPAG, LOGL_INFO, " IMSI %s matches\n", imsi); - - return mi_type; + if (!strcmp(mi.imsi, ms->subscr.imsi)) { + LOGP(DPAG, LOGL_INFO, " %s matches\n", buf); + return mi.type; } else - LOGP(DPAG, LOGL_INFO, " IMSI %s (not for us)\n", imsi); + LOGP(DPAG, LOGL_INFO, " %s (not for us)\n", buf); break; default: LOGP(DPAG, LOGL_NOTICE, "Paging with unsupported MI type %d.\n", - mi_type); + mi.type); } return 0; @@ -2215,9 +3157,7 @@ static int gsm48_rr_rx_pag_req_2(struct osmocom_ms *ms, struct msgb *msg) chan_2 = pa->cneed2; /* first MI */ if (ms->subscr.tmsi == ntohl(pa->tmsi1) - && ms->subscr.mcc == cs->sel_mcc - && ms->subscr.mnc == cs->sel_mnc - && ms->subscr.lac == cs->sel_lac) { + && (osmo_lai_cmp(&ms->subscr.lai, &cs->sel_cgi.lai) == 0)) { LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", ntohl(pa->tmsi1)); return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_1], 1, GSM_MI_TYPE_TMSI); @@ -2226,9 +3166,7 @@ static int gsm48_rr_rx_pag_req_2(struct osmocom_ms *ms, struct msgb *msg) ntohl(pa->tmsi1)); /* second MI */ if (ms->subscr.tmsi == ntohl(pa->tmsi2) - && ms->subscr.mcc == cs->sel_mcc - && ms->subscr.mnc == cs->sel_mnc - && ms->subscr.lac == cs->sel_lac) { + && (osmo_lai_cmp(&ms->subscr.lai, &cs->sel_cgi.lai) == 0)) { LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", ntohl(pa->tmsi2)); return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_2], 1, GSM_MI_TYPE_TMSI); @@ -2285,9 +3223,7 @@ static int gsm48_rr_rx_pag_req_3(struct osmocom_ms *ms, struct msgb *msg) chan_4 = pa->cneed4; /* first MI */ if (ms->subscr.tmsi == ntohl(pa->tmsi1) - && ms->subscr.mcc == cs->sel_mcc - && ms->subscr.mnc == cs->sel_mnc - && ms->subscr.lac == cs->sel_lac) { + && (osmo_lai_cmp(&ms->subscr.lai, &cs->sel_cgi.lai) == 0)) { LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", ntohl(pa->tmsi1)); return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_1], 1, GSM_MI_TYPE_TMSI); @@ -2296,20 +3232,16 @@ static int gsm48_rr_rx_pag_req_3(struct osmocom_ms *ms, struct msgb *msg) ntohl(pa->tmsi1)); /* second MI */ if (ms->subscr.tmsi == ntohl(pa->tmsi2) - && ms->subscr.mcc == cs->sel_mcc - && ms->subscr.mnc == cs->sel_mnc - && ms->subscr.lac == cs->sel_lac) { + && (osmo_lai_cmp(&ms->subscr.lai, &cs->sel_cgi.lai) == 0)) { LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", ntohl(pa->tmsi2)); return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_2], 1, GSM_MI_TYPE_TMSI); } else LOGP(DPAG, LOGL_INFO, " TMSI %08x (not for us)\n", ntohl(pa->tmsi2)); - /* thrid MI */ + /* third MI */ if (ms->subscr.tmsi == ntohl(pa->tmsi3) - && ms->subscr.mcc == cs->sel_mcc - && ms->subscr.mnc == cs->sel_mnc - && ms->subscr.lac == cs->sel_lac) { + && (osmo_lai_cmp(&ms->subscr.lai, &cs->sel_cgi.lai) == 0)) { LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", ntohl(pa->tmsi3)); return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_3], 1, GSM_MI_TYPE_TMSI); @@ -2318,9 +3250,7 @@ static int gsm48_rr_rx_pag_req_3(struct osmocom_ms *ms, struct msgb *msg) ntohl(pa->tmsi3)); /* fourth MI */ if (ms->subscr.tmsi == ntohl(pa->tmsi4) - && ms->subscr.mcc == cs->sel_mcc - && ms->subscr.mnc == cs->sel_mnc - && ms->subscr.lac == cs->sel_lac) { + && (osmo_lai_cmp(&ms->subscr.lai, &cs->sel_cgi.lai) == 0)) { LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", ntohl(pa->tmsi4)); return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_4], 1, GSM_MI_TYPE_TMSI); @@ -2335,24 +3265,23 @@ static int gsm48_rr_rx_pag_req_3(struct osmocom_ms *ms, struct msgb *msg) * (immediate) assignment */ -/* match request reference agains request history */ -static int gsm48_match_ra(struct osmocom_ms *ms, struct gsm48_req_ref *ref) +/* match request reference against request history */ +static int gsm48_match_ra(struct osmocom_ms *ms, struct gsm48_req_ref *ref, uint8_t hist_num) { struct gsm48_rrlayer *rr = &ms->rrlayer; int i; uint8_t ia_t1, ia_t2, ia_t3; uint8_t cr_t1, cr_t2, cr_t3; - for (i = 0; i < 3; i++) { + for (i = 0; i < hist_num; i++) { /* filter confirmed RACH requests only */ if (rr->cr_hist[i].valid && ref->ra == rr->cr_hist[i].ref.ra) { - ia_t1 = ref->t1; - ia_t2 = ref->t2; - ia_t3 = (ref->t3_high << 3) | ref->t3_low; - ref = &rr->cr_hist[i].ref; - cr_t1 = ref->t1; - cr_t2 = ref->t2; - cr_t3 = (ref->t3_high << 3) | ref->t3_low; + ia_t1 = ref->t1; + ia_t2 = ref->t2; + ia_t3 = (ref->t3_high << 3) | ref->t3_low; + cr_t1 = rr->cr_hist[i].ref.t1; + cr_t2 = rr->cr_hist[i].ref.t2; + cr_t3 = (rr->cr_hist[i].ref.t3_high << 3) | rr->cr_hist[i].ref.t3_low; if (ia_t1 == cr_t1 && ia_t2 == cr_t2 && ia_t3 == cr_t3) { LOGP(DRR, LOGL_INFO, "request %02x matches " @@ -2387,7 +3316,7 @@ static int gsm48_rr_rx_imm_ass(struct osmocom_ms *ms, struct msgb *msg) /* ignore imm.ass. while not camping on a cell */ if (!cs->selected || cs->neighbour || !s) { - LOGP(DRR, LOGL_INFO, "IMMEDIATED ASSGINMENT ignored, we are " + LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT ignored, we are " "have not proper selected the serving cell.\n"); return 0; @@ -2423,7 +3352,12 @@ static int gsm48_rr_rx_imm_ass(struct osmocom_ms *ms, struct msgb *msg) /* decode channel description */ LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT:\n"); cd.chan_nr = ia->chan_desc.chan_nr; - rsl_dec_chan_nr(cd.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(cd.chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, cd.chan_nr); + return -EINVAL; + } if (ia->chan_desc.h0.h) { cd.h = 1; gsm48_decode_chan_h1(&ia->chan_desc, &cd.tsc, &cd.maio, @@ -2437,8 +3371,7 @@ static int gsm48_rr_rx_imm_ass(struct osmocom_ms *ms, struct msgb *msg) } else { cd.h = 0; gsm48_decode_chan_h0(&ia->chan_desc, &cd.tsc, &cd.arfcn); - if (gsm_refer_pcs(cs->arfcn, s)) - cd.arfcn |= ARFCN_PCS; + cd.arfcn = gsm_arfcn_refer_pcs(cs->arfcn, s, cd.arfcn); LOGP(DRR, LOGL_INFO, " (ta %d/%dm ra 0x%02x chan_nr 0x%02x " "ARFCN %s TS %u SS %u TSC %u)\n", ia->timing_advance, @@ -2459,7 +3392,7 @@ static int gsm48_rr_rx_imm_ass(struct osmocom_ms *ms, struct msgb *msg) } /* request ref */ - if (gsm48_match_ra(ms, &ia->req_ref)) { + if (gsm48_match_ra(ms, &ia->req_ref, IMM_ASS_HISTORY)) { /* channel description */ memcpy(&rr->cd_now, &cd, sizeof(rr->cd_now)); /* timing advance */ @@ -2495,7 +3428,7 @@ static int gsm48_rr_rx_imm_ass_ext(struct osmocom_ms *ms, struct msgb *msg) /* ignore imm.ass.ext while not camping on a cell */ if (!cs->selected || cs->neighbour || !s) { - LOGP(DRR, LOGL_INFO, "IMMEDIATED ASSGINMENT ignored, we are " + LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT ignored, we are " "have not proper selected the serving cell.\n"); return 0; @@ -2537,7 +3470,12 @@ static int gsm48_rr_rx_imm_ass_ext(struct osmocom_ms *ms, struct msgb *msg) /* decode channel description */ LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT EXTENDED:\n"); cd1.chan_nr = ia->chan_desc1.chan_nr; - rsl_dec_chan_nr(cd1.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(cd1.chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, cd1.chan_nr); + return -EINVAL; + } if (ia->chan_desc1.h0.h) { cd1.h = 1; gsm48_decode_chan_h1(&ia->chan_desc1, &cd1.tsc, &cd1.maio, @@ -2551,8 +3489,7 @@ static int gsm48_rr_rx_imm_ass_ext(struct osmocom_ms *ms, struct msgb *msg) } else { cd1.h = 0; gsm48_decode_chan_h0(&ia->chan_desc1, &cd1.tsc, &cd1.arfcn); - if (gsm_refer_pcs(cs->arfcn, s)) - cd1.arfcn |= ARFCN_PCS; + cd1.arfcn = gsm_arfcn_refer_pcs(cs->arfcn, s, cd1.arfcn); LOGP(DRR, LOGL_INFO, " assignment 1 (ta %d/%dm ra 0x%02x " "chan_nr 0x%02x ARFCN %s TS %u SS %u TSC %u)\n", ia->timing_advance1, @@ -2561,7 +3498,12 @@ static int gsm48_rr_rx_imm_ass_ext(struct osmocom_ms *ms, struct msgb *msg) gsm_print_arfcn(cd1.arfcn), ch_ts, ch_subch, cd1.tsc); } cd2.chan_nr = ia->chan_desc2.chan_nr; - rsl_dec_chan_nr(cd2.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(cd2.chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, cd2.chan_nr); + return -EINVAL; + } if (ia->chan_desc2.h0.h) { cd2.h = 1; gsm48_decode_chan_h1(&ia->chan_desc2, &cd2.tsc, &cd2.maio, @@ -2575,8 +3517,7 @@ static int gsm48_rr_rx_imm_ass_ext(struct osmocom_ms *ms, struct msgb *msg) } else { cd2.h = 0; gsm48_decode_chan_h0(&ia->chan_desc2, &cd2.tsc, &cd2.arfcn); - if (gsm_refer_pcs(cs->arfcn, s)) - cd2.arfcn |= ARFCN_PCS; + cd2.arfcn = gsm_arfcn_refer_pcs(cs->arfcn, s, cd2.arfcn); LOGP(DRR, LOGL_INFO, " assignment 2 (ta %d/%dm ra 0x%02x " "chan_nr 0x%02x ARFCN %s TS %u SS %u TSC %u)\n", ia->timing_advance2, @@ -2597,7 +3538,7 @@ static int gsm48_rr_rx_imm_ass_ext(struct osmocom_ms *ms, struct msgb *msg) } /* request ref 1 */ - if (gsm48_match_ra(ms, &ia->req_ref1)) { + if (gsm48_match_ra(ms, &ia->req_ref1, IMM_ASS_HISTORY)) { /* channel description */ memcpy(&rr->cd_now, &cd1, sizeof(rr->cd_now)); /* timing advance */ @@ -2613,7 +3554,7 @@ static int gsm48_rr_rx_imm_ass_ext(struct osmocom_ms *ms, struct msgb *msg) return gsm48_rr_dl_est(ms); } /* request ref 2 */ - if (gsm48_match_ra(ms, &ia->req_ref2)) { + if (gsm48_match_ra(ms, &ia->req_ref2, IMM_ASS_HISTORY)) { /* channel description */ memcpy(&rr->cd_now, &cd2, sizeof(rr->cd_now)); /* timing advance */ @@ -2663,7 +3604,7 @@ static int gsm48_rr_rx_imm_ass_rej(struct osmocom_ms *ms, struct msgb *msg) (((uint8_t *)&ia->req_ref1) + i * 4); LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT REJECT " "(ref 0x%02x)\n", req_ref->ra); - if (gsm48_match_ra(ms, req_ref)) { + if (gsm48_match_ra(ms, req_ref, IMM_ASS_HISTORY)) { /* wait indication */ t3122_value = *(((uint8_t *)&ia->wait_ind1) + i * 4); if (t3122_value) @@ -2671,7 +3612,7 @@ static int gsm48_rr_rx_imm_ass_rej(struct osmocom_ms *ms, struct msgb *msg) /* start timer 3126 if not already */ if (!osmo_timer_pending(&rr->t3126)) start_rr_t3126(rr, 5, 0); /* TODO improve! */ - /* stop assignmnet requests */ + /* stop assignment requests */ rr->n_chan_req = 0; /* wait until timer 3126 expires, then release @@ -2683,7 +3624,7 @@ static int gsm48_rr_rx_imm_ass_rej(struct osmocom_ms *ms, struct msgb *msg) return 0; } -/* 9.1.1 ADDITIONAL ASSIGMENT is received */ +/* 9.1.1 ADDITIONAL ASSIGNMENT is received */ static int gsm48_rr_rx_add_ass(struct osmocom_ms *ms, struct msgb *msg) { struct gsm48_hdr *gh = msgb_l3(msg); @@ -2692,12 +3633,13 @@ static int gsm48_rr_rx_add_ass(struct osmocom_ms *ms, struct msgb *msg) struct tlv_parsed tp; if (payload_len < 0) { - LOGP(DRR, LOGL_NOTICE, "Short read of ADDITIONAL ASSIGNMENT " - "message.\n"); - return gsm48_rr_tx_rr_status(ms, - GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + LOGP(DRR, LOGL_NOTICE, "Short read of ADDITIONAL ASSIGNMENT message\n"); + return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + if (tlv_parse(&tp, &gsm48_rr_att_tlvdef, aa->data, payload_len, 0, 0) < 0) { + LOGP(DRR, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); } - tlv_parse(&tp, &gsm48_rr_att_tlvdef, aa->data, payload_len, 0, 0); return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); } @@ -2732,7 +3674,7 @@ static int gsm48_rr_tx_meas_rep(struct osmocom_ms *ms) if ((s->si5bis && s->nb_ext_ind_si5 && s->nb_ba_ind_si5bis != rep_ba) || (s->si5ter && s->nb_ba_ind_si5ter != rep_ba)) { - LOGP(DRR, LOGL_NOTICE, "BA-IND missmatch on SI5*"); + LOGP(DRR, LOGL_NOTICE, "BA-IND mismatch on SI5*"); } else rep_valid = 1; } @@ -2751,7 +3693,7 @@ static int gsm48_rr_tx_meas_rep(struct osmocom_ms *ms) if (rep_valid) { int8_t strongest, current; uint8_t ncc; - int i, index; + int i, index, strongest_i; #if 0 /* FIXME: multi-band reporting, if not: 0 = normal reporting */ @@ -2761,17 +3703,25 @@ static int gsm48_rr_tx_meas_rep(struct osmocom_ms *ms) /* get 6 strongest measurements */ strongest = 127; /* infinite */ + strongest_i = -1; /* make gcc happy */ for (n = 0; n < 6; n++) { current = -128; /* -infinite */ index = 0; for (i = 0; i < rrmeas->nc_num; i++) { + /* Skip stronger cells that have been added to measurement report so far. */ + if (rrmeas->nc_rxlev_dbm[i] > strongest) + continue; + /* Skip cells with equal strength that have been added so far. */ + if (rrmeas->nc_rxlev_dbm[i] == strongest && i <= strongest_i) + continue; /* only check if NCC is permitted */ ncc = rrmeas->nc_bsic[i] >> 3; if ((s->nb_ncc_permitted_si6 & (1 << ncc)) - && rrmeas->nc_rxlev_dbm[i] > current - && rrmeas->nc_rxlev_dbm[i] < strongest) { + && rrmeas->nc_rxlev_dbm[i] > current) { current = rrmeas->nc_rxlev_dbm[i]; + strongest = current; index = i; + strongest_i = i; } } if (current == -128) /* no more found */ @@ -2876,6 +3826,9 @@ int gsm48_rr_los(struct osmocom_ms *ms) LOGP(DSUM, LOGL_INFO, "Radio link lost signal\n"); + if (rr->vgcs.group_state != GSM48_RR_GST_OFF) + return gsm48_rr_group_rel(ms, RR_REL_CAUSE_LOST_SIGNAL); + /* stop T3211 if running */ stop_rr_t3110(rr); @@ -2980,18 +3933,22 @@ static int gsm48_rr_activate_channel(struct osmocom_ms *ms, gsm48_rr_tx_meas_rep(ms); /* establish */ - LOGP(DRR, LOGL_INFO, "establishing channel in dedicated mode\n"); - rsl_dec_chan_nr(cd->chan_nr, &ch_type, &ch_subch, &ch_ts); - LOGP(DRR, LOGL_INFO, " Channel type %d, subch %d, ts %d, mode %d, " - "audio-mode %d, cipher %d\n", ch_type, ch_subch, ch_ts, - cd->mode, rr->audio_mode, rr->cipher_type + 1); + LOGP(DRR, LOGL_INFO, "establishing channel in dedicated/group mode\n"); + + if (rsl_dec_chan_nr(cd->chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, cd->chan_nr); + return -EINVAL; + } + + LOGP(DRR, LOGL_INFO, " Channel type %d, subch %d, ts %d, mode %d, audio-mode %d, flags 0x%02x, cipher %d\n", + ch_type, ch_subch, ch_ts, cd->mode, rr->audio_mode, cd->tch_flags, rr->cipher_type + 1); if (cd->h) - l1ctl_tx_dm_est_req_h1(ms, cd->maio, cd->hsn, - ma, ma_len, cd->chan_nr, cd->tsc, cd->mode, - rr->audio_mode); + l1ctl_tx_dm_est_req_h1(ms, cd->maio, cd->hsn, ma, ma_len, cd->chan_nr, cd->tsc, cd->mode, + rr->audio_mode, cd->tch_flags); else - l1ctl_tx_dm_est_req_h0(ms, cd->arfcn, cd->chan_nr, cd->tsc, - cd->mode, rr->audio_mode); + l1ctl_tx_dm_est_req_h0(ms, cd->arfcn, cd->chan_nr, cd->tsc, cd->mode, rr->audio_mode, cd->tch_flags); rr->dm_est = 1; /* old SI 5/6 are not valid on a new dedicated channel */ @@ -3021,7 +3978,7 @@ static int gsm48_rr_channel_after_time(struct osmocom_ms *ms, l1ctl_tx_crypto_req(ms, rr->cd_now.chan_nr, rr->cipher_type + 1, subscr->key, 8); - gsm48_rr_set_mode(ms, cd->chan_nr, cd->mode); + gsm48_rr_set_mode(ms, cd->chan_nr, cd->mode, cd->tch_flags); return 0; } @@ -3033,8 +3990,8 @@ static int gsm48_rr_render_ma(struct osmocom_ms *ms, struct gsm48_rr_cd *cd, struct gsm322_cellsel *cs = &ms->cellsel; struct gsm48_sysinfo *s = cs->si; struct gsm_settings *set = &ms->settings; - int i, pcs, index; - uint16_t arfcn; + int i, index; + uint16_t arfcn, pcs; pcs = gsm_refer_pcs(cs->arfcn, s) ? ARFCN_PCS : 0; @@ -3054,7 +4011,7 @@ static int gsm48_rr_render_ma(struct osmocom_ms *ms, struct gsm48_rr_cd *cd, LOGP(DRR, LOGL_INFO, "using cell channel descr.\n"); if (cd->cell_desc_lv[0] != 16) { LOGP(DRR, LOGL_ERROR, "cell channel descr. " - "has invalid lenght\n"); + "has invalid length\n"); return GSM48_RR_CAUSE_ABNORMAL_UNSPEC; } gsm48_decode_freq_list(freq, cd->cell_desc_lv + 1, 16, @@ -3137,7 +4094,9 @@ static int gsm48_rr_render_ma(struct osmocom_ms *ms, struct gsm48_rr_cd *cd, /* convert to band_arfcn and check for unsported frequency */ for (i = 0; i < *ma_len; i++) { - arfcn = ma[i] | pcs; + arfcn = ma[i]; + if (arfcn >= 512 && arfcn <= 810) + arfcn |= pcs; ma[i] = arfcn; index = arfcn2index(arfcn); if (!(set->freq_map[index >> 3] & (1 << (index & 7)))) { @@ -3154,12 +4113,11 @@ static int gsm48_rr_render_ma(struct osmocom_ms *ms, struct gsm48_rr_cd *cd, static int gsm48_rr_dl_est(struct osmocom_ms *ms) { struct gsm48_rrlayer *rr = &ms->rrlayer; - struct gsm322_cellsel *cs = &ms->cellsel; struct gsm_subscriber *subscr = &ms->subscr; + struct gsm322_cellsel *cs = &ms->cellsel; struct msgb *nmsg; struct gsm48_hdr *gh; struct gsm48_pag_rsp *pr; - uint8_t mi[11]; uint16_t ma[64]; uint8_t ma_len; @@ -3233,26 +4191,21 @@ static int gsm48_rr_dl_est(struct osmocom_ms *ms) pr->cm2_len = sizeof(pr->cm2); gsm48_rr_enc_cm2(ms, &pr->cm2, rr->cd_now.arfcn); /* mobile identity */ - if (ms->subscr.tmsi != 0xffffffff - && ms->subscr.mcc == cs->sel_mcc - && ms->subscr.mnc == cs->sel_mnc - && ms->subscr.lac == cs->sel_lac + if (ms->subscr.tmsi != GSM_RESERVED_TMSI + && (osmo_lai_cmp(&ms->subscr.lai, &cs->sel_cgi.lai) == 0) && rr->paging_mi_type == GSM_MI_TYPE_TMSI) { - gsm48_generate_mid_from_tmsi(mi, subscr->tmsi); + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_TMSI, false); LOGP(DRR, LOGL_INFO, "sending paging response with " "TMSI\n"); } else if (subscr->imsi[0]) { - gsm48_generate_mid_from_imsi(mi, subscr->imsi); + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_IMSI, false); LOGP(DRR, LOGL_INFO, "sending paging response with " "IMSI\n"); } else { - mi[1] = 1; - mi[2] = 0xf0 | GSM_MI_TYPE_NONE; + gsm48_encode_mi_lv(ms, nmsg, GSM_MI_TYPE_NONE, false); LOGP(DRR, LOGL_INFO, "sending paging response without " "TMSI/IMSI\n"); } - msgb_put(nmsg, 1 + mi[1]); - memcpy(pr->data, mi + 1, 1 + mi[1]); } #ifdef TEST_FREQUENCY_MOD @@ -3326,6 +4279,12 @@ static int gsm48_rr_rel_ind(struct osmocom_ms *ms, struct msgb *msg) struct msgb *nmsg; struct gsm48_rr_hdr *nrrh; + /* Handle on group channel. */ + if (rr->vgcs.group_state == GSM48_RR_GST_TRANSMIT) { + LOGP(DRR, LOGL_INFO, "Returning to group receive mode, due to link release indication.\n"); + return gsm48_rr_uplink_access_abort(ms, RR_REL_CAUSE_NORMAL); + } + /* switch back to old channel, if modify/ho failed */ switch (rr->modify_state) { case GSM48_RR_MOD_ASSIGN: @@ -3372,12 +4331,13 @@ static int gsm48_rr_rx_chan_rel(struct osmocom_ms *ms, struct msgb *msg) uint8_t *mode; if (payload_len < 0) { - LOGP(DRR, LOGL_NOTICE, "Short read of CHANNEL RELEASE " - "message.\n"); - return gsm48_rr_tx_rr_status(ms, - GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + LOGP(DRR, LOGL_NOTICE, "Short read of CHANNEL RELEASE message\n"); + return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + if (tlv_parse(&tp, &gsm48_rr_att_tlvdef, cr->data, payload_len, 0, 0) < 0) { + LOGP(DRR, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); } - tlv_parse(&tp, &gsm48_rr_att_tlvdef, cr->data, payload_len, 0, 0); LOGP(DRR, LOGL_INFO, "channel release request with cause 0x%02x\n", cr->rr_cause); @@ -3395,6 +4355,9 @@ static int gsm48_rr_rx_chan_rel(struct osmocom_ms *ms, struct msgb *msg) new_rr_state(rr, GSM48_RR_ST_REL_PEND); + /* When we receive a channel release, we are not just releasing the transmit mode. */ + rr->vgcs.group_state = GSM48_RR_GST_OFF; + /* start T3110, so that two DISCs can be sent due to T200 timeout */ start_rr_t3110(rr, 1, 500000); @@ -3412,27 +4375,59 @@ static int gsm48_rr_rx_chan_rel(struct osmocom_ms *ms, struct msgb *msg) return 0; } +/* Release of channel on VGCS/VBS. */ +static int gsm48_rr_rx_chan_rel_ui(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_chan_rel *cr = (struct gsm48_chan_rel *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*cr); + struct tlv_parsed tp; + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of CHANNEL RELEASE message\n"); + return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + if (tlv_parse(&tp, &gsm48_rr_att_tlvdef, cr->data, payload_len, 0, 0) < 0) { + LOGP(DRR, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + + LOGP(DRR, LOGL_INFO, "CHANNEL RELESE via UI frame with cause 0x%02x\n", cr->rr_cause); + + /* Only allow when in group receive mode. */ + if (rr->vgcs.group_state != GSM48_RR_GST_RECEIVE) + return 0; + + return gsm48_rr_group_rel(ms, RR_REL_CAUSE_NORMAL); +} + /* - * frequency redefition, chanel mode modify, assignment, and handover + * frequency redefition, channel mode modify, assignment, and handover */ /* set channel mode in case of TCH */ -static int gsm48_rr_set_mode(struct osmocom_ms *ms, uint8_t chan_nr, - uint8_t mode) +static int gsm48_rr_set_mode(struct osmocom_ms *ms, uint8_t chan_nr, uint8_t mode, uint8_t tch_flags) { struct gsm48_rrlayer *rr = &ms->rrlayer; uint8_t ch_type, ch_subch, ch_ts; + if (rsl_dec_chan_nr(chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, chan_nr); + return -EINVAL; + } + /* only apply mode to TCH/F or TCH/H */ - rsl_dec_chan_nr(chan_nr, &ch_type, &ch_subch, &ch_ts); if (ch_type != RSL_CHAN_Bm_ACCHs && ch_type != RSL_CHAN_Lm_ACCHs) return -ENOTSUP; - /* setting (new) timing advance */ - LOGP(DRR, LOGL_INFO, "setting TCH mode to %d, audio mode to %d\n", - mode, rr->audio_mode); - l1ctl_tx_tch_mode_req(ms, mode, rr->audio_mode); + /* Apply indicated channel mode */ + LOGP(DRR, LOGL_INFO, "setting TCH mode to %s, audio mode to %d, tch flags to %d\n", + get_value_string(gsm48_chan_mode_names, mode), rr->audio_mode, tch_flags); + l1ctl_tx_tch_mode_req(ms, mode, rr->audio_mode, tch_flags, rr->tch_loop_mode); return 0; } @@ -3469,7 +4464,12 @@ static int gsm48_rr_rx_frq_redef(struct osmocom_ms *ms, struct msgb *msg) /* decode channel description */ LOGP(DRR, LOGL_INFO, "FREQUENCY REDEFINITION:\n"); cd.chan_nr = fr->chan_desc.chan_nr; - rsl_dec_chan_nr(cd.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(cd.chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, cd.chan_nr); + return -EINVAL; + } if (fr->chan_desc.h0.h) { cd.h = 1; gsm48_decode_chan_h1(&fr->chan_desc, &cd.tsc, &cd.maio, @@ -3479,8 +4479,7 @@ static int gsm48_rr_rx_frq_redef(struct osmocom_ms *ms, struct msgb *msg) } else { cd.h = 0; gsm48_decode_chan_h0(&fr->chan_desc, &cd.tsc, &cd.arfcn); - if (gsm_refer_pcs(cs->arfcn, s)) - cd.arfcn |= ARFCN_PCS; + cd.arfcn = gsm_arfcn_refer_pcs(cs->arfcn, s, cd.arfcn); LOGP(DRR, LOGL_INFO, " (ARFCN %s TS %u SS %u TSC %u)\n", gsm_print_arfcn(cd.arfcn), ch_ts, ch_subch, cd.tsc); } @@ -3559,7 +4558,7 @@ static int gsm48_rr_rx_chan_modify(struct osmocom_ms *ms, struct msgb *msg) int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*cm); struct gsm48_rr_cd *cd = &rr->cd_now; uint8_t ch_type, ch_subch, ch_ts; - uint8_t cause; + int rc; LOGP(DRR, LOGL_INFO, "CHANNEL MODE MODIFY\n"); @@ -3573,7 +4572,12 @@ static int gsm48_rr_rx_chan_modify(struct osmocom_ms *ms, struct msgb *msg) /* decode channel description */ cd->chan_nr = cm->chan_desc.chan_nr; - rsl_dec_chan_nr(cd->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(cd->chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, cd->chan_nr); + return -EINVAL; + } if (cm->chan_desc.h0.h) { cd->h = 1; gsm48_decode_chan_h1(&cm->chan_desc, &cd->tsc, &cd->maio, @@ -3584,21 +4588,36 @@ static int gsm48_rr_rx_chan_modify(struct osmocom_ms *ms, struct msgb *msg) } else { cd->h = 0; gsm48_decode_chan_h0(&cm->chan_desc, &cd->tsc, &cd->arfcn); - if (gsm_refer_pcs(cs->arfcn, s)) - cd->arfcn |= ARFCN_PCS; + cd->arfcn = gsm_arfcn_refer_pcs(cs->arfcn, s, cd->arfcn); LOGP(DRR, LOGL_INFO, " (chan_nr 0x%02x ARFCN %s TS %u SS %u " "TSC %u mode %u)\n", cm->chan_desc.chan_nr, gsm_print_arfcn(cd->arfcn), ch_ts, ch_subch, cd->tsc, cm->mode); } - /* mode */ - cause = gsm48_rr_check_mode(ms, cd->chan_nr, cm->mode); - if (cause) - return gsm48_rr_tx_rr_status(ms, cause); + + /** + * According to 3GPP TS 04.08, section 3.4.6.1.3 + * "Abnormal cases" of "channel mode modify procedure", + * if the MS doesn't support the indicated channel mode, + * it shall retain the old mode and return the associated + * channel mode information in the ACKNOWLEDGE message. + */ + + /* Check if we support this channel mode */ + rc = gsm48_rr_check_mode(ms, cd->chan_nr, cm->mode); + if (rc) + goto ack; + + /* Attempt to apply this mode */ + rc = gsm48_rr_set_mode(ms, cd->chan_nr, cm->mode, cd->tch_flags); + if (rc) + goto ack; + + /* Finally set (a new) mode */ cd->mode = cm->mode; - gsm48_rr_set_mode(ms, cd->chan_nr, cd->mode); - return gsm48_rr_tx_chan_modify_ack(ms, &cm->chan_desc, cm->mode); +ack: + return gsm48_rr_tx_chan_modify_ack(ms, &cm->chan_desc, cd->mode); } /* 9.1.3 sending ASSIGNMENT COMPLETE */ @@ -3684,19 +4703,25 @@ static int gsm48_rr_rx_ass_cmd(struct osmocom_ms *ms, struct msgb *msg) cdb->ind_tx_power = rr->cd_now.ind_tx_power; if (payload_len < 0) { - LOGP(DRR, LOGL_NOTICE, "Short read of ASSIGNMENT COMMAND " - "message.\n"); - return gsm48_rr_tx_rr_status(ms, - GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + LOGP(DRR, LOGL_NOTICE, "Short read of ASSIGNMENT COMMAND message\n"); + return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + if (tlv_parse(&tp, &gsm48_rr_att_tlvdef, ac->data, payload_len, 0, 0) < 0) { + LOGP(DRR, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); } - tlv_parse(&tp, &gsm48_rr_att_tlvdef, ac->data, payload_len, 0, 0); /* decode channel description (before time) */ if (TLVP_PRESENT(&tp, GSM48_IE_CH_DESC_1_BEFORE)) { struct gsm48_chan_desc *ccd = (struct gsm48_chan_desc *) TLVP_VAL(&tp, GSM48_IE_CH_DESC_1_BEFORE); cdb->chan_nr = ccd->chan_nr; - rsl_dec_chan_nr(cdb->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(cdb->chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, cdb->chan_nr); + return -EINVAL; + } if (ccd->h0.h) { cdb->h = 1; gsm48_decode_chan_h1(ccd, &cdb->tsc, &cdb->maio, @@ -3707,8 +4732,7 @@ static int gsm48_rr_rx_ass_cmd(struct osmocom_ms *ms, struct msgb *msg) } else { cdb->h = 0; gsm48_decode_chan_h0(ccd, &cdb->tsc, &cdb->arfcn); - if (gsm_refer_pcs(cs->arfcn, s)) - cdb->arfcn |= ARFCN_PCS; + cdb->arfcn = gsm_arfcn_refer_pcs(cs->arfcn, s, cdb->arfcn); LOGP(DRR, LOGL_INFO, " before: (chan_nr 0x%02x " "ARFCN %s TS %u SS %u TSC %u)\n", ccd->chan_nr, gsm_print_arfcn(cdb->arfcn), @@ -3719,7 +4743,12 @@ static int gsm48_rr_rx_ass_cmd(struct osmocom_ms *ms, struct msgb *msg) /* decode channel description (after time) */ cda->chan_nr = ac->chan_desc.chan_nr; - rsl_dec_chan_nr(cda->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(cda->chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, cda->chan_nr); + return -EINVAL; + } if (ac->chan_desc.h0.h) { cda->h = 1; gsm48_decode_chan_h1(&ac->chan_desc, &cda->tsc, &cda->maio, @@ -3730,8 +4759,7 @@ static int gsm48_rr_rx_ass_cmd(struct osmocom_ms *ms, struct msgb *msg) } else { cda->h = 0; gsm48_decode_chan_h0(&ac->chan_desc, &cda->tsc, &cda->arfcn); - if (gsm_refer_pcs(cs->arfcn, s)) - cda->arfcn |= ARFCN_PCS; + cda->arfcn = gsm_arfcn_refer_pcs(cs->arfcn, s, cda->arfcn); LOGP(DRR, LOGL_INFO, " after: (chan_nr 0x%02x ARFCN %s TS %u " "SS %u TSC %u)\n", ac->chan_desc.chan_nr, gsm_print_arfcn(cda->arfcn), ch_ts, ch_subch, cda->tsc); @@ -4057,10 +5085,12 @@ static int gsm48_rr_rx_hando_cmd(struct osmocom_ms *ms, struct msgb *msg) cdb->ind_tx_power = rr->cd_now.ind_tx_power; if (payload_len < 0) { - LOGP(DRR, LOGL_NOTICE, "Short read of HANDOVER COMMAND " - "message.\n"); - return gsm48_rr_tx_rr_status(ms, - GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + LOGP(DRR, LOGL_NOTICE, "Short read of HANDOVER COMMAND message\n"); + return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + if (tlv_parse(&tp, &gsm48_rr_att_tlvdef, ho->data, payload_len, 0, 0) < 0) { + LOGP(DRR, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); } /* cell description */ @@ -4070,8 +5100,6 @@ static int gsm48_rr_rx_hando_cmd(struct osmocom_ms *ms, struct msgb *msg) rr->chan_req_val = ho->ho_ref; rr->chan_req_mask = 0x00; - tlv_parse(&tp, &gsm48_rr_att_tlvdef, ho->data, payload_len, 0, 0); - /* sync ind */ if (TLVP_PRESENT(&tp, GSM48_IE_SYNC_IND)) { gsm48_decode_sync_ind(rr, (struct gsm48_sync_ind *) @@ -4085,7 +5113,12 @@ static int gsm48_rr_rx_hando_cmd(struct osmocom_ms *ms, struct msgb *msg) struct gsm48_chan_desc *ccd = (struct gsm48_chan_desc *) TLVP_VAL(&tp, GSM48_IE_CH_DESC_1_BEFORE); cdb->chan_nr = ccd->chan_nr; - rsl_dec_chan_nr(cdb->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(cdb->chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, cdb->chan_nr); + return -EINVAL; + } if (ccd->h0.h) { cdb->h = 1; gsm48_decode_chan_h1(ccd, &cdb->tsc, &cdb->maio, @@ -4096,8 +5129,7 @@ static int gsm48_rr_rx_hando_cmd(struct osmocom_ms *ms, struct msgb *msg) } else { cdb->h = 0; gsm48_decode_chan_h0(ccd, &cdb->tsc, &cdb->arfcn); - if (gsm_refer_pcs(cs->arfcn, s)) - cdb->arfcn |= ARFCN_PCS; + cdb->arfcn = gsm_arfcn_refer_pcs(cs->arfcn, s, cdb->arfcn); LOGP(DRR, LOGL_INFO, " before: (chan_nr 0x%02x " "ARFCN %s TS %u SS %u TSC %u)\n", ccd->chan_nr, gsm_print_arfcn(cdb->arfcn), @@ -4108,7 +5140,12 @@ static int gsm48_rr_rx_hando_cmd(struct osmocom_ms *ms, struct msgb *msg) /* decode channel description (after time) */ cda->chan_nr = ho->chan_desc.chan_nr; - rsl_dec_chan_nr(cda->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(cda->chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, cda->chan_nr); + return -EINVAL; + } if (ho->chan_desc.h0.h) { cda->h = 1; gsm48_decode_chan_h1(&ho->chan_desc, &cda->tsc, &cda->maio, @@ -4119,8 +5156,7 @@ static int gsm48_rr_rx_hando_cmd(struct osmocom_ms *ms, struct msgb *msg) } else { cda->h = 0; gsm48_decode_chan_h0(&ho->chan_desc, &cda->tsc, &cda->arfcn); - if (gsm_refer_pcs(cs->arfcn, s)) - cda->arfcn |= ARFCN_PCS; + cda->arfcn = gsm_arfcn_refer_pcs(cs->arfcn, s, cda->arfcn); LOGP(DRR, LOGL_INFO, " after: (chan_nr 0x%02x ARFCN %s TS %u " "SS %u TSC %u)\n", ho->chan_desc.chan_nr, gsm_print_arfcn(cda->arfcn), ch_ts, ch_subch, cda->tsc); @@ -4366,6 +5402,16 @@ static int gsm48_rr_rx_hando_cmd(struct osmocom_ms *ms, struct msgb *msg) return 0; } +/* send HANDOVER/UPLINK ACCESS burst (9.1.14) */ +static int gsm48_rr_tx_rand_acc_dedicated(struct osmocom_ms *ms, uint8_t ref, uint16_t offset, int8_t uic) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + LOGP(DRR, LOGL_INFO, "send access burst on DCCH (ref 0x%02x)\n", ref); + + return l1ctl_tx_rach_req(ms, rr->cd_now.chan_nr, 0x00, ref, 0, offset, uic); +} + /* send all queued messages down to layer 2 */ static int gsm48_rr_dequeue_down(struct osmocom_ms *ms) { @@ -4388,17 +5434,27 @@ static int gsm48_rr_dequeue_down(struct osmocom_ms *ms) return 0; } -/* channel is resumed in dedicated mode */ +/* Channel is resumed in dedicated mode or uplink is estabished. */ static int gsm48_rr_estab_cnf_dedicated(struct osmocom_ms *ms, struct msgb *msg) { struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *nmsg; - LOGP(DRR, LOGL_INFO, "data link is resumed\n"); + if (rr->vgcs.group_state == GSM48_RR_GST_TRANSMIT) { + LOGP(DRR, LOGL_INFO, "data link established on uplink\n"); - /* transmit queued frames during ho / ass transition */ - gsm48_rr_dequeue_down(ms); + /* Confirm uplink access. */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_UPLINK_CNF); + if (nmsg) + gsm48_rr_upmsg(ms, nmsg); + } else { + LOGP(DRR, LOGL_INFO, "data link is resumed\n"); - rr->modify_state = GSM48_RR_MOD_NONE; + /* transmit queued frames during ho / ass transition */ + gsm48_rr_dequeue_down(ms); + + rr->modify_state = GSM48_RR_MOD_NONE; + } return 0; } @@ -4488,6 +5544,13 @@ static int gsm48_rr_est_req(struct osmocom_ms *ms, struct msgb *msg) struct gsm48_rr_hdr *nrrh; uint16_t acc_class; + /* Reject during group call. */ + if (rr->vgcs.group_state != GSM48_RR_GST_OFF) { + LOGP(DRR, LOGL_INFO, "We have a group call, rejecting!\n"); + cause = RR_REL_CAUSE_TRY_LATER; + goto reject; + } + /* 3.3.1.1.3.2 */ if (osmo_timer_pending(&rr->t3122)) { if (rrh->cause != RR_EST_CAUSE_EMERGENCY) { @@ -4537,7 +5600,7 @@ static int gsm48_rr_est_req(struct osmocom_ms *ms, struct msgb *msg) goto reject; } - /* check for relevant informations */ + /* check for relevant information */ if (!s->si3) { LOGP(DRR, LOGL_INFO, "Not enough SI, rejecting!\n"); cause = RR_REL_CAUSE_TRY_LATER; @@ -4575,10 +5638,317 @@ static int gsm48_rr_est_req(struct osmocom_ms *ms, struct msgb *msg) memcpy(msgb_put(rr->rr_est_msg, msgb_l3len(msg)), msgb_l3(msg), msgb_l3len(msg)); + if (rr->vgcs.group_state == GSM48_RR_GST_RECEIVE) { + /* Release group receive mode. */ + l1ctl_tx_dm_rel_req(rr->ms); + rr->ms->meas.rl_fail = 0; + l1ctl_tx_reset_req(rr->ms, L1CTL_RES_T_SCHED); + } + /* request channel */ return gsm48_rr_chan_req(ms, rrh->cause, 0, 0); } +/* 3.3.3.2 Request for group receive mode. */ +static int gsm48_rr_group_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = &cs->sel_si; + struct gsm48_chan_desc *ch_desc = (struct gsm48_chan_desc *)(msg->data + sizeof(struct gsm48_rr_hdr)); + uint8_t cause; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + struct gsm48_rr_cd cd; + uint8_t ch_type, ch_subch, ch_ts; + uint16_t ma[64]; + uint8_t ma_len; + int rc; + + /* if state is not idle */ + if (rr->state != GSM48_RR_ST_IDLE || rr->vgcs.group_state != GSM48_RR_GST_OFF) { + LOGP(DRR, LOGL_INFO, "We are not IDLE yet, rejecting!\n"); + cause = RR_REL_CAUSE_TRY_LATER; +reject: + LOGP(DSUM, LOGL_INFO, "Joining group call channel not possible\n"); + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_GROUP_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = cause; + return gsm48_rr_upmsg(ms, nmsg); + } + + /* cell selected */ + if (!cs->selected) { + LOGP(DRR, LOGL_INFO, "No cell selected, rejecting!\n"); + cause = RR_REL_CAUSE_TRY_LATER; + goto reject; + } + + /* check if camping */ + if (cs->state != GSM322_C3_CAMPED_NORMALLY + && cs->state != GSM322_C7_CAMPED_ANY_CELL) { + LOGP(DRR, LOGL_INFO, "Not camping, rejecting! " + "(cs->state = %d)\n", cs->state); + cause = RR_REL_CAUSE_TRY_LATER; + goto reject; + } + + /* get channel description */ + memset(&cd, 0, sizeof(cd)); + cd.chan_nr = ch_desc->chan_nr; + if (rsl_dec_chan_nr(cd.chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRR, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, cd.chan_nr); + cause = GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + goto reject; + } + if (ch_desc->h0.h) { + LOGP(DRR, LOGL_ERROR, "HOPPING NOT SUPPORTED, PLEASE FIX!\n"); + cd.h = 1; + gsm48_decode_chan_h1(ch_desc, &cd.tsc, &cd.maio, + &cd.hsn); + LOGP(DRR, LOGL_INFO, " (chan_nr 0x%02x MAIO %u HSN %u TS %u SS %u TSC %u)\n", + ch_desc->chan_nr, cd.maio, cd.hsn, ch_ts, ch_subch, cd.tsc); + } else { + cd.h = 0; + gsm48_decode_chan_h0(ch_desc, &cd.tsc, &cd.arfcn); + cd.arfcn = gsm_arfcn_refer_pcs(cs->arfcn, s, cd.arfcn); + LOGP(DRR, LOGL_INFO, " (chan_nr 0x%02x ARFCN %s TS %u SS %u TSC %u)\n", + ch_desc->chan_nr, gsm_print_arfcn(cd.arfcn), ch_ts, ch_subch, cd.tsc); + } + if (gsm48_rr_render_ma(ms, &rr->vgcs.cd_group, ma, &ma_len)) { + cause = GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + goto reject; + } + + /* Turn off transmitter. */ + cd.tch_flags |= L1CTL_TCH_FLAG_RXONLY; + + /* Set mode to Speech V1. FIXME: Add AMR support. */ + cd.mode = GSM48_CMODE_SPEECH_V1; + + /* Set current channel and also store as 'vgcs.cd_group', used when leaving dedicated mode. */ + memcpy(&rr->cd_now, &cd, sizeof(rr->cd_now)); + memcpy(&rr->vgcs.cd_group, &cd, sizeof(rr->vgcs.cd_group)); + + /* tell cell selection process to leave idle mode + * NOTE: this must be sent unbuffered, because the state may not + * change until idle mode is left + */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_LEAVE_IDLE); + if (!nmsg) + return -ENOMEM; + rc = gsm322_c_event(ms, nmsg); + msgb_free(nmsg); + if (rc) { + LOGP(DRR, LOGL_INFO, "Failed to leave IDLE mode.\n"); + cause = GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + goto reject; + } + + /* Initial uplink state. */ + rr->vgcs.uplink_free = false; + + /* Set group state to receive mode. */ + rr->vgcs.group_state = GSM48_RR_GST_RECEIVE; + + /* Wait until synced to the CCCH ... */ + return 0; +} +/* ... Continue after synced to the CCCH. */ +static int gsm48_rr_group_req_continue(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + uint16_t ma[64]; + uint8_t ma_len; + struct msgb *nmsg; + + /* get hopping sequence, if required */ + gsm48_rr_render_ma(ms, &rr->vgcs.cd_group, ma, &ma_len); + /* activate channel */ + gsm48_rr_activate_channel(ms, &rr->vgcs.cd_group, ma, ma_len); + + /* Confirm group call channel. */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_GROUP_CNF); + if (!nmsg) + return -ENOMEM; + return gsm48_rr_upmsg(ms, nmsg); +} + +/* After "loss of signal"/release in group transmit or receive mode, return IDLE and release towards MM. */ +static int gsm48_rr_group_rel(struct osmocom_ms *ms, int cause) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + uint8_t *mode; + + /* Stop group receive and transmit timers. */ + stop_rr_t_ul_free(rr); + stop_rr_t3128(rr); + stop_rr_t3130(rr); + + if (rr->state == GSM48_RR_ST_DEDICATED || rr->state == GSM48_RR_ST_REL_PEND) { + struct msgb *nmsg; + + LOGP(DRR, LOGL_INFO, "Channel lost, send (local) release to layer 2.\n"); + + /* Go into group receive mode, so that the channel gets released on LAPD release confirm. */ + rr->vgcs.group_state = GSM48_RR_GST_RECEIVE; + if (rr->state != GSM48_RR_ST_REL_PEND) + new_rr_state(rr, GSM48_RR_ST_REL_PEND); + + /* release message */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = RSL_REL_LOCAL_END; + gsm48_send_rsl_nol3(ms, RSL_MT_REL_REQ, nmsg, 0); + + /* release SAPI 3 link, if exits */ + gsm48_release_sapi3_link(ms); + + /* Wait for LAPD to confirm. Then return idle. */ + return 0; + } + + /* Go back to IDLE mode. */ + rr->vgcs.group_state = GSM48_RR_GST_OFF; + new_rr_state(rr, GSM48_RR_ST_IDLE); + + /* Indicate release to MM. */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_GROUP_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = cause; + return gsm48_rr_upmsg(ms, nmsg); +} + +/* Release group channel, return to IDLE. */ +static int gsm48_rr_group_rel_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + /* Only in group receive/transmit mode. */ + if (rr->vgcs.group_state == GSM48_RR_GST_OFF) + return 0; + + if (rr->state == GSM48_RR_ST_CONN_PEND || rr->state == GSM48_RR_ST_DEDICATED) { + LOGP(DRR, LOGL_INFO, "Connot release group channel yet, perform uplink release first.\n"); + gsm48_rr_uplink_rel_req(ms, msg); + if (rr->state == GSM48_RR_ST_REL_PEND) { + /* Leave the group call state, so that we change to IDLE when we released the uplink. */ + rr->vgcs.group_state = GSM48_RR_GST_OFF; + return 0; + } + } + + /* Stop group receive and transmit timers. */ + stop_rr_t_ul_free(rr); + stop_rr_t3128(rr); + stop_rr_t3130(rr); + + /* Unset group state. Wait, if release is pending. */ + rr->vgcs.group_state = GSM48_RR_GST_OFF; + if (rr->state != GSM48_RR_ST_IDLE) { + LOGP(DRR, LOGL_INFO, "Cannot release group channel yet, wait to return to IDLE state.\n"); + return 0; + } + + LOGP(DRR, LOGL_INFO, "Release Group channel.\n"); + new_rr_state(rr, GSM48_RR_ST_IDLE); + return 0; +} + +/* Request uplink in group receive mode. */ +static int gsm48_rr_uplink_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + /* Only in group receive mode */ + if (rr->state != GSM48_RR_ST_IDLE || rr->vgcs.group_state != GSM48_RR_GST_RECEIVE) { + LOGP(DRR, LOGL_INFO, "We are not in group receive mode yet, rejecting!\n"); + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_UPLINK_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = RR_REL_CAUSE_TRY_LATER; + return gsm48_rr_upmsg(ms, nmsg); + } + + LOGP(DRR, LOGL_INFO, "Changing from group receive mode to group transmit mode.\n"); + + /* Enter uplink access procedure. */ + new_rr_state(rr, GSM48_RR_ST_CONN_PEND); + + /* Set group state to transmit mode. */ + rr->vgcs.group_state = GSM48_RR_GST_TRANSMIT; + + /* Uplink investigation procedure (3.3.1.2.1.1) */ + if (!rr->vgcs.uplink_free) { + start_rr_t3128(rr, GSM_T3128_MS); + return 0; + } + + rr->vgcs.uplink_tries = 3; + return gsm48_rr_uplink_access(ms, NULL); +} + +/* Leave uplink, also called when leaving group channel. */ +static int gsm48_rr_uplink_rel_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm_settings *set = &ms->settings; + struct msgb *nmsg; + uint8_t *mode; + + /* Only in group transmit mode */ + if (rr->vgcs.group_state != GSM48_RR_GST_TRANSMIT) + return -EINVAL; + + /* Stop group transmit mode timers. */ + stop_rr_t3128(rr); + stop_rr_t3130(rr); + + /* Continue if in dedicated mode, so we release and wait for uplink to become free. */ + if (rr->state != GSM48_RR_ST_DEDICATED) + return 0; + + LOGP(DRR, LOGL_INFO, "Returning from group transmit to group receive mode.\n"); + + /* Leave uplink, wait for uplink being free, channel release or channel/link failure. */ + gsm48_rr_tx_uplink_release(ms, GSM48_RR_CAUSE_LEAVE_GROUP_CA); + + /* Go into release pending mode. */ + new_rr_state(rr, GSM48_RR_ST_REL_PEND); + + /* Special setting where we release locally. This means we wait for free uplink an then release. */ + if (set->uplink_release_local) { + LOGP(DRR, LOGL_INFO, "Release L2 locally as specified via VTY. Wait for UPLINK FREE.\n"); + return 0; + } + + /* Release dedicated mode. */ + /* disconnect the main signalling link */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = RSL_REL_NORMAL; + gsm48_send_rsl_nol3(ms, RSL_MT_REL_REQ, nmsg, 0); + + return 0; +} + /* 3.4.2 transfer data in dedicated mode */ static int gsm48_rr_data_req(struct osmocom_ms *ms, struct msgb *msg) { @@ -4623,17 +5993,21 @@ static int gsm48_rr_data_req(struct osmocom_ms *ms, struct msgb *msg) /* 3.4.2 data from layer 2 to RR and upper layer*/ static int gsm48_rr_data_ind(struct osmocom_ms *ms, struct msgb *msg) { + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + uint8_t sapi = rllh->link_id & 7; struct gsm48_hdr *gh = msgb_l3(msg); struct gsm48_rr_hdr *rrh; uint8_t pdisc = gh->proto_discr & 0x0f; + int rc = -EINVAL; if (pdisc == GSM48_PDISC_RR) { - int rc = -EINVAL; uint8_t skip_ind = (gh->proto_discr & 0xf0) >> 4; /* ignore if skip indicator is not B'0000' */ - if (skip_ind) + if (skip_ind) { + msgb_free(msg); return 0; + } switch(gh->msg_type) { case GSM48_MT_RR_ADD_ASS: @@ -4660,6 +6034,9 @@ static int gsm48_rr_data_ind(struct osmocom_ms *ms, struct msgb *msg) case GSM48_MT_RR_CHAN_REL: rc = gsm48_rr_rx_chan_rel(ms, msg); break; + case GSM48_MT_RR_UPLINK_RELEASE: + rc = gsm48_rr_rx_uplink_release(ms, msg); + break; case GSM48_MT_RR_APP_INFO: LOGP(DRR, LOGL_NOTICE, "APP INFO not supported!\n"); break; @@ -4673,6 +6050,10 @@ static int gsm48_rr_data_ind(struct osmocom_ms *ms, struct msgb *msg) msgb_free(msg); return rc; + } else if (pdisc == GSM48_PDISC_TEST) { + rc = gsm414_rcv_test(ms, msg); + msgb_free(msg); + return rc; } /* pull off RSL header up to L3 message */ @@ -4682,6 +6063,7 @@ static int gsm48_rr_data_ind(struct osmocom_ms *ms, struct msgb *msg) msgb_push(msg, sizeof(struct gsm48_rr_hdr)); rrh = (struct gsm48_rr_hdr *)msg->data; rrh->msg_type = GSM48_RR_DATA_IND; + rrh->sapi = sapi; return gsm48_rr_upmsg(ms, msg); } @@ -4704,6 +6086,8 @@ static int gsm48_rr_rx_bcch(struct osmocom_ms *ms, struct msgb *msg) return gsm48_rr_rx_sysinfo3(ms, msg); case GSM48_MT_RR_SYSINFO_4: return gsm48_rr_rx_sysinfo4(ms, msg); + case GSM48_MT_RR_SYSINFO_13: + return gsm48_rr_rx_sysinfo13(ms, msg); default: #if 0 LOGP(DRR, LOGL_NOTICE, "BCCH message type 0x%02x not sup.\n", @@ -4718,6 +6102,11 @@ static int gsm48_rr_rx_pch_agch(struct osmocom_ms *ms, struct msgb *msg) { struct gsm48_system_information_type_header *sih = msgb_l3(msg); + if (msgb_l3len(msg) < sizeof(*sih)) { + LOGP(DRR, LOGL_NOTICE, "Short read of CCCH message.\n"); + return -EINVAL; + } + switch (sih->system_information) { case GSM48_MT_RR_PAG_REQ_1: return gsm48_rr_rx_pag_req_1(ms, msg); @@ -4732,6 +6121,10 @@ static int gsm48_rr_rx_pch_agch(struct osmocom_ms *ms, struct msgb *msg) return gsm48_rr_rx_imm_ass_ext(ms, msg); case GSM48_MT_RR_IMM_ASS_REJ: return gsm48_rr_rx_imm_ass_rej(ms, msg); + + case GSM48_MT_RR_NOTIF_NCH: + return gsm48_rr_rx_notif_nch(ms, msg); + default: #if 0 LOGP(DRR, LOGL_NOTICE, "CCCH message type 0x%02x unknown.\n", @@ -4741,48 +6134,70 @@ static int gsm48_rr_rx_pch_agch(struct osmocom_ms *ms, struct msgb *msg) } } +#define N201_Bter_SACCH 21 +#define N201_Bter_SDCCH_FACCH 23 +#define N201_B4 19 + /* receive ACCH at RR layer */ static int gsm48_rr_rx_acch(struct osmocom_ms *ms, struct msgb *msg) { - struct gsm48_rrlayer *rr = &ms->rrlayer; - struct gsm_settings *set = &ms->settings; - struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); - struct gsm48_hdr *sih = msgb_l3(msg); - uint8_t ind_ta, ind_tx_power; - - if (msgb_l2len(msg) < sizeof(*rllh) + 2 + 2) { - LOGP(DRR, LOGL_ERROR, "Missing TA and TX_POWER IEs\n"); - return -EINVAL; + const struct gsm48_system_information_type_header *sih; + const struct gsm48_hdr_sh *sgh; + const struct gsm48_hdr *gh; + + /* Bter frame (SACCH or SDCCH/FACCH) */ + if (msgb_l3len(msg) == N201_Bter_SACCH || msgb_l3len(msg) == N201_Bter_SDCCH_FACCH) { + sgh = msgb_l3(msg); + if (sgh->rr_short_pd != GSM48_PDISC_SH_RR) { + LOGP(DRR, LOGL_NOTICE, "Short header message is not an RR message.\n"); + return -EINVAL; + } + switch (sgh->msg_type) { + case GSM48_MT_RR_SH_UL_FREE: + return gsm48_rr_rx_uplink_free(ms, msg); + case GSM48_MT_RR_SH_FACCH: + return gsm48_rr_rx_notif_facch(ms, msg); + case GSM48_MT_RR_SH_SI10: + return gsm48_rr_rx_sysinfo_10(ms, msg); + default: + LOGP(DRR, LOGL_NOTICE, "Short header message type 0x%02x unsupported.\n", sgh->msg_type); + return -EINVAL; + } } - ind_ta = rllh->data[1]; - ind_tx_power = rllh->data[3]; - LOGP(DRR, LOGL_INFO, "Indicated ta %d (actual ta %d)\n", - ind_ta, ind_ta - set->alter_delay); - LOGP(DRR, LOGL_INFO, "Indicated tx_power %d\n", - ind_tx_power); - if (ind_ta != rr->cd_now.ind_ta - || ind_tx_power != rr->cd_now.ind_tx_power) { - LOGP(DRR, LOGL_INFO, "setting new ta and tx_power\n"); - l1ctl_tx_param_req(ms, ind_ta - set->alter_delay, - (set->alter_tx_power) ? set->alter_tx_power_value - : ind_tx_power); - rr->cd_now.ind_ta = ind_ta; - rr->cd_now.ind_tx_power = ind_tx_power; + /* B4 frame (SACCH) */ + if ((msg->cb[0] & 0x40) && msgb_l3len(msg) == N201_B4) { + sih = msgb_l3(msg); + switch (sih->system_information) { + case GSM48_MT_RR_SYSINFO_5: + return gsm48_rr_rx_sysinfo5(ms, msg); + case GSM48_MT_RR_SYSINFO_5bis: + return gsm48_rr_rx_sysinfo5bis(ms, msg); + case GSM48_MT_RR_SYSINFO_5ter: + return gsm48_rr_rx_sysinfo5ter(ms, msg); + case GSM48_MT_RR_SYSINFO_6: + return gsm48_rr_rx_sysinfo6(ms, msg); + default: + LOGP(DRR, LOGL_NOTICE, "ACCH message type 0x%02x unknown.\n", sih->system_information); + return -EINVAL; + } } - switch (sih->msg_type) { - case GSM48_MT_RR_SYSINFO_5: - return gsm48_rr_rx_sysinfo5(ms, msg); - case GSM48_MT_RR_SYSINFO_5bis: - return gsm48_rr_rx_sysinfo5bis(ms, msg); - case GSM48_MT_RR_SYSINFO_5ter: - return gsm48_rr_rx_sysinfo5ter(ms, msg); - case GSM48_MT_RR_SYSINFO_6: - return gsm48_rr_rx_sysinfo6(ms, msg); + /* B frame (UI frame on VGCS) */ + if (msgb_l3len(msg) < sizeof(*gh)) { + LOGP(DRR, LOGL_NOTICE, "ACCH message too short.\n"); + return -EINVAL; + } + gh = msgb_l3(msg); + switch (gh->msg_type) { + case GSM48_MT_RR_VGCS_UPL_GRANT: + return gsm48_rr_rx_vgcs_uplink_grant(ms, msg); + case GSM48_MT_RR_UPLINK_BUSY: + return gsm48_rr_rx_uplink_busy(ms, msg); + case GSM48_MT_RR_CHAN_REL: + return gsm48_rr_rx_chan_rel_ui(ms, msg); default: - LOGP(DRR, LOGL_NOTICE, "ACCH message type 0x%02x unknown.\n", - sih->msg_type); + LOGP(DRR, LOGL_NOTICE, "ACCH message type 0x%02x unknown.\n", gh->msg_type); return -EINVAL; } } @@ -4790,15 +6205,40 @@ static int gsm48_rr_rx_acch(struct osmocom_ms *ms, struct msgb *msg) /* unit data from layer 2 to RR layer */ static int gsm48_rr_unit_data_ind(struct osmocom_ms *ms, struct msgb *msg) { + struct gsm48_rrlayer *rr = &ms->rrlayer; struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm_settings *set = &ms->settings; struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); struct tlv_parsed tv; + int ind_ta = -1, ind_tx_power = -1; uint8_t ch_type, ch_subch, ch_ts; DEBUGP(DRSL, "RSLms UNIT DATA IND chan_nr=0x%02x link_id=0x%02x\n", rllh->chan_nr, rllh->link_id); - rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh)); + if (rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg) - sizeof(*rllh)) < 0) { + LOGP(DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return -EINVAL; + } + + /* Update TX power and timing advance, if included in message. */ + if (TLVP_PRES_LEN(&tv, RSL_IE_TIMING_ADVANCE, 1)) { + ind_ta = *TLVP_VAL(&tv, RSL_IE_TIMING_ADVANCE); + LOGP(DRR, LOGL_INFO, "DL SACCH indicates ta %d (actual ta %d)\n", ind_ta, ind_ta - set->alter_delay); + } + if (TLVP_PRES_LEN(&tv, RSL_IE_MS_POWER, 1)) { + ind_tx_power = *TLVP_VAL(&tv, RSL_IE_MS_POWER); + LOGP(DRR, LOGL_INFO, "DL SACCH indicates tx_power %d\n", ind_tx_power); + } + if ((ind_ta >= 0 && ind_ta != rr->cd_now.ind_ta) || + (ind_tx_power >= 0 && ind_tx_power != rr->cd_now.ind_tx_power)) { + LOGP(DRR, LOGL_INFO, "Applying new ta and tx_power\n"); + l1ctl_tx_param_req(ms, ind_ta - set->alter_delay, + (set->alter_tx_power) ? set->alter_tx_power_value : ind_tx_power); + rr->cd_now.ind_ta = ind_ta; + rr->cd_now.ind_tx_power = ind_tx_power; + } + if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { DEBUGP(DRSL, "UNIT_DATA_IND without L3 INFO ?!?\n"); return -EIO; @@ -4814,6 +6254,10 @@ static int gsm48_rr_unit_data_ind(struct osmocom_ms *ms, struct msgb *msg) LOGP(DCS, LOGL_INFO, "Channel provides data.\n"); cs->ccch_state = GSM322_CCCH_ST_DATA; + /* in group receive mode */ + if (ms->rrlayer.vgcs.group_state == GSM48_RR_GST_RECEIVE) + return gsm48_rr_group_req_continue(ms); + /* in dedicated mode */ if (ms->rrlayer.state == GSM48_RR_ST_CONN_PEND) return gsm48_rr_tx_rand_acc(ms, NULL); @@ -4832,7 +6276,13 @@ static int gsm48_rr_unit_data_ind(struct osmocom_ms *ms, struct msgb *msg) // TODO: timer depends on BCCH config } - rsl_dec_chan_nr(rllh->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(rllh->chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRSL, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, rllh->chan_nr); + return -EINVAL; + } + switch (ch_type) { case RSL_CHAN_PCH_AGCH: return gsm48_rr_rx_pch_agch(ms, msg); @@ -4842,6 +6292,7 @@ static int gsm48_rr_unit_data_ind(struct osmocom_ms *ms, struct msgb *msg) case RSL_CHAN_Lm_ACCHs: case RSL_CHAN_SDCCH4_ACCH: case RSL_CHAN_SDCCH8_ACCH: + msg->cb[0] = rllh->link_id; return gsm48_rr_rx_acch(ms, msg); default: LOGP(DRSL, LOGL_NOTICE, "RSL with chan_nr 0x%02x unknown.\n", @@ -4900,6 +6351,12 @@ static int gsm48_rr_rel_cnf(struct osmocom_ms *ms, struct msgb *msg) uint16_t ma[64]; uint8_t ma_len; + /* Handle on group channel. */ + if (rr->vgcs.group_state == GSM48_RR_GST_TRANSMIT) { + LOGP(DRR, LOGL_INFO, "Returning to group receive mode, due to link release confirm.\n"); + return gsm48_rr_uplink_access_abort(ms, RR_REL_CAUSE_NORMAL); + } + /* switch back to old channel, if modify/ho failed */ switch (rr->modify_state) { case GSM48_RR_MOD_ASSIGN: @@ -4946,7 +6403,11 @@ static int gsm48_rr_rel_cnf(struct osmocom_ms *ms, struct msgb *msg) stop_rr_t3110(rr); /* send release indication */ - nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (rr->vgcs.group_state == GSM48_RR_GST_RECEIVE) { + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_GROUP_REL_IND); + rr->vgcs.group_state = GSM48_RR_GST_OFF; + } else + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); if (!nmsg) return -ENOMEM; nrrh = (struct gsm48_rr_hdr *)nmsg->data; @@ -4994,6 +6455,12 @@ static int gsm48_rr_mdl_error_ind(struct osmocom_ms *ms, struct msgb *msg) if (rr->modify_state) return 0; + /* Handle on group channel. */ + if ((link_id & 7) == 0 && rr->vgcs.group_state == GSM48_RR_GST_TRANSMIT) { + LOGP(DRR, LOGL_INFO, "Returning to group receive mode, due to link failure.\n"); + return gsm48_rr_uplink_access_abort(ms, RR_REL_CAUSE_LINK_FAILURE); + } + /* send abort ind to upper layer */ nmsg = gsm48_rr_msgb_alloc(GSM48_RR_ABORT_IND); if (!nmsg) @@ -5156,7 +6623,13 @@ static int gsm48_rr_est_req_sapi3(struct osmocom_ms *ms, struct msgb *msg) return gsm48_rr_upmsg(ms, nmsg); } - rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRSL, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, rr->cd_now.chan_nr); + return -EINVAL; + } + if (ch_type != RSL_CHAN_Bm_ACCHs && ch_type != RSL_CHAN_Lm_ACCHs) { LOGP(DRR, LOGL_INFO, "Requesting DCCH link, because no TCH " @@ -5250,11 +6723,6 @@ static struct dldatastate { {SBIT(GSM48_RR_ST_DEDICATED), RSL_MT_SUSP_CONF, gsm48_rr_susp_cnf_dedicated}, -#if 0 - {SBIT(GSM48_RR_ST_DEDICATED), - RSL_MT_CHAN_CNF, gsm48_rr_rand_acc_cnf_dedicated}, -#endif - {SBIT(GSM48_RR_ST_DEDICATED), RSL_MT_ERROR_IND, gsm48_rr_mdl_error_ind}, }; @@ -5351,11 +6819,24 @@ static int gsm48_rcv_cch(struct osmocom_ms *ms, struct msgb *msg) "%s\n", ms->name, rsl_msg_name(msg_type), gsm48_rr_state_names[rr->state]); - if (rr->state == GSM48_RR_ST_CONN_PEND - && msg_type == RSL_MT_CHAN_CONF) { - rc = gsm48_rr_tx_rand_acc(ms, msg); - msgb_free(msg); - return rc; + if (msg_type == RSL_MT_CHAN_CONF) { + /* Recevie confirm to get the FN when the access burst was transmitted on VGCS channel. */ + if (rr->state == GSM48_RR_ST_CONN_PEND && rr->vgcs.group_state == GSM48_RR_GST_TRANSMIT) { + rc = gsm48_rr_uplink_access(ms, msg); + msgb_free(msg); + return rc; + } + /* Ignore subsequent access bursts in dedicated group transmit mode. */ + if (rr->state == GSM48_RR_ST_DEDICATED && rr->vgcs.group_state == GSM48_RR_GST_TRANSMIT) { + msgb_free(msg); + return 0; + } + /* Recevie confirm to get the FN when the access burst was transmitted on CCCH. */ + if (rr->state == GSM48_RR_ST_CONN_PEND) { + rc = gsm48_rr_tx_rand_acc(ms, msg); + msgb_free(msg); + return rc; + } } LOGP(DRSL, LOGL_NOTICE, "RSLms message unhandled\n"); @@ -5364,7 +6845,7 @@ static int gsm48_rcv_cch(struct osmocom_ms *ms, struct msgb *msg) } -/* input function for L2 messags up to L3 */ +/* input function for L2 messages up to L3 */ static int gsm48_rcv_rsl(struct osmocom_ms *ms, struct msgb *msg) { struct abis_rsl_common_hdr *rslh = msgb_l2(msg); @@ -5407,6 +6888,20 @@ static struct rrdownstate { {SBIT(GSM48_RR_ST_CONN_PEND) | SBIT(GSM48_RR_ST_DEDICATED), /* 3.4.13.3 */ GSM48_RR_ABORT_REQ, gsm48_rr_abort_req}, + + /* NOTE: If not IDLE, it is rejected there. */ + {ALL_STATES, /* 3.3.3.2 */ + GSM48_RR_GROUP_REQ, gsm48_rr_group_req}, + + {ALL_STATES, + GSM48_RR_GROUP_REL_REQ, gsm48_rr_group_rel_req}, + + /* NOTE: If not IDLE, it is rejected there. */ + {ALL_STATES, /* 3.3.1.2 */ + GSM48_RR_UPLINK_REQ, gsm48_rr_uplink_req}, + + {ALL_STATES, /* 3.4.13.4 */ + GSM48_RR_UPLINK_REL_REQ, gsm48_rr_uplink_rel_req}, }; #define RRDOWNSLLEN \ @@ -5501,7 +6996,11 @@ int gsm48_rr_init(struct osmocom_ms *ms) start_rr_t_meas(rr, 1, 0); - rr->audio_mode = AUDIO_TX_MICROPHONE | AUDIO_RX_SPEAKER; + rr->tch_loop_mode = L1CTL_TCH_LOOP_OPEN; + rr->audio_mode = 0x00; /* set in tch_{voice,data}_state_init() */ + + /* List of notifications about ongoing ASCI calls */ + INIT_LLIST_HEAD(&rr->vgcs.notif_list); return 0; } @@ -5531,6 +7030,12 @@ int gsm48_rr_exit(struct osmocom_ms *ms) stop_rr_t3122(rr); stop_rr_t3124(rr); stop_rr_t3126(rr); + stop_rr_t_ul_free(rr); + stop_rr_t3128(rr); + stop_rr_t3130(rr); + + /* Free pending list entries. */ + asci_notif_list_free(rr); return 0; } @@ -5574,75 +7079,34 @@ static void start_rr_t3124(struct gsm48_rrlayer *rr, int sec, int micro) osmo_timer_schedule(&rr->t3124, sec, micro); } -/* send HANDOVER ACCESS burst (9.1.14) */ -static int gsm48_rr_tx_hando_access(struct osmocom_ms *ms) -{ - nmsg = msgb_alloc_headroom(20, 16, "HAND_ACCESS"); - if (!nmsg) - return -ENOMEM; - *msgb_put(nmsg, 1) = rr->hando_ref; - todo burst - return gsm48_send_rsl(ms, RSL_MT_RAND_ACC_REQ, nmsg, 0); -} - -/* send next channel request in dedicated state */ -static int gsm48_rr_rand_acc_cnf_dedicated(struct osmocom_ms *ms, struct msgb *msg) -{ - struct gsm48_rrlayer *rr = &ms->rrlayer; - struct msgb *nmsg; - int s; - - if (rr->modify_state != GSM48_RR_MOD_HANDO) { - LOGP(DRR, LOGL_NOTICE, "Random acces confirm, but not in handover state.\n"); - return 0; - } - - /* send up to four handover access bursts */ - if (rr->hando_acc_left) { - rr->hando_acc_left--; - gsm48_rr_tx_hando_access(ms); - return; - } - - /* start timer for sending next HANDOVER ACCESS bursts afterwards */ - if (!osmo_timer_pending(&rr->t3124)) { - if (allocated channel is SDCCH) - start_rr_t3124(rr, GSM_T3124_675); - else - start_rr_t3124(rr, GSM_T3124_320); - } - if (!rr->n_chan_req) { - start_rr_t3126(rr, 5, 0); /* TODO improve! */ - return 0; - } - rr->n_chan_req--; - - /* wait for PHYSICAL INFORMATION message or T3124 timeout */ - return 0; - -} - #endif -int gsm48_rr_tx_voice(struct osmocom_ms *ms, struct msgb *msg) +int gsm48_rr_tx_traffic(struct osmocom_ms *ms, struct msgb *msg) { struct gsm48_rrlayer *rr = &ms->rrlayer; uint8_t ch_type, ch_subch, ch_ts; if (!rr->dm_est) { LOGP(DRR, LOGL_INFO, "Current channel is not active\n"); - msgb_free(msg); - return -ENOTSUP; + goto error; } - rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, &ch_subch, &ch_ts); - if (ch_type != RSL_CHAN_Bm_ACCHs) { - LOGP(DRR, LOGL_INFO, "Current channel is not (yet) TCH/F\n"); - msgb_free(msg); - return -ENOTSUP; + if (rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRSL, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, rr->cd_now.chan_nr); + goto error; + } + + if (ch_type != RSL_CHAN_Bm_ACCHs && ch_type != RSL_CHAN_Lm_ACCHs) { + LOGP(DRR, LOGL_INFO, "Current channel is not (yet) TCH\n"); + goto error; } return l1ctl_tx_traffic_req(ms, msg, rr->cd_now.chan_nr, 0); +error: + msgb_free(msg); + return -ENOTSUP; } int gsm48_rr_audio_mode(struct osmocom_ms *ms, uint8_t mode) @@ -5657,11 +7121,16 @@ int gsm48_rr_audio_mode(struct osmocom_ms *ms, uint8_t mode) if (!rr->dm_est) return 0; - rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, &ch_subch, &ch_ts) != 0) { + LOGP(DRSL, LOGL_ERROR, + "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n", + __func__, rr->cd_now.chan_nr); + return -EINVAL; + } + if (ch_type != RSL_CHAN_Bm_ACCHs && ch_type != RSL_CHAN_Lm_ACCHs) return 0; - return l1ctl_tx_tch_mode_req(ms, rr->cd_now.mode, mode); + return l1ctl_tx_tch_mode_req(ms, rr->cd_now.mode, mode, rr->cd_now.tch_flags, rr->tch_loop_mode); } - diff --git a/src/host/layer23/src/mobile/main.c b/src/host/layer23/src/mobile/main.c index 9764b333..8720ea76 100644 --- a/src/host/layer23/src/mobile/main.c +++ b/src/host/layer23/src/mobile/main.c @@ -15,14 +15,12 @@ * 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 <osmocom/bb/common/osmocom_data.h> #include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/l23_app.h> +#include <osmocom/bb/common/vty.h> #include <osmocom/bb/mobile/app_mobile.h> #include <osmocom/core/talloc.h> @@ -31,6 +29,10 @@ #include <osmocom/core/gsmtap.h> #include <osmocom/core/signal.h> #include <osmocom/core/application.h> +#include <osmocom/vty/vty.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/ports.h> #include <arpa/inet.h> @@ -46,51 +48,43 @@ #include <time.h> #include <libgen.h> +#include "config.h" + void *l23_ctx = NULL; +struct l23_global_config l23_cfg; struct llist_head ms_list; -static char *gsmtap_ip = 0; static const char *custom_cfg_file = NULL; -struct gsmtap_inst *gsmtap_inst = NULL; +static const char *log_cat_mask = NULL; +static char *config_file = NULL; char *config_dir = NULL; -int use_mncc_sock = 0; int daemonize = 0; +int quit = 0; -int mncc_recv_socket(struct osmocom_ms *ms, int msg_type, void *arg); +int (*l23_app_start)(void) = NULL; +int (*l23_app_work)(void) = NULL; +int (*l23_app_exit)(void) = NULL; int mobile_delete(struct osmocom_ms *ms, int force); -int mobile_signal_cb(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data); int mobile_work(struct osmocom_ms *ms); int mobile_exit(struct osmocom_ms *ms, int force); const char *debug_default = - "DCS:DNB:DPLMN:DRR:DMM:DSIM:DCC:DMNCC:DSS:DLSMS:DPAG:DSUM:DSAP:DGPS:DMOB:DPRIM:DLUA"; - -const char *openbsc_copyright = - "Copyright (C) 2010-2015 Andreas Eversberg, Sylvain Munaut, Holger Freyther, Harald Welte\n" - "Contributions by Alex Badea, Pablo Neira, Steve Markgraf and others\n\n" - "License GPLv2+: GNU GPL version 2 or later " - "<http://gnu.org/licenses/gpl.html>\n" - "This is free software: you are free to change and redistribute it.\n" - "There is NO WARRANTY, to the extent permitted by law.\n"; + "DCS:DNB:DPLMN:DRR:DMM:DSIM:DCC:DMNCC:DSS:DLSMS:DPAG:DSUM:DSAP:DGPS:DMOB:DPRIM:DLUA:DGAPK"; static void print_usage(const char *app) { printf("Usage: %s\n", app); } -static void print_help() +static void print_help(void) { printf(" Some help...\n"); printf(" -h --help this text\n"); - printf(" -i --gsmtap-ip The destination IP used for GSMTAP.\n"); printf(" -d --debug Change debug flags. default: %s\n", debug_default); printf(" -D --daemonize Run as daemon\n"); printf(" -c --config-file filename The config file to use.\n"); - printf(" -m --mncc-sock Disable built-in MNCC handler and " - "offer socket\n"); } static int handle_options(int argc, char **argv) @@ -99,12 +93,12 @@ static int handle_options(int argc, char **argv) int option_index = 0, c; static struct option long_options[] = { {"help", 0, 0, 'h'}, - {"gsmtap-ip", 1, 0, 'i'}, {"debug", 1, 0, 'd'}, {"daemonize", 0, 0, 'D'}, {"config-file", 1, 0, 'c'}, - {"mncc-sock", 0, 0, 'm'}, /* DEPRECATED options, to be removed */ + {"gsmtap-ip", 1, 0, 'i'}, + {"mncc-sock", 0, 0, 'm'}, {"vty-ip", 1, 0, 'u'}, {"vty-port", 1, 0, 'v'}, {0, 0, 0, 0}, @@ -121,22 +115,26 @@ static int handle_options(int argc, char **argv) print_help(); exit(0); break; - case 'i': - gsmtap_ip = optarg; - break; case 'c': custom_cfg_file = optarg; break; case 'd': - log_parse_category_mask(osmo_stderr_target, optarg); + log_cat_mask = optarg; break; case 'D': daemonize = 1; break; - case 'm': - use_mncc_sock = 1; - break; /* DEPRECATED options, to be removed */ + case 'i': + fprintf(stderr, "Option 'i' is deprecated! " + "Please use the configuration file " + "in order to set GSMTAP parameters.\n"); + return -EINVAL; + case 'm': + fprintf(stderr, "Option 'm' is deprecated! " + "Please use the configuration file " + "in order to change the MNCC handler.\n"); + return -EINVAL; case 'u': case 'v': fprintf(stderr, "Both 'u' and 'v' options are " @@ -166,7 +164,7 @@ void sighandler(int sigset) /* The first signal causes initiating of shutdown with detach * procedure. The second signal causes initiating of shutdown * without detach procedure. The third signal will exit process - * immidiately. (in case it hangs) + * immediately. (in case it hangs) */ if (count_int == 0) { fprintf(stderr, "Performing shutdown with clean " @@ -188,9 +186,16 @@ void sighandler(int sigset) osmo_signal_dispatch(SS_GLOBAL, S_GLOBAL_SHUTDOWN, &_quit); break; case SIGABRT: - /* in case of abort, we want to obtain a talloc report - * and then return to the caller, who will abort the process + /* in case of abort, we want to obtain a talloc report and + * then run default SIGABRT handler, who will generate coredump + * and abort the process. abort() should do this for us after we + * return, but program wouldn't exit if an external SIGABRT is + * received. */ + talloc_report_full(l23_ctx, stderr); + signal(SIGABRT, SIG_DFL); + raise(SIGABRT); + break; case SIGUSR1: case SIGUSR2: talloc_report_full(l23_ctx, stderr); @@ -198,13 +203,60 @@ void sighandler(int sigset) } } +static void print_copyright(void) +{ + printf("%s" + "%s\n" + "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n\n", + l23_app_info.copyright ? l23_app_info.copyright : "", + l23_app_info.contribution ? l23_app_info.contribution : ""); +} + +static int _vty_init(void) +{ + int rc; + + OSMO_ASSERT(l23_app_info.vty_info != NULL); + l23_app_info.vty_info->tall_ctx = l23_ctx; + + vty_init(l23_app_info.vty_info); + logging_vty_add_cmds(); + + if (l23_app_info.vty_init != NULL) + l23_app_info.vty_init(); + if (config_file) { + LOGP(DLGLOBAL, LOGL_INFO, "Using configuration from '%s'\n", config_file); + l23_vty_reading = true; + rc = vty_read_config_file(config_file, NULL); + l23_vty_reading = false; + if (rc < 0) { + LOGP(DLGLOBAL, LOGL_FATAL, + "Failed to parse the configuration file '%s'\n", config_file); + return rc; + } + } + rc = telnet_init_default(l23_ctx, NULL, OSMO_VTY_PORT_BB); + if (rc < 0) { + LOGP(DMOB, LOGL_FATAL, "Cannot init VTY on %s port %u: %s\n", + vty_get_bind_addr(), OSMO_VTY_PORT_BB, strerror(errno)); + return rc; + } + return rc; +} + int main(int argc, char **argv) { - char *config_file; - int quit = 0; int rc; - printf("%s\n", openbsc_copyright); + print_copyright(); + + rc = handle_options(argc, argv); + if (rc) { /* Abort in case of parsing errors */ + fprintf(stderr, "Error in command line options. Exiting.\n"); + return 1; + } srand(time(NULL)); @@ -217,19 +269,10 @@ int main(int argc, char **argv) /* Init default stderr logging */ osmo_init_logging2(l23_ctx, &log_info); - rc = handle_options(argc, argv); - if (rc) { /* Abort in case of parsing errors */ - fprintf(stderr, "Error in command line options. Exiting.\n"); - return 1; - } - - if (gsmtap_ip) { - gsmtap_inst = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 1); - if (!gsmtap_inst) { - fprintf(stderr, "Failed during gsmtap_init()\n"); - exit(1); - } - gsmtap_source_add_sink(gsmtap_inst); + rc = l23_app_init(); + if (rc < 0) { + fprintf(stderr, "Failed during l23_app_init()\n"); + exit(1); } if (custom_cfg_file) { @@ -250,12 +293,32 @@ int main(int argc, char **argv) config_dir = talloc_strdup(l23_ctx, config_file); config_dir = dirname(config_dir); - if (use_mncc_sock) - rc = l23_app_init(mncc_recv_socket, config_file); - else - rc = l23_app_init(NULL, config_file); - if (rc) - exit(rc); + if (l23_app_info.opt_supported & L23_OPT_VTY) { + if (_vty_init() < 0) + exit(1); + } + + if (log_cat_mask != NULL) + log_parse_category_mask(osmo_stderr_target, log_cat_mask); + + if (l23_cfg.gsmtap.remote_host) { + l23_cfg.gsmtap.inst = gsmtap_source_init2(l23_cfg.gsmtap.local_host, 0, + l23_cfg.gsmtap.remote_host, GSMTAP_UDP_PORT, 1); + if (!l23_cfg.gsmtap.inst) { + fprintf(stderr, "Failed during gsmtap_source_init2(%s -> %s:%u)\n", + l23_cfg.gsmtap.local_host, l23_cfg.gsmtap.remote_host, GSMTAP_UDP_PORT); + exit(1); + } + gsmtap_source_add_sink(l23_cfg.gsmtap.inst); + } + + if (l23_app_start) { + rc = l23_app_start(); + if (rc < 0) { + fprintf(stderr, "Failed during l23_app_start()\n"); + exit(1); + } + } signal(SIGINT, sighandler); signal(SIGTSTP, sighandler); @@ -274,13 +337,18 @@ int main(int argc, char **argv) } while (1) { - l23_app_work(&quit); + l23_app_work(); if (quit && llist_empty(&ms_list)) break; osmo_select_main(0); } - l23_app_exit(); + if (l23_app_exit) + rc = l23_app_exit(); + + if (l23_app_info.opt_supported & L23_OPT_VTY) + telnet_exit(); + log_fini(); talloc_free(config_file); diff --git a/src/host/layer23/src/mobile/mncc_sock.c b/src/host/layer23/src/mobile/mncc_sock.c index d7d56cc0..d31df291 100644 --- a/src/host/layer23/src/mobile/mncc_sock.c +++ b/src/host/layer23/src/mobile/mncc_sock.c @@ -14,10 +14,6 @@ * 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 <stdio.h> @@ -50,13 +46,19 @@ int mncc_sock_from_cc(struct mncc_sock_state *state, struct msgb *msg) /* Check if we currently have a MNCC handler connected */ if (state->conn_bfd.fd < 0) { + struct gsm_mncc mncc_out; LOGP(DMNCC, LOGL_ERROR, "mncc_sock receives %s for external CC app " "but socket is gone\n", get_mncc_name(msg_type)); - if (msg_type != GSM_TCHF_FRAME - && msg_type != GSM_TCHF_FRAME_EFR - && msg_type != MNCC_REL_IND) { + switch (msg_type) { + case MNCC_REL_IND: + case GSM_TCHF_FRAME: + case GSM_TCHF_FRAME_EFR: + case GSM_TCHH_FRAME: + case GSM_TCH_FRAME_AMR: + case GSM_BAD_FRAME: + break; + default: /* release the request */ - struct gsm_mncc mncc_out; memset(&mncc_out, 0, sizeof(mncc_out)); mncc_out.callref = mncc_in->callref; mncc_set_cause(&mncc_out, GSM48_CAUSE_LOC_PRN_S_LU, @@ -72,13 +74,13 @@ int mncc_sock_from_cc(struct mncc_sock_state *state, struct msgb *msg) /* Actually enqueue the message and mark socket write need */ msgb_enqueue(&state->upqueue, msg); - state->conn_bfd.when |= BSC_FD_WRITE; + osmo_fd_write_enable(&state->conn_bfd); return 0; } void mncc_sock_write_pending(struct mncc_sock_state *state) { - state->conn_bfd.when |= BSC_FD_WRITE; + osmo_fd_write_enable(&state->conn_bfd); } static void mncc_sock_close(struct mncc_sock_state *state) @@ -87,16 +89,16 @@ static void mncc_sock_close(struct mncc_sock_state *state) LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has closed connection\n"); + osmo_fd_unregister(bfd); close(bfd->fd); bfd->fd = -1; - osmo_fd_unregister(bfd); /* re-enable the generation of ACCEPT for new connections */ - state->listen_bfd.when |= BSC_FD_READ; + osmo_fd_read_enable(&state->listen_bfd); /* FIXME: make sure we don't enqueue anymore */ - /* release all exisitng calls */ + /* release all existing calls */ mncc_clear_trans(state->inst, GSM48_PDISC_CC); /* flush the queue */ @@ -156,7 +158,7 @@ static int mncc_sock_write(struct osmo_fd *bfd) msg = llist_entry(state->upqueue.next, struct msgb, list); mncc_prim = (struct gsm_mncc *)msg->data; - bfd->when &= ~BSC_FD_WRITE; + osmo_fd_write_disable(bfd); /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ if (!msgb_length(msg)) { @@ -171,7 +173,7 @@ static int mncc_sock_write(struct osmo_fd *bfd) goto close; if (rc < 0) { if (errno == EAGAIN) { - bfd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(bfd); break; } goto close; @@ -195,12 +197,12 @@ static int mncc_sock_cb(struct osmo_fd *bfd, unsigned int flags) { int rc = 0; - if (flags & BSC_FD_READ) + if (flags & OSMO_FD_READ) rc = mncc_sock_read(bfd); if (rc < 0) return rc; - if (flags & BSC_FD_WRITE) + if (flags & OSMO_FD_WRITE) rc = mncc_sock_write(bfd); return rc; @@ -226,16 +228,12 @@ static int mncc_sock_accept(struct osmo_fd *bfd, unsigned int flags) LOGP(DMNCC, LOGL_NOTICE, "MNCC app connects but we already have " "another active connection ?!?\n"); /* We already have one MNCC app connected, this is all we support */ - state->listen_bfd.when &= ~BSC_FD_READ; + osmo_fd_read_disable(&state->listen_bfd); close(rc); return 0; } - conn_bfd->fd = rc; - conn_bfd->when = BSC_FD_READ; - conn_bfd->cb = mncc_sock_cb; - conn_bfd->data = state; - + osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, mncc_sock_cb, state, 0); if (osmo_fd_register(conn_bfd) != 0) { LOGP(DMNCC, LOGL_ERROR, "Failed to register new connection fd\n"); close(conn_bfd->fd); diff --git a/src/host/layer23/src/mobile/mnccms.c b/src/host/layer23/src/mobile/mnccms.c index 22432913..67cf5a72 100644 --- a/src/host/layer23/src/mobile/mnccms.c +++ b/src/host/layer23/src/mobile/mnccms.c @@ -13,10 +13,6 @@ * 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 <stdint.h> @@ -26,9 +22,12 @@ #include <stdlib.h> #include <osmocom/core/talloc.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/common/settings.h> #include <osmocom/bb/mobile/mncc.h> #include <osmocom/bb/mobile/mncc_ms.h> #include <osmocom/bb/mobile/vty.h> @@ -36,7 +35,15 @@ static uint32_t new_callref = 1; static LLIST_HEAD(call_list); -static int dtmf_statemachine(struct gsm_call *call, struct gsm_mncc *mncc); +static const char * const gsm_call_type_str[] = { + [GSM_CALL_T_UNKNOWN] = "unknown", + [GSM_CALL_T_VOICE] = "voice", + [GSM_CALL_T_DATA] = "data", + [GSM_CALL_T_DATA_FAX] = "fax", +}; + +static int dtmf_statemachine(struct gsm_call *call, + const struct gsm_mncc *mncc); static void timeout_dtmf(void *arg); /* @@ -82,114 +89,452 @@ struct gsm_call *get_call_ref(uint32_t callref) return NULL; } -static int8_t mncc_get_bearer(struct gsm_settings *set, uint8_t speech_ver) +static int8_t mncc_get_bearer(const struct gsm_settings *set, uint8_t speech_ver) { switch (speech_ver) { - case 4: + case GSM48_BCAP_SV_AMR_F: if (set->full_v3) LOGP(DMNCC, LOGL_INFO, " net suggests full rate v3\n"); else { LOGP(DMNCC, LOGL_INFO, " full rate v3 not supported\n"); - speech_ver = -1; + return -1; } break; - case 2: + case GSM48_BCAP_SV_EFR: if (set->full_v2) LOGP(DMNCC, LOGL_INFO, " net suggests full rate v2\n"); else { LOGP(DMNCC, LOGL_INFO, " full rate v2 not supported\n"); - speech_ver = -1; + return -1; } break; - case 0: /* mandatory */ + case GSM48_BCAP_SV_FR: /* mandatory */ if (set->full_v1) LOGP(DMNCC, LOGL_INFO, " net suggests full rate v1\n"); else { LOGP(DMNCC, LOGL_INFO, " full rate v1 not supported\n"); - speech_ver = -1; + return -1; } break; - case 5: + case GSM48_BCAP_SV_AMR_H: if (set->half_v3) LOGP(DMNCC, LOGL_INFO, " net suggests half rate v3\n"); else { LOGP(DMNCC, LOGL_INFO, " half rate v3 not supported\n"); - speech_ver = -1; + return -1; } break; - case 1: + case GSM48_BCAP_SV_HR: if (set->half_v1) LOGP(DMNCC, LOGL_INFO, " net suggests half rate v1\n"); else { LOGP(DMNCC, LOGL_INFO, " half rate v1 not supported\n"); - speech_ver = -1; + return -1; } break; default: LOGP(DMNCC, LOGL_INFO, " net suggests unknown speech version " "%d\n", speech_ver); - speech_ver = -1; + return -1; } return speech_ver; } -static void mncc_set_bearer(struct osmocom_ms *ms, int8_t speech_ver, - struct gsm_mncc *mncc) +static void mncc_set_bcap_speech(struct gsm_mncc *mncc, + const struct gsm_settings *set, + int speech_ver) { - struct gsm_settings *set = &ms->settings; int i = 0; mncc->fields |= MNCC_F_BEARER_CAP; - mncc->bearer_cap.coding = 0; + mncc->bearer_cap.coding = GSM48_BCAP_CODING_GSM_STD; if (set->ch_cap == GSM_CAP_SDCCH_TCHF_TCHH && (set->half_v1 || set->half_v3)) { - mncc->bearer_cap.radio = 3; + mncc->bearer_cap.radio = GSM48_BCAP_RRQ_DUAL_FR; LOGP(DMNCC, LOGL_INFO, " support TCH/H also\n"); } else { - mncc->bearer_cap.radio = 1; + mncc->bearer_cap.radio = GSM48_BCAP_RRQ_FR_ONLY; LOGP(DMNCC, LOGL_INFO, " support TCH/F only\n"); } mncc->bearer_cap.speech_ctm = 0; /* if no specific speech_ver is given */ if (speech_ver < 0) { - /* if half rate is supported and prefered */ + /* if half rate is supported and preferred */ if (set->half_v3 && set->half && set->half_prefer) { - mncc->bearer_cap.speech_ver[i++] = 5; + mncc->bearer_cap.speech_ver[i++] = GSM48_BCAP_SV_AMR_H; LOGP(DMNCC, LOGL_INFO, " support half rate v3\n"); } if (set->half_v1 && set->half && set->half_prefer) { - mncc->bearer_cap.speech_ver[i++] = 1; + mncc->bearer_cap.speech_ver[i++] = GSM48_BCAP_SV_HR; LOGP(DMNCC, LOGL_INFO, " support half rate v1\n"); } /* if full rate is supported */ if (set->full_v3) { - mncc->bearer_cap.speech_ver[i++] = 4; + mncc->bearer_cap.speech_ver[i++] = GSM48_BCAP_SV_AMR_F; LOGP(DMNCC, LOGL_INFO, " support full rate v3\n"); } if (set->full_v2) { - mncc->bearer_cap.speech_ver[i++] = 2; + mncc->bearer_cap.speech_ver[i++] = GSM48_BCAP_SV_EFR; LOGP(DMNCC, LOGL_INFO, " support full rate v2\n"); } if (set->full_v1) { /* mandatory, so it's always true */ - mncc->bearer_cap.speech_ver[i++] = 0; + mncc->bearer_cap.speech_ver[i++] = GSM48_BCAP_SV_FR; LOGP(DMNCC, LOGL_INFO, " support full rate v1\n"); } - /* if half rate is supported and not prefered */ + /* if half rate is supported and not preferred */ if (set->half_v3 && set->half && !set->half_prefer) { - mncc->bearer_cap.speech_ver[i++] = 5; + mncc->bearer_cap.speech_ver[i++] = GSM48_BCAP_SV_AMR_H; LOGP(DMNCC, LOGL_INFO, " support half rate v3\n"); } if (set->half_v1 && set->half && !set->half_prefer) { - mncc->bearer_cap.speech_ver[i++] = 1; + mncc->bearer_cap.speech_ver[i++] = GSM48_BCAP_SV_HR; LOGP(DMNCC, LOGL_INFO, " support half rate v1\n"); } /* if specific speech_ver is given (it must be supported) */ } else mncc->bearer_cap.speech_ver[i++] = speech_ver; mncc->bearer_cap.speech_ver[i] = -1; /* end of list */ - mncc->bearer_cap.transfer = 0; - mncc->bearer_cap.mode = 0; + mncc->bearer_cap.transfer = GSM48_BCAP_ITCAP_SPEECH; + mncc->bearer_cap.mode = GSM48_BCAP_TMOD_CIRCUIT; +} + +static const struct bcap_data_set { + enum gsm48_bcap_ra ra; + enum gsm48_bcap_interm_rate ir; + enum gsm48_bcap_user_rate ur; + enum gsm48_bcap_modem_type mt; +} bcap_data_set_map[] = { + [DATA_CALL_TR_V21_300] = { + .ir = GSM48_BCAP_IR_8k, + .ur = GSM48_BCAP_UR_300, + .mt = GSM48_BCAP_MT_V21, + }, + [DATA_CALL_TR_V22_1200] = { + .ir = GSM48_BCAP_IR_8k, + .ur = GSM48_BCAP_UR_1200, + .mt = GSM48_BCAP_MT_V22, + }, + [DATA_CALL_TR_V23_1200_75] = { + .ir = GSM48_BCAP_IR_8k, + .ur = GSM48_BCAP_UR_1200_75, + .mt = GSM48_BCAP_MT_V23, + }, + [DATA_CALL_TR_V22bis_2400] = { + .ir = GSM48_BCAP_IR_8k, + .ur = GSM48_BCAP_UR_2400, + .mt = GSM48_BCAP_MT_V22bis, + }, + [DATA_CALL_TR_V26ter_2400] = { + .ir = GSM48_BCAP_IR_8k, + .ur = GSM48_BCAP_UR_2400, + .mt = GSM48_BCAP_MT_V26ter, + }, + [DATA_CALL_TR_V32_4800] = { + .ir = GSM48_BCAP_IR_8k, + .ur = GSM48_BCAP_UR_4800, + .mt = GSM48_BCAP_MT_V32, + }, + [DATA_CALL_TR_V32_9600] = { + .ir = GSM48_BCAP_IR_16k, + .ur = GSM48_BCAP_UR_9600, + .mt = GSM48_BCAP_MT_V32, + }, +#if 0 + [DATA_CALL_TR_V34_9600] = { + .ir = GSM48_BCAP_IR_16k, + .ur = GSM48_BCAP_UR_9600, + .mt = GSM48_BCAP_MT_V32, + /* (Octet 6c) Modem type: According to ITU-T Rec. V.32 + * (Octet 6d) Other modem type: According to ITU-T Rec. V.34 (2) + * TODO: gsm48_encode_bearer_cap() does not support octet 6d */ + }, +#endif + [DATA_CALL_TR_V110_300] = { + .ra = GSM48_BCAP_RA_V110_X30, + .ir = GSM48_BCAP_IR_8k, + .ur = GSM48_BCAP_UR_300, + }, + [DATA_CALL_TR_V110_1200] = { + .ra = GSM48_BCAP_RA_V110_X30, + .ir = GSM48_BCAP_IR_8k, + .ur = GSM48_BCAP_UR_1200, + }, + [DATA_CALL_TR_V110_2400] = { + .ra = GSM48_BCAP_RA_V110_X30, + .ir = GSM48_BCAP_IR_8k, + .ur = GSM48_BCAP_UR_2400, + }, + [DATA_CALL_TR_V110_4800] = { + .ra = GSM48_BCAP_RA_V110_X30, + .ir = GSM48_BCAP_IR_8k, + .ur = GSM48_BCAP_UR_4800, + }, + [DATA_CALL_TR_V110_9600] = { + .ra = GSM48_BCAP_RA_V110_X30, + .ir = GSM48_BCAP_IR_16k, + .ur = GSM48_BCAP_UR_9600, + }, +}; + +static void mncc_set_bcap_data(struct gsm_mncc *mncc, + const struct gsm_settings *set, + enum gsm_call_type call_type) +{ + const struct data_call_params *cp = &set->call_params.data; + struct gsm_mncc_bearer_cap *bcap = &mncc->bearer_cap; + const struct bcap_data_set *ds; + + OSMO_ASSERT(cp->type_rate < ARRAY_SIZE(bcap_data_set_map)); + ds = &bcap_data_set_map[cp->type_rate]; + if (ds->ir == 0 && ds->ur == 0) { + LOGP(DMNCC, LOGL_ERROR, "AT+CBST=%d is not supported\n", cp->type_rate); + return; + } + + mncc->fields |= MNCC_F_BEARER_CAP; + + *bcap = (struct gsm_mncc_bearer_cap) { + /* .transfer is set below */ + .mode = GSM48_BCAP_TMOD_CIRCUIT, + .coding = GSM48_BCAP_CODING_GSM_STD, + /* .radio is set below */ + .data = { + .sig_access = GSM48_BCAP_SA_I440_I450, + .rate_adaption = ds->ra, + .interm_rate = ds->ir, + .user_rate = ds->ur, + .modem_type = ds->mt, + .transp = cp->transp, + + /* async call params */ + .async = cp->is_async, + .nr_data_bits = cp->nr_data_bits, + .nr_stop_bits = cp->nr_stop_bits, + .parity = cp->parity, + }, + }; + + /* Radio channel requirement (octet 3) */ + if (set->ch_cap == GSM_CAP_SDCCH_TCHF_TCHH) { + if (set->half_prefer) + bcap->radio = GSM48_BCAP_RRQ_DUAL_HR; + else + bcap->radio = GSM48_BCAP_RRQ_DUAL_FR; + LOGP(DMNCC, LOGL_INFO, " support TCH/H also\n"); + } else { + bcap->radio = GSM48_BCAP_RRQ_FR_ONLY; + LOGP(DMNCC, LOGL_INFO, " support TCH/F only\n"); + } + + /* Information transfer capability (octet 3) */ + switch (call_type) { + case GSM_CALL_T_DATA: + if (ds->mt == GSM48_BCAP_MT_NONE) + bcap->transfer = GSM_MNCC_BCAP_UNR_DIG; + else /* analog modem */ + bcap->transfer = GSM_MNCC_BCAP_AUDIO; + break; + case GSM_CALL_T_DATA_FAX: + bcap->transfer = GSM_MNCC_BCAP_FAX_G3; + break; + case GSM_CALL_T_VOICE: + default: /* shall not happen */ + OSMO_ASSERT(0); + } + + /* FAX calls are special (see 3GPP TS 24.008, Annex D.3) */ + if (call_type == GSM_CALL_T_DATA_FAX) { + bcap->data.rate_adaption = GSM48_BCAP_RA_NONE; + bcap->data.async = 0; /* shall be sync */ + bcap->data.transp = GSM48_BCAP_TR_TRANSP; + bcap->data.modem_type = GSM48_BCAP_MT_NONE; + } +} + +/* Check the given Bearer Capability, select first supported speech codec version. + * The choice between half-rate and full-rate is made based on current settings. + * Return a selected codec or -1 if no speech codec was selected. */ +static int mncc_handle_bcap_speech(const struct gsm_mncc_bearer_cap *bcap, + const struct gsm_settings *set) +{ + int speech_ver_half = -1; + int speech_ver = -1; + + for (unsigned int i = 0; bcap->speech_ver[i] >= 0; i++) { + int temp = mncc_get_bearer(set, bcap->speech_ver[i]); + switch (temp) { + case GSM48_BCAP_SV_AMR_H: + case GSM48_BCAP_SV_HR: + /* only the first half rate */ + if (speech_ver_half < 0) + speech_ver_half = temp; + break; + default: + if (temp < 0) + continue; + /* only the first full rate */ + if (speech_ver < 0) + speech_ver = temp; + break; + } + } + + /* half and full given */ + if (speech_ver_half >= 0 && speech_ver >= 0) { + if (set->half_prefer) { + LOGP(DMNCC, LOGL_INFO, " both supported" + " codec rates are given, using " + "preferred half rate\n"); + speech_ver = speech_ver_half; + } else { + LOGP(DMNCC, LOGL_INFO, " both supported" + " codec rates are given, using " + "preferred full rate\n"); + } + } else if (speech_ver_half < 0 && speech_ver < 0) { + LOGP(DMNCC, LOGL_INFO, " no supported codec " + "rate is given\n"); + /* only half rate is given, use it */ + } else if (speech_ver_half >= 0) { + LOGP(DMNCC, LOGL_INFO, " only supported half " + "rate codec is given, using it\n"); + speech_ver = speech_ver_half; + /* only full rate is given, use it */ + } else { + LOGP(DMNCC, LOGL_INFO, " only supported full " + "rate codec is given, using it\n"); + } + + return speech_ver; +} + +/* Check the given Bearer Capability for a data call (CSD). + * Return 0 if the bearer is accepted, otherwise return -1. */ +static int mncc_handle_bcap_data(const struct gsm_mncc_bearer_cap *bcap, + const struct gsm_settings *set) +{ + switch (bcap->transfer) { + case GSM48_BCAP_ITCAP_UNR_DIG_INF: + if (bcap->data.rate_adaption != GSM48_BCAP_RA_V110_X30) { + LOGP(DMNCC, LOGL_ERROR, + "%s(): Rate adaption (octet 5) 0x%02x is not supported\n", + __func__, bcap->data.rate_adaption); + return -ENOTSUP; + } + break; + case GSM48_BCAP_ITCAP_3k1_AUDIO: + case GSM48_BCAP_ITCAP_FAX_G3: + if (bcap->data.rate_adaption != GSM48_BCAP_RA_NONE) { + LOGP(DMNCC, LOGL_ERROR, + "%s(): Rate adaption (octet 5) 0x%02x was expected to be NONE\n", + __func__, bcap->data.rate_adaption); + return -ENOTSUP; + } + break; + default: + break; + } + + if (bcap->data.sig_access != GSM48_BCAP_SA_I440_I450) { + LOGP(DMNCC, LOGL_ERROR, + "%s(): Signalling access protocol (octet 5) 0x%02x is not supported\n", + __func__, bcap->data.sig_access); + return -ENOTSUP; + } + +#define BCAP_RATE(interm_rate, user_rate) \ + ((interm_rate << 8) | (user_rate << 0)) + + switch (BCAP_RATE(bcap->data.interm_rate, bcap->data.user_rate)) { + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_300): + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_1200): + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_2400): + if (bcap->data.transp != GSM48_BCAP_TR_TRANSP) { + LOGP(DMNCC, LOGL_ERROR, + "%s(): wrong user-rate 0x%02x for a non-transparent call\n", + __func__, bcap->data.user_rate); + return -EINVAL; + } + /* fall-through */ + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_4800): + case BCAP_RATE(GSM48_BCAP_IR_16k, GSM48_BCAP_UR_9600): + if (bcap->data.transp != GSM48_BCAP_TR_TRANSP) { + LOGP(DMNCC, LOGL_ERROR, + "%s(): only transparent calls are supported so far\n", + __func__); + return -ENOTSUP; + } + break; + default: + LOGP(DMNCC, LOGL_ERROR, + "%s(): IR 0x%02x / UR 0x%02x combination is not supported\n", + __func__, bcap->data.interm_rate, bcap->data.user_rate); + return -ENOTSUP; + } + +#undef BCAP_RATE + + return 0; +} + +static int mncc_handle_bcap(struct gsm_mncc *mncc_out, /* CC Call Confirmed */ + const struct gsm_mncc *mncc_in, /* CC Setup */ + const struct gsm_settings *set) +{ + const struct gsm_mncc_bearer_cap *bcap = &mncc_in->bearer_cap; + + /* 3GPP TS 24.008, section 9.3.2.2 defines several cases in which the + * Bearer Capability 1 IE is to be included, provided that at least + * one of these conditions is met. */ + + /* if the Bearer Capability 1 IE is not present */ + if (~mncc_in->fields & MNCC_F_BEARER_CAP) { + /* ... include our own Bearer Capability, assuming a speech call */ + mncc_set_bcap_speech(mncc_out, set, -1); + return 0; + } + + if (bcap->mode != GSM48_BCAP_TMOD_CIRCUIT) { + LOGP(DMNCC, LOGL_ERROR, + "%s(): Transfer mode 0x%02x is not supported\n", + __func__, bcap->mode); + return -ENOTSUP; + } + if (bcap->coding != GSM48_BCAP_CODING_GSM_STD) { + LOGP(DMNCC, LOGL_ERROR, + "%s(): Coding standard 0x%02x is not supported\n", + __func__, bcap->coding); + return -ENOTSUP; + } + + switch (bcap->transfer) { + case GSM48_BCAP_ITCAP_SPEECH: + { + int speech_ver = mncc_handle_bcap_speech(bcap, set); + /* include bearer cap, if not given in setup (see above) + * or if multiple codecs are given + * or if not only full rate + * or if given codec is unimplemented + */ + if (speech_ver < 0) + mncc_set_bcap_speech(mncc_out, set, -1); + else if (bcap->speech_ver[1] >= 0 || speech_ver != 0) + mncc_set_bcap_speech(mncc_out, set, speech_ver); + break; + } + case GSM48_BCAP_ITCAP_UNR_DIG_INF: + case GSM48_BCAP_ITCAP_3k1_AUDIO: + case GSM48_BCAP_ITCAP_FAX_G3: + return mncc_handle_bcap_data(bcap, set); + default: + LOGP(DMNCC, LOGL_ERROR, + "%s(): Information transfer capability 0x%02x is not supported\n", + __func__, bcap->transfer); + return -ENOTSUP; + } + + return 0; } /* @@ -220,7 +565,7 @@ int mncc_recv_dummy(struct osmocom_ms *ms, int msg_type, void *arg) /* * MNCCms call application via socket */ -int mncc_recv_socket(struct osmocom_ms *ms, int msg_type, void *arg) +int mncc_recv_external(struct osmocom_ms *ms, int msg_type, void *arg) { struct mncc_sock_state *state = ms->mncc_entity.sock_state; struct gsm_mncc *mncc = arg; @@ -257,14 +602,13 @@ int mncc_recv_socket(struct osmocom_ms *ms, int msg_type, void *arg) * MNCCms basic call application */ -int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg) +int mncc_recv_internal(struct osmocom_ms *ms, int msg_type, void *arg) { - struct gsm_settings *set = &ms->settings; - struct gsm_mncc *data = arg; + const struct gsm_settings *set = &ms->settings; + const struct gsm_mncc *data = arg; struct gsm_call *call = get_call_ref(data->callref); struct gsm_mncc mncc; uint8_t cause; - int8_t speech_ver = -1, speech_ver_half = -1, temp; int first_call = 0; /* call does not exist */ @@ -290,6 +634,7 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg) return -ENOMEM; call->ms = ms; call->callref = data->callref; + call->type = GSM_CALL_T_UNKNOWN; llist_add_tail(&call->entry, &call_list); } @@ -298,62 +643,62 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg) switch (msg_type) { case MNCC_DISC_IND: - vty_notify(ms, NULL); + l23_vty_ms_notify(ms, NULL); switch (data->cause.value) { case GSM48_CC_CAUSE_UNASSIGNED_NR: - vty_notify(ms, "Call: Number not assigned\n"); + l23_vty_ms_notify(ms, "Call: Number not assigned\n"); break; case GSM48_CC_CAUSE_NO_ROUTE: - vty_notify(ms, "Call: Destination unreachable\n"); + l23_vty_ms_notify(ms, "Call: Destination unreachable\n"); break; case GSM48_CC_CAUSE_NORM_CALL_CLEAR: - vty_notify(ms, "Call: Remote hangs up\n"); + l23_vty_ms_notify(ms, "Call: Remote hangs up\n"); break; case GSM48_CC_CAUSE_USER_BUSY: - vty_notify(ms, "Call: Remote busy\n"); + l23_vty_ms_notify(ms, "Call: Remote busy\n"); break; case GSM48_CC_CAUSE_USER_NOTRESPOND: - vty_notify(ms, "Call: Remote not responding\n"); + l23_vty_ms_notify(ms, "Call: Remote not responding\n"); break; case GSM48_CC_CAUSE_USER_ALERTING_NA: - vty_notify(ms, "Call: Remote not answering\n"); + l23_vty_ms_notify(ms, "Call: Remote not answering\n"); break; case GSM48_CC_CAUSE_CALL_REJECTED: - vty_notify(ms, "Call has been rejected\n"); + l23_vty_ms_notify(ms, "Call has been rejected\n"); break; case GSM48_CC_CAUSE_NUMBER_CHANGED: - vty_notify(ms, "Call: Number changed\n"); + l23_vty_ms_notify(ms, "Call: Number changed\n"); break; case GSM48_CC_CAUSE_PRE_EMPTION: - vty_notify(ms, "Call: Cleared due to pre-emption\n"); + l23_vty_ms_notify(ms, "Call: Cleared due to pre-emption\n"); break; case GSM48_CC_CAUSE_DEST_OOO: - vty_notify(ms, "Call: Remote out of order\n"); + l23_vty_ms_notify(ms, "Call: Remote out of order\n"); break; case GSM48_CC_CAUSE_INV_NR_FORMAT: - vty_notify(ms, "Call: Number invalid or imcomplete\n"); + l23_vty_ms_notify(ms, "Call: Number invalid or incomplete\n"); break; case GSM48_CC_CAUSE_NO_CIRCUIT_CHAN: - vty_notify(ms, "Call: No channel available\n"); + l23_vty_ms_notify(ms, "Call: No channel available\n"); break; case GSM48_CC_CAUSE_NETWORK_OOO: - vty_notify(ms, "Call: Network out of order\n"); + l23_vty_ms_notify(ms, "Call: Network out of order\n"); break; case GSM48_CC_CAUSE_TEMP_FAILURE: - vty_notify(ms, "Call: Temporary failure\n"); + l23_vty_ms_notify(ms, "Call: Temporary failure\n"); break; case GSM48_CC_CAUSE_SWITCH_CONG: - vty_notify(ms, "Congestion\n"); + l23_vty_ms_notify(ms, "Congestion\n"); break; default: - vty_notify(ms, "Call has been disconnected " + l23_vty_ms_notify(ms, "Call has been disconnected " "(clear cause %d)\n", data->cause.value); } LOGP(DMNCC, LOGL_INFO, "Call has been disconnected " "(cause %d)\n", data->cause.value); if ((data->fields & MNCC_F_PROGRESS) && data->progress.descr == 8) { - vty_notify(ms, "Please hang up!\n"); + l23_vty_ms_notify(ms, "Please hang up!\n"); break; } free_call(call); @@ -361,18 +706,18 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg) goto release; case MNCC_REL_IND: case MNCC_REL_CNF: - vty_notify(ms, NULL); + l23_vty_ms_notify(ms, NULL); if (data->cause.value == GSM48_CC_CAUSE_CALL_REJECTED) - vty_notify(ms, "Call has been rejected\n"); + l23_vty_ms_notify(ms, "Call has been rejected\n"); else - vty_notify(ms, "Call has been released\n"); + l23_vty_ms_notify(ms, "Call has been released\n"); LOGP(DMNCC, LOGL_INFO, "Call has been released (cause %d)\n", data->cause.value); free_call(call); break; case MNCC_CALL_PROC_IND: - vty_notify(ms, NULL); - vty_notify(ms, "Call is proceeding\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Call is proceeding\n"); LOGP(DMNCC, LOGL_INFO, "Call is proceeding\n"); if ((data->fields & MNCC_F_BEARER_CAP) && data->bearer_cap.speech_ver[0] >= 0) { @@ -380,94 +725,59 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg) } break; case MNCC_ALERT_IND: - vty_notify(ms, NULL); - vty_notify(ms, "Call is alerting\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Call is alerting\n"); LOGP(DMNCC, LOGL_INFO, "Call is alerting\n"); break; case MNCC_SETUP_CNF: - vty_notify(ms, NULL); - vty_notify(ms, "Call is answered\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Call is answered\n"); LOGP(DMNCC, LOGL_INFO, "Call is answered\n"); break; case MNCC_SETUP_IND: - vty_notify(ms, NULL); + l23_vty_ms_notify(ms, NULL); if (!first_call && !ms->settings.cw) { - vty_notify(ms, "Incoming call rejected while busy\n"); + l23_vty_ms_notify(ms, "Incoming call rejected while busy\n"); LOGP(DMNCC, LOGL_INFO, "Incoming call but busy\n"); cause = GSM48_CC_CAUSE_USER_BUSY; goto release; } - /* select first supported speech_ver */ - if ((data->fields & MNCC_F_BEARER_CAP)) { - int i; - - for (i = 0; data->bearer_cap.speech_ver[i] >= 0; i++) { - - temp = mncc_get_bearer(set, - data->bearer_cap.speech_ver[i]); - if (temp < 0) - continue; - if (temp == 5 || temp == 1) { /* half */ - /* only the first half rate */ - if (speech_ver_half < 0) - speech_ver_half = temp; - } else { - /* only the first full rate */ - if (speech_ver < 0) - speech_ver = temp; - } - } - /* half and full given */ - if (speech_ver_half >= 0 && speech_ver >= 0) { - if (set->half_prefer) { - LOGP(DMNCC, LOGL_INFO, " both supported" - " codec rates are given, using " - "preferred half rate\n"); - speech_ver = speech_ver_half; - } else - LOGP(DMNCC, LOGL_INFO, " both supported" - " codec rates are given, using " - "preferred full rate\n"); - } else if (speech_ver_half < 0 && speech_ver < 0) { - LOGP(DMNCC, LOGL_INFO, " no supported codec " - "rate is given\n"); - /* only half rate is given, use it */ - } else if (speech_ver_half >= 0) { - LOGP(DMNCC, LOGL_INFO, " only supported half " - "rate codec is given, using it\n"); - speech_ver = speech_ver_half; - /* only full rate is given, use it */ - } else { - LOGP(DMNCC, LOGL_INFO, " only supported full " - "rate codec is given, using it\n"); - } - } /* presentation allowed if present == 0 */ if (data->calling.present || !data->calling.number[0]) - vty_notify(ms, "Incoming call (anonymous)\n"); + l23_vty_ms_notify(ms, "Incoming call (anonymous)\n"); else if (data->calling.type == 1) - vty_notify(ms, "Incoming call (from +%s)\n", + l23_vty_ms_notify(ms, "Incoming call (from +%s)\n", data->calling.number); else if (data->calling.type == 2) - vty_notify(ms, "Incoming call (from 0-%s)\n", + l23_vty_ms_notify(ms, "Incoming call (from 0-%s)\n", data->calling.number); else - vty_notify(ms, "Incoming call (from %s)\n", + l23_vty_ms_notify(ms, "Incoming call (from %s)\n", data->calling.number); LOGP(DMNCC, LOGL_INFO, "Incoming call (from %s callref %x)\n", data->calling.number, call->callref); memset(&mncc, 0, sizeof(struct gsm_mncc)); mncc.callref = call->callref; - /* only include bearer cap, if not given in setup - * or if multiple codecs are given - * or if not only full rate - * or if given codec is unimplemented - */ - if (!(data->fields & MNCC_F_BEARER_CAP) || speech_ver < 0) - mncc_set_bearer(ms, -1, &mncc); - else if (data->bearer_cap.speech_ver[1] >= 0 - || speech_ver != 0) - mncc_set_bearer(ms, speech_ver, &mncc); + /* Bearer capability (optional) */ + if (mncc_handle_bcap(&mncc, data, &ms->settings) != 0) { + cause = GSM48_CC_CAUSE_INCOMPAT_DEST; + goto release; + } + switch (mncc.bearer_cap.transfer) { + case GSM48_BCAP_ITCAP_SPEECH: + call->type = GSM_CALL_T_VOICE; + break; + case GSM48_BCAP_ITCAP_UNR_DIG_INF: + case GSM48_BCAP_ITCAP_3k1_AUDIO: + call->type = GSM_CALL_T_DATA; + break; + case GSM48_BCAP_ITCAP_FAX_G3: + call->type = GSM_CALL_T_DATA_FAX; + break; + default: + call->type = GSM_CALL_T_UNKNOWN; + break; + } /* CC capabilities (optional) */ if (ms->settings.cc_dtmf) { mncc.fields |= MNCC_F_CCCAP; @@ -490,30 +800,30 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg) } break; case MNCC_SETUP_COMPL_IND: - vty_notify(ms, NULL); - vty_notify(ms, "Call is connected\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Call is connected\n"); LOGP(DMNCC, LOGL_INFO, "Call is connected\n"); break; case MNCC_HOLD_CNF: - vty_notify(ms, NULL); - vty_notify(ms, "Call is on hold\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Call is on hold\n"); LOGP(DMNCC, LOGL_INFO, "Call is on hold\n"); call->hold = true; break; case MNCC_HOLD_REJ: - vty_notify(ms, NULL); - vty_notify(ms, "Call hold was rejected\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Call hold was rejected\n"); LOGP(DMNCC, LOGL_INFO, "Call hold was rejected\n"); break; case MNCC_RETRIEVE_CNF: - vty_notify(ms, NULL); - vty_notify(ms, "Call is retrieved\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Call is retrieved\n"); LOGP(DMNCC, LOGL_INFO, "Call is retrieved\n"); call->hold = false; break; case MNCC_RETRIEVE_REJ: - vty_notify(ms, NULL); - vty_notify(ms, "Call retrieve was rejected\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Call retrieve was rejected\n"); LOGP(DMNCC, LOGL_INFO, "Call retrieve was rejected\n"); break; case MNCC_FACILITY_IND: @@ -534,15 +844,16 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg) return 0; } -int mncc_call(struct osmocom_ms *ms, char *number) +int mncc_call(struct osmocom_ms *ms, const char *number, + enum gsm_call_type call_type) { struct gsm_call *call; struct gsm_mncc setup; llist_for_each_entry(call, &call_list, entry) { if (!call->hold) { - vty_notify(ms, NULL); - vty_notify(ms, "Please put active call on hold " + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Please put active call on hold " "first!\n"); LOGP(DMNCC, LOGL_INFO, "Cannot make a call, busy!\n"); return -EBUSY; @@ -554,6 +865,7 @@ int mncc_call(struct osmocom_ms *ms, char *number) return -ENOMEM; call->ms = ms; call->callref = new_callref++; + call->type = call_type; call->init = true; llist_add_tail(&call->entry, &call_list); @@ -565,7 +877,8 @@ int mncc_call(struct osmocom_ms *ms, char *number) /* emergency */ setup.emergency = 1; } else { - LOGP(DMNCC, LOGL_INFO, "Make call to %s\n", number); + LOGP(DMNCC, LOGL_INFO, "Make %s call to %s\n", + gsm_call_type_str[call_type], number); /* called number */ setup.fields |= MNCC_F_CALLED; if (number[0] == '+') { @@ -578,7 +891,10 @@ int mncc_call(struct osmocom_ms *ms, char *number) OSMO_STRLCPY_ARRAY(setup.called.number, number); /* bearer capability (mandatory) */ - mncc_set_bearer(ms, -1, &setup); + if (call_type == GSM_CALL_T_VOICE) + mncc_set_bcap_speech(&setup, &ms->settings, -1); + else + mncc_set_bcap_data(&setup, &ms->settings, call_type); /* CLIR */ if (ms->settings.clir) @@ -609,8 +925,8 @@ int mncc_hangup(struct osmocom_ms *ms) } if (!found) { LOGP(DMNCC, LOGL_INFO, "No active call to hangup\n"); - vty_notify(ms, NULL); - vty_notify(ms, "No active call\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No active call\n"); return -EINVAL; } @@ -636,14 +952,14 @@ int mncc_answer(struct osmocom_ms *ms) } if (!alerting) { LOGP(DMNCC, LOGL_INFO, "No call alerting\n"); - vty_notify(ms, NULL); - vty_notify(ms, "No alerting call\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No alerting call\n"); return -EBUSY; } if (active) { LOGP(DMNCC, LOGL_INFO, "Answer but we have an active call\n"); - vty_notify(ms, NULL); - vty_notify(ms, "Please put active call on hold first!\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Please put active call on hold first!\n"); return -EBUSY; } alerting->ring = false; @@ -667,8 +983,8 @@ int mncc_hold(struct osmocom_ms *ms) } if (!found) { LOGP(DMNCC, LOGL_INFO, "No active call to hold\n"); - vty_notify(ms, NULL); - vty_notify(ms, "No active call\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No active call\n"); return -EINVAL; } @@ -691,26 +1007,26 @@ int mncc_retrieve(struct osmocom_ms *ms, int number) } if (active) { LOGP(DMNCC, LOGL_INFO, "Cannot retrieve during active call\n"); - vty_notify(ms, NULL); - vty_notify(ms, "Hold active call first!\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Hold active call first!\n"); return -EINVAL; } if (holdnum == 0) { - vty_notify(ms, NULL); - vty_notify(ms, "No call on hold!\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No call on hold!\n"); return -EINVAL; } if (holdnum > 1 && number <= 0) { - vty_notify(ms, NULL); - vty_notify(ms, "Select call 1..%d\n", holdnum); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Select call 1..%d\n", holdnum); return -EINVAL; } if (holdnum == 1 && number <= 0) number = 1; if (number > holdnum) { - vty_notify(ms, NULL); - vty_notify(ms, "Given number %d out of range!\n", number); - vty_notify(ms, "Select call 1..%d\n", holdnum); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Given number %d out of range!\n", number); + l23_vty_ms_notify(ms, "Select call 1..%d\n", holdnum); return -EINVAL; } @@ -729,7 +1045,8 @@ int mncc_retrieve(struct osmocom_ms *ms, int number) * DTMF */ -static int dtmf_statemachine(struct gsm_call *call, struct gsm_mncc *mncc) +static int dtmf_statemachine(struct gsm_call *call, + const struct gsm_mncc *mncc) { struct osmocom_ms *ms = call->ms; struct gsm_mncc dtmf; @@ -795,8 +1112,8 @@ int mncc_dtmf(struct osmocom_ms *ms, char *dtmf) } if (!found) { LOGP(DMNCC, LOGL_INFO, "No active call to send DTMF\n"); - vty_notify(ms, NULL); - vty_notify(ms, "No active call\n"); + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No active call\n"); return -EINVAL; } diff --git a/src/host/layer23/src/mobile/primitives.c b/src/host/layer23/src/mobile/primitives.c index f562466a..4a40b4f7 100644 --- a/src/host/layer23/src/mobile/primitives.c +++ b/src/host/layer23/src/mobile/primitives.c @@ -12,10 +12,6 @@ * 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 <inttypes.h> diff --git a/src/host/layer23/src/mobile/script_lua.c b/src/host/layer23/src/mobile/script_lua.c index 2979e7cd..eb3f0085 100644 --- a/src/host/layer23/src/mobile/script_lua.c +++ b/src/host/layer23/src/mobile/script_lua.c @@ -12,10 +12,6 @@ * 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 <lua.h> @@ -23,6 +19,7 @@ #include <lauxlib.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/mobile/app_mobile.h> #include <osmocom/bb/common/logging.h> @@ -497,12 +494,13 @@ static int lua_fd_cb(struct osmo_fd *fd, unsigned int what) { static int lua_register_fd(lua_State *L) { struct fd_userdata *fdu; + int fd; /* fd, cb */ luaL_argcheck(L, lua_isnumber(L, -2), 1, "needs to be a filedescriptor"); luaL_argcheck(L, lua_isfunction(L, -1), 2, "Callback needs to be a function"); - /* Cretae a table so a user can unregister (and unregister on GC) */ + /* Create a table so a user can unregister (and unregister on GC) */ fdu = lua_newuserdata(L, sizeof(*fdu)); fdu->state = L; fdu->fd.fd = -1; @@ -510,10 +508,8 @@ static int lua_register_fd(lua_State *L) lua_setmetatable(L, -2); /* Set the filedescriptor */ - fdu->fd.fd = (int) lua_tonumber(L, -3); - fdu->fd.cb = lua_fd_cb; - fdu->fd.when = BSC_FD_READ; - fdu->fd.data = fdu; + fd = (int) lua_tonumber(L, -3); + osmo_fd_setup(&fdu->fd, fd, OSMO_FD_READ, lua_fd_cb, fdu, 0); /* Assuming that an error here will lead to a GC */ if (osmo_fd_register(&fdu->fd) != 0) { diff --git a/src/host/layer23/src/mobile/script_nolua.c b/src/host/layer23/src/mobile/script_nolua.c index 61466f77..5973a44c 100644 --- a/src/host/layer23/src/mobile/script_nolua.c +++ b/src/host/layer23/src/mobile/script_nolua.c @@ -12,10 +12,6 @@ * 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 <osmocom/bb/common/osmocom_data.h> diff --git a/src/host/layer23/src/mobile/tch.c b/src/host/layer23/src/mobile/tch.c new file mode 100644 index 00000000..b78682f5 --- /dev/null +++ b/src/host/layer23/src/mobile/tch.c @@ -0,0 +1,234 @@ +/* + * (C) 2010 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2017-2018 by Vadim Yanitskiy <axilirator@gmail.com> + * (C) 2022-2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * 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. + * + */ + +#include <string.h> +#include <errno.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/signal.h> +#include <osmocom/codec/codec.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/mncc_sock.h> +#include <osmocom/bb/mobile/transaction.h> +#include <osmocom/bb/mobile/tch.h> + +int tch_voice_state_init(struct gsm_trans *trans, + struct tch_voice_state *state); +int tch_data_state_init(struct gsm_trans *trans, + struct tch_data_state *state); + +void tch_voice_state_free(struct tch_voice_state *state); +void tch_data_state_free(struct tch_data_state *state); + +int tch_voice_recv(struct osmocom_ms *ms, struct msgb *msg); +int tch_data_recv(struct osmocom_ms *ms, struct msgb *msg); + +/* Receive a Downlink traffic (voice/data) frame from the lower layers */ +static int tch_recv_cb(struct osmocom_ms *ms, struct msgb *msg) +{ + struct tch_state *state = ms->tch_state; + + if (state == NULL) { + msgb_free(msg); + return 0; + } + + if (state->is_voice) { + return tch_voice_recv(ms, msg); + } else { + /* Rx only mode makes no sense for data calls, so we discard + * received DL frames and thus do not trigger sending UL frames. */ + if (!state->rx_only) + return tch_data_recv(ms, msg); + msgb_free(msg); + return 0; + } +} + +/* Send an Uplink voice frame to the lower layers */ +int tch_send_msg(struct osmocom_ms *ms, struct msgb *msg) +{ + struct tch_state *state = ms->tch_state; + + if (state == NULL || state->rx_only) { + /* TODO: fix callers and print a warning here */ + msgb_free(msg); + return -EIO; + } + + return gsm48_rr_tx_traffic(ms, msg); +} + +/* tch_send_msg() wrapper accepting an MNCC structure */ +int tch_send_mncc_frame(struct osmocom_ms *ms, const struct gsm_data_frame *frame) +{ + struct msgb *nmsg; + int len; + + switch (frame->msg_type) { + case GSM_TCHF_FRAME: + len = GSM_FR_BYTES; + break; + case GSM_TCHF_FRAME_EFR: + len = GSM_EFR_BYTES; + break; + case GSM_TCHH_FRAME: + len = GSM_HR_BYTES; + break; + /* TODO: case GSM_TCH_FRAME_AMR (variable length) */ + /* TODO: case GSM_BAD_FRAME (empty?) */ + default: + LOGP(DL1C, LOGL_ERROR, "%s(): msg_type=0x%02x: not implemented\n", + __func__, frame->msg_type); + return -EINVAL; + } + + nmsg = msgb_alloc_headroom(len + 64, 64, "TCH/F"); + if (!nmsg) + return -ENOMEM; + nmsg->l2h = msgb_put(nmsg, len); + memcpy(nmsg->l2h, frame->data, len); + + return tch_send_msg(ms, nmsg); +} + +static void tch_trans_cstate_active_cb(struct gsm_trans *trans, bool rx_only) +{ + struct osmocom_ms *ms = trans->ms; + const struct gsm48_rrlayer *rr = &ms->rrlayer; + const struct gsm48_rr_cd *cd = &rr->cd_now; + struct tch_state *state; + + if (ms->tch_state != NULL) { + ms->tch_state->rx_only = rx_only; + return; /* TODO: handle modify? */ + } + + state = talloc_zero(ms, struct tch_state); + OSMO_ASSERT(state != NULL); + ms->tch_state = state; + ms->tch_state->rx_only = rx_only; + + switch (cd->mode) { + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + state->is_voice = true; + state->voice.handler = ms->settings.tch_voice.io_handler; + if (tch_voice_state_init(trans, &state->voice) != 0) + goto exit_free; + break; + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + state->is_voice = false; + state->data.handler = ms->settings.tch_data.io_handler; + if (tch_data_state_init(trans, &state->data) != 0) + goto exit_free; + break; + case GSM48_CMODE_SIGN: + default: + LOGP(DL1C, LOGL_ERROR, "Unhandled channel mode %s\n", + get_value_string(gsm48_chan_mode_names, cd->mode)); +exit_free: + talloc_free(state); + ms->tch_state = NULL; + return; + } + + /* rr->audio_mode has been set by tch_{voice,data}_state_init(), apply it */ + gsm48_rr_audio_mode(ms, rr->audio_mode); +} + +static void tch_trans_free_cb(struct gsm_trans *trans) +{ + struct osmocom_ms *ms = trans->ms; + struct tch_state *state = ms->tch_state; + + if (state == NULL) + return; + if (state->is_voice) + tch_voice_state_free(&state->voice); + else + tch_data_state_free(&state->data); + ms->rrlayer.audio_mode = 0x00; + + talloc_free(state); + ms->tch_state = NULL; +} + +static void tch_trans_state_chg_cb(struct gsm_trans *trans) +{ + switch (trans->cc.state) { + case GSM_CSTATE_CALL_DELIVERED: /* MO call: Rx CC ALERTING */ + tch_trans_cstate_active_cb(trans, true); + break; + case GSM_CSTATE_ACTIVE: /* MO/MT call: Rx CC CONNECT */ + tch_trans_cstate_active_cb(trans, false); + break; + case GSM_CSTATE_NULL: + tch_trans_free_cb(trans); + break; + } +} + +/* a call-back for CC (Call Control) transaction related events */ +static int tch_trans_signal_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_trans *trans = signal_data; + + OSMO_ASSERT(subsys == SS_L23_TRANS); + OSMO_ASSERT(trans != NULL); + + /* we only care about CC transactions here */ + if (trans->protocol != GSM48_PDISC_CC) + return 0; + + switch ((enum osmobb_l23_trans_sig)signal) { + case S_L23_CC_TRANS_ALLOC: + break; + case S_L23_CC_TRANS_FREE: + tch_trans_free_cb(trans); + break; + case S_L23_CC_TRANS_STATE_CHG: + tch_trans_state_chg_cb(trans); + break; + } + + return 0; +} + +/* Initialize the TCH router */ +int tch_init(struct osmocom_ms *ms) +{ + ms->l1_entity.l1_traffic_ind = &tch_recv_cb; + + osmo_signal_register_handler(SS_L23_TRANS, &tch_trans_signal_cb, ms); + + return 0; +} diff --git a/src/host/layer23/src/mobile/tch_data.c b/src/host/layer23/src/mobile/tch_data.c new file mode 100644 index 00000000..b6b3be6a --- /dev/null +++ b/src/host/layer23/src/mobile/tch_data.c @@ -0,0 +1,587 @@ +/* + * (C) 2023-2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de> + * + * 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. + * + */ + +#include <stdint.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/soft_uart.h> + +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/gsm/gsm44021.h> + +#include <osmocom/isdn/v110.h> +#include <osmocom/isdn/v110_ta.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/transaction.h> +#include <osmocom/bb/mobile/tch.h> + +#include <l1ctl_proto.h> + +struct csd_v110_frame_desc { + uint16_t num_blocks; + uint16_t num_bits; +}; + +struct csd_v110_lchan_desc { + struct csd_v110_frame_desc fr; + struct csd_v110_frame_desc hr; +}; + +/* key is enum gsm48_chan_mode, so assuming a value in range 0..255 */ +const struct csd_v110_lchan_desc csd_v110_lchan_desc[256] = { +#if 0 + [GSM48_CMODE_DATA_14k5] = { + /* TCH/F14.4: 290 bits every 20 ms (14.5 kbit/s) */ + .fr = { .num_blocks = 1, .num_bits = 290 }, + }, +#endif + [GSM48_CMODE_DATA_12k0] = { + /* TCH/F9.6: 4 * 60 bits every 20 ms (12.0 kbit/s) */ + .fr = { .num_blocks = 4, .num_bits = 60 }, + }, + [GSM48_CMODE_DATA_6k0] = { + /* TCH/F4.8: 2 * 60 bits every 20 ms (6.0 kbit/s) */ + .fr = { .num_blocks = 2, .num_bits = 60 }, + /* TCH/H4.8: 4 * 60 bits every 40 ms (6.0 kbit/s) */ + .hr = { .num_blocks = 4, .num_bits = 60 }, + }, + [GSM48_CMODE_DATA_3k6] = { + /* TCH/F2.4: 2 * 36 bits every 20 ms (3.6 kbit/s) */ + .fr = { .num_blocks = 2, .num_bits = 36 }, + /* TCH/H2.4: 4 * 36 bits every 40 ms (3.6 kbit/s) */ + .hr = { .num_blocks = 4, .num_bits = 36 }, + }, +}; + +struct tch_csd_sock_state *tch_csd_sock_init(struct osmocom_ms *ms); +void tch_csd_sock_recv(struct tch_csd_sock_state *state, struct msgb *msg); +void tch_csd_sock_send(struct tch_csd_sock_state *state, struct msgb *msg); +void tch_csd_sock_exit(struct tch_csd_sock_state *state); + +static void tch_soft_uart_rx_cb(void *priv, struct msgb *msg, unsigned int flags) +{ + struct tch_data_state *state = (struct tch_data_state *)priv; + + LOGP(DCSD, LOGL_DEBUG, "%s(): [flags=0x%08x] %s\n", + __func__, flags, msgb_hexdump(msg)); + + if (state->sock != NULL && msgb_length(msg) > 0) + tch_csd_sock_send(state->sock, msg); + else + msgb_free(msg); +} + +static void tch_soft_uart_tx_cb(void *priv, struct msgb *msg) +{ + struct tch_data_state *state = (struct tch_data_state *)priv; + + tch_csd_sock_recv(state->sock, msg); + + LOGP(DCSD, LOGL_DEBUG, "%s(): [n_bytes=%u/%u] %s\n", + __func__, msg->len, msg->data_len, msgb_hexdump(msg)); +} + +struct osmo_soft_uart *tch_soft_uart_alloc(struct osmocom_ms *ms, + const struct gsm_mncc_bearer_cap *bcap) +{ + struct osmo_soft_uart *suart; + + struct osmo_soft_uart_cfg cfg = { + .num_data_bits = bcap->data.nr_data_bits, + .num_stop_bits = bcap->data.nr_stop_bits, + /* .parity_mode is set below */ + .rx_buf_size = 1024, /* TODO: align with the current TCH mode */ + .rx_timeout_ms = 100, /* TODO: align with TCH framing interval */ + .priv = (void *)&ms->tch_state->data, + .rx_cb = &tch_soft_uart_rx_cb, + .tx_cb = &tch_soft_uart_tx_cb, + }; + + switch (bcap->data.parity) { + case GSM48_BCAP_PAR_ODD: + cfg.parity_mode = OSMO_SUART_PARITY_ODD; + break; + case GSM48_BCAP_PAR_EVEN: + cfg.parity_mode = OSMO_SUART_PARITY_EVEN; + break; + case GSM48_BCAP_PAR_ZERO: + cfg.parity_mode = OSMO_SUART_PARITY_SPACE; + break; + case GSM48_BCAP_PAR_ONE: + cfg.parity_mode = OSMO_SUART_PARITY_MARK; + break; + case GSM48_BCAP_PAR_NONE: + default: + cfg.parity_mode = OSMO_SUART_PARITY_NONE; + break; + } + + suart = osmo_soft_uart_alloc(ms, "csd_soft_uart", &cfg); + if (suart == NULL) + return NULL; + + osmo_soft_uart_set_rx(suart, true); + osmo_soft_uart_set_tx(suart, true); + + return suart; +} + +/*************************************************************************************/ + +static void tch_v110_ta_rx_cb(void *priv, const ubit_t *buf, size_t buf_size) +{ + const struct tch_data_state *state = (struct tch_data_state *)priv; + + if (state->sock != NULL && buf_size > 0) { + struct msgb *msg = msgb_alloc(buf_size, __func__); + tch_csd_sock_send(state->sock, msg); + } +} + +static void tch_v110_ta_tx_cb(void *priv, ubit_t *buf, size_t buf_size) +{ + const struct tch_data_state *state = (struct tch_data_state *)priv; + + if (state->sock != NULL && buf_size > 0) { + struct msgb *msg = msgb_alloc(buf_size, __func__); + + tch_csd_sock_recv(state->sock, msg); + if (msgb_length(msg) < buf_size) { + LOGP(DCSD, LOGL_NOTICE, + "%s(): not enough bytes for sync Tx (%u < %zu)\n", + __func__, msgb_length(msg), buf_size); + } + } +} + +static void tch_v110_ta_async_rx_cb(void *priv, const ubit_t *buf, size_t buf_size) +{ + const struct tch_data_state *state = (struct tch_data_state *)priv; + + osmo_soft_uart_rx_ubits(state->suart, buf, buf_size); +} + +static void tch_v110_ta_async_tx_cb(void *priv, ubit_t *buf, size_t buf_size) +{ + const struct tch_data_state *state = (struct tch_data_state *)priv; + + osmo_soft_uart_tx_ubits(state->suart, buf, buf_size); +} + +static const struct { + enum osmo_v110_ta_circuit c; + enum osmo_soft_uart_status s; +} tch_v110_circuit_map[] = { + { OSMO_V110_TA_C_106, OSMO_SUART_STATUS_F_CTS }, + { OSMO_V110_TA_C_107, OSMO_SUART_STATUS_F_DSR }, + { OSMO_V110_TA_C_109, OSMO_SUART_STATUS_F_DCD }, +}; + +static void tch_v110_ta_status_update_cb(void *priv, unsigned int status) +{ + const struct tch_data_state *state = (struct tch_data_state *)priv; + + LOGP(DCSD, LOGL_DEBUG, "V.110 TA status mask=0x%08x\n", status); + + for (unsigned int i = 0; i < ARRAY_SIZE(tch_v110_circuit_map); i++) { + enum osmo_v110_ta_circuit c = tch_v110_circuit_map[i].c; + enum osmo_soft_uart_status s = tch_v110_circuit_map[i].s; + bool is_on = (status & (1 << c)) != 0; + + LOGP(DCSD, LOGL_DEBUG, "V.110 TA circuit %s (%s) is %s\n", + osmo_v110_ta_circuit_name(c), + osmo_v110_ta_circuit_desc(c), + is_on ? "ON" : "OFF"); + + /* update status lines of the soft-UART */ + if (state->suart != NULL) + osmo_soft_uart_set_status_line(state->suart, s, is_on); + } +} + +struct osmo_v110_ta *tch_v110_ta_alloc(struct osmocom_ms *ms, + const struct gsm_mncc_bearer_cap *bcap) +{ + struct tch_data_state *state = &ms->tch_state->data; + + struct osmo_v110_ta_cfg cfg = { + /* .rate is set below */ + .priv = (void *)state, + .rx_cb = &tch_v110_ta_rx_cb, + .tx_cb = &tch_v110_ta_tx_cb, + .status_update_cb = &tch_v110_ta_status_update_cb, + }; + + if (bcap->data.async) { + OSMO_ASSERT(state->suart != NULL); + cfg.rx_cb = &tch_v110_ta_async_rx_cb; + cfg.tx_cb = &tch_v110_ta_async_tx_cb; + } + +#define BCAP_RATE(interm_rate, user_rate) \ + ((interm_rate << 8) | (user_rate << 0)) + + switch (BCAP_RATE(bcap->data.interm_rate, bcap->data.user_rate)) { + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_1200): + cfg.rate = OSMO_V110_SYNC_RA1_1200; + break; + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_2400): + cfg.rate = OSMO_V110_SYNC_RA1_2400; + break; + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_4800): + cfg.rate = OSMO_V110_SYNC_RA1_4800; + break; + case BCAP_RATE(GSM48_BCAP_IR_16k, GSM48_BCAP_UR_9600): + cfg.rate = OSMO_V110_SYNC_RA1_9600; + break; + /* TODO: according to 3GPP TS 44.021, section 4.1, the 300 bit/s user data + * signalling rate shall be adapted to a synchronous 600 bit/s stream. */ + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_300): + default: + LOGP(DCSD, LOGL_ERROR, + "%s(): IR 0x%02x / UR 0x%02x combination is not supported\n", + __func__, bcap->data.interm_rate, bcap->data.user_rate); + return NULL; + } + +#undef BCAP_RATE + + osmo_v110_e1e2e3_set(state->e1e2e3, cfg.rate); + + return osmo_v110_ta_alloc(ms, "csd_v110_ta", &cfg); +} + +/*************************************************************************************/ + +static void swap_words(uint8_t *data, size_t data_len) +{ + /* swap bytes in words */ + while (data_len >= 2) { + uint8_t tmp = data[0]; + data[0] = data[1]; + data[1] = tmp; + data_len -= 2; + data += 2; + } +} + +static int tch_csd_rx_from_l1(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct tch_data_state *state = &ms->tch_state->data; + const struct gsm48_rr_cd *cd = &ms->rrlayer.cd_now; + const struct csd_v110_frame_desc *desc; + ubit_t data[4 * 60]; + size_t data_len; + + if ((cd->chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_Bm_ACCHs) + desc = &csd_v110_lchan_desc[cd->mode].fr; + else /* RSL_CHAN_Lm_ACCHs */ + desc = &csd_v110_lchan_desc[cd->mode].hr; + if (OSMO_UNLIKELY(desc->num_blocks == 0)) + return -ENOTSUP; + + data_len = desc->num_blocks * desc->num_bits; + OSMO_ASSERT(sizeof(data) >= data_len); + + switch (ms->settings.tch_data.io_format) { + case TCH_DATA_IOF_OSMO: + /* trxcon emits raw bits from the convolutional decoder */ + if (OSMO_UNLIKELY(msgb_l3len(msg) != data_len)) + return -EINVAL; + memcpy(&data[0], msgb_l3(msg), msgb_l3len(msg)); + break; + case TCH_DATA_IOF_TI: + /* the layer1 firmware emits packed bits (LE ordering) */ + if (OSMO_UNLIKELY(msgb_l3len(msg) < data_len / 8)) + return -EINVAL; + /* ... with swapped words (LE ordering) */ + swap_words(msgb_l3(msg), msgb_l3len(msg)); + osmo_pbit2ubit_ext(data, 0, msgb_l3(msg), 0, data_len, 1); + break; + default: + LOGP(DCSD, LOGL_FATAL, + "%s(): unhandled data I/O format\n", __func__); + OSMO_ASSERT(0); + } + + for (unsigned int i = 0; i < desc->num_blocks; i++) { + struct osmo_v110_decoded_frame df; + + if (desc->num_bits == 60) + osmo_csd_12k_6k_decode_frame(&df, &data[i * 60], 60); + else /* desc->num_bits == 36 */ + osmo_csd_3k6_decode_frame(&df, &data[i * 36], 36); + + /* E1/E2/E3 is out-of-band knowledge in GSM/CSD */ + memcpy(df.e_bits, state->e1e2e3, sizeof(state->e1e2e3)); + + osmo_v110_ta_frame_in(state->v110_ta, &df); + } + + if (state->suart != NULL) + osmo_soft_uart_flush_rx(state->suart); + + return 0; +} + +static int tch_csd_tx_to_l1(struct osmocom_ms *ms) +{ + struct tch_data_state *state = &ms->tch_state->data; + const struct gsm48_rr_cd *cd = &ms->rrlayer.cd_now; + const struct csd_v110_frame_desc *desc; + ubit_t data[60 * 4]; + struct msgb *nmsg; + size_t data_len; + + if ((cd->chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_Bm_ACCHs) + desc = &csd_v110_lchan_desc[cd->mode].fr; + else /* RSL_CHAN_Lm_ACCHs */ + desc = &csd_v110_lchan_desc[cd->mode].hr; + if (OSMO_UNLIKELY(desc->num_blocks == 0)) + return -ENOTSUP; + + data_len = desc->num_blocks * desc->num_bits; + OSMO_ASSERT(sizeof(data) >= data_len); + + for (unsigned int i = 0; i < desc->num_blocks; i++) { + struct osmo_v110_decoded_frame df; + + if (osmo_v110_ta_frame_out(state->v110_ta, &df) != 0) + memset(&df, 0x01, sizeof(df)); + + /* If E1/E2/E3 bits indicate a meaningful user data rate (see Table 5/V.110), + * set E7 to binary 0 in every 4-th frame (as per 3GPP TS 44.021, subclause 10.2.1). + * ITU-T V.110 requires this only for 600 bps, but 3GPP TS 44.021 clearly states + * that "such a multiframe structure exists for all user data rates". */ + if ((df.e_bits[0] + df.e_bits[1] + df.e_bits[2]) == 2) + df.e_bits[6] = (state->num_tx != 0); + state->num_tx = (state->num_tx + 1) & 0x03; + + if (desc->num_bits == 60) + osmo_csd_12k_6k_encode_frame(&data[i * 60], 60, &df); + else /* desc->num_bits == 36 */ + osmo_csd_3k6_encode_frame(&data[i * 36], 36, &df); + } + + switch (ms->settings.tch_data.io_format) { + case TCH_DATA_IOF_OSMO: + /* trxcon operates on unpacked bits */ + nmsg = msgb_alloc_headroom(data_len + 64, 64, __func__); + if (nmsg == NULL) + return -ENOMEM; + memcpy(msgb_put(nmsg, data_len), &data[0], data_len); + break; + case TCH_DATA_IOF_TI: + /* XXX: the layer1 firmware expects TRAFFIC.req with len=33 bytes */ + nmsg = msgb_alloc_headroom(33 + 64, 64, __func__); + if (nmsg == NULL) + return -ENOMEM; + nmsg->l2h = msgb_put(nmsg, 33); + /* the layer1 firmware expects packed bits (LE ordering) */ + osmo_ubit2pbit_ext(msgb_l2(nmsg), 0, &data[0], 0, sizeof(data), 1); + /* ... with swapped words (LE ordering) */ + swap_words(msgb_l2(nmsg), msgb_l2len(nmsg)); + break; + default: + LOGP(DCSD, LOGL_FATAL, + "%s(): unhandled data I/O format\n", __func__); + OSMO_ASSERT(0); + } + + return tch_send_msg(ms, nmsg); +} + +static int tch_data_check_bcap(const struct gsm_mncc_bearer_cap *bcap) +{ + if (bcap == NULL) { + LOGP(DCSD, LOGL_ERROR, + "%s(): CC transaction without BCap\n", + __func__); + return -ENODEV; + } + + if (bcap->mode != GSM48_BCAP_TMOD_CIRCUIT) { + LOGP(DCSD, LOGL_ERROR, + "%s(): Transfer mode 0x%02x is not supported\n", + __func__, bcap->mode); + return -ENOTSUP; + } + if (bcap->coding != GSM48_BCAP_CODING_GSM_STD) { + LOGP(DCSD, LOGL_ERROR, + "%s(): Coding standard 0x%02x is not supported\n", + __func__, bcap->coding); + return -ENOTSUP; + } + + switch (bcap->transfer) { + case GSM48_BCAP_ITCAP_UNR_DIG_INF: + if (bcap->data.rate_adaption != GSM48_BCAP_RA_V110_X30) { + LOGP(DCSD, LOGL_ERROR, + "%s(): Rate adaption (octet 5) 0x%02x is not supported\n", + __func__, bcap->data.rate_adaption); + return -ENOTSUP; + } + break; + case GSM48_BCAP_ITCAP_3k1_AUDIO: + case GSM48_BCAP_ITCAP_FAX_G3: + if (bcap->data.rate_adaption != GSM48_BCAP_RA_NONE) { + LOGP(DCSD, LOGL_ERROR, + "%s(): Rate adaption (octet 5) 0x%02x was expected to be NONE\n", + __func__, bcap->data.rate_adaption); + return -ENOTSUP; + } + break; + default: + LOGP(DCSD, LOGL_ERROR, + "%s(): Information transfer capability 0x%02x is not supported\n", + __func__, bcap->transfer); + return -ENOTSUP; + } + + if (bcap->data.sig_access != GSM48_BCAP_SA_I440_I450) { + LOGP(DCSD, LOGL_ERROR, + "%s(): Signalling access protocol (octet 5) 0x%02x is not supported\n", + __func__, bcap->data.sig_access); + return -ENOTSUP; + } + if (bcap->data.transp != GSM48_BCAP_TR_TRANSP) { + LOGP(DCSD, LOGL_ERROR, + "%s(): only transparent calls are supported so far\n", + __func__); + return -ENOTSUP; + } + + return 0; +} + +/*************************************************************************************/ + +int tch_data_recv(struct osmocom_ms *ms, struct msgb *msg) +{ + struct tch_data_state *state = &ms->tch_state->data; + + switch (state->handler) { + case TCH_DATA_IOH_LOOPBACK: + /* Remove the DL info header */ + msgb_pull_to_l2(msg); + /* Send data frame back */ + return tch_send_msg(ms, msg); + case TCH_DATA_IOH_UNIX_SOCK: + tch_csd_rx_from_l1(ms, msg); + tch_csd_tx_to_l1(ms); + msgb_free(msg); + break; + case TCH_DATA_IOH_NONE: + /* Drop voice frame */ + msgb_free(msg); + break; + } + + return 0; +} + +int tch_data_state_init(struct gsm_trans *trans, + struct tch_data_state *state) +{ + struct osmocom_ms *ms = trans->ms; + struct gsm48_rrlayer *rr = &ms->rrlayer; + const struct gsm_mncc_bearer_cap *bcap = trans->cc.bcap; + int rc; + + if ((rc = tch_data_check_bcap(bcap)) != 0) + return rc; + + switch (state->handler) { + case TCH_DATA_IOH_UNIX_SOCK: + state->sock = tch_csd_sock_init(ms); + if (state->sock == NULL) + return -ENOMEM; + rr->audio_mode = AUDIO_RX_TRAFFIC_IND | AUDIO_TX_TRAFFIC_REQ; + break; + case TCH_DATA_IOH_LOOPBACK: + case TCH_DATA_IOH_NONE: + rr->audio_mode = AUDIO_RX_TRAFFIC_IND | AUDIO_TX_TRAFFIC_REQ; + /* we don't need V.110 TA / soft-UART */ + return 0; + } + + if (bcap->data.async) { + state->suart = tch_soft_uart_alloc(ms, bcap); + if (state->suart == NULL) + goto exit_free; + } + + state->v110_ta = tch_v110_ta_alloc(ms, bcap); + if (state->v110_ta == NULL) + goto exit_free; + + return 0; + +exit_free: + if (state->sock != NULL) + tch_csd_sock_exit(state->sock); + if (state->suart != NULL) + osmo_soft_uart_free(state->suart); + if (state->v110_ta != NULL) + osmo_v110_ta_free(state->v110_ta); + return -1; +} + +void tch_data_state_free(struct tch_data_state *state) +{ + switch (state->handler) { + case TCH_DATA_IOH_UNIX_SOCK: + if (state->sock != NULL) + tch_csd_sock_exit(state->sock); + break; + default: + break; + } + + if (state->suart != NULL) + osmo_soft_uart_free(state->suart); + if (state->v110_ta != NULL) + osmo_v110_ta_free(state->v110_ta); +} + +void tch_csd_sock_state_cb(struct osmocom_ms *ms, bool connected) +{ + struct tch_data_state *state = NULL; + + if (ms->tch_state == NULL || ms->tch_state->is_voice) { + LOGP(DCSD, LOGL_INFO, "No data call is ongoing, " + "ignoring [dis]connection event for CSD socket\n"); + return; + } + + state = &ms->tch_state->data; + osmo_v110_ta_set_circuit(state->v110_ta, OSMO_V110_TA_C_108, connected); + + /* GSM/CSD employs the modified 60-bit V.110 frame format, which is basically + * a stripped down version of the nurmal 80-bit V.110 frame without E1/E2/E3 + * bits and without the sync pattern. These 60-bit V.110 frames are perfectly + * aligned with the radio interface block boundaries, so we're always in sync. */ + if (connected) + osmo_v110_ta_sync_ind(state->v110_ta); +} diff --git a/src/host/layer23/src/mobile/tch_data_sock.c b/src/host/layer23/src/mobile/tch_data_sock.c new file mode 100644 index 00000000..7ce5a4f1 --- /dev/null +++ b/src/host/layer23/src/mobile/tch_data_sock.c @@ -0,0 +1,243 @@ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de> + * + * 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. + * + */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <stdint.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/mobile/tch.h> + +struct tch_csd_sock_state { + struct osmocom_ms *ms; /* the MS instance we belong to */ + struct osmo_fd listen_bfd; /* fd for listen socket */ + struct osmo_fd conn_bfd; /* fd for a client connection */ + struct llist_head rxqueue; + struct llist_head txqueue; +}; + +void tch_csd_sock_state_cb(struct osmocom_ms *ms, bool connected); + +static void tch_csd_sock_close(struct tch_csd_sock_state *state) +{ + struct osmo_fd *bfd = &state->conn_bfd; + + LOGP(DCSD, LOGL_NOTICE, "TCH CSD sock has closed connection\n"); + + tch_csd_sock_state_cb(state->ms, false); + + osmo_fd_unregister(bfd); + close(bfd->fd); + bfd->fd = -1; + + /* re-enable the generation of ACCEPT for new connections */ + osmo_fd_read_enable(&state->listen_bfd); + + /* flush the queues */ + while (!llist_empty(&state->rxqueue)) + msgb_free(msgb_dequeue(&state->rxqueue)); + while (!llist_empty(&state->txqueue)) + msgb_free(msgb_dequeue(&state->txqueue)); +} + +static int tch_csd_sock_read(struct osmo_fd *bfd) +{ + struct tch_csd_sock_state *state = (struct tch_csd_sock_state *)bfd->data; + struct msgb *msg; + int rc; + + msg = msgb_alloc(256, "tch_csd_sock_rx"); + if (!msg) + return -ENOMEM; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) + return 0; + goto close; + } + + msgb_put(msg, rc); + msgb_enqueue(&state->rxqueue, msg); + return rc; + +close: + msgb_free(msg); + tch_csd_sock_close(state); + return -1; +} + +static int tch_csd_sock_write(struct osmo_fd *bfd) +{ + struct tch_csd_sock_state *state = bfd->data; + + while (!llist_empty(&state->txqueue)) { + struct msgb *msg; + int rc; + + /* dequeue a msgb */ + msg = msgb_dequeue(&state->txqueue); + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc < 0 && errno == EAGAIN) { + llist_add(&msg->list, &state->txqueue); + return 0; + } + msgb_free(msg); + if (rc <= 0) + goto close; + } + + osmo_fd_write_disable(bfd); + return 0; + +close: + tch_csd_sock_close(state); + return -1; +} + +static int tch_csd_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + int rc = 0; + + if (flags & OSMO_FD_READ) { + rc = tch_csd_sock_read(bfd); + if (rc < 0) + return rc; + } + + if (flags & OSMO_FD_WRITE) + rc = tch_csd_sock_write(bfd); + + return rc; +} + +static int tch_csd_sock_accept(struct osmo_fd *bfd, unsigned int flags) +{ + struct tch_csd_sock_state *state = (struct tch_csd_sock_state *)bfd->data; + struct osmo_fd *conn_bfd = &state->conn_bfd; + struct sockaddr_un un_addr; + socklen_t len; + int rc; + + len = sizeof(un_addr); + rc = accept(bfd->fd, (struct sockaddr *)&un_addr, &len); + if (rc < 0) { + LOGP(DCSD, LOGL_ERROR, "Failed to accept() a new connection\n"); + return -1; + } + + if (conn_bfd->fd >= 0) { + LOGP(DCSD, LOGL_NOTICE, "TCH CSD sock already has an active connection\n"); + close(rc); /* reject this connection request */ + return 0; + } + + osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, &tch_csd_sock_cb, state, 0); + if (osmo_fd_register(conn_bfd) != 0) { + LOGP(DCSD, LOGL_ERROR, "osmo_fd_register() failed\n"); + close(conn_bfd->fd); + conn_bfd->fd = -1; + return -1; + } + + LOGP(DCSD, LOGL_NOTICE, "TCH CSD sock got a connection\n"); + + tch_csd_sock_state_cb(state->ms, true); + + return 0; +} + +struct tch_csd_sock_state *tch_csd_sock_init(struct osmocom_ms *ms) +{ + const char *sock_path = ms->settings.tch_data.unix_socket_path; + struct tch_csd_sock_state *state; + struct osmo_fd *bfd; + int rc; + + state = talloc_zero(ms, struct tch_csd_sock_state); + if (state == NULL) + return NULL; + + INIT_LLIST_HEAD(&state->rxqueue); + INIT_LLIST_HEAD(&state->txqueue); + state->conn_bfd.fd = -1; + state->ms = ms; + + bfd = &state->listen_bfd; + + rc = osmo_sock_unix_init_ofd(bfd, SOCK_STREAM, 0, sock_path, OSMO_SOCK_F_BIND); + if (rc < 0) { + LOGP(DCSD, LOGL_ERROR, "Could not create unix socket: %s\n", strerror(errno)); + talloc_free(state); + return NULL; + } + + bfd->cb = &tch_csd_sock_accept; + bfd->data = state; + + return state; +} + +void tch_csd_sock_exit(struct tch_csd_sock_state *state) +{ + if (state->conn_bfd.fd > -1) + tch_csd_sock_close(state); + osmo_fd_unregister(&state->listen_bfd); + close(state->listen_bfd.fd); + talloc_free(state); +} + +void tch_csd_sock_recv(struct tch_csd_sock_state *state, struct msgb *msg) +{ + while (msgb_tailroom(msg) > 0) { + struct msgb *rmsg = msgb_dequeue(&state->rxqueue); + if (rmsg == NULL) + break; + size_t len = OSMO_MIN(msgb_tailroom(msg), msgb_length(rmsg)); + memcpy(msgb_put(msg, len), msgb_data(rmsg), len); + msgb_pull(rmsg, len); + if (msgb_length(rmsg) > 0) + llist_add(&rmsg->list, &state->rxqueue); + else + msgb_free(rmsg); + } +} + +void tch_csd_sock_send(struct tch_csd_sock_state *state, struct msgb *msg) +{ + msgb_enqueue(&state->txqueue, msg); + osmo_fd_write_enable(&state->conn_bfd); +} diff --git a/src/host/layer23/src/mobile/tch_voice.c b/src/host/layer23/src/mobile/tch_voice.c new file mode 100644 index 00000000..a740f71f --- /dev/null +++ b/src/host/layer23/src/mobile/tch_voice.c @@ -0,0 +1,155 @@ +/* + * (C) 2017-2018 by Vadim Yanitskiy <axilirator@gmail.com> + * (C) 2022-2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * 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. + * + */ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/mobile/gapk_io.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/mncc_sock.h> +#include <osmocom/bb/mobile/transaction.h> +#include <osmocom/bb/mobile/tch.h> + +#include <l1ctl_proto.h> + +/* Forward a Downlink voice frame to the external MNCC handler */ +static int tch_forward_mncc(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_data_frame *mncc; + + /* Drop the l1ctl_info_dl header */ + msgb_pull_to_l2(msg); + /* push mncc header in front of data */ + mncc = (struct gsm_data_frame *) + msgb_push(msg, sizeof(struct gsm_data_frame)); + mncc->callref = ms->mncc_entity.ref; + + switch (ms->rrlayer.cd_now.mode) { + case GSM48_CMODE_SPEECH_V1: + { + const uint8_t cbits = ms->rrlayer.cd_now.chan_nr >> 3; + if (cbits == ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs) + mncc->msg_type = GSM_TCHF_FRAME; + else + mncc->msg_type = GSM_TCHH_FRAME; + break; + } + case GSM48_CMODE_SPEECH_EFR: + mncc->msg_type = GSM_TCHF_FRAME_EFR; + break; + case GSM48_CMODE_SPEECH_AMR: /* TODO: no AMR support yet */ + default: + /* TODO: print error message here */ + goto exit_free; + } + + /* distribute and then free */ + if (ms->mncc_entity.sock_state && ms->mncc_entity.ref) + return mncc_sock_from_cc(ms->mncc_entity.sock_state, msg); + +exit_free: + msgb_free(msg); + return 0; +} + +int tch_voice_recv(struct osmocom_ms *ms, struct msgb *msg) +{ + struct tch_voice_state *state = &ms->tch_state->voice; + + switch (state->handler) { + case TCH_VOICE_IOH_LOOPBACK: + /* Remove the DL info header */ + msgb_pull_to_l2(msg); + /* Send voice frame back */ + return tch_send_msg(ms, msg); + case TCH_VOICE_IOH_MNCC_SOCK: + return tch_forward_mncc(ms, msg); + case TCH_VOICE_IOH_GAPK: +#ifdef WITH_GAPK_IO + if (state->gapk_io != NULL) { + gapk_io_enqueue_dl(state->gapk_io, msg); + gapk_io_dequeue_ul(ms, state->gapk_io); + } else { + msgb_free(msg); + } + break; +#endif + case TCH_VOICE_IOH_L1PHY: + case TCH_VOICE_IOH_NONE: + /* Drop voice frame */ + msgb_free(msg); + break; + } + + return 0; +} + +int tch_voice_state_init(struct gsm_trans *trans, struct tch_voice_state *state) +{ + struct osmocom_ms *ms = trans->ms; + struct gsm48_rrlayer *rr = &ms->rrlayer; + const struct gsm48_rr_cd *cd = &rr->cd_now; + + switch (state->handler) { + case TCH_VOICE_IOH_L1PHY: + rr->audio_mode = AUDIO_RX_SPEAKER | AUDIO_TX_MICROPHONE; + break; + case TCH_VOICE_IOH_MNCC_SOCK: + case TCH_VOICE_IOH_LOOPBACK: + rr->audio_mode = AUDIO_RX_TRAFFIC_IND | AUDIO_TX_TRAFFIC_REQ; + break; + case TCH_VOICE_IOH_GAPK: +#ifdef WITH_GAPK_IO + if ((cd->chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_Bm_ACCHs) + state->gapk_io = gapk_io_state_alloc_mode_rate(ms, cd->mode, true); + else /* RSL_CHAN_Lm_ACCHs */ + state->gapk_io = gapk_io_state_alloc_mode_rate(ms, cd->mode, false); + if (state->gapk_io == NULL) + return -1; + rr->audio_mode = AUDIO_RX_TRAFFIC_IND | AUDIO_TX_TRAFFIC_REQ; + break; +#endif + case TCH_VOICE_IOH_NONE: + rr->audio_mode = 0x00; + break; + } + + return 0; +} + +void tch_voice_state_free(struct tch_voice_state *state) +{ + switch (state->handler) { +#ifdef WITH_GAPK_IO + case TCH_VOICE_IOH_GAPK: + gapk_io_state_free(state->gapk_io); + break; +#endif + default: + break; + } +} diff --git a/src/host/layer23/src/mobile/transaction.c b/src/host/layer23/src/mobile/transaction.c index 9824bd1b..570545ba 100644 --- a/src/host/layer23/src/mobile/transaction.c +++ b/src/host/layer23/src/mobile/transaction.c @@ -13,19 +13,17 @@ * 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 <stdint.h> +#include <osmocom/core/signal.h> #include <osmocom/core/talloc.h> #include <osmocom/core/timer.h> #include <osmocom/core/msgb.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/mobile/mncc.h> #include <osmocom/bb/mobile/transaction.h> @@ -33,6 +31,7 @@ void _gsm48_cc_trans_free(struct gsm_trans *trans); void _gsm480_ss_trans_free(struct gsm_trans *trans); void _gsm411_sms_trans_free(struct gsm_trans *trans); +void _gsm44068_gcc_bcc_trans_free(struct gsm_trans *trans); struct gsm_trans *trans_find_by_id(struct osmocom_ms *ms, uint8_t proto, uint8_t trans_id) @@ -47,13 +46,13 @@ struct gsm_trans *trans_find_by_id(struct osmocom_ms *ms, return NULL; } -struct gsm_trans *trans_find_by_callref(struct osmocom_ms *ms, +struct gsm_trans *trans_find_by_callref(struct osmocom_ms *ms, uint8_t protocol, uint32_t callref) { struct gsm_trans *trans; llist_for_each_entry(trans, &ms->trans_list, entry) { - if (trans->callref == callref) + if (trans->protocol == protocol && trans->callref == callref) return trans; } return NULL; @@ -81,11 +80,15 @@ struct gsm_trans *trans_alloc(struct osmocom_ms *ms, llist_add_tail(&trans->entry, &ms->trans_list); + osmo_signal_dispatch(SS_L23_TRANS, S_L23_CC_TRANS_ALLOC, trans); + return trans; } void trans_free(struct gsm_trans *trans) { + osmo_signal_dispatch(SS_L23_TRANS, S_L23_CC_TRANS_FREE, trans); + switch (trans->protocol) { case GSM48_PDISC_CC: _gsm48_cc_trans_free(trans); @@ -96,6 +99,10 @@ void trans_free(struct gsm_trans *trans) case GSM48_PDISC_SMS: _gsm411_sms_trans_free(trans); break; + case GSM48_PDISC_GROUP_CC: + case GSM48_PDISC_BCAST_CC: + _gsm44068_gcc_bcc_trans_free(trans); + break; } DEBUGP(DCC, "ms %s frees transaction (mem %p)\n", trans->ms->name, diff --git a/src/host/layer23/src/mobile/voice.c b/src/host/layer23/src/mobile/voice.c deleted file mode 100644 index b7678337..00000000 --- a/src/host/layer23/src/mobile/voice.c +++ /dev/null @@ -1,78 +0,0 @@ -/* - * (C) 2010 by Andreas Eversberg <jolly@eversberg.eu> - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 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 <stdlib.h> - -#include <osmocom/core/msgb.h> - -#include <osmocom/bb/common/osmocom_data.h> -#include <osmocom/bb/mobile/mncc.h> -#include <osmocom/bb/mobile/voice.h> - - -/* - * receive voice - */ - -static int gsm_recv_voice(struct osmocom_ms *ms, struct msgb *msg) -{ - struct gsm_data_frame *mncc; - - /* distribute and then free */ - if (ms->mncc_entity.mncc_recv && ms->mncc_entity.ref) { - /* push mncc header in front of data */ - mncc = (struct gsm_data_frame *) - msgb_push(msg, sizeof(struct gsm_data_frame)); - mncc->msg_type = GSM_TCHF_FRAME; - mncc->callref = ms->mncc_entity.ref; - ms->mncc_entity.mncc_recv(ms, mncc->msg_type, mncc); - } - - msgb_free(msg); - return 0; -} - -/* - * send voice - */ -int gsm_send_voice(struct osmocom_ms *ms, struct gsm_data_frame *data) -{ - struct msgb *nmsg; - - nmsg = msgb_alloc_headroom(33 + 64, 64, "TCH/F"); - if (!nmsg) - return -ENOMEM; - nmsg->l2h = msgb_put(nmsg, 33); - memcpy(nmsg->l2h, data->data, 33); - - return gsm48_rr_tx_voice(ms, nmsg); -} - -/* - * init - */ - -int gsm_voice_init(struct osmocom_ms *ms) -{ - ms->l1_entity.l1_traffic_ind = gsm_recv_voice; - - return 0; -} diff --git a/src/host/layer23/src/mobile/vty_interface.c b/src/host/layer23/src/mobile/vty_interface.c index 2001b882..eb582d35 100644 --- a/src/host/layer23/src/mobile/vty_interface.c +++ b/src/host/layer23/src/mobile/vty_interface.c @@ -13,10 +13,6 @@ * 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 <string.h> @@ -30,8 +26,10 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/signal.h> #include <osmocom/crypt/auth.h> +#include <osmocom/gsm/gsm23003.h> #include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> #include <osmocom/bb/common/networks.h> #include <osmocom/bb/common/gps.h> #include <osmocom/bb/mobile/mncc.h> @@ -41,49 +39,39 @@ #include <osmocom/bb/mobile/app_mobile.h> #include <osmocom/bb/mobile/gsm480_ss.h> #include <osmocom/bb/mobile/gsm411_sms.h> +#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h> #include <osmocom/vty/telnet_interface.h> #include <osmocom/vty/misc.h> -extern struct llist_head ms_list; -extern struct llist_head active_connections; - -struct cmd_node ms_node = { - MS_NODE, - "%s(ms)# ", +struct cmd_node support_node = { + SUPPORT_NODE, + "%s(support)# ", 1 }; -struct cmd_node testsim_node = { - TESTSIM_NODE, - "%s(test-sim)# ", +struct cmd_node tch_voice_node = { + TCH_VOICE_NODE, + "%s(tch-voice)# ", 1 }; -struct cmd_node support_node = { - SUPPORT_NODE, - "%s(support)# ", +struct cmd_node tch_data_node = { + TCH_DATA_NODE, + "%s(tch-data)# ", 1 }; -static void print_vty(void *priv, const char *fmt, ...) -{ - char buffer[1000]; - struct vty *vty = priv; - va_list args; - - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer) - 1, fmt, args); - buffer[sizeof(buffer) - 1] = '\0'; - va_end(args); +struct cmd_node vgcs_node = { + VGCS_NODE, + "%s(group-call)# ", + 1 +}; - if (buffer[0]) { - if (buffer[strlen(buffer) - 1] == '\n') { - buffer[strlen(buffer) - 1] = '\0'; - vty_out(vty, "%s%s", buffer, VTY_NEWLINE); - } else - vty_out(vty, "%s", buffer); - } -} +struct cmd_node vbs_node = { + VBS_NODE, + "%s(broadcast-call)# ", + 1 +}; int vty_check_number(struct vty *vty, const char *number) { @@ -110,12 +98,35 @@ int vty_check_number(struct vty *vty, const char *number) return 0; } -int vty_reading = 0; -static int hide_default = 0; +int vty_check_callref(struct vty *vty, const char *number) +{ + int i, ii = strlen(number); + + /* First check digits, so that a false command result the following error message. */ + for (i = 0; i < ii; i++) { + if (!(number[i] >= '0' && number[i] <= '9')) { + vty_out(vty, "Invalid digit '%c' in callref!%s", + number[i], VTY_NEWLINE); + return -EINVAL; + } + } + + if (ii < 1) { + vty_out(vty, "Given callref has no digits!%s", VTY_NEWLINE); + return -EINVAL; + } + + if (ii > 8) { + vty_out(vty, "Given callref is too long!%s", VTY_NEWLINE); + return -EINVAL; + } + + return 0; +} static void vty_restart(struct vty *vty, struct osmocom_ms *ms) { - if (vty_reading) + if (l23_vty_reading) return; if (ms->shutdown != MS_SHUTDOWN_NONE) return; @@ -130,25 +141,6 @@ static void vty_restart_if_started(struct vty *vty, struct osmocom_ms *ms) vty_restart(vty, ms); } -static struct osmocom_ms *get_ms(const char *name, struct vty *vty) -{ - struct osmocom_ms *ms; - - llist_for_each_entry(ms, &ms_list, entity) { - if (!strcmp(ms->name, name)) { - if (ms->shutdown != MS_SHUTDOWN_NONE) { - vty_out(vty, "MS '%s' is admin down.%s", name, - VTY_NEWLINE); - return NULL; - } - return ms; - } - } - vty_out(vty, "MS name '%s' does not exits.%s", name, VTY_NEWLINE); - - return NULL; -} - static void gsm_ms_dump(struct osmocom_ms *ms, struct vty *vty) { struct gsm_settings *set = &ms->settings; @@ -204,26 +196,24 @@ static void gsm_ms_dump(struct osmocom_ms *ms, struct vty *vty) else vty_out(vty, " manual network selection state : %s%s", get_m_state_name(ms->plmn.state), VTY_NEWLINE); - if (ms->plmn.mcc) + if (ms->plmn.plmn.mcc) vty_out(vty, " MCC=%s " - "MNC=%s (%s, %s)%s", gsm_print_mcc(ms->plmn.mcc), - gsm_print_mnc(ms->plmn.mnc), gsm_get_mcc(ms->plmn.mcc), - gsm_get_mnc(ms->plmn.mcc, ms->plmn.mnc), VTY_NEWLINE); + "MNC=%s (%s, %s)%s", osmo_mcc_name(ms->plmn.plmn.mcc), + osmo_mnc_name(ms->plmn.plmn.mnc, ms->plmn.plmn.mnc_3_digits), + gsm_get_mcc(ms->plmn.plmn.mcc), + gsm_get_mnc(&ms->plmn.plmn), VTY_NEWLINE); vty_out(vty, " cell selection state: %s%s", get_cs_state_name(ms->cellsel.state), VTY_NEWLINE); - if (ms->cellsel.sel_mcc) { - vty_out(vty, " ARFCN=%s MCC=%s MNC=%s " - "LAC=0x%04x CELLID=0x%04x%s", + if (ms->cellsel.sel_cgi.lai.plmn.mcc) { + vty_out(vty, " ARFCN=%s CGI=%s%s", gsm_print_arfcn(ms->cellsel.sel_arfcn), - gsm_print_mcc(ms->cellsel.sel_mcc), - gsm_print_mnc(ms->cellsel.sel_mnc), - ms->cellsel.sel_lac, ms->cellsel.sel_id, VTY_NEWLINE); + osmo_cgi_name(&ms->cellsel.sel_cgi), VTY_NEWLINE); vty_out(vty, " (%s, %s)%s", - gsm_get_mcc(ms->cellsel.sel_mcc), - gsm_get_mnc(ms->cellsel.sel_mcc, ms->cellsel.sel_mnc), + gsm_get_mcc(ms->cellsel.sel_cgi.lai.plmn.mcc), + gsm_get_mnc(&ms->cellsel.sel_cgi.lai.plmn), VTY_NEWLINE); } - vty_out(vty, " radio ressource layer state: %s%s", + vty_out(vty, " radio resource layer state: %s%s", gsm48_rr_state_names[ms->rrlayer.state], VTY_NEWLINE); vty_out(vty, " mobility management layer state: %s", gsm48_mm_state_names[ms->mmlayer.state]); @@ -239,7 +229,8 @@ static void gsm_ms_dump(struct osmocom_ms *ms, struct vty *vty) DEFUN(show_ms, show_ms_cmd, "show ms [MS_NAME]", - SHOW_STR "Display available MS entities\n") + SHOW_STR "Display available MS entities\n" + "Display specific MS with given name") { struct osmocom_ms *ms; @@ -251,57 +242,13 @@ DEFUN(show_ms, show_ms_cmd, "show ms [MS_NAME]", } } vty_out(vty, "MS name '%s' does not exits.%s", argv[0], - VTY_NEWLINE); + VTY_NEWLINE); return CMD_WARNING; - } else { - llist_for_each_entry(ms, &ms_list, entity) { - gsm_ms_dump(ms, vty); - vty_out(vty, "%s", VTY_NEWLINE); - } - } - - return CMD_SUCCESS; -} - -DEFUN(show_support, show_support_cmd, "show support [MS_NAME]", - SHOW_STR "Display information about MS support\n" - "Name of MS (see \"show ms\")") -{ - struct osmocom_ms *ms; - - if (argc) { - ms = get_ms(argv[0], vty); - if (!ms) - return CMD_WARNING; - gsm_support_dump(ms, print_vty, vty); - } else { - llist_for_each_entry(ms, &ms_list, entity) { - gsm_support_dump(ms, print_vty, vty); - vty_out(vty, "%s", VTY_NEWLINE); - } } - return CMD_SUCCESS; -} - -DEFUN(show_subscr, show_subscr_cmd, "show subscriber [MS_NAME]", - SHOW_STR "Display information about subscriber\n" - "Name of MS (see \"show ms\")") -{ - struct osmocom_ms *ms; - - if (argc) { - ms = get_ms(argv[0], vty); - if (!ms) - return CMD_WARNING; - gsm_subscr_dump(&ms->subscr, print_vty, vty); - } else { - llist_for_each_entry(ms, &ms_list, entity) { - if (ms->shutdown == MS_SHUTDOWN_NONE) { - gsm_subscr_dump(&ms->subscr, print_vty, vty); - vty_out(vty, "%s", VTY_NEWLINE); - } - } + llist_for_each_entry(ms, &ms_list, entity) { + gsm_ms_dump(ms, vty); + vty_out(vty, "%s", VTY_NEWLINE); } return CMD_SUCCESS; @@ -313,11 +260,11 @@ DEFUN(show_cell, show_cell_cmd, "show cell MS_NAME", { struct osmocom_ms *ms; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; - gsm322_dump_cs_list(&ms->cellsel, GSM322_CS_FLAG_SUPPORT, print_vty, + gsm322_dump_cs_list(&ms->cellsel, GSM322_CS_FLAG_SUPPORT, l23_vty_printf, vty); return CMD_SUCCESS; @@ -332,7 +279,7 @@ DEFUN(show_cell_si, show_cell_si_cmd, "show cell MS_NAME <0-1023> [pcs]", struct gsm48_sysinfo *s; uint16_t arfcn = atoi(argv[1]); - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; @@ -352,7 +299,7 @@ DEFUN(show_cell_si, show_cell_si_cmd, "show cell MS_NAME <0-1023> [pcs]", return CMD_SUCCESS; } - gsm48_sysinfo_dump(s, arfcn, print_vty, vty, ms->settings.freq_map); + gsm48_sysinfo_dump(s, arfcn, l23_vty_printf, vty, ms->settings.freq_map); return CMD_SUCCESS; } @@ -363,11 +310,11 @@ DEFUN(show_nbcells, show_nbcells_cmd, "show neighbour-cells MS_NAME", { struct osmocom_ms *ms; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; - gsm322_dump_nb_list(&ms->cellsel, print_vty, vty); + gsm322_dump_nb_list(&ms->cellsel, l23_vty_printf, vty); return CMD_SUCCESS; } @@ -378,26 +325,26 @@ DEFUN(show_ba, show_ba_cmd, "show ba MS_NAME [MCC] [MNC]", "Mobile Network Code") { struct osmocom_ms *ms; - uint16_t mcc = 0, mnc = 0; + struct osmo_plmn_id plmn; + struct osmo_plmn_id *plmn_ptr = NULL; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; if (argc >= 3) { - mcc = gsm_input_mcc((char *)argv[1]); - mnc = gsm_input_mnc((char *)argv[2]); - if (mcc == GSM_INPUT_INVALID) { + if (osmo_mcc_from_str(argv[1], &plmn.mcc) < 0) { vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); return CMD_WARNING; } - if (mnc == GSM_INPUT_INVALID) { + if (osmo_mnc_from_str(argv[2], &plmn.mnc, &plmn.mnc_3_digits) < 0) { vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); return CMD_WARNING; } + plmn_ptr = &plmn; } - gsm322_dump_ba_list(&ms->cellsel, mcc, mnc, print_vty, vty); + gsm322_dump_ba_list(&ms->cellsel, plmn_ptr, l23_vty_printf, vty); return CMD_SUCCESS; } @@ -408,11 +355,11 @@ DEFUN(show_forb_plmn, show_forb_plmn_cmd, "show forbidden plmn MS_NAME", { struct osmocom_ms *ms; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; - gsm_subscr_dump_forbidden_plmn(ms, print_vty, vty); + gsm_subscr_dump_forbidden_plmn(ms, l23_vty_printf, vty); return CMD_SUCCESS; } @@ -423,11 +370,41 @@ DEFUN(show_forb_la, show_forb_la_cmd, "show forbidden location-area MS_NAME", { struct osmocom_ms *ms; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + gsm322_dump_forbidden_la(ms, l23_vty_printf, vty); + + return CMD_SUCCESS; +} + +#define SHOW_ASCI_STR SHOW_STR "Display information about ASCI items\nName of MS (see \"show ms\")\n" + +DEFUN(show_asci_calls, show_asci_calls_cmd, "show asci MS_NAME calls", + SHOW_ASCI_STR "Display ongoing ASCI calls") +{ + struct osmocom_ms *ms; + + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; - gsm322_dump_forbidden_la(ms, print_vty, vty); + gsm44068_dump_calls(ms, l23_vty_printf, vty); + + return CMD_SUCCESS; +} + +DEFUN(show_asci_neighbors, show_asci_neighbors_cmd, "show asci MS_NAME neighbors", + SHOW_ASCI_STR "Display neigbor cells of ongoing or last ASCI call") +{ + struct osmocom_ms *ms; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + gsm48_si10_dump(ms->cellsel.si, l23_vty_printf, vty); return CMD_SUCCESS; } @@ -437,7 +414,7 @@ DEFUN(monitor_network, monitor_network_cmd, "monitor network MS_NAME", { struct osmocom_ms *ms; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; @@ -452,7 +429,7 @@ DEFUN(no_monitor_network, no_monitor_network_cmd, "no monitor network MS_NAME", { struct osmocom_ms *ms; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; @@ -461,440 +438,482 @@ DEFUN(no_monitor_network, no_monitor_network_cmd, "no monitor network MS_NAME", return CMD_SUCCESS; } -static int _sim_test_cmd(struct vty *vty, int argc, const char *argv[], - int attached) +DEFUN(network_select, network_select_cmd, + "network select MS_NAME MCC MNC [force]", + "Select ...\nSelect Network\nName of MS (see \"show ms\")\n" + "Mobile Country Code\nMobile Network Code\n" + "Force selecting a network that is not in the list") { struct osmocom_ms *ms; - struct gsm_settings *set; - - /* Initial testcard settings */ - uint16_t mcc = 0x001, mnc = 0x01f, lac = 0x0000; - uint32_t tmsi = 0xffffffff; + struct gsm322_plmn *plmn322; + struct msgb *nmsg; + struct gsm322_msg *ngm; + struct gsm322_plmn_list *temp; + struct osmo_plmn_id plmn; + int found = 0; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; + plmn322 = &ms->plmn; - if (ms->subscr.sim_valid) { - vty_out(vty, "SIM already attached, remove first!%s", + if (ms->settings.plmn_mode != PLMN_MODE_MANUAL) { + vty_out(vty, "Not in manual network selection mode%s", VTY_NEWLINE); return CMD_WARNING; } - set = &ms->settings; - if (set->test_rplmn_valid) { - mcc = set->test_rplmn_mcc; - mnc = set->test_rplmn_mnc; - - if (set->test_lac > 0x0000 && set->test_lac < 0xfffe) - lac = set->test_lac; - - if (set->test_tmsi != 0xffffffff) - tmsi = set->test_tmsi; + if (osmo_mcc_from_str(argv[1], &plmn.mcc) < 0) { + vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); + return CMD_WARNING; } - - if (argc == 2) { - vty_out(vty, "Give MNC together with MCC%s", VTY_NEWLINE); + if (osmo_mnc_from_str(argv[2], &plmn.mnc, &plmn.mnc_3_digits) < 0) { + vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); return CMD_WARNING; } - if (argc >= 3) { - mcc = gsm_input_mcc((char *)argv[1]); - mnc = gsm_input_mnc((char *)argv[2]); - if (mcc == GSM_INPUT_INVALID) { - vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); - return CMD_WARNING; - } - if (mnc == GSM_INPUT_INVALID) { - vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); + + if (argc < 4) { + llist_for_each_entry(temp, &plmn322->sorted_plmn, entry) + if (osmo_plmn_cmp(&temp->plmn, &plmn) == 0) + found = 1; + if (!found) { + vty_out(vty, "Network not in list!%s", VTY_NEWLINE); + vty_out(vty, "To force selecting this network, use " + "'force' keyword%s", VTY_NEWLINE); return CMD_WARNING; } } - if (argc >= 4) - lac = strtoul(argv[3], NULL, 16); - - if (argc >= 5) - tmsi = strtoul(argv[4], NULL, 16); - - gsm_subscr_testcard(ms, mcc, mnc, lac, tmsi, attached); + nmsg = gsm322_msgb_alloc(GSM322_EVENT_CHOOSE_PLMN); + if (!nmsg) + return CMD_WARNING; + ngm = (struct gsm322_msg *) nmsg->data; + memcpy(&ngm->plmn, &plmn, sizeof(struct osmo_plmn_id)); + gsm322_plmn_sendmsg(ms, nmsg); return CMD_SUCCESS; } -DEFUN(sim_test, sim_test_cmd, - "sim testcard MS_NAME [MCC] [MNC] [LAC] [TMSI]", - "SIM actions\nAttach bulit in test SIM\nName of MS (see \"show ms\")\n" - "Optionally set mobile Country Code of RPLMN\n" - "Optionally set mobile Network Code of RPLMN\n" - "Optionally set location area code of RPLMN\n" - "Optionally set current assigned TMSI") -{ - return _sim_test_cmd(vty, argc, argv, 0); -} - -DEFUN(sim_test_att, sim_test_att_cmd, - "sim testcard MS_NAME MCC MNC LAC TMSI attached", - "SIM actions\nAttach bulit in test SIM\nName of MS (see \"show ms\")\n" - "Set mobile Country Code of RPLMN\nSet mobile Network Code of RPLMN\n" - "Set location area code\nSet current assigned TMSI\n" - "Indicate to MM that card is already attached") -{ - return _sim_test_cmd(vty, argc, argv, 1); -} +#define CALL_CMD "call MS_NAME" +#define CALL_CMD_DESC \ + "Call related commands\n" \ + "Name of MS (see \"show ms\")\n" -DEFUN(sim_sap, sim_sap_cmd, "sim sap MS_NAME", - "SIM actions\nAttach SIM over SAP interface\n" - "Name of MS (see \"show ms\")\n") +DEFUN(call_num, call_num_cmd, + CALL_CMD " NUMBER [(voice|data|fax)]", + CALL_CMD_DESC + "Phone number to call " + "(Use digits '0123456789*#abc', and '+' to dial international)\n" + "Initiate a regular voice call (default)\n" + "Initiate a data call (UDI or 3.1 kHz audio)\n" + "Initiate a data call (Facsimile group 3)\n") { struct osmocom_ms *ms; + struct gsm_settings *set; + struct gsm_settings_abbrev *abbrev; + enum gsm_call_type call_type; + const char *number; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; + set = &ms->settings; - if (ms->subscr.sim_valid) { - vty_out(vty, "SIM already attached, remove first!%s", - VTY_NEWLINE); + if (set->ch_cap == GSM_CAP_SDCCH) { + vty_out(vty, "Basic call is not supported for SDCCH only " + "mobile%s", VTY_NEWLINE); return CMD_WARNING; } - if (gsm_subscr_sapcard(ms) != 0) { - return CMD_WARNING; + number = argv[1]; + llist_for_each_entry(abbrev, &set->abbrev, list) { + if (!strcmp(number, abbrev->abbrev)) { + number = abbrev->number; + vty_out(vty, "Dialing number '%s'%s", number, + VTY_NEWLINE); + break; + } } - return CMD_SUCCESS; -} - -DEFUN(sim_reader, sim_reader_cmd, "sim reader MS_NAME", - "SIM actions\nAttach SIM from reader\nName of MS (see \"show ms\")") -{ - struct osmocom_ms *ms; - - ms = get_ms(argv[0], vty); - if (!ms) + if (vty_check_number(vty, number)) return CMD_WARNING; - if (ms->subscr.sim_valid) { - vty_out(vty, "SIM already attached, remove first!%s", - VTY_NEWLINE); + if (argc < 3 || !strcmp(argv[2], "voice")) + call_type = GSM_CALL_T_VOICE; /* implicit default */ + else if (!strcmp(argv[2], "data")) + call_type = GSM_CALL_T_DATA; + else if (!strcmp(argv[2], "fax")) + call_type = GSM_CALL_T_DATA_FAX; + else return CMD_WARNING; - } - gsm_subscr_simcard(ms); + mncc_call(ms, number, call_type); return CMD_SUCCESS; } -DEFUN(sim_remove, sim_remove_cmd, "sim remove MS_NAME", - "SIM actions\nDetach SIM card\nName of MS (see \"show ms\")") +DEFUN(call, call_cmd, + CALL_CMD " (emergency|answer|hangup|hold)", + CALL_CMD_DESC + "Make an emergency call\n" + "Answer an incoming call\n" + "Hangup a call\n" + "Hold current active call\n") { struct osmocom_ms *ms; + struct gsm_settings *set; + const char *number; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; + set = &ms->settings; - if (!ms->subscr.sim_valid) { - vty_out(vty, "No SIM attached!%s", VTY_NEWLINE); + if (set->ch_cap == GSM_CAP_SDCCH) { + vty_out(vty, "Basic call is not supported for SDCCH only " + "mobile%s", VTY_NEWLINE); return CMD_WARNING; } - if (ms->subscr.sim_type == GSM_SIM_TYPE_SAP) { - gsm_subscr_remove_sapcard(ms); - } + number = argv[1]; + if (!strcmp(number, "emergency")) + mncc_call(ms, number, GSM_CALL_T_VOICE); + else if (!strcmp(number, "answer")) + mncc_answer(ms); + else if (!strcmp(number, "hangup")) + mncc_hangup(ms); + else if (!strcmp(number, "hold")) + mncc_hold(ms); + else /* shall not happen */ + OSMO_ASSERT(0); - gsm_subscr_remove(ms); return CMD_SUCCESS; } -DEFUN(sim_pin, sim_pin_cmd, "sim pin MS_NAME PIN", - "SIM actions\nEnter PIN for SIM card\nName of MS (see \"show ms\")\n" - "PIN number") +DEFUN(call_retr, call_retr_cmd, + CALL_CMD " retrieve [NUMBER]", + CALL_CMD_DESC + "Retrieve call on hold\n" + "Number of call to retrieve\n") { struct osmocom_ms *ms; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; - if (strlen(argv[1]) < 4 || strlen(argv[1]) > 8) { - vty_out(vty, "PIN must be in range 4..8!%s", VTY_NEWLINE); - return CMD_WARNING; - } - - if (!ms->subscr.sim_pin_required) { - vty_out(vty, "No PIN is required at this time!%s", VTY_NEWLINE); - return CMD_WARNING; - } - - gsm_subscr_sim_pin(ms, (char *)argv[1], "", 0); + mncc_retrieve(ms, (argc > 1) ? atoi(argv[1]) : 0); return CMD_SUCCESS; } -DEFUN(sim_disable_pin, sim_disable_pin_cmd, "sim disable-pin MS_NAME PIN", - "SIM actions\nDisable PIN of SIM card\nName of MS (see \"show ms\")\n" - "PIN number") +DEFUN(call_dtmf, call_dtmf_cmd, + CALL_CMD " dtmf DIGITS", + CALL_CMD_DESC + "Send DTMF (Dual-Tone Multi-Frequency) tones\n" + "One or more DTMF digits to transmit\n") { struct osmocom_ms *ms; + struct gsm_settings *set; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; + set = &ms->settings; - if (strlen(argv[1]) < 4 || strlen(argv[1]) > 8) { - vty_out(vty, "PIN must be in range 4..8!%s", VTY_NEWLINE); + if (!set->cc_dtmf) { + vty_out(vty, "DTMF not supported, please enable!%s", + VTY_NEWLINE); return CMD_WARNING; } - gsm_subscr_sim_pin(ms, (char *)argv[1], "", -1); + mncc_dtmf(ms, (char *)argv[1]); return CMD_SUCCESS; } -DEFUN(sim_enable_pin, sim_enable_pin_cmd, "sim enable-pin MS_NAME PIN", - "SIM actions\nEnable PIN of SIM card\nName of MS (see \"show ms\")\n" - "PIN number") +#define CALL_PARAMS_CMD \ + CALL_CMD " params" +#define CALL_PARAMS_CMD_DESC \ + CALL_CMD_DESC \ + "Call related parameters\n" + +#define CALL_PARAMS_DATA_CMD \ + CALL_PARAMS_CMD " data" +#define CALL_PARAMS_DATA_CMD_DESC \ + CALL_PARAMS_CMD_DESC \ + "Parameters for data calls\n" + +#define CFG_TCH_DATA_CALL_PARAMS_CMD \ + "call-params" +#define CFG_TCH_DATA_CALL_PARAMS_CMD_DESC \ + "Parameters for data calls\n" + +/* only supported rate/type ('<speed>' in AT+CBST) values are listed here */ +static const struct value_string data_type_rate_descs[] = { +#if 0 + /* TODO: rates below 2400 bps are not supported */ + { DATA_CALL_TR_V21_300, "300 bps (V.21)" }, + { DATA_CALL_TR_V22_1200, "1200 bps (V.22)" }, + { DATA_CALL_TR_V23_1200_75, "1200/75 bps (V.23)" }, +#endif + { DATA_CALL_TR_V22bis_2400, "2400 bps (V.22bis)" }, + { DATA_CALL_TR_V26ter_2400, "2400 bps (V.26ter)" }, + { DATA_CALL_TR_V32_4800, "4800 bps (V.32)" }, + { DATA_CALL_TR_V32_9600, "9600 bps (V.32)" }, +#if 0 + /* TODO: V.34 is not supported, see notes in bcap_data_set[] */ + { DATA_CALL_TR_V34_9600, "9600 bps (V.34)" }, + /* TODO: rates below 2400 bps are not supported */ + { DATA_CALL_TR_V110_300, "300 bps (V.110)" }, + { DATA_CALL_TR_V110_1200, "1200 bps (V.110)" }, +#endif + { DATA_CALL_TR_V110_2400, "2400 bps (V.110 or X.31 flag stuffing)" }, + { DATA_CALL_TR_V110_4800, "4800 bps (V.110 or X.31 flag stuffing)" }, + { DATA_CALL_TR_V110_9600, "9600 bps (V.110 or X.31 flag stuffing)" }, +#if 0 + /* TODO: 14400 bps is not supported */ + { DATA_CALL_TR_V110_14400, "14400 bps (V.110 or X.31 flag stuffing)" }, +#endif + { 0, NULL } +}; + +static void _data_type_rate_cmd_string(void *ctx, struct cmd_element *cmd) { - struct osmocom_ms *ms; - ms = get_ms(argv[0], vty); - if (!ms) - return CMD_WARNING; + const struct value_string *vs; + char *string; - if (strlen(argv[1]) < 4 || strlen(argv[1]) > 8) { - vty_out(vty, "PIN must be in range 4..8!%s", VTY_NEWLINE); - return CMD_WARNING; - } + string = talloc_asprintf(ctx, "%s type-rate (", cmd->string); + for (vs = &data_type_rate_descs[0]; vs->value || vs->str; vs++) + string = talloc_asprintf_append(string, "%u|", vs->value); + string[strlen(string) - 1] = ')'; + cmd->string = string; +} - gsm_subscr_sim_pin(ms, (char *)argv[1], "", 1); +DEFUN(cfg_ms_tch_data_cp_type_rate, + cfg_ms_tch_data_cp_type_rate_cmd, + CFG_TCH_DATA_CALL_PARAMS_CMD /* generated */, + CFG_TCH_DATA_CALL_PARAMS_CMD_DESC /* generated */) +{ + struct osmocom_ms *ms = (struct osmocom_ms *)vty->index; + struct data_call_params *cp = &ms->settings.call_params.data; + int val; + + val = atoi(argv[0]); + OSMO_ASSERT(get_value_string_or_null(data_type_rate_descs, val) != NULL); + cp->type_rate = (enum data_call_type_rate)val; return CMD_SUCCESS; } -DEFUN(sim_change_pin, sim_change_pin_cmd, "sim change-pin MS_NAME OLD NEW", - "SIM actions\nChange PIN of SIM card\nName of MS (see \"show ms\")\n" - "Old PIN number\nNew PIN number") +DEFUN(call_params_data_type_rate, + call_params_data_type_rate_cmd, + CALL_PARAMS_DATA_CMD /* generated */, + CALL_PARAMS_DATA_CMD_DESC /* generated */) { - struct osmocom_ms *ms; - - ms = get_ms(argv[0], vty); - if (!ms) + vty->index = l23_vty_get_ms(argv[0], vty); + if (vty->index == NULL) return CMD_WARNING; - if (strlen(argv[1]) < 4 || strlen(argv[1]) > 8) { - vty_out(vty, "Old PIN must be in range 4..8!%s", VTY_NEWLINE); - return CMD_WARNING; - } - if (strlen(argv[2]) < 4 || strlen(argv[2]) > 8) { - vty_out(vty, "New PIN must be in range 4..8!%s", VTY_NEWLINE); + return cfg_ms_tch_data_cp_type_rate(self, vty, argc - 1, argv + 1); +} + +#define CALL_PARAMS_CE_CMD \ + "ce (transparent|non-transparent) [prefer]" +#define CALL_PARAMS_CE_CMD_DESC \ + "Connection element (does not apply to FAX calls)\n" \ + "Transparent connection\n" \ + "Non-transparent connection (RLP)\n" \ + "Prefer the selected mode, but also accept other(s)\n" + +DEFUN(cfg_ms_tch_data_cp_ce, + cfg_ms_tch_data_cp_ce_cmd, + CFG_TCH_DATA_CALL_PARAMS_CMD " " CALL_PARAMS_CE_CMD, + CFG_TCH_DATA_CALL_PARAMS_CMD_DESC CALL_PARAMS_CE_CMD_DESC) +{ + struct osmocom_ms *ms = (struct osmocom_ms *)vty->index; + struct data_call_params *cp = &ms->settings.call_params.data; + + if (!strcmp(argv[0], "transparent")) { + if (argc > 1) + cp->transp = GSM48_BCAP_TR_TR_PREF; + else + cp->transp = GSM48_BCAP_TR_TRANSP; + } else if (!strcmp(argv[0], "non-transparent")) { + if (argc > 1) + cp->transp = GSM48_BCAP_TR_RLP_PREF; + else + cp->transp = GSM48_BCAP_TR_RLP; + } else { /* should not happen */ return CMD_WARNING; } - gsm_subscr_sim_pin(ms, (char *)argv[1], (char *)argv[2], 2); - return CMD_SUCCESS; } -DEFUN(sim_unblock_pin, sim_unblock_pin_cmd, "sim unblock-pin MS_NAME PUC NEW", - "SIM actions\nChange PIN of SIM card\nName of MS (see \"show ms\")\n" - "Personal Unblock Key\nNew PIN number") +DEFUN(call_params_data_ce, + call_params_data_ce_cmd, + CALL_PARAMS_DATA_CMD " " CALL_PARAMS_CE_CMD, + CALL_PARAMS_DATA_CMD_DESC CALL_PARAMS_CE_CMD_DESC) { - struct osmocom_ms *ms; - - ms = get_ms(argv[0], vty); - if (!ms) + vty->index = l23_vty_get_ms(argv[0], vty); + if (vty->index == NULL) return CMD_WARNING; - if (strlen(argv[1]) != 8) { - vty_out(vty, "PUC must be 8 digits!%s", VTY_NEWLINE); - return CMD_WARNING; - } - if (strlen(argv[2]) < 4 || strlen(argv[2]) > 8) { - vty_out(vty, "PIN must be in range 4..8!%s", VTY_NEWLINE); - return CMD_WARNING; - } + return cfg_ms_tch_data_cp_ce(self, vty, argc - 1, argv + 1); +} - gsm_subscr_sim_pin(ms, (char *)argv[1], (char *)argv[2], 99); +#define CALL_PARAMS_SYNC_ASYNC_CMD "(sync|async)" +#define CALL_PARAMS_SYNC_ASYNC_CMD_DESC \ + "Synchronous connection (always used for FAX calls)\n" \ + "Asynchronous connection (does not apply to FAX calls)\n" + +DEFUN(cfg_ms_tch_data_cp_sync_async, + cfg_ms_tch_data_cp_sync_async_cmd, + CFG_TCH_DATA_CALL_PARAMS_CMD " " CALL_PARAMS_SYNC_ASYNC_CMD, + CFG_TCH_DATA_CALL_PARAMS_CMD_DESC CALL_PARAMS_SYNC_ASYNC_CMD_DESC) +{ + struct osmocom_ms *ms = (struct osmocom_ms *)vty->index; + struct data_call_params *cp = &ms->settings.call_params.data; + + cp->is_async = (argv[0][0] == 'a'); return CMD_SUCCESS; } -DEFUN(sim_lai, sim_lai_cmd, "sim lai MS_NAME MCC MNC LAC", - "SIM actions\nChange LAI of SIM card\nName of MS (see \"show ms\")\n" - "Mobile Country Code\nMobile Network Code\nLocation Area Code " - " (use 0000 to remove LAI)") +DEFUN(call_params_data_sync_async, + call_params_data_sync_async_cmd, + CALL_PARAMS_DATA_CMD " " CALL_PARAMS_SYNC_ASYNC_CMD, + CALL_PARAMS_DATA_CMD_DESC CALL_PARAMS_SYNC_ASYNC_CMD_DESC) { - struct osmocom_ms *ms; - uint16_t mcc = gsm_input_mcc((char *)argv[1]), - mnc = gsm_input_mnc((char *)argv[2]), - lac = strtoul(argv[3], NULL, 16); - - ms = get_ms(argv[0], vty); - if (!ms) + vty->index = l23_vty_get_ms(argv[0], vty); + if (vty->index == NULL) return CMD_WARNING; - if (mcc == GSM_INPUT_INVALID) { - vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); - return CMD_WARNING; - } - if (mnc == GSM_INPUT_INVALID) { - vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); - return CMD_WARNING; - } + return cfg_ms_tch_data_cp_sync_async(self, vty, argc - 1, argv + 1); +} + +#define CALL_PARAMS_ASYNC_CMD "async" +#define CALL_PARAMS_ASYNC_CMD_DESC \ + "Asynchronous connection params (does not apply to FAX calls)\n" + +#define CALL_PARAMS_ASYNC_NR_STOP_BITS_CMD \ + CALL_PARAMS_ASYNC_CMD " nr-stop-bits <1-2>" +#define CALL_PARAMS_ASYNC_NR_STOP_BITS_CMD_DESC \ + CALL_PARAMS_ASYNC_CMD_DESC \ + "Number of stop bits (soft-UART config)\n" \ + "Number of stop bits (default: 1)\n" - ms->subscr.mcc = mcc; - ms->subscr.mnc = mnc; - ms->subscr.lac = lac; - ms->subscr.tmsi = 0xffffffff; +DEFUN(cfg_ms_tch_data_cp_async_nr_stop_bits, + cfg_ms_tch_data_cp_async_nr_stop_bits_cmd, + CFG_TCH_DATA_CALL_PARAMS_CMD " " CALL_PARAMS_ASYNC_NR_STOP_BITS_CMD, + CFG_TCH_DATA_CALL_PARAMS_CMD_DESC CALL_PARAMS_ASYNC_NR_STOP_BITS_CMD_DESC) +{ + struct osmocom_ms *ms = (struct osmocom_ms *)vty->index; + struct data_call_params *cp = &ms->settings.call_params.data; - gsm_subscr_write_loci(ms); + cp->nr_stop_bits = atoi(argv[0]); return CMD_SUCCESS; } -DEFUN(network_select, network_select_cmd, - "network select MS_NAME MCC MNC [force]", - "Select ...\nSelect Network\nName of MS (see \"show ms\")\n" - "Mobile Country Code\nMobile Network Code\n" - "Force selecting a network that is not in the list") +DEFUN(call_params_data_async_nr_stop_bits, + call_params_data_async_nr_stop_bits_cmd, + CALL_PARAMS_DATA_CMD " " CALL_PARAMS_ASYNC_NR_STOP_BITS_CMD, + CALL_PARAMS_DATA_CMD_DESC CALL_PARAMS_ASYNC_NR_STOP_BITS_CMD_DESC) { - struct osmocom_ms *ms; - struct gsm322_plmn *plmn; - struct msgb *nmsg; - struct gsm322_msg *ngm; - struct gsm322_plmn_list *temp; - uint16_t mcc = gsm_input_mcc((char *)argv[1]), - mnc = gsm_input_mnc((char *)argv[2]); - int found = 0; - - ms = get_ms(argv[0], vty); - if (!ms) + vty->index = l23_vty_get_ms(argv[0], vty); + if (vty->index == NULL) return CMD_WARNING; - plmn = &ms->plmn; - if (ms->settings.plmn_mode != PLMN_MODE_MANUAL) { - vty_out(vty, "Not in manual network selection mode%s", - VTY_NEWLINE); - return CMD_WARNING; - } + return cfg_ms_tch_data_cp_async_nr_stop_bits(self, vty, argc - 1, argv + 1); +} - if (mcc == GSM_INPUT_INVALID) { - vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); - return CMD_WARNING; - } - if (mnc == GSM_INPUT_INVALID) { - vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); - return CMD_WARNING; - } +#define CALL_PARAMS_ASYNC_NR_DATA_BITS_CMD \ + CALL_PARAMS_ASYNC_CMD " nr-data-bits <7-8>" +#define CALL_PARAMS_ASYNC_NR_DATA_BITS_CMD_DESC \ + CALL_PARAMS_ASYNC_CMD_DESC \ + "Number of data bits (soft-UART config)\n" \ + "Number of data bits (default: 8)\n" - if (argc < 4) { - llist_for_each_entry(temp, &plmn->sorted_plmn, entry) - if (temp->mcc == mcc && temp->mnc == mnc) - found = 1; - if (!found) { - vty_out(vty, "Network not in list!%s", VTY_NEWLINE); - vty_out(vty, "To force selecting this network, use " - "'force' keyword%s", VTY_NEWLINE); - return CMD_WARNING; - } - } +DEFUN(cfg_ms_tch_data_cp_async_nr_data_bits, + cfg_ms_tch_data_cp_async_nr_data_bits_cmd, + CFG_TCH_DATA_CALL_PARAMS_CMD " " CALL_PARAMS_ASYNC_NR_DATA_BITS_CMD, + CFG_TCH_DATA_CALL_PARAMS_CMD_DESC CALL_PARAMS_ASYNC_NR_DATA_BITS_CMD_DESC) +{ + struct osmocom_ms *ms = (struct osmocom_ms *)vty->index; + struct data_call_params *cp = &ms->settings.call_params.data; - nmsg = gsm322_msgb_alloc(GSM322_EVENT_CHOOSE_PLMN); - if (!nmsg) - return CMD_WARNING; - ngm = (struct gsm322_msg *) nmsg->data; - ngm->mcc = mcc; - ngm->mnc = mnc; - gsm322_plmn_sendmsg(ms, nmsg); + cp->nr_data_bits = atoi(argv[0]); return CMD_SUCCESS; } -DEFUN(call, call_cmd, "call MS_NAME (NUMBER|emergency|answer|hangup|hold)", - "Make a call\nName of MS (see \"show ms\")\nPhone number to call " - "(Use digits '0123456789*#abc', and '+' to dial international)\n" - "Make an emergency call\nAnswer an incomming call\nHangup a call\n" - "Hold current active call\n") +DEFUN(call_params_data_async_nr_data_bits, + call_params_data_async_nr_data_bits_cmd, + CALL_PARAMS_DATA_CMD " " CALL_PARAMS_ASYNC_NR_DATA_BITS_CMD, + CALL_PARAMS_DATA_CMD_DESC CALL_PARAMS_ASYNC_NR_DATA_BITS_CMD_DESC) { - struct osmocom_ms *ms; - struct gsm_settings *set; - struct gsm_settings_abbrev *abbrev; - char *number; - - ms = get_ms(argv[0], vty); - if (!ms) + vty->index = l23_vty_get_ms(argv[0], vty); + if (vty->index == NULL) return CMD_WARNING; - set = &ms->settings; - if (set->ch_cap == GSM_CAP_SDCCH) { - vty_out(vty, "Basic call is not supported for SDCCH only " - "mobile%s", VTY_NEWLINE); - return CMD_WARNING; - } + return cfg_ms_tch_data_cp_async_nr_data_bits(self, vty, argc - 1, argv + 1); +} - number = (char *)argv[1]; - if (!strcmp(number, "emergency")) - mncc_call(ms, number); - else if (!strcmp(number, "answer")) - mncc_answer(ms); - else if (!strcmp(number, "hangup")) - mncc_hangup(ms); - else if (!strcmp(number, "hold")) - mncc_hold(ms); - else { - llist_for_each_entry(abbrev, &set->abbrev, list) { - if (!strcmp(number, abbrev->abbrev)) { - number = abbrev->number; - vty_out(vty, "Dialing number '%s'%s", number, - VTY_NEWLINE); - break; - } - } - if (vty_check_number(vty, number)) - return CMD_WARNING; - mncc_call(ms, number); - } +static const struct value_string async_parity_names[] = { + { GSM48_BCAP_PAR_NONE, "none" }, + { GSM48_BCAP_PAR_EVEN, "even" }, + { GSM48_BCAP_PAR_ODD, "odd" }, + { GSM48_BCAP_PAR_ONE, "mark" }, + { GSM48_BCAP_PAR_ZERO, "space" }, + { 0, NULL } +}; - return CMD_SUCCESS; -} +static const struct value_string async_parity_descs[] = { + { GSM48_BCAP_PAR_NONE, "No parity bit (default)" }, + { GSM48_BCAP_PAR_EVEN, "Even parity" }, + { GSM48_BCAP_PAR_ODD, "Odd parity" }, + { GSM48_BCAP_PAR_ONE, "Always 1" }, + { GSM48_BCAP_PAR_ZERO, "Always 0" }, + { 0, NULL } +}; -DEFUN(call_retr, call_retr_cmd, "call MS_NAME retrieve [NUMBER]", - "Make a call\nName of MS (see \"show ms\")\n" - "Retrieve call on hold\nNumber of call to retrieve") -{ - struct osmocom_ms *ms; +#define CALL_PARAMS_ASYNC_PARITY_CMD \ + CALL_PARAMS_ASYNC_CMD " parity" +#define CALL_PARAMS_ASYNC_PARITY_CMD_DESC \ + CALL_PARAMS_ASYNC_CMD_DESC \ + "Parity mode (soft-UART config)\n" - ms = get_ms(argv[0], vty); - if (!ms) - return CMD_WARNING; +DEFUN(cfg_ms_tch_data_cp_async_parity, + cfg_ms_tch_data_cp_async_parity_cmd, + CFG_TCH_DATA_CALL_PARAMS_CMD /* generated */, + CFG_TCH_DATA_CALL_PARAMS_CMD_DESC + CALL_PARAMS_ASYNC_PARITY_CMD_DESC /* generated */) +{ + struct osmocom_ms *ms = (struct osmocom_ms *)vty->index; + struct data_call_params *cp = &ms->settings.call_params.data; + int val; - mncc_retrieve(ms, (argc > 1) ? atoi(argv[1]) : 0); + val = get_string_value(async_parity_names, argv[0]); + OSMO_ASSERT(val >= 0); /* should not happen */ + cp->parity = (enum gsm48_bcap_parity)val; return CMD_SUCCESS; } -DEFUN(call_dtmf, call_dtmf_cmd, "call MS_NAME dtmf DIGITS", - "Make a call\nName of MS (see \"show ms\")\n" - "One or more DTMF digits to transmit") +DEFUN(call_params_data_async_parity, + call_params_data_async_parity_cmd, + CALL_PARAMS_DATA_CMD /* generated */, + CALL_PARAMS_DATA_CMD_DESC + CALL_PARAMS_ASYNC_PARITY_CMD_DESC /* generated */) { - struct osmocom_ms *ms; - struct gsm_settings *set; - - ms = get_ms(argv[0], vty); - if (!ms) + vty->index = l23_vty_get_ms(argv[0], vty); + if (vty->index == NULL) return CMD_WARNING; - set = &ms->settings; - if (!set->cc_dtmf) { - vty_out(vty, "DTMF not supported, please enable!%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - mncc_dtmf(ms, (char *)argv[1]); - - return CMD_SUCCESS; + return cfg_ms_tch_data_cp_async_parity(self, vty, argc - 1, argv + 1); } DEFUN(sms, sms_cmd, "sms MS_NAME NUMBER .LINE", @@ -907,7 +926,7 @@ DEFUN(sms, sms_cmd, "sms MS_NAME NUMBER .LINE", struct gsm_settings_abbrev *abbrev; char *number, *sms_sca = NULL; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; set = &ms->settings; @@ -966,7 +985,7 @@ DEFUN(service, service_cmd, "service MS_NAME (*#06#|*#21#|*#67#|*#61#|*#62#" { struct osmocom_ms *ms; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; @@ -975,14 +994,157 @@ DEFUN(service, service_cmd, "service MS_NAME (*#06#|*#21#|*#67#|*#61#|*#62#" return CMD_SUCCESS; } +#define VGCS_STR "Make a voice group call\nName of MS (see \"show ms\")\n" +#define VGCS_CMDS "(CALLREF|hangup|leave|talk|listen)" +#define VGCS_CMDS_TXT \ + "Voice group to call or join\nHangup voice group call\nLeave voice group call\nBecome talker\nBecome listener" + +/* This command enters VGCS call node with given MS. */ +DEFUN(vgcs_enter, vgcs_enter_cmd, "group-call MS_NAME", + VGCS_STR) +{ + struct osmocom_ms *ms; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + vty->index = ms; + vty->node = VGCS_NODE; + + return CMD_SUCCESS; +} + +/* These commands perform VGCS on VGCS node. */ +DEFUN(vgcs, vgcs_cmd, VGCS_CMDS, + VGCS_CMDS_TXT) +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set; + const char *command; + + set = &ms->settings; + + if (!set->vgcs) { + vty_out(vty, "VGCS not supported by this mobile, please enable VGCS support%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (set->ch_cap == GSM_CAP_SDCCH) { + vty_out(vty, "ASCI call is not supported for SDCCH only mobile%s", VTY_NEWLINE); + return CMD_WARNING; + } + + command = (char *)argv[0]; + if (!strcmp(command, "hangup")) + gcc_bcc_hangup(ms); + else if (!strcmp(command, "leave")) + gcc_leave(ms); + else if (!strcmp(command, "talk")) + gcc_talk(ms); + else if (!strcmp(command, "listen")) + gcc_listen(ms); + else { + if (vty_check_callref(vty, command)) + return CMD_WARNING; + gcc_bcc_call(ms, GSM48_PDISC_GROUP_CC, command); + } + + return CMD_SUCCESS; +} + +/* These commands perform VGCS on given MS without entering the VGCS node. */ +DEFUN(vgcs_direct, vgcs_direct_cmd, "group-call MS_NAME " VGCS_CMDS, + VGCS_STR VGCS_CMDS_TXT) +{ + struct osmocom_ms *ms; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + vty->index = ms; + return vgcs(self, vty, argc - 1, argv + 1); +} + +#define VBS_STR "Make a voice broadcast call\nName of MS (see \"show ms\")\n" +#define VBS_CMDS "(CALLREF|hangup)" +#define VBS_CMDS_TXT \ + "Voice broadcast to call or join\nHangup voice broadcast call" + +/* This command enters VBS call node with given MS. */ +DEFUN(vbs_enter, vbs_enter_cmd, "broadcast-call MS_NAME", + VBS_STR) +{ + struct osmocom_ms *ms; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + vty->index = ms; + vty->node = VBS_NODE; + + return CMD_SUCCESS; +} + +/* These commands perform VBS on VBS node. */ +DEFUN(vbs, vbs_cmd, VBS_CMDS, + VBS_CMDS_TXT) +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set; + const char *command; + + set = &ms->settings; + + if (!set->vbs) { + vty_out(vty, "VBS not supported by this mobile, please enable VBS support%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (set->ch_cap == GSM_CAP_SDCCH) { + vty_out(vty, "ASCI call is not supported for SDCCH only mobile%s", VTY_NEWLINE); + return CMD_WARNING; + } + + command = (char *)argv[0]; + if (!strcmp(command, "hangup")) + gcc_bcc_hangup(ms); + else { + if (vty_check_callref(vty, command)) + return CMD_WARNING; + gcc_bcc_call(ms, GSM48_PDISC_BCAST_CC, command); + } + + return CMD_SUCCESS; +} + +/* These commands perform VBS on given MS without entering the VBS node. */ +DEFUN(vbs_direct, vbs_direct_cmd, "broadcast-call MS_NAME " VBS_CMDS, + VBS_STR VBS_CMDS_TXT) +{ + struct osmocom_ms *ms; + + ms = l23_vty_get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + vty->index = ms; + return vbs(self, vty, argc - 1, argv + 1); +} + +#define TEST_CMD_DESC "Special commands for testing\n" + DEFUN(test_reselection, test_reselection_cmd, "test re-selection NAME", - "Manually trigger cell re-selection\nName of MS (see \"show ms\")") + TEST_CMD_DESC "Manually trigger cell re-selection\n" + "Name of MS (see \"show ms\")") { struct osmocom_ms *ms; struct gsm_settings *set; struct msgb *nmsg; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; set = &ms->settings; @@ -1008,23 +1170,22 @@ DEFUN(delete_forbidden_plmn, delete_forbidden_plmn_cmd, "Mobile Country Code\nMobile Network Code") { struct osmocom_ms *ms; - uint16_t mcc = gsm_input_mcc((char *)argv[1]), - mnc = gsm_input_mnc((char *)argv[2]); + struct osmo_plmn_id plmn; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; - if (mcc == GSM_INPUT_INVALID) { + if (osmo_mcc_from_str(argv[1], &plmn.mcc) < 0) { vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); return CMD_WARNING; } - if (mnc == GSM_INPUT_INVALID) { + if (osmo_mnc_from_str(argv[2], &plmn.mnc, &plmn.mnc_3_digits) < 0) { vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); return CMD_WARNING; } - gsm_subscr_del_forbidden_plmn(&ms->subscr, mcc, mnc); + gsm_subscr_del_forbidden_plmn(&ms->subscr, &plmn); return CMD_SUCCESS; } @@ -1038,7 +1199,7 @@ DEFUN(network_show, network_show_cmd, "network show MS_NAME", struct gsm322_plmn *plmn; struct gsm322_plmn_list *temp; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; set = &ms->settings; @@ -1052,9 +1213,10 @@ DEFUN(network_show, network_show_cmd, "network show MS_NAME", llist_for_each_entry(temp, &plmn->sorted_plmn, entry) vty_out(vty, " Network %s, %s (%s, %s)%s", - gsm_print_mcc(temp->mcc), gsm_print_mnc(temp->mnc), - gsm_get_mcc(temp->mcc), - gsm_get_mnc(temp->mcc, temp->mnc), VTY_NEWLINE); + osmo_mcc_name(temp->plmn.mcc), + osmo_mnc_name(temp->plmn.mnc, temp->plmn.mnc_3_digits), + gsm_get_mcc(temp->plmn.mcc), + gsm_get_mnc(&temp->plmn), VTY_NEWLINE); return CMD_SUCCESS; } @@ -1065,7 +1227,7 @@ DEFUN(network_search, network_search_cmd, "network search MS_NAME", struct osmocom_ms *ms; struct msgb *nmsg; - ms = get_ms(argv[0], vty); + ms = l23_vty_get_ms(argv[0], vty); if (!ms) return CMD_WARNING; @@ -1171,22 +1333,6 @@ DEFUN(cfg_gps_baud, cfg_gps_baud_cmd, "gps baudrate " return CMD_SUCCESS; } -DEFUN(cfg_hide_default, cfg_hide_default_cmd, "hide-default", - "Hide most default values in config to make it more compact") -{ - hide_default = 1; - - return CMD_SUCCESS; -} - -DEFUN(cfg_no_hide_default, cfg_no_hide_default_cmd, "no hide-default", - NO_STR "Show default values in config") -{ - hide_default = 0; - - return CMD_SUCCESS; -} - /* per MS config */ DEFUN(cfg_ms, cfg_ms_cmd, "ms MS_NAME", "Select a mobile station to configure\nName of MS (see \"show ms\")") @@ -1202,7 +1348,7 @@ DEFUN(cfg_ms, cfg_ms_cmd, "ms MS_NAME", } if (!found) { - if (!vty_reading) { + if (!l23_vty_reading) { vty_out(vty, "MS name '%s' does not exits, try " "'ms %s create'%s", argv[0], argv[0], VTY_NEWLINE); @@ -1305,7 +1451,7 @@ DEFUN(cfg_no_ms, cfg_no_ms_cmd, "no ms MS_NAME", #define SUP_WRITE(item, cmd) \ if (sup->item) \ - if (!hide_default || !set->item) \ + if (!l23_vty_hide_default || !set->item) \ vty_out(vty, " %s%s%s", (set->item) ? "" : "no ", \ cmd, VTY_NEWLINE); @@ -1316,58 +1462,48 @@ static void config_write_ms(struct vty *vty, struct osmocom_ms *ms) struct gsm_settings_abbrev *abbrev; vty_out(vty, "ms %s%s", ms->name, VTY_NEWLINE); - vty_out(vty, " layer2-socket %s%s", set->layer2_socket_path, - VTY_NEWLINE); + + l23_vty_config_write_ms_node_contents(vty, ms, " "); + vty_out(vty, " sap-socket %s%s", set->sap_socket_path, VTY_NEWLINE); - switch(set->sim_type) { - case GSM_SIM_TYPE_NONE: - vty_out(vty, " sim none%s", VTY_NEWLINE); + vty_out(vty, " mncc-socket %s%s", set->mncc_socket_path, VTY_NEWLINE); + switch (set->mncc_handler) { + case MNCC_HANDLER_INTERNAL: + vty_out(vty, " mncc-handler internal%s", VTY_NEWLINE); break; - case GSM_SIM_TYPE_L1PHY: - vty_out(vty, " sim reader%s", VTY_NEWLINE); - break; - case GSM_SIM_TYPE_TEST: - vty_out(vty, " sim test%s", VTY_NEWLINE); - break; - case GSM_SIM_TYPE_SAP: - vty_out(vty, " sim sap%s", VTY_NEWLINE); + case MNCC_HANDLER_EXTERNAL: + vty_out(vty, " mncc-handler external%s", VTY_NEWLINE); break; + case MNCC_HANDLER_DUMMY: + vty_out(vty, " mncc-handler dummy%s", VTY_NEWLINE); } vty_out(vty, " network-selection-mode %s%s", (set->plmn_mode == PLMN_MODE_AUTO) ? "auto" : "manual", VTY_NEWLINE); - vty_out(vty, " imei %s %s%s", set->imei, - set->imeisv + strlen(set->imei), VTY_NEWLINE); - if (set->imei_random) - vty_out(vty, " imei-random %d%s", set->imei_random, - VTY_NEWLINE); - else - if (!hide_default) - vty_out(vty, " imei-fixed%s", VTY_NEWLINE); if (set->emergency_imsi[0]) vty_out(vty, " emergency-imsi %s%s", set->emergency_imsi, VTY_NEWLINE); else - if (!hide_default) + if (!l23_vty_hide_default) vty_out(vty, " no emergency-imsi%s", VTY_NEWLINE); if (set->sms_sca[0]) vty_out(vty, " sms-service-center %s%s", set->sms_sca, VTY_NEWLINE); else - if (!hide_default) + if (!l23_vty_hide_default) vty_out(vty, " no sms-service-center%s", VTY_NEWLINE); - if (!hide_default || set->cw) + if (!l23_vty_hide_default || set->cw) vty_out(vty, " %scall-waiting%s", (set->cw) ? "" : "no ", VTY_NEWLINE); - if (!hide_default || set->auto_answer) + if (!l23_vty_hide_default || set->auto_answer) vty_out(vty, " %sauto-answer%s", (set->auto_answer) ? "" : "no ", VTY_NEWLINE); - if (!hide_default || set->force_rekey) + if (!l23_vty_hide_default || set->force_rekey) vty_out(vty, " %sforce-rekey%s", (set->force_rekey) ? "" : "no ", VTY_NEWLINE); - if (!hide_default || set->clip) + if (!l23_vty_hide_default || set->clip) vty_out(vty, " %sclip%s", (set->clip) ? "" : "no ", VTY_NEWLINE); - if (!hide_default || set->clir) + if (!l23_vty_hide_default || set->clir) vty_out(vty, " %sclir%s", (set->clir) ? "" : "no ", VTY_NEWLINE); if (set->alter_tx_power) @@ -1377,25 +1513,25 @@ static void config_write_ms(struct vty *vty, struct osmocom_ms *ms) else vty_out(vty, " tx-power full%s", VTY_NEWLINE); else - if (!hide_default) + if (!l23_vty_hide_default) vty_out(vty, " tx-power auto%s", VTY_NEWLINE); if (set->alter_delay) vty_out(vty, " simulated-delay %d%s", set->alter_delay, VTY_NEWLINE); else - if (!hide_default) + if (!l23_vty_hide_default) vty_out(vty, " no simulated-delay%s", VTY_NEWLINE); if (set->stick) vty_out(vty, " stick %d%s%s", set->stick_arfcn & 1023, (set->stick_arfcn & ARFCN_PCS) ? " pcs" : "", VTY_NEWLINE); else - if (!hide_default) + if (!l23_vty_hide_default) vty_out(vty, " no stick%s", VTY_NEWLINE); - if (!hide_default || set->no_lupd) + if (!l23_vty_hide_default || set->no_lupd) vty_out(vty, " %slocation-updating%s", (set->no_lupd) ? "no " : "", VTY_NEWLINE); - if (!hide_default || set->no_neighbour) + if (!l23_vty_hide_default || set->no_neighbour) vty_out(vty, " %sneighbour-measurement%s", (set->no_neighbour) ? "no " : "", VTY_NEWLINE); if (set->full_v1 || set->full_v2 || set->full_v3) { @@ -1413,7 +1549,7 @@ static void config_write_ms(struct vty *vty, struct osmocom_ms *ms) vty_out(vty, " no codec half-speed%s", VTY_NEWLINE); } if (llist_empty(&set->abbrev)) { - if (!hide_default) + if (!l23_vty_hide_default) vty_out(vty, " no abbrev%s", VTY_NEWLINE); } else { llist_for_each_entry(abbrev, &set->abbrev, list) @@ -1433,32 +1569,32 @@ static void config_write_ms(struct vty *vty, struct osmocom_ms *ms) SUP_WRITE(p_gsm, "p-gsm"); SUP_WRITE(e_gsm, "e-gsm"); SUP_WRITE(r_gsm, "r-gsm"); - SUP_WRITE(pcs, "gsm-850"); + SUP_WRITE(gsm_850, "gsm-850"); SUP_WRITE(gsm_480, "gsm-480"); SUP_WRITE(gsm_450, "gsm-450"); SUP_WRITE(dcs, "dcs"); SUP_WRITE(pcs, "pcs"); if (sup->r_gsm || sup->e_gsm || sup->p_gsm) - if (!hide_default || sup->class_900 != set->class_900) + if (!l23_vty_hide_default || sup->class_900 != set->class_900) vty_out(vty, " class-900 %d%s", set->class_900, VTY_NEWLINE); if (sup->gsm_850) - if (!hide_default || sup->class_850 != set->class_850) + if (!l23_vty_hide_default || sup->class_850 != set->class_850) vty_out(vty, " class-850 %d%s", set->class_850, VTY_NEWLINE); if (sup->gsm_480 || sup->gsm_450) - if (!hide_default || sup->class_400 != set->class_400) + if (!l23_vty_hide_default || sup->class_400 != set->class_400) vty_out(vty, " class-400 %d%s", set->class_400, VTY_NEWLINE); if (sup->dcs) - if (!hide_default || sup->class_dcs != set->class_dcs) + if (!l23_vty_hide_default || sup->class_dcs != set->class_dcs) vty_out(vty, " class-dcs %d%s", set->class_dcs, VTY_NEWLINE); if (sup->pcs) - if (!hide_default || sup->class_pcs != set->class_pcs) + if (!l23_vty_hide_default || sup->class_pcs != set->class_pcs) vty_out(vty, " class-pcs %d%s", set->class_pcs, VTY_NEWLINE); - if (!hide_default || sup->ch_cap != set->ch_cap) { + if (!l23_vty_hide_default || sup->ch_cap != set->ch_cap) { switch (set->ch_cap) { case GSM_CAP_SDCCH: vty_out(vty, " channel-capability sdcch%s", @@ -1479,59 +1615,84 @@ static void config_write_ms(struct vty *vty, struct osmocom_ms *ms) SUP_WRITE(full_v3, "full-speech-v3"); SUP_WRITE(half_v1, "half-speech-v1"); SUP_WRITE(half_v3, "half-speech-v3"); - if (!hide_default || sup->min_rxlev_dbm != set->min_rxlev_dbm) + SUP_WRITE(csd_tch_f144, "full-data-14400"); + SUP_WRITE(csd_tch_f96, "full-data-9600"); + SUP_WRITE(csd_tch_f48, "full-data-4800"); + SUP_WRITE(csd_tch_h48, "half-data-4800"); + SUP_WRITE(csd_tch_f24, "full-data-2400"); + SUP_WRITE(csd_tch_h24, "half-data-2400"); + if (!l23_vty_hide_default || sup->min_rxlev_dbm != set->min_rxlev_dbm) vty_out(vty, " min-rxlev %d%s", set->min_rxlev_dbm, VTY_NEWLINE); - if (!hide_default || sup->dsc_max != set->dsc_max) + if (!l23_vty_hide_default || sup->dsc_max != set->dsc_max) vty_out(vty, " dsc-max %d%s", set->dsc_max, VTY_NEWLINE); - if (!hide_default || set->skip_max_per_band) + if (!l23_vty_hide_default || set->skip_max_per_band) vty_out(vty, " %sskip-max-per-band%s", (set->skip_max_per_band) ? "" : "no ", VTY_NEWLINE); - vty_out(vty, " test-sim%s", VTY_NEWLINE); - vty_out(vty, " imsi %s%s", set->test_imsi, VTY_NEWLINE); - switch (set->test_ki_type) { - case OSMO_AUTH_ALG_XOR: - vty_out(vty, " ki xor %s%s", - osmo_hexdump(set->test_ki, 12), VTY_NEWLINE); + SUP_WRITE(vgcs, "vgcs"); + SUP_WRITE(vbs, "vbs"); + if (!l23_vty_hide_default || set->any_timeout != MOB_C7_DEFLT_ANY_TIMEOUT) + vty_out(vty, " c7-any-timeout %d%s", + set->any_timeout, VTY_NEWLINE); + if (!l23_vty_hide_default || !set->uplink_release_local) + vty_out(vty, " %suplink-release-local%s", + (!set->uplink_release_local) ? "no " : "", VTY_NEWLINE); + if (!l23_vty_hide_default || set->asci_allow_any) + vty_out(vty, " %sasci-allow-any%s", + (set->asci_allow_any) ? "" : "no ", VTY_NEWLINE); + + vty_out(vty, " tch-voice%s", VTY_NEWLINE); + vty_out(vty, " io-handler %s%s", + tch_voice_io_handler_name(set->tch_voice.io_handler), VTY_NEWLINE); + if (set->tch_voice.io_handler == TCH_VOICE_IOH_GAPK) { + vty_out(vty, " io-tch-format %s%s", + tch_voice_io_format_name(set->tch_voice.io_format), VTY_NEWLINE); + vty_out(vty, " alsa-output-dev %s%s", + &set->tch_voice.alsa_output_dev[0], VTY_NEWLINE); + vty_out(vty, " alsa-input-dev %s%s", + &set->tch_voice.alsa_input_dev[0], VTY_NEWLINE); + } + + vty_out(vty, " tch-data%s", VTY_NEWLINE); + vty_out(vty, " io-handler %s%s", + tch_data_io_handler_name(set->tch_data.io_handler), VTY_NEWLINE); + vty_out(vty, " io-tch-format %s%s", + tch_data_io_format_name(set->tch_data.io_format), VTY_NEWLINE); + if (set->tch_data.io_handler == TCH_DATA_IOH_UNIX_SOCK) { + vty_out(vty, " unix-socket %s%s", + set->tch_data.unix_socket_path, VTY_NEWLINE); + } + + vty_out(vty, " call-params type-rate %d%s", + (int)set->call_params.data.type_rate, VTY_NEWLINE); + switch (set->call_params.data.transp) { + case GSM48_BCAP_TR_TR_PREF: + vty_out(vty, " call-params ce transparent prefer%s", VTY_NEWLINE); + break; + case GSM48_BCAP_TR_TRANSP: + vty_out(vty, " call-params ce transparent%s", VTY_NEWLINE); break; - case OSMO_AUTH_ALG_COMP128v1: - vty_out(vty, " ki comp128 %s%s", - osmo_hexdump(set->test_ki, 16), VTY_NEWLINE); + case GSM48_BCAP_TR_RLP_PREF: + vty_out(vty, " call-params ce non-transparent prefer%s", VTY_NEWLINE); + break; + case GSM48_BCAP_TR_RLP: + vty_out(vty, " call-params ce non-transparent%s", VTY_NEWLINE); break; } - if (!hide_default || set->test_barr) - vty_out(vty, " %sbarred-access%s", - (set->test_barr) ? "" : "no ", VTY_NEWLINE); - if (set->test_rplmn_valid) { - vty_out(vty, " rplmn %s %s", - gsm_print_mcc(set->test_rplmn_mcc), - gsm_print_mnc(set->test_rplmn_mnc)); - if (set->test_lac > 0x0000 && set->test_lac < 0xfffe) { - vty_out(vty, " 0x%04x", set->test_lac); - if (set->test_tmsi != 0xffffffff) { - vty_out(vty, " 0x%08x", set->test_tmsi); - if (set->test_imsi_attached) - vty_out(vty, " attached"); - } - } - vty_out(vty, "%s", VTY_NEWLINE); - } else - if (!hide_default) - vty_out(vty, " no rplmn%s", VTY_NEWLINE); - if (!hide_default || set->test_always) - vty_out(vty, " hplmn-search %s%s", - (set->test_always) ? "everywhere" : "foreign-country", - VTY_NEWLINE); - if (!hide_default || set->any_timeout != MOB_C7_DEFLT_ANY_TIMEOUT) - vty_out(vty, " c7-any-timeout %d%s", - set->any_timeout, VTY_NEWLINE); - - /* no shutdown must be written to config, because shutdown is default */ - vty_out(vty, " %sshutdown%s", (ms->shutdown != MS_SHUTDOWN_NONE) ? "" : "no ", + vty_out(vty, " call-params %s%s", + set->call_params.data.is_async ? "async" : "sync", VTY_NEWLINE); + vty_out(vty, " call-params async nr-stop-bits %u%s", + set->call_params.data.nr_stop_bits, VTY_NEWLINE); + vty_out(vty, " call-params async nr-data-bits %u%s", + set->call_params.data.nr_data_bits, VTY_NEWLINE); + vty_out(vty, " call-params async parity %s%s", + get_value_string(async_parity_names, set->call_params.data.parity), VTY_NEWLINE); + if (ms->lua_script) vty_out(vty, " lua-script %s%s", ms->lua_script, VTY_NEWLINE); - vty_out(vty, "!%s", VTY_NEWLINE); + + l23_vty_config_write_ms_node_contents_final(vty, ms, " "); } static int config_write(struct vty *vty) @@ -1549,7 +1710,7 @@ static int config_write(struct vty *vty) vty_out(vty, "%sgps enable%s", (g.enable) ? "" : "no ", VTY_NEWLINE); vty_out(vty, "!%s", VTY_NEWLINE); - vty_out(vty, "%shide-default%s", (hide_default) ? "": "no ", + vty_out(vty, "%shide-default%s", (l23_vty_hide_default) ? "" : "no ", VTY_NEWLINE); vty_out(vty, "!%s", VTY_NEWLINE); @@ -1569,60 +1730,75 @@ DEFUN(cfg_ms_show_this, cfg_ms_show_this_cmd, "show this", return CMD_SUCCESS; } -DEFUN(cfg_ms_layer2, cfg_ms_layer2_cmd, "layer2-socket PATH", - "Define socket path to connect between layer 2 and layer 1\n" - "Unix socket, default '/tmp/osmocom_l2'") +DEFUN(cfg_ms_sap, cfg_ms_sap_cmd, "sap-socket PATH", + "Define socket path to connect to SIM reader\n" + "Unix socket, default '/tmp/osmocom_sap'") { struct osmocom_ms *ms = vty->index; struct gsm_settings *set = &ms->settings; - OSMO_STRLCPY_ARRAY(set->layer2_socket_path, argv[0]); + OSMO_STRLCPY_ARRAY(set->sap_socket_path, argv[0]); vty_restart(vty, ms); return CMD_SUCCESS; } -DEFUN(cfg_ms_sap, cfg_ms_sap_cmd, "sap-socket PATH", - "Define socket path to connect to SIM reader\n" - "Unix socket, default '/tmp/osmocom_sap'") +DEFUN(cfg_ms_mncc_sock, cfg_ms_mncc_sock_cmd, "mncc-socket PATH", + "Define socket path for MNCC interface\n" + "UNIX socket path (default '/tmp/ms_mncc_' + MS_NAME)") { struct osmocom_ms *ms = vty->index; struct gsm_settings *set = &ms->settings; - OSMO_STRLCPY_ARRAY(set->sap_socket_path, argv[0]); + OSMO_STRLCPY_ARRAY(set->mncc_socket_path, argv[0]); vty_restart(vty, ms); return CMD_SUCCESS; } -DEFUN(cfg_ms_sim, cfg_ms_sim_cmd, "sim (none|reader|test|sap)", - "Set SIM card to attach when powering on\nAttach no SIM\n" - "Attach SIM from reader\nAttach bulit in test SIM\n" - "Attach SIM over SAP interface") +DEFUN(cfg_ms_mncc_handler, cfg_ms_mncc_handler_cmd, + "mncc-handler (internal|external|dummy)", + "Set MNCC (Call Control) handler\n" + "Built-in MNCC handler (default)\n" + "External MNCC application via UNIX-socket (e.g. LCR)\n" + "Dummy MNCC handler (no Call Control)\n") { struct osmocom_ms *ms = vty->index; struct gsm_settings *set = &ms->settings; switch (argv[0][0]) { - case 'n': - set->sim_type = GSM_SIM_TYPE_NONE; - break; - case 'r': - set->sim_type = GSM_SIM_TYPE_L1PHY; + case 'i': + if (set->ch_cap == GSM_CAP_SDCCH) { /* SDCCH only */ + vty_out(vty, "TCH support is disabled, " + "check 'channel-capability' param%s", VTY_NEWLINE); + return CMD_WARNING; + } + set->mncc_handler = MNCC_HANDLER_INTERNAL; break; - case 't': - set->sim_type = GSM_SIM_TYPE_TEST; + case 'e': + set->mncc_handler = MNCC_HANDLER_EXTERNAL; break; - case 's': - set->sim_type = GSM_SIM_TYPE_SAP; + case 'd': + set->mncc_handler = MNCC_HANDLER_DUMMY; break; default: - vty_out(vty, "unknown SIM type%s", VTY_NEWLINE); - return CMD_WARNING; + /* Shall not happen */ + OSMO_ASSERT(0); } vty_restart_if_started(vty, ms); + return CMD_SUCCESS; +} +DEFUN(cfg_ms_no_mncc_handler, cfg_ms_no_mncc_handler_cmd, + "no mncc-handler", NO_STR "Disable Call Control") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->mncc_handler = MNCC_HANDLER_DUMMY; + + vty_restart_if_started(vty, ms); return CMD_SUCCESS; } @@ -1652,63 +1828,14 @@ DEFUN(cfg_ms_mode, cfg_ms_mode_cmd, "network-selection-mode (auto|manual)", return CMD_SUCCESS; } -DEFUN(cfg_ms_imei, cfg_ms_imei_cmd, "imei IMEI [SV]", - "Set IMEI (enter without control digit)\n15 Digits IMEI\n" - "Software version digit") -{ - struct osmocom_ms *ms = vty->index; - struct gsm_settings *set = &ms->settings; - char *error, *sv = "0"; - - if (argc >= 2) - sv = (char *)argv[1]; - - error = gsm_check_imei(argv[0], sv); - if (error) { - vty_out(vty, "%s%s", error, VTY_NEWLINE); - return CMD_WARNING; - } - - strcpy(set->imei, argv[0]); - strcpy(set->imeisv, argv[0]); - strcpy(set->imeisv + 15, sv); - - return CMD_SUCCESS; -} - -DEFUN(cfg_ms_imei_fixed, cfg_ms_imei_fixed_cmd, "imei-fixed", - "Use fixed IMEI on every power on") -{ - struct osmocom_ms *ms = vty->index; - struct gsm_settings *set = &ms->settings; - - set->imei_random = 0; - - return CMD_SUCCESS; -} - -DEFUN(cfg_ms_imei_random, cfg_ms_imei_random_cmd, "imei-random <0-15>", - "Use random IMEI on every power on\n" - "Number of trailing digits to randomize") -{ - struct osmocom_ms *ms = vty->index; - struct gsm_settings *set = &ms->settings; - - set->imei_random = atoi(argv[0]); - - return CMD_SUCCESS; -} - DEFUN(cfg_ms_emerg_imsi, cfg_ms_emerg_imsi_cmd, "emergency-imsi IMSI", "Use special IMSI for emergency calls\n15 digits IMSI") { struct osmocom_ms *ms = vty->index; struct gsm_settings *set = &ms->settings; - char *error; - error = gsm_check_imsi(argv[0]); - if (error) { - vty_out(vty, "%s%s", error, VTY_NEWLINE); + if (!osmo_imsi_str_valid(argv[0])) { + vty_out(vty, "Wrong IMSI format%s", VTY_NEWLINE); return CMD_WARNING; } strcpy(set->emergency_imsi, argv[0]); @@ -2199,6 +2326,48 @@ DEFUN(cfg_ms_any_timeout, cfg_ms_any_timeout_cmd, "c7-any-timeout <0-255>", return CMD_SUCCESS; } +DEFUN(cfg_ms_no_uplink_release_local, cfg_ms_no_uplink_release_local_cmd, "no uplink-release-local", + NO_STR "Release L2 on uplink of VGCS channel normally. Release locally when UPLINK FREE is received.") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->uplink_release_local = false; + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_uplink_release_local, cfg_ms_uplink_release_local_cmd, "uplink-release-local", + "Release L2 on uplink of VGCS channel locally after receiving UPLINK FREE.") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->uplink_release_local = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_asci_allow_any, cfg_ms_asci_allow_any_cmd, "asci-allow-any", + "Allow any ASCI related call feature, even if service is limited or SIM invalid.") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->asci_allow_any = true; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_no_asci_allow_any, cfg_ms_no_asci_allow_any_cmd, "no asci-allow-any", + NO_STR "Do not allow any ASCI related call feature, if service is not normal.") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->asci_allow_any = false; + + return CMD_SUCCESS; +} + static int config_write_dummy(struct vty *vty) { return CMD_SUCCESS; @@ -2213,15 +2382,17 @@ DEFUN(cfg_ms_support, cfg_ms_support_cmd, "support", return CMD_SUCCESS; } -#define SUP_EN(cfg, cfg_cmd, item, cmd, desc, restart) \ -DEFUN(cfg, cfg_cmd, cmd, "Enable " desc "support") \ +#define SUP_EN(item, cmd, desc, restart) \ +DEFUN(cfg_ms_sup_en_##item, \ + cfg_ms_sup_en_##item##_cmd, \ + cmd, "Enable " desc "support") \ { \ struct osmocom_ms *ms = vty->index; \ struct gsm_settings *set = &ms->settings; \ struct gsm_support *sup = &ms->support; \ if (!sup->item) { \ vty_out(vty, desc " not supported%s", VTY_NEWLINE); \ - if (vty_reading) \ + if (l23_vty_reading) \ return CMD_SUCCESS; \ return CMD_WARNING; \ } \ @@ -2231,15 +2402,17 @@ DEFUN(cfg, cfg_cmd, cmd, "Enable " desc "support") \ return CMD_SUCCESS; \ } -#define SUP_DI(cfg, cfg_cmd, item, cmd, desc, restart) \ -DEFUN(cfg, cfg_cmd, "no " cmd, NO_STR "Disable " desc " support") \ +#define SUP_DI(item, cmd, desc, restart) \ +DEFUN(cfg_ms_sup_di_##item, \ + cfg_ms_sup_di_##item##_cmd, \ + "no " cmd, NO_STR "Disable " desc " support") \ { \ struct osmocom_ms *ms = vty->index; \ struct gsm_settings *set = &ms->settings; \ struct gsm_support *sup = &ms->support; \ if (!sup->item) { \ vty_out(vty, desc " not supported%s", VTY_NEWLINE); \ - if (vty_reading) \ + if (l23_vty_reading) \ return CMD_SUCCESS; \ return CMD_WARNING; \ } \ @@ -2249,8 +2422,15 @@ DEFUN(cfg, cfg_cmd, "no " cmd, NO_STR "Disable " desc " support") \ return CMD_SUCCESS; \ } -#define SET_EN(cfg, cfg_cmd, item, cmd, desc, restart) \ -DEFUN(cfg, cfg_cmd, cmd, "Enable " desc "support") \ +#define SUP_EN_DI(item, cmd, desc, restart) \ + SUP_EN(item, cmd, desc, restart); \ + SUP_DI(item, cmd, desc, restart) + + +#define SET_EN(item, cmd, desc, restart) \ +DEFUN(cfg_ms_set_en_##item, \ + cfg_ms_set_en_##item##_cmd, \ + cmd, "Enable " desc "support") \ { \ struct osmocom_ms *ms = vty->index; \ struct gsm_settings *set = &ms->settings; \ @@ -2260,8 +2440,10 @@ DEFUN(cfg, cfg_cmd, cmd, "Enable " desc "support") \ return CMD_SUCCESS; \ } -#define SET_DI(cfg, cfg_cmd, item, cmd, desc, restart) \ -DEFUN(cfg, cfg_cmd, "no " cmd, NO_STR "Disable " desc " support") \ +#define SET_DI(item, cmd, desc, restart) \ +DEFUN(cfg_ms_set_di_##item, \ + cfg_ms_set_di_##item##_cmd, \ + "no " cmd, NO_STR "Disable " desc " support") \ { \ struct osmocom_ms *ms = vty->index; \ struct gsm_settings *set = &ms->settings; \ @@ -2271,52 +2453,28 @@ DEFUN(cfg, cfg_cmd, "no " cmd, NO_STR "Disable " desc " support") \ return CMD_SUCCESS; \ } -SET_EN(cfg_ms_sup_dtmf, cfg_ms_sup_dtmf_cmd, cc_dtmf, "dtmf", "DTMF", 0); -SET_DI(cfg_ms_sup_no_dtmf, cfg_ms_sup_no_dtmf_cmd, cc_dtmf, "dtmf", "DTMF", 0); -SUP_EN(cfg_ms_sup_sms, cfg_ms_sup_sms_cmd, sms_ptp, "sms", "SMS", 0); -SUP_DI(cfg_ms_sup_no_sms, cfg_ms_sup_no_sms_cmd, sms_ptp, "sms", "SMS", 0); -SUP_EN(cfg_ms_sup_a5_1, cfg_ms_sup_a5_1_cmd, a5_1, "a5/1", "A5/1", 0); -SUP_DI(cfg_ms_sup_no_a5_1, cfg_ms_sup_no_a5_1_cmd, a5_1, "a5/1", "A5/1", 0); -SUP_EN(cfg_ms_sup_a5_2, cfg_ms_sup_a5_2_cmd, a5_2, "a5/2", "A5/2", 0); -SUP_DI(cfg_ms_sup_no_a5_2, cfg_ms_sup_no_a5_2_cmd, a5_2, "a5/2", "A5/2", 0); -SUP_EN(cfg_ms_sup_a5_3, cfg_ms_sup_a5_3_cmd, a5_3, "a5/3", "A5/3", 0); -SUP_DI(cfg_ms_sup_no_a5_3, cfg_ms_sup_no_a5_3_cmd, a5_3, "a5/3", "A5/3", 0); -SUP_EN(cfg_ms_sup_a5_4, cfg_ms_sup_a5_4_cmd, a5_4, "a5/4", "A5/4", 0); -SUP_DI(cfg_ms_sup_no_a5_4, cfg_ms_sup_no_a5_4_cmd, a5_4, "a5/4", "A5/4", 0); -SUP_EN(cfg_ms_sup_a5_5, cfg_ms_sup_a5_5_cmd, a5_5, "a5/5", "A5/5", 0); -SUP_DI(cfg_ms_sup_no_a5_5, cfg_ms_sup_no_a5_5_cmd, a5_5, "a5/5", "A5/5", 0); -SUP_EN(cfg_ms_sup_a5_6, cfg_ms_sup_a5_6_cmd, a5_6, "a5/6", "A5/6", 0); -SUP_DI(cfg_ms_sup_no_a5_6, cfg_ms_sup_no_a5_6_cmd, a5_6, "a5/6", "A5/6", 0); -SUP_EN(cfg_ms_sup_a5_7, cfg_ms_sup_a5_7_cmd, a5_7, "a5/7", "A5/7", 0); -SUP_DI(cfg_ms_sup_no_a5_7, cfg_ms_sup_no_a5_7_cmd, a5_7, "a5/7", "A5/7", 0); -SUP_EN(cfg_ms_sup_p_gsm, cfg_ms_sup_p_gsm_cmd, p_gsm, "p-gsm", "P-GSM (900)", - 1); -SUP_DI(cfg_ms_sup_no_p_gsm, cfg_ms_sup_no_p_gsm_cmd, p_gsm, "p-gsm", - "P-GSM (900)", 1); -SUP_EN(cfg_ms_sup_e_gsm, cfg_ms_sup_e_gsm_cmd, e_gsm, "e-gsm", "E-GSM (850)", - 1); -SUP_DI(cfg_ms_sup_no_e_gsm, cfg_ms_sup_no_e_gsm_cmd, e_gsm, "e-gsm", - "E-GSM (850)", 1); -SUP_EN(cfg_ms_sup_r_gsm, cfg_ms_sup_r_gsm_cmd, r_gsm, "r-gsm", "R-GSM (850)", - 1); -SUP_DI(cfg_ms_sup_no_r_gsm, cfg_ms_sup_no_r_gsm_cmd, r_gsm, "r-gsm", - "R-GSM (850)", 1); -SUP_EN(cfg_ms_sup_dcs, cfg_ms_sup_dcs_cmd, dcs, "dcs", "DCS (1800)", 1); -SUP_DI(cfg_ms_sup_no_dcs, cfg_ms_sup_no_dcs_cmd, dcs, "dcs", "DCS (1800)", 1); -SUP_EN(cfg_ms_sup_gsm_850, cfg_ms_sup_gsm_850_cmd, gsm_850, "gsm-850", - "GSM 850", 1); -SUP_DI(cfg_ms_sup_no_gsm_850, cfg_ms_sup_no_gsm_850_cmd, gsm_850, "gsm-850", - "GSM 850", 1); -SUP_EN(cfg_ms_sup_pcs, cfg_ms_sup_pcs_cmd, pcs, "pcs", "PCS (1900)", 1); -SUP_DI(cfg_ms_sup_no_pcs, cfg_ms_sup_no_pcs_cmd, pcs, "pcs", "PCS (1900)", 1); -SUP_EN(cfg_ms_sup_gsm_480, cfg_ms_sup_gsm_480_cmd, gsm_480, "gsm-480", - "GSM 480", 1); -SUP_DI(cfg_ms_sup_no_gsm_480, cfg_ms_sup_no_gsm_480_cmd, gsm_480, "gsm-480", - "GSM 480", 1); -SUP_EN(cfg_ms_sup_gsm_450, cfg_ms_sup_gsm_450_cmd, gsm_450, "gsm-450", - "GSM 450", 1); -SUP_DI(cfg_ms_sup_no_gsm_450, cfg_ms_sup_no_gsm_450_cmd, gsm_450, "gsm-450", - "GSM 450", 1); +#define SET_EN_DI(item, cmd, desc, restart) \ + SET_EN(item, cmd, desc, restart); \ + SET_DI(item, cmd, desc, restart) + + +SET_EN_DI(cc_dtmf, "dtmf", "DTMF", 0); +SUP_EN_DI(sms_ptp, "sms", "SMS", 0); +SUP_EN_DI(a5_1, "a5/1", "A5/1", 0); +SUP_EN_DI(a5_2, "a5/2", "A5/2", 0); +SUP_EN_DI(a5_3, "a5/3", "A5/3", 0); +SUP_EN_DI(a5_4, "a5/4", "A5/4", 0); +SUP_EN_DI(a5_5, "a5/5", "A5/5", 0); +SUP_EN_DI(a5_6, "a5/6", "A5/6", 0); +SUP_EN_DI(a5_7, "a5/7", "A5/7", 0); +SUP_EN_DI(p_gsm, "p-gsm", "P-GSM (900)", 1); +SUP_EN_DI(e_gsm, "e-gsm", "E-GSM (850)", 1); +SUP_EN_DI(r_gsm, "r-gsm", "R-GSM (850)", 1); +SUP_EN_DI(dcs, "dcs", "DCS (1800)", 1); +SUP_EN_DI(gsm_850, "gsm-850", "GSM 850", 1); +SUP_EN_DI(pcs, "pcs", "PCS (1900)", 1); +SUP_EN_DI(gsm_480, "gsm-480", "GSM 480", 1); +SUP_EN_DI(gsm_450, "gsm-450", "GSM 450", 1); DEFUN(cfg_ms_sup_class_900, cfg_ms_sup_class_900_cmd, "class-900 (1|2|3|4|5)", "Select power class for GSM 900\n" @@ -2332,7 +2490,7 @@ DEFUN(cfg_ms_sup_class_900, cfg_ms_sup_class_900_cmd, "class-900 (1|2|3|4|5)", set->class_900 = atoi(argv[0]); - if (set->class_900 < sup->class_900 && !vty_reading) + if (set->class_900 < sup->class_900 && !l23_vty_reading) vty_out(vty, "Note: You selected a higher class than supported " " by hardware!%s", VTY_NEWLINE); @@ -2353,7 +2511,7 @@ DEFUN(cfg_ms_sup_class_850, cfg_ms_sup_class_850_cmd, "class-850 (1|2|3|4|5)", set->class_850 = atoi(argv[0]); - if (set->class_850 < sup->class_850 && !vty_reading) + if (set->class_850 < sup->class_850 && !l23_vty_reading) vty_out(vty, "Note: You selected a higher class than supported " " by hardware!%s", VTY_NEWLINE); @@ -2374,7 +2532,7 @@ DEFUN(cfg_ms_sup_class_400, cfg_ms_sup_class_400_cmd, "class-400 (1|2|3|4|5)", set->class_400 = atoi(argv[0]); - if (set->class_400 < sup->class_400 && !vty_reading) + if (set->class_400 < sup->class_400 && !l23_vty_reading) vty_out(vty, "Note: You selected a higher class than supported " " by hardware!%s", VTY_NEWLINE); @@ -2394,7 +2552,7 @@ DEFUN(cfg_ms_sup_class_dcs, cfg_ms_sup_class_dcs_cmd, "class-dcs (1|2|3)", set->class_dcs = atoi(argv[0]); if (((set->class_dcs + 1) & 3) < ((sup->class_dcs + 1) & 3) - && !vty_reading) + && !l23_vty_reading) vty_out(vty, "Note: You selected a higher class than supported " " by hardware!%s", VTY_NEWLINE); @@ -2414,7 +2572,7 @@ DEFUN(cfg_ms_sup_class_pcs, cfg_ms_sup_class_pcs_cmd, "class-pcs (1|2|3)", set->class_pcs = atoi(argv[0]); if (((set->class_pcs + 1) & 3) < ((sup->class_pcs + 1) & 3) - && !vty_reading) + && !l23_vty_reading) vty_out(vty, "Note: You selected a higher class than supported " " by hardware!%s", VTY_NEWLINE); @@ -2440,7 +2598,7 @@ DEFUN(cfg_ms_sup_ch_cap, cfg_ms_sup_ch_cap_cmd, else ch_cap = GSM_CAP_SDCCH; - if (ch_cap > sup->ch_cap && !vty_reading) { + if (ch_cap > sup->ch_cap && !l23_vty_reading) { vty_out(vty, "You selected an higher capability than supported " " by hardware!%s", VTY_NEWLINE); return CMD_WARNING; @@ -2455,26 +2613,18 @@ DEFUN(cfg_ms_sup_ch_cap, cfg_ms_sup_ch_cap_cmd, return CMD_SUCCESS; } -SUP_EN(cfg_ms_sup_full_v1, cfg_ms_sup_full_v1_cmd, full_v1, "full-speech-v1", - "Full rate speech V1", 0); -SUP_DI(cfg_ms_sup_no_full_v1, cfg_ms_sup_no_full_v1_cmd, full_v1, - "full-speech-v1", "Full rate speech V1", 0); -SUP_EN(cfg_ms_sup_full_v2, cfg_ms_sup_full_v2_cmd, full_v2, "full-speech-v2", - "Full rate speech V2 (EFR)", 0); -SUP_DI(cfg_ms_sup_no_full_v2, cfg_ms_sup_no_full_v2_cmd, full_v2, - "full-speech-v2", "Full rate speech V2 (EFR)", 0); -SUP_EN(cfg_ms_sup_full_v3, cfg_ms_sup_full_v3_cmd, full_v3, "full-speech-v3", - "Full rate speech V3 (AMR)", 0); -SUP_DI(cfg_ms_sup_no_full_v3, cfg_ms_sup_no_full_v3_cmd, full_v3, - "full-speech-v3", "Full rate speech V3 (AMR)", 0); -SUP_EN(cfg_ms_sup_half_v1, cfg_ms_sup_half_v1_cmd, half_v1, "half-speech-v1", - "Half rate speech V1", 0); -SUP_DI(cfg_ms_sup_no_half_v1, cfg_ms_sup_no_half_v1_cmd, half_v1, - "half-speech-v1", "Half rate speech V1", 0); -SUP_EN(cfg_ms_sup_half_v3, cfg_ms_sup_half_v3_cmd, half_v3, "half-speech-v3", - "Half rate speech V3 (AMR)", 0); -SUP_DI(cfg_ms_sup_no_half_v3, cfg_ms_sup_no_half_v3_cmd, half_v3, - "half-speech-v3", "Half rate speech V3 (AMR)", 0); +SUP_EN_DI(full_v1, "full-speech-v1", "Full rate speech V1", 0); +SUP_EN_DI(full_v2, "full-speech-v2", "Full rate speech V2 (EFR)", 0); +SUP_EN_DI(full_v3, "full-speech-v3", "Full rate speech V3 (AMR)", 0); +SUP_EN_DI(half_v1, "half-speech-v1", "Half rate speech V1", 0); +SUP_EN_DI(half_v3, "half-speech-v3", "Half rate speech V3 (AMR)", 0); + +SUP_EN_DI(csd_tch_f144, "full-data-14400", "CSD TCH/F14.4", 0); +SUP_EN_DI(csd_tch_f96, "full-data-9600", "CSD TCH/F9.6", 0); +SUP_EN_DI(csd_tch_f48, "full-data-4800", "CSD TCH/F4.8", 0); +SUP_EN_DI(csd_tch_h48, "half-data-4800", "CSD TCH/H4.8", 0); +SUP_EN_DI(csd_tch_f24, "full-data-2400", "CSD TCH/F2.4", 0); +SUP_EN_DI(csd_tch_h24, "half-data-2400", "CSD TCH/H2.4", 0); DEFUN(cfg_ms_sup_min_rxlev, cfg_ms_sup_min_rxlev_cmd, "min-rxlev <-110--47>", "Set the minimum receive level to select a cell\n" @@ -2526,251 +2676,184 @@ DEFUN(cfg_ms_sup_no_skip_max_per_band, cfg_ms_sup_no_skip_max_per_band_cmd, return CMD_SUCCESS; } -/* per testsim config */ -DEFUN(cfg_ms_testsim, cfg_ms_testsim_cmd, "test-sim", - "Configure test SIM emulation") -{ - vty->node = TESTSIM_NODE; +SUP_EN_DI(vgcs, "vgcs", "Voice Group Call Service (VGCS)", 0); +SUP_EN_DI(vbs, "vbs", "Voice Broadcast Service (VBS)", 0); +/* TCH config */ +DEFUN(cfg_ms_tch_voice, + cfg_ms_tch_voice_cmd, + "tch-voice", "Configure TCH (Traffic CHannel) params for voice calls\n") +{ + vty->node = TCH_VOICE_NODE; return CMD_SUCCESS; } -DEFUN(cfg_test_imsi, cfg_test_imsi_cmd, "imsi IMSI", - "Set IMSI on test card\n15 digits IMSI") +ALIAS_DEPRECATED(cfg_ms_tch_voice, /* alias to 'tch-voice' */ + cfg_ms_audio_cmd, + "audio", "(deprecated alias for 'tch-voice')\n"); + +DEFUN(cfg_ms_tch_voice_io_handler, cfg_ms_tch_voice_io_handler_cmd, + "io-handler (none|gapk|l1phy|mncc-sock|loopback)", + "Set TCH frame I/O handler for voice calls\n" + "No handler, drop TCH frames (default)\n" + "libosmo-gapk based I/O handler (requires ALSA)\n" + "L1 PHY (e.g. Calypso DSP in Motorola C1xx phones)\n" + "External MNCC application (e.g. LCR) via MNCC socket\n" + "Return TCH frame payload back to sender\n") { - struct osmocom_ms *ms = vty->index; + int val = get_string_value(tch_voice_io_handler_names, argv[0]); + struct osmocom_ms *ms = (struct osmocom_ms *)vty->index; struct gsm_settings *set = &ms->settings; - char *error = gsm_check_imsi(argv[0]); - if (error) { - vty_out(vty, "%s%s", error, VTY_NEWLINE); - return CMD_WARNING; + OSMO_ASSERT(val >= 0); + + if (val == TCH_VOICE_IOH_MNCC_SOCK) { + if (ms->settings.mncc_handler != MNCC_HANDLER_INTERNAL) { + vty_out(vty, "TCH voice I/O handler 'mncc-sock' can only be used " + "with MNCC handler 'external'%s", VTY_NEWLINE); + return CMD_WARNING; + } } - strcpy(set->test_imsi, argv[0]); +#ifndef WITH_GAPK_IO + if (val == TCH_VOICE_IOH_GAPK) { + vty_out(vty, "GAPK I/O is not compiled in (--with-gapk-io)%s", VTY_NEWLINE); + return CMD_WARNING; + } +#endif - vty_restart_if_started(vty, ms); + set->tch_voice.io_handler = (enum tch_voice_io_handler)val; return CMD_SUCCESS; } -#define HEX_STR "\nByte as two digits hexadecimal" -DEFUN(cfg_test_ki_xor, cfg_test_ki_xor_cmd, "ki xor HEX HEX HEX HEX HEX HEX " - "HEX HEX HEX HEX HEX HEX", - "Set Key (Ki) on test card\nUse XOR algorithm" HEX_STR HEX_STR HEX_STR - HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR) +DEFUN(cfg_ms_tch_voice_no_io_handler, cfg_ms_tch_voice_no_io_handler_cmd, + "no io-handler", NO_STR "Disable TCH frame handling for voice calls\n") { - struct osmocom_ms *ms = vty->index; + struct osmocom_ms *ms = (struct osmocom_ms *)vty->index; struct gsm_settings *set = &ms->settings; - uint8_t ki[12]; - const char *p; - int i; - for (i = 0; i < 12; i++) { - p = argv[i]; - if (!strncmp(p, "0x", 2)) - p += 2; - if (strlen(p) != 2) { - vty_out(vty, "Expecting two digits hex value (with or " - "without 0x in front)%s", VTY_NEWLINE); - return CMD_WARNING; - } - ki[i] = strtoul(p, NULL, 16); - } + set->tch_voice.io_handler = TCH_VOICE_IOH_NONE; - set->test_ki_type = OSMO_AUTH_ALG_XOR; - memcpy(set->test_ki, ki, 12); return CMD_SUCCESS; } -DEFUN(cfg_test_ki_comp128, cfg_test_ki_comp128_cmd, "ki comp128 HEX HEX HEX " - "HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX", - "Set Key (Ki) on test card\nUse XOR algorithm" HEX_STR HEX_STR HEX_STR - HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR - HEX_STR HEX_STR HEX_STR HEX_STR) +DEFUN(cfg_ms_tch_voice_io_tch_format, cfg_ms_tch_voice_io_tch_format_cmd, + "io-tch-format (rtp|ti)", + "Set TCH I/O frame format used by the L1 PHY (for GAPK only)\n" + "RTP format (RFC3551 for FR/EFR, RFC5993 for HR, RFC4867 for AMR)\n" + "Texas Instruments format, used by Calypso based phones (e.g. Motorola C1xx)\n") { - struct osmocom_ms *ms = vty->index; + int val = get_string_value(tch_voice_io_format_names, argv[0]); + struct osmocom_ms *ms = (struct osmocom_ms *)vty->index; struct gsm_settings *set = &ms->settings; - uint8_t ki[16]; - const char *p; - int i; - for (i = 0; i < 16; i++) { - p = argv[i]; - if (!strncmp(p, "0x", 2)) - p += 2; - if (strlen(p) != 2) { - vty_out(vty, "Expecting two digits hex value (with or " - "without 0x in front)%s", VTY_NEWLINE); - return CMD_WARNING; - } - ki[i] = strtoul(p, NULL, 16); + OSMO_ASSERT(val >= 0); + + if (set->tch_voice.io_handler != TCH_VOICE_IOH_GAPK) { + vty_out(vty, "This parameter is only valid for GAPK%s", VTY_NEWLINE); + return CMD_WARNING; } - set->test_ki_type = OSMO_AUTH_ALG_COMP128v1; - memcpy(set->test_ki, ki, 16); + set->tch_voice.io_format = val; + return CMD_SUCCESS; } -DEFUN(cfg_test_barr, cfg_test_barr_cmd, "barred-access", - "Allow access to barred cells") +DEFUN(cfg_ms_tch_voice_alsa_out_dev, cfg_ms_tch_voice_alsa_out_dev_cmd, + "alsa-output-dev (default|NAME)", + "Set ALSA output (playback) device name (for GAPK only)\n" + "Default system playback device (default)\n" + "Name of a custom playback device") { struct osmocom_ms *ms = vty->index; struct gsm_settings *set = &ms->settings; - set->test_barr = 1; + OSMO_STRLCPY_ARRAY(set->tch_voice.alsa_output_dev, argv[0]); return CMD_SUCCESS; } -DEFUN(cfg_test_no_barr, cfg_test_no_barr_cmd, "no barred-access", - NO_STR "Deny access to barred cells") +DEFUN(cfg_ms_tch_voice_alsa_in_dev, cfg_ms_tch_voice_alsa_in_dev_cmd, + "alsa-input-dev (default|NAME)", + "Set ALSA input (capture) device name (for GAPK only)\n" + "Default system recording device (default)\n" + "Name of a custom recording device") { struct osmocom_ms *ms = vty->index; struct gsm_settings *set = &ms->settings; - set->test_barr = 0; + OSMO_STRLCPY_ARRAY(set->tch_voice.alsa_input_dev, argv[0]); return CMD_SUCCESS; } -DEFUN(cfg_test_no_rplmn, cfg_test_no_rplmn_cmd, "no rplmn", - NO_STR "Unset Registered PLMN") +DEFUN(cfg_ms_tch_data, + cfg_ms_tch_data_cmd, + "tch-data", "Configure TCH (Traffic CHannel) params for data calls\n") { - struct osmocom_ms *ms = vty->index; - struct gsm_settings *set = &ms->settings; - - set->test_rplmn_valid = 0; - - vty_restart_if_started(vty, ms); - + vty->node = TCH_DATA_NODE; return CMD_SUCCESS; } -static int _test_rplmn_cmd(struct vty *vty, int argc, const char *argv[], - int attached) +DEFUN(cfg_ms_tch_data_io_handler, + cfg_ms_tch_data_io_handler_cmd, + "io-handler (none|unix-sock|loopback)", + "Set TCH frame I/O handler for data calls\n" + "No handler, drop TCH frames (default)\n" + "UNIX socket (path set by 'data-unix-socket')\n" + "Return TCH frame payload back to sender\n") { - struct osmocom_ms *ms = vty->index; + int val = get_string_value(tch_data_io_handler_names, argv[0]); + struct osmocom_ms *ms = (struct osmocom_ms *)vty->index; struct gsm_settings *set = &ms->settings; - uint16_t mcc = gsm_input_mcc((char *)argv[0]), - mnc = gsm_input_mnc((char *)argv[1]); - - if (mcc == GSM_INPUT_INVALID) { - vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); - return CMD_WARNING; - } - if (mnc == GSM_INPUT_INVALID) { - vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); - return CMD_WARNING; - } - set->test_rplmn_valid = 1; - set->test_rplmn_mcc = mcc; - set->test_rplmn_mnc = mnc; - if (argc >= 3) - set->test_lac = strtoul(argv[2], NULL, 16); - else - set->test_lac = 0xfffe; - - if (argc >= 4) - set->test_tmsi = strtoul(argv[3], NULL, 16); - else - set->test_tmsi = 0xffffffff; - - if (attached) - set->test_imsi_attached = 1; - else - set->test_imsi_attached = 0; - - vty_restart_if_started(vty, ms); + OSMO_ASSERT(val >= 0); + set->tch_data.io_handler = (enum tch_data_io_handler)val; return CMD_SUCCESS; } -DEFUN(cfg_test_rplmn, cfg_test_rplmn_cmd, - "rplmn MCC MNC [LAC] [TMSI]", - "Set Registered PLMN\nMobile Country Code\nMobile Network Code\n" - "Optionally set location area code\n" - "Optionally set current assigned TMSI") -{ - return _test_rplmn_cmd(vty, argc, argv, 0); -} - -DEFUN(cfg_test_rplmn_att, cfg_test_rplmn_att_cmd, - "rplmn MCC MNC LAC TMSI attached", - "Set Registered PLMN\nMobile Country Code\nMobile Network Code\n" - "Set location area code\nSet current assigned TMSI\n" - "Indicate to MM that card is already attached") -{ - return _test_rplmn_cmd(vty, argc, argv, 1); -} - -DEFUN(cfg_test_hplmn, cfg_test_hplmn_cmd, "hplmn-search (everywhere|foreign-country)", - "Set Home PLMN search mode\n" - "Search for HPLMN when on any other network\n" - "Search for HPLMN when in a different country") +DEFUN(cfg_ms_tch_data_no_io_handler, + cfg_ms_tch_data_no_io_handler_cmd, + "no io-handler", NO_STR "Disable TCH frame handling for data calls\n") { - struct osmocom_ms *ms = vty->index; + struct osmocom_ms *ms = (struct osmocom_ms *)vty->index; struct gsm_settings *set = &ms->settings; - switch (argv[0][0]) { - case 'e': - set->test_always = 1; - break; - case 'f': - set->test_always = 0; - break; - } - - vty_restart_if_started(vty, ms); + set->tch_data.io_handler = TCH_DATA_IOH_NONE; return CMD_SUCCESS; } -DEFUN(cfg_no_shutdown, cfg_ms_no_shutdown_cmd, "no shutdown", - NO_STR "Activate and run MS") +DEFUN(cfg_ms_tch_data_io_tch_format, + cfg_ms_tch_data_io_tch_format_cmd, + "io-tch-format (osmo|ti)", + "Set TCH I/O frame format used by the L1 PHY\n" + "Osmocom format used by both trxcon and viryphy (default)\n" + "Texas Instruments format, used by Calypso based phones (e.g. Motorola C1xx)\n") { - struct osmocom_ms *ms = vty->index; - char *other_name = NULL; - int rc; + int val = get_string_value(tch_data_io_format_names, argv[0]); + struct osmocom_ms *ms = (struct osmocom_ms *)vty->index; + struct gsm_settings *set = &ms->settings; - rc = mobile_start(ms, &other_name); - switch (rc) { - case -1: - vty_out(vty, "Cannot start MS '%s', because MS '%s' " - "use the same layer2-socket.%sPlease shutdown " - "MS '%s' first.%s", ms->name, other_name, - VTY_NEWLINE, other_name, VTY_NEWLINE); - return CMD_WARNING; - case -2: - vty_out(vty, "Cannot start MS '%s', because MS '%s' " - "use the same sap-socket.%sPlease shutdown " - "MS '%s' first.%s", ms->name, other_name, - VTY_NEWLINE, other_name, VTY_NEWLINE); - return CMD_WARNING; - case -3: - vty_out(vty, "Connection to layer 1 failed!%s", - VTY_NEWLINE); - return CMD_WARNING; - } + OSMO_ASSERT(val >= 0); + set->tch_data.io_format = val; return CMD_SUCCESS; } -DEFUN(cfg_shutdown, cfg_ms_shutdown_cmd, "shutdown", - "Shut down and deactivate MS") +DEFUN(cfg_ms_tch_data_unix_sock, + cfg_ms_tch_data_unix_sock_cmd, + "unix-socket PATH", + "Define UNIX socket path (for 'io-handler unix-sock')\n" + "UNIX socket path (default '/tmp/ms_data_' + MS_NAME)\n") { - struct osmocom_ms *ms = vty->index; - mobile_stop(ms, 0); - return CMD_SUCCESS; -} + struct osmocom_ms *ms = (struct osmocom_ms *)vty->index; + struct gsm_settings *set = &ms->settings; -DEFUN(cfg_shutdown_force, cfg_ms_shutdown_force_cmd, "shutdown force", - "Shut down and deactivate MS\nDo not perform IMSI detach") -{ - struct osmocom_ms *ms = vty->index; + OSMO_STRLCPY_ARRAY(set->tch_data.unix_socket_path, argv[0]); - mobile_stop(ms, 1); return CMD_SUCCESS; } @@ -2798,24 +2881,6 @@ DEFUN(cfg_ms_no_script_load_run, cfg_ms_no_script_load_run_cmd, "no lua-script", return CMD_SUCCESS; } -int ms_vty_go_parent(struct vty *vty) -{ - switch (vty->node) { - case MS_NODE: - vty->node = CONFIG_NODE; - vty->index = NULL; - break; - case TESTSIM_NODE: - case SUPPORT_NODE: - vty->node = MS_NODE; - break; - default: - vty->node = CONFIG_NODE; - } - - return vty->node; -} - DEFUN(off, off_cmd, "off", "Turn mobiles off (shutdown) and exit") { @@ -2824,43 +2889,138 @@ DEFUN(off, off_cmd, "off", return CMD_SUCCESS; } +/* run ms instance, if layer1 is available */ +static int l23_vty_signal_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct osmobb_l23_vty_sig_data *d = signal_data; + struct vty *vty = d->vty; + char *other_name = NULL; + int rc; + + if (subsys != SS_L23_VTY) + return 0; + + switch (signal) { + case S_L23_VTY_MS_START: + rc = mobile_start(d->ms_start.ms, &other_name); + switch (rc) { + case -1: + vty_out(vty, "Cannot start MS '%s', because MS '%s' " + "use the same layer2-socket.%sPlease shutdown " + "MS '%s' first.%s", d->ms_start.ms->name, other_name, + VTY_NEWLINE, other_name, VTY_NEWLINE); + break; + case -2: + vty_out(vty, "Cannot start MS '%s', because MS '%s' " + "use the same sap-socket.%sPlease shutdown " + "MS '%s' first.%s", d->ms_start.ms->name, other_name, + VTY_NEWLINE, other_name, VTY_NEWLINE); + break; + case -3: + vty_out(vty, "Connection to layer 1 failed!%s", + VTY_NEWLINE); + break; + } + d->ms_start.rc = (rc == 0) ? CMD_SUCCESS : CMD_WARNING; + break; + case S_L23_VTY_MS_STOP: + mobile_stop(d->ms_stop.ms, d->ms_stop.force); + d->ms_start.rc = CMD_SUCCESS; + break; + } + return 0; +} + + #define SUP_NODE(item) \ install_element(SUPPORT_NODE, &cfg_ms_sup_item_cmd); int ms_vty_init(void) { + int rc; + + _data_type_rate_cmd_string(NULL, &cfg_ms_tch_data_cp_type_rate_cmd); + _data_type_rate_cmd_string(NULL, &call_params_data_type_rate_cmd); + + cfg_ms_tch_data_cp_type_rate_cmd.doc = + vty_cmd_string_from_valstr(NULL, + data_type_rate_descs, + CFG_TCH_DATA_CALL_PARAMS_CMD_DESC + "Type and rate (values like in AT+CBST; " + "see 3GPP TS 27.007, section 6.7)\n", + "\n", "", 0); + call_params_data_type_rate_cmd.doc = + vty_cmd_string_from_valstr(NULL, + data_type_rate_descs, + CALL_PARAMS_DATA_CMD_DESC + "Type and rate (values like in AT+CBST; " + "see 3GPP TS 27.007, section 6.7)\n", + "\n", "", 0); + + cfg_ms_tch_data_cp_async_parity_cmd.string = + vty_cmd_string_from_valstr(NULL, + async_parity_names, + CFG_TCH_DATA_CALL_PARAMS_CMD " " + CALL_PARAMS_ASYNC_PARITY_CMD " (", + "|", ")", 0); + call_params_data_async_parity_cmd.string = + vty_cmd_string_from_valstr(NULL, + async_parity_names, + CALL_PARAMS_DATA_CMD " " + CALL_PARAMS_ASYNC_PARITY_CMD " (", + "|", ")", 0); + + cfg_ms_tch_data_cp_async_parity_cmd.doc = + vty_cmd_string_from_valstr(NULL, + async_parity_descs, + cfg_ms_tch_data_cp_async_parity_cmd.doc, + "\n", "", 0); + call_params_data_async_parity_cmd.doc = + vty_cmd_string_from_valstr(NULL, + async_parity_descs, + call_params_data_async_parity_cmd.doc, + "\n", "", 0); + + if ((rc = l23_vty_init(config_write, l23_vty_signal_cb)) < 0) + return rc; + install_element_ve(&show_ms_cmd); - install_element_ve(&show_subscr_cmd); - install_element_ve(&show_support_cmd); install_element_ve(&show_cell_cmd); install_element_ve(&show_cell_si_cmd); install_element_ve(&show_nbcells_cmd); install_element_ve(&show_ba_cmd); install_element_ve(&show_forb_la_cmd); install_element_ve(&show_forb_plmn_cmd); + install_element_ve(&show_asci_calls_cmd); + install_element_ve(&show_asci_neighbors_cmd); install_element_ve(&monitor_network_cmd); install_element_ve(&no_monitor_network_cmd); install_element(ENABLE_NODE, &off_cmd); - install_element(ENABLE_NODE, &sim_test_cmd); - install_element(ENABLE_NODE, &sim_test_att_cmd); - install_element(ENABLE_NODE, &sim_sap_cmd); - install_element(ENABLE_NODE, &sim_reader_cmd); - install_element(ENABLE_NODE, &sim_remove_cmd); - install_element(ENABLE_NODE, &sim_pin_cmd); - install_element(ENABLE_NODE, &sim_disable_pin_cmd); - install_element(ENABLE_NODE, &sim_enable_pin_cmd); - install_element(ENABLE_NODE, &sim_change_pin_cmd); - install_element(ENABLE_NODE, &sim_unblock_pin_cmd); - install_element(ENABLE_NODE, &sim_lai_cmd); install_element(ENABLE_NODE, &network_search_cmd); install_element(ENABLE_NODE, &network_show_cmd); install_element(ENABLE_NODE, &network_select_cmd); + install_element(ENABLE_NODE, &call_num_cmd); install_element(ENABLE_NODE, &call_cmd); install_element(ENABLE_NODE, &call_retr_cmd); install_element(ENABLE_NODE, &call_dtmf_cmd); + install_element(ENABLE_NODE, &call_params_data_type_rate_cmd); + install_element(ENABLE_NODE, &call_params_data_ce_cmd); + install_element(ENABLE_NODE, &call_params_data_sync_async_cmd); + install_element(ENABLE_NODE, &call_params_data_async_nr_stop_bits_cmd); + install_element(ENABLE_NODE, &call_params_data_async_nr_data_bits_cmd); + install_element(ENABLE_NODE, &call_params_data_async_parity_cmd); install_element(ENABLE_NODE, &sms_cmd); install_element(ENABLE_NODE, &service_cmd); + install_element(ENABLE_NODE, &vgcs_enter_cmd); + install_element(ENABLE_NODE, &vgcs_direct_cmd); + install_node(&vgcs_node, config_write_dummy); + install_element(VGCS_NODE, &vgcs_cmd); + install_element(ENABLE_NODE, &vbs_enter_cmd); + install_element(ENABLE_NODE, &vbs_direct_cmd); + install_node(&vbs_node, config_write_dummy); + install_element(VBS_NODE, &vbs_cmd); install_element(ENABLE_NODE, &test_reselection_cmd); install_element(ENABLE_NODE, &delete_forbidden_plmn_cmd); @@ -2872,22 +3032,18 @@ int ms_vty_init(void) install_element(CONFIG_NODE, &cfg_gps_enable_cmd); install_element(CONFIG_NODE, &cfg_no_gps_enable_cmd); - install_element(CONFIG_NODE, &cfg_hide_default_cmd); - install_element(CONFIG_NODE, &cfg_no_hide_default_cmd); - install_element(CONFIG_NODE, &cfg_ms_cmd); install_element(CONFIG_NODE, &cfg_ms_create_cmd); install_element(CONFIG_NODE, &cfg_ms_rename_cmd); install_element(CONFIG_NODE, &cfg_no_ms_cmd); - install_node(&ms_node, config_write); + + /* MS_NODE is installed by l23_vty_init(). App specific commands below: */ install_element(MS_NODE, &cfg_ms_show_this_cmd); - install_element(MS_NODE, &cfg_ms_layer2_cmd); install_element(MS_NODE, &cfg_ms_sap_cmd); - install_element(MS_NODE, &cfg_ms_sim_cmd); + install_element(MS_NODE, &cfg_ms_mncc_sock_cmd); + install_element(MS_NODE, &cfg_ms_mncc_handler_cmd); + install_element(MS_NODE, &cfg_ms_no_mncc_handler_cmd); install_element(MS_NODE, &cfg_ms_mode_cmd); - install_element(MS_NODE, &cfg_ms_imei_cmd); - install_element(MS_NODE, &cfg_ms_imei_fixed_cmd); - install_element(MS_NODE, &cfg_ms_imei_random_cmd); install_element(MS_NODE, &cfg_ms_no_emerg_imsi_cmd); install_element(MS_NODE, &cfg_ms_emerg_imsi_cmd); install_element(MS_NODE, &cfg_ms_no_sms_sca_cmd); @@ -2917,122 +3073,112 @@ int ms_vty_init(void) install_element(MS_NODE, &cfg_ms_no_codec_half_cmd); install_element(MS_NODE, &cfg_ms_abbrev_cmd); install_element(MS_NODE, &cfg_ms_no_abbrev_cmd); - install_element(MS_NODE, &cfg_ms_testsim_cmd); + install_element(MS_NODE, &cfg_ms_tch_voice_cmd); + install_element(MS_NODE, &cfg_ms_audio_cmd); + install_element(MS_NODE, &cfg_ms_tch_data_cmd); install_element(MS_NODE, &cfg_ms_neighbour_cmd); install_element(MS_NODE, &cfg_ms_no_neighbour_cmd); install_element(MS_NODE, &cfg_ms_any_timeout_cmd); install_element(MS_NODE, &cfg_ms_sms_store_cmd); install_element(MS_NODE, &cfg_ms_no_sms_store_cmd); + install_element(MS_NODE, &cfg_ms_uplink_release_local_cmd); + install_element(MS_NODE, &cfg_ms_no_uplink_release_local_cmd); + install_element(MS_NODE, &cfg_ms_asci_allow_any_cmd); + install_element(MS_NODE, &cfg_ms_no_asci_allow_any_cmd); install_element(MS_NODE, &cfg_ms_support_cmd); install_node(&support_node, config_write_dummy); - install_element(SUPPORT_NODE, &cfg_ms_sup_dtmf_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_dtmf_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_sms_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_sms_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_a5_1_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_1_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_a5_2_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_2_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_a5_3_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_3_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_a5_4_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_4_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_a5_5_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_5_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_a5_6_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_6_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_a5_7_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_7_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_p_gsm_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_p_gsm_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_e_gsm_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_e_gsm_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_r_gsm_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_r_gsm_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_dcs_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_dcs_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_gsm_850_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_gsm_850_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_pcs_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_pcs_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_gsm_480_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_gsm_480_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_gsm_450_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_gsm_450_cmd); + install_element(SUPPORT_NODE, &cfg_ms_set_en_cc_dtmf_cmd); + install_element(SUPPORT_NODE, &cfg_ms_set_di_cc_dtmf_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_sms_ptp_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_sms_ptp_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_a5_1_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_a5_1_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_a5_2_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_a5_2_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_a5_3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_a5_3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_a5_4_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_a5_4_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_a5_5_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_a5_5_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_a5_6_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_a5_6_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_a5_7_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_a5_7_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_p_gsm_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_p_gsm_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_e_gsm_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_e_gsm_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_r_gsm_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_r_gsm_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_dcs_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_dcs_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_gsm_850_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_gsm_850_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_pcs_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_pcs_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_gsm_480_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_gsm_480_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_gsm_450_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_gsm_450_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_class_900_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_class_dcs_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_class_850_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_class_pcs_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_class_400_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_ch_cap_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_full_v1_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_full_v1_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_full_v2_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_full_v2_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_full_v3_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_full_v3_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_half_v1_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_half_v1_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_half_v3_cmd); - install_element(SUPPORT_NODE, &cfg_ms_sup_no_half_v3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_full_v1_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_full_v1_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_full_v2_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_full_v2_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_full_v3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_full_v3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_half_v1_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_half_v1_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_half_v3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_half_v3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_csd_tch_f144_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_csd_tch_f144_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_csd_tch_f96_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_csd_tch_f96_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_csd_tch_f48_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_csd_tch_f48_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_csd_tch_h48_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_csd_tch_h48_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_csd_tch_f24_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_csd_tch_f24_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_csd_tch_h24_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_csd_tch_h24_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_min_rxlev_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_dsc_max_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_skip_max_per_band_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_no_skip_max_per_band_cmd); - install_node(&testsim_node, config_write_dummy); - install_element(TESTSIM_NODE, &cfg_test_imsi_cmd); - install_element(TESTSIM_NODE, &cfg_test_ki_xor_cmd); - install_element(TESTSIM_NODE, &cfg_test_ki_comp128_cmd); - install_element(TESTSIM_NODE, &cfg_test_barr_cmd); - install_element(TESTSIM_NODE, &cfg_test_no_barr_cmd); - install_element(TESTSIM_NODE, &cfg_test_no_rplmn_cmd); - install_element(TESTSIM_NODE, &cfg_test_rplmn_cmd); - install_element(TESTSIM_NODE, &cfg_test_rplmn_att_cmd); - install_element(TESTSIM_NODE, &cfg_test_hplmn_cmd); - install_element(MS_NODE, &cfg_ms_shutdown_cmd); - install_element(MS_NODE, &cfg_ms_shutdown_force_cmd); - install_element(MS_NODE, &cfg_ms_no_shutdown_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_vgcs_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_vgcs_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_vbs_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_vbs_cmd); install_element(MS_NODE, &cfg_ms_script_load_run_cmd); install_element(MS_NODE, &cfg_ms_no_script_load_run_cmd); - /* Register the talloc context introspection command */ - osmo_talloc_vty_add_cmds(); + install_node(&tch_voice_node, config_write_dummy); + install_element(TCH_VOICE_NODE, &cfg_ms_tch_voice_io_handler_cmd); + install_element(TCH_VOICE_NODE, &cfg_ms_tch_voice_no_io_handler_cmd); + install_element(TCH_VOICE_NODE, &cfg_ms_tch_voice_io_tch_format_cmd); + install_element(TCH_VOICE_NODE, &cfg_ms_tch_voice_alsa_out_dev_cmd); + install_element(TCH_VOICE_NODE, &cfg_ms_tch_voice_alsa_in_dev_cmd); + + install_node(&tch_data_node, config_write_dummy); + install_element(TCH_DATA_NODE, &cfg_ms_tch_data_io_handler_cmd); + install_element(TCH_DATA_NODE, &cfg_ms_tch_data_no_io_handler_cmd); + install_element(TCH_DATA_NODE, &cfg_ms_tch_data_io_tch_format_cmd); + install_element(TCH_DATA_NODE, &cfg_ms_tch_data_unix_sock_cmd); + install_element(TCH_DATA_NODE, &cfg_ms_tch_data_cp_type_rate_cmd); + install_element(TCH_DATA_NODE, &cfg_ms_tch_data_cp_ce_cmd); + install_element(TCH_DATA_NODE, &cfg_ms_tch_data_cp_sync_async_cmd); + install_element(TCH_DATA_NODE, &cfg_ms_tch_data_cp_async_nr_stop_bits_cmd); + install_element(TCH_DATA_NODE, &cfg_ms_tch_data_cp_async_nr_data_bits_cmd); + install_element(TCH_DATA_NODE, &cfg_ms_tch_data_cp_async_parity_cmd); return 0; } -void vty_notify(struct osmocom_ms *ms, const char *fmt, ...) -{ - struct telnet_connection *connection; - char buffer[1000]; - va_list args; - struct vty *vty; - - if (fmt) { - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer) - 1, fmt, args); - buffer[sizeof(buffer) - 1] = '\0'; - va_end(args); - - if (!buffer[0]) - return; - } - - llist_for_each_entry(connection, &active_connections, entry) { - vty = connection->vty; - if (!vty) - continue; - if (!fmt) { - vty_out(vty, "%s%% (MS %s)%s", VTY_NEWLINE, ms->name, - VTY_NEWLINE); - continue; - } - if (buffer[strlen(buffer) - 1] == '\n') { - buffer[strlen(buffer) - 1] = '\0'; - vty_out(vty, "%% %s%s", buffer, VTY_NEWLINE); - buffer[strlen(buffer)] = '\n'; - } else - vty_out(vty, "%% %s", buffer); - } -} - diff --git a/src/host/layer23/src/modem/Makefile.am b/src/host/layer23/src/modem/Makefile.am new file mode 100644 index 00000000..935722b1 --- /dev/null +++ b/src/host/layer23/src/modem/Makefile.am @@ -0,0 +1,41 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOGPRSRLCMAC_CFLAGS) \ + $(LIBOSMOGPRSLLC_CFLAGS) \ + $(LIBOSMOGPRSSNDCP_CFLAGS) \ + $(LIBOSMOGPRSGMM_CFLAGS) \ + $(LIBOSMOGPRSSM_CFLAGS) \ + $(NULL) + +bin_PROGRAMS = modem + +modem_SOURCES = \ + $(top_srcdir)/src/common/main.c \ + app_modem.c \ + gmm.c \ + grr.c \ + llc.c \ + rlcmac.c \ + sm.c \ + sndcp.c \ + vty.c \ + $(NULL) +modem_LDADD = \ + $(top_builddir)/src/common/liblayer23.a \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOGPRSRLCMAC_LIBS) \ + $(LIBOSMOGPRSLLC_LIBS) \ + $(LIBOSMOGPRSSNDCP_LIBS) \ + $(LIBOSMOGPRSGMM_LIBS) \ + $(LIBOSMOGPRSSM_LIBS) \ + $(NULL) diff --git a/src/host/layer23/src/modem/app_modem.c b/src/host/layer23/src/modem/app_modem.c new file mode 100644 index 00000000..2787ec70 --- /dev/null +++ b/src/host/layer23/src/modem/app_modem.c @@ -0,0 +1,348 @@ +/* modem app (gprs) */ + +/* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * 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/lienses/>. + * + */ + +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/signal.h> +#include <osmocom/core/application.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/tun.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/lapdm.h> +#include <osmocom/vty/vty.h> + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/settings.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/l23_app.h> +#include <osmocom/bb/common/l1l2_interface.h> +#include <osmocom/bb/common/apn.h> +#include <osmocom/bb/modem/rlcmac.h> +#include <osmocom/bb/modem/llc.h> +#include <osmocom/bb/modem/sndcp.h> +#include <osmocom/bb/modem/gmm.h> +#include <osmocom/bb/modem/sm.h> +#include <osmocom/bb/modem/vty.h> +#include <osmocom/bb/modem/grr.h> +#include <osmocom/bb/modem/modem.h> + +#include <l1ctl_proto.h> + +#include "config.h" + +struct modem_app app_data; + +int modem_gprs_attach_if_needed(struct osmocom_ms *ms) +{ + int rc; + + if (app_data.modem_state != MODEM_ST_IDLE) + return 0; + + if (ms->grr_fi->state == GRR_ST_PACKET_NOT_READY) + return 0; + + if (!ms->subscr.sim_valid) + return 0; + + app_data.modem_state = MODEM_ST_ATTACHING; + rc = modem_gmm_gmmreg_attach_req(ms); + if (rc < 0) + app_data.modem_state = MODEM_ST_IDLE; + return rc; +} + +/* Local network-originated IP packet, needs to be sent via SNDCP/LLC (GPRS) towards GSM network */ +static int modem_tun_data_ind_cb(struct osmo_tundev *tun, struct msgb *msg) +{ + struct osmobb_apn *apn = (struct osmobb_apn *)osmo_tundev_get_priv_data(tun); + struct osmo_sockaddr dst; + struct iphdr *iph = (struct iphdr *)msgb_data(msg); + struct ip6_hdr *ip6h = (struct ip6_hdr *)msgb_data(msg); + size_t pkt_len = msgb_length(msg); + uint8_t pref_offset; + char addrstr[INET6_ADDRSTRLEN]; + int rc = 0; + + switch (iph->version) { + case 4: + if (pkt_len < sizeof(*iph) || pkt_len < 4*iph->ihl) + return -1; + dst.u.sin.sin_family = AF_INET; + dst.u.sin.sin_addr.s_addr = iph->daddr; + break; + case 6: + /* Due to the fact that 3GPP requires an allocation of a + * /64 prefix to each MS, we must instruct + * ippool_getip() below to match only the leading /64 + * prefix, i.e. the first 8 bytes of the address. If the ll addr + * is used, then the match should be done on the trailing 64 + * bits. */ + dst.u.sin6.sin6_family = AF_INET6; + pref_offset = IN6_IS_ADDR_LINKLOCAL(&ip6h->ip6_dst) ? 8 : 0; + memcpy(&dst.u.sin6.sin6_addr, ((uint8_t *)&ip6h->ip6_dst) + pref_offset, 8); + break; + default: + LOGTUN(LOGL_NOTICE, tun, "non-IPv%u packet received\n", iph->version); + rc = -1; + goto free_ret; + } + + LOGPAPN(LOGL_DEBUG, apn, "system wants to transmit IPv%c pkt to %s (%zu bytes)\n", + iph->version == 4 ? '4' : '6', osmo_sockaddr_ntop(&dst.u.sa, addrstr), pkt_len); + + switch (apn->pdp.pdp_addr_ietf_type) { + case OSMO_GPRS_SM_PDP_ADDR_IETF_IPV4: + if (iph->version != 4) { + LOGPAPN(LOGL_NOTICE, apn, + "system wants to transmit IPv%u pkt to %s (%zu bytes) on IPv4-only PDP Ctx, discarding!\n", + iph->version, osmo_sockaddr_ntop(&dst.u.sa, addrstr), pkt_len); + goto free_ret; + } + break; + case OSMO_GPRS_SM_PDP_ADDR_IETF_IPV6: + if (iph->version != 6) { + LOGPAPN(LOGL_NOTICE, apn, + "system wants to transmit IPv%u pkt to %s (%zu bytes) on IPv6-only PDP Ctx, discarding!\n", + iph->version, osmo_sockaddr_ntop(&dst.u.sa, addrstr), pkt_len); + goto free_ret; + } + break; + default: /* OSMO_GPRS_SM_PDP_ADDR_IETF_IPV4V6 */ + /* Allow any */ + break; + } + + rc = modem_sndcp_sn_unitdata_req(apn, msgb_data(msg), pkt_len); + +free_ret: + msgb_free(msg); + return rc; +} + +void layer3_app_reset(void) +{ + memset(&app_data, 0x00, sizeof(app_data)); +} + +/* SIM becomes ATTACHED/DETACHED, or answers a request */ +static int modem_l23_subscr_signal_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct osmocom_ms *ms; + struct osmobb_l23_subscr_sim_auth_resp_sig_data *sim_auth_resp; + + OSMO_ASSERT(subsys == SS_L23_SUBSCR); + + switch (signal) { + case S_L23_SUBSCR_SIM_ATTACHED: + ms = signal_data; + modem_gprs_attach_if_needed(ms); + break; + case S_L23_SUBSCR_SIM_DETACHED: + ms = signal_data; + modem_gmm_gmmreg_detach_req(ms); + break; + case S_L23_SUBSCR_SIM_AUTH_RESP: + sim_auth_resp = signal_data; + ms = sim_auth_resp->ms; + modem_gmm_gmmreg_sim_auth_rsp(ms, sim_auth_resp->sres, + ms->subscr.key, + sizeof(ms->subscr.key)); + break; + default: + OSMO_ASSERT(0); + } + + return 0; +} + +int modem_sync_to_cell(struct osmocom_ms *ms) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + + if (cs->sync_pending) { + LOGP(DCS, LOGL_INFO, "Sync to ARFCN=%s, but there is a sync " + "already pending\n", gsm_print_arfcn(cs->arfcn)); + return 0; + } + + cs->sync_pending = true; + l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL); + return l1ctl_tx_fbsb_req(ms, cs->arfcn, + L1CTL_FBSB_F_FB01SB, 100, 0, + cs->ccch_mode, dbm2rxlev(-85)); +} + +static int global_signal_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct osmocom_ms *ms; + struct gsm322_cellsel *cs; + struct osmobb_fbsb_res *fr; + + if (subsys != SS_L1CTL) + return 0; + + switch (signal) { + case S_L1CTL_RESET: + LOGP(DCS, LOGL_NOTICE, "S_L1CTL_RESET\n"); + ms = signal_data; + ms->cellsel.arfcn = ms->test_arfcn; + if (ms->started) + break; + layer3_app_reset(); + app_data.ms = ms; + + /* insert test card, if enabled */ + if (ms->settings.sim_type != GSM_SIM_TYPE_NONE) { + /* insert sim card */ + gsm_subscr_insert(ms); + } else { + /* No SIM, trigger PLMN selection process. + * FIXME: not implemented. Code in mobile needs to be + * moved to common/ and reuse it here. + */ + } + + ms->started = true; + return l1ctl_tx_fbsb_req(ms, ms->test_arfcn, + L1CTL_FBSB_F_FB01SB, 100, 0, + CCCH_MODE_NONE, dbm2rxlev(-85)); + case S_L1CTL_FBSB_RESP: + LOGP(DCS, LOGL_NOTICE, "S_L1CTL_FBSB_RESP\n"); + fr = signal_data; + ms = fr->ms; + cs = &ms->cellsel; + cs->sync_pending = false; + break; + case S_L1CTL_FBSB_ERR: + LOGP(DCS, LOGL_NOTICE, "S_L1CTL_FBSB_ERR\n"); + fr = signal_data; + ms = fr->ms; + cs = &ms->cellsel; + cs->sync_pending = false; + /* Retry: */ + modem_sync_to_cell(ms); + break; + } + + return 0; +} + +static int _modem_start(void) +{ + int rc; + + rc = layer2_open(app_data.ms, app_data.ms->settings.layer2_socket_path); + if (rc < 0) { + fprintf(stderr, "Failed during layer2_open()\n"); + return rc; + } + + l1ctl_tx_reset_req(app_data.ms, L1CTL_RES_T_FULL); + return 0; +} + +/* global exit */ +static int _modem_exit(void) +{ + osmo_signal_unregister_handler(SS_L23_SUBSCR, &modem_l23_subscr_signal_cb, NULL); + osmo_signal_unregister_handler(SS_GLOBAL, &global_signal_cb, NULL); + return 0; +} + +int l23_app_init(void) +{ + int rc; + + l23_app_start = _modem_start; + l23_app_exit = _modem_exit; + + log_set_category_filter(osmo_stderr_target, DLGLOBAL, 1, LOGL_DEBUG); + log_set_category_filter(osmo_stderr_target, DLCSN1, 1, LOGL_DEBUG); + log_set_category_filter(osmo_stderr_target, DRR, 1, LOGL_INFO); + + app_data.ms = osmocom_ms_alloc(l23_ctx, "1"); + OSMO_ASSERT(app_data.ms); + + if ((rc = modem_rlcmac_init(app_data.ms))) { + LOGP(DRLCMAC, LOGL_FATAL, "Failed initializing RLC/MAC layer\n"); + return rc; + } + + if ((rc = modem_llc_init(app_data.ms, NULL))) { + LOGP(DLLC, LOGL_FATAL, "Failed initializing LLC layer\n"); + return rc; + } + + if ((rc = modem_sndcp_init(app_data.ms))) { + LOGP(DSNDCP, LOGL_FATAL, "Failed initializing SNDCP layer\n"); + return rc; + } + + if ((rc = modem_gmm_init(app_data.ms))) { + LOGP(DGMM, LOGL_FATAL, "Failed initializing GMM layer\n"); + return rc; + } + + if ((rc = modem_sm_init(app_data.ms))) { + LOGP(DSM, LOGL_FATAL, "Failed initializing SM layer\n"); + return rc; + } + + /* TODO: move to a separate function */ + app_data.ms->grr_fi = osmo_fsm_inst_alloc(&grr_fsm_def, NULL, + app_data.ms, LOGL_DEBUG, + app_data.ms->name); + OSMO_ASSERT(app_data.ms->grr_fi != NULL); + + osmo_signal_register_handler(SS_L1CTL, &global_signal_cb, NULL); + osmo_signal_register_handler(SS_L23_SUBSCR, &modem_l23_subscr_signal_cb, NULL); + lapdm_channel_set_l3(&app_data.ms->lapdm_channel, &modem_grr_rslms_cb, app_data.ms); + return 0; +} + +static struct vty_app_info _modem_vty_info = { + .name = "OsmocomBB(modem)", + .version = PACKAGE_VERSION, + .go_parent_cb = modem_vty_go_parent, +}; + +const struct l23_app_info l23_app_info = { + .copyright = "Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>\n", + .opt_supported = L23_OPT_ARFCN | L23_OPT_TAP | L23_OPT_VTY | L23_OPT_DBG, + .vty_info = &_modem_vty_info, + .vty_init = modem_vty_init, + .tun_data_ind_cb = modem_tun_data_ind_cb, +}; diff --git a/src/host/layer23/src/modem/gmm.c b/src/host/layer23/src/modem/gmm.c new file mode 100644 index 00000000..c0e69368 --- /dev/null +++ b/src/host/layer23/src/modem/gmm.c @@ -0,0 +1,271 @@ +/* GPRS GMM interfaces as per 3GPP TS 24.008, TS 24.007 */ +/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * 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/lienses/>. + * + */ + +#include <stdbool.h> +#include <string.h> +#include <stdint.h> +#include <errno.h> +#include <stdio.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/prim.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/tun.h> +#include <osmocom/gsm/protocol/gsm_04_08_gprs.h> + +#include <osmocom/gprs/llc/llc.h> +#include <osmocom/gprs/llc/llc_prim.h> +#include <osmocom/gprs/gmm/gmm_prim.h> +#include <osmocom/gprs/gmm/gmm.h> +#include <osmocom/gprs/rlcmac/rlcmac_prim.h> +#include <osmocom/gprs/sm/sm_prim.h> + +#include <osmocom/bb/common/settings.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/apn.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/common/vty.h> +#include <osmocom/bb/modem/gmm.h> +#include <osmocom/bb/modem/sm.h> +#include <osmocom/bb/modem/modem.h> + +static int modem_gmm_prim_up_cb(struct osmo_gprs_gmm_prim *gmm_prim, void *user_data) +{ + const char *pdu_name = osmo_gprs_gmm_prim_name(gmm_prim); + struct osmocom_ms *ms = user_data; + struct osmobb_apn *apn; + int rc = 0; + + switch (gmm_prim->oph.sap) { + case OSMO_GPRS_GMM_SAP_GMMREG: + switch (OSMO_PRIM_HDR(&gmm_prim->oph)) { + case OSMO_PRIM(OSMO_GPRS_GMM_GMMREG_ATTACH, PRIM_OP_CONFIRM): + if (gmm_prim->gmmreg.attach_cnf.accepted) { + LOGP(DGMM, LOGL_NOTICE, "%s(): Rx %s: Attach success P-TMSI=0x%08x TLLI=0x%08x\n", + __func__, pdu_name, gmm_prim->gmmreg.attach_cnf.acc.allocated_ptmsi, + gmm_prim->gmmreg.attach_cnf.acc.allocated_tlli); + ms->subscr.gprs.ptmsi = gmm_prim->gmmreg.attach_cnf.acc.allocated_ptmsi; + ms->gmmlayer.tlli = gmm_prim->gmmreg.attach_cnf.acc.allocated_tlli; + app_data.modem_state = MODEM_ST_ATTACHED; + /* Activate APN if not yet already: */ + llist_for_each_entry(apn, &ms->gprs.apn_list, list) { + if (apn->fsm.fi->state != APN_ST_INACTIVE) + continue; + osmo_fsm_inst_dispatch(apn->fsm.fi, APN_EV_GMM_ATTACHED, NULL); + modem_sm_smreg_pdp_act_req(ms, apn); + } + } else { + uint8_t cause = gmm_prim->gmmreg.attach_cnf.rej.cause; + LOGP(DGMM, LOGL_ERROR, "%s(): Rx %s: Attach rejected, cause=%u (%s)\n", + __func__, pdu_name, cause, get_value_string(gsm48_gmm_cause_names, cause)); + app_data.modem_state = MODEM_ST_IDLE; + modem_gprs_attach_if_needed(ms); + } + break; + case OSMO_PRIM(OSMO_GPRS_GMM_GMMREG_SIM_AUTH, PRIM_OP_INDICATION): + LOGP(DGMM, LOGL_NOTICE, "%s(): Rx %s ac_ref_nr=%u key_seq=%u rand=%s\n", + __func__, pdu_name, + gmm_prim->gmmreg.sim_auth_ind.ac_ref_nr, + gmm_prim->gmmreg.sim_auth_ind.key_seq, + osmo_hexdump(gmm_prim->gmmreg.sim_auth_ind.rand, + sizeof(gmm_prim->gmmreg.sim_auth_ind.rand))); + /* Cache request information, it'll be needed during response time: */ + ms->gmmlayer.ac_ref_nr = gmm_prim->gmmreg.sim_auth_ind.ac_ref_nr; + ms->gmmlayer.key_seq = gmm_prim->gmmreg.sim_auth_ind.key_seq; + memcpy(ms->gmmlayer.rand, gmm_prim->gmmreg.sim_auth_ind.rand, + sizeof(ms->gmmlayer.rand)); + /* Request SIM to authenticate. Wait for signal S_L23_SUBSCR_SIM_AUTH_RESP. */ + rc = gsm_subscr_generate_kc(ms, gmm_prim->gmmreg.sim_auth_ind.key_seq, + gmm_prim->gmmreg.sim_auth_ind.rand, false); + break; + case OSMO_PRIM(OSMO_GPRS_GMM_GMMREG_DETACH, PRIM_OP_CONFIRM): + case OSMO_PRIM(OSMO_GPRS_GMM_GMMREG_DETACH, PRIM_OP_INDICATION): + LOGP(DGMM, LOGL_NOTICE, "%s(): Rx %s\n", __func__, pdu_name); + ms_dispatch_all_apn(ms, APN_EV_GMM_DETACHED, NULL); + break; + default: + LOGP(DGMM, LOGL_ERROR, "%s(): Rx %s UNIMPLEMENTED\n", __func__, pdu_name); + break; + }; + break; + case OSMO_GPRS_GMM_SAP_GMMSM: + switch (OSMO_PRIM_HDR(&gmm_prim->oph)) { + case OSMO_PRIM(OSMO_GPRS_GMM_GMMSM_ESTABLISH, PRIM_OP_CONFIRM): + case OSMO_PRIM(OSMO_GPRS_GMM_GMMSM_RELEASE, PRIM_OP_INDICATION): + case OSMO_PRIM(OSMO_GPRS_GMM_GMMSM_UNITDATA, PRIM_OP_INDICATION): + case OSMO_PRIM(OSMO_GPRS_GMM_GMMSM_MODIFY, PRIM_OP_INDICATION): + osmo_gprs_sm_prim_gmm_lower_up(gmm_prim); + rc = 1; /* Tell RLCMAC that we take ownership of the prim. */ + break; + default: + LOGP(DGMM, LOGL_ERROR, "%s(): Rx %s UNIMPLEMENTED\n", __func__, pdu_name); + break; + }; + break; + default: + LOGP(DGMM, LOGL_ERROR, "%s(): Unexpected Rx %s\n", __func__, pdu_name); + OSMO_ASSERT(0); + } + + return rc; +} + +static int modem_gmm_prim_down_cb(struct osmo_gprs_gmm_prim *gmm_prim, void *user_data) +{ + const char *pdu_name = osmo_gprs_gmm_prim_name(gmm_prim); + int rc = 0; + uint32_t old_tlli, new_tlli; + struct osmocom_ms *ms, *ms_found = NULL; + + osmo_static_assert(sizeof(struct osmo_gprs_gmm_gmmrr_prim) == sizeof(struct osmo_gprs_rlcmac_gmmrr_prim), + _gmmrr_prim_size); + + switch (gmm_prim->oph.sap) { + case OSMO_GPRS_GMM_SAP_GMMRR: + OSMO_ASSERT(gmm_prim->oph.primitive == OSMO_GPRS_GMM_GMMRR_ASSIGN); + /* Update app TLLI reference. This usually happens as a result of a RAU ACCEPT */ + old_tlli = gmm_prim->gmmrr.tlli; + new_tlli = gmm_prim->gmmrr.assign_req.new_tlli; + llist_for_each_entry(ms, &ms_list, entity) { + if (old_tlli != ms->gmmlayer.tlli) + continue; + ms_found = ms; + break; + } + if (ms_found) { + if (new_tlli != OSMO_GPRS_GMM_TLLI_UNASSIGNED) { + LOGP(DGMM, LOGL_INFO, "%s(): Rx %s Update TLLI 0x%08x -> 0x%08x\n", + __func__, pdu_name, old_tlli, new_tlli); + ms_found->gmmlayer.tlli = new_tlli; + } else { + LOGP(DGMM, LOGL_ERROR, "%s(): Rx %s with TLLI=0x%08x is being released, GMM should be restarted?\n", + __func__, pdu_name, old_tlli); + } + } else { + if (old_tlli != OSMO_GPRS_GMM_TLLI_UNASSIGNED) + LOGP(DGMM, LOGL_NOTICE, "%s(): Rx %s with unknown TLLI=0x%08x, probably the MS is still attaching\n", + __func__, pdu_name, old_tlli); + } + + /* Forward it to lower layers, pass ownership over to RLCMAC: */ + /* Optimization: GMM-GMMRR-ASSIGN-REQ is 1-to-1 ABI compatible with + RLCMAC-GMMRR-ASSIGN-REQ, we just need to adapt the header. + See osmo_static_assert(_gmmrr_prim_size) above. + */ + gmm_prim->oph.sap = OSMO_GPRS_RLCMAC_SAP_GMMRR; + gmm_prim->oph.primitive = OSMO_GPRS_RLCMAC_GMMRR_ASSIGN; + osmo_gprs_rlcmac_prim_upper_down((struct osmo_gprs_rlcmac_prim *)gmm_prim); + rc = 1; /* Tell GMM that we take ownership of the prim. */ + break; + case OSMO_GPRS_GMM_SAP_GMMREG: + default: + LOGP(DGMM, LOGL_ERROR, "%s(): Unexpected Rx %s\n", __func__, pdu_name); + OSMO_ASSERT(0); + } + + return rc; +} + +static int modem_gmm_prim_llc_down_cb(struct osmo_gprs_llc_prim *llc_prim, void *user_data) +{ + int rc; + + rc = osmo_gprs_llc_prim_upper_down(llc_prim); + + /* LLC took ownership of the message, tell GMM layer to not free it: */ + rc = 1; + return rc; +} + +int modem_gmm_init(struct osmocom_ms *ms) +{ + int rc; + rc = osmo_gprs_gmm_init(OSMO_GPRS_GMM_LOCATION_MS); + if (rc != 0) + return rc; + + osmo_gprs_gmm_set_log_cat(OSMO_GPRS_GMM_LOGC_GMM, DGMM); + + osmo_gprs_gmm_prim_set_up_cb(modem_gmm_prim_up_cb, ms); + osmo_gprs_gmm_prim_set_down_cb(modem_gmm_prim_down_cb, ms); + osmo_gprs_gmm_prim_set_llc_down_cb(modem_gmm_prim_llc_down_cb, ms); + + osmo_gprs_gmm_enable_gprs(true); + return rc; +} + +int modem_gmm_gmmreg_attach_req(const struct osmocom_ms *ms) +{ + struct osmo_gprs_gmm_prim *gmm_prim; + const struct gsm_subscriber *subscr = &ms->subscr; + int rc; + + gmm_prim = osmo_gprs_gmm_prim_alloc_gmmreg_attach_req(); + gmm_prim->gmmreg.attach_req.attach_type = OSMO_GPRS_GMM_ATTACH_TYPE_GPRS; + gmm_prim->gmmreg.attach_req.ptmsi = subscr->gprs.ptmsi; + gmm_prim->gmmreg.attach_req.ptmsi_sig = subscr->gprs.ptmsi_sig; + gmm_prim->gmmreg.attach_req.attach_with_imsi = (subscr->gprs.ptmsi == GSM_RESERVED_TMSI); + memcpy(gmm_prim->gmmreg.attach_req.imsi, subscr->imsi, ARRAY_SIZE(subscr->imsi)); + memcpy(gmm_prim->gmmreg.attach_req.imei, ms->settings.imei, ARRAY_SIZE(ms->settings.imei)); + memcpy(gmm_prim->gmmreg.attach_req.imeisv, ms->settings.imeisv, ARRAY_SIZE(ms->settings.imeisv)); + memcpy(&gmm_prim->gmmreg.attach_req.old_rai, &subscr->gprs.rai, sizeof(subscr->gprs.rai)); + rc = osmo_gprs_gmm_prim_upper_down(gmm_prim); + if (rc < 0) + LOGP(DMM, LOGL_ERROR, "Failed submitting GMMREG-ATTACH.req\n"); + return rc; +} + +int modem_gmm_gmmreg_detach_req(const struct osmocom_ms *ms) +{ + struct osmo_gprs_gmm_prim *gmm_prim; + const struct gsm_subscriber *subscr = &ms->subscr; + int rc; + + gmm_prim = osmo_gprs_gmm_prim_alloc_gmmreg_detach_req(); + gmm_prim->gmmreg.detach_req.ptmsi = subscr->gprs.ptmsi; + gmm_prim->gmmreg.detach_req.detach_type = OSMO_GPRS_GMM_DETACH_MS_TYPE_GPRS; + gmm_prim->gmmreg.detach_req.poweroff_type = OSMO_GPRS_GMM_DETACH_POWEROFF_TYPE_NORMAL; + rc = osmo_gprs_gmm_prim_upper_down(gmm_prim); + if (rc < 0) + LOGP(DMM, LOGL_ERROR, "Failed submitting GMMREG-DETACH.req\n"); + return rc; +} + +int modem_gmm_gmmreg_sim_auth_rsp(const struct osmocom_ms *ms, uint8_t *sres, uint8_t *kc, uint8_t kc_len) +{ + struct osmo_gprs_gmm_prim *gmm_prim; + int rc; + + gmm_prim = osmo_gprs_gmm_prim_alloc_gmmreg_sim_auth_rsp(); + gmm_prim->gmmreg.sim_auth_rsp.ac_ref_nr = ms->gmmlayer.ac_ref_nr; + gmm_prim->gmmreg.sim_auth_rsp.key_seq = ms->gmmlayer.key_seq; + memcpy(gmm_prim->gmmreg.sim_auth_rsp.rand, ms->gmmlayer.rand, + sizeof(gmm_prim->gmmreg.sim_auth_rsp.rand)); + memcpy(gmm_prim->gmmreg.sim_auth_rsp.sres, sres, + sizeof(gmm_prim->gmmreg.sim_auth_rsp.sres)); + memcpy(gmm_prim->gmmreg.sim_auth_rsp.kc, kc, + kc_len); + rc = osmo_gprs_gmm_prim_upper_down(gmm_prim); + if (rc < 0) + LOGP(DMM, LOGL_ERROR, "Failed submitting GMMREG-SIM_AUTH.rsp\n"); + return rc; +} diff --git a/src/host/layer23/src/modem/grr.c b/src/host/layer23/src/modem/grr.c new file mode 100644 index 00000000..cd325c4c --- /dev/null +++ b/src/host/layer23/src/modem/grr.c @@ -0,0 +1,875 @@ +/* + * (C) 2022-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de> + * + * 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/lienses/>. + * + */ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/logging.h> + +#include <osmocom/gsm/rsl.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/lapdm.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/gsm/gsm48_ie.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/gprs/rlcmac/rlcmac_prim.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/sysinfo.h> +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/modem/modem.h> +#include <osmocom/bb/modem/grr.h> + +#include <osmocom/bb/mobile/gsm322.h> +#include <osmocom/bb/mobile/gsm48_rr.h> + +#include <l1ctl_proto.h> + +static uint32_t _gsm48_req_ref2fn(const struct gsm48_req_ref *ref) +{ + const struct gsm_time time = { + .t3 = ref->t3_high << 3 | ref->t3_low, + .t2 = ref->t2, + .t1 = ref->t1, + }; + return gsm_gsmtime2fn(&time); +} + +/* Generate an 8-bit CHANNEL REQUEST message as per 3GPP TS 44.018, 9.1.8 */ +static uint8_t grr_gen_chan_req(bool single_block) +{ + uint8_t rnd = (uint8_t)rand(); + + if (single_block) /* 01110xxx */ + return 0x70 | (rnd & 0x07); + + /* 011110xx or 01111x0x or 01111xx0 */ + if ((rnd & 0x07) == 0x07) + return 0x78; + return 0x78 | (rnd & 0x07); +} + +static bool grr_match_req_ref(struct osmocom_ms *ms, + const struct gsm48_req_ref *ref) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + for (unsigned int i = 0; i < ARRAY_SIZE(rr->cr_hist); i++) { + const struct gsm48_cr_hist *hist = &rr->cr_hist[i]; + if (!hist->valid) + continue; + if (memcmp(&hist->ref, ref, sizeof(*ref)) == 0) + return true; + } + + return false; +} + +static int forward_to_rlcmac(struct osmocom_ms *ms, struct msgb *msg) +{ + struct osmo_gprs_rlcmac_prim *rlcmac_prim; + const uint32_t fn = *(uint32_t *)(&msg->cb[0]); + + /* Forward a CCCH/BCCH block to the RLC/MAC layer */ + rlcmac_prim = osmo_gprs_rlcmac_prim_alloc_l1ctl_ccch_data_ind(fn, msgb_l3(msg)); + return osmo_gprs_rlcmac_prim_lower_up(rlcmac_prim); +} + +static int grr_handle_si1(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + int rc; + + if (msgb_l3len(msg) != GSM_MACBLOCK_LEN) + return -EINVAL; + if (!memcmp(&cs->sel_si.si1_msg[0], msgb_l3(msg), msgb_l3len(msg))) + return 0; /* this message is already handled */ + + rc = gsm48_decode_sysinfo1(&cs->sel_si, msgb_l3(msg), msgb_l3len(msg)); + if (rc != 0) { + LOGP(DRR, LOGL_ERROR, "Failed to decode SI1 message\n"); + return rc; + } + + return 0; +} + +static int grr_handle_si3(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + int rc; + + if (msgb_l3len(msg) != GSM_MACBLOCK_LEN) + return -EINVAL; + if (!memcmp(&cs->sel_si.si3_msg[0], msgb_l3(msg), msgb_l3len(msg))) + return 0; /* this message is already handled */ + + rc = gsm48_decode_sysinfo3(&cs->sel_si, msgb_l3(msg), msgb_l3len(msg)); + if (rc != 0) { + LOGP(DRR, LOGL_ERROR, "Failed to decode SI3 message\n"); + return rc; + } + + if (cs->ccch_mode == CCCH_MODE_NONE) { + if (cs->sel_si.ccch_conf == RSL_BCCH_CCCH_CONF_1_C) + cs->ccch_mode = CCCH_MODE_COMBINED; + else + cs->ccch_mode = CCCH_MODE_NON_COMBINED; + l1ctl_tx_ccch_mode_req(ms, cs->ccch_mode); + } + + if (!cs->sel_si.gprs.supported) { + LOGP(DRR, LOGL_NOTICE, "SI3 Rest Octets IE contains no GPRS Indicator\n"); + return 0; + } + + LOGP(DRR, LOGL_NOTICE, "Found GPRS Indicator (RA Colour %u, SI13 on BCCH %s)\n", + cs->sel_si.gprs.ra_colour, cs->sel_si.gprs.si13_pos ? "Ext" : "Norm"); + + return 0; +} + +static int grr_handle_si4(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + int rc; + + if (msgb_l3len(msg) != GSM_MACBLOCK_LEN) + return -EINVAL; + if (!memcmp(&cs->sel_si.si4_msg[0], msgb_l3(msg), msgb_l3len(msg))) + return 0; /* this message is already handled */ + + rc = gsm48_decode_sysinfo4(&cs->sel_si, msgb_l3(msg), msgb_l3len(msg)); + if (rc != 0) { + LOGP(DRR, LOGL_ERROR, "Failed to decode SI4 message\n"); + return rc; + } + + if (!cs->sel_si.gprs.supported) { + LOGP(DRR, LOGL_NOTICE, "SI4 Rest Octets IE contains no GPRS Indicator\n"); + return 0; + } + + LOGP(DRR, LOGL_NOTICE, "Found GPRS Indicator (RA Colour %u, SI13 on BCCH %s)\n", + cs->sel_si.gprs.ra_colour, cs->sel_si.gprs.si13_pos ? "Ext" : "Norm"); + + return 0; +} + +static int grr_handle_si13(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + int rc; + + if (msgb_l3len(msg) != GSM_MACBLOCK_LEN) + return -EINVAL; + if (!memcmp(&cs->sel_si.si13_msg[0], msgb_l3(msg), msgb_l3len(msg))) + return 0; /* this message is already handled */ + + rc = gsm48_decode_sysinfo13(&cs->sel_si, msgb_l3(msg), msgb_l3len(msg)); + if (rc != 0) + return rc; + + /* Forward SI13 to RLC/MAC layer */ + return forward_to_rlcmac(ms, msg); +} + +static int grr_rx_bcch(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct gsm48_system_information_type_header *si_hdr = msgb_l3(msg); + const uint8_t si_type = si_hdr->system_information; + const uint32_t fn = *(uint32_t *)(&msg->cb[0]); + + LOGP(DRR, LOGL_INFO, "BCCH message (type=0x%02x, fn=%u): %s\n", + si_type, fn, gsm48_rr_msg_name(si_type)); + + switch (si_type) { + case GSM48_MT_RR_SYSINFO_1: + return grr_handle_si1(ms, msg); + case GSM48_MT_RR_SYSINFO_3: + return grr_handle_si3(ms, msg); + case GSM48_MT_RR_SYSINFO_4: + return grr_handle_si4(ms, msg); + case GSM48_MT_RR_SYSINFO_13: + return grr_handle_si13(ms, msg); + default: + return 0; + }; +} + +static int grr_rx_imm_ass(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct gsm48_imm_ass *ia = msgb_l3(msg); + struct gsm48_rrlayer *rr = &ms->rrlayer; + + /* 3GPP TS 44.018, section 10.5.2.25b "Dedicated mode or TBF". + * As per table 9.1.18.1, only the value part (4 bits) is present in the + * IMMEDIATE ASSIGNMENT message. In struct gsm48_imm_ass it's combined + * with the Page Mode IE, perhaps due to historical reasons. */ + const uint8_t dm_or_tbf = ia->page_mode >> 4; + + /* T/D flag: discard dedicated channel assignment */ + if ((dm_or_tbf & (1 << 0)) == 0) { + LOGP(DRR, LOGL_INFO, + "%s(): Discarding IMM ASS: dedicated channel assignment\n", + __func__); + return 0; + } + /* NRA flag: discard No Resource Allocated */ + if ((dm_or_tbf & (1 << 3)) != 0) { + LOGP(DRR, LOGL_INFO, + "%s(): Discarding IMM ASS: NRA flag is set\n", + __func__); + return 0; + } + + /* If this is an Uplink TBF assignment, check the Request Reference IE. + * Checking this IE in Downlink TBF assignment makes no sense because + * no CHANNEL REQUEST was sent by the MS prior to it. */ + if ((dm_or_tbf & (1 << 1)) == 0) { + if (rr->state != GSM48_RR_ST_CONN_PEND) { + LOGP(DRR, LOGL_INFO, + "%s(): rr_state != GSM48_RR_ST_CONN_PEND\n", __func__); + return 0; + } + if (!grr_match_req_ref(ms, &ia->req_ref)) { + LOGP(DRR, LOGL_INFO, + "%s(): req_ref mismatch (RA=0x%02x, T1=%u, T3=%u, T2=%u, FN=%u)\n", + __func__, ia->req_ref.ra, ia->req_ref.t1, + ia->req_ref.t3_high << 3 | ia->req_ref.t3_low, ia->req_ref.t2, + _gsm48_req_ref2fn(&ia->req_ref)); + return 0; + } + } + + return forward_to_rlcmac(ms, msg); +} + +/* TS 44.018 9.1.22 "Paging request type 1" */ +static int grr_rx_pag_req_1(struct osmocom_ms *ms, struct msgb *msg) +{ + LOGP(DRR, LOGL_INFO, "Rx Paging Request Type 1\n"); + + return forward_to_rlcmac(ms, msg); +} + +/* TS 44.018 9.1.23 "Paging request type 2" */ +static int grr_rx_pag_req_2(struct osmocom_ms *ms, struct msgb *msg) +{ + LOGP(DRR, LOGL_INFO, "Rx Paging Request Type 2\n"); + + return forward_to_rlcmac(ms, msg); +} + +/* 9.1.24 Paging request type 3 */ +static int grr_rx_pag_req_3(struct osmocom_ms *ms, struct msgb *msg) +{ + LOGP(DRR, LOGL_INFO, "Rx Paging Request Type 3\n"); + + /* Paging Request Type 3 contains 4 TMSI/P-TMSI, but P3 Rest Octets + contain no "Packet Page Indication" IE, hence it cannot be used to page + for GPRS. Simply ignore it. */ + return 0; +} + +/* Dummy Paging Request 1 with "no identity" */ +static const uint8_t paging_fill[] = { + 0x15, 0x06, 0x21, 0x00, 0x01, 0xf0, 0x2b, + /* The rest part may be randomized */ +}; + +/* LAPDm func=UI fill frame (for the BTS side) */ +static const uint8_t lapdm_fill[] = { + 0x03, 0x03, 0x01, 0x2b, + /* The rest part may be randomized */ +}; + +/* TODO: share / generalize this code */ +static bool is_fill_frame(const struct msgb *msg) +{ + const uint8_t *l2 = msgb_l3(msg); + + if (!memcmp(l2, paging_fill, sizeof(paging_fill))) + return true; + if (!memcmp(l2, lapdm_fill, sizeof(lapdm_fill))) + return true; + + return false; +} + +static int grr_rx_ccch(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct gsm48_system_information_type_header *sih = msgb_l3(msg); + + /* Skip frames with wrong length */ + if (msgb_l3len(msg) != GSM_MACBLOCK_LEN) { + LOGP(DRR, LOGL_ERROR, "Rx CCCH message with odd length=%u: %s\n", + msgb_l3len(msg), msgb_hexdump_l3(msg)); + return -EINVAL; + } + + /* Skip dummy (fill) frames */ + if (is_fill_frame(msg)) + return 0; + + if (sih->rr_protocol_discriminator != GSM48_PDISC_RR) { + LOGP(DRR, LOGL_ERROR, "PCH pdisc (%s) != RR\n", + gsm48_pdisc_name(sih->rr_protocol_discriminator)); + } + + switch (sih->system_information) { + case GSM48_MT_RR_IMM_ASS: + return grr_rx_imm_ass(ms, msg); + case GSM48_MT_RR_PAG_REQ_1: + return grr_rx_pag_req_1(ms, msg); + case GSM48_MT_RR_PAG_REQ_2: + return grr_rx_pag_req_2(ms, msg); + case GSM48_MT_RR_PAG_REQ_3: + return grr_rx_pag_req_3(ms, msg); + default: + return 0; + } +} + +static int grr_rx_rslms_rll_ud(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + struct tlv_parsed tv; + + DEBUGP(DRSL, "RSLms UNIT DATA IND chan_nr=0x%02x link_id=0x%02x\n", + rllh->chan_nr, rllh->link_id); + + if (rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg) - sizeof(*rllh)) < 0) { + LOGP(DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return -EINVAL; + } + + if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { + LOGP(DRSL, LOGL_ERROR, "UNIT_DATA_IND without L3 INFO ?!?\n"); + return -EINVAL; + } + + msg->l3h = (uint8_t *)TLVP_VAL(&tv, RSL_IE_L3_INFO); + + switch (rllh->chan_nr) { + case RSL_CHAN_PCH_AGCH: + return osmo_fsm_inst_dispatch(ms->grr_fi, GRR_EV_PCH_AGCH_BLOCK_IND, msg); + case RSL_CHAN_BCCH: + return osmo_fsm_inst_dispatch(ms->grr_fi, GRR_EV_BCCH_BLOCK_IND, msg); + default: + return 0; + } +} + +static int grr_rx_rslms_rll(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + + switch (rllh->c.msg_type) { + case RSL_MT_UNIT_DATA_IND: + return grr_rx_rslms_rll_ud(ms, msg); + default: + LOGP(DRSL, LOGL_NOTICE, "Unhandled RSLms RLL message " + "(msg_type 0x%02x)\n", rllh->c.msg_type); + return -EINVAL; + } +} + +static int grr_rx_rslms_cchan(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct abis_rsl_cchan_hdr *ch = msgb_l2(msg); + + switch (ch->c.msg_type) { + case RSL_MT_CHAN_CONF: /* RACH.conf */ + return osmo_fsm_inst_dispatch(ms->grr_fi, GRR_EV_CHAN_ACCESS_CNF, + (void *)&ch->data[1]); + default: + LOGP(DRSL, LOGL_NOTICE, "Unhandled RSLms CCHAN message " + "(msg_type 0x%02x)\n", ch->c.msg_type); + return -EINVAL; + } +} + +int modem_grr_rslms_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx) +{ + const struct abis_rsl_common_hdr *rslh = msgb_l2(msg); + int rc; + + /* Obtain FN from message context: */ + *(uint32_t *)(&msg->cb[0]) = le->datalink[DL_SAPI0].mctx.fn; + + switch (rslh->msg_discr & 0xfe) { + case ABIS_RSL_MDISC_RLL: + rc = grr_rx_rslms_rll((struct osmocom_ms *)ctx, msg); + break; + case ABIS_RSL_MDISC_COM_CHAN: + rc = grr_rx_rslms_cchan((struct osmocom_ms *)ctx, msg); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "Unhandled RSLms message " + "(msg_discr 0x%02x)\n", rslh->msg_discr); + rc = -EINVAL; + break; + } + + msgb_free(msg); + return rc; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +#define S(x) (1 << (x)) + +#include <osmocom/gsm/gsm0502.h> // XXX + +/* RACH re-transmission delay value (in ms) */ +#define GRR_PACKET_ACCESS_DELAY_MS 300 +/* RACH max number of transmissions */ +#define GRR_PACKET_ACCESS_MAX_CHAN_REQ 3 + +static void handle_chan_access_req(struct osmo_fsm_inst *fi, + const struct osmo_gprs_rlcmac_l1ctl_prim *lp) +{ + struct osmocom_ms *ms = fi->priv; + struct gsm48_rrlayer *rr = &ms->rrlayer; + + if (lp->rach_req.is_11bit) { /* TODO: implement 11-bit RACH */ + LOGPFSML(fi, LOGL_ERROR, "11-bit RACH is not supported\n"); + return; + } + + memset(&rr->cr_hist[0], 0x00, sizeof(rr->cr_hist)); + rr->chan_req_val = lp->rach_req.ra & ~0x07; + rr->n_chan_req = GRR_PACKET_ACCESS_MAX_CHAN_REQ; + rr->state = GSM48_RR_ST_CONN_PEND; + + osmo_fsm_inst_state_chg_ms(fi, GRR_ST_PACKET_ACCESS, + GRR_PACKET_ACCESS_DELAY_MS, 0); +} + +static void handle_pdch_establish_req(struct osmo_fsm_inst *fi, + const struct osmo_gprs_rlcmac_l1ctl_prim *lp) +{ + struct osmocom_ms *ms = fi->priv; + + if (!lp->pdch_est_req.fh) { + LOGPFSML(fi, LOGL_INFO, + "PDCH Establish.Req: TSC=%u, H0, ARFCN=%u\n", + lp->pdch_est_req.tsc, lp->pdch_est_req.arfcn); + l1ctl_tx_dm_est_req_h0(ms, lp->pdch_est_req.arfcn, + RSL_CHAN_OSMO_PDCH | lp->pdch_est_req.ts_nr, + lp->pdch_est_req.tsc, GSM48_CMODE_SIGN, 0, 0); + } else { + /* Hopping */ + uint8_t ma_len = 0; + uint16_t ma[64]; + + LOGPFSML(fi, LOGL_INFO, + "PDCH Establish.Req: TSC=%u, H1, HSN=%u, MAIO=%u\n", + lp->pdch_est_req.tsc, + lp->pdch_est_req.fhp.hsn, + lp->pdch_est_req.fhp.maio); + + for (unsigned int i = 1, j = 0; i <= 1024; i++) { + unsigned int arfcn = i & 1023; + unsigned int k; + + if (~ms->cellsel.sel_si.freq[arfcn].mask & 0x01) + continue; + + k = lp->pdch_est_req.fhp.ma_len - (j >> 3) - 1; + if (lp->pdch_est_req.fhp.ma[k] & (1 << (j & 7))) + ma[ma_len++] = arfcn; + j++; + } + + l1ctl_tx_dm_est_req_h1(ms, + lp->pdch_est_req.fhp.maio, + lp->pdch_est_req.fhp.hsn, + &ma[0], ma_len, + RSL_CHAN_OSMO_PDCH | lp->pdch_est_req.ts_nr, + lp->pdch_est_req.tsc, GSM48_CMODE_SIGN, 0, 0); + } + + osmo_fsm_inst_state_chg(fi, GRR_ST_PACKET_TRANSFER, 0, 0); +} + +static void handle_pdch_block_cnf(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct l1ctl_gprs_ul_block_cnf *cnf = (void *)msg->l1h; + const uint32_t fn = osmo_load32be(&cnf->fn); + struct osmo_gprs_rlcmac_prim *prim; + + prim = osmo_gprs_rlcmac_prim_alloc_l1ctl_pdch_data_cnf(cnf->tn, fn, + msgb_l2(msg), + msgb_l2len(msg)); + osmo_gprs_rlcmac_prim_lower_up(prim); +} + +static void handle_pdch_block_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct l1ctl_gprs_dl_block_ind *ind = (void *)msg->l1h; + const uint32_t fn = osmo_load32be(&ind->hdr.fn); + struct osmo_gprs_rlcmac_prim *prim; + + /* FIXME: sadly, rlcmac_prim_l1ctl_alloc() is not exposed */ + prim = osmo_gprs_rlcmac_prim_alloc_l1ctl_pdch_data_ind(0, 0, 0, 0, 0, NULL, 0); + prim->l1ctl = (struct osmo_gprs_rlcmac_l1ctl_prim) { + .pdch_data_ind = { + .fn = fn, + .ts_nr = ind->hdr.tn, + .rx_lev = ind->meas.rx_lev, + .ber10k = osmo_load16be(&ind->meas.ber10k), + .ci_cb = osmo_load16be(&ind->meas.ci_cb), + .data_len = msgb_l2len(msg), + .data = msgb_l2(msg), + } + }; + osmo_gprs_rlcmac_prim_lower_up(prim); +} + +static void handle_pdch_rts_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct l1ctl_gprs_rts_ind *ind = (void *)msg->l1h; + const uint32_t fn = osmo_load32be(&ind->fn); + struct osmo_gprs_rlcmac_prim *prim; + + prim = osmo_gprs_rlcmac_prim_alloc_l1ctl_pdch_rts_ind(ind->tn, fn, ind->usf); + osmo_gprs_rlcmac_prim_lower_up(prim); +} + +static bool grr_cell_is_usable(const struct osmocom_ms *ms) +{ + const struct gsm322_cellsel *cs = &ms->cellsel; + const struct gsm48_sysinfo *si = &cs->sel_si; + + if (cs->sync_pending) /* FBSB in process */ + return false; + + if (!si->si1 || !si->si3 || !si->si4 || !si->si13) + return false; + if (!si->gprs.supported) + return false; + + return true; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static void grr_st_packet_not_ready_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct osmocom_ms *ms = fi->priv; + + switch (event) { + case GRR_EV_BCCH_BLOCK_IND: + grr_rx_bcch(ms, (struct msgb *)data); + if (grr_cell_is_usable(ms)) { + LOGPFSML(fi, LOGL_NOTICE, "Cell is usable, GRR becomes ready\n"); + osmo_fsm_inst_state_chg(fi, GRR_ST_PACKET_IDLE, 0, 0); + } + break; + case GRR_EV_PCH_AGCH_BLOCK_IND: + grr_rx_ccch(ms, (struct msgb *)data); + break; + default: + OSMO_ASSERT(0); + } +} + +static void grr_st_packet_idle_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmocom_ms *ms = fi->priv; + struct osmo_gprs_rlcmac_prim *prim; + + prim = osmo_gprs_rlcmac_prim_alloc_l1ctl_ccch_ready_ind(); + osmo_gprs_rlcmac_prim_lower_up(prim); + + modem_gprs_attach_if_needed(ms); +} + +static void grr_st_packet_idle_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct osmocom_ms *ms = fi->priv; + + switch (event) { + case GRR_EV_BCCH_BLOCK_IND: + grr_rx_bcch(ms, (struct msgb *)data); + if (!grr_cell_is_usable(ms)) { + LOGPFSML(fi, LOGL_NOTICE, "Cell is not usable, GRR becomes not ready\n"); + osmo_fsm_inst_state_chg(fi, GRR_ST_PACKET_NOT_READY, 0, 0); + } + break; + case GRR_EV_PCH_AGCH_BLOCK_IND: + grr_rx_ccch(ms, (struct msgb *)data); + break; + case GRR_EV_CHAN_ACCESS_REQ: + handle_chan_access_req(fi, data); + break; + case GRR_EV_PDCH_ESTABLISH_REQ: + handle_pdch_establish_req(fi, data); + break; + default: + OSMO_ASSERT(0); + } +} + +static void grr_st_packet_access_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmocom_ms *ms = fi->priv; + struct gsm48_rrlayer *rr = &ms->rrlayer; + + if (rr->n_chan_req == 0) { + LOGPFSML(fi, LOGL_ERROR, "Packet access failed (no more attempts left)\n"); + osmo_fsm_inst_state_chg(fi, GRR_ST_PACKET_NOT_READY, 0, 0); + return; + } + + /* (re-)generate the RA value */ + if ((rr->chan_req_val >> 3) == 0x0e) /* 01110xxx */ + rr->cr_ra = grr_gen_chan_req(true); + else /* 011110xx or 01111x0x or 01111xx0 */ + rr->cr_ra = grr_gen_chan_req(false); + rr->n_chan_req--; + + LOGPFSML(fi, LOGL_INFO, + "Sending CHANNEL REQUEST (ra=0x%02x, num_attempts=%u)\n", + rr->cr_ra, rr->n_chan_req); + + l1ctl_tx_rach_req(ms, RSL_CHAN_RACH, 0x00, rr->cr_ra, 0, + ms->cellsel.ccch_mode == CCCH_MODE_COMBINED, 0xff); +} + +static void grr_st_packet_access_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct osmocom_ms *ms = fi->priv; + + switch (event) { + case GRR_EV_BCCH_BLOCK_IND: + grr_rx_bcch(ms, (struct msgb *)data); + if (!grr_cell_is_usable(ms)) { + LOGPFSML(fi, LOGL_NOTICE, "Cell is not usable, GRR becomes not ready\n"); + osmo_fsm_inst_state_chg(fi, GRR_ST_PACKET_NOT_READY, 0, 0); + } + break; + case GRR_EV_PCH_AGCH_BLOCK_IND: + grr_rx_ccch(ms, (struct msgb *)data); + break; + case GRR_EV_CHAN_ACCESS_CNF: + { + struct gsm48_rrlayer *rr = &ms->rrlayer; + const struct gsm48_req_ref *ref = data; + + LOGPFSML(fi, LOGL_NOTICE, + "Rx RACH.conf (RA=0x%02x, T1=%u, T3=%u, T2=%u, FN=%u)\n", + rr->cr_ra, ref->t1, ref->t3_high << 3 | ref->t3_low, ref->t2, + _gsm48_req_ref2fn(ref)); + + if (ms->rrlayer.state != GSM48_RR_ST_CONN_PEND) { + LOGPFSML(fi, LOGL_ERROR, "Rx unexpected RACH.conf\n"); + return; + } + + /* shift the CHANNEL REQUEST history buffer */ + memmove(&rr->cr_hist[1], &rr->cr_hist[0], + sizeof(rr->cr_hist) - sizeof(rr->cr_hist[0])); + /* store the new entry */ + rr->cr_hist[0].ref = *ref; + rr->cr_hist[0].ref.ra = rr->cr_ra; + rr->cr_hist[0].valid = 1; + break; + } + case GRR_EV_PDCH_ESTABLISH_REQ: + handle_pdch_establish_req(fi, data); + break; + default: + OSMO_ASSERT(0); + } +} + +static void grr_st_packet_transfer_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmocom_ms *ms = fi->priv; + + ms->rrlayer.state = GSM48_RR_ST_DEDICATED; +} + +static void grr_st_packet_transfer_onleave(struct osmo_fsm_inst *fi, uint32_t next_state) +{ + struct osmocom_ms *ms = fi->priv; + + ms->rrlayer.state = GSM48_RR_ST_IDLE; +} + +static void grr_st_packet_transfer_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct osmocom_ms *ms = fi->priv; + + switch (event) { + case GRR_EV_PDCH_UL_TBF_CFG_REQ: + { + const struct osmo_gprs_rlcmac_l1ctl_prim *lp = data; + l1ctl_tx_gprs_ul_tbf_cfg_req(ms, + lp->cfg_ul_tbf_req.ul_tbf_nr, + lp->cfg_ul_tbf_req.ul_slotmask, + lp->cfg_ul_tbf_req.start_fn); + break; + } + case GRR_EV_PDCH_DL_TBF_CFG_REQ: + { + const struct osmo_gprs_rlcmac_l1ctl_prim *lp = data; + l1ctl_tx_gprs_dl_tbf_cfg_req(ms, + lp->cfg_dl_tbf_req.dl_tbf_nr, + lp->cfg_dl_tbf_req.dl_slotmask, + lp->cfg_ul_tbf_req.start_fn, + lp->cfg_dl_tbf_req.dl_tfi); + break; + } + case GRR_EV_PDCH_BLOCK_REQ: + { + const struct osmo_gprs_rlcmac_l1ctl_prim *lp = data; + l1ctl_tx_gprs_ul_block_req(ms, + lp->pdch_data_req.fn, + lp->pdch_data_req.ts_nr, + lp->pdch_data_req.data, + lp->pdch_data_req.data_len); + break; + } + case GRR_EV_PDCH_BLOCK_CNF: + handle_pdch_block_cnf(ms, (struct msgb *)data); + break; + case GRR_EV_PDCH_BLOCK_IND: + handle_pdch_block_ind(ms, (struct msgb *)data); + break; + case GRR_EV_PDCH_RTS_IND: + handle_pdch_rts_ind(ms, (struct msgb *)data); + break; + case GRR_EV_PDCH_RELEASE_REQ: + modem_sync_to_cell(ms); + osmo_fsm_inst_state_chg(fi, GRR_ST_PACKET_NOT_READY, 0, 0); + break; + default: + OSMO_ASSERT(0); + } +} + +static int grr_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + switch (fi->state) { + case GRR_ST_PACKET_ACCESS: + /* perform a loop transaction, restarting the timer */ + osmo_fsm_inst_state_chg_ms(fi, GRR_ST_PACKET_ACCESS, + GRR_PACKET_ACCESS_DELAY_MS, 0); + return 0; + default: + OSMO_ASSERT(0); + } +} + +static const struct osmo_fsm_state grr_fsm_states[] = { + [GRR_ST_PACKET_NOT_READY] = { + .name = "PACKET_NOT_READY", + .out_state_mask = S(GRR_ST_PACKET_IDLE), + .in_event_mask = S(GRR_EV_BCCH_BLOCK_IND) + | S(GRR_EV_PCH_AGCH_BLOCK_IND), + .action = &grr_st_packet_not_ready_action, + }, + [GRR_ST_PACKET_IDLE] = { + .name = "PACKET_IDLE", + .out_state_mask = S(GRR_ST_PACKET_NOT_READY) + | S(GRR_ST_PACKET_ACCESS) + | S(GRR_ST_PACKET_TRANSFER), + .in_event_mask = S(GRR_EV_BCCH_BLOCK_IND) + | S(GRR_EV_PCH_AGCH_BLOCK_IND) + | S(GRR_EV_CHAN_ACCESS_REQ) + | S(GRR_EV_PDCH_ESTABLISH_REQ), /* DL TBF ASS */ + .action = &grr_st_packet_idle_action, + .onenter = &grr_st_packet_idle_onenter, + }, + [GRR_ST_PACKET_ACCESS] = { + .name = "PACKET_ACCESS", + .out_state_mask = S(GRR_ST_PACKET_NOT_READY) + | S(GRR_ST_PACKET_ACCESS) + | S(GRR_ST_PACKET_TRANSFER), + .in_event_mask = S(GRR_EV_BCCH_BLOCK_IND) + | S(GRR_EV_PCH_AGCH_BLOCK_IND) + | S(GRR_EV_CHAN_ACCESS_CNF) + | S(GRR_EV_PDCH_ESTABLISH_REQ), /* UL TBF ASS */ + .onenter = &grr_st_packet_access_onenter, + .action = &grr_st_packet_access_action, + }, + [GRR_ST_PACKET_TRANSFER] = { + .name = "PACKET_TRANSFER", + .out_state_mask = S(GRR_ST_PACKET_NOT_READY), + .in_event_mask = S(GRR_EV_PDCH_UL_TBF_CFG_REQ) + | S(GRR_EV_PDCH_DL_TBF_CFG_REQ) + | S(GRR_EV_PDCH_BLOCK_REQ) + | S(GRR_EV_PDCH_BLOCK_CNF) + | S(GRR_EV_PDCH_BLOCK_IND) + | S(GRR_EV_PDCH_RTS_IND) + | S(GRR_EV_PDCH_RELEASE_REQ), + .action = &grr_st_packet_transfer_action, + .onenter = &grr_st_packet_transfer_onenter, + .onleave = &grr_st_packet_transfer_onleave, + }, +}; + +static const struct value_string grr_fsm_event_names[] = { + OSMO_VALUE_STRING(GRR_EV_BCCH_BLOCK_IND), + OSMO_VALUE_STRING(GRR_EV_PCH_AGCH_BLOCK_IND), + OSMO_VALUE_STRING(GRR_EV_CHAN_ACCESS_REQ), + OSMO_VALUE_STRING(GRR_EV_CHAN_ACCESS_CNF), + OSMO_VALUE_STRING(GRR_EV_PDCH_ESTABLISH_REQ), + OSMO_VALUE_STRING(GRR_EV_PDCH_RELEASE_REQ), + OSMO_VALUE_STRING(GRR_EV_PDCH_UL_TBF_CFG_REQ), + OSMO_VALUE_STRING(GRR_EV_PDCH_DL_TBF_CFG_REQ), + OSMO_VALUE_STRING(GRR_EV_PDCH_BLOCK_REQ), + OSMO_VALUE_STRING(GRR_EV_PDCH_BLOCK_CNF), + OSMO_VALUE_STRING(GRR_EV_PDCH_BLOCK_IND), + OSMO_VALUE_STRING(GRR_EV_PDCH_RTS_IND), + { 0, NULL } +}; + +struct osmo_fsm grr_fsm_def = { + .name = "GPRS-RR", + .log_subsys = DRR, + .states = grr_fsm_states, + .num_states = ARRAY_SIZE(grr_fsm_states), + .event_names = grr_fsm_event_names, + .timer_cb = &grr_fsm_timer_cb, +}; + +static __attribute__((constructor)) void on_dso_load(void) +{ + OSMO_ASSERT(osmo_fsm_register(&grr_fsm_def) == 0); +} diff --git a/src/host/layer23/src/modem/llc.c b/src/host/layer23/src/modem/llc.c new file mode 100644 index 00000000..fe920ee7 --- /dev/null +++ b/src/host/layer23/src/modem/llc.c @@ -0,0 +1,181 @@ +/* GPRS LLC protocol implementation as per 3GPP TS 04.64 */ +/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * 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/lienses/>. + * + */ + +#include <errno.h> +#include <stdint.h> +#include <string.h> +#include <stdbool.h> + +#include <osmocom/core/prim.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/logging.h> + +#include <osmocom/gprs/gprs_msgb.h> +#include <osmocom/gprs/llc/llc_prim.h> +#include <osmocom/gprs/llc/llc.h> +#include <osmocom/gprs/gmm/gmm_prim.h> +#include <osmocom/gprs/rlcmac/rlcmac_prim.h> +#include <osmocom/gprs/sndcp/sndcp_prim.h> + +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/modem/llc.h> + +static int modem_llc_handle_ll_gmm(struct osmo_gprs_llc_prim *llc_prim) +{ + int rc; + + switch (llc_prim->oph.primitive) { + case OSMO_GPRS_LLC_LL_UNITDATA: + break; + case OSMO_GPRS_LLC_LL_RESET: + case OSMO_GPRS_LLC_LL_ESTABLISH: + case OSMO_GPRS_LLC_LL_XID: + case OSMO_GPRS_LLC_LL_DATA: + case OSMO_GPRS_LLC_LL_STATUS: + default: + LOGP(DLLC, LOGL_NOTICE, "%s(): Unexpected Rx LL prim %u\n", + __func__, llc_prim->oph.primitive); + return -EINVAL; + } + + /* GMM took ownership of the message, tell LLC layer to not free it: */ + rc = osmo_gprs_gmm_prim_llc_lower_up(llc_prim); + rc = 1; + return rc; +} + +static int modem_llc_handle_ll_sndcp(struct osmo_gprs_llc_prim *llc_prim) +{ + int rc; + switch (llc_prim->oph.primitive) { + case OSMO_GPRS_LLC_LL_RESET: + case OSMO_GPRS_LLC_LL_ESTABLISH: + case OSMO_GPRS_LLC_LL_XID: + case OSMO_GPRS_LLC_LL_DATA: + case OSMO_GPRS_LLC_LL_UNITDATA: + case OSMO_GPRS_LLC_LL_STATUS: + case OSMO_GPRS_LLC_LL_ASSIGN: + /* Forward it to upper layers, pass owneserip over to SNDCP: */ + osmo_gprs_sndcp_prim_lower_up(llc_prim); + rc = 1; /* Tell LLC that we take ownership of the prim. */ + break; + default: + LOGP(DLLC, LOGL_NOTICE, "%s(): Unexpected Rx LL prim %u\n", + __func__, llc_prim->oph.primitive); + rc = -EINVAL; + } + return rc; +} + +int modem_llc_prim_up_cb(struct osmo_gprs_llc_prim *llc_prim, void *user_data) +{ + const char *pdu_name = osmo_gprs_llc_prim_name(llc_prim); + int rc = 0; + + switch (llc_prim->oph.sap) { + case OSMO_GPRS_LLC_SAP_LLGMM: + LOGP(DLLC, LOGL_DEBUG, "%s(): Rx %s TLLI=0x%08x\n", + __func__, pdu_name, llc_prim->llgmm.tlli); + break; + case OSMO_GPRS_LLC_SAP_LL: + LOGP(DLLC, LOGL_DEBUG, "%s(): Rx %s TLLI=0x%08x SAPI=%s l3=[%s]\n", + __func__, pdu_name, llc_prim->ll.tlli, + osmo_gprs_llc_sapi_name(llc_prim->ll.sapi), + osmo_hexdump(llc_prim->ll.l3_pdu, llc_prim->ll.l3_pdu_len)); + + switch (llc_prim->ll.sapi) { + case OSMO_GPRS_LLC_SAPI_GMM: + rc = modem_llc_handle_ll_gmm(llc_prim); + break; + case OSMO_GPRS_LLC_SAPI_SNDCP3: + case OSMO_GPRS_LLC_SAPI_SNDCP5: + case OSMO_GPRS_LLC_SAPI_SNDCP9: + case OSMO_GPRS_LLC_SAPI_SNDCP11: + rc = modem_llc_handle_ll_sndcp(llc_prim); + break; + case OSMO_GPRS_LLC_SAPI_TOM2: + case OSMO_GPRS_LLC_SAPI_SMS: + case OSMO_GPRS_LLC_SAPI_TOM8: + LOGP(DLLC, LOGL_NOTICE, "%s(): Unimplemented Rx llc_sapi %s\n", __func__, pdu_name); + rc = -EINVAL; + break; + default: + LOGP(DLLC, LOGL_NOTICE, "%s(): Unexpected Rx llc_sapi %s\n", __func__, pdu_name); + rc = -EINVAL; + break; + } + break; + default: + LOGP(DLLC, LOGL_NOTICE, "%s(): Unexpected Rx %s\n", __func__, pdu_name); + OSMO_ASSERT(0); + } + return rc; +} + +int modem_llc_prim_down_cb(struct osmo_gprs_llc_prim *llc_prim, void *user_data) +{ + const char *pdu_name = osmo_gprs_llc_prim_name(llc_prim); + int rc = 0; + + osmo_static_assert(sizeof(struct osmo_gprs_llc_grr_prim) == sizeof(struct osmo_gprs_rlcmac_grr_prim), + _grr_prim_size); + + switch (llc_prim->oph.sap) { + case OSMO_GPRS_LLC_SAP_GRR: + LOGP(DLLC, LOGL_DEBUG, "%s(): Rx %s TLLI=0x%08x SAPI=%s RadioPrio=%u ll=[%s]\n", + __func__, pdu_name, llc_prim->grr.tlli, + /* data_req.sapi in same union pos: */ + osmo_gprs_llc_sapi_name(llc_prim->grr.unitdata_req.sapi), + /* data_req.radio_prio in same union pos: */ + llc_prim->grr.unitdata_req.radio_prio, + osmo_hexdump(llc_prim->grr.ll_pdu, llc_prim->grr.ll_pdu_len)); + /* Forward it to lower layers, pass ownership over to RLCMAC: */ + /* Optimization: LLC-GRR-UNITDATA-IND is 1-to-1 ABI compatible with + RLCMAC-GRR-UNITDATA-IND, we just need to adapt the header. + See osmo_static_assert(_grr_prim_size) above. + */ + llc_prim->oph.sap = OSMO_GPRS_RLCMAC_SAP_GRR; + llc_prim->oph.primitive = OSMO_GPRS_RLCMAC_GRR_UNITDATA; + osmo_gprs_rlcmac_prim_upper_down((struct osmo_gprs_rlcmac_prim *)llc_prim); + rc = 1; /* Tell RLCMAC that we take ownership of the prim. */ + break; + default: + LOGP(DLLC, LOGL_DEBUG, "%s(): Unexpected Rx %s\n", __func__, pdu_name); + OSMO_ASSERT(0); + } + return rc; +} + +int modem_llc_init(struct osmocom_ms *ms, const char *cipher_plugin_path) +{ + int rc; + rc = osmo_gprs_llc_init(OSMO_GPRS_LLC_LOCATION_MS, cipher_plugin_path); + if (rc != 0) + return rc; + + osmo_gprs_llc_set_log_cat(OSMO_GPRS_LLC_LOGC_LLC, DLLC); + + osmo_gprs_llc_prim_set_up_cb(modem_llc_prim_up_cb, ms); + osmo_gprs_llc_prim_set_down_cb(modem_llc_prim_down_cb, ms); + return rc; +} diff --git a/src/host/layer23/src/modem/rlcmac.c b/src/host/layer23/src/modem/rlcmac.c new file mode 100644 index 00000000..7f7b5661 --- /dev/null +++ b/src/host/layer23/src/modem/rlcmac.c @@ -0,0 +1,199 @@ +/* GPRS RLC/MAC protocol implementation as per 3GPP TS 44.060 */ +/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * 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/lienses/>. + * + */ + +#include <errno.h> +#include <stdint.h> +#include <stdbool.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/bits.h> +#include <osmocom/core/prim.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/logging.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/gprs/rlcmac/rlcmac_prim.h> +#include <osmocom/gprs/rlcmac/rlcmac.h> +#include <osmocom/gprs/llc/llc_prim.h> +#include <osmocom/gprs/gmm/gmm_prim.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/modem/rlcmac.h> +#include <osmocom/bb/modem/grr.h> + +static int modem_rlcmac_handle_grr(struct osmo_gprs_rlcmac_prim *rlcmac_prim) +{ + int rc; + + osmo_static_assert(sizeof(struct osmo_gprs_rlcmac_grr_prim) == sizeof(struct osmo_gprs_llc_grr_prim), + _grr_prim_size); + + switch (rlcmac_prim->oph.primitive) { + case OSMO_GPRS_RLCMAC_GRR_UNITDATA: + /* Forward it to upper layers, pass ownership over to LLC: */ + /* Optimization: RLCMAC-GRR-UNITDATA-IND is 1-to-1 ABI compatible with + LLC-GRR-UNITDATA-IND, we just need to adapt the header. + See osmo_static_assert(_grr_prim_size) above. + */ + rlcmac_prim->oph.sap = OSMO_GPRS_LLC_SAP_GRR; + rlcmac_prim->oph.primitive = OSMO_GPRS_LLC_GRR_UNITDATA; + osmo_gprs_llc_prim_lower_up((struct osmo_gprs_llc_prim *)rlcmac_prim); + rc = 1; /* Tell RLCMAC that we take ownership of the prim. */ + break; + case OSMO_GPRS_RLCMAC_GRR_DATA: + default: + LOGP(DRLCMAC, LOGL_NOTICE, "%s(): Unexpected Rx RLCMAC GRR prim %u\n", + __func__, rlcmac_prim->oph.primitive); + rc = -EINVAL; + break; + } + return rc; +} + +static int modem_rlcmac_handle_gmmrr(struct osmo_gprs_rlcmac_prim *rlcmac_prim) +{ + struct osmo_gprs_gmm_prim *gmm_prim; + int rc; + + osmo_static_assert(sizeof(struct osmo_gprs_rlcmac_gmmrr_prim) == sizeof(struct osmo_gprs_gmm_gmmrr_prim), + _gmmrr_prim_size); + + switch (rlcmac_prim->oph.primitive) { + case OSMO_GPRS_RLCMAC_GMMRR_PAGE: + /* Forward it to upper layers, pass ownership over to GMM: */ + /* Optimization: RLCMAC-GMMRR-PAGE-IND is 1-to-1 ABI compatible with + GMM-GMMRR-PAGE-IND, we just need to adapt the header. + See osmo_static_assert(_gmmrr_prim_size) above. + */ + gmm_prim = (struct osmo_gprs_gmm_prim *)rlcmac_prim; + gmm_prim->oph.sap = OSMO_GPRS_GMM_SAP_GMMRR; + gmm_prim->oph.primitive = OSMO_GPRS_RLCMAC_GMMRR_PAGE; + osmo_gprs_gmm_prim_lower_up(gmm_prim); + rc = 1; /* Tell RLCMAC that we take ownership of the prim. */ + break; + case OSMO_GPRS_RLCMAC_GMMRR_LLC_TRANSMITTED: + /* Forward it to upper layers, pass ownership over to GMM: */ + /* Optimization: RLCMAC-GMMRR-LLC-TRANSMITTED-IND is 1-to-1 ABI compatible with + GMM-GMMRR-LLC-TRANSMITTED-IND, we just need to adapt the header. + See osmo_static_assert(_gmmrr_prim_size) above. + */ + gmm_prim = (struct osmo_gprs_gmm_prim *)rlcmac_prim; + gmm_prim->oph.sap = OSMO_GPRS_GMM_SAP_GMMRR; + gmm_prim->oph.primitive = OSMO_GPRS_GMM_GMMRR_LLC_TRANSMITTED; + osmo_gprs_gmm_prim_lower_up(gmm_prim); + rc = 1; /* Tell RLCMAC that we take ownership of the prim. */ + break; + default: + LOGP(DRLCMAC, LOGL_NOTICE, "%s(): Unexpected Rx RLCMAC GMMRR prim %u\n", + __func__, rlcmac_prim->oph.primitive); + rc = -EINVAL; + } + return rc; +} + +static int modem_rlcmac_prim_up_cb(struct osmo_gprs_rlcmac_prim *rlcmac_prim, void *user_data) +{ + const char *pdu_name = osmo_gprs_rlcmac_prim_name(rlcmac_prim); + int rc = 0; + + switch (rlcmac_prim->oph.sap) { + case OSMO_GPRS_RLCMAC_SAP_GRR: + LOGP(DRLCMAC, LOGL_DEBUG, "%s(): Rx %s TLLI=0x%08x ll=[%s]\n", + __func__, pdu_name, rlcmac_prim->grr.tlli, + osmo_hexdump(rlcmac_prim->grr.ll_pdu, rlcmac_prim->grr.ll_pdu_len)); + rc = modem_rlcmac_handle_grr(rlcmac_prim); + break; + case OSMO_GPRS_RLCMAC_SAP_GMMRR: + LOGP(DRLCMAC, LOGL_DEBUG, "%s(): Rx %s\n", + __func__, pdu_name); + rc = modem_rlcmac_handle_gmmrr(rlcmac_prim); + break; + default: + LOGP(DRLCMAC, LOGL_NOTICE, "%s(): Unexpected Rx %s\n", __func__, pdu_name); + OSMO_ASSERT(0); + } + return rc; +} + +static int modem_rlcmac_prim_down_cb(struct osmo_gprs_rlcmac_prim *prim, void *user_data) +{ + struct osmo_gprs_rlcmac_l1ctl_prim *lp = &prim->l1ctl; + const char *pdu_name = osmo_gprs_rlcmac_prim_name(prim); + struct osmocom_ms *ms = user_data; + + switch (OSMO_PRIM_HDR(&prim->oph)) { + case OSMO_PRIM(OSMO_GPRS_RLCMAC_L1CTL_RACH, PRIM_OP_REQUEST): + return osmo_fsm_inst_dispatch(ms->grr_fi, GRR_EV_CHAN_ACCESS_REQ, lp); + case OSMO_PRIM(OSMO_GPRS_RLCMAC_L1CTL_PDCH_DATA, PRIM_OP_REQUEST): + return osmo_fsm_inst_dispatch(ms->grr_fi, GRR_EV_PDCH_BLOCK_REQ, lp); + case OSMO_PRIM(OSMO_GPRS_RLCMAC_L1CTL_CFG_UL_TBF, PRIM_OP_REQUEST): + return osmo_fsm_inst_dispatch(ms->grr_fi, GRR_EV_PDCH_UL_TBF_CFG_REQ, lp); + case OSMO_PRIM(OSMO_GPRS_RLCMAC_L1CTL_CFG_DL_TBF, PRIM_OP_REQUEST): + return osmo_fsm_inst_dispatch(ms->grr_fi, GRR_EV_PDCH_DL_TBF_CFG_REQ, lp); + case OSMO_PRIM(OSMO_GPRS_RLCMAC_L1CTL_PDCH_ESTABLISH, PRIM_OP_REQUEST): + return osmo_fsm_inst_dispatch(ms->grr_fi, GRR_EV_PDCH_ESTABLISH_REQ, lp); + case OSMO_PRIM(OSMO_GPRS_RLCMAC_L1CTL_PDCH_RELEASE, PRIM_OP_REQUEST): + return osmo_fsm_inst_dispatch(ms->grr_fi, GRR_EV_PDCH_RELEASE_REQ, lp); + default: + LOGP(DRLCMAC, LOGL_DEBUG, "%s(): Unexpected Rx %s\n", __func__, pdu_name); + OSMO_ASSERT(0); + } +} + +static int l1ctl_ul_block_cnf_cb(struct osmocom_ms *ms, struct msgb *msg) +{ + return osmo_fsm_inst_dispatch(ms->grr_fi, GRR_EV_PDCH_BLOCK_CNF, msg); +} + +static int l1ctl_dl_block_ind_cb(struct osmocom_ms *ms, struct msgb *msg) +{ + return osmo_fsm_inst_dispatch(ms->grr_fi, GRR_EV_PDCH_BLOCK_IND, msg); +} + +static int l1ctl_rts_ind_cb(struct osmocom_ms *ms, struct msgb *msg) +{ + return osmo_fsm_inst_dispatch(ms->grr_fi, GRR_EV_PDCH_RTS_IND, msg); +} + +int modem_rlcmac_init(struct osmocom_ms *ms) +{ + int rc; + rc = osmo_gprs_rlcmac_init(OSMO_GPRS_RLCMAC_LOCATION_MS); + if (rc != 0) + return rc; + + osmo_gprs_rlcmac_set_log_cat(OSMO_GPRS_RLCMAC_LOGC_RLCMAC, DRLCMAC); + osmo_gprs_rlcmac_set_log_cat(OSMO_GPRS_RLCMAC_LOGC_TBFUL, DRLCMAC); + osmo_gprs_rlcmac_set_log_cat(OSMO_GPRS_RLCMAC_LOGC_TBFDL, DRLCMAC); + + osmo_gprs_rlcmac_prim_set_up_cb(modem_rlcmac_prim_up_cb, ms); + osmo_gprs_rlcmac_prim_set_down_cb(modem_rlcmac_prim_down_cb, ms); + + ms->l1_entity.l1_gprs_ul_block_cnf = &l1ctl_ul_block_cnf_cb; + ms->l1_entity.l1_gprs_dl_block_ind = &l1ctl_dl_block_ind_cb; + ms->l1_entity.l1_gprs_rts_ind = &l1ctl_rts_ind_cb; + + return rc; +} diff --git a/src/host/layer23/src/modem/sm.c b/src/host/layer23/src/modem/sm.c new file mode 100644 index 00000000..1977cd6c --- /dev/null +++ b/src/host/layer23/src/modem/sm.c @@ -0,0 +1,277 @@ +/* GPRS SM interfaces as per 3GPP TS 24.008, TS 24.007 */ +/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * 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/lienses/>. + * + */ + +#include <stdbool.h> +#include <string.h> +#include <stdint.h> +#include <errno.h> +#include <stdio.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/prim.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/tun.h> +#include <osmocom/gsm/protocol/gsm_04_08_gprs.h> + +#include <osmocom/gprs/gmm/gmm.h> +#include <osmocom/gprs/gmm/gmm_prim.h> +#include <osmocom/gprs/sndcp/sndcp.h> +#include <osmocom/gprs/sndcp/sndcp_prim.h> +#include <osmocom/gprs/sm/sm_prim.h> +#include <osmocom/gprs/sm/sm.h> +#include <osmocom/gprs/rlcmac/rlcmac_prim.h> + +#include <osmocom/bb/common/settings.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/apn.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/modem/sm.h> + + +static int modem_sm_handle_pdp_act_cnf(struct osmocom_ms *ms, struct osmo_gprs_sm_prim *sm_prim) +{ + const char *pdu_name = osmo_gprs_sm_prim_name(sm_prim); + struct osmobb_apn *apn = NULL, *apn_it; + struct osmo_netdev *netdev; + char buf_addr[INET6_ADDRSTRLEN]; + char buf_addr2[INET6_ADDRSTRLEN]; + int rc; + + llist_for_each_entry(apn_it, &ms->gprs.apn_list, list) { + if (apn_it->fsm.fi->state == APN_ST_ACTIVATING) { + apn = apn_it; + break; + } + } + + if (!apn) { + LOGP(DSM, LOGL_ERROR, "Rx %s but have no APN!\n", pdu_name); + return -ENOENT; + } + + if (!sm_prim->smreg.pdp_act_cnf.accepted) { + LOGPAPN(LOGL_ERROR, apn, "Rx %s: Activate PDP failed! cause '%s'\n", pdu_name, + get_value_string(gsm48_gsm_cause_names, sm_prim->smreg.pdp_act_cnf.rej.cause)); + osmo_fsm_inst_dispatch(apn->fsm.fi, APN_EV_RX_SM_ACT_PDP_CTX_REJ, NULL); + /* TODO: maybe retry ? */ + return 0; + } + + ms->subscr.gprs.ptmsi = sm_prim->smreg.pdp_act_cnf.acc.gmm.allocated_ptmsi; + ms->gmmlayer.tlli = sm_prim->smreg.pdp_act_cnf.acc.gmm.allocated_tlli; + + apn->pdp.pdp_addr_ietf_type = sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_ietf_type; + + netdev = osmo_tundev_get_netdev(apn->tun); + switch (sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_ietf_type) { + case OSMO_GPRS_SM_PDP_ADDR_IETF_IPV4: + LOGPAPN(LOGL_INFO, apn, "Rx %s: IPv4=%s\n", pdu_name, + osmo_sockaddr_ntop(&sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v4.u.sa, buf_addr)); + memcpy(&apn->pdp.pdp_addr_v4, &sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v4, sizeof(struct osmo_sockaddr)); + rc = osmo_netdev_add_addr(netdev, &sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v4, 30); + if (rc < 0) { + LOGPAPN(LOGL_ERROR, apn, "Rx %s: Failed setting IPv4=%s\n", pdu_name, + osmo_sockaddr_ntop(&sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v4.u.sa, buf_addr)); + return rc; + } + break; + case OSMO_GPRS_SM_PDP_ADDR_IETF_IPV6: + LOGPAPN(LOGL_INFO, apn, "Rx %s: IPv6=%s [FIXME: IPv6 not yet supported!]\n", pdu_name, + osmo_sockaddr_ntop(&sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v6.u.sa, buf_addr)); + memcpy(&apn->pdp.pdp_addr_v6, &sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v6, sizeof(struct osmo_sockaddr)); + rc = osmo_netdev_add_addr(netdev, &sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v6, 64); + if (rc < 0) { + LOGPAPN(LOGL_ERROR, apn, "Rx %s: Failed setting IPv6=%s\n", pdu_name, + osmo_sockaddr_ntop(&sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v6.u.sa, buf_addr)); + return rc; + } + break; + case OSMO_GPRS_SM_PDP_ADDR_IETF_IPV4V6: + LOGPAPN(LOGL_INFO, apn, "Rx %s: IPv4=%s IPv6=%s [FIXME: IPv6 not yet supported!]\n", pdu_name, + osmo_sockaddr_ntop(&sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v4.u.sa, buf_addr), + osmo_sockaddr_ntop(&sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v6.u.sa, buf_addr2)); + memcpy(&apn->pdp.pdp_addr_v4, &sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v4, sizeof(struct osmo_sockaddr)); + memcpy(&apn->pdp.pdp_addr_v6, &sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v6, sizeof(struct osmo_sockaddr)); + rc = osmo_netdev_add_addr(netdev, &sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v4, 30); + if (rc < 0) { + LOGPAPN(LOGL_ERROR, apn, "Rx %s: Failed setting IPv4=%s\n", pdu_name, + osmo_sockaddr_ntop(&sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v4.u.sa, buf_addr)); + return rc; + } + rc = osmo_netdev_add_addr(netdev, &sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v6, 64); + if (rc < 0) { + LOGPAPN(LOGL_ERROR, apn, "Rx %s: Failed setting IPv6=%s\n", pdu_name, + osmo_sockaddr_ntop(&sm_prim->smreg.pdp_act_cnf.acc.pdp_addr_v6.u.sa, buf_addr)); + return rc; + } + break; + default: + OSMO_ASSERT(0); + } + + /* TODO: Handle PCO */ + /* TODO: Handle QoS */ + + osmo_fsm_inst_dispatch(apn->fsm.fi, APN_EV_RX_SM_ACT_PDP_CTX_ACC, NULL); + return rc; +} + +static int modem_sm_prim_up_cb(struct osmo_gprs_sm_prim *sm_prim, void *user_data) +{ + const char *pdu_name = osmo_gprs_sm_prim_name(sm_prim); + struct osmocom_ms *ms = user_data; + int rc = 0; + + switch (sm_prim->oph.sap) { + case OSMO_GPRS_SM_SAP_SMREG: + switch (OSMO_PRIM_HDR(&sm_prim->oph)) { + case OSMO_PRIM(OSMO_GPRS_SM_SMREG_PDP_ACTIVATE, PRIM_OP_CONFIRM): + modem_sm_handle_pdp_act_cnf(ms, sm_prim); + break; + case OSMO_PRIM(OSMO_GPRS_SM_SMREG_PDP_ACTIVATE, PRIM_OP_INDICATION): + case OSMO_PRIM(OSMO_GPRS_SM_SMREG_PDP_DEACTIVATE, PRIM_OP_CONFIRM): + case OSMO_PRIM(OSMO_GPRS_SM_SMREG_PDP_DEACTIVATE, PRIM_OP_INDICATION): + case OSMO_PRIM(OSMO_GPRS_SM_SMREG_PDP_MODIFY, PRIM_OP_CONFIRM): + case OSMO_PRIM(OSMO_GPRS_SM_SMREG_PDP_MODIFY, PRIM_OP_INDICATION): + case OSMO_PRIM(OSMO_GPRS_SM_SMREG_PDP_ACTIVATE_SEC, PRIM_OP_CONFIRM): + default: + LOGP(DSM, LOGL_ERROR, "%s(): Rx %s UNIMPLEMENTED\n", __func__, pdu_name); + break; + }; + break; + default: + LOGP(DSM, LOGL_ERROR, "%s(): Unexpected Rx %s\n", __func__, pdu_name); + OSMO_ASSERT(0); + } + + return rc; +} + +int modem_sm_prim_sndcp_up_cb(struct osmo_gprs_sndcp_prim *sndcp_prim, void *user_data) +{ + const char *pdu_name = osmo_gprs_sndcp_prim_name(sndcp_prim); + int rc; + + switch (sndcp_prim->oph.sap) { + case OSMO_GPRS_SNDCP_SAP_SNSM: + switch (OSMO_PRIM_HDR(&sndcp_prim->oph)) { + case OSMO_PRIM(OSMO_GPRS_SNDCP_SNSM_ACTIVATE, PRIM_OP_INDICATION): + LOGP(DSM, LOGL_INFO, "%s(): Rx %s\n", __func__, pdu_name); + rc = osmo_gprs_sndcp_prim_dispatch_snsm(sndcp_prim); + rc = 1; /* Tell SM that we take ownership of the prim. */ + break; + default: + LOGP(DSM, LOGL_ERROR, "%s(): Unexpected Rx %s\n", __func__, pdu_name); + OSMO_ASSERT(0); + } + break; + default: + LOGP(DSM, LOGL_ERROR, "%s(): Unexpected Rx %s\n", __func__, pdu_name); + OSMO_ASSERT(0); + } + return rc; +} + +static int modem_sm_prim_down_cb(struct osmo_gprs_sm_prim *sm_prim, void *user_data) +{ + const char *pdu_name = osmo_gprs_sm_prim_name(sm_prim); + int rc = 0; + + switch (sm_prim->oph.sap) { + default: + LOGP(DSM, LOGL_ERROR, "%s(): Unexpected Rx %s\n", __func__, pdu_name); + OSMO_ASSERT(0); + } + + return rc; +} + +static int modem_sm_prim_gmm_down_cb(struct osmo_gprs_gmm_prim *gmm_prim, void *user_data) +{ + int rc; + + rc = osmo_gprs_gmm_prim_upper_down(gmm_prim); + + /* GMM took ownership of the message, tell SM layer to not free it: */ + rc = 1; + return rc; +} + +int modem_sm_init(struct osmocom_ms *ms) +{ + int rc; + rc = osmo_gprs_sm_init(OSMO_GPRS_SM_LOCATION_MS); + if (rc != 0) + return rc; + + osmo_gprs_sm_set_log_cat(OSMO_GPRS_SM_LOGC_SM, DSM); + + osmo_gprs_sm_prim_set_up_cb(modem_sm_prim_up_cb, ms); + osmo_gprs_sm_prim_set_sndcp_up_cb(modem_sm_prim_sndcp_up_cb, ms); + osmo_gprs_sm_prim_set_down_cb(modem_sm_prim_down_cb, ms); + osmo_gprs_sm_prim_set_gmm_down_cb(modem_sm_prim_gmm_down_cb, ms); + + return rc; +} + +int modem_sm_smreg_pdp_act_req(const struct osmocom_ms *ms, const struct osmobb_apn *apn) +{ + struct osmo_gprs_sm_prim *sm_prim; + const struct gsm_subscriber *subscr = &ms->subscr; + enum osmo_gprs_sm_pdp_addr_ietf_type pdp_addr_ietf_type; + struct osmo_sockaddr pdp_addr_any = {0}; + int rc; + + if (apn->cfg.apn_type_mask & APN_TYPE_IPv4v6) { + pdp_addr_ietf_type = OSMO_GPRS_SM_PDP_ADDR_IETF_IPV4V6; + } else if (apn->cfg.apn_type_mask & APN_TYPE_IPv4) { + pdp_addr_ietf_type = OSMO_GPRS_SM_PDP_ADDR_IETF_IPV4; + } else if (apn->cfg.apn_type_mask & APN_TYPE_IPv6) { + pdp_addr_ietf_type = OSMO_GPRS_SM_PDP_ADDR_IETF_IPV6; + } else { + LOGP(DSM, LOGL_ERROR, "APN '%s' has no PDP address type set\n", apn->cfg.name); + return -EINVAL; + } + + sm_prim = osmo_gprs_sm_prim_alloc_smreg_pdp_act_req(); + sm_prim->smreg.pdp_act_req.nsapi = apn->pdp.nsapi; + sm_prim->smreg.pdp_act_req.llc_sapi = apn->pdp.llc_sapi; + sm_prim->smreg.pdp_act_req.pdp_addr_ietf_type = pdp_addr_ietf_type; + sm_prim->smreg.pdp_act_req.pdp_addr_v4 = pdp_addr_any; + sm_prim->smreg.pdp_act_req.pdp_addr_v6 = pdp_addr_any; + memcpy(sm_prim->smreg.pdp_act_req.qos, apn->pdp.qos, apn->pdp.qos_len); + sm_prim->smreg.pdp_act_req.qos_len = apn->pdp.qos_len; + memcpy(sm_prim->smreg.pdp_act_req.pco, apn->pdp.pco, apn->pdp.pco_len); + sm_prim->smreg.pdp_act_req.pco_len = apn->pdp.pco_len; + OSMO_STRLCPY_ARRAY(sm_prim->smreg.pdp_act_req.apn, apn->cfg.name); + sm_prim->smreg.pdp_act_req.gmm.ptmsi = subscr->gprs.ptmsi; + sm_prim->smreg.pdp_act_req.gmm.ptmsi_sig = subscr->gprs.ptmsi; + OSMO_STRLCPY_ARRAY(sm_prim->smreg.pdp_act_req.gmm.imsi, subscr->imsi); + OSMO_STRLCPY_ARRAY(sm_prim->smreg.pdp_act_req.gmm.imei, ms->settings.imei); + OSMO_STRLCPY_ARRAY(sm_prim->smreg.pdp_act_req.gmm.imeisv, ms->settings.imeisv); + memcpy(&sm_prim->smreg.pdp_act_req.gmm.old_rai, &subscr->gprs.rai, sizeof(subscr->gprs.rai)); + + rc = osmo_gprs_sm_prim_upper_down(sm_prim); + if (rc < 0) + LOGP(DSM, LOGL_ERROR, "Failed submitting SMREG-PDP_ACT_REQ.req\n"); + return rc; +} diff --git a/src/host/layer23/src/modem/sndcp.c b/src/host/layer23/src/modem/sndcp.c new file mode 100644 index 00000000..d5b38213 --- /dev/null +++ b/src/host/layer23/src/modem/sndcp.c @@ -0,0 +1,217 @@ +/* GPRS SNDCP User/SN/SNSM interfaces as per 3GPP TS 04.65 */ +/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * 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/lienses/>. + * + */ + +#include <stdbool.h> +#include <string.h> +#include <stdint.h> +#include <errno.h> +#include <stdio.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/prim.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/tun.h> + +#include <osmocom/gprs/llc/llc.h> +#include <osmocom/gprs/llc/llc_prim.h> +#include <osmocom/gprs/sm/sm_prim.h> +#include <osmocom/gprs/sm/sm.h> +#include <osmocom/gprs/sndcp/sndcp_prim.h> +#include <osmocom/gprs/sndcp/sndcp.h> + +#include <osmocom/bb/common/settings.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/apn.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/modem/sndcp.h> + +/* Received SN-XID.cnf from SNDCP layer: */ +static int modem_sndcp_handle_sn_xid_cnf(struct osmobb_apn *apn, struct osmo_gprs_sndcp_prim *sndcp_prim) +{ + LOGP(DSNDCP, LOGL_ERROR, "%s(): Rx SN-XID.cnf: TODO IMPLEMENT!\n", __func__); + return 0; +} + +/* Received SN-UNITDTA.ind from SNDCP layer: */ +static int modem_sndcp_handle_sn_unitdata_ind(struct osmobb_apn *apn, struct osmo_gprs_sndcp_prim *sndcp_prim) +{ + const char *npdu_name = osmo_gprs_sndcp_prim_name(sndcp_prim); + struct msgb *msg; + int rc; + + LOGP(DSNDCP, LOGL_DEBUG, "Rx %s TLLI=0x%08x SAPI=%s NSAPI=%u NPDU=[%s]\n", + npdu_name, + sndcp_prim->sn.tlli, osmo_gprs_llc_sapi_name(sndcp_prim->sn.sapi), + sndcp_prim->sn.data_req.nsapi, + osmo_hexdump(sndcp_prim->sn.data_ind.npdu, sndcp_prim->sn.data_ind.npdu_len)); + + msg = msgb_alloc(sndcp_prim->sn.data_ind.npdu_len, "tx_tun"); + memcpy(msgb_put(msg, sndcp_prim->sn.data_ind.npdu_len), + sndcp_prim->sn.data_ind.npdu, + sndcp_prim->sn.data_ind.npdu_len); + rc = osmo_tundev_send(apn->tun, msg); + return rc; +} + +static int modem_sndcp_prim_up_cb(struct osmo_gprs_sndcp_prim *sndcp_prim, void *user_data) +{ + struct osmocom_ms *ms = user_data; + struct osmobb_apn *apn = NULL, *apn_it; + const char *npdu_name = osmo_gprs_sndcp_prim_name(sndcp_prim); + int rc = 0; + + if (sndcp_prim->oph.sap != OSMO_GPRS_SNDCP_SAP_SN) { + LOGP(DSNDCP, LOGL_ERROR, "%s(): Unexpected Rx %s\n", __func__, npdu_name); + OSMO_ASSERT(0); + } + + if (ms->gmmlayer.tlli != sndcp_prim->sn.tlli) { + LOGP(DSNDCP, LOGL_ERROR, "%s(): Rx %s: MS has no TLLI=0x%08x\n", __func__, npdu_name, sndcp_prim->sn.tlli); + return -ENOENT; + } + + llist_for_each_entry(apn_it, &ms->gprs.apn_list, list) { + if (apn_it->pdp.nsapi != sndcp_prim->sn.unitdata_ind.nsapi) + continue; + apn = apn_it; + break; + } + if (!apn) { + LOGP(DSNDCP, LOGL_NOTICE, "Unable to find destination APN: Rx %s\n", npdu_name); + return -ENODEV; + } + + switch (OSMO_PRIM_HDR(&sndcp_prim->oph)) { + case OSMO_PRIM(OSMO_GPRS_SNDCP_SN_UNITDATA, PRIM_OP_INDICATION): + rc = modem_sndcp_handle_sn_unitdata_ind(apn, sndcp_prim); + break; + case OSMO_PRIM(OSMO_GPRS_SNDCP_SN_XID, PRIM_OP_CONFIRM): + rc = modem_sndcp_handle_sn_xid_cnf(apn, sndcp_prim); + break; + default: + LOGP(DSNDCP, LOGL_ERROR, "%s(): Rx %s UNIMPLEMENTED\n", __func__, npdu_name); + break; + }; + return rc; +} + +static int modem_sndcp_prim_down_cb(struct osmo_gprs_llc_prim *llc_prim, void *user_data) +{ + const char *pdu_name = osmo_gprs_llc_prim_name(llc_prim); + int rc = 0; + + if (llc_prim->oph.sap != OSMO_GPRS_LLC_SAP_LL) { + LOGP(DSNDCP, LOGL_ERROR, "%s(): Unexpected Rx %s\n", __func__, pdu_name); + OSMO_ASSERT(0); + } + + switch (OSMO_PRIM_HDR(&llc_prim->oph)) { + case OSMO_PRIM(OSMO_GPRS_LLC_LL_UNITDATA, PRIM_OP_REQUEST): + case OSMO_PRIM(OSMO_GPRS_LLC_LL_XID, PRIM_OP_REQUEST): + LOGP(DSNDCP, LOGL_DEBUG, "%s(): Rx %s TLLI=0x%08x SAPI=%s L3=[%s]\n", + __func__, pdu_name, + llc_prim->ll.tlli, osmo_gprs_llc_sapi_name(llc_prim->ll.sapi), + osmo_hexdump(llc_prim->ll.l3_pdu, llc_prim->ll.l3_pdu_len)); + rc = osmo_gprs_llc_prim_upper_down(llc_prim); + rc = 1; /* Tell SNDCP layer we took msgb ownsership and transfer it to LLC */ + break; + default: + LOGP(DSNDCP, LOGL_ERROR, "%s(): Rx %s UNIMPLEMENTED\n", __func__, pdu_name); + break; + }; + return rc; +} + +static int modem_sndcp_prim_snsm_cb(struct osmo_gprs_sndcp_prim *sndcp_prim, void *user_data) +{ + const char *npdu_name = osmo_gprs_sndcp_prim_name(sndcp_prim); + int rc = 0; + + if (sndcp_prim->oph.sap != OSMO_GPRS_SNDCP_SAP_SNSM) { + LOGP(DSNDCP, LOGL_ERROR, "%s(): Unexpected Rx %s\n", __func__, npdu_name); + OSMO_ASSERT(0); + } + + switch (OSMO_PRIM_HDR(&sndcp_prim->oph)) { + case OSMO_PRIM(OSMO_GPRS_SM_SMREG_PDP_ACTIVATE, PRIM_OP_RESPONSE): + LOGP(DSNDCP, LOGL_INFO, "%s(): Rx %s\n", __func__, npdu_name); + rc = osmo_gprs_sm_prim_sndcp_upper_down(sndcp_prim); + rc = 1; /* Tell SNDCP layer we took msgb ownership and transfer it to SM */ + break; + default: + LOGP(DSNDCP, LOGL_ERROR, "%s(): Unexpected Rx %s\n", __func__, npdu_name); + OSMO_ASSERT(0); + } + return rc; +} + + +int modem_sndcp_init(struct osmocom_ms *ms) +{ + int rc; + rc = osmo_gprs_sndcp_init(OSMO_GPRS_SNDCP_LOCATION_MS); + if (rc != 0) + return rc; + + osmo_gprs_sndcp_set_log_cat(OSMO_GPRS_SNDCP_LOGC_SNDCP, DSNDCP); + osmo_gprs_sndcp_set_log_cat(OSMO_GPRS_SNDCP_LOGC_SLHC, DSNDCP); + + osmo_gprs_sndcp_prim_set_up_cb(modem_sndcp_prim_up_cb, ms); + osmo_gprs_sndcp_prim_set_down_cb(modem_sndcp_prim_down_cb, ms); + osmo_gprs_sndcp_prim_set_snsm_cb(modem_sndcp_prim_snsm_cb, ms); + return rc; +} + +int modem_sndcp_sn_xid_req(struct osmobb_apn *apn) +{ + struct osmo_gprs_sndcp_prim *sndcp_prim; + int rc; + struct osmocom_ms *ms = apn->ms; + struct gprs_settings *set = &ms->gprs; + + sndcp_prim = osmo_gprs_sndcp_prim_alloc_sn_xid_req(ms->gmmlayer.tlli, + apn->pdp.llc_sapi, + apn->pdp.nsapi); + OSMO_ASSERT(sndcp_prim); + sndcp_prim->sn.xid_req.pcomp_rfc1144.active = set->pcomp_rfc1144.active; + sndcp_prim->sn.xid_req.pcomp_rfc1144.s01 = set->pcomp_rfc1144.s01; + sndcp_prim->sn.xid_req.dcomp_v42bis.active = set->dcomp_v42bis.active; + sndcp_prim->sn.xid_req.dcomp_v42bis.p0 = set->dcomp_v42bis.p0; + sndcp_prim->sn.xid_req.dcomp_v42bis.p1 = set->dcomp_v42bis.p1; + sndcp_prim->sn.xid_req.dcomp_v42bis.p2 = set->dcomp_v42bis.p2; + rc = osmo_gprs_sndcp_prim_upper_down(sndcp_prim); + return rc; +} + +int modem_sndcp_sn_unitdata_req(struct osmobb_apn *apn, uint8_t *npdu, size_t npdu_len) +{ + struct osmo_gprs_sndcp_prim *sndcp_prim; + int rc; + + sndcp_prim = osmo_gprs_sndcp_prim_alloc_sn_unitdata_req(apn->ms->gmmlayer.tlli, + apn->pdp.llc_sapi, + apn->pdp.nsapi, + npdu, npdu_len); + OSMO_ASSERT(sndcp_prim); + rc = osmo_gprs_sndcp_prim_upper_down(sndcp_prim); + return rc; +} diff --git a/src/host/layer23/src/modem/vty.c b/src/host/layer23/src/modem/vty.c new file mode 100644 index 00000000..f42aa669 --- /dev/null +++ b/src/host/layer23/src/modem/vty.c @@ -0,0 +1,450 @@ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * 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. + * + */ + +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/linuxlist.h> + +#include <osmocom/gprs/llc/llc.h> +#include <osmocom/gprs/llc/llc_prim.h> +#include <osmocom/gprs/gmm/gmm_prim.h> +#include <osmocom/gprs/sm/sm_prim.h> +#include <osmocom/gprs/rlcmac/rlcmac_prim.h> + + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> + +#include <osmocom/bb/common/settings.h> +#include <osmocom/bb/common/vty.h> +#include <osmocom/bb/common/apn.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/modem/gmm.h> +#include <osmocom/bb/modem/grr.h> +#include <osmocom/bb/modem/sm.h> +#include <osmocom/bb/modem/vty.h> + +static struct cmd_node apn_node = { + APN_NODE, + "%s(apn)# ", + 1 +}; + +int modem_vty_go_parent(struct vty *vty) +{ + struct osmobb_apn *apn; + + switch (vty->node) { + case APN_NODE: + apn = vty->index; + vty->index = apn->ms; + vty->node = MS_NODE; + break; + } + return vty->node; +} + +#define MS_NAME_DESC "Name of MS (see \"show ms\")\n" +#define TEST_CMD_DESC "Testing commands for developers\n" +#define GRR_CMDG_DESC "GPRS RR specific commands\n" +#define LLC_CMDG_DESC "GPRS LLC specific commands\n" +#define GMM_CMDG_DESC "GPRS GMM specific commands\n" +#define SM_CMDG_DESC "GPRS SM specific commands\n" + +/* testing commands */ +DEFUN_HIDDEN(test_grr_tx_chan_req, + test_grr_tx_chan_req_cmd, + "test MS_NAME grr start-chan-access (1phase|2phase)", + TEST_CMD_DESC MS_NAME_DESC GRR_CMDG_DESC + "Send a CHANNEL REQUEST (RACH) to the network\n" + "One-phase packet access (011110xx or 01111x0x or 01111xx0)\n" + "Two-phase (single block) packet access (01110xxx)\n") +{ + struct osmocom_ms *ms; + + if ((ms = l23_vty_get_ms(argv[0], vty)) == NULL) + return CMD_WARNING; + + const struct osmo_gprs_rlcmac_l1ctl_prim lp = { + .rach_req = { + .is_11bit = false, + /* the 3 LSBs are randomized during (re)transmission */ + .ra = argv[1][0] == '1' ? 0x78 : 0x70, + } + }; + + if (osmo_fsm_inst_dispatch(ms->grr_fi, GRR_EV_CHAN_ACCESS_REQ, (void *)&lp)) { + vty_out(vty, "Failed to send a CHANNEL REQUEST%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN(test_llc_unitdata_req_hexpdu, + test_llc_unitdata_req_hexpdu_cmd, + "test MS_NAME llc unitdata-req <0x00-0xffffffff> SAPI HEXSTRING", + TEST_CMD_DESC MS_NAME_DESC LLC_CMDG_DESC + "Enqueue an LLC UNITDATA.req for transmission\n" + "TLLI (Temporary Logical Link Identifier) value to be used\n" + "SAPI value to be used (for example, GMM, SMS, SNDCP3)\n" + "LLC PDU as a hexstring (up to 512 octets)\n") +{ + struct osmo_gprs_llc_prim *llc_prim; + struct osmocom_ms *ms; + uint8_t buf[512]; + int tlli, sapi, pdu_len; + + if ((ms = l23_vty_get_ms(argv[0], vty)) == NULL) + return CMD_WARNING; + + if (osmo_str_to_int(&tlli, argv[1], 0, 0, 0xffffff) < 0) + return CMD_WARNING; + sapi = get_string_value(osmo_gprs_llc_sapi_names, argv[2]); + if (sapi < 0) + return CMD_WARNING; + pdu_len = osmo_hexparse(argv[3], &buf[0], sizeof(buf)); + if (pdu_len < 0) + return CMD_WARNING; + + llc_prim = osmo_gprs_llc_prim_alloc_ll_unitdata_req(tlli, sapi, &buf[0], pdu_len); + if (osmo_gprs_llc_prim_upper_down(llc_prim) != 0) { + vty_out(vty, "Failed to enqueue an LLC PDU%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +static uint8_t pdu_gmmm_attach_req[] = { + 0x08, 0x01, 0x02, 0xe5, 0xe0, 0x01, 0x0a, 0x00, 0x05, 0xf4, 0xf4, 0x3c, 0xec, 0x71, 0x32, 0xf4, + 0x07, 0x00, 0x05, 0x00, 0x17, 0x19, 0x33, 0x43, 0x2b, 0x37, 0x15, 0x9e, 0xf9, 0x88, 0x79, 0xcb, + 0xa2, 0x8c, 0x66, 0x21, 0xe7, 0x26, 0x88, 0xb1, 0x98, 0x87, 0x9c, 0x00, 0x17, 0x05, +}; + +/* TODO: remove this command once we have the GMM layer implemented */ +DEFUN_HIDDEN(test_llc_unitdata_req_gmm_attch, + test_llc_unitdata_req_gmm_attch_cmd, + "test MS_NAME llc unitdata-req gmm-attach-req", + TEST_CMD_DESC MS_NAME_DESC LLC_CMDG_DESC + "Enqueue an LLC UNITDATA.req for transmission\n" + "Hard-coded GMM Attach Request (SAPI=GMM, TLLI=0xe1c5d364)\n") +{ + struct osmo_gprs_llc_prim *llc_prim; + const uint32_t tlli = 0xe1c5d364; + struct osmocom_ms *ms; + + if ((ms = l23_vty_get_ms(argv[0], vty)) == NULL) + return CMD_WARNING; + + llc_prim = osmo_gprs_llc_prim_alloc_ll_unitdata_req(tlli, OSMO_GPRS_LLC_SAPI_GMM, + &pdu_gmmm_attach_req[0], + sizeof(pdu_gmmm_attach_req)); + if (osmo_gprs_llc_prim_upper_down(llc_prim) != 0) { + vty_out(vty, "Failed to enqueue an LLC PDU%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN(test_gmm_reg_attach, + test_gmm_reg_attach_cmd, + "test MS_NAME gmm attach", + TEST_CMD_DESC MS_NAME_DESC GMM_CMDG_DESC + "Enqueue a GMM GMMREG-ATTACH.req for transmission\n") +{ + struct osmocom_ms *ms; + + if ((ms = l23_vty_get_ms(argv[0], vty)) == NULL) { + vty_out(vty, "Failed to find ms '%s'%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + if (modem_gmm_gmmreg_attach_req(ms) < 0) { + vty_out(vty, "Failed to enqueue a GMM PDU%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN(test_gmm_reg_detach, + test_gmm_reg_detach_cmd, + "test MS_NAME gmm detach", + TEST_CMD_DESC MS_NAME_DESC GMM_CMDG_DESC + "Enqueue a GMM GMMREG-DETACH.req for transmission\n") +{ + struct osmocom_ms *ms; + + if ((ms = l23_vty_get_ms(argv[0], vty)) == NULL) { + vty_out(vty, "Failed to find ms '%s'%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + if (modem_gmm_gmmreg_detach_req(ms) < 0) { + vty_out(vty, "Failed to enqueue a GMM PDU%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN(test_sm_act_pdp_ctx, + test_sm_act_pdp_ctx_cmd, + "test MS_NAME sm act-pdp-ctx APN", + TEST_CMD_DESC MS_NAME_DESC SM_CMDG_DESC + "Enqueue a SM SMREG-ACTIVATE.req for transmission\n" + "APN to activate\n") +{ + struct osmocom_ms *ms; + struct osmobb_apn *apn; + + if ((ms = l23_vty_get_ms(argv[0], vty)) == NULL) { + vty_out(vty, "Unable to find MS '%s'%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + apn = ms_find_apn_by_name(ms, argv[1]); + if (!apn) { + vty_out(vty, "Unable to find APN '%s'%s", argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + if (modem_sm_smreg_pdp_act_req(ms, apn) < 0) { + vty_out(vty, "Failed submitting SM PDP Act Req%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +/* per APN config */ +DEFUN(cfg_ms_apn, cfg_ms_apn_cmd, "apn APN_NAME", + "Configure an APN\n" + "Name of APN\n") +{ + struct osmocom_ms *ms = vty->index; + struct osmobb_apn *apn; + + apn = ms_find_apn_by_name(ms, argv[0]); + if (!apn) + apn = apn_alloc(ms, argv[0]); + if (!apn) { + vty_out(vty, "Unable to create APN '%s'%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + vty->index = apn; + vty->node = APN_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_no_apn, cfg_ms_no_apn_cmd, "no apn APN_NAME", + NO_STR "Configure an APN\n" + "Name of APN\n") +{ + struct osmocom_ms *ms = vty->index; + struct osmobb_apn *apn; + bool on = false; + + apn = ms_find_apn_by_name(ms, argv[0]); + if (!apn) { + vty_out(vty, "Unable to find APN '%s'%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + /* Disable APN before getting rid of it. */ + osmo_fsm_inst_dispatch(apn->fsm.fi, APN_EV_GPRS_ALLOWED, &on); + + apn_free(apn); + + return CMD_SUCCESS; +} + +DEFUN(cfg_apn_tun_dev_name, cfg_apn_tun_dev_name_cmd, + "tun-device NAME", + "Configure tun device name\n" + "TUN device name") +{ + struct osmobb_apn *apn = (struct osmobb_apn *) vty->index; + osmo_talloc_replace_string(apn, &apn->cfg.dev_name, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_apn_tun_netns_name, cfg_apn_tun_netns_name_cmd, + "tun-netns NAME", + "Configure tun device network namespace name\n" + "TUN device network namespace name") +{ + struct osmobb_apn *apn = (struct osmobb_apn *) vty->index; + osmo_talloc_replace_string(apn, &apn->cfg.dev_netns_name, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_apn_no_tun_netns_name, cfg_apn_no_tun_netns_name_cmd, + "no tun-netns", + "Configure tun device to use default network namespace name\n") +{ + struct osmobb_apn *apn = (struct osmobb_apn *) vty->index; + TALLOC_FREE(apn->cfg.dev_netns_name); + return CMD_SUCCESS; +} + +static const struct value_string pdp_type_names[] = { + { APN_TYPE_IPv4, "v4" }, + { APN_TYPE_IPv6, "v6" }, + { APN_TYPE_IPv4v6, "v4v6" }, + { 0, NULL } +}; + +#define V4V6V46_STRING "IPv4(-only) PDP Type\n" \ + "IPv6(-only) PDP Type\n" \ + "IPv4v6 (dual-stack) PDP Type\n" + +DEFUN(cfg_apn_type_support, cfg_apn_type_support_cmd, + "type-support (v4|v6|v4v6)", + "Enable support for PDP Type\n" + V4V6V46_STRING) +{ + struct osmobb_apn *apn = (struct osmobb_apn *) vty->index; + uint32_t type = get_string_value(pdp_type_names, argv[0]); + + apn->cfg.apn_type_mask |= type; + return CMD_SUCCESS; +} + +DEFUN(cfg_apn_shutdown, cfg_apn_shutdown_cmd, + "shutdown", + "Put the APN in administrative shut-down\n") +{ + struct osmobb_apn *apn = (struct osmobb_apn *) vty->index; + bool on = false; + int rc; + + if (!apn->cfg.shutdown) { + rc = osmo_fsm_inst_dispatch(apn->fsm.fi, APN_EV_GPRS_ALLOWED, &on); + if (rc < 0) { + vty_out(vty, "%% Failed to Stop APN%s", VTY_NEWLINE); + return CMD_WARNING; + } + apn->cfg.shutdown = true; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_apn_no_shutdown, cfg_apn_no_shutdown_cmd, + "no shutdown", + NO_STR "Remove the APN from administrative shut-down\n") +{ + struct osmobb_apn *apn = (struct osmobb_apn *) vty->index; + bool on = true; + int rc; + + if (apn->cfg.shutdown) { + if (!apn->cfg.dev_name) { + vty_out(vty, "%% Failed to start APN, tun-device is not configured%s", VTY_NEWLINE); + return CMD_WARNING; + } + rc = osmo_fsm_inst_dispatch(apn->fsm.fi, APN_EV_GPRS_ALLOWED, &on); + if (rc < 0) { + vty_out(vty, "%% Failed to start APN, check log for details%s", VTY_NEWLINE); + return CMD_WARNING; + } + apn->cfg.shutdown = false; + } + + return CMD_SUCCESS; +} + +static void config_write_apn(struct vty *vty, const struct osmobb_apn *apn) +{ + unsigned int i; + + vty_out(vty, " apn %s%s", apn->cfg.name, VTY_NEWLINE); + + if (apn->cfg.dev_name) + vty_out(vty, " tun-device %s%s", apn->cfg.dev_name, VTY_NEWLINE); + if (apn->cfg.dev_netns_name) + vty_out(vty, " tun-netns %s%s", apn->cfg.dev_netns_name, VTY_NEWLINE); + + for (i = 0; i < 32; i++) { + if (!(apn->cfg.apn_type_mask & (UINT32_C(1) << i))) + continue; + vty_out(vty, " type-support %s%s", get_value_string(pdp_type_names, (UINT32_C(1) << i)), + VTY_NEWLINE); + } + + /* must be last */ + vty_out(vty, " %sshutdown%s", apn->cfg.shutdown ? "" : "no ", VTY_NEWLINE); +} + +static void config_write_ms(struct vty *vty, const struct osmocom_ms *ms) +{ + struct osmobb_apn *apn; + + vty_out(vty, "ms %s%s", ms->name, VTY_NEWLINE); + + l23_vty_config_write_ms_node_contents(vty, ms, " "); + + llist_for_each_entry(apn, &ms->gprs.apn_list, list) + config_write_apn(vty, apn); + + l23_vty_config_write_ms_node_contents_final(vty, ms, " "); +} + +static int config_write(struct vty *vty) +{ + struct osmocom_ms *ms; + llist_for_each_entry(ms, &ms_list, entity) + config_write_ms(vty, ms); + return CMD_SUCCESS; +} + +int modem_vty_init(void) +{ + int rc; + + if ((rc = l23_vty_init(config_write, NULL)) < 0) + return rc; + install_element_ve(&l23_show_ms_cmd); + install_element_ve(&test_grr_tx_chan_req_cmd); + install_element_ve(&test_llc_unitdata_req_hexpdu_cmd); + install_element_ve(&test_llc_unitdata_req_gmm_attch_cmd); + install_element_ve(&test_gmm_reg_attach_cmd); + install_element_ve(&test_gmm_reg_detach_cmd); + install_element_ve(&test_sm_act_pdp_ctx_cmd); + install_element(CONFIG_NODE, &l23_cfg_ms_cmd); + + install_element(MS_NODE, &cfg_ms_apn_cmd); + install_element(MS_NODE, &cfg_ms_no_apn_cmd); + install_node(&apn_node, NULL); + install_element(APN_NODE, &cfg_apn_tun_dev_name_cmd); + install_element(APN_NODE, &cfg_apn_tun_netns_name_cmd); + install_element(APN_NODE, &cfg_apn_no_tun_netns_name_cmd); + install_element(APN_NODE, &cfg_apn_type_support_cmd); + install_element(APN_NODE, &cfg_apn_shutdown_cmd); + install_element(APN_NODE, &cfg_apn_no_shutdown_cmd); + + return 0; +} diff --git a/src/host/osmocon/configure.ac b/src/host/osmocon/configure.ac index a42f4874..4195ecd7 100644 --- a/src/host/osmocon/configure.ac +++ b/src/host/osmocon/configure.ac @@ -5,6 +5,8 @@ AC_INIT([osmocon], AM_INIT_AUTOMAKE([dist-bzip2]) +CFLAGS="$CFLAGS -std=gnu11" + dnl kernel style compile messages m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) @@ -14,7 +16,7 @@ AC_PROG_CC AC_PROG_INSTALL dnl checks for libraries -PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore) +PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.5.0) dnl checks for header files AC_HEADER_STDC @@ -42,6 +44,7 @@ AC_ARG_ENABLE(werror, if test x"$werror" = x"yes" then WERROR_FLAGS="-Werror" + WERROR_FLAGS+=" -Werror=implicit-int -Werror=int-conversion -Werror=old-style-definition" WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations" WERROR_FLAGS+=" -Wno-error=cpp" # "#warning" CFLAGS="$CFLAGS $WERROR_FLAGS" diff --git a/src/host/osmocon/osmocon.c b/src/host/osmocon/osmocon.c index f4d91113..547f49fe 100644 --- a/src/host/osmocon/osmocon.c +++ b/src/host/osmocon/osmocon.c @@ -16,10 +16,6 @@ * 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 <ctype.h> @@ -500,7 +496,7 @@ static int romload_prepare_block(void) dnload.block_ptr = dnload.block; dnload.block_number++; - dnload.serial_fd.when = BSC_FD_READ | BSC_FD_WRITE; + osmo_fd_update_when(&dnload.serial_fd, 0, OSMO_FD_READ | OSMO_FD_WRITE); return 0; } @@ -586,7 +582,7 @@ static int handle_write_block(void) printf("Progress: %i%%\r", progress); fflush(stdout); dnload.write_ptr = dnload.data; - dnload.serial_fd.when &= ~BSC_FD_WRITE; + osmo_fd_write_disable(&dnload.serial_fd); if (dnload.romload_state == SENDING_LAST_BLOCK) { dnload.romload_state = LAST_BLOCK_SENT; printf("Finished, sent %i blocks in total\n", @@ -638,7 +634,7 @@ static int handle_write_dnload(void) } else if (dnload.write_ptr >= dnload.data + dnload.data_len) { printf("finished\n"); dnload.write_ptr = dnload.data; - dnload.serial_fd.when &= ~BSC_FD_WRITE; + osmo_fd_write_disable(&dnload.serial_fd); return 1; } @@ -682,7 +678,7 @@ static int handle_sercomm_write(void) } if (end) - dnload.serial_fd.when &= ~BSC_FD_WRITE; + osmo_fd_write_disable(&dnload.serial_fd); return 0; } @@ -751,7 +747,7 @@ static void hdlc_send_to_phone(uint8_t dlci, uint8_t *data, int len) sercomm_sendmsg(dlci, msg); - dnload.serial_fd.when |= BSC_FD_WRITE; + osmo_fd_write_enable(&dnload.serial_fd); } static void hdlc_console_cb(uint8_t dlci, struct msgb *msg) @@ -842,12 +838,12 @@ static int handle_read(void) } } else if (!memcmp(buffer, phone_prompt2, sizeof(phone_prompt2))) { printf("Received PROMPT2 from phone, starting download\n"); - dnload.serial_fd.when = BSC_FD_READ | BSC_FD_WRITE; + osmo_fd_update_when(&dnload.serial_fd, 0, OSMO_FD_READ | OSMO_FD_WRITE); dnload.state = DOWNLOADING; } else if (!memcmp(buffer, phone_ack, sizeof(phone_ack))) { printf("Received DOWNLOAD ACK from phone, your code is" " running now!\n"); - dnload.serial_fd.when = BSC_FD_READ; + osmo_fd_update_when(&dnload.serial_fd, 0, OSMO_FD_READ); dnload.state = WAITING_PROMPT1; dnload.write_ptr = dnload.data; dnload.expect_hdlc = 1; @@ -868,18 +864,18 @@ static int handle_read(void) } else if (!memcmp(buffer, phone_nack, sizeof(phone_nack))) { printf("Received DOWNLOAD NACK from phone, something went" " wrong :(\n"); - dnload.serial_fd.when = BSC_FD_READ; + osmo_fd_update_when(&dnload.serial_fd, 0, OSMO_FD_READ); dnload.state = WAITING_PROMPT1; dnload.write_ptr = dnload.data; } else if (!memcmp(buffer, phone_nack_magic, sizeof(phone_nack_magic))) { printf("Received MAGIC NACK from phone, you need to" " have \"1003\" at 0x803ce0\n"); - dnload.serial_fd.when = BSC_FD_READ; + osmo_fd_update_when(&dnload.serial_fd, 0, OSMO_FD_READ); dnload.state = WAITING_PROMPT1; dnload.write_ptr = dnload.data; } else if (!memcmp(buffer, ftmtool, sizeof(ftmtool))) { printf("Received FTMTOOL from phone, ramloader has aborted\n"); - dnload.serial_fd.when = BSC_FD_READ; + osmo_fd_update_when(&dnload.serial_fd, 0, OSMO_FD_READ); dnload.state = WAITING_PROMPT1; dnload.write_ptr = dnload.data; } @@ -1000,7 +996,7 @@ static int handle_read_romload(void) if (!memcmp(buffer, romload_branch_ack, sizeof(romload_branch_ack))) { printf("Received branch ack, your code is running now!\n"); - dnload.serial_fd.when = BSC_FD_READ; + osmo_fd_update_when(&dnload.serial_fd, 0, OSMO_FD_READ); dnload.romload_state = FINISHED; dnload.write_ptr = dnload.data; dnload.expect_hdlc = 1; @@ -1116,7 +1112,7 @@ static int handle_read_mtk(void) printf("Received size ack\n"); dnload.expect_hdlc = 1; dnload.mtk_state = MTK_SENDING_BLOCKS; - dnload.serial_fd.when = BSC_FD_READ | BSC_FD_WRITE; + osmo_fd_update_when(&dnload.serial_fd, 0, OSMO_FD_READ | OSMO_FD_WRITE); bufptr -= 3; break; case MTK_SENDING_BLOCKS: @@ -1138,7 +1134,7 @@ static int handle_read_mtk(void) printf("Received Block %i preparing next block\n", dnload.block_number); mtk_prepare_block(); - dnload.serial_fd.when = BSC_FD_READ | BSC_FD_WRITE; + osmo_fd_update_when(&dnload.serial_fd, 0, OSMO_FD_READ | OSMO_FD_WRITE); } break; case MTK_WAIT_BRANCH_CMD_ACK: @@ -1156,7 +1152,7 @@ static int handle_read_mtk(void) break; printf("Received branch address ack, code should run now\n"); osmo_serial_set_baudrate(dnload.serial_fd.fd, MODEM_BAUDRATE); - dnload.serial_fd.when = BSC_FD_READ; + osmo_fd_update_when(&dnload.serial_fd, 0, OSMO_FD_READ); dnload.mtk_state = MTK_FINISHED; dnload.write_ptr = dnload.data; dnload.expect_hdlc = 1; @@ -1172,7 +1168,7 @@ static int handle_read_mtk(void) static int serial_read(struct osmo_fd *fd, unsigned int flags) { int rc; - if (flags & BSC_FD_READ) { + if (flags & OSMO_FD_READ) { switch (dnload.mode) { case MODE_ROMLOAD: while ((rc = handle_read_romload()) > 0); @@ -1188,7 +1184,7 @@ static int serial_read(struct osmo_fd *fd, unsigned int flags) exit(2); } - if (flags & BSC_FD_WRITE) { + if (flags & OSMO_FD_WRITE) { rc = handle_write(); if (rc == 1) dnload.state = WAITING_PROMPT1; @@ -1322,10 +1318,7 @@ static int tool_accept(struct osmo_fd *fd, unsigned int flags) con->server = srv; - con->fd.fd = rc; - con->fd.when = BSC_FD_READ; - con->fd.cb = un_tool_read; - con->fd.data = con; + osmo_fd_setup(&con->fd, rc, OSMO_FD_READ, un_tool_read, con, 0); if (osmo_fd_register(&con->fd) != 0) { fprintf(stderr, "Failed to register the fd.\n"); talloc_free(con); @@ -1389,7 +1382,7 @@ void parse_debug(const char *str) int main(int argc, char **argv) { - int opt, flags; + int opt, flags, fd; uint32_t tmp_load_address = ROMLOAD_ADDRESS; const char *serial_dev = "/dev/ttyUSB1"; const char *layer2_un_path = "/tmp/osmocom_l2"; @@ -1442,12 +1435,13 @@ int main(int argc, char **argv) dnload.filename = argv[optind]; } - dnload.serial_fd.fd = osmo_serial_init(serial_dev, MODEM_BAUDRATE); - if (dnload.serial_fd.fd < 0) { + fd = osmo_serial_init(serial_dev, MODEM_BAUDRATE); + if (fd < 0) { fprintf(stderr, "Cannot open serial device %s\n", serial_dev); exit(1); } + osmo_fd_setup(&dnload.serial_fd, fd, OSMO_FD_READ, serial_read, NULL, 0); if (osmo_fd_register(&dnload.serial_fd) != 0) { fprintf(stderr, "Failed to register the serial.\n"); exit(1); @@ -1458,9 +1452,6 @@ int main(int argc, char **argv) flags |= O_NONBLOCK; fcntl(dnload.serial_fd.fd, F_SETFL, flags); - dnload.serial_fd.when = BSC_FD_READ; - dnload.serial_fd.cb = serial_read; - /* initialize the HDLC layer */ sercomm_init(); sercomm_register_rx_cb(SC_DLCI_CONSOLE, hdlc_console_cb); diff --git a/src/host/osmocon/osmoload.c b/src/host/osmocon/osmoload.c index d320b298..b2f14bbd 100644 --- a/src/host/osmocon/osmoload.c +++ b/src/host/osmocon/osmoload.c @@ -15,10 +15,6 @@ * 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 <errno.h> diff --git a/src/host/osmocon/tpu_debug.c b/src/host/osmocon/tpu_debug.c index c9dac903..82c8d30f 100644 --- a/src/host/osmocon/tpu_debug.c +++ b/src/host/osmocon/tpu_debug.c @@ -14,10 +14,6 @@ * 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 <stdint.h> @@ -57,6 +53,60 @@ static const char *tpu_addr_name[0x1f] = { static uint8_t tpu_reg_cache[0x1f]; static uint16_t tpu_qbit; +static uint16_t tspact_cache; + +static const char *tspact_name(unsigned int n) +{ + static char buf[32]; + + /* FIXME: This is rffe_dualband.c specific */ + switch (n) { + case 0: + return "RITA_RESET"; + case 1: + return "PA_ENABLE"; + case 6: + return "TRENA"; + case 8: + return "GSM_TXEN"; + default: + snprintf(buf, sizeof(buf), "TSPACT%u", n); + return buf; + } +} + +static const char *tps_strobe_name(unsigned int n) +{ + static char buf[32]; + + switch (n) { + case 0: + return "TWL3025"; + case 2: + return "TRF6151"; + default: + snprintf(buf, sizeof(buf), "STROBE%u", n); + return buf; + } +} + +static void handle_tspact(uint16_t mask, uint16_t bits) +{ + uint16_t newval = (tspact_cache & mask) | bits; + unsigned int i; + + if (newval == tspact_cache) + return; + + for (i = 0; i < 16; i++) { + uint16_t shifted = (1 << i); + if ((tspact_cache & shifted) != (newval & shifted)) { + printf("%s:%d->%d ", tspact_name(i), + tspact_cache & shifted ? 1 : 0, newval & shifted ? 1 : 0); + } + } + tspact_cache = newval; +} static void tpu_show_instr(uint16_t tpu) { @@ -93,7 +143,7 @@ static void tpu_show_instr(uint16_t tpu) switch (addr) { case 0: bitlen = (data & 0x1f) + 1; - printf("DEV_IDX=%u, BITLEN=%u ", data >> 5, bitlen); + printf("DEV_IDX=%s, BITLEN=%u ", tps_strobe_name(data >> 5), bitlen); if (bitlen <= 8) { tsp_data = tpu_reg_cache[4]; printf(" TSP_DATA=0x%02x ", tsp_data); @@ -120,6 +170,12 @@ static void tpu_show_instr(uint16_t tpu) if (data & 0x02) printf("WRITE "); break; + case 6: + handle_tspact(0xff00, data); + break; + case 7: + handle_tspact(0x00ff, data << 8); + break; } } printf("\n"); diff --git a/src/host/trxcon/.gitignore b/src/host/trxcon/.gitignore index fe90e43c..b5c3a99d 100644 --- a/src/host/trxcon/.gitignore +++ b/src/host/trxcon/.gitignore @@ -9,6 +9,11 @@ install-sh missing compile +# libtool by-products +ltmain.sh +libtool +m4/*.m4 + # configure by-products .deps/ Makefile @@ -18,9 +23,11 @@ version.h # build by-products *.o +*.lo *.a +*.la -trxcon +/src/trxcon # various .version diff --git a/src/host/trxcon/Makefile.am b/src/host/trxcon/Makefile.am index b51db02f..5b1002c8 100644 --- a/src/host/trxcon/Makefile.am +++ b/src/host/trxcon/Makefile.am @@ -1,52 +1,21 @@ AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 -# versioning magic -BUILT_SOURCES = $(top_srcdir)/.version -$(top_srcdir)/.version: - echo $(VERSION) > $@-t && mv $@-t $@ -dist-hook: - echo $(VERSION) > $(distdir)/.tarball-version - -AM_CPPFLAGS = \ - $(all_includes) \ - -I$(top_srcdir)/include \ - $(NULL) - -AM_CFLAGS = \ - -Wall \ - $(LIBOSMOCORE_CFLAGS) \ - $(LIBOSMOCODING_CFLAGS) \ - $(LIBOSMOGSM_CFLAGS) \ +SUBDIRS = \ + include \ + src \ $(NULL) -bin_PROGRAMS = trxcon +ACLOCAL_AMFLAGS = -I m4 -trxcon_SOURCES = \ - l1ctl_link.c \ - l1ctl.c \ - trx_if.c \ - logging.c \ - trxcon.c \ +BUILT_SOURCES = \ + $(top_srcdir)/.version \ $(NULL) - -# Scheduler -trxcon_SOURCES += \ - sched_lchan_common.c \ - sched_lchan_pdtch.c \ - sched_lchan_desc.c \ - sched_lchan_xcch.c \ - sched_lchan_tchf.c \ - sched_lchan_tchh.c \ - sched_lchan_rach.c \ - sched_lchan_sch.c \ - sched_mframe.c \ - sched_clck.c \ - sched_prim.c \ - sched_trx.c \ +EXTRA_DIST = \ + .version \ $(NULL) -trxcon_LDADD = \ - $(LIBOSMOCORE_LIBS) \ - $(LIBOSMOCODING_LIBS) \ - $(LIBOSMOGSM_LIBS) \ - $(NULL) +# versioning magic +$(top_srcdir)/.version: + echo $(VERSION) > $@-t && mv $@-t $@ +dist-hook: + echo $(VERSION) > $(distdir)/.tarball-version diff --git a/src/host/trxcon/configure.ac b/src/host/trxcon/configure.ac index 1f24260d..6508689b 100644 --- a/src/host/trxcon/configure.ac +++ b/src/host/trxcon/configure.ac @@ -2,6 +2,8 @@ dnl Process this file with autoconf to produce a configure script AC_INIT([trxcon], [0.0.0]) AM_INIT_AUTOMAKE +CFLAGS="$CFLAGS -std=gnu11" + dnl kernel style compile messages m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) @@ -18,6 +20,9 @@ PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm) dnl checks for header files AC_HEADER_STDC +dnl init libtool +LT_INIT + AC_ARG_ENABLE(sanitize, [AS_HELP_STRING( [--enable-sanitize], @@ -29,7 +34,36 @@ then CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined" fi +AC_ARG_ENABLE(werror, + [AS_HELP_STRING( + [--enable-werror], + [Turn all compiler warnings into errors, with exceptions: + a) deprecation (allow upstream to mark deprecation without breaking builds); + b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds) + ] + )], + [werror=$enableval], [werror="no"]) +if test x"$werror" = x"yes" +then + WERROR_FLAGS="-Werror" + WERROR_FLAGS+=" -Werror=implicit-int -Werror=int-conversion -Werror=old-style-definition" + WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations" + WERROR_FLAGS+=" -Wno-error=cpp" # "#warning" + CFLAGS="$CFLAGS $WERROR_FLAGS" + CPPFLAGS="$CPPFLAGS $WERROR_FLAGS" +fi + +AC_MSG_RESULT([CFLAGS="$CFLAGS"]) +AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"]) + dnl Checks for typedefs, structures and compiler characteristics -AC_OUTPUT( - Makefile) +AC_CONFIG_MACRO_DIRS([m4]) +AC_CONFIG_FILES([include/Makefile + include/osmocom/Makefile + include/osmocom/bb/Makefile + include/osmocom/bb/l1sched/Makefile + include/osmocom/bb/trxcon/Makefile + src/Makefile + Makefile]) +AC_OUTPUT diff --git a/src/host/trxcon/include/Makefile.am b/src/host/trxcon/include/Makefile.am new file mode 100644 index 00000000..9d963a02 --- /dev/null +++ b/src/host/trxcon/include/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + osmocom \ + $(NULL) diff --git a/src/host/trxcon/include/osmocom/Makefile.am b/src/host/trxcon/include/osmocom/Makefile.am new file mode 100644 index 00000000..83c6385c --- /dev/null +++ b/src/host/trxcon/include/osmocom/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + bb \ + $(NULL) diff --git a/src/host/trxcon/include/osmocom/bb/Makefile.am b/src/host/trxcon/include/osmocom/bb/Makefile.am new file mode 100644 index 00000000..4a575ffb --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/Makefile.am @@ -0,0 +1,9 @@ +SUBDIRS = \ + l1sched \ + trxcon \ + $(NULL) + +noinst_HEADERS = \ + l1ctl_proto.h \ + l1gprs.h \ + $(NULL) diff --git a/src/host/trxcon/include/osmocom/bb/l1ctl_proto.h b/src/host/trxcon/include/osmocom/bb/l1ctl_proto.h new file mode 120000 index 00000000..ee19b80e --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/l1ctl_proto.h @@ -0,0 +1 @@ +../../../../../../include/l1ctl_proto.h
\ No newline at end of file diff --git a/src/host/trxcon/include/osmocom/bb/l1gprs.h b/src/host/trxcon/include/osmocom/bb/l1gprs.h new file mode 120000 index 00000000..3bf85176 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/l1gprs.h @@ -0,0 +1 @@ +../../../../../../include/l1gprs.h
\ No newline at end of file diff --git a/src/host/trxcon/include/osmocom/bb/l1sched/Makefile.am b/src/host/trxcon/include/osmocom/bb/l1sched/Makefile.am new file mode 100644 index 00000000..39c32ba0 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/l1sched/Makefile.am @@ -0,0 +1,5 @@ +noinst_HEADERS = \ + l1sched.h \ + logging.h \ + prim.h \ + $(NULL) diff --git a/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h b/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h new file mode 100644 index 00000000..6c5a31e8 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h @@ -0,0 +1,417 @@ +#pragma once + +#include <time.h> +#include <stdint.h> +#include <stdbool.h> + +#include <arpa/inet.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/gsm0502.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/timer.h> + +#include <osmocom/bb/l1sched/prim.h> + +#define GPRS_L2_MAX_LEN 54 +#define EDGE_L2_MAX_LEN 155 + +#define L1SCHED_CH_LID_DEDIC 0x00 +#define L1SCHED_CH_LID_SACCH 0x40 + +/* Osmocom-specific extension for PTCCH (see 3GPP TS 45.002, section 3.3.4.2). + * Shall be used to distinguish PTCCH and PDTCH channels on a PDCH time-slot. */ +#define L1SCHED_CH_LID_PTCCH 0x80 + +/* Is a channel related to PDCH (GPRS) */ +#define L1SCHED_CH_FLAG_PDCH (1 << 0) +/* Should a channel be activated automatically */ +#define L1SCHED_CH_FLAG_AUTO (1 << 1) + +#define MAX_A5_KEY_LEN (128 / 8) +#define TRX_TS_COUNT 8 + +struct l1sched_lchan_state; +struct l1sched_meas_set; +struct l1sched_state; +struct l1sched_ts; + +enum l1sched_burst_type { + L1SCHED_BURST_GMSK, + L1SCHED_BURST_8PSK, +}; + +/** + * These types define the different channels on a multiframe. + * Each channel has queues and can be activated individually. + */ +enum l1sched_lchan_type { + L1SCHED_IDLE = 0, + L1SCHED_FCCH, + L1SCHED_SCH, + L1SCHED_BCCH, + L1SCHED_RACH, + L1SCHED_CCCH, + L1SCHED_TCHF, + L1SCHED_TCHH_0, + L1SCHED_TCHH_1, + L1SCHED_SDCCH4_0, + L1SCHED_SDCCH4_1, + L1SCHED_SDCCH4_2, + L1SCHED_SDCCH4_3, + L1SCHED_SDCCH8_0, + L1SCHED_SDCCH8_1, + L1SCHED_SDCCH8_2, + L1SCHED_SDCCH8_3, + L1SCHED_SDCCH8_4, + L1SCHED_SDCCH8_5, + L1SCHED_SDCCH8_6, + L1SCHED_SDCCH8_7, + L1SCHED_SACCHTF, + L1SCHED_SACCHTH_0, + L1SCHED_SACCHTH_1, + L1SCHED_SACCH4_0, + L1SCHED_SACCH4_1, + L1SCHED_SACCH4_2, + L1SCHED_SACCH4_3, + L1SCHED_SACCH8_0, + L1SCHED_SACCH8_1, + L1SCHED_SACCH8_2, + L1SCHED_SACCH8_3, + L1SCHED_SACCH8_4, + L1SCHED_SACCH8_5, + L1SCHED_SACCH8_6, + L1SCHED_SACCH8_7, + L1SCHED_PDTCH, + L1SCHED_PTCCH, + L1SCHED_SDCCH4_CBCH, + L1SCHED_SDCCH8_CBCH, + _L1SCHED_CHAN_MAX +}; + +/* Represents a burst to be transmitted */ +struct l1sched_burst_req { + uint32_t fn; + uint8_t tn; + uint8_t pwr; + + /* Internally used by the scheduler */ + uint8_t bid; + + ubit_t burst[GSM_NBITS_NB_8PSK_BURST]; + size_t burst_len; +}; + +/* Represents a received burst */ +struct l1sched_burst_ind { + uint32_t fn; + uint8_t tn; + + /*! ToA256 (Timing of Arrival, 1/256 of a symbol) */ + int16_t toa256; + /*! RSSI (Received Signal Strength Indication) */ + int8_t rssi; + + /* Internally used by the scheduler */ + uint8_t bid; + + sbit_t burst[GSM_NBITS_NB_8PSK_BURST]; + size_t burst_len; +}; + +/* Probed lchan is active */ +#define L1SCHED_PROBE_F_ACTIVE (1 << 0) + +/* RTR (Ready-to-Receive) probe */ +struct l1sched_probe { + uint32_t flags; /* see L1SCHED_PROBE_F_* above */ + uint32_t fn; + uint8_t tn; +}; + +typedef int l1sched_lchan_rx_func(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi); + +typedef int l1sched_lchan_tx_func(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br); + +struct l1sched_lchan_desc { + /*! Human-readable name */ + const char *name; + /*! Human-readable description */ + const char *desc; + + /*! Channel Number (like in RSL) */ + uint8_t chan_nr; + /*! Link ID (like in RSL) */ + uint8_t link_id; + + /*! How much memory do we need to store bursts */ + size_t burst_buf_size; + /*! Channel specific flags */ + uint8_t flags; + + /*! Function to call when burst received from PHY */ + l1sched_lchan_rx_func *rx_fn; + /*! Function to call when data received from L2 */ + l1sched_lchan_tx_func *tx_fn; +}; + +struct l1sched_tdma_frame { + /*! Downlink channel (slot) type */ + enum l1sched_lchan_type dl_chan; + /*! Downlink block ID */ + uint8_t dl_bid; + /*! Uplink channel (slot) type */ + enum l1sched_lchan_type ul_chan; + /*! Uplink block ID */ + uint8_t ul_bid; +}; + +struct l1sched_tdma_multiframe { + /*! Channel combination */ + enum gsm_phys_chan_config chan_config; + /*! Human-readable name */ + const char *name; + /*! Repeats how many frames */ + uint8_t period; + /*! Applies to which timeslots */ + uint8_t slotmask; + /*! Contains which lchans */ + uint64_t lchan_mask; + /*! Pointer to scheduling structure */ + const struct l1sched_tdma_frame *frames; +}; + +struct l1sched_meas_set { + /*! TDMA frame number of the first burst this set belongs to */ + uint32_t fn; + /*! ToA256 (Timing of Arrival, 1/256 of a symbol) */ + int16_t toa256; + /*! RSSI (Received Signal Strength Indication) */ + int8_t rssi; +}; + +/* Simple ring buffer (up to 24 unique measurements) */ +struct l1sched_lchan_meas_hist { + struct l1sched_meas_set buf[24]; + struct l1sched_meas_set *head; +}; + +/* States each channel on a multiframe */ +struct l1sched_lchan_state { + /*! Channel type */ + enum l1sched_lchan_type type; + /*! Channel status */ + uint8_t active; + /*! Link to a list of channels */ + struct llist_head list; + + /*! Burst type: GMSK or 8PSK */ + enum l1sched_burst_type burst_type; + /*! Mask of received bursts */ + uint32_t rx_burst_mask; + /*! Mask of transmitted bursts */ + uint32_t tx_burst_mask; + /*! Burst buffer for RX */ + sbit_t *rx_bursts; + /*! Burst buffer for TX */ + ubit_t *tx_bursts; + + /*! Queue of Tx primitives */ + struct llist_head tx_prims; + /*! Tx primitive being sent */ + struct msgb *prim; + + /*! Mode for TCH channels (see GSM48_CMODE_*) */ + uint8_t tch_mode; + /*! Training Sequence Code */ + uint8_t tsc; + + /*! FACCH/H on downlink */ + bool dl_ongoing_facch; + /*! pending FACCH/H blocks on Uplink */ + uint8_t ul_facch_blocks; + + /*! Downlink measurements history */ + struct l1sched_lchan_meas_hist meas_hist; + /*! AVG measurements of the last received block */ + struct l1sched_meas_set meas_avg; + + /*! TDMA loss detection state */ + struct { + /*! Last processed TDMA frame number */ + uint32_t last_proc; + /*! Number of processed TDMA frames */ + unsigned long num_proc; + /*! Number of lost TDMA frames */ + unsigned long num_lost; + } tdma; + + /*! SACCH state */ + struct { + /*! Cached measurement report (last received) */ + uint8_t mr_cache[GSM_MACBLOCK_LEN]; + /*! Cache usage counter */ + uint8_t mr_cache_usage; + /*! Was a MR transmitted last time? */ + bool mr_tx_last; + } sacch; + + /* AMR specific */ + struct { + /*! 4 possible codecs for AMR */ + uint8_t codec[4]; + /*! Number of possible codecs */ + uint8_t codecs; + /*! Current uplink FT index */ + uint8_t ul_ft; + /*! Current downlink FT index */ + uint8_t dl_ft; + /*! Current uplink CMR index */ + uint8_t ul_cmr; + /*! Current downlink CMR index */ + uint8_t dl_cmr; + /*! If AMR loop is enabled */ + uint8_t amr_loop; + /*! Number of bit error rates */ + uint8_t ber_num; + /*! Sum of bit error rates */ + float ber_sum; + /* last received dtx frame type */ + uint8_t last_dtx; + } amr; + + /*! A5/X encryption state */ + struct { + uint8_t key[MAX_A5_KEY_LEN]; + uint8_t key_len; + uint8_t algo; + } a5; + + /* TS that this lchan belongs to */ + struct l1sched_ts *ts; +}; + +struct l1sched_ts { + /*! Timeslot index within a frame (0..7) */ + uint8_t index; + + /*! Pointer to multiframe layout */ + const struct l1sched_tdma_multiframe *mf_layout; + /*! Channel states for logical channels */ + struct llist_head lchans; + /*! Backpointer to the scheduler */ + struct l1sched_state *sched; +}; + +/*! Scheduler configuration */ +struct l1sched_cfg { + /*! Logging context (used as prefix for messages) */ + const char *log_prefix; +}; + +/*! One scheduler instance */ +struct l1sched_state { + /*! List of timeslots maintained by this scheduler */ + struct l1sched_ts *ts[TRX_TS_COUNT]; + /*! SACCH cache (common for all lchans) */ + uint8_t sacch_cache[GSM_MACBLOCK_LEN]; + /*! BSIC value learned from SCH bursts */ + uint8_t bsic; + /*! Logging context (used as prefix for messages) */ + const char *log_prefix; + /*! Some private data */ + void *priv; +}; + +extern const struct l1sched_lchan_desc l1sched_lchan_desc[_L1SCHED_CHAN_MAX]; +const struct l1sched_tdma_multiframe * +l1sched_mframe_layout(enum gsm_phys_chan_config config, uint8_t tn); + +/* Scheduler management functions */ +struct l1sched_state *l1sched_alloc(void *ctx, const struct l1sched_cfg *cfg, void *priv); +void l1sched_reset(struct l1sched_state *sched, bool reset_clock); +void l1sched_free(struct l1sched_state *sched); + +/* Timeslot management functions */ +struct l1sched_ts *l1sched_add_ts(struct l1sched_state *sched, int tn); +void l1sched_del_ts(struct l1sched_state *sched, int tn); +int l1sched_reset_ts(struct l1sched_state *sched, int tn); +int l1sched_configure_ts(struct l1sched_state *sched, int tn, + enum gsm_phys_chan_config config); +int l1sched_start_ciphering(struct l1sched_ts *ts, uint8_t algo, + const uint8_t *key, uint8_t key_len); + +/* Logical channel management functions */ +enum gsm_phys_chan_config l1sched_chan_nr2pchan_config(uint8_t chan_nr); + +void l1sched_deactivate_all_lchans(struct l1sched_ts *ts); +int l1sched_set_lchans(struct l1sched_ts *ts, uint8_t chan_nr, + int active, uint8_t tch_mode, uint8_t tsc); +int l1sched_lchan_set_amr_cfg(struct l1sched_lchan_state *lchan, + uint8_t codecs_bitmask, uint8_t start_codec); +int l1sched_activate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan); +int l1sched_deactivate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan); +struct l1sched_lchan_state *l1sched_find_lchan_by_type(struct l1sched_ts *ts, + enum l1sched_lchan_type type); +struct l1sched_lchan_state *l1sched_find_lchan_by_chan_nr(struct l1sched_state *sched, + uint8_t chan_nr, uint8_t link_id); + +#define L1SCHED_TCH_MODE_IS_SPEECH(mode) \ + (mode == GSM48_CMODE_SPEECH_V1 \ + || mode == GSM48_CMODE_SPEECH_EFR \ + || mode == GSM48_CMODE_SPEECH_AMR) + +#define L1SCHED_TCH_MODE_IS_DATA(mode) \ + (mode == GSM48_CMODE_DATA_14k5 \ + || mode == GSM48_CMODE_DATA_12k0 \ + || mode == GSM48_CMODE_DATA_6k0 \ + || mode == GSM48_CMODE_DATA_3k6) + +#define L1SCHED_CHAN_IS_TCH(chan) \ + (chan == L1SCHED_TCHF || chan == L1SCHED_TCHH_0 || chan == L1SCHED_TCHH_1) + +#define L1SCHED_CHAN_IS_SACCH(chan) \ + (l1sched_lchan_desc[chan].link_id & L1SCHED_CH_LID_SACCH) + +int l1sched_handle_rx_burst(struct l1sched_state *sched, + struct l1sched_burst_ind *bi); +int l1sched_handle_rx_probe(struct l1sched_state *sched, + struct l1sched_probe *probe); +int l1sched_handle_burst_req(struct l1sched_state *sched, + const struct l1sched_burst_req *br); + +/* Shared declarations for lchan handlers */ +extern const uint8_t l1sched_nb_training_bits[8][26]; + +const char *l1sched_burst_mask2str(const uint32_t *mask, int bits); + +/* Interleaved TCH/H block TDMA frame mapping */ +bool l1sched_tchh_block_map_fn(enum l1sched_lchan_type chan, + uint32_t fn, bool ul, bool facch, bool start); + +#define l1sched_tchh_traffic_start(chan, fn, ul) \ + l1sched_tchh_block_map_fn(chan, fn, ul, 0, 1) +#define l1sched_tchh_traffic_end(chan, fn, ul) \ + l1sched_tchh_block_map_fn(chan, fn, ul, 0, 0) + +#define l1sched_tchh_facch_start(chan, fn, ul) \ + l1sched_tchh_block_map_fn(chan, fn, ul, 1, 1) +#define l1sched_tchh_facch_end(chan, fn, ul) \ + l1sched_tchh_block_map_fn(chan, fn, ul, 1, 0) + +/* Measurement history */ +void l1sched_lchan_meas_push(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi); +void l1sched_lchan_meas_avg(struct l1sched_lchan_state *lchan, unsigned int n); + +/* Clock and Downlink scheduling trigger */ +int l1sched_clck_handle(struct l1sched_state *sched, uint32_t fn); +void l1sched_clck_reset(struct l1sched_state *sched); + +void l1sched_pull_burst(struct l1sched_state *sched, struct l1sched_burst_req *br); +void l1sched_pull_send_frame(struct l1sched_state *sched); diff --git a/src/host/trxcon/include/osmocom/bb/l1sched/logging.h b/src/host/trxcon/include/osmocom/bb/l1sched/logging.h new file mode 100644 index 00000000..21be9955 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/l1sched/logging.h @@ -0,0 +1,37 @@ +#pragma once + +extern int l1sched_log_cat_common; +extern int l1sched_log_cat_data; + +void l1sched_logging_init(int log_cat_common, int log_cat_data); + +/* Messages using l1sched_state as the context */ +#define LOGP_SCHED_CAT(sched, cat, level, fmt, args...) \ + LOGP(l1sched_log_cat_##cat, level, "%s" fmt, \ + (sched)->log_prefix, ## args) + +/* Common messages using l1sched_state as the context */ +#define LOGP_SCHEDC(sched, level, fmt, args...) \ + LOGP_SCHED_CAT(sched, common, level, fmt, ## args) + +/* Data messages using l1sched_state as the context */ +#define LOGP_SCHEDD(sched, level, fmt, args...) \ + LOGP_SCHED_CAT(sched, data, level, fmt, ## args) + + +#define LOGP_LCHAN_NAME_FMT "TS%u-%s" +#define LOGP_LCHAN_NAME_ARGS(lchan) \ + (lchan)->ts->index, l1sched_lchan_desc[(lchan)->type].name + +/* Messages using l1sched_lchan_state as the context */ +#define LOGP_LCHAN_CAT(lchan, cat, level, fmt, args...) \ + LOGP_SCHED_CAT((lchan)->ts->sched, cat, level, LOGP_LCHAN_NAME_FMT " " fmt, \ + LOGP_LCHAN_NAME_ARGS(lchan), ## args) + +/* Common messages using l1sched_lchan_state as the context */ +#define LOGP_LCHANC(lchan, level, fmt, args...) \ + LOGP_LCHAN_CAT(lchan, common, level, fmt, ## args) + +/* Data messages using l1sched_lchan_state as the context */ +#define LOGP_LCHAND(lchan, level, fmt, args...) \ + LOGP_LCHAN_CAT(lchan, data, level, fmt, ## args) diff --git a/src/host/trxcon/include/osmocom/bb/l1sched/prim.h b/src/host/trxcon/include/osmocom/bb/l1sched/prim.h new file mode 100644 index 00000000..a9187c2a --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/l1sched/prim.h @@ -0,0 +1,132 @@ +#pragma once + +#include <stdint.h> +#include <stdbool.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/prim.h> +#include <osmocom/core/utils.h> + +#define l1sched_prim_from_msgb(msg) \ + ((struct l1sched_prim *)(msg)->l1h) + +#define l1sched_prim_data_from_msgb(msg) \ + ((uint8_t *)msgb_l2(msg)) + +#define l1sched_prim_type_from_msgb(msg) \ + l1sched_prim_from_msgb(msg)->oph.primitive + +#define L1SCHED_PRIM_STR_FMT "%s.%s" +#define L1SCHED_PRIM_STR_ARGS(prim) \ + l1sched_prim_type_name((prim)->oph.primitive), \ + osmo_prim_operation_name((prim)->oph.operation) + +enum l1sched_prim_type { + L1SCHED_PRIM_T_DATA, /* Req | Ind | Cnf */ + L1SCHED_PRIM_T_RACH, /* Req | Cnf */ + L1SCHED_PRIM_T_SCH, /* Ind */ + L1SCHED_PRIM_T_PCHAN_COMB, /* Ind */ +}; + +extern const struct value_string l1sched_prim_type_names[]; +static inline const char *l1sched_prim_type_name(enum l1sched_prim_type val) +{ + return get_value_string(l1sched_prim_type_names, val); +} + +/*! Common header for L1SCHED_PRIM_T_{DATA,RACH} */ +struct l1sched_prim_chdr { + /*! TDMA Frame Number */ + uint32_t frame_nr; + /*! RSL Channel Number */ + uint8_t chan_nr; + /*! RSL Link Identifier */ + uint8_t link_id; + /*! Traffic or signalling */ + bool traffic; +}; + +/*! Payload of L1SCHED_PRIM_T_DATA | Ind */ +struct l1sched_prim_data_ind { + /*! Common sub-header */ + struct l1sched_prim_chdr chdr; + int16_t toa256; + int8_t rssi; + int n_errors; + int n_bits_total; +}; + +/*! Payload of L1SCHED_PRIM_T_RACH | {Req,Cnf} */ +struct l1sched_prim_rach { + /*! Common sub-header */ + struct l1sched_prim_chdr chdr; + /*! Training Sequence (only for 11-bit RA) */ + uint8_t synch_seq; + /*! Transmission offset (how many frames to skip) */ + uint8_t offset; + /*! RA value is 11 bit */ + bool is_11bit; + /*! RA value */ + uint16_t ra; +}; + +struct l1sched_prim { + /*! Primitive header */ + struct osmo_prim_hdr oph; + /*! Type specific header */ + union { + /*! L1SCHED_PRIM_T_DATA | Req */ + struct l1sched_prim_chdr data_req; + /*! L1SCHED_PRIM_T_DATA | Cnf */ + struct l1sched_prim_chdr data_cnf; + /*! L1SCHED_PRIM_T_DATA | Ind */ + struct l1sched_prim_data_ind data_ind; + + /*! L1SCHED_PRIM_T_RACH | Req */ + struct l1sched_prim_rach rach_req; + /*! L1SCHED_PRIM_T_RACH | Cnf */ + struct l1sched_prim_rach rach_cnf; + + /*! L1SCHED_PRIM_T_SCH | Ind */ + struct { + /*! TDMA frame number */ + uint32_t frame_nr; + /*! BSIC */ + uint8_t bsic; + } sch_ind; + + /*! L1SCHED_PRIM_T_PCHAN_COMB | Ind */ + struct { + /*! Timeslot number */ + uint8_t tn; + /*! Channel combination for a timeslot */ + enum gsm_phys_chan_config pchan; + } pchan_comb_ind; + }; +}; + + +struct l1sched_state; +struct l1sched_lchan_state; + +void l1sched_prim_init(struct msgb *msg, + enum l1sched_prim_type type, + enum osmo_prim_operation op); + +struct msgb *l1sched_prim_alloc(enum l1sched_prim_type type, + enum osmo_prim_operation op); + +bool l1sched_lchan_amr_prim_is_valid(struct l1sched_lchan_state *lchan, + struct msgb *msg, bool is_cmr); +struct msgb *l1sched_lchan_prim_dequeue_sacch(struct l1sched_lchan_state *lchan); +struct msgb *l1sched_lchan_prim_dequeue_tch(struct l1sched_lchan_state *lchan, bool facch); +struct msgb *l1sched_lchan_prim_dummy_lapdm(const struct l1sched_lchan_state *lchan); + +int l1sched_lchan_emit_data_ind(struct l1sched_lchan_state *lchan, + const uint8_t *data, size_t data_len, + int n_errors, int n_bits_total, bool traffic); +int l1sched_lchan_emit_data_cnf(struct l1sched_lchan_state *lchan, + struct msgb *msg, uint32_t fn); + +int l1sched_prim_from_user(struct l1sched_state *sched, struct msgb *msg); +int l1sched_prim_to_user(struct l1sched_state *sched, struct msgb *msg); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/Makefile.am b/src/host/trxcon/include/osmocom/bb/trxcon/Makefile.am new file mode 100644 index 00000000..ad1e89a0 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/Makefile.am @@ -0,0 +1,9 @@ +noinst_HEADERS = \ + l1ctl_server.h \ + l1ctl.h \ + phyif.h \ + trx_if.h \ + logging.h \ + trxcon.h \ + trxcon_fsm.h \ + $(NULL) diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl.h b/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl.h new file mode 100644 index 00000000..7e2fa6a5 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl.h @@ -0,0 +1,22 @@ +#pragma once + +#include <stdint.h> + +struct msgb; +struct trxcon_param_rx_data_ind; +struct trxcon_param_tx_data_cnf; +struct trxcon_param_tx_access_burst_cnf; + +int l1ctl_tx_fbsb_conf(struct trxcon_inst *trxcon, uint16_t band_arfcn, uint8_t bsic); +int l1ctl_tx_fbsb_fail(struct trxcon_inst *trxcon, uint16_t band_arfcn); +int l1ctl_tx_ccch_mode_conf(struct trxcon_inst *trxcon, uint8_t mode); +int l1ctl_tx_pm_conf(struct trxcon_inst *trxcon, uint16_t band_arfcn, int dbm, int last); +int l1ctl_tx_reset_conf(struct trxcon_inst *trxcon, uint8_t type); +int l1ctl_tx_reset_ind(struct trxcon_inst *trxcon, uint8_t type); + +int l1ctl_tx_dt_ind(struct trxcon_inst *trxcon, + const struct trxcon_param_rx_data_ind *ind); +int l1ctl_tx_dt_conf(struct trxcon_inst *trxcon, + const struct trxcon_param_tx_data_cnf *cnf); +int l1ctl_tx_rach_conf(struct trxcon_inst *trxcon, + const struct trxcon_param_tx_access_burst_cnf *cnf); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl_server.h b/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl_server.h new file mode 100644 index 00000000..83c61f02 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl_server.h @@ -0,0 +1,67 @@ +#pragma once + +#include <stdint.h> + +#include <osmocom/core/write_queue.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> + +#define L1CTL_LENGTH 512 +#define L1CTL_HEADROOM 32 + +/** + * Each L1CTL message gets its own length pushed + * as two bytes in front before sending. + */ +#define L1CTL_MSG_LEN_FIELD 2 + +struct l1ctl_client; + +typedef int l1ctl_conn_data_func(struct l1ctl_client *, struct msgb *); +typedef void l1ctl_conn_state_func(struct l1ctl_client *); + +struct l1ctl_server_cfg { + /* UNIX socket path to listen on */ + const char *sock_path; + /* maximum number of connected clients */ + unsigned int num_clients_max; + /* functions to be called on various events */ + l1ctl_conn_data_func *conn_read_cb; /* mandatory */ + l1ctl_conn_state_func *conn_accept_cb; /* optional */ + l1ctl_conn_state_func *conn_close_cb; /* optional */ +}; + +struct l1ctl_server { + /* list of connected clients */ + struct llist_head clients; + /* number of connected clients */ + unsigned int num_clients; + /* used for client ID generation */ + unsigned int next_client_id; + /* socket on which we listen for connections */ + struct osmo_fd ofd; + /* server configuration */ + const struct l1ctl_server_cfg *cfg; +}; + +struct l1ctl_client { + /* list head in l1ctl_server.clients */ + struct llist_head list; + /* struct l1ctl_server we belong to */ + struct l1ctl_server *server; + /* client's write queue */ + struct osmo_wqueue wq; + /* logging context (used as prefix for messages) */ + const char *log_prefix; + /* unique client ID */ + unsigned int id; + /* some private data */ + void *priv; +}; + +struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg); +void l1ctl_server_free(struct l1ctl_server *server); + +int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg); +void l1ctl_client_conn_close(struct l1ctl_client *client); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/logging.h b/src/host/trxcon/include/osmocom/bb/trxcon/logging.h new file mode 100644 index 00000000..ce149926 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/logging.h @@ -0,0 +1,16 @@ +#pragma once + +#include <osmocom/core/logging.h> + +enum { + DAPP, + DL1C, + DL1D, + DTRXC, + DTRXD, + DSCH, + DSCHD, + DGPRS, +}; + +int trxcon_logging_init(void *tall_ctx, const char *category_mask); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/phyif.h b/src/host/trxcon/include/osmocom/bb/trxcon/phyif.h new file mode 100644 index 00000000..2ad7a678 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/phyif.h @@ -0,0 +1,120 @@ +#pragma once + +#include <stdint.h> + +#include <osmocom/core/bits.h> + +/* PHYIF command type */ +enum trxcon_phyif_cmd_type { + TRXCON_PHYIF_CMDT_RESET, + TRXCON_PHYIF_CMDT_POWERON, + TRXCON_PHYIF_CMDT_POWEROFF, + TRXCON_PHYIF_CMDT_MEASURE, + TRXCON_PHYIF_CMDT_SETFREQ_H0, + TRXCON_PHYIF_CMDT_SETFREQ_H1, + TRXCON_PHYIF_CMDT_SETSLOT, + TRXCON_PHYIF_CMDT_SETTA, +}; + +/* param of TRXCON_PHYIF_CMDT_SETFREQ_H0 */ +struct trxcon_phyif_cmdp_setfreq_h0 { + uint16_t band_arfcn; +}; + +/* param of TRXCON_PHYIF_CMDT_SETFREQ_H1 */ +struct trxcon_phyif_cmdp_setfreq_h1 { + uint8_t hsn; + uint8_t maio; + const uint16_t *ma; + unsigned int ma_len; +}; + +/* param of TRXCON_PHYIF_CMDT_SETSLOT */ +struct trxcon_phyif_cmdp_setslot { + uint8_t tn; + uint8_t pchan; /* enum gsm_phys_chan_config */ +}; + +/* param of TRXCON_PHYIF_CMDT_SETTA */ +struct trxcon_phyif_cmdp_setta { + int8_t ta; /* intentionally signed */ +}; + +/* param of TRXCON_PHYIF_CMDT_MEASURE (command) */ +struct trxcon_phyif_cmdp_measure { + uint16_t band_arfcn; +}; + +/* param of TRXCON_PHYIF_CMDT_MEASURE (response) */ +struct trxcon_phyif_rspp_measure { + uint16_t band_arfcn; + int dbm; +}; + +struct trxcon_phyif_cmd { + enum trxcon_phyif_cmd_type type; + union { + struct trxcon_phyif_cmdp_setfreq_h0 setfreq_h0; + struct trxcon_phyif_cmdp_setfreq_h1 setfreq_h1; + struct trxcon_phyif_cmdp_setslot setslot; + struct trxcon_phyif_cmdp_setta setta; + struct trxcon_phyif_cmdp_measure measure; + } param; +}; + +struct trxcon_phyif_rsp { + enum trxcon_phyif_cmd_type type; + union { + struct trxcon_phyif_rspp_measure measure; + } param; +}; + +/* RTS.ind - Ready-to-Send indication */ +struct trxcon_phyif_rts_ind { + uint32_t fn; + uint8_t tn; +}; + +/* RTR.ind - Ready-to-Receive indicaton */ +struct trxcon_phyif_rtr_ind { + uint32_t fn; + uint8_t tn; +}; + +/* The probed lchan is active */ +#define TRXCON_PHYIF_RTR_F_ACTIVE (1 << 0) + +/* RTR.rsp - Ready-to-Receive response */ +struct trxcon_phyif_rtr_rsp { + uint32_t flags; /* see TRXCON_PHYIF_RTR_F_* above */ +}; + +/* BURST.req - a burst to be transmitted */ +struct trxcon_phyif_burst_req { + uint32_t fn; + uint8_t tn; + uint8_t pwr; + const ubit_t *burst; + unsigned int burst_len; +}; + +/* BURST.ind - a received burst */ +struct trxcon_phyif_burst_ind { + uint32_t fn; + uint8_t tn; + int16_t toa256; + int8_t rssi; + const sbit_t *burst; + unsigned int burst_len; +}; + +int trxcon_phyif_handle_burst_req(void *phyif, const struct trxcon_phyif_burst_req *br); +int trxcon_phyif_handle_burst_ind(void *priv, const struct trxcon_phyif_burst_ind *bi); + +int trxcon_phyif_handle_rts_ind(void *priv, const struct trxcon_phyif_rts_ind *rts); +int trxcon_phyif_handle_rtr_ind(void *priv, const struct trxcon_phyif_rtr_ind *ind, + struct trxcon_phyif_rtr_rsp *rsp); + +int trxcon_phyif_handle_cmd(void *phyif, const struct trxcon_phyif_cmd *cmd); +int trxcon_phyif_handle_rsp(void *priv, const struct trxcon_phyif_rsp *rsp); +void trxcon_phyif_close(void *phyif); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/trx_if.h b/src/host/trxcon/include/osmocom/bb/trxcon/trx_if.h new file mode 100644 index 00000000..e564fd8e --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/trx_if.h @@ -0,0 +1,61 @@ +#pragma once + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/fsm.h> + +#include <osmocom/bb/trxcon/phyif.h> + +#define TRXC_BUF_SIZE 1024 +#define TRXD_BUF_SIZE 512 + +enum trx_fsm_states { + TRX_STATE_OFFLINE = 0, + TRX_STATE_IDLE, + TRX_STATE_ACTIVE, + TRX_STATE_RSP_WAIT, +}; + +struct trx_instance { + struct osmo_fd trx_ofd_ctrl; + struct osmo_fd trx_ofd_data; + + struct osmo_timer_list trx_ctrl_timer; + struct llist_head trx_ctrl_list; + struct osmo_fsm_inst *fi; + uint32_t fn_advance; + + /* HACK: we need proper state machines */ + uint32_t prev_state; + bool powered_up; + + /* Some private data */ + void *priv; +}; + +struct trx_ctrl_msg { + struct llist_head list; + char cmd[TRXC_BUF_SIZE]; + int retry_cnt; + int critical; + int cmd_len; +}; + +struct trx_if_params { + const char *local_host; + const char *remote_host; + uint16_t base_port; + uint32_t fn_advance; + uint8_t instance; + + struct osmo_fsm_inst *parent_fi; + uint32_t parent_term_event; + void *priv; +}; + +struct trx_instance *trx_if_open(const struct trx_if_params *params); +void trx_if_close(struct trx_instance *trx); + +int trx_if_handle_phyif_burst_req(struct trx_instance *trx, const struct trxcon_phyif_burst_req *br); +int trx_if_handle_phyif_cmd(struct trx_instance *trx, const struct trxcon_phyif_cmd *cmd); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h new file mode 100644 index 00000000..ff54e785 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h @@ -0,0 +1,64 @@ +#pragma once + +#include <stdint.h> + +struct osmo_fsm_inst; +struct l1sched_state; +struct l1gprs_state; +struct msgb; + +struct trxcon_inst { + struct osmo_fsm_inst *fi; + unsigned int id; + + /* Logging context for sched and l1c */ + const char *log_prefix; + + /* GSMTAP instance (optional) */ + struct gsmtap_inst *gsmtap; + + /* The L1 scheduler */ + struct l1sched_state *sched; + /* GPRS state (MAC layer) */ + struct l1gprs_state *gprs; + + /* PHY interface (e.g. TRXC/TRXD) */ + void *phyif; + /* L2 interface (e.g. L1CTL) */ + void *l2if; + + /* State specific data of trxcon_fsm */ + void *fi_data; + + /* L1 parameters */ + struct { + uint16_t band_arfcn; + uint8_t tx_power; + uint8_t tsc; /* only valid for DCCH/PDCH */ + int8_t ta; + } l1p; + + /* PHY specific quirks */ + struct { + /* FBSB timeout extension (in TDMA FNs) */ + unsigned int fbsb_extend_fns; + } phy_quirks; +}; + +enum trxcon_log_cat { + TRXCON_LOGC_FSM, /* trxcon_fsm */ + TRXCON_LOGC_L1C, /* L1CTL control */ + TRXCON_LOGC_L1D, /* L1CTL data */ + TRXCON_LOGC_SCHC, /* l1sched control */ + TRXCON_LOGC_SCHD, /* l1sched data */ + TRXCON_LOGC_GPRS, /* l1gprs logging */ +}; + +void trxcon_set_log_cfg(const int *logc, unsigned int logc_num); + +struct trxcon_inst *trxcon_inst_alloc(void *ctx, unsigned int id); +void trxcon_inst_free(struct trxcon_inst *trxcon); + +int trxcon_l1ctl_receive(struct trxcon_inst *trxcon, struct msgb *msg); +int trxcon_l1ctl_send(struct trxcon_inst *trxcon, struct msgb *msg); +void trxcon_l1ctl_close(struct trxcon_inst *trxcon); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/trxcon_fsm.h b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon_fsm.h new file mode 100644 index 00000000..9eba4fde --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon_fsm.h @@ -0,0 +1,169 @@ +#pragma once + +#include <stdint.h> +#include <stdbool.h> + +#include <osmocom/core/fsm.h> + +extern struct osmo_fsm trxcon_fsm_def; + +enum trxcon_fsm_states { + TRXCON_ST_RESET, + TRXCON_ST_FULL_POWER_SCAN, + TRXCON_ST_FBSB_SEARCH, + TRXCON_ST_BCCH_CCCH, + TRXCON_ST_DEDICATED, + TRXCON_ST_PACKET_DATA, +}; + +enum trxcon_fsm_events { + TRXCON_EV_PHYIF_FAILURE, + TRXCON_EV_L2IF_FAILURE, + TRXCON_EV_RESET_FULL_REQ, + TRXCON_EV_RESET_SCHED_REQ, + TRXCON_EV_FULL_POWER_SCAN_REQ, + TRXCON_EV_FULL_POWER_SCAN_RES, + TRXCON_EV_FBSB_SEARCH_REQ, + TRXCON_EV_FBSB_SEARCH_RES, + TRXCON_EV_SET_CCCH_MODE_REQ, + TRXCON_EV_SET_TCH_MODE_REQ, + TRXCON_EV_SET_PHY_CONFIG_REQ, + TRXCON_EV_TX_ACCESS_BURST_REQ, + TRXCON_EV_TX_ACCESS_BURST_CNF, + TRXCON_EV_UPDATE_SACCH_CACHE_REQ, + TRXCON_EV_DCH_EST_REQ, + TRXCON_EV_DCH_REL_REQ, + TRXCON_EV_TX_DATA_REQ, + TRXCON_EV_TX_DATA_CNF, + TRXCON_EV_RX_DATA_IND, + TRXCON_EV_CRYPTO_REQ, + TRXCON_EV_GPRS_UL_TBF_CFG_REQ, /* param: L1CTL msgb */ + TRXCON_EV_GPRS_DL_TBF_CFG_REQ, /* param: L1CTL msgb */ + TRXCON_EV_GPRS_UL_BLOCK_REQ, /* param: L1CTL msgb */ +}; + +/* param of TRXCON_EV_FULL_POWER_SCAN_REQ */ +struct trxcon_param_full_power_scan_req { + uint16_t band_arfcn_start; + uint16_t band_arfcn_stop; +}; + +/* param of TRXCON_EV_FULL_POWER_SCAN_RES */ +struct trxcon_param_full_power_scan_res { + uint16_t band_arfcn; + int dbm; +}; + +/* param of TRXCON_EV_FBSB_SEARCH_REQ */ +struct trxcon_param_fbsb_search_req { + uint16_t band_arfcn; + uint16_t timeout_fns; /* in TDMA Fn periods */ + uint8_t pchan_config; +}; + +/* param of TRXCON_EV_SET_{CCCH,TCH}_MODE_REQ */ +struct trxcon_param_set_ccch_tch_mode_req { + uint8_t mode; + struct { + uint8_t start_codec; + uint8_t codecs_bitmask; + } amr; + bool applied; +}; + +/* param of TRXCON_EV_SET_PHY_CONFIG_REQ */ +struct trxcon_param_set_phy_config_req { + enum { + TRXCON_PHY_CFGT_PCHAN_COMB, + TRXCON_PHY_CFGT_TX_PARAMS, + } type; + union { + struct { + uint8_t tn; + uint8_t pchan; + } pchan_comb; + struct { + uint8_t timing_advance; + uint8_t tx_power; + } tx_params; + }; +}; + +/* param of TRXCON_EV_TX_DATA_REQ */ +struct trxcon_param_tx_data_req { + bool traffic; + uint8_t chan_nr; + uint8_t link_id; + size_t data_len; + const uint8_t *data; +}; + +/* param of TRXCON_EV_TX_DATA_CNF */ +struct trxcon_param_tx_data_cnf { + bool traffic; + uint8_t chan_nr; + uint8_t link_id; + uint16_t band_arfcn; + uint32_t frame_nr; + size_t data_len; + const uint8_t *data; +}; + +/* param of TRXCON_EV_RX_DATA_IND */ +struct trxcon_param_rx_data_ind { + bool traffic; + uint8_t chan_nr; + uint8_t link_id; + uint16_t band_arfcn; + uint32_t frame_nr; + int16_t toa256; + int8_t rssi; + int n_errors; + int n_bits_total; + size_t data_len; + const uint8_t *data; +}; + +/* param of TRXCON_EV_TX_ACCESS_BURST_REQ */ +struct trxcon_param_tx_access_burst_req { + uint8_t chan_nr; + uint8_t link_id; + uint8_t offset; + uint8_t synch_seq; + uint16_t ra; + bool is_11bit; +}; + +/* param of TRXCON_EV_TX_ACCESS_BURST_CNF */ +struct trxcon_param_tx_access_burst_cnf { + uint16_t band_arfcn; + uint32_t frame_nr; +}; + +/* param of TRXCON_EV_DCH_EST_REQ */ +struct trxcon_param_dch_est_req { + uint8_t chan_nr; + uint8_t tch_mode; + uint8_t tsc; + + bool hopping; + union { + struct { /* hopping=false */ + uint16_t band_arfcn; + } h0; + struct { /* hopping=true */ + uint8_t hsn; + uint8_t maio; + uint8_t n; + uint16_t ma[64]; + } h1; + }; +}; + +/* param of TRXCON_EV_CRYPTO_REQ */ +struct trxcon_param_crypto_req { + uint8_t chan_nr; + uint8_t a5_algo; /* 0 is A5/0 */ + uint8_t key_len; + const uint8_t *key; +}; diff --git a/src/host/trxcon/l1ctl.c b/src/host/trxcon/l1ctl.c deleted file mode 100644 index e722624c..00000000 --- a/src/host/trxcon/l1ctl.c +++ /dev/null @@ -1,905 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * GSM L1 control interface handlers - * - * (C) 2014 by Sylvain Munaut <tnt@246tNt.com> - * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.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 <stdio.h> -#include <errno.h> -#include <unistd.h> -#include <stdlib.h> -#include <stdint.h> -#include <string.h> -#include <assert.h> - -#include <arpa/inet.h> - -#include <osmocom/core/msgb.h> -#include <osmocom/core/talloc.h> -#include <osmocom/core/select.h> -#include <osmocom/gsm/gsm_utils.h> -#include <osmocom/gsm/protocol/gsm_08_58.h> - -#include "logging.h" -#include "l1ctl_link.h" -#include "l1ctl_proto.h" - -#include "trx_if.h" -#include "sched_trx.h" - -static const char *arfcn2band_name(uint16_t arfcn) -{ - enum gsm_band band; - - if (gsm_arfcn2band_rc(arfcn, &band) < 0) - return "(invalid)"; - - return gsm_band_name(band); -} - -static struct msgb *l1ctl_alloc_msg(uint8_t msg_type) -{ - struct l1ctl_hdr *l1h; - struct msgb *msg; - - /** - * Each L1CTL message gets its own length pushed in front - * before sending. This is why we need this small headroom. - */ - msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_MSG_LEN_FIELD, - L1CTL_MSG_LEN_FIELD, "l1ctl_tx_msg"); - if (!msg) { - LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n"); - return NULL; - } - - msg->l1h = msgb_put(msg, sizeof(*l1h)); - l1h = (struct l1ctl_hdr *) msg->l1h; - l1h->msg_type = msg_type; - - return msg; -} - -int l1ctl_tx_pm_conf(struct l1ctl_link *l1l, uint16_t band_arfcn, - int dbm, int last) -{ - struct l1ctl_pm_conf *pmc; - struct msgb *msg; - - msg = l1ctl_alloc_msg(L1CTL_PM_CONF); - if (!msg) - return -ENOMEM; - - LOGP(DL1C, LOGL_DEBUG, "Send PM Conf (%s %d = %d dBm)\n", - arfcn2band_name(band_arfcn), - band_arfcn &~ ARFCN_FLAG_MASK, dbm); - - pmc = (struct l1ctl_pm_conf *) msgb_put(msg, sizeof(*pmc)); - pmc->band_arfcn = htons(band_arfcn); - pmc->pm[0] = dbm2rxlev(dbm); - pmc->pm[1] = 0; - - if (last) { - struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->l1h; - l1h->flags |= L1CTL_F_DONE; - } - - return l1ctl_link_send(l1l, msg); -} - -int l1ctl_tx_reset_ind(struct l1ctl_link *l1l, uint8_t type) -{ - struct msgb *msg; - struct l1ctl_reset *res; - - msg = l1ctl_alloc_msg(L1CTL_RESET_IND); - if (!msg) - return -ENOMEM; - - LOGP(DL1C, LOGL_DEBUG, "Send Reset Ind (%u)\n", type); - - res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res)); - res->type = type; - - return l1ctl_link_send(l1l, msg); -} - -int l1ctl_tx_reset_conf(struct l1ctl_link *l1l, uint8_t type) -{ - struct msgb *msg; - struct l1ctl_reset *res; - - msg = l1ctl_alloc_msg(L1CTL_RESET_CONF); - if (!msg) - return -ENOMEM; - - LOGP(DL1C, LOGL_DEBUG, "Send Reset Conf (%u)\n", type); - res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res)); - res->type = type; - - return l1ctl_link_send(l1l, msg); -} - -static struct l1ctl_info_dl *put_dl_info_hdr(struct msgb *msg, struct l1ctl_info_dl *dl_info) -{ - size_t len = sizeof(struct l1ctl_info_dl); - struct l1ctl_info_dl *dl = (struct l1ctl_info_dl *) msgb_put(msg, len); - - if (dl_info) /* Copy DL info provided by handler */ - memcpy(dl, dl_info, len); - else /* Init DL info header */ - memset(dl, 0x00, len); - - return dl; -} - -/* Fill in FBSB payload: BSIC and sync result */ -static struct l1ctl_fbsb_conf *fbsb_conf_make(struct msgb *msg, uint8_t result, uint8_t bsic) -{ - struct l1ctl_fbsb_conf *conf = (struct l1ctl_fbsb_conf *) msgb_put(msg, sizeof(*conf)); - - LOGP(DL1C, LOGL_DEBUG, "Send FBSB Conf (result=%u, bsic=%u)\n", result, bsic); - - conf->result = result; - conf->bsic = bsic; - - return conf; -} - -int l1ctl_tx_fbsb_conf(struct l1ctl_link *l1l, uint8_t result, - struct l1ctl_info_dl *dl_info, uint8_t bsic) -{ - struct l1ctl_fbsb_conf *conf; - struct msgb *msg; - - msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF); - if (msg == NULL) - return -ENOMEM; - - put_dl_info_hdr(msg, dl_info); - talloc_free(dl_info); - - conf = fbsb_conf_make(msg, result, bsic); - - /* FIXME: set proper value */ - conf->initial_freq_err = 0; - - /* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */ - l1l->fbsb_conf_sent = true; - - /* Abort FBSB expire timer */ - if (osmo_timer_pending(&l1l->fbsb_timer)) - osmo_timer_del(&l1l->fbsb_timer); - - return l1ctl_link_send(l1l, msg); -} - -int l1ctl_tx_ccch_mode_conf(struct l1ctl_link *l1l, uint8_t mode) -{ - struct l1ctl_ccch_mode_conf *conf; - struct msgb *msg; - - msg = l1ctl_alloc_msg(L1CTL_CCCH_MODE_CONF); - if (msg == NULL) - return -ENOMEM; - - conf = (struct l1ctl_ccch_mode_conf *) msgb_put(msg, sizeof(*conf)); - conf->ccch_mode = mode; - - return l1ctl_link_send(l1l, msg); -} - -/** - * Handles both L1CTL_DATA_IND and L1CTL_TRAFFIC_IND. - */ -int l1ctl_tx_dt_ind(struct l1ctl_link *l1l, struct l1ctl_info_dl *data, - uint8_t *l2, size_t l2_len, bool traffic) -{ - struct msgb *msg; - uint8_t *msg_l2; - - msg = l1ctl_alloc_msg(traffic ? - L1CTL_TRAFFIC_IND : L1CTL_DATA_IND); - if (msg == NULL) - return -ENOMEM; - - put_dl_info_hdr(msg, data); - - /* Copy the L2 payload if preset */ - if (l2 && l2_len > 0) { - msg_l2 = (uint8_t *) msgb_put(msg, l2_len); - memcpy(msg_l2, l2, l2_len); - } - - /* Put message to upper layers */ - return l1ctl_link_send(l1l, msg); -} - -int l1ctl_tx_rach_conf(struct l1ctl_link *l1l, - uint16_t band_arfcn, uint32_t fn) -{ - struct l1ctl_info_dl *dl; - struct msgb *msg; - - msg = l1ctl_alloc_msg(L1CTL_RACH_CONF); - if (msg == NULL) - return -ENOMEM; - - dl = put_dl_info_hdr(msg, NULL); - memset(dl, 0x00, sizeof(*dl)); - - dl->band_arfcn = htons(band_arfcn); - dl->frame_nr = htonl(fn); - - return l1ctl_link_send(l1l, msg); -} - - -/** - * Handles both L1CTL_DATA_CONF and L1CTL_TRAFFIC_CONF. - */ -int l1ctl_tx_dt_conf(struct l1ctl_link *l1l, - struct l1ctl_info_dl *data, bool traffic) -{ - struct msgb *msg; - - msg = l1ctl_alloc_msg(traffic ? - L1CTL_TRAFFIC_CONF : L1CTL_DATA_CONF); - if (msg == NULL) - return -ENOMEM; - - /* Copy DL frame header from source message */ - put_dl_info_hdr(msg, data); - - return l1ctl_link_send(l1l, msg); -} - -static enum gsm_phys_chan_config l1ctl_ccch_mode2pchan_config(enum ccch_mode mode) -{ - switch (mode) { - /* TODO: distinguish extended BCCH */ - case CCCH_MODE_NON_COMBINED: - case CCCH_MODE_NONE: - return GSM_PCHAN_CCCH; - - case CCCH_MODE_COMBINED: - return GSM_PCHAN_CCCH_SDCCH4; - case CCCH_MODE_COMBINED_CBCH: - return GSM_PCHAN_CCCH_SDCCH4_CBCH; - - default: - LOGP(DL1C, LOGL_NOTICE, "Undandled CCCH mode (%u), " - "assuming non-combined configuration\n", mode); - return GSM_PCHAN_CCCH; - } -} - -/* FBSB expire timer */ -static void fbsb_timer_cb(void *data) -{ - struct l1ctl_link *l1l = (struct l1ctl_link *) data; - struct l1ctl_info_dl *dl; - struct msgb *msg; - - msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF); - if (msg == NULL) - return; - - LOGP(DL1C, LOGL_NOTICE, "FBSB timer fired for ARFCN %u\n", l1l->trx->band_arfcn &~ ARFCN_FLAG_MASK); - - dl = put_dl_info_hdr(msg, NULL); - - /* Fill in current ARFCN */ - dl->band_arfcn = htons(l1l->trx->band_arfcn); - - fbsb_conf_make(msg, 255, 0); - - /* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */ - l1l->fbsb_conf_sent = true; - - l1ctl_link_send(l1l, msg); -} - -static int l1ctl_rx_fbsb_req(struct l1ctl_link *l1l, struct msgb *msg) -{ - enum gsm_phys_chan_config ch_config; - struct l1ctl_fbsb_req *fbsb; - uint16_t band_arfcn; - uint16_t timeout; - int rc = 0; - - fbsb = (struct l1ctl_fbsb_req *) msg->l1h; - if (msgb_l1len(msg) < sizeof(*fbsb)) { - LOGP(DL1C, LOGL_ERROR, "MSG too short FBSB Req: %u\n", - msgb_l1len(msg)); - rc = -EINVAL; - goto exit; - } - - ch_config = l1ctl_ccch_mode2pchan_config(fbsb->ccch_mode); - band_arfcn = ntohs(fbsb->band_arfcn); - timeout = ntohs(fbsb->timeout); - - LOGP(DL1C, LOGL_NOTICE, "Received FBSB request (%s %d)\n", - arfcn2band_name(band_arfcn), - band_arfcn &~ ARFCN_FLAG_MASK); - - /* Reset scheduler and clock counter */ - sched_trx_reset(l1l->trx, true); - - /* Configure a single timeslot */ - sched_trx_configure_ts(l1l->trx, 0, ch_config); - - /* Ask SCH handler to send L1CTL_FBSB_CONF */ - l1l->fbsb_conf_sent = false; - - /* Only if current ARFCN differs */ - if (l1l->trx->band_arfcn != band_arfcn) { - /* Update current ARFCN */ - l1l->trx->band_arfcn = band_arfcn; - - /* Tune transceiver to required ARFCN */ - trx_if_cmd_rxtune(l1l->trx, band_arfcn); - trx_if_cmd_txtune(l1l->trx, band_arfcn); - } - - /* Transceiver might have been powered on before, e.g. - * in case of sending L1CTL_FBSB_REQ due to signal loss. */ - if (!l1l->trx->powered_up) - trx_if_cmd_poweron(l1l->trx); - - /* Start FBSB expire timer */ - l1l->fbsb_timer.data = l1l; - l1l->fbsb_timer.cb = fbsb_timer_cb; - LOGP(DL1C, LOGL_INFO, "Starting FBSB timer %u ms\n", timeout * FRAME_DURATION_uS / 1000); - osmo_timer_schedule(&l1l->fbsb_timer, 0, - timeout * FRAME_DURATION_uS); - -exit: - msgb_free(msg); - return rc; -} - -static int l1ctl_rx_pm_req(struct l1ctl_link *l1l, struct msgb *msg) -{ - uint16_t band_arfcn_start, band_arfcn_stop; - struct l1ctl_pm_req *pmr; - int rc = 0; - - pmr = (struct l1ctl_pm_req *) msg->l1h; - if (msgb_l1len(msg) < sizeof(*pmr)) { - LOGP(DL1C, LOGL_ERROR, "MSG too short PM Req: %u\n", - msgb_l1len(msg)); - rc = -EINVAL; - goto exit; - } - - band_arfcn_start = ntohs(pmr->range.band_arfcn_from); - band_arfcn_stop = ntohs(pmr->range.band_arfcn_to); - - LOGP(DL1C, LOGL_NOTICE, "Received power measurement " - "request (%s: %d -> %d)\n", - arfcn2band_name(band_arfcn_start), - band_arfcn_start &~ ARFCN_FLAG_MASK, - band_arfcn_stop &~ ARFCN_FLAG_MASK); - - /* Send measurement request to transceiver */ - rc = trx_if_cmd_measure(l1l->trx, band_arfcn_start, band_arfcn_stop); - -exit: - msgb_free(msg); - return rc; -} - -static int l1ctl_rx_reset_req(struct l1ctl_link *l1l, struct msgb *msg) -{ - struct l1ctl_reset *res; - int rc = 0; - - res = (struct l1ctl_reset *) msg->l1h; - if (msgb_l1len(msg) < sizeof(*res)) { - LOGP(DL1C, LOGL_ERROR, "MSG too short Reset Req: %u\n", - msgb_l1len(msg)); - rc = -EINVAL; - goto exit; - } - - LOGP(DL1C, LOGL_NOTICE, "Received reset request (%u)\n", - res->type); - - switch (res->type) { - case L1CTL_RES_T_FULL: - /* TODO: implement trx_if_reset() */ - trx_if_cmd_poweroff(l1l->trx); - trx_if_cmd_echo(l1l->trx); - - /* Fall through */ - case L1CTL_RES_T_SCHED: - sched_trx_reset(l1l->trx, true); - break; - default: - LOGP(DL1C, LOGL_ERROR, "Unknown L1CTL_RESET_REQ type\n"); - goto exit; - } - - /* Confirm */ - rc = l1ctl_tx_reset_conf(l1l, res->type); - -exit: - msgb_free(msg); - return rc; -} - -static int l1ctl_rx_echo_req(struct l1ctl_link *l1l, struct msgb *msg) -{ - struct l1ctl_hdr *l1h; - - LOGP(DL1C, LOGL_NOTICE, "Recv Echo Req\n"); - LOGP(DL1C, LOGL_NOTICE, "Send Echo Conf\n"); - - /* Nothing to do, just send it back */ - l1h = (struct l1ctl_hdr *) msg->l1h; - l1h->msg_type = L1CTL_ECHO_CONF; - msg->data = msg->l1h; - - return l1ctl_link_send(l1l, msg); -} - -static int l1ctl_rx_ccch_mode_req(struct l1ctl_link *l1l, struct msgb *msg) -{ - enum gsm_phys_chan_config ch_config; - struct l1ctl_ccch_mode_req *req; - struct trx_ts *ts; - int rc = 0; - - req = (struct l1ctl_ccch_mode_req *) msg->l1h; - if (msgb_l1len(msg) < sizeof(*req)) { - LOGP(DL1C, LOGL_ERROR, "MSG too short Reset Req: %u\n", - msgb_l1len(msg)); - rc = -EINVAL; - goto exit; - } - - LOGP(DL1C, LOGL_NOTICE, "Received CCCH mode request (%u)\n", - req->ccch_mode); /* TODO: add value-string for ccch_mode */ - - /* Make sure that TS0 is allocated and configured */ - ts = l1l->trx->ts_list[0]; - if (ts == NULL || ts->mf_layout == NULL) { - LOGP(DL1C, LOGL_ERROR, "TS0 is not configured"); - rc = -EINVAL; - goto exit; - } - - /* Choose corresponding channel combination */ - ch_config = l1ctl_ccch_mode2pchan_config(req->ccch_mode); - - /* Do nothing if the current mode matches required */ - if (ts->mf_layout->chan_config != ch_config) - rc = sched_trx_configure_ts(l1l->trx, 0, ch_config); - - /* Confirm reconfiguration */ - if (!rc) - rc = l1ctl_tx_ccch_mode_conf(l1l, req->ccch_mode); - -exit: - msgb_free(msg); - return rc; -} - -static int l1ctl_rx_rach_req(struct l1ctl_link *l1l, struct msgb *msg, bool ext) -{ - struct l1ctl_ext_rach_req *ext_req; - struct l1ctl_rach_req *req; - struct l1ctl_info_ul *ul; - struct trx_ts_prim *prim; - size_t len; - int rc; - - ul = (struct l1ctl_info_ul *) msg->l1h; - - /* Is it extended (11-bit) RACH or not? */ - if (ext) { - ext_req = (struct l1ctl_ext_rach_req *) ul->payload; - ext_req->offset = ntohs(ext_req->offset); - ext_req->ra11 = ntohs(ext_req->ra11); - len = sizeof(*ext_req); - - LOGP(DL1C, LOGL_NOTICE, "Received extended (11-bit) RACH request " - "(offset=%u, synch_seq=%u, ra11=0x%02hx)\n", - ext_req->offset, ext_req->synch_seq, ext_req->ra11); - } else { - req = (struct l1ctl_rach_req *) ul->payload; - req->offset = ntohs(req->offset); - len = sizeof(*req); - - LOGP(DL1C, LOGL_NOTICE, "Received regular (8-bit) RACH request " - "(offset=%u, ra=0x%02x)\n", req->offset, req->ra); - } - - /* The controlling L1CTL side always does include the UL info header, - * but may leave it empty. We assume RACH is on TS0 in this case. */ - if (ul->chan_nr == 0x00) { - LOGP(DL1C, LOGL_NOTICE, "The UL info header is empty, " - "assuming RACH is on TS0\n"); - ul->chan_nr = RSL_CHAN_RACH; - } - - /* Init a new primitive */ - rc = sched_prim_init(l1l->trx, &prim, len, ul->chan_nr, ul->link_id); - if (rc) - goto exit; - - /** - * Push this primitive to the transmit queue. - * Indicated timeslot needs to be configured. - */ - rc = sched_prim_push(l1l->trx, prim, ul->chan_nr); - if (rc) { - talloc_free(prim); - goto exit; - } - - /* Fill in the payload */ - memcpy(prim->payload, ul->payload, len); - -exit: - msgb_free(msg); - return rc; -} - -static int l1ctl_proc_est_req_h0(struct trx_instance *trx, struct l1ctl_h0 *h) -{ - uint16_t band_arfcn; - int rc = 0; - - band_arfcn = ntohs(h->band_arfcn); - - LOGP(DL1C, LOGL_NOTICE, "L1CTL_DM_EST_REQ indicates a single " - "ARFCN=%u channel\n", band_arfcn &~ ARFCN_FLAG_MASK); - - /* Do we need to retune? */ - if (trx->band_arfcn == band_arfcn) - return 0; - - /* Tune transceiver to required ARFCN */ - rc |= trx_if_cmd_rxtune(trx, band_arfcn); - rc |= trx_if_cmd_txtune(trx, band_arfcn); - if (rc) - return rc; - - /* Update current ARFCN */ - trx->band_arfcn = band_arfcn; - - return 0; -} - -static int l1ctl_proc_est_req_h1(struct trx_instance *trx, struct l1ctl_h1 *h) -{ - int rc; - - LOGP(DL1C, LOGL_NOTICE, "L1CTL_DM_EST_REQ indicates a Frequency " - "Hopping (hsn=%u, maio=%u, chans=%u) channel\n", - h->hsn, h->maio, h->n); - - /* No channels?!? */ - if (!h->n) { - LOGP(DL1C, LOGL_ERROR, "No channels in mobile allocation?!?\n"); - return -EINVAL; - } - - /* Forward hopping parameters to TRX */ - rc = trx_if_cmd_setfh(trx, h->hsn, h->maio, h->ma, h->n); - if (rc) - return rc; - - /** - * TODO: update the state of trx_instance somehow - * in order to indicate that it is in hopping mode... - */ - return 0; -} - -static int l1ctl_rx_dm_est_req(struct l1ctl_link *l1l, struct msgb *msg) -{ - enum gsm_phys_chan_config config; - struct l1ctl_dm_est_req *est_req; - struct l1ctl_info_ul *ul; - struct trx_ts *ts; - uint8_t chan_nr, tn; - int rc; - - ul = (struct l1ctl_info_ul *) msg->l1h; - est_req = (struct l1ctl_dm_est_req *) ul->payload; - - chan_nr = ul->chan_nr; - tn = chan_nr & 0x07; - - LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_DM_EST_REQ " - "(tn=%u, chan_nr=0x%02x, tsc=%u, tch_mode=0x%02x)\n", - tn, chan_nr, est_req->tsc, est_req->tch_mode); - - /* Determine channel config */ - config = sched_trx_chan_nr2pchan_config(chan_nr); - if (config == GSM_PCHAN_NONE) { - LOGP(DL1C, LOGL_ERROR, "Couldn't determine channel config\n"); - rc = -EINVAL; - goto exit; - } - - /* Frequency hopping? */ - if (est_req->h) - rc = l1ctl_proc_est_req_h1(l1l->trx, &est_req->h1); - else /* Single ARFCN */ - rc = l1ctl_proc_est_req_h0(l1l->trx, &est_req->h0); - if (rc) - goto exit; - - /* Update TSC (Training Sequence Code) */ - l1l->trx->tsc = est_req->tsc; - - /* Configure requested TS */ - rc = sched_trx_configure_ts(l1l->trx, tn, config); - ts = l1l->trx->ts_list[tn]; - if (rc) { - rc = -EINVAL; - goto exit; - } - - /* Deactivate all lchans */ - sched_trx_deactivate_all_lchans(ts); - - /* Activate only requested lchans */ - rc = sched_trx_set_lchans(ts, chan_nr, 1, est_req->tch_mode); - if (rc) { - LOGP(DL1C, LOGL_ERROR, "Couldn't activate requested lchans\n"); - rc = -EINVAL; - goto exit; - } - -exit: - msgb_free(msg); - return rc; -} - -static int l1ctl_rx_dm_rel_req(struct l1ctl_link *l1l, struct msgb *msg) -{ - LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_DM_REL_REQ, " - "switching back to CCCH\n"); - - /* Reset scheduler */ - sched_trx_reset(l1l->trx, false); - - msgb_free(msg); - return 0; -} - -/** - * Handles both L1CTL_DATA_REQ and L1CTL_TRAFFIC_REQ. - */ -static int l1ctl_rx_dt_req(struct l1ctl_link *l1l, - struct msgb *msg, bool traffic) -{ - struct l1ctl_info_ul *ul; - struct trx_ts_prim *prim; - uint8_t chan_nr, link_id; - size_t payload_len; - int rc; - - /* Extract UL frame header */ - ul = (struct l1ctl_info_ul *) msg->l1h; - - /* Calculate the payload len */ - msg->l2h = ul->payload; - payload_len = msgb_l2len(msg); - - /* Obtain channel description */ - chan_nr = ul->chan_nr; - link_id = ul->link_id & 0x40; - - LOGP(DL1D, LOGL_DEBUG, "Recv %s Req (chan_nr=0x%02x, " - "link_id=0x%02x, len=%zu)\n", traffic ? "TRAFFIC" : "DATA", - chan_nr, link_id, payload_len); - - /* Init a new primitive */ - rc = sched_prim_init(l1l->trx, &prim, payload_len, - chan_nr, link_id); - if (rc) - goto exit; - - /* Push this primitive to transmit queue */ - rc = sched_prim_push(l1l->trx, prim, chan_nr); - if (rc) { - talloc_free(prim); - goto exit; - } - - /* Fill in the payload */ - memcpy(prim->payload, ul->payload, payload_len); - -exit: - msgb_free(msg); - return rc; -} - -static int l1ctl_rx_param_req(struct l1ctl_link *l1l, struct msgb *msg) -{ - struct l1ctl_par_req *par_req; - struct l1ctl_info_ul *ul; - - ul = (struct l1ctl_info_ul *) msg->l1h; - par_req = (struct l1ctl_par_req *) ul->payload; - - LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_PARAM_REQ " - "(ta=%d, tx_power=%u)\n", par_req->ta, par_req->tx_power); - - /* Instruct TRX to use new TA value */ - if (l1l->trx->ta != par_req->ta) { - trx_if_cmd_setta(l1l->trx, par_req->ta); - l1l->trx->ta = par_req->ta; - } - - l1l->trx->tx_power = par_req->tx_power; - - msgb_free(msg); - return 0; -} - -static int l1ctl_rx_tch_mode_req(struct l1ctl_link *l1l, struct msgb *msg) -{ - struct l1ctl_tch_mode_req *req; - struct trx_lchan_state *lchan; - struct trx_ts *ts; - int i; - - req = (struct l1ctl_tch_mode_req *) msg->l1h; - - LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_TCH_MODE_REQ " - "(tch_mode=%u, audio_mode=%u)\n", req->tch_mode, req->audio_mode); - - /* Iterate over timeslot list */ - for (i = 0; i < TRX_TS_COUNT; i++) { - /* Timeslot is not allocated */ - ts = l1l->trx->ts_list[i]; - if (ts == NULL) - continue; - - /* Timeslot is not configured */ - if (ts->mf_layout == NULL) - continue; - - /* Iterate over all allocated lchans */ - llist_for_each_entry(lchan, &ts->lchans, list) { - /* Omit inactive channels */ - if (!lchan->active) - continue; - - /* Set TCH mode */ - lchan->tch_mode = req->tch_mode; - } - } - - /* TODO: do we need to care about audio_mode? */ - - msgb_free(msg); - return 0; -} - -static int l1ctl_rx_crypto_req(struct l1ctl_link *l1l, struct msgb *msg) -{ - struct l1ctl_crypto_req *req; - struct l1ctl_info_ul *ul; - struct trx_ts *ts; - uint8_t tn; - int rc = 0; - - ul = (struct l1ctl_info_ul *) msg->l1h; - req = (struct l1ctl_crypto_req *) ul->payload; - - LOGP(DL1C, LOGL_NOTICE, "L1CTL_CRYPTO_REQ (algo=A5/%u, key_len=%u)\n", - req->algo, req->key_len); - - /* Determine TS index */ - tn = ul->chan_nr & 0x7; - - /* Make sure that required TS is allocated and configured */ - ts = l1l->trx->ts_list[tn]; - if (ts == NULL || ts->mf_layout == NULL) { - LOGP(DL1C, LOGL_ERROR, "TS %u is not configured\n", tn); - rc = -EINVAL; - goto exit; - } - - /* Poke scheduler */ - rc = sched_trx_start_ciphering(ts, req->algo, req->key, req->key_len); - if (rc) { - LOGP(DL1C, LOGL_ERROR, "Couldn't configure ciphering\n"); - rc = -EINVAL; - goto exit; - } - -exit: - msgb_free(msg); - return rc; -} - -int l1ctl_rx_cb(struct l1ctl_link *l1l, struct msgb *msg) -{ - struct l1ctl_hdr *l1h; - - l1h = (struct l1ctl_hdr *) msg->l1h; - msg->l1h = l1h->data; - - switch (l1h->msg_type) { - case L1CTL_FBSB_REQ: - return l1ctl_rx_fbsb_req(l1l, msg); - case L1CTL_PM_REQ: - return l1ctl_rx_pm_req(l1l, msg); - case L1CTL_RESET_REQ: - return l1ctl_rx_reset_req(l1l, msg); - case L1CTL_ECHO_REQ: - return l1ctl_rx_echo_req(l1l, msg); - case L1CTL_CCCH_MODE_REQ: - return l1ctl_rx_ccch_mode_req(l1l, msg); - case L1CTL_RACH_REQ: - return l1ctl_rx_rach_req(l1l, msg, false); - case L1CTL_EXT_RACH_REQ: - return l1ctl_rx_rach_req(l1l, msg, true); - case L1CTL_DM_EST_REQ: - return l1ctl_rx_dm_est_req(l1l, msg); - case L1CTL_DM_REL_REQ: - return l1ctl_rx_dm_rel_req(l1l, msg); - case L1CTL_DATA_REQ: - return l1ctl_rx_dt_req(l1l, msg, false); - case L1CTL_TRAFFIC_REQ: - return l1ctl_rx_dt_req(l1l, msg, true); - case L1CTL_PARAM_REQ: - return l1ctl_rx_param_req(l1l, msg); - case L1CTL_TCH_MODE_REQ: - return l1ctl_rx_tch_mode_req(l1l, msg); - case L1CTL_CRYPTO_REQ: - return l1ctl_rx_crypto_req(l1l, msg); - - /* Not (yet) handled messages */ - case L1CTL_NEIGH_PM_REQ: - case L1CTL_DATA_TBF_REQ: - case L1CTL_TBF_CFG_REQ: - case L1CTL_DM_FREQ_REQ: - case L1CTL_SIM_REQ: - LOGP(DL1C, LOGL_NOTICE, "Ignoring unsupported message " - "(type=%u)\n", l1h->msg_type); - msgb_free(msg); - return -ENOTSUP; - default: - LOGP(DL1C, LOGL_ERROR, "Unknown MSG type %u: %s\n", l1h->msg_type, - osmo_hexdump(msgb_data(msg), msgb_length(msg))); - msgb_free(msg); - return -EINVAL; - } -} - -void l1ctl_shutdown_cb(struct l1ctl_link *l1l) -{ - /* Abort FBSB expire timer */ - if (osmo_timer_pending(&l1l->fbsb_timer)) - osmo_timer_del(&l1l->fbsb_timer); -} diff --git a/src/host/trxcon/l1ctl.h b/src/host/trxcon/l1ctl.h deleted file mode 100644 index 48bbe097..00000000 --- a/src/host/trxcon/l1ctl.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include <stdint.h> -#include <osmocom/core/msgb.h> - -#include "l1ctl_link.h" -#include "l1ctl_proto.h" - -/* Event handlers */ -int l1ctl_rx_cb(struct l1ctl_link *l1l, struct msgb *msg); -void l1ctl_shutdown_cb(struct l1ctl_link *l1l); - -int l1ctl_tx_fbsb_conf(struct l1ctl_link *l1l, uint8_t result, - struct l1ctl_info_dl *dl_info, uint8_t bsic); -int l1ctl_tx_ccch_mode_conf(struct l1ctl_link *l1l, uint8_t mode); -int l1ctl_tx_pm_conf(struct l1ctl_link *l1l, uint16_t band_arfcn, - int dbm, int last); -int l1ctl_tx_reset_conf(struct l1ctl_link *l1l, uint8_t type); -int l1ctl_tx_reset_ind(struct l1ctl_link *l1l, uint8_t type); - -int l1ctl_tx_dt_ind(struct l1ctl_link *l1l, struct l1ctl_info_dl *data, - uint8_t *l2, size_t l2_len, bool traffic); -int l1ctl_tx_dt_conf(struct l1ctl_link *l1l, - struct l1ctl_info_dl *data, bool traffic); -int l1ctl_tx_rach_conf(struct l1ctl_link *l1l, - uint16_t band_arfcn, uint32_t fn); diff --git a/src/host/trxcon/l1ctl_link.c b/src/host/trxcon/l1ctl_link.c deleted file mode 100644 index b7ea262a..00000000 --- a/src/host/trxcon/l1ctl_link.c +++ /dev/null @@ -1,319 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * GSM L1 control socket (/tmp/osmocom_l2) handlers - * - * (C) 2013 by Sylvain Munaut <tnt@246tNt.com> - * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.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 <stdio.h> -#include <errno.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <assert.h> - -#include <sys/un.h> -#include <arpa/inet.h> -#include <sys/socket.h> - -#include <osmocom/core/fsm.h> -#include <osmocom/core/talloc.h> -#include <osmocom/core/select.h> -#include <osmocom/core/socket.h> -#include <osmocom/core/write_queue.h> - -#include "trxcon.h" -#include "logging.h" -#include "l1ctl_link.h" -#include "l1ctl.h" - -static struct value_string l1ctl_evt_names[] = { - { 0, NULL } /* no events? */ -}; - -static struct osmo_fsm_state l1ctl_fsm_states[] = { - [L1CTL_STATE_IDLE] = { - .out_state_mask = GEN_MASK(L1CTL_STATE_CONNECTED), - .name = "IDLE", - }, - [L1CTL_STATE_CONNECTED] = { - .out_state_mask = GEN_MASK(L1CTL_STATE_IDLE), - .name = "CONNECTED", - }, -}; - -static struct osmo_fsm l1ctl_fsm = { - .name = "l1ctl_link_fsm", - .states = l1ctl_fsm_states, - .num_states = ARRAY_SIZE(l1ctl_fsm_states), - .log_subsys = DL1C, - .event_names = l1ctl_evt_names, -}; - -static int l1ctl_link_read_cb(struct osmo_fd *bfd) -{ - struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data; - struct msgb *msg; - uint16_t len; - int rc; - - /* Attempt to read from socket */ - rc = read(bfd->fd, &len, L1CTL_MSG_LEN_FIELD); - if (rc < L1CTL_MSG_LEN_FIELD) { - LOGP(DL1D, LOGL_NOTICE, "L1CTL has lost connection\n"); - if (rc >= 0) - rc = -EIO; - l1ctl_link_close_conn(l1l); - return rc; - } - - /* Check message length */ - len = ntohs(len); - if (len > L1CTL_LENGTH) { - LOGP(DL1D, LOGL_ERROR, "Length is too big: %u\n", len); - return -EINVAL; - } - - /* Allocate a new msg */ - msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM, - L1CTL_HEADROOM, "l1ctl_rx_msg"); - if (!msg) { - LOGP(DL1D, LOGL_ERROR, "Failed to allocate msg\n"); - return -ENOMEM; - } - - msg->l1h = msgb_put(msg, len); - rc = read(bfd->fd, msg->l1h, msgb_l1len(msg)); - if (rc != len) { - LOGP(DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: " - "%s\n", len, rc, strerror(errno)); - msgb_free(msg); - return rc; - } - - /* Debug print */ - LOGP(DL1D, LOGL_DEBUG, "RX: '%s'\n", - osmo_hexdump(msg->data, msg->len)); - - /* Call L1CTL handler */ - l1ctl_rx_cb(l1l, msg); - - return 0; -} - -static int l1ctl_link_write_cb(struct osmo_fd *bfd, struct msgb *msg) -{ - int len; - - if (bfd->fd <= 0) - return -EINVAL; - - len = write(bfd->fd, msg->data, msg->len); - if (len != msg->len) { - LOGP(DL1D, LOGL_ERROR, "Failed to write data: " - "written (%d) < msg_len (%d)\n", len, msg->len); - return -1; - } - - return 0; -} - -/* Connection handler */ -static int l1ctl_link_accept(struct osmo_fd *bfd, unsigned int flags) -{ - struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data; - struct osmo_fd *conn_bfd = &l1l->wq.bfd; - struct sockaddr_un un_addr; - socklen_t len; - int cfd; - - len = sizeof(un_addr); - cfd = accept(bfd->fd, (struct sockaddr *) &un_addr, &len); - if (cfd < 0) { - LOGP(DL1C, LOGL_ERROR, "Failed to accept a new connection\n"); - return -1; - } - - /* Check if we already have an active connection */ - if (conn_bfd->fd != -1) { - LOGP(DL1C, LOGL_NOTICE, "A new connection rejected: " - "we already have another active\n"); - close(cfd); - return 0; - } - - osmo_wqueue_init(&l1l->wq, 100); - INIT_LLIST_HEAD(&conn_bfd->list); - - l1l->wq.write_cb = l1ctl_link_write_cb; - l1l->wq.read_cb = l1ctl_link_read_cb; - conn_bfd->when = BSC_FD_READ; - conn_bfd->data = l1l; - conn_bfd->fd = cfd; - - if (osmo_fd_register(conn_bfd) != 0) { - LOGP(DL1C, LOGL_ERROR, "Failed to register new connection fd\n"); - close(conn_bfd->fd); - conn_bfd->fd = -1; - return -1; - } - - osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_CONNECT, l1l); - osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_CONNECTED, 0, 0); - - LOGP(DL1C, LOGL_NOTICE, "L1CTL has a new connection\n"); - - return 0; -} - -int l1ctl_link_send(struct l1ctl_link *l1l, struct msgb *msg) -{ - uint16_t *len; - - /* Debug print */ - LOGP(DL1D, LOGL_DEBUG, "TX: '%s'\n", - osmo_hexdump(msg->data, msg->len)); - - if (msg->l1h != msg->data) - LOGP(DL1D, LOGL_INFO, "Message L1 header != Message Data\n"); - - /* Prepend 16-bit length before sending */ - len = (uint16_t *) msgb_push(msg, L1CTL_MSG_LEN_FIELD); - *len = htons(msg->len - L1CTL_MSG_LEN_FIELD); - - if (osmo_wqueue_enqueue(&l1l->wq, msg) != 0) { - LOGP(DL1D, LOGL_ERROR, "Failed to enqueue msg!\n"); - msgb_free(msg); - return -EIO; - } - - return 0; -} - -int l1ctl_link_close_conn(struct l1ctl_link *l1l) -{ - struct osmo_fd *conn_bfd = &l1l->wq.bfd; - - if (conn_bfd->fd <= 0) - return -EINVAL; - - /* Close connection socket */ - osmo_fd_unregister(conn_bfd); - close(conn_bfd->fd); - conn_bfd->fd = -1; - - /* Clear pending messages */ - osmo_wqueue_clear(&l1l->wq); - - osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_DISCONNECT, l1l); - osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_IDLE, 0, 0); - - return 0; -} - -struct l1ctl_link *l1ctl_link_init(void *tall_ctx, const char *sock_path) -{ - struct l1ctl_link *l1l; - struct osmo_fd *bfd; - int rc; - - LOGP(DL1C, LOGL_NOTICE, "Init L1CTL link (%s)\n", sock_path); - - l1l = talloc_zero(tall_ctx, struct l1ctl_link); - if (!l1l) { - LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n"); - return NULL; - } - - /* Allocate a new dedicated state machine */ - l1l->fsm = osmo_fsm_inst_alloc(&l1ctl_fsm, l1l, - NULL, LOGL_DEBUG, "l1ctl_link"); - if (l1l->fsm == NULL) { - LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance " - "of FSM '%s'\n", l1ctl_fsm.name); - talloc_free(l1l); - return NULL; - } - - /* Create a socket and bind handlers */ - bfd = &l1l->listen_bfd; - rc = osmo_sock_unix_init_ofd(bfd, SOCK_STREAM, 0, sock_path, - OSMO_SOCK_F_BIND); - if (rc < 0) { - LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n", - strerror(errno)); - osmo_fsm_inst_free(l1l->fsm); - talloc_free(l1l); - return NULL; - } - - /* Bind shutdown handler */ - l1l->shutdown_cb = l1ctl_shutdown_cb; - - /* Bind connection handler */ - bfd->cb = l1ctl_link_accept; - bfd->when = BSC_FD_READ; - bfd->data = l1l; - - /** - * To be able to accept first connection and - * drop others, it should be set to -1 - */ - l1l->wq.bfd.fd = -1; - - return l1l; -} - -void l1ctl_link_shutdown(struct l1ctl_link *l1l) -{ - struct osmo_fd *listen_bfd; - - /* May be unallocated due to init error */ - if (!l1l) - return; - - LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL link\n"); - - /* Call shutdown callback */ - if (l1l->shutdown_cb != NULL) - l1l->shutdown_cb(l1l); - - listen_bfd = &l1l->listen_bfd; - - /* Check if we have an established connection */ - if (l1l->wq.bfd.fd != -1) - l1ctl_link_close_conn(l1l); - - /* Unbind listening socket */ - if (listen_bfd->fd != -1) { - osmo_fd_unregister(listen_bfd); - close(listen_bfd->fd); - listen_bfd->fd = -1; - } - - osmo_fsm_inst_free(l1l->fsm); - talloc_free(l1l); -} - -static __attribute__((constructor)) void on_dso_load(void) -{ - OSMO_ASSERT(osmo_fsm_register(&l1ctl_fsm) == 0); -} diff --git a/src/host/trxcon/l1ctl_link.h b/src/host/trxcon/l1ctl_link.h deleted file mode 100644 index a333e407..00000000 --- a/src/host/trxcon/l1ctl_link.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include <stdint.h> - -#include <osmocom/core/write_queue.h> -#include <osmocom/core/select.h> -#include <osmocom/core/timer.h> -#include <osmocom/core/msgb.h> -#include <osmocom/core/fsm.h> - -#define L1CTL_LENGTH 256 -#define L1CTL_HEADROOM 32 - -/** - * Each L1CTL message gets its own length pushed - * as two bytes in front before sending. - */ -#define L1CTL_MSG_LEN_FIELD 2 - -/* Forward declaration to avoid mutual include */ -struct trx_instance; - -enum l1ctl_fsm_states { - L1CTL_STATE_IDLE = 0, - L1CTL_STATE_CONNECTED, -}; - -struct l1ctl_link { - struct osmo_fsm_inst *fsm; - struct osmo_fd listen_bfd; - struct osmo_wqueue wq; - - /* Bind TRX instance */ - struct trx_instance *trx; - - /* L1CTL handlers specific */ - struct osmo_timer_list fbsb_timer; - bool fbsb_conf_sent; - - /* Shutdown callback */ - void (*shutdown_cb)(struct l1ctl_link *l1l); -}; - -struct l1ctl_link *l1ctl_link_init(void *tall_ctx, const char *sock_path); -void l1ctl_link_shutdown(struct l1ctl_link *l1l); - -int l1ctl_link_send(struct l1ctl_link *l1l, struct msgb *msg); -int l1ctl_link_close_conn(struct l1ctl_link *l1l); diff --git a/src/host/trxcon/l1ctl_proto.h b/src/host/trxcon/l1ctl_proto.h deleted file mode 120000 index 75862bae..00000000 --- a/src/host/trxcon/l1ctl_proto.h +++ /dev/null @@ -1 +0,0 @@ -../../../include/l1ctl_proto.h
\ No newline at end of file diff --git a/src/host/trxcon/logging.h b/src/host/trxcon/logging.h deleted file mode 100644 index 152c3467..00000000 --- a/src/host/trxcon/logging.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include <osmocom/core/logging.h> - -#define DEBUG_DEFAULT "DAPP:DL1C:DL1D:DTRX:DTRXD:DSCH:DSCHD" - -enum { - DAPP, - DL1C, - DL1D, - DTRX, - DTRXD, - DSCH, - DSCHD, -}; - -int trx_log_init(void *tall_ctx, const char *category_mask); diff --git a/src/host/trxcon/m4/.gitkeep b/src/host/trxcon/m4/.gitkeep new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/host/trxcon/m4/.gitkeep diff --git a/src/host/trxcon/sched_clck.c b/src/host/trxcon/sched_clck.c deleted file mode 100644 index 66477b24..00000000 --- a/src/host/trxcon/sched_clck.c +++ /dev/null @@ -1,206 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: clock synchronization - * - * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> - * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> - * (C) 2015 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 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 <stdio.h> -#include <errno.h> -#include <stdlib.h> -#include <stdint.h> -#include <inttypes.h> -#include <string.h> - -#include <osmocom/core/talloc.h> -#include <osmocom/core/msgb.h> -#include <osmocom/core/bits.h> -#include <osmocom/core/fsm.h> -#include <osmocom/core/timer.h> -#include <osmocom/core/timer_compat.h> -#include <osmocom/gsm/a5.h> - -#include "scheduler.h" -#include "logging.h" -#include "trx_if.h" - -#define MAX_FN_SKEW 50 -#define TRX_LOSS_FRAMES 400 - -static void sched_clck_tick(void *data) -{ - struct trx_sched *sched = (struct trx_sched *) data; - struct timespec tv_now, *tv_clock, elapsed; - int64_t elapsed_us; - const struct timespec frame_duration = { .tv_sec = 0, .tv_nsec = FRAME_DURATION_uS * 1000 }; - - /* Check if transceiver is still alive */ - if (sched->fn_counter_lost++ == TRX_LOSS_FRAMES) { - LOGP(DSCH, LOGL_DEBUG, "No more clock from transceiver\n"); - sched->state = SCH_CLCK_STATE_WAIT; - - return; - } - - /* Get actual / previous frame time */ - osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now); - tv_clock = &sched->clock; - - timespecsub(&tv_now, tv_clock, &elapsed); - elapsed_us = (elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000); - - /* If someone played with clock, or if the process stalled */ - if (elapsed_us > FRAME_DURATION_uS * MAX_FN_SKEW || elapsed_us < 0) { - LOGP(DSCH, LOGL_NOTICE, "PC clock skew: " - "elapsed uS %" PRId64 "\n", elapsed_us); - - sched->state = SCH_CLCK_STATE_WAIT; - - return; - } - - /* Schedule next FN clock */ - while (elapsed_us > FRAME_DURATION_uS / 2) { - timespecadd(tv_clock, &frame_duration, tv_clock); - elapsed_us -= FRAME_DURATION_uS; - - sched->fn_counter_proc = TDMA_FN_INC(sched->fn_counter_proc); - - /* Call frame callback */ - if (sched->clock_cb) - sched->clock_cb(sched); - } - - osmo_timer_schedule(&sched->clock_timer, 0, - FRAME_DURATION_uS - elapsed_us); -} - -static void sched_clck_correct(struct trx_sched *sched, - struct timespec *tv_now, uint32_t fn) -{ - sched->fn_counter_proc = fn; - - /* Call frame callback */ - if (sched->clock_cb) - sched->clock_cb(sched); - - /* Schedule first FN clock */ - sched->clock = *tv_now; - memset(&sched->clock_timer, 0, sizeof(sched->clock_timer)); - - sched->clock_timer.cb = sched_clck_tick; - sched->clock_timer.data = sched; - osmo_timer_schedule(&sched->clock_timer, 0, FRAME_DURATION_uS); -} - -int sched_clck_handle(struct trx_sched *sched, uint32_t fn) -{ - struct timespec tv_now, *tv_clock, elapsed; - int64_t elapsed_us, elapsed_fn; - - /* Reset lost counter */ - sched->fn_counter_lost = 0; - - /* Get actual / previous frame time */ - osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now); - tv_clock = &sched->clock; - - /* If this is the first CLCK IND */ - if (sched->state == SCH_CLCK_STATE_WAIT) { - sched_clck_correct(sched, &tv_now, fn); - - LOGP(DSCH, LOGL_DEBUG, "Initial clock received: fn=%u\n", fn); - sched->state = SCH_CLCK_STATE_OK; - - return 0; - } - - LOGP(DSCH, LOGL_NOTICE, "Clock indication: fn=%u\n", fn); - - osmo_timer_del(&sched->clock_timer); - - /* Calculate elapsed time / frames since last processed fn */ - timespecsub(&tv_now, tv_clock, &elapsed); - elapsed_us = (elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000); - elapsed_fn = TDMA_FN_SUB(fn, sched->fn_counter_proc); - - if (elapsed_fn >= 135774) - elapsed_fn -= GSM_HYPERFRAME; - - /* Check for max clock skew */ - if (elapsed_fn > MAX_FN_SKEW || elapsed_fn < -MAX_FN_SKEW) { - LOGP(DSCH, LOGL_NOTICE, "GSM clock skew: old fn=%u, " - "new fn=%u\n", sched->fn_counter_proc, fn); - - sched_clck_correct(sched, &tv_now, fn); - return 0; - } - - LOGP(DSCH, LOGL_INFO, "GSM clock jitter: %" PRId64 "\n", - elapsed_fn * FRAME_DURATION_uS - elapsed_us); - - /* Too many frames have been processed already */ - if (elapsed_fn < 0) { - struct timespec duration; - /** - * Set clock to the time or last FN should - * have been transmitted - */ - duration.tv_nsec = (0 - elapsed_fn) * FRAME_DURATION_uS * 1000; - duration.tv_sec = duration.tv_nsec / 1000000000; - duration.tv_nsec = duration.tv_nsec % 1000000000; - timespecadd(&tv_now, &duration, tv_clock); - - /* Set time to the time our next FN has to be transmitted */ - osmo_timer_schedule(&sched->clock_timer, 0, - FRAME_DURATION_uS * (1 - elapsed_fn)); - - return 0; - } - - /* Transmit what we still need to transmit */ - while (fn != sched->fn_counter_proc) { - sched->fn_counter_proc = TDMA_FN_INC(sched->fn_counter_proc); - - /* Call frame callback */ - if (sched->clock_cb) - sched->clock_cb(sched); - } - - /* Schedule next FN to be transmitted */ - *tv_clock = tv_now; - osmo_timer_schedule(&sched->clock_timer, 0, FRAME_DURATION_uS); - - return 0; -} - -void sched_clck_reset(struct trx_sched *sched) -{ - /* Reset internal state */ - sched->state = SCH_CLCK_STATE_WAIT; - - /* Stop clock timer */ - osmo_timer_del(&sched->clock_timer); - - /* Flush counters */ - sched->fn_counter_proc = 0; - sched->fn_counter_lost = 0; -} diff --git a/src/host/trxcon/sched_lchan_common.c b/src/host/trxcon/sched_lchan_common.c deleted file mode 100644 index 615d81c9..00000000 --- a/src/host/trxcon/sched_lchan_common.c +++ /dev/null @@ -1,181 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: common routines for lchan handlers - * - * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.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 <errno.h> -#include <string.h> -#include <talloc.h> -#include <stdint.h> -#include <stdbool.h> - -#include <arpa/inet.h> - -#include <osmocom/core/logging.h> -#include <osmocom/core/bits.h> - -#include <osmocom/codec/codec.h> - -#include <osmocom/gsm/protocol/gsm_04_08.h> - -#include "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" -#include "logging.h" -#include "trx_if.h" -#include "l1ctl.h" - -/* GSM 05.02 Chapter 5.2.3 Normal Burst (NB) */ -const uint8_t sched_nb_training_bits[8][26] = { - { - 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, - 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, - }, - { - 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, - 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, - }, - { - 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, - 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, - }, - { - 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, - }, - { - 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, - 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, - }, - { - 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, - 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, - }, - { - 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, - 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, - }, - { - 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, - 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, - }, -}; - -int sched_send_dt_ind(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint8_t *l2, size_t l2_len, - int bit_error_count, bool dec_failed, bool traffic) -{ - const struct trx_lchan_desc *lchan_desc; - struct l1ctl_info_dl dl_hdr; - int dbm_avg; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - - /* Fill in known downlink info */ - dl_hdr.chan_nr = lchan_desc->chan_nr | ts->index; - dl_hdr.link_id = lchan_desc->link_id; - dl_hdr.band_arfcn = htons(trx->band_arfcn); - dl_hdr.frame_nr = htonl(lchan->rx_first_fn); - dl_hdr.num_biterr = bit_error_count; - - /* Convert average RSSI to RX level */ - if (lchan->meas.num) { - /* RX level: 0 .. 63 in typical GSM notation (dBm + 110) */ - dbm_avg = lchan->meas.rssi_sum / lchan->meas.num; - dl_hdr.rx_level = dbm2rxlev(dbm_avg); - } else { - /* No measurements, assuming the worst */ - dl_hdr.rx_level = 0; - } - - /* FIXME: set proper values */ - dl_hdr.snr = 0; - - /* Mark frame as broken if so */ - dl_hdr.fire_crc = dec_failed ? 2 : 0; - - /* Put a packet to higher layers */ - l1ctl_tx_dt_ind(trx->l1l, &dl_hdr, l2, l2_len, traffic); - - return 0; -} - -int sched_send_dt_conf(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, bool traffic) -{ - const struct trx_lchan_desc *lchan_desc; - struct l1ctl_info_dl dl_hdr; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - - /* Zero-initialize DL header, because we don't set all fields */ - memset(&dl_hdr, 0x00, sizeof(struct l1ctl_info_dl)); - - /* Fill in known downlink info */ - dl_hdr.chan_nr = lchan_desc->chan_nr | ts->index; - dl_hdr.link_id = lchan_desc->link_id; - dl_hdr.band_arfcn = htons(trx->band_arfcn); - dl_hdr.frame_nr = htonl(fn); - - l1ctl_tx_dt_conf(trx->l1l, &dl_hdr, traffic); - - return 0; -} - -/** - * Composes a bad frame indication message - * according to the current tch_mode. - * - * @param l2 Caller-allocated byte array - * @param lchan Logical channel to generate BFI for - * @return How much bytes were written - */ -size_t sched_bad_frame_ind(uint8_t *l2, struct trx_lchan_state *lchan) -{ - switch (lchan->tch_mode) { - case GSM48_CMODE_SPEECH_V1: - if (lchan->type == TRXC_TCHF) { /* Full Rate */ - memset(l2, 0x00, GSM_FR_BYTES); - l2[0] = 0xd0; - return GSM_FR_BYTES; - } else { /* Half Rate */ - memset(l2 + 1, 0x00, GSM_HR_BYTES); - l2[0] = 0x70; /* F = 0, FT = 111 */ - return GSM_HR_BYTES + 1; - } - case GSM48_CMODE_SPEECH_EFR: /* Enhanced Full Rate */ - memset(l2, 0x00, GSM_EFR_BYTES); - l2[0] = 0xc0; - return GSM_EFR_BYTES; - case GSM48_CMODE_SPEECH_AMR: /* Adaptive Multi Rate */ - /* FIXME: AMR is not implemented yet */ - return 0; - case GSM48_CMODE_SIGN: - LOGP(DSCH, LOGL_ERROR, "BFI is not allowed in signalling mode\n"); - return 0; - default: - LOGP(DSCH, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode); - return 0; - } -} diff --git a/src/host/trxcon/sched_lchan_pdtch.c b/src/host/trxcon/sched_lchan_pdtch.c deleted file mode 100644 index 733e5741..00000000 --- a/src/host/trxcon/sched_lchan_pdtch.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: handlers for DL / UL bursts on logical channels - * - * (C) 2018 by Vadim Yanitskiy <axilirator@gmail.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 <errno.h> -#include <string.h> -#include <stdint.h> - -#include <osmocom/core/logging.h> -#include <osmocom/core/bits.h> - -#include <osmocom/gsm/gsm_utils.h> -#include <osmocom/gsm/protocol/gsm_04_08.h> -#include <osmocom/coding/gsm0503_coding.h> - -#include "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" -#include "logging.h" -#include "trx_if.h" -#include "l1ctl.h" - -int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, - sbit_t *bits, int8_t rssi, int16_t toa256) -{ - const struct trx_lchan_desc *lchan_desc; - uint8_t l2[GPRS_L2_MAX_LEN], *mask; - int n_errors, n_bits_total, rc; - sbit_t *buffer, *offset; - uint32_t *first_fn; - size_t l2_len; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - first_fn = &lchan->rx_first_fn; - mask = &lchan->rx_burst_mask; - buffer = lchan->rx_bursts; - - LOGP(DSCHD, LOGL_DEBUG, "Packet data received on %s: " - "fn=%u ts=%u bid=%u\n", lchan_desc->name, fn, ts->index, bid); - - /* Reset internal state */ - if (bid == 0) { - /* Clean up old measurements */ - memset(&lchan->meas, 0x00, sizeof(lchan->meas)); - - *first_fn = fn; - *mask = 0x0; - } - - /* Update mask */ - *mask |= (1 << bid); - - /* Update measurements */ - lchan->meas.toa256_sum += toa256; - lchan->meas.rssi_sum += rssi; - lchan->meas.num++; - - /* Copy burst to buffer of 4 bursts */ - offset = buffer + bid * 116; - memcpy(offset, bits + 3, 58); - memcpy(offset + 58, bits + 87, 58); - - /* Wait until complete set of bursts */ - if (bid != 3) - return 0; - - /* Check for complete set of bursts */ - if ((*mask & 0xf) != 0xf) { - LOGP(DSCHD, LOGL_ERROR, "Received incomplete data frame at " - "fn=%u (%u/%u) for %s\n", *first_fn, - (*first_fn) % ts->mf_layout->period, - ts->mf_layout->period, - lchan_desc->name); - - return -1; - } - - /* Attempt to decode */ - rc = gsm0503_pdtch_decode(l2, buffer, - NULL, &n_errors, &n_bits_total); - if (rc < 0) { - LOGP(DSCHD, LOGL_ERROR, "Received bad packet data frame " - "at fn=%u (%u/%u) for %s\n", *first_fn, - (*first_fn) % ts->mf_layout->period, - ts->mf_layout->period, - lchan_desc->name); - } - - /* Determine L2 length */ - l2_len = rc > 0 ? rc : 0; - - /* Send a L2 frame to the higher layers */ - sched_send_dt_ind(trx, ts, lchan, - l2, l2_len, n_errors, rc < 0, true); - - return 0; -} - - -int tx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid) -{ - const struct trx_lchan_desc *lchan_desc; - ubit_t burst[GSM_BURST_LEN]; - ubit_t *buffer, *offset; - const uint8_t *tsc; - uint8_t *mask; - int rc; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - mask = &lchan->tx_burst_mask; - buffer = lchan->tx_bursts; - - if (bid > 0) { - /* If we have encoded bursts */ - if (*mask) - goto send_burst; - else - return 0; - } - - /* Encode payload */ - rc = gsm0503_pdtch_encode(buffer, lchan->prim->payload, - lchan->prim->payload_len); - if (rc < 0) { - LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n", - lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, - lchan->prim->payload_len)); - - /* Forget this primitive */ - sched_prim_drop(lchan); - - return -EINVAL; - } - -send_burst: - /* Determine which burst should be sent */ - offset = buffer + bid * 116; - - /* Update mask */ - *mask |= (1 << bid); - - /* Choose proper TSC */ - tsc = sched_nb_training_bits[trx->tsc]; - - /* Compose a new burst */ - memset(burst, 0, 3); /* TB */ - memcpy(burst + 3, offset, 58); /* Payload 1/2 */ - memcpy(burst + 61, tsc, 26); /* TSC */ - memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */ - memset(burst + 145, 0, 3); /* TB */ - - LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n", - lchan_desc->name, fn, ts->index, bid); - - /* Forward burst to scheduler */ - rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst); - if (rc) { - /* Forget this primitive */ - sched_prim_drop(lchan); - - /* Reset mask */ - *mask = 0x00; - - return rc; - } - - /* If we have sent the last (4/4) burst */ - if ((*mask & 0x0f) == 0x0f) { - /* Confirm data / traffic sending */ - sched_send_dt_conf(trx, ts, lchan, fn, true); - - /* Forget processed primitive */ - sched_prim_drop(lchan); - - /* Reset mask */ - *mask = 0x00; - } - - return 0; -} diff --git a/src/host/trxcon/sched_lchan_rach.c b/src/host/trxcon/sched_lchan_rach.c deleted file mode 100644 index 5d1f3ab9..00000000 --- a/src/host/trxcon/sched_lchan_rach.c +++ /dev/null @@ -1,181 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: handlers for DL / UL bursts on logical channels - * - * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.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 <errno.h> -#include <string.h> -#include <stdint.h> -#include <stdbool.h> - -#include <osmocom/core/logging.h> -#include <osmocom/core/bits.h> - -#include <osmocom/gsm/gsm_utils.h> -#include <osmocom/coding/gsm0503_coding.h> - -#include "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" -#include "logging.h" -#include "trx_if.h" -#include "l1ctl.h" - -/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)" */ -#define RACH_EXT_TAIL_BITS_LEN 8 -#define RACH_SYNCH_SEQ_LEN 41 -#define RACH_PAYLOAD_LEN 36 - -/* Extended tail bits (BN0..BN7) */ -static const ubit_t rach_ext_tail_bits[] = { - 0, 0, 1, 1, 1, 0, 1, 0, -}; - -/* Synchronization (training) sequence types */ -enum rach_synch_seq_t { - RACH_SYNCH_SEQ_UNKNOWN = -1, - RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */ - RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */ - RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */ - RACH_SYNCH_SEQ_NUM -}; - -/* Synchronization (training) sequence bits */ -static const char rach_synch_seq_bits[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = { - [RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000", - [RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101", - [RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111", -}; - -/* Synchronization (training) sequence names */ -static struct value_string rach_synch_seq_names[] = { - { RACH_SYNCH_SEQ_UNKNOWN, "UNKNOWN" }, - { RACH_SYNCH_SEQ_TS0, "TS0: GSM, GMSK" }, - { RACH_SYNCH_SEQ_TS1, "TS1: EGPRS, 8-PSK" }, - { RACH_SYNCH_SEQ_TS2, "TS2: EGPRS, GMSK" }, - { 0, NULL }, -}; - -/* Obtain a to-be-transmitted RACH burst */ -int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid) -{ - struct l1ctl_ext_rach_req *ext_req = NULL; - struct l1ctl_rach_req *req = NULL; - enum rach_synch_seq_t synch_seq; - uint8_t burst[GSM_BURST_LEN]; - uint8_t *burst_ptr = burst; - uint8_t payload[36]; - int i, rc; - - /* Is it extended (11-bit) RACH or not? */ - if (PRIM_IS_RACH11(lchan->prim)) { - ext_req = (struct l1ctl_ext_rach_req *) lchan->prim->payload; - synch_seq = ext_req->synch_seq; - - /* Check requested synch. sequence */ - if (synch_seq >= RACH_SYNCH_SEQ_NUM) { - LOGP(DSCHD, LOGL_ERROR, "Unknown RACH synch. sequence=0x%02x\n", synch_seq); - - /* Forget this primitive */ - sched_prim_drop(lchan); - return -ENOTSUP; - } - - /* Delay sending according to offset value */ - if (ext_req->offset-- > 0) - return 0; - - /* Encode extended (11-bit) payload */ - rc = gsm0503_rach_ext_encode(payload, ext_req->ra11, trx->bsic, true); - if (rc) { - LOGP(DSCHD, LOGL_ERROR, "Could not encode extended RACH burst " - "(ra=%u bsic=%u)\n", ext_req->ra11, trx->bsic); - - /* Forget this primitive */ - sched_prim_drop(lchan); - return rc; - } - } else if (PRIM_IS_RACH8(lchan->prim)) { - req = (struct l1ctl_rach_req *) lchan->prim->payload; - synch_seq = RACH_SYNCH_SEQ_TS0; - - /* Delay sending according to offset value */ - if (req->offset-- > 0) - return 0; - - /* Encode regular (8-bit) payload */ - rc = gsm0503_rach_ext_encode(payload, req->ra, trx->bsic, false); - if (rc) { - LOGP(DSCHD, LOGL_ERROR, "Could not encode RACH burst " - "(ra=%u bsic=%u)\n", req->ra, trx->bsic); - - /* Forget this primitive */ - sched_prim_drop(lchan); - return rc; - } - } else { - LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu (expected %zu or %zu), " - "so dropping...\n", lchan->prim->payload_len, - sizeof(*req), sizeof(*ext_req)); - sched_prim_drop(lchan); - return -EINVAL; - } - - - /* BN0-7: extended tail bits */ - memcpy(burst_ptr, rach_ext_tail_bits, RACH_EXT_TAIL_BITS_LEN); - burst_ptr += RACH_EXT_TAIL_BITS_LEN; - - /* BN8-48: chosen synch. (training) sequence */ - for (i = 0; i < RACH_SYNCH_SEQ_LEN; i++) - *(burst_ptr++) = rach_synch_seq_bits[synch_seq][i] == '1'; - - /* BN49-84: encrypted bits (the payload) */ - memcpy(burst_ptr, payload, RACH_PAYLOAD_LEN); - burst_ptr += RACH_PAYLOAD_LEN; - - /* BN85-156: tail bits & extended guard period */ - memset(burst_ptr, 0, burst + GSM_BURST_LEN - burst_ptr); - - LOGP(DSCHD, LOGL_NOTICE, "Transmitting %s RACH (%s) on fn=%u, tn=%u, lchan=%s\n", - PRIM_IS_RACH11(lchan->prim) ? "extended (11-bit)" : "regular (8-bit)", - get_value_string(rach_synch_seq_names, synch_seq), fn, - ts->index, trx_lchan_desc[lchan->type].name); - - /* Forward burst to scheduler */ - rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst); - if (rc) { - /* Forget this primitive */ - sched_prim_drop(lchan); - - return rc; - } - - /* Confirm RACH request */ - l1ctl_tx_rach_conf(trx->l1l, trx->band_arfcn, fn); - - /* Forget processed primitive */ - sched_prim_drop(lchan); - - return 0; -} diff --git a/src/host/trxcon/sched_lchan_tchf.c b/src/host/trxcon/sched_lchan_tchf.c deleted file mode 100644 index f2ecdcc6..00000000 --- a/src/host/trxcon/sched_lchan_tchf.c +++ /dev/null @@ -1,297 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: handlers for DL / UL bursts on logical channels - * - * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.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 <errno.h> -#include <string.h> -#include <stdint.h> - -#include <osmocom/core/logging.h> -#include <osmocom/core/bits.h> - -#include <osmocom/gsm/protocol/gsm_04_08.h> -#include <osmocom/gsm/gsm_utils.h> - -#include <osmocom/coding/gsm0503_coding.h> -#include <osmocom/codec/codec.h> - -#include "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" -#include "logging.h" -#include "trx_if.h" -#include "l1ctl.h" - -int rx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, - sbit_t *bits, int8_t rssi, int16_t toa256) -{ - const struct trx_lchan_desc *lchan_desc; - int n_errors = -1, n_bits_total, rc; - sbit_t *buffer, *offset; - uint8_t l2[128], *mask; - uint32_t *first_fn; - size_t l2_len; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - first_fn = &lchan->rx_first_fn; - mask = &lchan->rx_burst_mask; - buffer = lchan->rx_bursts; - - LOGP(DSCHD, LOGL_DEBUG, "Traffic received on %s: fn=%u ts=%u bid=%u\n", - lchan_desc->name, fn, ts->index, bid); - - /* Reset internal state */ - if (bid == 0) { - /* Clean up old measurements */ - memset(&lchan->meas, 0x00, sizeof(lchan->meas)); - - *first_fn = fn; - *mask = 0x00; - } - - /* Update mask */ - *mask |= (1 << bid); - - /* Update mask and RSSI */ - lchan->meas.rssi_sum += rssi; - lchan->meas.toa256_sum += toa256; - lchan->meas.num++; - - /* Copy burst to end of buffer of 8 bursts */ - offset = buffer + bid * 116 + 464; - memcpy(offset, bits + 3, 58); - memcpy(offset + 58, bits + 87, 58); - - /* Wait until complete set of bursts */ - if (bid != 3) - return 0; - - /* Check for complete set of bursts */ - if ((*mask & 0xf) != 0xf) { - LOGP(DSCHD, LOGL_ERROR, "Received incomplete traffic frame at " - "fn=%u (%u/%u) for %s\n", *first_fn, - (*first_fn) % ts->mf_layout->period, - ts->mf_layout->period, - lchan_desc->name); - - /* Send BFI */ - goto bfi; - } - - switch (lchan->tch_mode) { - case GSM48_CMODE_SIGN: - case GSM48_CMODE_SPEECH_V1: /* FR */ - rc = gsm0503_tch_fr_decode(l2, buffer, - 1, 0, &n_errors, &n_bits_total); - break; - case GSM48_CMODE_SPEECH_EFR: /* EFR */ - rc = gsm0503_tch_fr_decode(l2, buffer, - 1, 1, &n_errors, &n_bits_total); - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - /** - * TODO: AMR requires a dedicated loop, - * which will be implemented later... - */ - LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet\n"); - return -ENOTSUP; - default: - LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode); - return -EINVAL; - } - - /* Shift buffer by 4 bursts for interleaving */ - memcpy(buffer, buffer + 464, 464); - - /* Check decoding result */ - if (rc < 4) { - LOGP(DSCHD, LOGL_ERROR, "Received bad TCH frame ending at " - "fn=%u for %s\n", fn, lchan_desc->name); - - /* Send BFI */ - goto bfi; - } else if (rc == GSM_MACBLOCK_LEN) { - /* FACCH received, forward it to the higher layers */ - sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN, - n_errors, false, false); - - /* Send BFI instead of stolen TCH frame */ - goto bfi; - } else { - /* A good TCH frame received */ - l2_len = rc; - } - - /* Send a traffic frame to the higher layers */ - return sched_send_dt_ind(trx, ts, lchan, l2, l2_len, - n_errors, false, true); - -bfi: - /* Didn't try to decode */ - if (n_errors < 0) - n_errors = 116 * 4; - - /* BFI is not applicable in signalling mode */ - if (lchan->tch_mode == GSM48_CMODE_SIGN) - return sched_send_dt_ind(trx, ts, lchan, NULL, 0, - n_errors, true, false); - - /* Bad frame indication */ - l2_len = sched_bad_frame_ind(l2, lchan); - - /* Send a BFI frame to the higher layers */ - return sched_send_dt_ind(trx, ts, lchan, l2, l2_len, - n_errors, true, true); -} - -int tx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid) -{ - const struct trx_lchan_desc *lchan_desc; - ubit_t burst[GSM_BURST_LEN]; - ubit_t *buffer, *offset; - const uint8_t *tsc; - uint8_t *mask; - size_t l2_len; - int rc; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - mask = &lchan->tx_burst_mask; - buffer = lchan->tx_bursts; - - /* If we have encoded bursts */ - if (*mask) - goto send_burst; - - /* Wait until a first burst in period */ - if (bid > 0) - return 0; - - /* Check the current TCH mode */ - switch (lchan->tch_mode) { - case GSM48_CMODE_SIGN: - case GSM48_CMODE_SPEECH_V1: /* FR */ - l2_len = GSM_FR_BYTES; - break; - case GSM48_CMODE_SPEECH_EFR: /* EFR */ - l2_len = GSM_EFR_BYTES; - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - /** - * TODO: AMR requires a dedicated loop, - * which will be implemented later... - */ - LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet, " - "dropping frame...\n"); - - /* Forget this primitive */ - sched_prim_drop(lchan); - - return -ENOTSUP; - default: - LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u, " - "dropping frame...\n", lchan->tch_mode); - - /* Forget this primitive */ - sched_prim_drop(lchan); - - return -EINVAL; - } - - /* Determine and check the payload length */ - if (lchan->prim->payload_len == GSM_MACBLOCK_LEN) { - l2_len = GSM_MACBLOCK_LEN; /* FACCH */ - } else if (lchan->prim->payload_len != l2_len) { - LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu " - "(expected %zu for TCH or %u for FACCH), so dropping...\n", - lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN); - - sched_prim_drop(lchan); - return -EINVAL; - } - - /* Shift buffer by 4 bursts back for interleaving */ - memcpy(buffer, buffer + 464, 464); - - /* Encode payload */ - rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, l2_len, 1); - if (rc) { - LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n", - lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, - lchan->prim->payload_len)); - - /* Forget this primitive */ - sched_prim_drop(lchan); - - return -EINVAL; - } - -send_burst: - /* Determine which burst should be sent */ - offset = buffer + bid * 116; - - /* Update mask */ - *mask |= (1 << bid); - - /* Choose proper TSC */ - tsc = sched_nb_training_bits[trx->tsc]; - - /* Compose a new burst */ - memset(burst, 0, 3); /* TB */ - memcpy(burst + 3, offset, 58); /* Payload 1/2 */ - memcpy(burst + 61, tsc, 26); /* TSC */ - memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */ - memset(burst + 145, 0, 3); /* TB */ - - LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n", - lchan_desc->name, fn, ts->index, bid); - - /* Forward burst to scheduler */ - rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst); - if (rc) { - /* Forget this primitive */ - sched_prim_drop(lchan); - - /* Reset mask */ - *mask = 0x00; - - return rc; - } - - /* If we have sent the last (4/4) burst */ - if (*mask == 0x0f) { - /* Confirm data / traffic sending */ - sched_send_dt_conf(trx, ts, lchan, fn, PRIM_IS_TCH(lchan->prim)); - - /* Forget processed primitive */ - sched_prim_drop(lchan); - - /* Reset mask */ - *mask = 0x00; - } - - return 0; -} diff --git a/src/host/trxcon/sched_lchan_tchh.c b/src/host/trxcon/sched_lchan_tchh.c deleted file mode 100644 index 0201ee35..00000000 --- a/src/host/trxcon/sched_lchan_tchh.c +++ /dev/null @@ -1,502 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: handlers for DL / UL bursts on logical channels - * - * (C) 2018 by Vadim Yanitskiy <axilirator@gmail.com> - * (C) 2018 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. - * - */ - -#include <errno.h> -#include <string.h> -#include <stdint.h> -#include <stdbool.h> - -#include <osmocom/core/logging.h> -#include <osmocom/core/utils.h> -#include <osmocom/core/bits.h> - -#include <osmocom/gsm/protocol/gsm_04_08.h> -#include <osmocom/gsm/gsm_utils.h> - -#include <osmocom/coding/gsm0503_coding.h> -#include <osmocom/codec/codec.h> - -#include "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" -#include "logging.h" -#include "trx_if.h" -#include "l1ctl.h" - -static const uint8_t tch_h0_traffic_block_map[3][4] = { - /* B0(0,2,4,6), B1(4,6,8,10), B2(8,10,0,2) */ - { 0, 2, 4, 6 }, - { 4, 6, 8, 10 }, - { 8, 10, 0, 2 }, -}; - -static const uint8_t tch_h1_traffic_block_map[3][4] = { - /* B0(1,3,5,7), B1(5,7,9,11), B2(9,11,1,3) */ - { 1, 3, 5, 7 }, - { 5, 7, 9, 11 }, - { 9, 11, 1, 3 }, -}; - -static const uint8_t tch_h0_dl_facch_block_map[3][6] = { - /* B0(4,6,8,10,13,15), B1(13,15,17,19,21,23), B2(21,23,0,2,4,6) */ - { 4, 6, 8, 10, 13, 15 }, - { 13, 15, 17, 19, 21, 23 }, - { 21, 23, 0, 2, 4, 6 }, -}; - -static const uint8_t tch_h0_ul_facch_block_map[3][6] = { - /* B0(0,2,4,6,8,10), B1(8,10,13,15,17,19), B2(17,19,21,23,0,2) */ - { 0, 2, 4, 6, 8, 10 }, - { 8, 10, 13, 15, 17, 19 }, - { 17, 19, 21, 23, 0, 2 }, -}; - -static const uint8_t tch_h1_dl_facch_block_map[3][6] = { - /* B0(5,7,9,11,14,16), B1(14,16,18,20,22,24), B2(22,24,1,3,5,7) */ - { 5, 7, 9, 11, 14, 16 }, - { 14, 16, 18, 20, 22, 24 }, - { 22, 24, 1, 3, 5, 7 }, -}; - -const uint8_t tch_h1_ul_facch_block_map[3][6] = { - /* B0(1,3,5,7,9,11), B1(9,11,14,16,18,20), B2(18,20,22,24,1,3) */ - { 1, 3, 5, 7, 9, 11 }, - { 9, 11, 14, 16, 18, 20 }, - { 18, 20, 22, 24, 1, 3 }, -}; - -/** - * Can a TCH/H block transmission be initiated / finished - * on a given frame number and a given channel type? - * - * See GSM 05.02, clause 7, table 1 - * - * @param chan channel type (TRXC_TCHH_0 or TRXC_TCHH_1) - * @param fn the current frame number - * @param ul Uplink or Downlink? - * @param facch FACCH/H or traffic? - * @param start init or end of transmission? - * @return true (yes) or false (no) - */ -bool sched_tchh_block_map_fn(enum trx_lchan_type chan, - uint32_t fn, bool ul, bool facch, bool start) -{ - uint8_t fn_mf; - int i = 0; - - /* Just to be sure */ - OSMO_ASSERT(chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1); - - /* Calculate a modulo */ - fn_mf = facch ? (fn % 26) : (fn % 13); - -#define MAP_GET_POS(map) \ - (start ? 0 : ARRAY_SIZE(map[i]) - 1) - -#define BLOCK_MAP_FN(map) \ - do { \ - if (map[i][MAP_GET_POS(map)] == fn_mf) \ - return true; \ - } while (++i < ARRAY_SIZE(map)) - - /* Choose a proper block map */ - if (facch) { - if (ul) { - if (chan == TRXC_TCHH_0) - BLOCK_MAP_FN(tch_h0_ul_facch_block_map); - else - BLOCK_MAP_FN(tch_h1_ul_facch_block_map); - } else { - if (chan == TRXC_TCHH_0) - BLOCK_MAP_FN(tch_h0_dl_facch_block_map); - else - BLOCK_MAP_FN(tch_h1_dl_facch_block_map); - } - } else { - if (chan == TRXC_TCHH_0) - BLOCK_MAP_FN(tch_h0_traffic_block_map); - else - BLOCK_MAP_FN(tch_h1_traffic_block_map); - } - - return false; -} - -/** - * Calculates a frame number of the first burst - * using given frame number of the last burst. - * - * See GSM 05.02, clause 7, table 1 - * - * @param chan channel type (TRXC_TCHH_0 or TRXC_TCHH_1) - * @param last_fn frame number of the last burst - * @param facch FACCH/H or traffic? - * @return either frame number of the first burst, - * or fn=last_fn if calculation failed - */ -uint32_t sched_tchh_block_dl_first_fn(enum trx_lchan_type chan, - uint32_t last_fn, bool facch) -{ - uint8_t fn_mf, fn_diff; - int i = 0; - - /* Just to be sure */ - OSMO_ASSERT(chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1); - - /* Calculate a modulo */ - fn_mf = facch ? (last_fn % 26) : (last_fn % 13); - -#define BLOCK_FIRST_FN(map) \ - do { \ - if (map[i][ARRAY_SIZE(map[i]) - 1] == fn_mf) { \ - fn_diff = TDMA_FN_DIFF(fn_mf, map[i][0]); \ - return TDMA_FN_SUB(last_fn, fn_diff); \ - } \ - } while (++i < ARRAY_SIZE(map)) - - /* Choose a proper block map */ - if (facch) { - if (chan == TRXC_TCHH_0) - BLOCK_FIRST_FN(tch_h0_dl_facch_block_map); - else - BLOCK_FIRST_FN(tch_h1_dl_facch_block_map); - } else { - if (chan == TRXC_TCHH_0) - BLOCK_FIRST_FN(tch_h0_traffic_block_map); - else - BLOCK_FIRST_FN(tch_h1_traffic_block_map); - } - - LOGP(DSCHD, LOGL_ERROR, "Failed to calculate TDMA " - "frame number of the first burst of %s block, " - "using the current fn=%u\n", facch ? - "FACCH/H" : "TCH/H", last_fn); - - /* Couldn't calculate the first fn, return the last */ - return last_fn; -} - -int rx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, - sbit_t *bits, int8_t rssi, int16_t toa256) -{ - const struct trx_lchan_desc *lchan_desc; - int n_errors = -1, n_bits_total, rc; - sbit_t *buffer, *offset; - uint8_t l2[128], *mask; - size_t l2_len; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - mask = &lchan->rx_burst_mask; - buffer = lchan->rx_bursts; - - LOGP(DSCHD, LOGL_DEBUG, "Traffic received on %s: fn=%u ts=%u bid=%u\n", - lchan_desc->name, fn, ts->index, bid); - - if (*mask == 0x00) { - /* Align to the first burst */ - if (bid > 0) - return 0; - - /* Align reception of the first FACCH/H frame */ - if (lchan->tch_mode == GSM48_CMODE_SIGN) { - if (!sched_tchh_facch_start(lchan->type, fn, 0)) - return 0; - } else { /* or TCH/H traffic frame */ - if (!sched_tchh_traffic_start(lchan->type, fn, 0)) - return 0; - } - } - - /* Update mask */ - *mask |= (1 << bid); - - /** - * FIXME: properly update measurements - * - * Since TCH/H channel is using block-diagonal interleaving, - * a single burst may carry 57 bits of one encoded frame, - * and 57 bits of another. This should be taken into account. - */ - lchan->meas.rssi_sum += rssi; - lchan->meas.toa256_sum += toa256; - lchan->meas.num++; - - /* Copy burst to the end of buffer of 6 bursts */ - offset = buffer + bid * 116 + 464; - memcpy(offset, bits + 3, 58); - memcpy(offset + 58, bits + 87, 58); - - /* Wait until the second burst */ - if (bid != 1) - return 0; - - /* Wait for complete set of bursts */ - if (lchan->tch_mode == GSM48_CMODE_SIGN) { - /* FACCH/H is interleaved over 6 bursts */ - if ((*mask & 0x3f) != 0x3f) - goto bfi_shift; - } else { - /* Traffic is interleaved over 4 bursts */ - if ((*mask & 0x0f) != 0x0f) - goto bfi_shift; - } - - /* Skip decoding attempt in case of FACCH/H */ - if (lchan->dl_ongoing_facch) { - lchan->dl_ongoing_facch = false; - goto bfi_shift; /* 2/2 BFI */ - } - - switch (lchan->tch_mode) { - case GSM48_CMODE_SIGN: - case GSM48_CMODE_SPEECH_V1: /* HR */ - rc = gsm0503_tch_hr_decode(l2, buffer, - !sched_tchh_facch_end(lchan->type, fn, 0), - &n_errors, &n_bits_total); - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - /** - * TODO: AMR requires a dedicated loop, - * which will be implemented later... - */ - LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet\n"); - return -ENOTSUP; - default: - LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode); - return -EINVAL; - } - - /* Shift buffer by 4 bursts for interleaving */ - memcpy(buffer, buffer + 232, 232); - memcpy(buffer + 232, buffer + 464, 232); - - /* Shift burst mask */ - *mask = *mask << 2; - - /* Check decoding result */ - if (rc < 4) { - LOGP(DSCHD, LOGL_ERROR, "Received bad TCH frame ending at " - "fn=%u on %s (rc=%d)\n", fn, lchan_desc->name, rc); - - /* Send BFI */ - goto bfi; - } else if (rc == GSM_MACBLOCK_LEN) { - /* Skip decoding of the next 2 stolen bursts */ - lchan->dl_ongoing_facch = true; - - /* Calculate TDMA frame number of the first burst */ - lchan->rx_first_fn = sched_tchh_block_dl_first_fn(lchan->type, - fn, true); /* FACCH/H */ - - /* FACCH/H received, forward to the higher layers */ - sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN, - n_errors, false, false); - - /* 1/2 BFI */ - goto bfi; - } else { - /* A good TCH frame received */ - l2_len = rc; - } - - /* Calculate TDMA frame number of the first burst */ - lchan->rx_first_fn = sched_tchh_block_dl_first_fn(lchan->type, - fn, false); /* TCH/H */ - - /* Send a traffic frame to the higher layers */ - return sched_send_dt_ind(trx, ts, lchan, l2, l2_len, - n_errors, false, true); - -bfi_shift: - /* Shift buffer */ - memcpy(buffer, buffer + 232, 232); - memcpy(buffer + 232, buffer + 464, 232); - - /* Shift burst mask */ - *mask = *mask << 2; - -bfi: - /* Didn't try to decode */ - if (n_errors < 0) - n_errors = 116 * 2; - - /* Calculate TDMA frame number of the first burst */ - lchan->rx_first_fn = sched_tchh_block_dl_first_fn(lchan->type, - fn, false); /* TCH/H */ - - /* BFI is not applicable in signalling mode */ - if (lchan->tch_mode == GSM48_CMODE_SIGN) - return sched_send_dt_ind(trx, ts, lchan, NULL, 0, - n_errors, true, false); - - /* Bad frame indication */ - l2_len = sched_bad_frame_ind(l2, lchan); - - /* Send a BFI frame to the higher layers */ - return sched_send_dt_ind(trx, ts, lchan, l2, l2_len, - n_errors, true, true); -} - -int tx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid) -{ - const struct trx_lchan_desc *lchan_desc; - ubit_t burst[GSM_BURST_LEN]; - ubit_t *buffer, *offset; - const uint8_t *tsc; - uint8_t *mask; - size_t l2_len; - int rc; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - mask = &lchan->tx_burst_mask; - buffer = lchan->tx_bursts; - - if (bid > 0) { - /* Align to the first burst */ - if (*mask == 0x00) - return 0; - goto send_burst; - } - - if (*mask == 0x00) { - /* Align transmission of the first FACCH/H frame */ - if (lchan->tch_mode == GSM48_CMODE_SIGN) - if (!sched_tchh_facch_start(lchan->type, fn, 1)) - return 0; - } - - /* Shift buffer by 2 bursts back for interleaving */ - memcpy(buffer, buffer + 232, 232); - - /* Also shift TX burst mask */ - *mask = *mask << 2; - - /* If FACCH/H blocks are still pending */ - if (lchan->ul_facch_blocks > 2) { - memcpy(buffer + 232, buffer + 464, 232); - goto send_burst; - } - - /* Check the current TCH mode */ - switch (lchan->tch_mode) { - case GSM48_CMODE_SIGN: - case GSM48_CMODE_SPEECH_V1: /* HR */ - l2_len = GSM_HR_BYTES + 1; - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - /** - * TODO: AMR requires a dedicated loop, - * which will be implemented later... - */ - LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet, " - "dropping frame...\n"); - - /* Forget this primitive */ - sched_prim_drop(lchan); - return -ENOTSUP; - default: - LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u, " - "dropping frame...\n", lchan->tch_mode); - - /* Forget this primitive */ - sched_prim_drop(lchan); - return -EINVAL; - } - - /* Determine payload length */ - if (PRIM_IS_FACCH(lchan->prim)) { - l2_len = GSM_MACBLOCK_LEN; /* FACCH */ - } else if (lchan->prim->payload_len != l2_len) { - LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu " - "(expected %zu for TCH or %u for FACCH), so dropping...\n", - lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN); - - /* Forget this primitive */ - sched_prim_drop(lchan); - return -EINVAL; - } - - /* Encode the payload */ - rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, l2_len); - if (rc) { - LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n", - lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, - lchan->prim->payload_len)); - - /* Forget this primitive */ - sched_prim_drop(lchan); - return -EINVAL; - } - - /* A FACCH/H frame occupies 6 bursts */ - if (PRIM_IS_FACCH(lchan->prim)) - lchan->ul_facch_blocks = 6; - -send_burst: - /* Determine which burst should be sent */ - offset = buffer + bid * 116; - - /* Update mask */ - *mask |= (1 << bid); - - /* Choose proper TSC */ - tsc = sched_nb_training_bits[trx->tsc]; - - /* Compose a new burst */ - memset(burst, 0, 3); /* TB */ - memcpy(burst + 3, offset, 58); /* Payload 1/2 */ - memcpy(burst + 61, tsc, 26); /* TSC */ - memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */ - memset(burst + 145, 0, 3); /* TB */ - - LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n", - lchan_desc->name, fn, ts->index, bid); - - /* Forward burst to transceiver */ - sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst); - - /* In case of a FACCH/H frame, one block less */ - if (lchan->ul_facch_blocks) - lchan->ul_facch_blocks--; - - if ((*mask & 0x0f) == 0x0f) { - /** - * If no more FACCH/H blocks pending, - * confirm data / traffic sending - */ - if (!lchan->ul_facch_blocks) - sched_send_dt_conf(trx, ts, lchan, fn, - PRIM_IS_TCH(lchan->prim)); - - /* Forget processed primitive */ - sched_prim_drop(lchan); - } - - return 0; -} diff --git a/src/host/trxcon/sched_lchan_xcch.c b/src/host/trxcon/sched_lchan_xcch.c deleted file mode 100644 index 196f949c..00000000 --- a/src/host/trxcon/sched_lchan_xcch.c +++ /dev/null @@ -1,213 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: handlers for DL / UL bursts on logical channels - * - * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.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 <errno.h> -#include <string.h> -#include <stdint.h> - -#include <osmocom/core/logging.h> -#include <osmocom/core/bits.h> - -#include <osmocom/gsm/gsm_utils.h> -#include <osmocom/gsm/protocol/gsm_04_08.h> -#include <osmocom/coding/gsm0503_coding.h> - -#include "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" -#include "logging.h" -#include "trx_if.h" -#include "l1ctl.h" - -int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, - sbit_t *bits, int8_t rssi, int16_t toa256) -{ - const struct trx_lchan_desc *lchan_desc; - uint8_t l2[GSM_MACBLOCK_LEN], *mask; - int n_errors, n_bits_total, rc; - sbit_t *buffer, *offset; - uint32_t *first_fn; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - first_fn = &lchan->rx_first_fn; - mask = &lchan->rx_burst_mask; - buffer = lchan->rx_bursts; - - LOGP(DSCHD, LOGL_DEBUG, "Data received on %s: fn=%u ts=%u bid=%u\n", - lchan_desc->name, fn, ts->index, bid); - - /* Reset internal state */ - if (bid == 0) { - /* Clean up old measurements */ - memset(&lchan->meas, 0x00, sizeof(lchan->meas)); - - *first_fn = fn; - *mask = 0x0; - } - - /* Update mask */ - *mask |= (1 << bid); - - /* Update measurements */ - lchan->meas.rssi_sum += rssi; - lchan->meas.toa256_sum += toa256; - lchan->meas.num++; - - /* Copy burst to buffer of 4 bursts */ - offset = buffer + bid * 116; - memcpy(offset, bits + 3, 58); - memcpy(offset + 58, bits + 87, 58); - - /* Wait until complete set of bursts */ - if (bid != 3) - return 0; - - /* Check for complete set of bursts */ - if ((*mask & 0xf) != 0xf) { - LOGP(DSCHD, LOGL_ERROR, "Received incomplete data frame at " - "fn=%u (%u/%u) for %s\n", *first_fn, - (*first_fn) % ts->mf_layout->period, - ts->mf_layout->period, - lchan_desc->name); - /* NOTE: xCCH has an insane amount of redundancy for error - * correction, so even just 2 valid bursts might be enough - * to reconstruct some L2 frames. This is why we do not - * abort here. */ - } - - /* Attempt to decode */ - rc = gsm0503_xcch_decode(l2, buffer, &n_errors, &n_bits_total); - if (rc) { - LOGP(DSCHD, LOGL_ERROR, "Received bad data frame at fn=%u " - "(%u/%u) for %s\n", *first_fn, - (*first_fn) % ts->mf_layout->period, - ts->mf_layout->period, - lchan_desc->name); - - /** - * We should anyway send dummy frame for - * proper measurement reporting... - */ - return sched_send_dt_ind(trx, ts, lchan, NULL, 0, - n_errors, true, false); - } - - /* Send a L2 frame to the higher layers */ - return sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN, - n_errors, false, false); -} - -int tx_data_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid) -{ - const struct trx_lchan_desc *lchan_desc; - ubit_t burst[GSM_BURST_LEN]; - ubit_t *buffer, *offset; - const uint8_t *tsc; - uint8_t *mask; - int rc; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - mask = &lchan->tx_burst_mask; - buffer = lchan->tx_bursts; - - if (bid > 0) { - /* If we have encoded bursts */ - if (*mask) - goto send_burst; - else - return 0; - } - - /* Check the prim payload length */ - if (lchan->prim->payload_len != GSM_MACBLOCK_LEN) { - LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu (expected %u), " - "so dropping...\n", lchan->prim->payload_len, GSM_MACBLOCK_LEN); - - sched_prim_drop(lchan); - return -EINVAL; - } - - /* Encode payload */ - rc = gsm0503_xcch_encode(buffer, lchan->prim->payload); - if (rc) { - LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n", - lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, - lchan->prim->payload_len)); - - /* Forget this primitive */ - sched_prim_drop(lchan); - - return -EINVAL; - } - -send_burst: - /* Determine which burst should be sent */ - offset = buffer + bid * 116; - - /* Update mask */ - *mask |= (1 << bid); - - /* Choose proper TSC */ - tsc = sched_nb_training_bits[trx->tsc]; - - /* Compose a new burst */ - memset(burst, 0, 3); /* TB */ - memcpy(burst + 3, offset, 58); /* Payload 1/2 */ - memcpy(burst + 61, tsc, 26); /* TSC */ - memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */ - memset(burst + 145, 0, 3); /* TB */ - - LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n", - lchan_desc->name, fn, ts->index, bid); - - /* Forward burst to scheduler */ - rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst); - if (rc) { - /* Forget this primitive */ - sched_prim_drop(lchan); - - /* Reset mask */ - *mask = 0x00; - - return rc; - } - - /* If we have sent the last (4/4) burst */ - if ((*mask & 0x0f) == 0x0f) { - /* Forget processed primitive */ - sched_prim_drop(lchan); - - /* Reset mask */ - *mask = 0x00; - - /* Confirm data sending */ - sched_send_dt_conf(trx, ts, lchan, fn, false); - } - - return 0; -} diff --git a/src/host/trxcon/sched_mframe.c b/src/host/trxcon/sched_mframe.c deleted file mode 100644 index 9b759af3..00000000 --- a/src/host/trxcon/sched_mframe.c +++ /dev/null @@ -1,2101 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: channel combinations, burst mapping - * - * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> - * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> - * (C) 2015 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 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 <osmocom/gsm/gsm_utils.h> - -#include "sched_trx.h" - -/* Non-combined CCCH */ -static const struct trx_frame frame_bcch[51] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_BCCH, 0, TRXC_RACH, 0 }, - { TRXC_BCCH, 1, TRXC_RACH, 0 }, - { TRXC_BCCH, 2, TRXC_RACH, 0 }, - { TRXC_BCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_IDLE, 0, TRXC_RACH, 0 }, -}; - -/* Combined CCCH+SDCCH4 */ -static const struct trx_frame frame_bcch_sdcch4[102] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, - { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, - { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, - { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, - { TRXC_BCCH, 2, TRXC_RACH, 0 }, - { TRXC_BCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_SACCH4_2, 0 }, - { TRXC_CCCH, 1, TRXC_SACCH4_2, 1 }, - { TRXC_CCCH, 2, TRXC_SACCH4_2, 2 }, - { TRXC_CCCH, 3, TRXC_SACCH4_2, 3 }, - { TRXC_FCCH, 0, TRXC_SACCH4_3, 0 }, - { TRXC_SCH, 0, TRXC_SACCH4_3, 1 }, - { TRXC_CCCH, 0, TRXC_SACCH4_3, 2 }, - { TRXC_CCCH, 1, TRXC_SACCH4_3, 3 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, - { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, - { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, - { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, - { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, - { TRXC_SACCH4_0, 0, TRXC_SDCCH4_1, 1 }, - { TRXC_SACCH4_0, 1, TRXC_SDCCH4_1, 2 }, - { TRXC_SACCH4_0, 2, TRXC_SDCCH4_1, 3 }, - { TRXC_SACCH4_0, 3, TRXC_RACH, 0 }, - { TRXC_SACCH4_1, 0, TRXC_RACH, 0 }, - { TRXC_SACCH4_1, 1, TRXC_SDCCH4_2, 0 }, - { TRXC_SACCH4_1, 2, TRXC_SDCCH4_2, 1 }, - { TRXC_SACCH4_1, 3, TRXC_SDCCH4_2, 2 }, - { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 }, - - { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, - { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, - { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, - { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, - { TRXC_BCCH, 2, TRXC_RACH, 0 }, - { TRXC_BCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_SACCH4_0, 0 }, - { TRXC_CCCH, 1, TRXC_SACCH4_0, 1 }, - { TRXC_CCCH, 2, TRXC_SACCH4_0, 2 }, - { TRXC_CCCH, 3, TRXC_SACCH4_0, 3 }, - { TRXC_FCCH, 0, TRXC_SACCH4_1, 0 }, - { TRXC_SCH, 0, TRXC_SACCH4_1, 1 }, - { TRXC_CCCH, 0, TRXC_SACCH4_1, 2 }, - { TRXC_CCCH, 1, TRXC_SACCH4_1, 3 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, - { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, - { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, - { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, - { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, - { TRXC_SACCH4_2, 0, TRXC_SDCCH4_1, 1 }, - { TRXC_SACCH4_2, 1, TRXC_SDCCH4_1, 2 }, - { TRXC_SACCH4_2, 2, TRXC_SDCCH4_1, 3 }, - { TRXC_SACCH4_2, 3, TRXC_RACH, 0 }, - { TRXC_SACCH4_3, 0, TRXC_RACH, 0 }, - { TRXC_SACCH4_3, 1, TRXC_SDCCH4_2, 0 }, - { TRXC_SACCH4_3, 2, TRXC_SDCCH4_2, 1 }, - { TRXC_SACCH4_3, 3, TRXC_SDCCH4_2, 2 }, - { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 }, -}; - -static const struct trx_frame frame_bcch_sdcch4_cbch[102] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, - { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, - { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, - { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, - { TRXC_BCCH, 2, TRXC_RACH, 0 }, - { TRXC_BCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_IDLE, 0 }, - { TRXC_CCCH, 1, TRXC_IDLE, 1 }, - { TRXC_CCCH, 2, TRXC_IDLE, 2 }, - { TRXC_CCCH, 3, TRXC_IDLE, 3 }, - { TRXC_FCCH, 0, TRXC_SACCH4_3, 0 }, - { TRXC_SCH, 0, TRXC_SACCH4_3, 1 }, - { TRXC_CCCH, 0, TRXC_SACCH4_3, 2 }, - { TRXC_CCCH, 1, TRXC_SACCH4_3, 3 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, - { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, - { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, - { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, - { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, - { TRXC_SACCH4_0, 0, TRXC_SDCCH4_1, 1 }, - { TRXC_SACCH4_0, 1, TRXC_SDCCH4_1, 2 }, - { TRXC_SACCH4_0, 2, TRXC_SDCCH4_1, 3 }, - { TRXC_SACCH4_0, 3, TRXC_RACH, 0 }, - { TRXC_SACCH4_1, 0, TRXC_RACH, 0 }, - { TRXC_SACCH4_1, 1, TRXC_IDLE, 0 }, - { TRXC_SACCH4_1, 2, TRXC_IDLE, 1 }, - { TRXC_SACCH4_1, 3, TRXC_IDLE, 2 }, - { TRXC_IDLE, 0, TRXC_IDLE, 3 }, - - { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, - { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, - { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, - { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, - { TRXC_BCCH, 2, TRXC_RACH, 0 }, - { TRXC_BCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_SACCH4_0, 0 }, - { TRXC_CCCH, 1, TRXC_SACCH4_0, 1 }, - { TRXC_CCCH, 2, TRXC_SACCH4_0, 2 }, - { TRXC_CCCH, 3, TRXC_SACCH4_0, 3 }, - { TRXC_FCCH, 0, TRXC_SACCH4_1, 0 }, - { TRXC_SCH, 0, TRXC_SACCH4_1, 1 }, - { TRXC_CCCH, 0, TRXC_SACCH4_1, 2 }, - { TRXC_CCCH, 1, TRXC_SACCH4_1, 3 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, - { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, - { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, - { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, - { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, - { TRXC_IDLE, 0, TRXC_SDCCH4_1, 1 }, - { TRXC_IDLE, 1, TRXC_SDCCH4_1, 2 }, - { TRXC_IDLE, 2, TRXC_SDCCH4_1, 3 }, - { TRXC_IDLE, 3, TRXC_RACH, 0 }, - { TRXC_SACCH4_3, 0, TRXC_RACH, 0 }, - { TRXC_SACCH4_3, 1, TRXC_IDLE, 0 }, - { TRXC_SACCH4_3, 2, TRXC_IDLE, 1 }, - { TRXC_SACCH4_3, 3, TRXC_IDLE, 2 }, - { TRXC_IDLE, 0, TRXC_IDLE, 3 }, -}; - -static const struct trx_frame frame_sdcch8[102] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_SDCCH8_0, 0, TRXC_SACCH8_5, 0 }, - { TRXC_SDCCH8_0, 1, TRXC_SACCH8_5, 1 }, - { TRXC_SDCCH8_0, 2, TRXC_SACCH8_5, 2 }, - { TRXC_SDCCH8_0, 3, TRXC_SACCH8_5, 3 }, - { TRXC_SDCCH8_1, 0, TRXC_SACCH8_6, 0 }, - { TRXC_SDCCH8_1, 1, TRXC_SACCH8_6, 1 }, - { TRXC_SDCCH8_1, 2, TRXC_SACCH8_6, 2 }, - { TRXC_SDCCH8_1, 3, TRXC_SACCH8_6, 3 }, - { TRXC_SDCCH8_2, 0, TRXC_SACCH8_7, 0 }, - { TRXC_SDCCH8_2, 1, TRXC_SACCH8_7, 1 }, - { TRXC_SDCCH8_2, 2, TRXC_SACCH8_7, 2 }, - { TRXC_SDCCH8_2, 3, TRXC_SACCH8_7, 3 }, - { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, - { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, - { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, - { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, - { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, - { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, - { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, - { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, - { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 }, - { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 }, - { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 }, - { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 }, - { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, - { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, - { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, - { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, - { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, - { TRXC_SACCH8_0, 0, TRXC_SDCCH8_4, 1 }, - { TRXC_SACCH8_0, 1, TRXC_SDCCH8_4, 2 }, - { TRXC_SACCH8_0, 2, TRXC_SDCCH8_4, 3 }, - { TRXC_SACCH8_0, 3, TRXC_SDCCH8_5, 0 }, - { TRXC_SACCH8_1, 0, TRXC_SDCCH8_5, 1 }, - { TRXC_SACCH8_1, 1, TRXC_SDCCH8_5, 2 }, - { TRXC_SACCH8_1, 2, TRXC_SDCCH8_5, 3 }, - { TRXC_SACCH8_1, 3, TRXC_SDCCH8_6, 0 }, - { TRXC_SACCH8_2, 0, TRXC_SDCCH8_6, 1 }, - { TRXC_SACCH8_2, 1, TRXC_SDCCH8_6, 2 }, - { TRXC_SACCH8_2, 2, TRXC_SDCCH8_6, 3 }, - { TRXC_SACCH8_2, 3, TRXC_SDCCH8_7, 0 }, - { TRXC_SACCH8_3, 0, TRXC_SDCCH8_7, 1 }, - { TRXC_SACCH8_3, 1, TRXC_SDCCH8_7, 2 }, - { TRXC_SACCH8_3, 2, TRXC_SDCCH8_7, 3 }, - { TRXC_SACCH8_3, 3, TRXC_SACCH8_0, 0 }, - { TRXC_IDLE, 0, TRXC_SACCH8_0, 1 }, - { TRXC_IDLE, 0, TRXC_SACCH8_0, 2 }, - { TRXC_IDLE, 0, TRXC_SACCH8_0, 3 }, - - { TRXC_SDCCH8_0, 0, TRXC_SACCH8_1, 0 }, - { TRXC_SDCCH8_0, 1, TRXC_SACCH8_1, 1 }, - { TRXC_SDCCH8_0, 2, TRXC_SACCH8_1, 2 }, - { TRXC_SDCCH8_0, 3, TRXC_SACCH8_1, 3 }, - { TRXC_SDCCH8_1, 0, TRXC_SACCH8_2, 0 }, - { TRXC_SDCCH8_1, 1, TRXC_SACCH8_2, 1 }, - { TRXC_SDCCH8_1, 2, TRXC_SACCH8_2, 2 }, - { TRXC_SDCCH8_1, 3, TRXC_SACCH8_2, 3 }, - { TRXC_SDCCH8_2, 0, TRXC_SACCH8_3, 0 }, - { TRXC_SDCCH8_2, 1, TRXC_SACCH8_3, 1 }, - { TRXC_SDCCH8_2, 2, TRXC_SACCH8_3, 2 }, - { TRXC_SDCCH8_2, 3, TRXC_SACCH8_3, 3 }, - { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, - { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, - { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, - { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, - { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, - { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, - { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, - { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, - { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 }, - { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 }, - { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 }, - { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 }, - { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, - { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, - { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, - { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, - { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, - { TRXC_SACCH8_4, 0, TRXC_SDCCH8_4, 1 }, - { TRXC_SACCH8_4, 1, TRXC_SDCCH8_4, 2 }, - { TRXC_SACCH8_4, 2, TRXC_SDCCH8_4, 3 }, - { TRXC_SACCH8_4, 3, TRXC_SDCCH8_5, 0 }, - { TRXC_SACCH8_5, 0, TRXC_SDCCH8_5, 1 }, - { TRXC_SACCH8_5, 1, TRXC_SDCCH8_5, 2 }, - { TRXC_SACCH8_5, 2, TRXC_SDCCH8_5, 3 }, - { TRXC_SACCH8_5, 3, TRXC_SDCCH8_6, 0 }, - { TRXC_SACCH8_6, 0, TRXC_SDCCH8_6, 1 }, - { TRXC_SACCH8_6, 1, TRXC_SDCCH8_6, 2 }, - { TRXC_SACCH8_6, 2, TRXC_SDCCH8_6, 3 }, - { TRXC_SACCH8_6, 3, TRXC_SDCCH8_7, 0 }, - { TRXC_SACCH8_7, 0, TRXC_SDCCH8_7, 1 }, - { TRXC_SACCH8_7, 1, TRXC_SDCCH8_7, 2 }, - { TRXC_SACCH8_7, 2, TRXC_SDCCH8_7, 3 }, - { TRXC_SACCH8_7, 3, TRXC_SACCH8_4, 0 }, - { TRXC_IDLE, 0, TRXC_SACCH8_4, 1 }, - { TRXC_IDLE, 0, TRXC_SACCH8_4, 2 }, - { TRXC_IDLE, 0, TRXC_SACCH8_4, 3 }, -}; - -static const struct trx_frame frame_sdcch8_cbch[102] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_SDCCH8_0, 0, TRXC_SACCH8_5, 0 }, - { TRXC_SDCCH8_0, 1, TRXC_SACCH8_5, 1 }, - { TRXC_SDCCH8_0, 2, TRXC_SACCH8_5, 2 }, - { TRXC_SDCCH8_0, 3, TRXC_SACCH8_5, 3 }, - { TRXC_SDCCH8_1, 0, TRXC_SACCH8_6, 0 }, - { TRXC_SDCCH8_1, 1, TRXC_SACCH8_6, 1 }, - { TRXC_SDCCH8_1, 2, TRXC_SACCH8_6, 2 }, - { TRXC_SDCCH8_1, 3, TRXC_SACCH8_6, 3 }, - { TRXC_SDCCH8_CBCH, 0, TRXC_SACCH8_7, 0 }, - { TRXC_SDCCH8_CBCH, 1, TRXC_SACCH8_7, 1 }, - { TRXC_SDCCH8_CBCH, 2, TRXC_SACCH8_7, 2 }, - { TRXC_SDCCH8_CBCH, 3, TRXC_SACCH8_7, 3 }, - { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, - { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, - { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, - { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, - { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, - { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, - { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, - { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, - { TRXC_SDCCH8_5, 3, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_6, 0, TRXC_IDLE, 1 }, - { TRXC_SDCCH8_6, 1, TRXC_IDLE, 2 }, - { TRXC_SDCCH8_6, 2, TRXC_IDLE, 3 }, - { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, - { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, - { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, - { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, - { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, - { TRXC_SACCH8_0, 0, TRXC_SDCCH8_4, 1 }, - { TRXC_SACCH8_0, 1, TRXC_SDCCH8_4, 2 }, - { TRXC_SACCH8_0, 2, TRXC_SDCCH8_4, 3 }, - { TRXC_SACCH8_0, 3, TRXC_SDCCH8_5, 0 }, - { TRXC_SACCH8_1, 0, TRXC_SDCCH8_5, 1 }, - { TRXC_SACCH8_1, 1, TRXC_SDCCH8_5, 2 }, - { TRXC_SACCH8_1, 2, TRXC_SDCCH8_5, 3 }, - { TRXC_SACCH8_1, 3, TRXC_SDCCH8_6, 0 }, - { TRXC_IDLE, 0, TRXC_SDCCH8_6, 1 }, - { TRXC_IDLE, 1, TRXC_SDCCH8_6, 2 }, - { TRXC_IDLE, 2, TRXC_SDCCH8_6, 3 }, - { TRXC_IDLE, 3, TRXC_SDCCH8_7, 0 }, - { TRXC_SACCH8_3, 0, TRXC_SDCCH8_7, 1 }, - { TRXC_SACCH8_3, 1, TRXC_SDCCH8_7, 2 }, - { TRXC_SACCH8_3, 2, TRXC_SDCCH8_7, 3 }, - { TRXC_SACCH8_3, 3, TRXC_SACCH8_0, 0 }, - { TRXC_IDLE, 0, TRXC_SACCH8_0, 1 }, - { TRXC_IDLE, 0, TRXC_SACCH8_0, 2 }, - { TRXC_IDLE, 0, TRXC_SACCH8_0, 3 }, - - { TRXC_SDCCH8_0, 0, TRXC_SACCH8_1, 0 }, - { TRXC_SDCCH8_0, 1, TRXC_SACCH8_1, 1 }, - { TRXC_SDCCH8_0, 2, TRXC_SACCH8_1, 2 }, - { TRXC_SDCCH8_0, 3, TRXC_SACCH8_1, 3 }, - { TRXC_SDCCH8_1, 0, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_1, 1, TRXC_IDLE, 1 }, - { TRXC_SDCCH8_1, 2, TRXC_IDLE, 2 }, - { TRXC_SDCCH8_1, 3, TRXC_IDLE, 3 }, - { TRXC_SDCCH8_CBCH, 0, TRXC_SACCH8_3, 0 }, - { TRXC_SDCCH8_CBCH, 1, TRXC_SACCH8_3, 1 }, - { TRXC_SDCCH8_CBCH, 2, TRXC_SACCH8_3, 2 }, - { TRXC_SDCCH8_CBCH, 3, TRXC_SACCH8_3, 3 }, - { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, - { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, - { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, - { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, - { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, - { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, - { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, - { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, - { TRXC_SDCCH8_5, 3, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_6, 0, TRXC_IDLE, 1 }, - { TRXC_SDCCH8_6, 1, TRXC_IDLE, 2 }, - { TRXC_SDCCH8_6, 2, TRXC_IDLE, 3 }, - { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, - { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, - { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, - { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, - { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, - { TRXC_SACCH8_4, 0, TRXC_SDCCH8_4, 1 }, - { TRXC_SACCH8_4, 1, TRXC_SDCCH8_4, 2 }, - { TRXC_SACCH8_4, 2, TRXC_SDCCH8_4, 3 }, - { TRXC_SACCH8_4, 3, TRXC_SDCCH8_5, 0 }, - { TRXC_SACCH8_5, 0, TRXC_SDCCH8_5, 1 }, - { TRXC_SACCH8_5, 1, TRXC_SDCCH8_5, 2 }, - { TRXC_SACCH8_5, 2, TRXC_SDCCH8_5, 3 }, - { TRXC_SACCH8_5, 3, TRXC_SDCCH8_6, 0 }, - { TRXC_SACCH8_6, 0, TRXC_SDCCH8_6, 1 }, - { TRXC_SACCH8_6, 1, TRXC_SDCCH8_6, 2 }, - { TRXC_SACCH8_6, 2, TRXC_SDCCH8_6, 3 }, - { TRXC_SACCH8_6, 3, TRXC_SDCCH8_7, 0 }, - { TRXC_SACCH8_7, 0, TRXC_SDCCH8_7, 1 }, - { TRXC_SACCH8_7, 1, TRXC_SDCCH8_7, 2 }, - { TRXC_SACCH8_7, 2, TRXC_SDCCH8_7, 3 }, - { TRXC_SACCH8_7, 3, TRXC_SACCH8_4, 0 }, - { TRXC_IDLE, 0, TRXC_SACCH8_4, 1 }, - { TRXC_IDLE, 0, TRXC_SACCH8_4, 2 }, - { TRXC_IDLE, 0, TRXC_SACCH8_4, 3 }, -}; - -static const struct trx_frame frame_tchf_ts0[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, -}; - -static const struct trx_frame frame_tchf_ts1[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, -}; - -static const struct trx_frame frame_tchf_ts2[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, -}; - -static const struct trx_frame frame_tchf_ts3[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, -}; - -static const struct trx_frame frame_tchf_ts4[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, -}; - -static const struct trx_frame frame_tchf_ts5[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, -}; - -static const struct trx_frame frame_tchf_ts6[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, -}; - -static const struct trx_frame frame_tchf_ts7[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, -}; - -static const struct trx_frame frame_tchh_ts01[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, -}; - -static const struct trx_frame frame_tchh_ts23[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, -}; - -static const struct trx_frame frame_tchh_ts45[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, -}; - -static const struct trx_frame frame_tchh_ts67[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, -}; - -static const struct trx_frame frame_pdch[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PTCCH, 0, TRXC_PTCCH, 0 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PTCCH, 1, TRXC_PTCCH, 1 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PTCCH, 2, TRXC_PTCCH, 2 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PTCCH, 3, TRXC_PTCCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, -}; - -/* Logical channel mask for a single channel */ -#define M64(x) \ - ((uint64_t) 0x01 << x) - -/* Logical channel mask for BCCH+CCCH */ -#define M64_BCCH_CCCH \ - (uint64_t) 0x00 \ - | M64(TRXC_FCCH) \ - | M64(TRXC_SCH) \ - | M64(TRXC_BCCH) \ - | M64(TRXC_RACH) \ - | M64(TRXC_CCCH) - -/* Logical channel mask for SDCCH4 (with SACCH, all sub-channels) */ -#define M64_SDCCH4 \ - (uint64_t) 0x00 \ - | M64(TRXC_SDCCH4_0) | M64(TRXC_SACCH4_0) \ - | M64(TRXC_SDCCH4_1) | M64(TRXC_SACCH4_1) \ - | M64(TRXC_SDCCH4_2) | M64(TRXC_SACCH4_2) \ - | M64(TRXC_SDCCH4_3) | M64(TRXC_SACCH4_3) - -/* Logical channel mask for SDCCH8 (with SACCH, all sub-channels) */ -#define M64_SDCCH8 \ - (uint64_t) 0x00 \ - | M64(TRXC_SDCCH8_0) | M64(TRXC_SACCH8_0) \ - | M64(TRXC_SDCCH8_1) | M64(TRXC_SACCH8_1) \ - | M64(TRXC_SDCCH8_2) | M64(TRXC_SACCH8_2) \ - | M64(TRXC_SDCCH8_3) | M64(TRXC_SACCH8_3) \ - | M64(TRXC_SDCCH8_4) | M64(TRXC_SACCH8_4) \ - | M64(TRXC_SDCCH8_5) | M64(TRXC_SACCH8_5) \ - | M64(TRXC_SDCCH8_6) | M64(TRXC_SACCH8_6) \ - | M64(TRXC_SDCCH8_7) | M64(TRXC_SACCH8_7) - -/* Logical channel mask for TCH/F (with SACCH) */ -#define M64_TCHF \ - (uint64_t) 0x00 \ - | M64(TRXC_TCHF) | M64(TRXC_SACCHTF) - -/* Logical channel mask for TCH/H (with SACCH, all sub-channels) */ -#define M64_TCHH \ - (uint64_t) 0x00 \ - | M64(TRXC_TCHH_0) | M64(TRXC_SACCHTH_0) \ - | M64(TRXC_TCHH_1) | M64(TRXC_SACCHTH_1) - -/** - * A few notes about frame count: - * - * 26 frame multiframe - traffic multiframe - * 51 frame multiframe - control multiframe - * - * 102 = 2 x 51 frame multiframe - * 104 = 4 x 26 frame multiframe - */ -static const struct trx_multiframe layouts[] = { - { - GSM_PCHAN_NONE, "NONE", - 0, 0xff, - 0x00, - NULL - }, - { - GSM_PCHAN_CCCH, "BCCH+CCCH", - 51, 0xff, - M64_BCCH_CCCH, - frame_bcch - }, - { - GSM_PCHAN_CCCH_SDCCH4, "BCCH+CCCH+SDCCH/4+SACCH/4", - 102, 0xff, - M64_BCCH_CCCH | M64_SDCCH4, - frame_bcch_sdcch4 - }, - { - GSM_PCHAN_CCCH_SDCCH4_CBCH, "BCCH+CCCH+SDCCH/4+SACCH/4+CBCH", - 102, 0xff, - M64_BCCH_CCCH | M64_SDCCH4 | M64(TRXC_SDCCH4_CBCH), - frame_bcch_sdcch4_cbch - }, - { - GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH/8+SACCH/8", - 102, 0xff, - M64_SDCCH8, - frame_sdcch8 - }, - { - GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH/8+SACCH/8+CBCH", - 102, 0xff, - M64_SDCCH8 | M64(TRXC_SDCCH8_CBCH), - frame_sdcch8_cbch - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x01, - M64_TCHF, - frame_tchf_ts0 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x02, - M64_TCHF, - frame_tchf_ts1 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x04, - M64_TCHF, - frame_tchf_ts2 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x08, - M64_TCHF, - frame_tchf_ts3 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x10, - M64_TCHF, - frame_tchf_ts4 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x20, - M64_TCHF, - frame_tchf_ts5 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x40, - M64_TCHF, - frame_tchf_ts6 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x80, - M64_TCHF, - frame_tchf_ts7 - }, - { - GSM_PCHAN_TCH_H, "TCH/H+SACCH", - 104, 0x03, - M64_TCHH, - frame_tchh_ts01 - }, - { - GSM_PCHAN_TCH_H, "TCH/H+SACCH", - 104, 0x0c, - M64_TCHH, - frame_tchh_ts23 - }, - { - GSM_PCHAN_TCH_H, "TCH/H+SACCH", - 104, 0x30, - M64_TCHH, - frame_tchh_ts45 - }, - { - GSM_PCHAN_TCH_H, "TCH/H+SACCH", - 104, 0xc0, - M64_TCHH, - frame_tchh_ts67 - }, - { - GSM_PCHAN_PDCH, "PDCH", - 104, 0xff, - M64(TRXC_PDTCH) | M64(TRXC_PTCCH), - frame_pdch - }, -}; - -const struct trx_multiframe *sched_mframe_layout( - enum gsm_phys_chan_config config, int tn) -{ - int i, ts_allowed; - - for (i = 0; i < ARRAY_SIZE(layouts); i++) { - ts_allowed = layouts[i].slotmask & (0x01 << tn); - if (layouts[i].chan_config == config && ts_allowed) - return &layouts[i]; - } - - return NULL; -} diff --git a/src/host/trxcon/sched_prim.c b/src/host/trxcon/sched_prim.c deleted file mode 100644 index 50dfd6e9..00000000 --- a/src/host/trxcon/sched_prim.c +++ /dev/null @@ -1,611 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: primitive management - * - * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.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 <errno.h> -#include <stdlib.h> -#include <string.h> -#include <talloc.h> - -#include <osmocom/core/msgb.h> -#include <osmocom/core/logging.h> -#include <osmocom/core/linuxlist.h> - -#include <osmocom/gsm/protocol/gsm_04_08.h> - -#include "scheduler.h" -#include "sched_trx.h" -#include "trx_if.h" -#include "logging.h" - -/** - * Initializes a new primitive by allocating memory - * and filling some meta-information (e.g. lchan type). - * - * @param ctx parent talloc context - * @param prim external prim pointer (will point to the allocated prim) - * @param pl_len prim payload length - * @param chan_nr RSL channel description (used to set a proper chan) - * @param link_id RSL link description (used to set a proper chan) - * @return zero in case of success, otherwise a error number - */ -int sched_prim_init(void *ctx, struct trx_ts_prim **prim, - size_t pl_len, uint8_t chan_nr, uint8_t link_id) -{ - enum trx_lchan_type lchan_type; - struct trx_ts_prim *new_prim; - uint8_t len; - - /* Determine lchan type */ - lchan_type = sched_trx_chan_nr2lchan_type(chan_nr, link_id); - if (!lchan_type) { - LOGP(DSCH, LOGL_ERROR, "Couldn't determine lchan type " - "for chan_nr=%02x and link_id=%02x\n", chan_nr, link_id); - return -EINVAL; - } - - /* How much memory do we need? */ - len = sizeof(struct trx_ts_prim); /* Primitive header */ - len += pl_len; /* Requested payload size */ - - /* Allocate a new primitive */ - new_prim = talloc_zero_size(ctx, len); - if (new_prim == NULL) { - LOGP(DSCH, LOGL_ERROR, "Failed to allocate memory\n"); - return -ENOMEM; - } - - /* Init primitive header */ - new_prim->payload_len = pl_len; - new_prim->chan = lchan_type; - - /* Set external pointer */ - *prim = new_prim; - - return 0; -} - -/** - * Adds a primitive to the end of transmit queue of a particular - * timeslot, whose index is parsed from chan_nr. - * - * @param trx TRX instance - * @param prim to be enqueued primitive - * @param chan_nr RSL channel description - * @return zero in case of success, otherwise a error number - */ -int sched_prim_push(struct trx_instance *trx, - struct trx_ts_prim *prim, uint8_t chan_nr) -{ - struct trx_ts *ts; - uint8_t tn; - - /* Determine TS index */ - tn = chan_nr & 0x7; - - /* Check whether required timeslot is allocated and configured */ - ts = trx->ts_list[tn]; - if (ts == NULL || ts->mf_layout == NULL) { - LOGP(DSCH, LOGL_ERROR, "Timeslot %u isn't configured\n", tn); - return -EINVAL; - } - - /** - * Change talloc context of primitive - * from trx to the parent ts - */ - talloc_steal(ts, prim); - - /* Add primitive to TS transmit queue */ - llist_add_tail(&prim->list, &ts->tx_prims); - - return 0; -} - -/** - * Composes a new primitive using either cached (if populated), - * or "dummy" Measurement Report message. - * - * @param lchan lchan to assign a primitive - * @return SACCH primitive to be transmitted - */ -static struct trx_ts_prim *prim_compose_mr(struct trx_lchan_state *lchan) -{ - struct trx_ts_prim *prim; - uint8_t *mr_src_ptr; - bool cached; - int rc; - - /* "Dummy" Measurement Report */ - static const uint8_t meas_rep_dummy[] = { - /* L1 SACCH pseudo-header */ - 0x0f, 0x00, - - /* LAPDm header */ - 0x01, 0x03, 0x49, - - /* Measurement report */ - 0x06, 0x15, 0x36, 0x36, 0x01, 0xC0, - - /* TODO: Padding? Randomize if so */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }; - - /* Allocate a new primitive */ - rc = sched_prim_init(lchan, &prim, GSM_MACBLOCK_LEN, - trx_lchan_desc[lchan->type].chan_nr, TRX_CH_LID_SACCH); - OSMO_ASSERT(rc == 0); - - /* Check if the MR cache is populated (verify LAPDm header) */ - cached = (lchan->sacch.mr_cache[2] != 0x00 - && lchan->sacch.mr_cache[3] != 0x00 - && lchan->sacch.mr_cache[4] != 0x00); - if (cached) { /* Use the cached one */ - mr_src_ptr = lchan->sacch.mr_cache; - lchan->sacch.mr_cache_usage++; - } else { /* Use "dummy" one */ - mr_src_ptr = (uint8_t *) meas_rep_dummy; - } - - /* Compose a new Measurement Report primitive */ - memcpy(prim->payload, mr_src_ptr, GSM_MACBLOCK_LEN); - -#if 0 - /** - * Update the L1 SACCH pseudo-header (only for cached MRs) - * - * FIXME: this would require having access to the trx_instance, - * what can be achieved either by chain-passing the pointer - * through sched_prim_dequeue(), or by adding some - * back-pointers to the logical channel state. - * - * TODO: filling of the actual values into cached Measurement - * Reports would break the distance spoofing feature. If it - * were known whether the spoofing is enabled or not, we could - * decide whether to update the cached L1 SACCH header here. - */ - if (!cached) { - prim->payload[0] = trx->tx_power; - prim->payload[1] = trx->ta; - } -#endif - - /* Inform about the cache usage count */ - if (cached && lchan->sacch.mr_cache_usage > 5) { - LOGP(DSCHD, LOGL_NOTICE, "SACCH MR cache usage count=%u > 5 " - "on lchan=%s => ancient measurements, please fix!\n", - lchan->sacch.mr_cache_usage, - trx_lchan_desc[lchan->type].name); - } - - LOGP(DSCHD, LOGL_NOTICE, "Using a %s Measurement Report " - "on lchan=%s\n", (cached ? "cached" : "dummy"), - trx_lchan_desc[lchan->type].name); - - return prim; -} - -/** - * Dequeues a SACCH primitive from transmit queue, if present. - * Otherwise dequeues a cached Measurement Report (the last - * received one). Finally, if the cache is empty, a "dummy" - * measurement report is used. - * - * According to 3GPP TS 04.08, section 3.4.1, SACCH channel - * accompanies either a traffic or a signaling channel. It - * has the particularity that continuous transmission must - * occur in both directions, so on the Uplink direction - * measurement result messages are sent at each possible - * occasion when nothing else has to be sent. The LAPDm - * fill frames (0x01, 0x03, 0x01, 0x2b, ...) are not - * applicable on SACCH channels! - * - * Unfortunately, 3GPP TS 04.08 doesn't clearly state - * which "else messages" besides Measurement Reports - * can be send by the MS on SACCH channels. However, - * in sub-clause 3.4.1 it's stated that the interval - * between two successive measurement result messages - * shall not exceed one L2 frame. - * - * @param queue transmit queue to take a prim from - * @param lchan lchan to assign a primitive - * @return SACCH primitive to be transmitted - */ -static struct trx_ts_prim *prim_dequeue_sacch(struct llist_head *queue, - struct trx_lchan_state *lchan) -{ - struct trx_ts_prim *prim_nmr = NULL; - struct trx_ts_prim *prim_mr = NULL; - struct trx_ts_prim *prim; - bool mr_now; - - /* Shall we transmit MR now? */ - mr_now = !lchan->sacch.mr_tx_last; - -#define PRIM_IS_MR(prim) \ - (prim->payload[5] == GSM48_PDISC_RR \ - && prim->payload[6] == GSM48_MT_RR_MEAS_REP) - - /* Iterate over all primitives in the queue */ - llist_for_each_entry(prim, queue, list) { - /* We are looking for particular channel */ - if (prim->chan != lchan->type) - continue; - - /* Look for a Measurement Report */ - if (!prim_mr && PRIM_IS_MR(prim)) - prim_mr = prim; - - /* Look for anything else */ - if (!prim_nmr && !PRIM_IS_MR(prim)) - prim_nmr = prim; - - /* Should we look further? */ - if (mr_now && prim_mr) - break; /* MR was found */ - else if (!mr_now && prim_nmr) - break; /* something else was found */ - } - - LOGP(DSCHD, LOGL_DEBUG, "SACCH MR selection on lchan=%s: " - "mr_tx_last=%d prim_mr=%p prim_nmr=%p\n", - trx_lchan_desc[lchan->type].name, - lchan->sacch.mr_tx_last, - prim_mr, prim_nmr); - - /* Prioritize non-MR prim if possible */ - if (mr_now && prim_mr) - prim = prim_mr; - else if (!mr_now && prim_nmr) - prim = prim_nmr; - else if (!mr_now && prim_mr) - prim = prim_mr; - else /* Nothing was found */ - prim = NULL; - - /* Have we found what we were looking for? */ - if (prim) /* Dequeue if so */ - llist_del(&prim->list); - else /* Otherwise compose a new MR */ - prim = prim_compose_mr(lchan); - - /* Update the cached report */ - if (prim == prim_mr) { - memcpy(lchan->sacch.mr_cache, - prim->payload, GSM_MACBLOCK_LEN); - lchan->sacch.mr_cache_usage = 0; - - LOGP(DSCHD, LOGL_DEBUG, "SACCH MR cache has been updated " - "for lchan=%s\n", trx_lchan_desc[lchan->type].name); - } - - /* Update the MR transmission state */ - lchan->sacch.mr_tx_last = PRIM_IS_MR(prim); - - LOGP(DSCHD, LOGL_DEBUG, "SACCH decision on lchan=%s: %s\n", - trx_lchan_desc[lchan->type].name, PRIM_IS_MR(prim) ? - "Measurement Report" : "data frame"); - - return prim; -} - -/* Dequeues a primitive of a given channel type */ -static struct trx_ts_prim *prim_dequeue_one(struct llist_head *queue, - enum trx_lchan_type lchan_type) -{ - struct trx_ts_prim *prim; - - /** - * There is no need to use the 'safe' list iteration here - * as an item removal is immediately followed by return. - */ - llist_for_each_entry(prim, queue, list) { - if (prim->chan == lchan_type) { - llist_del(&prim->list); - return prim; - } - } - - return NULL; -} - -/** - * Dequeues either a FACCH, or a speech TCH primitive - * of a given channel type (Lm or Bm). - * - * Note: we could avoid 'lchan_type' parameter and just - * check the prim's channel type using CHAN_IS_TCH(), - * but the current approach is a bit more flexible, - * and allows one to have both sub-slots of TCH/H - * enabled on same timeslot e.g. for testing... - * - * @param queue transmit queue to take a prim from - * @param lchan_type required channel type of a primitive, - * e.g. TRXC_TCHF, TRXC_TCHH_0, or TRXC_TCHH_1 - * @param facch FACCH (true) or speech (false) prim? - * @return either a FACCH, or a TCH primitive if found, - * otherwise NULL - */ -static struct trx_ts_prim *prim_dequeue_tch(struct llist_head *queue, - enum trx_lchan_type lchan_type, bool facch) -{ - struct trx_ts_prim *prim; - - /** - * There is no need to use the 'safe' list iteration here - * as an item removal is immediately followed by return. - */ - llist_for_each_entry(prim, queue, list) { - if (prim->chan != lchan_type) - continue; - - /* Either FACCH, or not FACCH */ - if (PRIM_IS_FACCH(prim) != facch) - continue; - - llist_del(&prim->list); - return prim; - } - - return NULL; -} - -/** - * Dequeues either a TCH/F, or a FACCH/F prim (preferred). - * If a FACCH/F prim is found, one TCH/F prim is being - * dropped (i.e. replaced). - * - * @param queue a transmit queue to take a prim from - * @return either a FACCH/F, or a TCH/F primitive, - * otherwise NULL - */ -static struct trx_ts_prim *prim_dequeue_tch_f(struct llist_head *queue) -{ - struct trx_ts_prim *facch; - struct trx_ts_prim *tch; - - /* Attempt to find a pair of both FACCH/F and TCH/F frames */ - facch = prim_dequeue_tch(queue, TRXC_TCHF, true); - tch = prim_dequeue_tch(queue, TRXC_TCHF, false); - - /* Prioritize FACCH/F, if found */ - if (facch) { - /* One TCH/F prim is replaced */ - if (tch) - talloc_free(tch); - return facch; - } else if (tch) { - /* Only TCH/F prim was found */ - return tch; - } else { - /* Nothing was found, e.g. when only SACCH frames are in queue */ - return NULL; - } -} - -/** - * Dequeues either a TCH/H, or a FACCH/H prim (preferred). - * If a FACCH/H prim is found, two TCH/H prims are being - * dropped (i.e. replaced). - * - * According to GSM 05.02, the following blocks can be used - * to carry FACCH/H data (see clause 7, table 1 of 9): - * - * UL FACCH/H0: - * B0(0,2,4,6,8,10), B1(8,10,13,15,17,19), B2(17,19,21,23,0,2) - * - * UL FACCH/H1: - * B0(1,3,5,7,9,11), B1(9,11,14,16,18,20), B2(18,20,22,24,1,3) - * - * where the numbers within brackets are fn % 26. - * - * @param queue transmit queue to take a prim from - * @param fn the current frame number - * @param lchan_type required channel type of a primitive, - * @return either a FACCH/H, or a TCH/H primitive, - * otherwise NULL - */ -static struct trx_ts_prim *prim_dequeue_tch_h(struct llist_head *queue, - uint32_t fn, enum trx_lchan_type lchan_type) -{ - struct trx_ts_prim *facch; - struct trx_ts_prim *tch; - bool facch_now; - - /* May we initiate an UL FACCH/H frame transmission now? */ - facch_now = sched_tchh_facch_start(lchan_type, fn, true); - if (!facch_now) /* Just dequeue a TCH/H prim */ - goto no_facch; - - /* If there are no FACCH/H prims in the queue */ - facch = prim_dequeue_tch(queue, lchan_type, true); - if (!facch) /* Just dequeue a TCH/H prim */ - goto no_facch; - - /* FACCH/H prim replaces two TCH/F prims */ - tch = prim_dequeue_tch(queue, lchan_type, false); - if (tch) { - /* At least one TCH/H prim is dropped */ - talloc_free(tch); - - /* Attempt to find another */ - tch = prim_dequeue_tch(queue, lchan_type, false); - if (tch) /* Drop the second TCH/H prim */ - talloc_free(tch); - } - - return facch; - -no_facch: - return prim_dequeue_tch(queue, lchan_type, false); -} - -/** - * Dequeues a single primitive of required type - * from a specified transmit queue. - * - * @param queue a transmit queue to take a prim from - * @param fn the current frame number (used for FACCH/H) - * @param lchan logical channel state - * @return a primitive or NULL if not found - */ -struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue, - uint32_t fn, struct trx_lchan_state *lchan) -{ - /* SACCH is unorthodox, see 3GPP TS 04.08, section 3.4.1 */ - if (CHAN_IS_SACCH(lchan->type)) - return prim_dequeue_sacch(queue, lchan); - - /* There is nothing to dequeue */ - if (llist_empty(queue)) - return NULL; - - switch (lchan->type) { - /* TCH/F requires FACCH/F prioritization */ - case TRXC_TCHF: - return prim_dequeue_tch_f(queue); - - /* FACCH/H prioritization is a bit more complex */ - case TRXC_TCHH_0: - case TRXC_TCHH_1: - return prim_dequeue_tch_h(queue, fn, lchan->type); - - /* Other kinds of logical channels */ - default: - return prim_dequeue_one(queue, lchan->type); - } -} - -/** - * Drops the current primitive of specified logical channel - * - * @param lchan a logical channel to drop prim from - */ -void sched_prim_drop(struct trx_lchan_state *lchan) -{ - /* Forget this primitive */ - talloc_free(lchan->prim); - lchan->prim = NULL; -} - -/** - * Assigns a dummy primitive to a lchan depending on its type. - * Could be used when there is nothing to transmit, but - * CBTX (Continuous Burst Transmission) is assumed. - * - * @param lchan lchan to assign a primitive - * @return zero in case of success, otherwise a error code - */ -int sched_prim_dummy(struct trx_lchan_state *lchan) -{ - enum trx_lchan_type chan = lchan->type; - uint8_t tch_mode = lchan->tch_mode; - struct trx_ts_prim *prim; - uint8_t prim_buffer[40]; - size_t prim_len = 0; - int i; - - /** - * TS 144.006, section 8.4.2.3 "Fill frames" - * A fill frame is a UI command frame for SAPI 0, P=0 - * and with an information field of 0 octet length. - */ - static const uint8_t lapdm_fill_frame[] = { - 0x01, 0x03, 0x01, 0x2b, - /* Pending part is to be randomized */ - }; - - /* Make sure that there is no existing primitive */ - OSMO_ASSERT(lchan->prim == NULL); - /* Not applicable for SACCH! */ - OSMO_ASSERT(!CHAN_IS_SACCH(lchan->type)); - - /** - * Determine what actually should be generated: - * TCH in GSM48_CMODE_SIGN: LAPDm fill frame; - * TCH in other modes: silence frame; - * other channels: LAPDm fill frame. - */ - if (CHAN_IS_TCH(chan) && TCH_MODE_IS_SPEECH(tch_mode)) { - /* Bad frame indication */ - prim_len = sched_bad_frame_ind(prim_buffer, lchan); - } else if (CHAN_IS_TCH(chan) && TCH_MODE_IS_DATA(tch_mode)) { - /* FIXME: should we do anything for CSD? */ - return 0; - } else { - /* Copy LAPDm fill frame's header */ - memcpy(prim_buffer, lapdm_fill_frame, sizeof(lapdm_fill_frame)); - - /** - * TS 144.006, section 5.2 "Frame delimitation and fill bits" - * Except for the first octet containing fill bits which shall - * be set to the binary value "00101011", each fill bit should - * be set to a random value when sent by the network. - */ - for (i = sizeof(lapdm_fill_frame); i < GSM_MACBLOCK_LEN; i++) - prim_buffer[i] = (uint8_t) rand(); - - /* Define a prim length */ - prim_len = GSM_MACBLOCK_LEN; - } - - /* Nothing to allocate / assign */ - if (!prim_len) - return 0; - - /* Allocate a new primitive */ - prim = talloc_zero_size(lchan, sizeof(struct trx_ts_prim) + prim_len); - if (prim == NULL) - return -ENOMEM; - - /* Init primitive header */ - prim->payload_len = prim_len; - prim->chan = lchan->type; - - /* Fill in the payload */ - memcpy(prim->payload, prim_buffer, prim_len); - - /* Assign the current prim */ - lchan->prim = prim; - - LOGP(DSCHD, LOGL_DEBUG, "Transmitting a dummy / silence frame " - "on lchan=%s\n", trx_lchan_desc[chan].name); - - return 0; -} - -/** - * Flushes a queue of primitives - * - * @param list list of prims going to be flushed - */ -void sched_prim_flush_queue(struct llist_head *list) -{ - struct trx_ts_prim *prim, *prim_next; - - llist_for_each_entry_safe(prim, prim_next, list, list) { - llist_del(&prim->list); - talloc_free(prim); - } -} diff --git a/src/host/trxcon/sched_trx.c b/src/host/trxcon/sched_trx.c deleted file mode 100644 index 37d1acf6..00000000 --- a/src/host/trxcon/sched_trx.c +++ /dev/null @@ -1,704 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: GSM PHY routines - * - * (C) 2017-2019 by Vadim Yanitskiy <axilirator@gmail.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 <error.h> -#include <errno.h> -#include <string.h> -#include <talloc.h> -#include <stdbool.h> - -#include <osmocom/gsm/a5.h> -#include <osmocom/gsm/protocol/gsm_08_58.h> -#include <osmocom/core/bits.h> -#include <osmocom/core/msgb.h> -#include <osmocom/core/logging.h> -#include <osmocom/core/linuxlist.h> - -#include "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" -#include "trx_if.h" -#include "logging.h" - -static void sched_frame_clck_cb(struct trx_sched *sched) -{ - struct trx_instance *trx = (struct trx_instance *) sched->data; - const struct trx_frame *frame; - struct trx_lchan_state *lchan; - trx_lchan_tx_func *handler; - enum trx_lchan_type chan; - uint8_t offset, bid; - struct trx_ts *ts; - uint32_t fn; - int i; - - /* Iterate over timeslot list */ - for (i = 0; i < TRX_TS_COUNT; i++) { - /* Timeslot is not allocated */ - ts = trx->ts_list[i]; - if (ts == NULL) - continue; - - /* Timeslot is not configured */ - if (ts->mf_layout == NULL) - continue; - - /** - * Advance frame number, giving the transceiver more - * time until a burst must be transmitted... - */ - fn = TDMA_FN_SUM(sched->fn_counter_proc, - sched->fn_counter_advance); - - /* Get frame from multiframe */ - offset = fn % ts->mf_layout->period; - frame = ts->mf_layout->frames + offset; - - /* Get required info from frame */ - bid = frame->ul_bid; - chan = frame->ul_chan; - handler = trx_lchan_desc[chan].tx_fn; - - /* Omit lchans without handler */ - if (!handler) - continue; - - /* Make sure that lchan was allocated and activated */ - lchan = sched_trx_find_lchan(ts, chan); - if (lchan == NULL) - continue; - - /* Omit inactive lchans */ - if (!lchan->active) - continue; - - /** - * If we aren't processing any primitive yet, - * attempt to obtain a new one from queue - */ - if (lchan->prim == NULL) - lchan->prim = sched_prim_dequeue(&ts->tx_prims, fn, lchan); - - /* TODO: report TX buffers health to the higher layers */ - - /* If CBTX (Continuous Burst Transmission) is assumed */ - if (trx_lchan_desc[chan].flags & TRX_CH_FLAG_CBTX) { - /** - * Probably, a TX buffer is empty. Nevertheless, - * we shall continuously transmit anything on - * CBTX channels. - */ - if (lchan->prim == NULL) - sched_prim_dummy(lchan); - } - - /* If there is no primitive, do nothing */ - if (lchan->prim == NULL) - continue; - - /* Handover RACH needs to be handled regardless of the - * current channel type and the associated handler. */ - if (PRIM_IS_RACH(lchan->prim) && lchan->prim->chan != TRXC_RACH) - handler = trx_lchan_desc[TRXC_RACH].tx_fn; - - /* Poke lchan handler */ - handler(trx, ts, lchan, fn, bid); - } -} - -int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance) -{ - struct trx_sched *sched; - - if (!trx) - return -EINVAL; - - LOGP(DSCH, LOGL_NOTICE, "Init scheduler\n"); - - /* Obtain a scheduler instance from TRX */ - sched = &trx->sched; - - /* Register frame clock callback */ - sched->clock_cb = sched_frame_clck_cb; - - /* Set pointers */ - sched = &trx->sched; - sched->data = trx; - - /* Set frame counter advance */ - sched->fn_counter_advance = fn_advance; - - return 0; -} - -int sched_trx_shutdown(struct trx_instance *trx) -{ - int i; - - if (!trx) - return -EINVAL; - - LOGP(DSCH, LOGL_NOTICE, "Shutdown scheduler\n"); - - /* Free all potentially allocated timeslots */ - for (i = 0; i < TRX_TS_COUNT; i++) - sched_trx_del_ts(trx, i); - - return 0; -} - -int sched_trx_reset(struct trx_instance *trx, bool reset_clock) -{ - int i; - - if (!trx) - return -EINVAL; - - LOGP(DSCH, LOGL_NOTICE, "Reset scheduler %s\n", - reset_clock ? "and clock counter" : ""); - - /* Free all potentially allocated timeslots */ - for (i = 0; i < TRX_TS_COUNT; i++) - sched_trx_del_ts(trx, i); - - /* Stop and reset clock counter if required */ - if (reset_clock) - sched_clck_reset(&trx->sched); - - return 0; -} - -struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn) -{ - /* Make sure that ts isn't allocated yet */ - if (trx->ts_list[tn] != NULL) { - LOGP(DSCH, LOGL_ERROR, "Timeslot #%u already allocated\n", tn); - return NULL; - } - - LOGP(DSCH, LOGL_NOTICE, "Add a new TDMA timeslot #%u\n", tn); - - /* Allocate a new one */ - trx->ts_list[tn] = talloc_zero(trx, struct trx_ts); - - /* Assign TS index */ - trx->ts_list[tn]->index = tn; - - return trx->ts_list[tn]; -} - -void sched_trx_del_ts(struct trx_instance *trx, int tn) -{ - struct trx_lchan_state *lchan, *lchan_next; - struct trx_ts *ts; - - /* Find ts in list */ - ts = trx->ts_list[tn]; - if (ts == NULL) - return; - - LOGP(DSCH, LOGL_NOTICE, "Delete TDMA timeslot #%u\n", tn); - - /* Deactivate all logical channels */ - sched_trx_deactivate_all_lchans(ts); - - /* Free channel states */ - llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) { - llist_del(&lchan->list); - talloc_free(lchan); - } - - /* Flush queue primitives for TX */ - sched_prim_flush_queue(&ts->tx_prims); - - /* Remove ts from list and free memory */ - trx->ts_list[tn] = NULL; - talloc_free(ts); - - /* Notify transceiver about that */ - trx_if_cmd_setslot(trx, tn, 0); -} - -#define LAYOUT_HAS_LCHAN(layout, lchan) \ - (layout->lchan_mask & ((uint64_t) 0x01 << lchan)) - -int sched_trx_configure_ts(struct trx_instance *trx, int tn, - enum gsm_phys_chan_config config) -{ - struct trx_lchan_state *lchan; - enum trx_lchan_type type; - struct trx_ts *ts; - - /* Try to find specified ts */ - ts = trx->ts_list[tn]; - if (ts != NULL) { - /* Reconfiguration of existing one */ - sched_trx_reset_ts(trx, tn); - } else { - /* Allocate a new one if doesn't exist */ - ts = sched_trx_add_ts(trx, tn); - if (ts == NULL) - return -ENOMEM; - } - - /* Choose proper multiframe layout */ - ts->mf_layout = sched_mframe_layout(config, tn); - if (ts->mf_layout->chan_config != config) - return -EINVAL; - - LOGP(DSCH, LOGL_NOTICE, "(Re)configure TDMA timeslot #%u as %s\n", - tn, ts->mf_layout->name); - - /* Init queue primitives for TX */ - INIT_LLIST_HEAD(&ts->tx_prims); - /* Init logical channels list */ - INIT_LLIST_HEAD(&ts->lchans); - - /* Allocate channel states */ - for (type = 0; type < _TRX_CHAN_MAX; type++) { - if (!LAYOUT_HAS_LCHAN(ts->mf_layout, type)) - continue; - - /* Allocate a channel state */ - lchan = talloc_zero(ts, struct trx_lchan_state); - if (!lchan) - return -ENOMEM; - - /* Set channel type */ - lchan->type = type; - - /* Add to the list of channel states */ - llist_add_tail(&lchan->list, &ts->lchans); - - /* Enable channel automatically if required */ - if (trx_lchan_desc[type].flags & TRX_CH_FLAG_AUTO) - sched_trx_activate_lchan(ts, type); - } - - /* Notify transceiver about TS activation */ - /* FIXME: set proper channel type */ - trx_if_cmd_setslot(trx, tn, 1); - - return 0; -} - -int sched_trx_reset_ts(struct trx_instance *trx, int tn) -{ - struct trx_lchan_state *lchan, *lchan_next; - struct trx_ts *ts; - - /* Try to find specified ts */ - ts = trx->ts_list[tn]; - if (ts == NULL) - return -EINVAL; - - /* Flush TS frame counter */ - ts->mf_last_fn = 0; - - /* Undefine multiframe layout */ - ts->mf_layout = NULL; - - /* Flush queue primitives for TX */ - sched_prim_flush_queue(&ts->tx_prims); - - /* Deactivate all logical channels */ - sched_trx_deactivate_all_lchans(ts); - - /* Free channel states */ - llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) { - llist_del(&lchan->list); - talloc_free(lchan); - } - - /* Notify transceiver about that */ - trx_if_cmd_setslot(trx, tn, 0); - - return 0; -} - -int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo, - uint8_t *key, uint8_t key_len) -{ - struct trx_lchan_state *lchan; - - /* Prevent NULL-pointer deference */ - if (!ts) - return -EINVAL; - - /* Make sure we can store this key */ - if (key_len > MAX_A5_KEY_LEN) - return -ERANGE; - - /* Iterate over all allocated logical channels */ - llist_for_each_entry(lchan, &ts->lchans, list) { - /* Omit inactive channels */ - if (!lchan->active) - continue; - - /* Set key length and algorithm */ - lchan->a5.key_len = key_len; - lchan->a5.algo = algo; - - /* Copy requested key */ - if (key_len) - memcpy(lchan->a5.key, key, key_len); - } - - return 0; -} - -struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts, - enum trx_lchan_type chan) -{ - struct trx_lchan_state *lchan; - - llist_for_each_entry(lchan, &ts->lchans, list) - if (lchan->type == chan) - return lchan; - - return NULL; -} - -int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode) -{ - const struct trx_lchan_desc *lchan_desc; - struct trx_lchan_state *lchan; - int rc = 0; - - /* Prevent NULL-pointer deference */ - if (ts == NULL) { - LOGP(DSCH, LOGL_ERROR, "Timeslot isn't configured\n"); - return -EINVAL; - } - - /* Iterate over all allocated lchans */ - llist_for_each_entry(lchan, &ts->lchans, list) { - lchan_desc = &trx_lchan_desc[lchan->type]; - - if (lchan_desc->chan_nr == (chan_nr & 0xf8)) { - if (active) { - rc |= sched_trx_activate_lchan(ts, lchan->type); - lchan->tch_mode = tch_mode; - } else - rc |= sched_trx_deactivate_lchan(ts, lchan->type); - } - } - - return rc; -} - -int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan) -{ - const struct trx_lchan_desc *lchan_desc = &trx_lchan_desc[chan]; - struct trx_lchan_state *lchan; - - /* Try to find requested logical channel */ - lchan = sched_trx_find_lchan(ts, chan); - if (lchan == NULL) - return -EINVAL; - - if (lchan->active) { - LOGP(DSCH, LOGL_ERROR, "Logical channel %s already activated " - "on ts=%d\n", trx_lchan_desc[chan].name, ts->index); - return -EINVAL; - } - - LOGP(DSCH, LOGL_NOTICE, "Activating lchan=%s " - "on ts=%d\n", trx_lchan_desc[chan].name, ts->index); - - /* Conditionally allocate memory for bursts */ - if (lchan_desc->rx_fn && lchan_desc->burst_buf_size > 0) { - lchan->rx_bursts = talloc_zero_size(lchan, - lchan_desc->burst_buf_size); - if (lchan->rx_bursts == NULL) - return -ENOMEM; - } - - if (lchan_desc->tx_fn && lchan_desc->burst_buf_size > 0) { - lchan->tx_bursts = talloc_zero_size(lchan, - lchan_desc->burst_buf_size); - if (lchan->tx_bursts == NULL) - return -ENOMEM; - } - - /* Finally, update channel status */ - lchan->active = 1; - - return 0; -} - -static void sched_trx_reset_lchan(struct trx_lchan_state *lchan) -{ - /* Prevent NULL-pointer deference */ - OSMO_ASSERT(lchan != NULL); - - /* Reset internal state variables */ - lchan->rx_burst_mask = 0x00; - lchan->tx_burst_mask = 0x00; - lchan->rx_first_fn = 0; - - /* Free burst memory */ - talloc_free(lchan->rx_bursts); - talloc_free(lchan->tx_bursts); - - lchan->rx_bursts = NULL; - lchan->tx_bursts = NULL; - - /* Forget the current prim */ - sched_prim_drop(lchan); - - /* Channel specific stuff */ - if (CHAN_IS_TCH(lchan->type)) { - lchan->dl_ongoing_facch = 0; - lchan->ul_facch_blocks = 0; - - lchan->tch_mode = GSM48_CMODE_SIGN; - - /* Reset AMR state */ - memset(&lchan->amr, 0x00, sizeof(lchan->amr)); - } else if (CHAN_IS_SACCH(lchan->type)) { - /* Reset SACCH state */ - memset(&lchan->sacch, 0x00, sizeof(lchan->sacch)); - } - - /* Reset ciphering state */ - memset(&lchan->a5, 0x00, sizeof(lchan->a5)); -} - -int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan) -{ - struct trx_lchan_state *lchan; - - /* Try to find requested logical channel */ - lchan = sched_trx_find_lchan(ts, chan); - if (lchan == NULL) - return -EINVAL; - - if (!lchan->active) { - LOGP(DSCH, LOGL_ERROR, "Logical channel %s already deactivated " - "on ts=%d\n", trx_lchan_desc[chan].name, ts->index); - return -EINVAL; - } - - LOGP(DSCH, LOGL_DEBUG, "Deactivating lchan=%s " - "on ts=%d\n", trx_lchan_desc[chan].name, ts->index); - - /* Reset internal state, free memory */ - sched_trx_reset_lchan(lchan); - - /* Update activation flag */ - lchan->active = 0; - - return 0; -} - -void sched_trx_deactivate_all_lchans(struct trx_ts *ts) -{ - struct trx_lchan_state *lchan; - - LOGP(DSCH, LOGL_DEBUG, "Deactivating all logical channels " - "on ts=%d\n", ts->index); - - llist_for_each_entry(lchan, &ts->lchans, list) { - /* Omit inactive channels */ - if (!lchan->active) - continue; - - /* Reset internal state, free memory */ - sched_trx_reset_lchan(lchan); - - /* Update activation flag */ - lchan->active = 0; - } -} - -enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr) -{ - uint8_t cbits = chan_nr >> 3; - - if (cbits == ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs) - return GSM_PCHAN_TCH_F; - else if ((cbits & 0x1e) == ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0)) - return GSM_PCHAN_TCH_H; - else if ((cbits & 0x1c) == ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0)) - return GSM_PCHAN_CCCH_SDCCH4; - else if ((cbits & 0x18) == ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0)) - return GSM_PCHAN_SDCCH8_SACCH8C; - else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH4) - return GSM_PCHAN_CCCH_SDCCH4_CBCH; - else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH8) - return GSM_PCHAN_SDCCH8_SACCH8C_CBCH; - else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH) - return GSM_PCHAN_PDCH; - - return GSM_PCHAN_NONE; -} - -enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr, - uint8_t link_id) -{ - int i; - - /* Iterate over all known lchan types */ - for (i = 0; i < _TRX_CHAN_MAX; i++) - if (trx_lchan_desc[i].chan_nr == (chan_nr & 0xf8)) - if (trx_lchan_desc[i].link_id == link_id) - return i; - - return TRXC_IDLE; -} - -static void sched_trx_a5_burst_dec(struct trx_lchan_state *lchan, - uint32_t fn, sbit_t *burst) -{ - ubit_t ks[114]; - int i; - - /* Generate keystream for a DL burst */ - osmo_a5(lchan->a5.algo, lchan->a5.key, fn, ks, NULL); - - /* Apply keystream over ciphertext */ - for (i = 0; i < 57; i++) { - if (ks[i]) - burst[i + 3] *= -1; - if (ks[i + 57]) - burst[i + 88] *= -1; - } -} - -static void sched_trx_a5_burst_enc(struct trx_lchan_state *lchan, - uint32_t fn, ubit_t *burst) -{ - ubit_t ks[114]; - int i; - - /* Generate keystream for an UL burst */ - osmo_a5(lchan->a5.algo, lchan->a5.key, fn, NULL, ks); - - /* Apply keystream over plaintext */ - for (i = 0; i < 57; i++) { - burst[i + 3] ^= ks[i]; - burst[i + 88] ^= ks[i + 57]; - } -} - -int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn, - uint32_t burst_fn, sbit_t *bits, uint16_t nbits, - int8_t rssi, int16_t toa256) -{ - struct trx_lchan_state *lchan; - const struct trx_frame *frame; - struct trx_ts *ts; - - trx_lchan_rx_func *handler; - enum trx_lchan_type chan; - uint32_t fn, elapsed; - uint8_t offset, bid; - - /* Check whether required timeslot is allocated and configured */ - ts = trx->ts_list[tn]; - if (ts == NULL || ts->mf_layout == NULL) { - LOGP(DSCHD, LOGL_DEBUG, "TDMA timeslot #%u isn't configured, " - "ignoring burst...\n", tn); - return -EINVAL; - } - - /* Calculate how many frames have been elapsed */ - elapsed = TDMA_FN_SUB(burst_fn, ts->mf_last_fn); - - /** - * If not too many frames have been elapsed, - * start counting from last fn + 1 - */ - if (elapsed < 10) - fn = TDMA_FN_INC(ts->mf_last_fn); - else - fn = burst_fn; - - while (1) { - /* Get frame from multiframe */ - offset = fn % ts->mf_layout->period; - frame = ts->mf_layout->frames + offset; - - /* Get required info from frame */ - bid = frame->dl_bid; - chan = frame->dl_chan; - handler = trx_lchan_desc[chan].rx_fn; - - /* Omit bursts which have no handler, like IDLE bursts */ - if (!handler) - goto next_frame; - - /* Find required channel state */ - lchan = sched_trx_find_lchan(ts, chan); - if (lchan == NULL) - goto next_frame; - - /* Ensure that channel is active */ - if (!lchan->active) - goto next_frame; - - /* Reached current fn */ - if (fn == burst_fn) { - /* Perform A5/X decryption if required */ - if (lchan->a5.algo) - sched_trx_a5_burst_dec(lchan, fn, bits); - - /* Put burst to handler */ - handler(trx, ts, lchan, fn, bid, bits, rssi, toa256); - } - -next_frame: - /* Reached current fn */ - if (fn == burst_fn) - break; - - fn = TDMA_FN_INC(fn); - } - - /* Set last processed frame number */ - ts->mf_last_fn = fn; - - return 0; -} - -int sched_trx_handle_tx_burst(struct trx_instance *trx, - struct trx_ts *ts, struct trx_lchan_state *lchan, - uint32_t fn, ubit_t *bits) -{ - int rc; - - /* Perform A5/X burst encryption if required */ - if (lchan->a5.algo) - sched_trx_a5_burst_enc(lchan, fn, bits); - - /* Forward burst to transceiver */ - rc = trx_if_tx_burst(trx, ts->index, fn, trx->tx_power, bits); - if (rc) { - LOGP(DSCHD, LOGL_ERROR, "Could not send burst to transceiver\n"); - return rc; - } - - return 0; -} diff --git a/src/host/trxcon/sched_trx.h b/src/host/trxcon/sched_trx.h deleted file mode 100644 index 0d424999..00000000 --- a/src/host/trxcon/sched_trx.h +++ /dev/null @@ -1,369 +0,0 @@ -#pragma once - -#include <stdint.h> -#include <stdbool.h> - -#include <osmocom/core/bits.h> -#include <osmocom/core/utils.h> -#include <osmocom/gsm/gsm_utils.h> -#include <osmocom/core/linuxlist.h> - -#include "logging.h" -#include "scheduler.h" - -#define GSM_BURST_LEN 148 -#define GSM_BURST_PL_LEN 116 - -#define GPRS_BURST_LEN GSM_BURST_LEN -#define EDGE_BURST_LEN 444 - -#define GPRS_L2_MAX_LEN 54 -#define EDGE_L2_MAX_LEN 155 - -#define TRX_CH_LID_DEDIC 0x00 -#define TRX_CH_LID_SACCH 0x40 - -/* Osmocom-specific extension for PTCCH (see 3GPP TS 45.002, section 3.3.4.2). - * Shall be used to distinguish PTCCH and PDTCH channels on a PDCH time-slot. */ -#define TRX_CH_LID_PTCCH 0x80 - -/* Is a channel related to PDCH (GPRS) */ -#define TRX_CH_FLAG_PDCH (1 << 0) -/* Should a channel be activated automatically */ -#define TRX_CH_FLAG_AUTO (1 << 1) -/* Is continuous burst transmission assumed */ -#define TRX_CH_FLAG_CBTX (1 << 2) - -#define MAX_A5_KEY_LEN (128 / 8) -#define TRX_TS_COUNT 8 - -/* Forward declaration to avoid mutual include */ -struct trx_lchan_state; -struct trx_instance; -struct trx_ts; - -enum trx_burst_type { - TRX_BURST_GMSK, - TRX_BURST_8PSK, -}; - -/** - * These types define the different channels on a multiframe. - * Each channel has queues and can be activated individually. - */ -enum trx_lchan_type { - TRXC_IDLE = 0, - TRXC_FCCH, - TRXC_SCH, - TRXC_BCCH, - TRXC_RACH, - TRXC_CCCH, - TRXC_TCHF, - TRXC_TCHH_0, - TRXC_TCHH_1, - TRXC_SDCCH4_0, - TRXC_SDCCH4_1, - TRXC_SDCCH4_2, - TRXC_SDCCH4_3, - TRXC_SDCCH8_0, - TRXC_SDCCH8_1, - TRXC_SDCCH8_2, - TRXC_SDCCH8_3, - TRXC_SDCCH8_4, - TRXC_SDCCH8_5, - TRXC_SDCCH8_6, - TRXC_SDCCH8_7, - TRXC_SACCHTF, - TRXC_SACCHTH_0, - TRXC_SACCHTH_1, - TRXC_SACCH4_0, - TRXC_SACCH4_1, - TRXC_SACCH4_2, - TRXC_SACCH4_3, - TRXC_SACCH8_0, - TRXC_SACCH8_1, - TRXC_SACCH8_2, - TRXC_SACCH8_3, - TRXC_SACCH8_4, - TRXC_SACCH8_5, - TRXC_SACCH8_6, - TRXC_SACCH8_7, - TRXC_PDTCH, - TRXC_PTCCH, - TRXC_SDCCH4_CBCH, - TRXC_SDCCH8_CBCH, - _TRX_CHAN_MAX -}; - -typedef int trx_lchan_rx_func(struct trx_instance *trx, - struct trx_ts *ts, struct trx_lchan_state *lchan, - uint32_t fn, uint8_t bid, sbit_t *bits, - int8_t rssi, int16_t toa256); - -typedef int trx_lchan_tx_func(struct trx_instance *trx, - struct trx_ts *ts, struct trx_lchan_state *lchan, - uint32_t fn, uint8_t bid); - -struct trx_lchan_desc { - /*! \brief Human-readable name */ - const char *name; - /*! \brief Human-readable description */ - const char *desc; - - /*! \brief Channel Number (like in RSL) */ - uint8_t chan_nr; - /*! \brief Link ID (like in RSL) */ - uint8_t link_id; - - /*! \brief How much memory do we need to store bursts */ - size_t burst_buf_size; - /*! \brief Channel specific flags */ - uint8_t flags; - - /*! \brief Function to call when burst received from PHY */ - trx_lchan_rx_func *rx_fn; - /*! \brief Function to call when data received from L2 */ - trx_lchan_tx_func *tx_fn; -}; - -struct trx_frame { - /*! \brief Downlink TRX channel type */ - enum trx_lchan_type dl_chan; - /*! \brief Downlink block ID */ - uint8_t dl_bid; - /*! \brief Uplink TRX channel type */ - enum trx_lchan_type ul_chan; - /*! \brief Uplink block ID */ - uint8_t ul_bid; -}; - -struct trx_multiframe { - /*! \brief Channel combination */ - enum gsm_phys_chan_config chan_config; - /*! \brief Human-readable name */ - const char *name; - /*! \brief Repeats how many frames */ - uint8_t period; - /*! \brief Applies to which timeslots */ - uint8_t slotmask; - /*! \brief Contains which lchans */ - uint64_t lchan_mask; - /*! \brief Pointer to scheduling structure */ - const struct trx_frame *frames; -}; - -/* States each channel on a multiframe */ -struct trx_lchan_state { - /*! \brief Channel type */ - enum trx_lchan_type type; - /*! \brief Channel status */ - uint8_t active; - /*! \brief Link to a list of channels */ - struct llist_head list; - - /*! \brief Burst type: GMSK or 8PSK */ - enum trx_burst_type burst_type; - /*! \brief Frame number of first burst */ - uint32_t rx_first_fn; - /*! \brief Mask of received bursts */ - uint8_t rx_burst_mask; - /*! \brief Mask of transmitted bursts */ - uint8_t tx_burst_mask; - /*! \brief Burst buffer for RX */ - sbit_t *rx_bursts; - /*! \brief Burst buffer for TX */ - ubit_t *tx_bursts; - - /*! \brief A primitive being sent */ - struct trx_ts_prim *prim; - - /*! \brief Mode for TCH channels (see GSM48_CMODE_*) */ - uint8_t tch_mode; - - /*! \brief FACCH/H on downlink */ - bool dl_ongoing_facch; - /*! \brief pending FACCH/H blocks on Uplink */ - uint8_t ul_facch_blocks; - - struct { - /*! \brief Number of measurements */ - unsigned int num; - /*! \brief Sum of RSSI values */ - float rssi_sum; - /*! \brief Sum of TOA values */ - int32_t toa256_sum; - } meas; - - /*! \brief SACCH state */ - struct { - /*! \brief Cached measurement report (last received) */ - uint8_t mr_cache[GSM_MACBLOCK_LEN]; - /*! \brief Cache usage counter */ - uint8_t mr_cache_usage; - /*! \brief Was a MR transmitted last time? */ - bool mr_tx_last; - } sacch; - - /* AMR specific */ - struct { - /*! \brief 4 possible codecs for AMR */ - uint8_t codec[4]; - /*! \brief Number of possible codecs */ - uint8_t codecs; - /*! \brief Current uplink FT index */ - uint8_t ul_ft; - /*! \brief Current downlink FT index */ - uint8_t dl_ft; - /*! \brief Current uplink CMR index */ - uint8_t ul_cmr; - /*! \brief Current downlink CMR index */ - uint8_t dl_cmr; - /*! \brief If AMR loop is enabled */ - uint8_t amr_loop; - /*! \brief Number of bit error rates */ - uint8_t ber_num; - /*! \brief Sum of bit error rates */ - float ber_sum; - } amr; - - /*! \brief A5/X encryption state */ - struct { - uint8_t key[MAX_A5_KEY_LEN]; - uint8_t key_len; - uint8_t algo; - } a5; -}; - -struct trx_ts { - /*! \brief Timeslot index within a frame (0..7) */ - uint8_t index; - /*! \brief Last received frame number */ - uint32_t mf_last_fn; - - /*! \brief Pointer to multiframe layout */ - const struct trx_multiframe *mf_layout; - /*! \brief Channel states for logical channels */ - struct llist_head lchans; - /*! \brief Queue primitives for TX */ - struct llist_head tx_prims; -}; - -/* Represents one TX primitive in the queue of trx_ts */ -struct trx_ts_prim { - /*! \brief Link to queue of TS */ - struct llist_head list; - /*! \brief Logical channel type */ - enum trx_lchan_type chan; - /*! \brief Payload length */ - size_t payload_len; - /*! \brief Payload */ - uint8_t payload[0]; -}; - -extern const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX]; -const struct trx_multiframe *sched_mframe_layout( - enum gsm_phys_chan_config config, int tn); - -/* Scheduler management functions */ -int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance); -int sched_trx_reset(struct trx_instance *trx, bool reset_clock); -int sched_trx_shutdown(struct trx_instance *trx); - -/* Timeslot management functions */ -struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn); -void sched_trx_del_ts(struct trx_instance *trx, int tn); -int sched_trx_reset_ts(struct trx_instance *trx, int tn); -int sched_trx_configure_ts(struct trx_instance *trx, int tn, - enum gsm_phys_chan_config config); -int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo, - uint8_t *key, uint8_t key_len); - -/* Logical channel management functions */ -enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr); -enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr, - uint8_t link_id); - -void sched_trx_deactivate_all_lchans(struct trx_ts *ts); -int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode); -int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan); -int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan); -struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts, - enum trx_lchan_type chan); - -/* Primitive management functions */ -int sched_prim_init(void *ctx, struct trx_ts_prim **prim, - size_t pl_len, uint8_t chan_nr, uint8_t link_id); -int sched_prim_push(struct trx_instance *trx, - struct trx_ts_prim *prim, uint8_t chan_nr); - -#define TCH_MODE_IS_SPEECH(mode) \ - (mode == GSM48_CMODE_SPEECH_V1 \ - || mode == GSM48_CMODE_SPEECH_EFR \ - || mode == GSM48_CMODE_SPEECH_AMR) - -#define TCH_MODE_IS_DATA(mode) \ - (mode == GSM48_CMODE_DATA_14k5 \ - || mode == GSM48_CMODE_DATA_12k0 \ - || mode == GSM48_CMODE_DATA_6k0 \ - || mode == GSM48_CMODE_DATA_3k6) - -#define CHAN_IS_TCH(chan) \ - (chan == TRXC_TCHF || chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1) - -#define CHAN_IS_SACCH(chan) \ - (trx_lchan_desc[chan].link_id & TRX_CH_LID_SACCH) - -/* FIXME: we need a better way to identify / distinguish primitives */ -#define PRIM_IS_RACH11(prim) \ - (prim->payload_len == sizeof(struct l1ctl_ext_rach_req)) - -#define PRIM_IS_RACH8(prim) \ - (prim->payload_len == sizeof(struct l1ctl_rach_req)) - -#define PRIM_IS_RACH(prim) \ - (PRIM_IS_RACH8(prim) || PRIM_IS_RACH11(prim)) - -#define PRIM_IS_TCH(prim) \ - (CHAN_IS_TCH(prim->chan) && prim->payload_len != GSM_MACBLOCK_LEN) - -#define PRIM_IS_FACCH(prim) \ - (CHAN_IS_TCH(prim->chan) && prim->payload_len == GSM_MACBLOCK_LEN) - -struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue, - uint32_t fn, struct trx_lchan_state *lchan); -int sched_prim_dummy(struct trx_lchan_state *lchan); -void sched_prim_drop(struct trx_lchan_state *lchan); -void sched_prim_flush_queue(struct llist_head *list); - -int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn, - uint32_t burst_fn, sbit_t *bits, uint16_t nbits, - int8_t rssi, int16_t toa256); -int sched_trx_handle_tx_burst(struct trx_instance *trx, - struct trx_ts *ts, struct trx_lchan_state *lchan, - uint32_t fn, ubit_t *bits); - -/* Shared declarations for lchan handlers */ -extern const uint8_t sched_nb_training_bits[8][26]; - -size_t sched_bad_frame_ind(uint8_t *l2, struct trx_lchan_state *lchan); -int sched_send_dt_ind(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint8_t *l2, size_t l2_len, - int bit_error_count, bool dec_failed, bool traffic); -int sched_send_dt_conf(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, bool traffic); - -/* Interleaved TCH/H block TDMA frame mapping */ -uint32_t sched_tchh_block_dl_first_fn(enum trx_lchan_type chan, - uint32_t last_fn, bool facch); -bool sched_tchh_block_map_fn(enum trx_lchan_type chan, - uint32_t fn, bool ul, bool facch, bool start); - -#define sched_tchh_traffic_start(chan, fn, ul) \ - sched_tchh_block_map_fn(chan, fn, ul, 0, 1) -#define sched_tchh_traffic_end(chan, fn, ul) \ - sched_tchh_block_map_fn(chan, fn, ul, 0, 0) - -#define sched_tchh_facch_start(chan, fn, ul) \ - sched_tchh_block_map_fn(chan, fn, ul, 1, 1) -#define sched_tchh_facch_end(chan, fn, ul) \ - sched_tchh_block_map_fn(chan, fn, ul, 1, 0) diff --git a/src/host/trxcon/scheduler.h b/src/host/trxcon/scheduler.h deleted file mode 100644 index 7ab17ab5..00000000 --- a/src/host/trxcon/scheduler.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include <stdint.h> -#include <time.h> - -#include <osmocom/core/timer.h> - -#define FRAME_DURATION_uS 4615 - -#define GSM_SUPERFRAME (26 * 51) -#define GSM_HYPERFRAME (2048 * GSM_SUPERFRAME) - -/* TDMA frame number arithmetics */ -#define TDMA_FN_SUM(a, b) \ - ((a + b) % GSM_HYPERFRAME) -#define TDMA_FN_SUB(a, b) \ - ((a + GSM_HYPERFRAME - b) % GSM_HYPERFRAME) -#define TDMA_FN_INC(fn) \ - TDMA_FN_SUM(fn, 1) -#define TDMA_FN_MIN(a, b) \ - (a < b ? a : b) -#define TDMA_FN_DIFF(a, b) \ - TDMA_FN_MIN(TDMA_FN_SUB(a, b), TDMA_FN_SUB(b, a)) - -enum tdma_sched_clck_state { - SCH_CLCK_STATE_WAIT, - SCH_CLCK_STATE_OK, -}; - -/* Forward structure declaration */ -struct trx_sched; - -/*! \brief One scheduler instance */ -struct trx_sched { - /*! \brief Clock state */ - uint8_t state; - /*! \brief Local clock source */ - struct timespec clock; - /*! \brief Count of processed frames */ - uint32_t fn_counter_proc; - /*! \brief Local frame counter advance */ - uint32_t fn_counter_advance; - /*! \brief Frame counter */ - uint32_t fn_counter_lost; - /*! \brief Frame callback timer */ - struct osmo_timer_list clock_timer; - /*! \brief Frame callback */ - void (*clock_cb)(struct trx_sched *sched); - /*! \brief Private data (e.g. pointer to trx instance) */ - void *data; -}; - -int sched_clck_handle(struct trx_sched *sched, uint32_t fn); -void sched_clck_reset(struct trx_sched *sched); diff --git a/src/host/trxcon/src/Makefile.am b/src/host/trxcon/src/Makefile.am new file mode 100644 index 00000000..7be7de62 --- /dev/null +++ b/src/host/trxcon/src/Makefile.am @@ -0,0 +1,64 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOCODING_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(NULL) + + +noinst_LTLIBRARIES = libl1sched.la + +libl1sched_la_SOURCES = \ + sched_lchan_common.c \ + sched_lchan_pdtch.c \ + sched_lchan_desc.c \ + sched_lchan_xcch.c \ + sched_lchan_tchf.c \ + sched_lchan_tchh.c \ + sched_lchan_rach.c \ + sched_lchan_sch.c \ + sched_mframe.c \ + sched_prim.c \ + sched_trx.c \ + $(NULL) + + +noinst_LTLIBRARIES += libl1gprs.la + +libl1gprs_la_SOURCES = \ + l1gprs.c \ + $(NULL) + + +noinst_LTLIBRARIES += libtrxcon.la + +libtrxcon_la_SOURCES = \ + trxcon_inst.c \ + trxcon_fsm.c \ + trxcon_shim.c \ + l1ctl.c \ + $(NULL) + + +bin_PROGRAMS = trxcon + +trxcon_SOURCES = \ + l1ctl_server.c \ + trxcon_main.c \ + logging.c \ + trx_if.c \ + $(NULL) + +trxcon_LDADD = \ + libtrxcon.la \ + libl1sched.la \ + libl1gprs.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCODING_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) diff --git a/src/host/trxcon/src/l1ctl.c b/src/host/trxcon/src/l1ctl.c new file mode 100644 index 00000000..0f6bd1be --- /dev/null +++ b/src/host/trxcon/src/l1ctl.c @@ -0,0 +1,849 @@ +/* + * OsmocomBB <-> SDR connection bridge + * GSM L1 control interface handlers + * + * (C) 2014 by Sylvain Munaut <tnt@246tNt.com> + * (C) 2016-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * Contributions by sysmocom - s.f.m.c. GmbH + * + * 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. + * + */ + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> + +#include <arpa/inet.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +#include <osmocom/bb/l1ctl_proto.h> +#include <osmocom/bb/trxcon/logging.h> +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> + +#define L1CTL_LENGTH 512 +#define L1CTL_HEADROOM 32 + +/* Logging categories configurable via trxcon_set_log_cfg() */ +int g_logc_l1c = DLGLOBAL; +int g_logc_l1d = DLGLOBAL; + +static const struct value_string l1ctl_ccch_mode_names[] = { + { CCCH_MODE_NONE, "NONE" }, + { CCCH_MODE_NON_COMBINED, "NON_COMBINED" }, + { CCCH_MODE_COMBINED, "COMBINED" }, + { CCCH_MODE_COMBINED_CBCH, "COMBINED_CBCH" }, + { 0, NULL }, +}; + +static const struct value_string l1ctl_reset_names[] = { + { L1CTL_RES_T_BOOT, "BOOT" }, + { L1CTL_RES_T_FULL, "FULL" }, + { L1CTL_RES_T_SCHED, "SCHED" }, + { 0, NULL }, +}; + +static const char *arfcn2band_name(uint16_t arfcn) +{ + enum gsm_band band; + + if (gsm_arfcn2band_rc(arfcn, &band) < 0) + return "(invalid)"; + + return gsm_band_name(band); +} + +static struct msgb *l1ctl_alloc_msg(uint8_t msg_type) +{ + struct l1ctl_hdr *l1h; + struct msgb *msg; + + /** + * Each L1CTL message gets its own length pushed in front + * before sending. This is why we need this small headroom. + */ + msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM, + L1CTL_HEADROOM, "l1ctl_tx_msg"); + if (!msg) { + LOGP(g_logc_l1c, LOGL_ERROR, "Failed to allocate memory\n"); + return NULL; + } + + msg->l1h = msgb_put(msg, sizeof(*l1h)); + l1h = (struct l1ctl_hdr *) msg->l1h; + l1h->msg_type = msg_type; + + return msg; +} + +int l1ctl_tx_pm_conf(struct trxcon_inst *trxcon, uint16_t band_arfcn, int dbm, int last) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + struct l1ctl_pm_conf *pmc; + struct msgb *msg; + + msg = l1ctl_alloc_msg(L1CTL_PM_CONF); + if (!msg) + return -ENOMEM; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_DEBUG, + "Send PM Conf (%s %d = %d dBm)\n", + arfcn2band_name(band_arfcn), + band_arfcn & ~ARFCN_FLAG_MASK, dbm); + + pmc = (struct l1ctl_pm_conf *) msgb_put(msg, sizeof(*pmc)); + pmc->band_arfcn = htons(band_arfcn); + pmc->pm[0] = dbm2rxlev(dbm); + pmc->pm[1] = 0; + + if (last) { + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->l1h; + l1h->flags |= L1CTL_F_DONE; + } + + return trxcon_l1ctl_send(trxcon, msg); +} + +int l1ctl_tx_reset_ind(struct trxcon_inst *trxcon, uint8_t type) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + struct msgb *msg; + struct l1ctl_reset *res; + + msg = l1ctl_alloc_msg(L1CTL_RESET_IND); + if (!msg) + return -ENOMEM; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_DEBUG, "Send Reset Ind (%s)\n", + get_value_string(l1ctl_reset_names, type)); + + res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res)); + res->type = type; + + return trxcon_l1ctl_send(trxcon, msg); +} + +int l1ctl_tx_reset_conf(struct trxcon_inst *trxcon, uint8_t type) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + struct msgb *msg; + struct l1ctl_reset *res; + + msg = l1ctl_alloc_msg(L1CTL_RESET_CONF); + if (!msg) + return -ENOMEM; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_DEBUG, "Send Reset Conf (%s)\n", + get_value_string(l1ctl_reset_names, type)); + res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res)); + res->type = type; + + return trxcon_l1ctl_send(trxcon, msg); +} + +static struct l1ctl_info_dl *put_dl_info_hdr(struct msgb *msg, + const struct l1ctl_info_dl *dl_info) +{ + size_t len = sizeof(struct l1ctl_info_dl); + struct l1ctl_info_dl *dl = (struct l1ctl_info_dl *) msgb_put(msg, len); + + if (dl_info) /* Copy DL info provided by handler */ + memcpy(dl, dl_info, len); + else /* Init DL info header */ + memset(dl, 0x00, len); + + return dl; +} + +/* Fill in FBSB payload: BSIC and sync result */ +static struct l1ctl_fbsb_conf *fbsb_conf_make(struct msgb *msg, uint8_t result, uint8_t bsic) +{ + struct l1ctl_fbsb_conf *conf = (struct l1ctl_fbsb_conf *) msgb_put(msg, sizeof(*conf)); + + conf->result = result; + conf->bsic = bsic; + + return conf; +} + +int l1ctl_tx_fbsb_fail(struct trxcon_inst *trxcon, uint16_t band_arfcn) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + struct l1ctl_info_dl *dl; + struct msgb *msg; + + msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF); + if (msg == NULL) + return -ENOMEM; + + dl = put_dl_info_hdr(msg, NULL); + + /* Fill in current ARFCN */ + dl->band_arfcn = htons(band_arfcn); + + fbsb_conf_make(msg, 255, 0); + + LOGPFSMSL(fi, g_logc_l1c, LOGL_DEBUG, "Send FBSB Conf (timeout)\n"); + + return trxcon_l1ctl_send(trxcon, msg); +} + +int l1ctl_tx_fbsb_conf(struct trxcon_inst *trxcon, uint16_t band_arfcn, uint8_t bsic) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + struct l1ctl_fbsb_conf *conf; + struct l1ctl_info_dl *dl; + struct msgb *msg; + + msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF); + if (msg == NULL) + return -ENOMEM; + + dl = put_dl_info_hdr(msg, NULL); + + /* Fill in current ARFCN */ + dl->band_arfcn = htons(band_arfcn); + + conf = fbsb_conf_make(msg, 0, bsic); + + /* FIXME: set proper value */ + conf->initial_freq_err = 0; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_DEBUG, + "Send FBSB Conf (result=%u, bsic=%u)\n", + conf->result, conf->bsic); + + return trxcon_l1ctl_send(trxcon, msg); +} + +int l1ctl_tx_ccch_mode_conf(struct trxcon_inst *trxcon, uint8_t mode) +{ + struct l1ctl_ccch_mode_conf *conf; + struct msgb *msg; + + msg = l1ctl_alloc_msg(L1CTL_CCCH_MODE_CONF); + if (msg == NULL) + return -ENOMEM; + + conf = (struct l1ctl_ccch_mode_conf *) msgb_put(msg, sizeof(*conf)); + conf->ccch_mode = mode; + + return trxcon_l1ctl_send(trxcon, msg); +} + +/** + * Handles both L1CTL_DATA_IND and L1CTL_TRAFFIC_IND. + */ +int l1ctl_tx_dt_ind(struct trxcon_inst *trxcon, + const struct trxcon_param_rx_data_ind *ind) +{ + struct msgb *msg; + + msg = l1ctl_alloc_msg(ind->traffic ? L1CTL_TRAFFIC_IND : L1CTL_DATA_IND); + if (msg == NULL) + return -ENOMEM; + + const struct l1ctl_info_dl dl_hdr = { + .chan_nr = ind->chan_nr, + .link_id = ind->link_id, + .frame_nr = htonl(ind->frame_nr), + .band_arfcn = htons(ind->band_arfcn), + .fire_crc = ind->data_len > 0 ? 0 : 2, + .rx_level = dbm2rxlev(ind->rssi), + .num_biterr = ind->n_errors, + /* TODO: set proper .snr */ + }; + + put_dl_info_hdr(msg, &dl_hdr); + + /* Copy the L2 payload if preset */ + if (ind->data && ind->data_len > 0) + memcpy(msgb_put(msg, ind->data_len), ind->data, ind->data_len); + + /* Put message to upper layers */ + return trxcon_l1ctl_send(trxcon, msg); +} + +int l1ctl_tx_rach_conf(struct trxcon_inst *trxcon, + const struct trxcon_param_tx_access_burst_cnf *cnf) +{ + struct msgb *msg; + + msg = l1ctl_alloc_msg(L1CTL_RACH_CONF); + if (msg == NULL) + return -ENOMEM; + + const struct l1ctl_info_dl dl_hdr = { + .band_arfcn = htons(cnf->band_arfcn), + .frame_nr = htonl(cnf->frame_nr), + }; + + put_dl_info_hdr(msg, &dl_hdr); + + return trxcon_l1ctl_send(trxcon, msg); +} + + +/** + * Handles both L1CTL_DATA_CONF and L1CTL_TRAFFIC_CONF. + */ +int l1ctl_tx_dt_conf(struct trxcon_inst *trxcon, + const struct trxcon_param_tx_data_cnf *cnf) +{ + struct msgb *msg; + + msg = l1ctl_alloc_msg(cnf->traffic ? L1CTL_TRAFFIC_CONF : L1CTL_DATA_CONF); + if (msg == NULL) + return -ENOMEM; + + const struct l1ctl_info_dl dl_hdr = { + .chan_nr = cnf->chan_nr, + .link_id = cnf->link_id, + .frame_nr = htonl(cnf->frame_nr), + .band_arfcn = htons(cnf->band_arfcn), + }; + + /* Copy DL frame header from source message */ + put_dl_info_hdr(msg, &dl_hdr); + + return trxcon_l1ctl_send(trxcon, msg); +} + +static enum gsm_phys_chan_config l1ctl_ccch_mode2pchan_config(enum ccch_mode mode) +{ + switch (mode) { + /* TODO: distinguish extended BCCH */ + case CCCH_MODE_NON_COMBINED: + case CCCH_MODE_NONE: + return GSM_PCHAN_CCCH; + + case CCCH_MODE_COMBINED: + return GSM_PCHAN_CCCH_SDCCH4; + case CCCH_MODE_COMBINED_CBCH: + return GSM_PCHAN_CCCH_SDCCH4_CBCH; + + default: + LOGP(g_logc_l1c, LOGL_NOTICE, "Undandled CCCH mode (%u), " + "assuming non-combined configuration\n", mode); + return GSM_PCHAN_CCCH; + } +} + +static int l1ctl_rx_fbsb_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_fbsb_req *fbsb; + int rc = 0; + + fbsb = (const struct l1ctl_fbsb_req *)msg->l1h; + if (msgb_l1len(msg) < sizeof(*fbsb)) { + LOGPFSMSL(fi, g_logc_l1c, LOGL_ERROR, + "MSG too short FBSB Req: %u\n", + msgb_l1len(msg)); + rc = -EINVAL; + goto exit; + } + + struct trxcon_param_fbsb_search_req req = { + .pchan_config = l1ctl_ccch_mode2pchan_config(fbsb->ccch_mode), + .timeout_fns = ntohs(fbsb->timeout), + .band_arfcn = ntohs(fbsb->band_arfcn), + }; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "Received FBSB request (%s %d, timeout %u TDMA FNs)\n", + arfcn2band_name(req.band_arfcn), + req.band_arfcn & ~ARFCN_FLAG_MASK, + req.timeout_fns); + + osmo_fsm_inst_dispatch(fi, TRXCON_EV_FBSB_SEARCH_REQ, &req); + +exit: + msgb_free(msg); + return rc; +} + +static int l1ctl_rx_pm_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_pm_req *pmr; + int rc = 0; + + pmr = (const struct l1ctl_pm_req *)msg->l1h; + if (msgb_l1len(msg) < sizeof(*pmr)) { + LOGPFSMSL(fi, g_logc_l1c, LOGL_ERROR, + "MSG too short PM Req: %u\n", + msgb_l1len(msg)); + rc = -EINVAL; + goto exit; + } + + struct trxcon_param_full_power_scan_req req = { + .band_arfcn_start = ntohs(pmr->range.band_arfcn_from), + .band_arfcn_stop = ntohs(pmr->range.band_arfcn_to), + }; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "Received power measurement request (%s: %d -> %d)\n", + arfcn2band_name(req.band_arfcn_start), + req.band_arfcn_start & ~ARFCN_FLAG_MASK, + req.band_arfcn_stop & ~ARFCN_FLAG_MASK); + + osmo_fsm_inst_dispatch(fi, TRXCON_EV_FULL_POWER_SCAN_REQ, &req); + +exit: + msgb_free(msg); + return rc; +} + +static int l1ctl_rx_reset_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_reset *res; + int rc = 0; + + res = (const struct l1ctl_reset *)msg->l1h; + if (msgb_l1len(msg) < sizeof(*res)) { + LOGPFSMSL(fi, g_logc_l1c, LOGL_ERROR, + "MSG too short Reset Req: %u\n", + msgb_l1len(msg)); + rc = -EINVAL; + goto exit; + } + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "Received reset request (%s)\n", + get_value_string(l1ctl_reset_names, res->type)); + + switch (res->type) { + case L1CTL_RES_T_FULL: + osmo_fsm_inst_dispatch(fi, TRXCON_EV_RESET_FULL_REQ, NULL); + break; + case L1CTL_RES_T_SCHED: + osmo_fsm_inst_dispatch(fi, TRXCON_EV_RESET_SCHED_REQ, NULL); + break; + default: + LOGPFSMSL(fi, g_logc_l1c, LOGL_ERROR, + "Unknown L1CTL_RESET_REQ type\n"); + goto exit; + } + + /* Confirm */ + rc = l1ctl_tx_reset_conf(trxcon, res->type); + +exit: + msgb_free(msg); + return rc; +} + +static int l1ctl_rx_echo_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + struct l1ctl_hdr *l1h; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, "Recv Echo Req\n"); + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, "Send Echo Conf\n"); + + /* Nothing to do, just send it back */ + l1h = (struct l1ctl_hdr *) msg->l1h; + l1h->msg_type = L1CTL_ECHO_CONF; + msg->data = msg->l1h; + + return trxcon_l1ctl_send(trxcon, msg); +} + +static int l1ctl_rx_ccch_mode_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_ccch_mode_req *mode_req; + int rc; + + mode_req = (const struct l1ctl_ccch_mode_req *)msg->l1h; + if (msgb_l1len(msg) < sizeof(*mode_req)) { + LOGPFSMSL(fi, g_logc_l1c, LOGL_ERROR, + "MSG too short Reset Req: %u\n", + msgb_l1len(msg)); + rc = -EINVAL; + goto exit; + } + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, "Received CCCH mode request (%s)\n", + get_value_string(l1ctl_ccch_mode_names, mode_req->ccch_mode)); + + struct trxcon_param_set_ccch_tch_mode_req req = { + /* Choose corresponding channel combination */ + .mode = l1ctl_ccch_mode2pchan_config(mode_req->ccch_mode), + }; + + rc = osmo_fsm_inst_dispatch(fi, TRXCON_EV_SET_CCCH_MODE_REQ, &req); + if (rc == 0 && req.applied) + l1ctl_tx_ccch_mode_conf(trxcon, mode_req->ccch_mode); + +exit: + msgb_free(msg); + return rc; +} + +static int l1ctl_rx_rach_req(struct trxcon_inst *trxcon, struct msgb *msg, bool is_11bit) +{ + struct trxcon_param_tx_access_burst_req req; + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_info_ul *ul; + + ul = (const struct l1ctl_info_ul *)msg->l1h; + + if (is_11bit) { + const struct l1ctl_ext_rach_req *rr = (void *)ul->payload; + + req = (struct trxcon_param_tx_access_burst_req) { + .offset = ntohs(rr->offset), + .synch_seq = rr->synch_seq, + .ra = ntohs(rr->ra11), + .is_11bit = true, + }; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "Received 11-bit RACH request " + "(offset=%u, synch_seq=%u, ra11=0x%02hx)\n", + req.offset, req.synch_seq, req.ra); + } else { + const struct l1ctl_rach_req *rr = (void *)ul->payload; + + req = (struct trxcon_param_tx_access_burst_req) { + .offset = ntohs(rr->offset), + .ra = rr->ra, + }; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "Received 8-bit RACH request " + "(offset=%u, ra=0x%02x)\n", req.offset, req.ra); + } + + /* The controlling L1CTL side always does include the UL info header, + * but may leave it empty. We assume RACH is on TS0 in this case. */ + if (ul->chan_nr == 0x00) { + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "The UL info header is empty, assuming RACH is on TS0\n"); + req.chan_nr = RSL_CHAN_RACH; + req.link_id = 0x00; + } else { + req.chan_nr = ul->chan_nr; + req.link_id = ul->link_id; + } + + osmo_fsm_inst_dispatch(fi, TRXCON_EV_TX_ACCESS_BURST_REQ, &req); + + msgb_free(msg); + return 0; +} + +static int l1ctl_proc_est_req_h0(struct osmo_fsm_inst *fi, + struct trxcon_param_dch_est_req *req, + const struct l1ctl_h0 *h) +{ + req->h0.band_arfcn = ntohs(h->band_arfcn); + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "L1CTL_DM_EST_REQ indicates single ARFCN %s %u\n", + arfcn2band_name(req->h0.band_arfcn), + req->h0.band_arfcn & ~ARFCN_FLAG_MASK); + + return 0; +} + +static int l1ctl_proc_est_req_h1(struct osmo_fsm_inst *fi, + struct trxcon_param_dch_est_req *req, + const struct l1ctl_h1 *h) +{ + unsigned int i; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "L1CTL_DM_EST_REQ indicates a Frequency " + "Hopping (hsn=%u, maio=%u, chans=%u) channel\n", + h->hsn, h->maio, h->n); + + /* No channels?!? */ + if (!h->n) { + LOGPFSMSL(fi, g_logc_l1c, LOGL_ERROR, + "No channels in mobile allocation?!?\n"); + return -EINVAL; + } else if (h->n > ARRAY_SIZE(h->ma)) { + LOGPFSMSL(fi, g_logc_l1c, LOGL_ERROR, + "More than 64 channels in mobile allocation?!?\n"); + return -EINVAL; + } + + /* Convert from network to host byte order */ + for (i = 0; i < h->n; i++) + req->h1.ma[i] = ntohs(h->ma[i]); + req->h1.n = h->n; + req->h1.hsn = h->hsn; + req->h1.maio = h->maio; + + return 0; +} + +static int l1ctl_rx_dm_est_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_dm_est_req *est_req; + const struct l1ctl_info_ul *ul; + int rc; + + ul = (const struct l1ctl_info_ul *)msg->l1h; + est_req = (const struct l1ctl_dm_est_req *)ul->payload; + + struct trxcon_param_dch_est_req req = { + .chan_nr = ul->chan_nr, + .tch_mode = est_req->tch_mode, + .tsc = est_req->tsc, + .hopping = est_req->h, + }; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "Received L1CTL_DM_EST_REQ " + "(tn=%u, chan_nr=0x%02x, tsc=%u, tch_mode=%s)\n", + req.chan_nr & 0x07, req.chan_nr, req.tsc, + gsm48_chan_mode_name(est_req->tch_mode)); + + /* Frequency hopping? */ + if (est_req->h) + rc = l1ctl_proc_est_req_h1(fi, &req, &est_req->h1); + else /* Single ARFCN */ + rc = l1ctl_proc_est_req_h0(fi, &req, &est_req->h0); + if (rc) + goto exit; + + osmo_fsm_inst_dispatch(fi, TRXCON_EV_DCH_EST_REQ, &req); + +exit: + msgb_free(msg); + return rc; +} + +static int l1ctl_rx_dm_rel_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, "Received L1CTL_DM_REL_REQ\n"); + + osmo_fsm_inst_dispatch(fi, TRXCON_EV_DCH_REL_REQ, NULL); + + msgb_free(msg); + return 0; +} + +/** + * Handles both L1CTL_DATA_REQ and L1CTL_TRAFFIC_REQ. + */ +static int l1ctl_rx_dt_req(struct trxcon_inst *trxcon, struct msgb *msg, bool traffic) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_info_ul *ul; + + /* Extract UL frame header */ + ul = (const struct l1ctl_info_ul *)msg->l1h; + msg->l2h = (uint8_t *)ul->payload; + + struct trxcon_param_tx_data_req req = { + .traffic = traffic, + .chan_nr = ul->chan_nr, + .link_id = ul->link_id & 0x40, + .data_len = msgb_l2len(msg), + .data = ul->payload, + }; + + LOGPFSMSL(fi, g_logc_l1d, LOGL_DEBUG, + "Recv %s Req (chan_nr=0x%02x, link_id=0x%02x, len=%zu)\n", + traffic ? "TRAFFIC" : "DATA", req.chan_nr, req.link_id, req.data_len); + + switch (fi->state) { + case TRXCON_ST_DEDICATED: + osmo_fsm_inst_dispatch(fi, TRXCON_EV_TX_DATA_REQ, &req); + break; + default: + if (!traffic && req.link_id == 0x40) /* only for SACCH */ + osmo_fsm_inst_dispatch(fi, TRXCON_EV_UPDATE_SACCH_CACHE_REQ, &req); + /* TODO: log an error about uhnandled DATA.req / TRAFFIC.req */ + } + + msgb_free(msg); + return 0; +} + +static int l1ctl_rx_param_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_par_req *par_req; + const struct l1ctl_info_ul *ul; + + ul = (const struct l1ctl_info_ul *)msg->l1h; + par_req = (const struct l1ctl_par_req *)ul->payload; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "Received L1CTL_PARAM_REQ (ta=%d, tx_power=%u)\n", + par_req->ta, par_req->tx_power); + + struct trxcon_param_set_phy_config_req req = { + .type = TRXCON_PHY_CFGT_TX_PARAMS, + .tx_params = { + .timing_advance = par_req->ta, + .tx_power = par_req->tx_power, + } + }; + + osmo_fsm_inst_dispatch(fi, TRXCON_EV_SET_PHY_CONFIG_REQ, &req); + + msgb_free(msg); + return 0; +} + +static int l1ctl_rx_tch_mode_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_tch_mode_req *mode_req; + int rc; + + mode_req = (const struct l1ctl_tch_mode_req *)msg->l1h; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "Received L1CTL_TCH_MODE_REQ (tch_mode=%s, audio_mode=%u)\n", + gsm48_chan_mode_name(mode_req->tch_mode), mode_req->audio_mode); + + /* TODO: do we need to care about audio_mode? */ + + struct trxcon_param_set_ccch_tch_mode_req req = { + .mode = mode_req->tch_mode, + }; + if (mode_req->tch_mode == GSM48_CMODE_SPEECH_AMR) { + req.amr.start_codec = mode_req->amr.start_codec; + req.amr.codecs_bitmask = mode_req->amr.codecs_bitmask; + } + + rc = osmo_fsm_inst_dispatch(fi, TRXCON_EV_SET_TCH_MODE_REQ, &req); + if (rc != 0 || !req.applied) { + talloc_free(msg); + return rc; + } + + /* Re-use the original message as confirmation */ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + l1h->msg_type = L1CTL_TCH_MODE_CONF; + + return trxcon_l1ctl_send(trxcon, msg); +} + +static int l1ctl_rx_crypto_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_crypto_req *cr; + const struct l1ctl_info_ul *ul; + + ul = (const struct l1ctl_info_ul *)msg->l1h; + cr = (const struct l1ctl_crypto_req *)ul->payload; + + struct trxcon_param_crypto_req req = { + .chan_nr = ul->chan_nr, + .a5_algo = cr->algo, + .key_len = cr->key_len, + .key = cr->key, + }; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "L1CTL_CRYPTO_REQ (algo=A5/%u, key_len=%u)\n", + req.a5_algo, req.key_len); + + osmo_fsm_inst_dispatch(fi, TRXCON_EV_CRYPTO_REQ, &req); + + msgb_free(msg); + return 0; +} + +int trxcon_l1ctl_receive(struct trxcon_inst *trxcon, struct msgb *msg) +{ + const struct l1ctl_hdr *l1h; + int rc; + + l1h = (const struct l1ctl_hdr *)msg->l1h; + msg->l1h = (uint8_t *)l1h->data; + + switch (l1h->msg_type) { + case L1CTL_FBSB_REQ: + return l1ctl_rx_fbsb_req(trxcon, msg); + case L1CTL_PM_REQ: + return l1ctl_rx_pm_req(trxcon, msg); + case L1CTL_RESET_REQ: + return l1ctl_rx_reset_req(trxcon, msg); + case L1CTL_ECHO_REQ: + return l1ctl_rx_echo_req(trxcon, msg); + case L1CTL_CCCH_MODE_REQ: + return l1ctl_rx_ccch_mode_req(trxcon, msg); + case L1CTL_RACH_REQ: + return l1ctl_rx_rach_req(trxcon, msg, false); + case L1CTL_EXT_RACH_REQ: + return l1ctl_rx_rach_req(trxcon, msg, true); + case L1CTL_DM_EST_REQ: + return l1ctl_rx_dm_est_req(trxcon, msg); + case L1CTL_DM_REL_REQ: + return l1ctl_rx_dm_rel_req(trxcon, msg); + case L1CTL_DATA_REQ: + return l1ctl_rx_dt_req(trxcon, msg, false); + case L1CTL_TRAFFIC_REQ: + return l1ctl_rx_dt_req(trxcon, msg, true); + case L1CTL_PARAM_REQ: + return l1ctl_rx_param_req(trxcon, msg); + case L1CTL_TCH_MODE_REQ: + return l1ctl_rx_tch_mode_req(trxcon, msg); + case L1CTL_CRYPTO_REQ: + return l1ctl_rx_crypto_req(trxcon, msg); + case L1CTL_GPRS_UL_TBF_CFG_REQ: + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_GPRS_UL_TBF_CFG_REQ, msg); + msgb_free(msg); + return rc; + case L1CTL_GPRS_DL_TBF_CFG_REQ: + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_GPRS_DL_TBF_CFG_REQ, msg); + msgb_free(msg); + return rc; + case L1CTL_GPRS_UL_BLOCK_REQ: + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_GPRS_UL_BLOCK_REQ, msg); + msgb_free(msg); + return rc; + /* Not (yet) handled messages */ + case L1CTL_NEIGH_PM_REQ: + case L1CTL_DM_FREQ_REQ: + case L1CTL_SIM_REQ: + LOGPFSMSL(trxcon->fi, g_logc_l1c, LOGL_NOTICE, + "Ignoring unsupported message (type=%u)\n", + l1h->msg_type); + msgb_free(msg); + return -ENOTSUP; + default: + LOGPFSMSL(trxcon->fi, g_logc_l1c, LOGL_ERROR, "Unknown MSG type %u: %s\n", + l1h->msg_type, osmo_hexdump(msgb_data(msg), msgb_length(msg))); + msgb_free(msg); + return -EINVAL; + } +} diff --git a/src/host/trxcon/src/l1ctl_server.c b/src/host/trxcon/src/l1ctl_server.c new file mode 100644 index 00000000..c0f10158 --- /dev/null +++ b/src/host/trxcon/src/l1ctl_server.c @@ -0,0 +1,282 @@ +/* + * OsmocomBB <-> SDR connection bridge + * UNIX socket server for L1CTL + * + * (C) 2013 by Sylvain Munaut <tnt@246tNt.com> + * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com> + * (C) 2022 by by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * 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. + * + */ + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#include <sys/un.h> +#include <arpa/inet.h> +#include <sys/socket.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/write_queue.h> + +#include <osmocom/bb/trxcon/logging.h> +#include <osmocom/bb/trxcon/l1ctl_server.h> + +#define LOGP_CLI(cli, cat, level, fmt, args...) \ + LOGP(cat, level, "%s" fmt, (cli)->log_prefix, ## args) + +static int l1ctl_client_read_cb(struct osmo_fd *ofd) +{ + struct l1ctl_client *client = (struct l1ctl_client *)ofd->data; + struct msgb *msg; + uint16_t len; + int rc; + + /* Attempt to read from socket */ + rc = read(ofd->fd, &len, L1CTL_MSG_LEN_FIELD); + if (rc != L1CTL_MSG_LEN_FIELD) { + if (rc <= 0) { + LOGP_CLI(client, DL1D, LOGL_NOTICE, + "L1CTL connection error: read() failed (rc=%d): %s\n", + rc, strerror(errno)); + } else { + LOGP_CLI(client, DL1D, LOGL_NOTICE, + "L1CTL connection error: short read\n"); + rc = -EIO; + } + l1ctl_client_conn_close(client); + return -EBADF; /* client fd is gone, avoid processing any other events. */ + } + + /* Check message length */ + len = ntohs(len); + if (len > L1CTL_LENGTH) { + LOGP_CLI(client, DL1D, LOGL_ERROR, "Length is too big: %u\n", len); + return -EINVAL; + } + + /* Allocate a new msg */ + msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM, + L1CTL_HEADROOM, "l1ctl_rx_msg"); + if (!msg) { + LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to allocate msg\n"); + return -ENOMEM; + } + + msg->l1h = msgb_put(msg, len); + rc = read(ofd->fd, msg->l1h, msgb_l1len(msg)); + if (rc != len) { + LOGP_CLI(client, DL1D, LOGL_ERROR, + "Can not read data: len=%d < rc=%d: %s\n", + len, rc, strerror(errno)); + msgb_free(msg); + return rc; + } + + /* Debug print */ + LOGP_CLI(client, DL1D, LOGL_DEBUG, "RX: '%s'\n", osmo_hexdump(msg->data, msg->len)); + + /* Call L1CTL handler */ + client->server->cfg->conn_read_cb(client, msg); + + return 0; +} + +static int l1ctl_client_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + struct l1ctl_client *client = (struct l1ctl_client *)ofd->data; + int len; + + if (ofd->fd <= 0) + return -EINVAL; + + len = write(ofd->fd, msg->data, msg->len); + if (len != msg->len) { + LOGP_CLI(client, DL1D, LOGL_ERROR, + "Failed to write data: written (%d) < msg_len (%d)\n", + len, msg->len); + return -1; + } + + return 0; +} + +/* Connection handler */ +static int l1ctl_server_conn_cb(struct osmo_fd *sfd, unsigned int flags) +{ + struct l1ctl_server *server = (struct l1ctl_server *)sfd->data; + struct l1ctl_client *client; + int rc, client_fd; + + client_fd = accept(sfd->fd, NULL, NULL); + if (client_fd < 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to accept() a new connection: " + "%s\n", strerror(errno)); + return client_fd; + } + + if (server->cfg->num_clients_max > 0 /* 0 means unlimited */ && + server->num_clients >= server->cfg->num_clients_max) { + LOGP(DL1C, LOGL_NOTICE, "L1CTL server cannot accept more " + "than %u connection(s)\n", server->cfg->num_clients_max); + close(client_fd); + return -ENOMEM; + } + + client = talloc_zero(server, struct l1ctl_client); + if (client == NULL) { + LOGP(DL1C, LOGL_ERROR, "Failed to allocate an L1CTL client\n"); + close(client_fd); + return -ENOMEM; + } + + /* Init the client's write queue */ + osmo_wqueue_init(&client->wq, 100); + INIT_LLIST_HEAD(&client->wq.bfd.list); + + client->wq.write_cb = &l1ctl_client_write_cb; + client->wq.read_cb = &l1ctl_client_read_cb; + osmo_fd_setup(&client->wq.bfd, client_fd, OSMO_FD_READ, &osmo_wqueue_bfd_cb, client, 0); + + /* Register the client's write queue */ + rc = osmo_fd_register(&client->wq.bfd); + if (rc != 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to register a new connection fd\n"); + close(client->wq.bfd.fd); + talloc_free(client); + return rc; + } + + llist_add_tail(&client->list, &server->clients); + client->id = server->next_client_id++; + client->server = server; + server->num_clients++; + + LOGP(DL1C, LOGL_NOTICE, "L1CTL server got a new connection (id=%u)\n", client->id); + + if (client->server->cfg->conn_accept_cb != NULL) + client->server->cfg->conn_accept_cb(client); + + return 0; +} + +int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg) +{ + uint8_t *len; + + /* Debug print */ + LOGP_CLI(client, DL1D, LOGL_DEBUG, "TX: '%s'\n", osmo_hexdump(msg->data, msg->len)); + + if (msg->l1h != msg->data) + LOGP_CLI(client, DL1D, LOGL_INFO, "Message L1 header != Message Data\n"); + + /* Prepend 16-bit length before sending */ + len = msgb_push(msg, L1CTL_MSG_LEN_FIELD); + osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len); + + if (osmo_wqueue_enqueue(&client->wq, msg) != 0) { + LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to enqueue msg!\n"); + msgb_free(msg); + return -EIO; + } + + return 0; +} + +void l1ctl_client_conn_close(struct l1ctl_client *client) +{ + struct l1ctl_server *server = client->server; + + LOGP_CLI(client, DL1C, LOGL_NOTICE, "Closing L1CTL connection\n"); + + if (server->cfg->conn_close_cb != NULL) + server->cfg->conn_close_cb(client); + + /* Close connection socket */ + osmo_fd_unregister(&client->wq.bfd); + close(client->wq.bfd.fd); + client->wq.bfd.fd = -1; + + /* Clear pending messages */ + osmo_wqueue_clear(&client->wq); + + client->server->num_clients--; + llist_del(&client->list); + talloc_free(client); + + /* If this was the last client, reset the client IDs generator to 0. + * This way avoid assigning huge unreadable client IDs like 26545. */ + if (llist_empty(&server->clients)) + server->next_client_id = 0; +} + +struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg) +{ + struct l1ctl_server *server; + int rc; + + LOGP(DL1C, LOGL_NOTICE, "Init L1CTL server (sock_path=%s)\n", cfg->sock_path); + + server = talloc(ctx, struct l1ctl_server); + OSMO_ASSERT(server != NULL); + + *server = (struct l1ctl_server) { + .clients = LLIST_HEAD_INIT(server->clients), + .cfg = cfg, + }; + + /* conn_read_cb shall not be NULL */ + OSMO_ASSERT(cfg->conn_read_cb != NULL); + + /* Bind connection handler */ + osmo_fd_setup(&server->ofd, -1, OSMO_FD_READ, &l1ctl_server_conn_cb, server, 0); + + rc = osmo_sock_unix_init_ofd(&server->ofd, SOCK_STREAM, 0, + cfg->sock_path, OSMO_SOCK_F_BIND); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n", + strerror(errno)); + talloc_free(server); + return NULL; + } + + return server; +} + +void l1ctl_server_free(struct l1ctl_server *server) +{ + LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL server\n"); + + /* Close all client connections */ + while (!llist_empty(&server->clients)) { + struct l1ctl_client *client = llist_entry(server->clients.next, + struct l1ctl_client, + list); + l1ctl_client_conn_close(client); + } + + /* Unbind listening socket */ + if (server->ofd.fd != -1) { + osmo_fd_unregister(&server->ofd); + close(server->ofd.fd); + server->ofd.fd = -1; + } + + talloc_free(server); +} diff --git a/src/host/trxcon/src/l1gprs.c b/src/host/trxcon/src/l1gprs.c new file mode 120000 index 00000000..0185f68b --- /dev/null +++ b/src/host/trxcon/src/l1gprs.c @@ -0,0 +1 @@ +../../../shared/l1gprs.c
\ No newline at end of file diff --git a/src/host/trxcon/logging.c b/src/host/trxcon/src/logging.c index 78915f21..e8730450 100644 --- a/src/host/trxcon/logging.c +++ b/src/host/trxcon/src/logging.c @@ -15,19 +15,16 @@ * 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 <osmocom/core/application.h> #include <osmocom/core/logging.h> #include <osmocom/core/utils.h> -#include "logging.h" +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/logging.h> -static struct log_info_cat trx_log_info_cat[] = { +static struct log_info_cat trxcon_log_info_cat[] = { [DAPP] = { .name = "DAPP", .description = "Application", @@ -46,8 +43,8 @@ static struct log_info_cat trx_log_info_cat[] = { .color = "\033[1;31m", .enabled = 1, .loglevel = LOGL_NOTICE, }, - [DTRX] = { - .name = "DTRX", + [DTRXC] = { + .name = "DTRXC", .description = "Transceiver control interface", .color = "\033[1;33m", .enabled = 1, .loglevel = LOGL_NOTICE, @@ -70,19 +67,37 @@ static struct log_info_cat trx_log_info_cat[] = { .color = "\033[1;36m", .enabled = 1, .loglevel = LOGL_NOTICE, }, + [DGPRS] = { + .name = "DGPRS", + .description = "L1 GPRS (MAC layer)", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, }; -static const struct log_info trx_log_info = { - .cat = trx_log_info_cat, - .num_cat = ARRAY_SIZE(trx_log_info_cat), +static const struct log_info trxcon_log_info = { + .cat = trxcon_log_info_cat, + .num_cat = ARRAY_SIZE(trxcon_log_info_cat), }; -int trx_log_init(void *tall_ctx, const char *category_mask) +static const int trxcon_log_cfg[] = { + [TRXCON_LOGC_FSM] = DAPP, + [TRXCON_LOGC_L1C] = DL1C, + [TRXCON_LOGC_L1D] = DL1D, + [TRXCON_LOGC_SCHC] = DSCH, + [TRXCON_LOGC_SCHD] = DSCHD, + [TRXCON_LOGC_GPRS] = DGPRS, +}; + +int trxcon_logging_init(void *tall_ctx, const char *category_mask) { - osmo_init_logging2(tall_ctx, &trx_log_info); + osmo_init_logging2(tall_ctx, &trxcon_log_info); + log_target_file_switch_to_wqueue(osmo_stderr_target); if (category_mask) log_parse_category_mask(osmo_stderr_target, category_mask); + trxcon_set_log_cfg(&trxcon_log_cfg[0], ARRAY_SIZE(trxcon_log_cfg)); + return 0; } diff --git a/src/host/trxcon/src/sched_lchan_common.c b/src/host/trxcon/src/sched_lchan_common.c new file mode 100644 index 00000000..2b1729ae --- /dev/null +++ b/src/host/trxcon/src/sched_lchan_common.c @@ -0,0 +1,137 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: common routines for lchan handlers + * + * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * Contributions by sysmocom - s.f.m.c. GmbH + * + * 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. + * + */ + +#include <errno.h> +#include <string.h> +#include <talloc.h> +#include <stdint.h> +#include <stdbool.h> + +#include <arpa/inet.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/bits.h> + +#include <osmocom/codec/codec.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> + +/* GSM 05.02 Chapter 5.2.3 Normal Burst (NB) */ +const uint8_t l1sched_nb_training_bits[8][26] = { + { + 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, + }, + { + 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, + 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, + }, + { + 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, + 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, + }, + { + 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, + }, + { + 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, + }, + { + 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, + 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, + }, + { + 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, + 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, + }, + { + 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, + 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, + }, +}; + +/* Get a string representation of the burst buffer's completeness. + * Examples: " ****.." (incomplete, 4/6 bursts) + * " ****" (complete, all 4 bursts) + * "**.***.." (incomplete, 5/8 bursts) */ +const char *l1sched_burst_mask2str(const uint32_t *mask, int bits) +{ + static char buf[32 + 1]; + char *ptr = buf; + + OSMO_ASSERT(bits <= 32 && bits > 0); + + while (--bits >= 0) + *(ptr++) = (*mask & (1 << bits)) ? '*' : '.'; + *ptr = '\0'; + + return buf; +} + +bool l1sched_lchan_amr_prim_is_valid(struct l1sched_lchan_state *lchan, + struct msgb *msg, bool is_cmr) +{ + enum osmo_amr_type ft_codec; + uint8_t cmr_codec; + int ft, cmr, len; + + len = osmo_amr_rtp_dec(msgb_l2(msg), msgb_l2len(msg), + &cmr_codec, NULL, &ft_codec, NULL, NULL); + if (len < 0) { + LOGP_LCHAND(lchan, LOGL_ERROR, "Cannot send invalid AMR payload (%u): %s\n", + msgb_l2len(msg), msgb_hexdump_l2(msg)); + return false; + } + ft = -1; + cmr = -1; + for (unsigned int i = 0; i < lchan->amr.codecs; i++) { + if (lchan->amr.codec[i] == ft_codec) + ft = i; + if (lchan->amr.codec[i] == cmr_codec) + cmr = i; + } + if (ft < 0) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Codec (FT = %d) of RTP frame not in list\n", ft_codec); + return false; + } + if (is_cmr && lchan->amr.ul_ft != ft) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Codec (FT = %d) of RTP cannot be changed now, but in next frame\n", + ft_codec); + return false; + } + lchan->amr.ul_ft = ft; + if (cmr < 0) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Codec (CMR = %d) of RTP frame not in list\n", cmr_codec); + } else { + lchan->amr.ul_cmr = cmr; + } + + return true; +} diff --git a/src/host/trxcon/sched_lchan_desc.c b/src/host/trxcon/src/sched_lchan_desc.c index 67f770c5..db5446e3 100644 --- a/src/host/trxcon/sched_lchan_desc.c +++ b/src/host/trxcon/src/sched_lchan_desc.c @@ -5,6 +5,7 @@ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> * (C) 2015 by Harald Welte <laforge@gnumonks.org> + * Contributions by sysmocom - s.f.m.c. GmbH * * All Rights Reserved * @@ -24,66 +25,62 @@ */ #include <osmocom/gsm/protocol/gsm_08_58.h> -#include "sched_trx.h" + +#include <osmocom/bb/l1sched/l1sched.h> /* Forward declaration of handlers */ -int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, - sbit_t *bits, int8_t rssi, int16_t toa256); +int rx_data_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi); -int tx_data_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid); +int tx_data_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br); -int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, - sbit_t *bits, int8_t rssi, int16_t toa256); +int rx_sch_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi); -int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid); +int tx_rach_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br); -int rx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, - sbit_t *bits, int8_t rssi, int16_t toa256); +int rx_tchf_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi); -int tx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid); +int tx_tchf_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br); -int rx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, - sbit_t *bits, int8_t rssi, int16_t toa256); +int rx_tchh_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi); -int tx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid); +int tx_tchh_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br); -int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, - sbit_t *bits, int8_t rssi, int16_t toa256); +int rx_pdtch_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi); -int tx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid); +int tx_pdtch_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br); -const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = { - [TRXC_IDLE] = { +const struct l1sched_lchan_desc l1sched_lchan_desc[_L1SCHED_CHAN_MAX] = { + [L1SCHED_IDLE] = { .name = "IDLE", .desc = "Idle channel", /* The MS needs to perform neighbour measurements during * IDLE slots, however this is not implemented (yet). */ }, - [TRXC_FCCH] = { + [L1SCHED_FCCH] = { .name = "FCCH", /* 3GPP TS 05.02, section 3.3.2.1 */ .desc = "Frequency correction channel", /* Handled by transceiver, nothing to do. */ }, - [TRXC_SCH] = { + [L1SCHED_SCH] = { .name = "SCH", /* 3GPP TS 05.02, section 3.3.2.2 */ .desc = "Synchronization channel", /* 3GPP TS 05.03, section 4.7. Handled by transceiver, * however we still need to parse BSIC (BCC / NCC). */ - .flags = TRX_CH_FLAG_AUTO, + .flags = L1SCHED_CH_FLAG_AUTO, .rx_fn = rx_sch_fn, }, - [TRXC_BCCH] = { + [L1SCHED_BCCH] = { .name = "BCCH", /* 3GPP TS 05.02, section 3.3.2.3 */ .desc = "Broadcast control channel", .chan_nr = RSL_CHAN_BCCH, @@ -91,20 +88,20 @@ const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = { /* Rx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4), * regular interleaving (3GPP TS 05.02, clause 7, table 3): * a L2 frame is interleaved over 4 consecutive bursts. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_AUTO, + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, + .flags = L1SCHED_CH_FLAG_AUTO, .rx_fn = rx_data_fn, }, - [TRXC_RACH] = { + [L1SCHED_RACH] = { .name = "RACH", /* 3GPP TS 05.02, section 3.3.3.1 */ .desc = "Random access channel", .chan_nr = RSL_CHAN_RACH, /* Tx only, RACH convolutional coding (3GPP TS 05.03, section 4.6). */ - .flags = TRX_CH_FLAG_AUTO, + .flags = L1SCHED_CH_FLAG_AUTO, .tx_fn = tx_rach_fn, }, - [TRXC_CCCH] = { + [L1SCHED_CCCH] = { .name = "CCCH", /* 3GPP TS 05.02, section 3.3.3.1 */ .desc = "Common control channel", .chan_nr = RSL_CHAN_PCH_AGCH, @@ -112,15 +109,15 @@ const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = { /* Rx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4), * regular interleaving (3GPP TS 05.02, clause 7, table 3): * a L2 frame is interleaved over 4 consecutive bursts. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_AUTO, + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, + .flags = L1SCHED_CH_FLAG_AUTO, .rx_fn = rx_data_fn, }, - [TRXC_TCHF] = { + [L1SCHED_TCHF] = { .name = "TCH/F", /* 3GPP TS 05.02, section 3.2 */ .desc = "Full Rate traffic channel", .chan_nr = RSL_CHAN_Bm_ACCHs, - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, /* Rx and Tx, multiple convolutional coding types (3GPP TS 05.03, * chapter 3), block diagonal interleaving (3GPP TS 05.02, clause 7): @@ -133,21 +130,23 @@ const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = { * * The MS shall continuously transmit bursts, even if there is nothing * to send, unless DTX (Discontinuous Transmission) is used. */ - .burst_buf_size = 8 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + .burst_buf_size = 24 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_tchf_fn, .tx_fn = tx_tchf_fn, }, - [TRXC_TCHH_0] = { + [L1SCHED_TCHH_0] = { .name = "TCH/H(0)", /* 3GPP TS 05.02, section 3.2 */ .desc = "Half Rate traffic channel (sub-channel 0)", .chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, /* Rx and Tx, multiple convolutional coding types (3GPP TS 05.03, * chapter 3), block diagonal interleaving (3GPP TS 05.02, clause 7): * - * - a traffic frame is interleaved over 6 consecutive bursts + * - a traffic frame is interleaved over 4 non-consecutive bursts + * using the even numbered bits of the first 2 bursts, + * and odd numbered bits of the last 2 bursts; + * - a FACCH/H frame is interleaved over 6 non-consecutive bursts * using the even numbered bits of the first 2 bursts, * all bits of the middle two 2 bursts, * and odd numbered bits of the last 2 bursts; @@ -157,348 +156,319 @@ const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = { * * The MS shall continuously transmit bursts, even if there is nothing * to send, unless DTX (Discontinuous Transmission) is used. */ - .burst_buf_size = 6 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + .burst_buf_size = 24 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_tchh_fn, .tx_fn = tx_tchh_fn, }, - [TRXC_TCHH_1] = { + [L1SCHED_TCHH_1] = { .name = "TCH/H(1)", /* 3GPP TS 05.02, section 3.2 */ .desc = "Half Rate traffic channel (sub-channel 1)", .chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_TCHH_0, see above. */ - .burst_buf_size = 6 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_TCHH_0, see above. */ + .burst_buf_size = 24 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_tchh_fn, .tx_fn = tx_tchh_fn, }, - [TRXC_SDCCH4_0] = { + [L1SCHED_SDCCH4_0] = { .name = "SDCCH/4(0)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Stand-alone dedicated control channel (sub-channel 0)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SDCCH4_1] = { + [L1SCHED_SDCCH4_1] = { .name = "SDCCH/4(1)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Stand-alone dedicated control channel (sub-channel 1)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SDCCH4_2] = { + [L1SCHED_SDCCH4_2] = { .name = "SDCCH/4(2)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Stand-alone dedicated control channel (sub-channel 2)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SDCCH4_3] = { + [L1SCHED_SDCCH4_3] = { .name = "SDCCH/4(3)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Stand-alone dedicated control channel (sub-channel 3)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SDCCH8_0] = { + [L1SCHED_SDCCH8_0] = { .name = "SDCCH/8(0)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Stand-alone dedicated control channel (sub-channel 0)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SDCCH8_1] = { + [L1SCHED_SDCCH8_1] = { .name = "SDCCH/8(1)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Stand-alone dedicated control channel (sub-channel 1)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SDCCH8_2] = { + [L1SCHED_SDCCH8_2] = { .name = "SDCCH/8(2)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Stand-alone dedicated control channel (sub-channel 2)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SDCCH8_3] = { + [L1SCHED_SDCCH8_3] = { .name = "SDCCH/8(3)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Stand-alone dedicated control channel (sub-channel 3)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SDCCH8_4] = { + [L1SCHED_SDCCH8_4] = { .name = "SDCCH/8(4)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Stand-alone dedicated control channel (sub-channel 4)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SDCCH8_5] = { + [L1SCHED_SDCCH8_5] = { .name = "SDCCH/8(5)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Stand-alone dedicated control channel (sub-channel 5)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SDCCH8_6] = { + [L1SCHED_SDCCH8_6] = { .name = "SDCCH/8(6)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Stand-alone dedicated control channel (sub-channel 6)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SDCCH8_7] = { + [L1SCHED_SDCCH8_7] = { .name = "SDCCH/8(7)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Stand-alone dedicated control channel (sub-channel 7)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCHTF] = { + [L1SCHED_SACCHTF] = { .name = "SACCH/TF", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow TCH/F associated control channel", .chan_nr = RSL_CHAN_Bm_ACCHs, - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCHTH_0] = { + [L1SCHED_SACCHTH_0] = { .name = "SACCH/TH(0)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow TCH/H associated control channel (sub-channel 0)", .chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCHTH_1] = { + [L1SCHED_SACCHTH_1] = { .name = "SACCH/TH(1)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow TCH/H associated control channel (sub-channel 1)", .chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCH4_0] = { + [L1SCHED_SACCH4_0] = { .name = "SACCH/4(0)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow SDCCH/4 associated control channel (sub-channel 0)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCH4_1] = { + [L1SCHED_SACCH4_1] = { .name = "SACCH/4(1)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow SDCCH/4 associated control channel (sub-channel 1)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCH4_2] = { + [L1SCHED_SACCH4_2] = { .name = "SACCH/4(2)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow SDCCH/4 associated control channel (sub-channel 2)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCH4_3] = { + [L1SCHED_SACCH4_3] = { .name = "SACCH/4(3)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow SDCCH/4 associated control channel (sub-channel 3)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCH8_0] = { + [L1SCHED_SACCH8_0] = { .name = "SACCH/8(0)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow SDCCH/8 associated control channel (sub-channel 0)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCH8_1] = { + [L1SCHED_SACCH8_1] = { .name = "SACCH/8(1)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow SDCCH/8 associated control channel (sub-channel 1)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCH8_2] = { + [L1SCHED_SACCH8_2] = { .name = "SACCH/8(2)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow SDCCH/8 associated control channel (sub-channel 2)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCH8_3] = { + [L1SCHED_SACCH8_3] = { .name = "SACCH/8(3)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow SDCCH/8 associated control channel (sub-channel 3)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCH8_4] = { + [L1SCHED_SACCH8_4] = { .name = "SACCH/8(4)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow SDCCH/8 associated control channel (sub-channel 4)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCH8_5] = { + [L1SCHED_SACCH8_5] = { .name = "SACCH/8(5)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow SDCCH/8 associated control channel (sub-channel 5)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCH8_6] = { + [L1SCHED_SACCH8_6] = { .name = "SACCH/8(6)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow SDCCH/8 associated control channel (sub-channel 6)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_SACCH8_7] = { + [L1SCHED_SACCH8_7] = { .name = "SACCH/8(7)", /* 3GPP TS 05.02, section 3.3.4.1 */ .desc = "Slow SDCCH/8 associated control channel (sub-channel 7)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, .tx_fn = tx_data_fn, }, - [TRXC_PDTCH] = { + [L1SCHED_PDTCH] = { .name = "PDTCH", /* 3GPP TS 05.02, sections 3.2.4, 3.3.2.4 */ .desc = "Packet data traffic & control channel", .chan_nr = RSL_CHAN_OSMO_PDCH, @@ -507,44 +477,44 @@ const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = { * 05.03, chapter 5), regular interleaving as specified for xCCH. * NOTE: the burst buffer is three times bigger because the * payload of EDGE bursts is three times longer. */ - .burst_buf_size = 3 * 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_PDCH, + .burst_buf_size = 4 * GSM_NBITS_NB_8PSK_PAYLOAD, + .flags = L1SCHED_CH_FLAG_PDCH, .rx_fn = rx_pdtch_fn, .tx_fn = tx_pdtch_fn, }, - [TRXC_PTCCH] = { + [L1SCHED_PTCCH] = { .name = "PTCCH", /* 3GPP TS 05.02, section 3.3.4.2 */ .desc = "Packet Timing advance control channel", .chan_nr = RSL_CHAN_OSMO_PDCH, - .link_id = TRX_CH_LID_PTCCH, + .link_id = L1SCHED_CH_LID_PTCCH, /* On the Uplink, mobile stations transmit random Access Bursts * to allow estimation of the timing advance for one MS in packet * transfer mode. On Downlink, the network sends timing advance * updates for several mobile stations. The coding scheme used * for PTCCH/D messages is the same as for PDTCH CS-1. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_PDCH, + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, + .flags = L1SCHED_CH_FLAG_PDCH, .rx_fn = rx_pdtch_fn, .tx_fn = tx_rach_fn, }, - [TRXC_SDCCH4_CBCH] = { + [L1SCHED_SDCCH4_CBCH] = { .name = "SDCCH/4(CBCH)", /* 3GPP TS 05.02, section 3.3.5 */ .desc = "Cell Broadcast channel on SDCCH/4", .chan_nr = RSL_CHAN_OSMO_CBCH4, - /* Same as for TRXC_BCCH (xCCH), but Rx only. See above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_AUTO, + /* Same as for L1SCHED_BCCH (xCCH), but Rx only. See above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, + .flags = L1SCHED_CH_FLAG_AUTO, .rx_fn = rx_data_fn, }, - [TRXC_SDCCH8_CBCH] = { + [L1SCHED_SDCCH8_CBCH] = { .name = "SDCCH/8(CBCH)", /* 3GPP TS 05.02, section 3.3.5 */ .desc = "Cell Broadcast channel on SDCCH/8", .chan_nr = RSL_CHAN_OSMO_CBCH8, - /* Same as for TRXC_BCCH (xCCH), but Rx only. See above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, + /* Same as for L1SCHED_BCCH (xCCH), but Rx only. See above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, }, }; diff --git a/src/host/trxcon/src/sched_lchan_pdtch.c b/src/host/trxcon/src/sched_lchan_pdtch.c new file mode 100644 index 00000000..5b884ddc --- /dev/null +++ b/src/host/trxcon/src/sched_lchan_pdtch.c @@ -0,0 +1,195 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: handlers for DL / UL bursts on logical channels + * + * (C) 2018-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * Contributions by sysmocom - s.f.m.c. GmbH + * + * 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. + * + */ + +#include <errno.h> +#include <string.h> +#include <stdint.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/bits.h> + +#include <osmocom/gsm/gsm0502.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/coding/gsm0503_coding.h> + +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> + +int rx_pdtch_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi) +{ + uint8_t l2[GPRS_L2_MAX_LEN]; + int n_errors, n_bits_total, rc; + sbit_t *bursts_p, *burst; + size_t l2_len; + uint32_t *mask; + + /* Set up pointers */ + mask = &lchan->rx_burst_mask; + bursts_p = lchan->rx_bursts; + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "Packet data received: fn=%u bid=%u\n", bi->fn, bi->bid); + + /* Align to the first burst of a block */ + if (*mask == 0x00 && bi->bid != 0) + return 0; + + /* Update mask */ + *mask |= (1 << bi->bid); + + /* Store the measurements */ + l1sched_lchan_meas_push(lchan, bi); + + /* Copy burst to buffer of 4 bursts */ + burst = bursts_p + bi->bid * 116; + memcpy(burst, bi->burst + 3, 58); + memcpy(burst + 58, bi->burst + 87, 58); + + /* Wait until complete set of bursts */ + if (bi->bid != 3) + return 0; + + /* Calculate AVG of the measurements */ + l1sched_lchan_meas_avg(lchan, 4); + + /* Check for complete set of bursts */ + if ((*mask & 0xf) != 0xf) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Received incomplete (%s) packet data at fn=%u (%u/%u)\n", + l1sched_burst_mask2str(mask, 4), lchan->meas_avg.fn, + lchan->meas_avg.fn % lchan->ts->mf_layout->period, + lchan->ts->mf_layout->period); + /* NOTE: do not abort here, give it a try. Maybe we're lucky ;) */ + } + + /* Keep the mask updated */ + *mask = *mask << 4; + + /* Attempt to decode */ + rc = gsm0503_pdtch_decode(l2, bursts_p, + NULL, &n_errors, &n_bits_total); + if (rc < 0) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Received bad frame (rc=%d, ber=%d/%d) at fn=%u\n", + rc, n_errors, n_bits_total, lchan->meas_avg.fn); + } + + /* Determine L2 length */ + l2_len = rc > 0 ? rc : 0; + + /* Send a L2 frame to the higher layers */ + l1sched_lchan_emit_data_ind(lchan, l2, l2_len, n_errors, n_bits_total, true); + + return 0; +} + +static struct msgb *prim_dequeue_pdtch(struct l1sched_lchan_state *lchan, uint32_t fn) +{ + while (!llist_empty(&lchan->tx_prims)) { + struct msgb *msg = llist_first_entry(&lchan->tx_prims, struct msgb, list); + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + int ret = gsm0502_fncmp(prim->data_req.frame_nr, fn); + + if (OSMO_LIKELY(ret == 0)) { /* it's a match! */ + llist_del(&msg->list); + return msg; + } else if (ret > 0) { /* not now, come back later */ + break; + } /* else: the ship has sailed, drop your ticket */ + + LOGP_LCHAND(lchan, LOGL_ERROR, + "%s(): dropping stale Tx prim (current Fn=%u, prim Fn=%u): %s\n", + __func__, fn, prim->data_req.frame_nr, msgb_hexdump_l2(msg)); + llist_del(&msg->list); + msgb_free(msg); + } + + return NULL; +} + +int tx_pdtch_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br) +{ + ubit_t *bursts_p, *burst; + const uint8_t *tsc; + uint32_t *mask; + int rc; + + /* Set up pointers */ + mask = &lchan->tx_burst_mask; + bursts_p = lchan->tx_bursts; + + if (br->bid > 0) { + if ((*mask & 0x01) != 0x01) + return -ENOENT; + goto send_burst; + } + + *mask = *mask << 4; + + struct msgb *msg = prim_dequeue_pdtch(lchan, br->fn); + if (msg == NULL) + return -ENOENT; + + /* Encode payload */ + rc = gsm0503_pdtch_encode(bursts_p, msgb_l2(msg), msgb_l2len(msg)); + if (rc < 0) { + LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%u): %s\n", + msgb_l2len(msg), msgb_hexdump_l2(msg)); + msgb_free(msg); + return -EINVAL; + } + + /* Cache the prim, so that we can confirm it later (see below) */ + OSMO_ASSERT(lchan->prim == NULL); + lchan->prim = msg; + +send_burst: + /* Determine which burst should be sent */ + burst = bursts_p + br->bid * 116; + + /* Update mask */ + *mask |= (1 << br->bid); + + /* Choose proper TSC */ + tsc = l1sched_nb_training_bits[lchan->tsc]; + + /* Compose a new burst */ + memset(br->burst, 0, 3); /* TB */ + memcpy(br->burst + 3, burst, 58); /* Payload 1/2 */ + memcpy(br->burst + 61, tsc, 26); /* TSC */ + memcpy(br->burst + 87, burst + 58, 58); /* Payload 2/2 */ + memset(br->burst + 145, 0, 3); /* TB */ + br->burst_len = GSM_NBITS_NB_GMSK_BURST; + + LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled at fn=%u burst=%u\n", br->fn, br->bid); + + if (br->bid == 3) { + /* Confirm data / traffic sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, lchan->prim, + GSM_TDMA_FN_SUB(br->fn, 3)); + lchan->prim = NULL; + } + + return 0; +} diff --git a/src/host/trxcon/src/sched_lchan_rach.c b/src/host/trxcon/src/sched_lchan_rach.c new file mode 100644 index 00000000..905f1d57 --- /dev/null +++ b/src/host/trxcon/src/sched_lchan_rach.c @@ -0,0 +1,136 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: handlers for DL / UL bursts on logical channels + * + * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * Contributions by sysmocom - s.f.m.c. GmbH + * + * 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. + * + */ + +#include <errno.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/bits.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/coding/gsm0503_coding.h> + +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> + +/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)" */ +#define RACH_EXT_TAIL_BITS_LEN 8 +#define RACH_SYNCH_SEQ_LEN 41 +#define RACH_PAYLOAD_LEN 36 + +/* Extended tail bits (BN0..BN7) */ +static const ubit_t rach_ext_tail_bits[] = { + 0, 0, 1, 1, 1, 0, 1, 0, +}; + +/* Synchronization (training) sequence types */ +enum rach_synch_seq_t { + RACH_SYNCH_SEQ_UNKNOWN = -1, + RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */ + RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */ + RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */ + RACH_SYNCH_SEQ_NUM +}; + +/* Synchronization (training) sequence bits */ +static const char rach_synch_seq_bits[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = { + [RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000", + [RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101", + [RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111", +}; + +/* Synchronization (training) sequence names */ +static struct value_string rach_synch_seq_names[] = { + { RACH_SYNCH_SEQ_UNKNOWN, "UNKNOWN" }, + { RACH_SYNCH_SEQ_TS0, "TS0: GSM, GMSK" }, + { RACH_SYNCH_SEQ_TS1, "TS1: EGPRS, 8-PSK" }, + { RACH_SYNCH_SEQ_TS2, "TS2: EGPRS, GMSK" }, + { 0, NULL }, +}; + +/* Obtain a to-be-transmitted RACH burst */ +int tx_rach_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br) +{ + const uint8_t bsic = lchan->ts->sched->bsic; + uint8_t *burst_ptr = br->burst; + uint8_t payload[36]; + int i, rc; + + if (llist_empty(&lchan->tx_prims)) + return 0; + + struct msgb *msg = llist_first_entry(&lchan->tx_prims, struct msgb, list); + struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + + /* Delay sending according to offset value */ + if (prim->rach_req.offset-- > 0) + return 0; + llist_del(&msg->list); + + /* Check requested synch. sequence */ + if (prim->rach_req.synch_seq >= RACH_SYNCH_SEQ_NUM) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Unknown RACH synch. sequence=0x%02x\n", + prim->rach_req.synch_seq); + msgb_free(msg); + return -ENOTSUP; + } + + /* Encode the payload */ + rc = gsm0503_rach_ext_encode(payload, prim->rach_req.ra, + bsic, prim->rach_req.is_11bit); + if (rc) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Could not encode %s-bit RACH burst (ra=%u bsic=%u)\n", + prim->rach_req.is_11bit ? "11" : "8", + prim->rach_req.ra, bsic); + msgb_free(msg); + return rc; + } + + /* BN0-7: extended tail bits */ + memcpy(burst_ptr, rach_ext_tail_bits, RACH_EXT_TAIL_BITS_LEN); + burst_ptr += RACH_EXT_TAIL_BITS_LEN; + + /* BN8-48: chosen synch. (training) sequence */ + for (i = 0; i < RACH_SYNCH_SEQ_LEN; i++) + *(burst_ptr++) = rach_synch_seq_bits[prim->rach_req.synch_seq][i] == '1'; + + /* BN49-84: encrypted bits (the payload) */ + memcpy(burst_ptr, payload, RACH_PAYLOAD_LEN); + burst_ptr += RACH_PAYLOAD_LEN; + + /* BN85-156: tail bits & extended guard period */ + memset(burst_ptr, 0, br->burst + GSM_NBITS_NB_GMSK_BURST - burst_ptr); + br->burst_len = GSM_NBITS_NB_GMSK_BURST; + + LOGP_LCHAND(lchan, LOGL_NOTICE, "Scheduled %s-bit RACH (%s) at fn=%u\n", + prim->rach_req.is_11bit ? "11" : "8", + get_value_string(rach_synch_seq_names, prim->rach_req.synch_seq), br->fn); + + /* Confirm RACH request (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + + return 0; +} diff --git a/src/host/trxcon/sched_lchan_sch.c b/src/host/trxcon/src/sched_lchan_sch.c index 9eed506b..e2420050 100644 --- a/src/host/trxcon/sched_lchan_sch.c +++ b/src/host/trxcon/src/sched_lchan_sch.c @@ -2,7 +2,8 @@ * OsmocomBB <-> SDR connection bridge * TDMA scheduler: handlers for DL / UL bursts on logical channels * - * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com> + * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * Contributions by sysmocom - s.f.m.c. GmbH * * All Rights Reserved * @@ -16,10 +17,6 @@ * 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 <errno.h> @@ -35,12 +32,8 @@ #include <osmocom/gsm/gsm_utils.h> #include <osmocom/coding/gsm0503_coding.h> -#include "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" -#include "logging.h" -#include "trx_if.h" -#include "l1ctl.h" +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> static void decode_sb(struct gsm_time *time, uint8_t *bsic, uint8_t *sb_info) { @@ -68,9 +61,23 @@ static void decode_sb(struct gsm_time *time, uint8_t *bsic, uint8_t *sb_info) time->fn = gsm_gsmtime2fn(time); } -int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, - sbit_t *bits, int8_t rssi, int16_t toa256) +static int handle_sch_ind(struct l1sched_state *sched, uint32_t fn, uint8_t bsic) +{ + struct l1sched_prim *prim; + struct msgb *msg; + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_SCH, PRIM_OP_INDICATION); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->sch_ind.frame_nr = fn; + prim->sch_ind.bsic = bsic; + + return l1sched_prim_to_user(sched, msg); +} + +int rx_sch_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi) { sbit_t payload[2 * 39]; struct gsm_time time; @@ -79,55 +86,34 @@ int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts, int rc; /* Obtain payload from burst */ - memcpy(payload, bits + 3, 39); - memcpy(payload + 39, bits + 3 + 39 + 64, 39); + memcpy(payload, bi->burst + 3, 39); + memcpy(payload + 39, bi->burst + 3 + 39 + 64, 39); /* Attempt to decode */ rc = gsm0503_sch_decode(sb_info, payload); if (rc) { - LOGP(DSCHD, LOGL_ERROR, "Received bad SCH burst at fn=%u\n", fn); + LOGP_LCHAND(lchan, LOGL_ERROR, + "Received bad SCH burst at fn=%u\n", bi->fn); return rc; } /* Decode BSIC and TDMA frame number */ decode_sb(&time, &bsic, sb_info); - LOGP(DSCHD, LOGL_DEBUG, "Received SCH: bsic=%u, fn=%u, sched_fn=%u\n", - bsic, time.fn, trx->sched.fn_counter_proc); + LOGP_LCHAND(lchan, LOGL_DEBUG, + "Received SCH: bsic=%u, fn=%u, sched_fn=%u\n", + bsic, time.fn, bi->fn); /* Check if decoded frame number matches */ - if (time.fn != fn) { - LOGP(DSCHD, LOGL_ERROR, "Decoded fn=%u does not match " - "fn=%u provided by scheduler\n", time.fn, fn); + if (time.fn != bi->fn) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Decoded fn=%u does not match sched_fn=%u\n", + time.fn, bi->fn); return -EINVAL; } - /* We don't need to send L1CTL_FBSB_CONF */ - if (trx->l1l->fbsb_conf_sent) - return 0; - - /* Send L1CTL_FBSB_CONF to higher layers */ - struct l1ctl_info_dl *data; - data = talloc_zero_size(ts, sizeof(struct l1ctl_info_dl)); - if (data == NULL) - return -ENOMEM; - - /* Fill in some downlink info */ - data->chan_nr = trx_lchan_desc[lchan->type].chan_nr | ts->index; - data->link_id = trx_lchan_desc[lchan->type].link_id; - data->band_arfcn = htons(trx->band_arfcn); - data->frame_nr = htonl(fn); - data->rx_level = -rssi; - - /* FIXME: set proper values */ - data->num_biterr = 0; - data->fire_crc = 0; - data->snr = 0; - - l1ctl_tx_fbsb_conf(trx->l1l, 0, data, bsic); - - /* Update BSIC value of trx_instance */ - trx->bsic = bsic; + /* Update BSIC value in the scheduler state */ + lchan->ts->sched->bsic = bsic; - return 0; + return handle_sch_ind(lchan->ts->sched, time.fn, bsic); } diff --git a/src/host/trxcon/src/sched_lchan_tchf.c b/src/host/trxcon/src/sched_lchan_tchf.c new file mode 100644 index 00000000..985bea48 --- /dev/null +++ b/src/host/trxcon/src/sched_lchan_tchf.c @@ -0,0 +1,439 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: handlers for DL / UL bursts on logical channels + * + * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * (C) 2021-2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * 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. + * + */ + +#include <errno.h> +#include <string.h> +#include <stdint.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/bits.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/coding/gsm0503_coding.h> +#include <osmocom/coding/gsm0503_amr_dtx.h> +#include <osmocom/codec/codec.h> + +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> + +/* Burst Payload LENgth (short alias) */ +#define BPLEN GSM_NBITS_NB_GMSK_PAYLOAD + +/* Burst BUFfer capacity (in BPLEN units) */ +#define BUFMAX 24 + +/* Burst BUFfer position macros */ +#define BUFPOS(buf, n) &buf[(n) * BPLEN] +#define BUFTAIL8(buf) BUFPOS(buf, (BUFMAX - 8)) + +/* 3GPP TS 45.009, table 3.2.1.3-{1,3}: AMR on Downlink TCH/F. + * + * +---+---+---+---+---+---+---+---+ + * | a | b | c | d | e | f | g | h | Burst 'a' received first + * +---+---+---+---+---+---+---+---+ + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Speech/FACCH frame (bursts 'a' .. 'h') + * + * TDMA frame number of burst 'h' is always used as the table index. */ +static const uint8_t sched_tchf_dl_amr_cmi_map[26] = { + [11] = 1, /* TCH/F: a=4 / h=11 */ + [20] = 1, /* TCH/F: a=13 / h=20 */ + [3] = 1, /* TCH/F: a=21 / h=3 (21+7=28, 25 is idle -> 29. 29%26=3) */ +}; + +/* TDMA frame number of burst 'a' should be used as the table index. */ +static const uint8_t sched_tchf_ul_amr_cmi_map[26] = { + [0] = 1, /* TCH/F: a=0 */ + [8] = 1, /* TCH/F: a=8 */ + [17] = 1, /* TCH/F: a=17 */ +}; + +static int decode_fr_facch(struct l1sched_lchan_state *lchan) +{ + uint8_t data[GSM_MACBLOCK_LEN]; + int n_errors, n_bits_total; + int rc; + + rc = gsm0503_tch_fr_facch_decode(&data[0], BUFTAIL8(lchan->rx_bursts), + &n_errors, &n_bits_total); + if (rc != GSM_MACBLOCK_LEN) + return rc; + + /* calculate AVG of the measurements (FACCH/F takes 8 bursts) */ + l1sched_lchan_meas_avg(lchan, 8); + + l1sched_lchan_emit_data_ind(lchan, &data[0], GSM_MACBLOCK_LEN, + n_errors, n_bits_total, false); + + return GSM_MACBLOCK_LEN; +} + +int rx_tchf_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi) +{ + int n_errors = -1, n_bits_total = 0, rc; + sbit_t *bursts_p, *burst; + uint8_t tch_data[290]; + size_t tch_data_len; + uint32_t *mask; + int amr = 0; + uint8_t ft; + + /* Set up pointers */ + mask = &lchan->rx_burst_mask; + bursts_p = lchan->rx_bursts; + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "Traffic received: fn=%u bid=%u\n", bi->fn, bi->bid); + + if (bi->bid == 0) { + /* Shift the burst buffer by 4 bursts leftwards */ + memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 4), 20 * BPLEN); + memset(BUFPOS(bursts_p, 20), 0, 4 * BPLEN); + *mask = *mask << 4; + } else { + /* Align to the first burst of a block */ + if (*mask == 0x00) + return 0; + } + + /* Update mask */ + *mask |= (1 << bi->bid); + + /* Store the measurements */ + l1sched_lchan_meas_push(lchan, bi); + + /* Copy burst to end of buffer of 24 bursts */ + burst = BUFPOS(bursts_p, 20 + bi->bid); + memcpy(burst, bi->burst + 3, 58); + memcpy(burst + 58, bi->burst + 87, 58); + + /* Wait until complete set of bursts */ + if (bi->bid != 3) + return 0; + + /* Calculate AVG of the measurements */ + l1sched_lchan_meas_avg(lchan, 8); // XXX + + /* Check for complete set of bursts */ + if ((*mask & 0xff) != 0xff) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Received incomplete (%s) traffic frame at fn=%u (%u/%u)\n", + l1sched_burst_mask2str(mask, 8), lchan->meas_avg.fn, + lchan->meas_avg.fn % lchan->ts->mf_layout->period, + lchan->ts->mf_layout->period); + /* NOTE: do not abort here, give it a try. Maybe we're lucky ;) */ + + } + + /* TCH/F: speech and signalling frames are interleaved over 8 bursts, while + * CSD frames are interleaved over 22 bursts. Unless we're in CSD mode, + * decode only the last 8 bursts to avoid introducing additional delays. */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + case GSM48_CMODE_SPEECH_V1: /* FR */ + rc = gsm0503_tch_fr_decode(&tch_data[0], BUFTAIL8(bursts_p), + 1, 0, &n_errors, &n_bits_total); + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + rc = gsm0503_tch_fr_decode(&tch_data[0], BUFTAIL8(bursts_p), + 1, 1, &n_errors, &n_bits_total); + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + /* we store tch_data + 2 header bytes, the amr variable set to + * 2 will allow us to skip the first 2 bytes in case we did + * receive an FACCH frame instead of a voice frame (we do not + * know this before we actually decode the frame) */ + amr = 2; + rc = gsm0503_tch_afs_decode_dtx(&tch_data[amr], BUFTAIL8(bursts_p), + !sched_tchf_dl_amr_cmi_map[bi->fn % 26], + lchan->amr.codec, + lchan->amr.codecs, + &lchan->amr.dl_ft, + &lchan->amr.dl_cmr, + &n_errors, &n_bits_total, + &lchan->amr.last_dtx); + + /* only good speech frames get rtp header */ + if (rc != GSM_MACBLOCK_LEN && rc >= 4) { + if (lchan->amr.last_dtx == AMR_OTHER) { + ft = lchan->amr.codec[lchan->amr.dl_ft]; + } else { + /* SID frames will always get Frame Type Index 8 (AMR_SID) */ + ft = AMR_SID; + } + rc = osmo_amr_rtp_enc(&tch_data[0], + lchan->amr.codec[lchan->amr.dl_cmr], + ft, AMR_GOOD); + if (rc < 0) + LOGP_LCHAND(lchan, LOGL_ERROR, + "osmo_amr_rtp_enc() returned rc=%d\n", rc); + } + break; + /* CSD (TCH/F14.4): 14.5 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_14k5: + /* FACCH/F does not steal TCH/F14.4 frames, but only disturbs some bits */ + decode_fr_facch(lchan); + rc = gsm0503_tch_fr144_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + break; + /* CSD (TCH/F9.6): 12.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_12k0: + /* FACCH/F does not steal TCH/F9.6 frames, but only disturbs some bits */ + decode_fr_facch(lchan); + rc = gsm0503_tch_fr96_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + break; + /* CSD (TCH/F4.8): 6.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_6k0: + /* FACCH/F does not steal TCH/F4.8 frames, but only disturbs some bits */ + decode_fr_facch(lchan); + rc = gsm0503_tch_fr48_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + break; + /* CSD (TCH/F2.4): 3.6 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_3k6: + /* TCH/F2.4 employs the same interleaving as TCH/FS (8 bursts), + * so FACCH/F *does* steal TCH/F2.4 frames completely. */ + if (decode_fr_facch(lchan) == GSM_MACBLOCK_LEN) + return 0; /* TODO: emit BFI? */ + rc = gsm0503_tch_fr24_decode(&tch_data[0], BUFTAIL8(bursts_p), + &n_errors, &n_bits_total); + break; + default: + LOGP_LCHAND(lchan, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode); + return -EINVAL; + } + + /* Check decoding result */ + if (rc < 4) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Received bad frame (rc=%d, ber=%d/%d) at fn=%u\n", + rc, n_errors, n_bits_total, lchan->meas_avg.fn); + + /* Send BFI (DATA.ind without payload) */ + tch_data_len = 0; + } else if (rc == GSM_MACBLOCK_LEN) { + /* FACCH received, forward it to the higher layers */ + l1sched_lchan_emit_data_ind(lchan, &tch_data[amr], GSM_MACBLOCK_LEN, + n_errors, n_bits_total, false); + + /* Send BFI (DATA.ind without payload) */ + if (lchan->tch_mode == GSM48_CMODE_SIGN) + return 0; + tch_data_len = 0; + } else { + /* A good TCH frame received */ + tch_data_len = rc; + } + + /* Send a traffic frame to the higher layers */ + return l1sched_lchan_emit_data_ind(lchan, &tch_data[0], tch_data_len, + n_errors, n_bits_total, true); +} + +int tx_tchf_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br) +{ + struct msgb *msg_facch, *msg_tch, *msg; + ubit_t *bursts_p, *burst; + const uint8_t *tsc; + uint32_t *mask; + int rc; + + /* Set up pointers */ + mask = &lchan->tx_burst_mask; + bursts_p = lchan->tx_bursts; + + if (br->bid > 0) { + if ((*mask & 0x01) != 0x01) + return -ENOENT; + goto send_burst; + } + + /* Shift the burst buffer by 4 bursts leftwards for interleaving */ + memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 4), 20 * BPLEN); + memset(BUFPOS(bursts_p, 20), 0, 4 * BPLEN); + *mask = *mask << 4; + + /* dequeue a pair of TCH and FACCH frames */ + msg_tch = l1sched_lchan_prim_dequeue_tch(lchan, false); + msg_facch = l1sched_lchan_prim_dequeue_tch(lchan, true); + /* prioritize FACCH over TCH */ + msg = (msg_facch != NULL) ? msg_facch : msg_tch; + + /* populate the buffer with bursts */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + if (msg == NULL) + msg = l1sched_lchan_prim_dummy_lapdm(lchan); + /* fall-through */ + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + /* if msg == NULL, transmit a dummy speech block with inverted CRC3 */ + rc = gsm0503_tch_fr_encode(BUFPOS(bursts_p, 0), + msg ? msgb_l2(msg) : NULL, + msg ? msgb_l2len(msg) : 0, 1); + /* confirm traffic sending (pass ownership of the msgb/prim) */ + if (OSMO_LIKELY(rc == 0)) + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + else /* unlikely: encoding failed, drop msgb/prim */ + msgb_free(msg); + /* drop the other msgb/prim */ + msgb_free((msg == msg_facch) ? msg_tch : msg_facch); + break; + case GSM48_CMODE_SPEECH_AMR: + { + bool amr_fn_is_cmr = !sched_tchf_ul_amr_cmi_map[br->fn % 26]; + unsigned int offset = 0; + + if (msg != NULL && msg != msg_facch) { /* TCH/AFS: speech */ + if (!l1sched_lchan_amr_prim_is_valid(lchan, msg, amr_fn_is_cmr)) { + msgb_free(msg); + msg_tch = NULL; + msg = NULL; + } + /* pull the AMR header - sizeof(struct amr_hdr) */ + offset = 2; + } + + /* if msg == NULL, transmit a dummy speech block with inverted CRC6 */ + rc = gsm0503_tch_afs_encode(BUFPOS(bursts_p, 0), + msg ? msgb_l2(msg) + offset : NULL, + msg ? msgb_l2len(msg) - offset : 0, + amr_fn_is_cmr, + lchan->amr.codec, + lchan->amr.codecs, + lchan->amr.ul_ft, + lchan->amr.ul_cmr); + /* confirm traffic sending (pass ownership of the msgb/prim) */ + if (OSMO_LIKELY(rc == 0)) + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + else /* unlikely: encoding failed, drop prim */ + msgb_free(msg); + /* drop the other primitive */ + msgb_free((msg == msg_facch) ? msg_tch : msg_facch); + break; + } + /* CSD (TCH/F14.4): 14.5 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_14k5: + if ((msg = msg_tch) != NULL) { + OSMO_ASSERT(msgb_l2len(msg) == 290); + gsm0503_tch_fr144_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm data sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } else { + ubit_t idle[290]; + memset(&idle[0], 0x01, sizeof(idle)); + gsm0503_tch_fr144_encode(BUFPOS(bursts_p, 0), &idle[0]); + } + if ((msg = msg_facch) != NULL) { + gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm FACCH sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } + break; + /* CSD (TCH/F9.6): 12.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_12k0: + if ((msg = msg_tch) != NULL) { + OSMO_ASSERT(msgb_l2len(msg) == 4 * 60); + gsm0503_tch_fr96_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm data sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } else { + ubit_t idle[4 * 60]; + memset(&idle[0], 0x01, sizeof(idle)); + gsm0503_tch_fr96_encode(BUFPOS(bursts_p, 0), &idle[0]); + } + if ((msg = msg_facch) != NULL) { + gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm FACCH sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } + break; + /* CSD (TCH/F4.8): 6.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_6k0: + if ((msg = msg_tch) != NULL) { + OSMO_ASSERT(msgb_l2len(msg) == 2 * 60); + gsm0503_tch_fr48_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm data sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } else { + ubit_t idle[2 * 60]; + memset(&idle[0], 0x01, sizeof(idle)); + gsm0503_tch_fr48_encode(BUFPOS(bursts_p, 0), &idle[0]); + } + if ((msg = msg_facch) != NULL) { + gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm FACCH sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } + break; + /* CSD (TCH/F2.4): 3.6 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_3k6: + if ((msg = msg_facch) != NULL) { + /* FACCH/F does steal a TCH/F2.4 frame completely */ + gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + msgb_free(msg_tch); + } else if ((msg = msg_tch) != NULL) { + OSMO_ASSERT(msgb_l2len(msg) == 2 * 36); + gsm0503_tch_fr24_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } else { + ubit_t idle[2 * 36]; + memset(&idle[0], 0x01, sizeof(idle)); + gsm0503_tch_fr24_encode(BUFPOS(bursts_p, 0), &idle[0]); + } + break; + default: + LOGP_LCHAND(lchan, LOGL_ERROR, + "TCH mode %s is unknown or not supported\n", + gsm48_chan_mode_name(lchan->tch_mode)); + msgb_free(msg_facch); + msgb_free(msg_tch); + break; + } + +send_burst: + /* Determine which burst should be sent */ + burst = BUFPOS(bursts_p, br->bid); + + /* Update mask */ + *mask |= (1 << br->bid); + + /* Choose proper TSC */ + tsc = l1sched_nb_training_bits[lchan->tsc]; + + /* Compose a new burst */ + memset(br->burst, 0, 3); /* TB */ + memcpy(br->burst + 3, burst, 58); /* Payload 1/2 */ + memcpy(br->burst + 61, tsc, 26); /* TSC */ + memcpy(br->burst + 87, burst + 58, 58); /* Payload 2/2 */ + memset(br->burst + 145, 0, 3); /* TB */ + br->burst_len = GSM_NBITS_NB_GMSK_BURST; + + LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled fn=%u burst=%u\n", br->fn, br->bid); + + return 0; +} diff --git a/src/host/trxcon/src/sched_lchan_tchh.c b/src/host/trxcon/src/sched_lchan_tchh.c new file mode 100644 index 00000000..d684976d --- /dev/null +++ b/src/host/trxcon/src/sched_lchan_tchh.c @@ -0,0 +1,619 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: handlers for DL / UL bursts on logical channels + * + * (C) 2018-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * (C) 2018 by Harald Welte <laforge@gnumonks.org> + * (C) 2020-2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * 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. + * + */ + +#include <errno.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/bits.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/coding/gsm0503_coding.h> +#include <osmocom/coding/gsm0503_amr_dtx.h> +#include <osmocom/codec/codec.h> + +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> + +/* Burst Payload LENgth (short alias) */ +#define BPLEN GSM_NBITS_NB_GMSK_PAYLOAD + +/* Burst BUFfer capacity (in BPLEN units) */ +#define BUFMAX 24 + +/* Burst BUFfer position macros */ +#define BUFPOS(buf, n) &buf[(n) * BPLEN] +#define BUFTAIL8(buf) BUFPOS(buf, (BUFMAX - 8)) + +/* 3GPP TS 45.009, table 3.2.1.3-{2,4}: AMR on Downlink TCH/H. + * + * +---+---+---+---+---+---+ + * | a | b | c | d | e | f | Burst 'a' received first + * +---+---+---+---+---+---+ + * ^^^^^^^^^^^^^^^^^^^^^^^ FACCH frame (bursts 'a' .. 'f') + * ^^^^^^^^^^^^^^^ Speech frame (bursts 'a' .. 'd') + * + * TDMA frame number of burst 'f' is always used as the table index. */ +static const uint8_t sched_tchh_dl_amr_cmi_map[26] = { + [15] = 1, /* TCH/H(0): a=4 / d=10 / f=15 */ + [23] = 1, /* TCH/H(0): a=13 / d=19 / f=23 */ + [6] = 1, /* TCH/H(0): a=21 / d=2 / f=6 */ + + [16] = 1, /* TCH/H(1): a=5 / d=11 / f=16 */ + [24] = 1, /* TCH/H(1): a=14 / d=20 / f=24 */ + [7] = 1, /* TCH/H(1): a=22 / d=3 / f=7 */ +}; + +/* TDMA frame number of burst 'a' is always used as the table index. */ +static const uint8_t sched_tchh_ul_amr_cmi_map[26] = { + [0] = 1, /* TCH/H(0): a=0 */ + [8] = 1, /* TCH/H(0): a=8 */ + [17] = 1, /* TCH/H(0): a=17 */ + + [1] = 1, /* TCH/H(1): a=1 */ + [9] = 1, /* TCH/H(1): a=9 */ + [18] = 1, /* TCH/H(1): a=18 */ +}; + +static const uint8_t tch_h0_traffic_block_map[3][4] = { + /* B0(0,2,4,6), B1(4,6,8,10), B2(8,10,0,2) */ + { 0, 2, 4, 6 }, + { 4, 6, 8, 10 }, + { 8, 10, 0, 2 }, +}; + +static const uint8_t tch_h1_traffic_block_map[3][4] = { + /* B0(1,3,5,7), B1(5,7,9,11), B2(9,11,1,3) */ + { 1, 3, 5, 7 }, + { 5, 7, 9, 11 }, + { 9, 11, 1, 3 }, +}; + +static const uint8_t tch_h0_dl_facch_block_map[3][6] = { + /* B0(4,6,8,10,13,15), B1(13,15,17,19,21,23), B2(21,23,0,2,4,6) */ + { 4, 6, 8, 10, 13, 15 }, + { 13, 15, 17, 19, 21, 23 }, + { 21, 23, 0, 2, 4, 6 }, +}; + +static const uint8_t tch_h0_ul_facch_block_map[3][6] = { + /* B0(0,2,4,6,8,10), B1(8,10,13,15,17,19), B2(17,19,21,23,0,2) */ + { 0, 2, 4, 6, 8, 10 }, + { 8, 10, 13, 15, 17, 19 }, + { 17, 19, 21, 23, 0, 2 }, +}; + +static const uint8_t tch_h1_dl_facch_block_map[3][6] = { + /* B0(5,7,9,11,14,16), B1(14,16,18,20,22,24), B2(22,24,1,3,5,7) */ + { 5, 7, 9, 11, 14, 16 }, + { 14, 16, 18, 20, 22, 24 }, + { 22, 24, 1, 3, 5, 7 }, +}; + +const uint8_t tch_h1_ul_facch_block_map[3][6] = { + /* B0(1,3,5,7,9,11), B1(9,11,14,16,18,20), B2(18,20,22,24,1,3) */ + { 1, 3, 5, 7, 9, 11 }, + { 9, 11, 14, 16, 18, 20 }, + { 18, 20, 22, 24, 1, 3 }, +}; + +/* FACCH/H channel mapping for Downlink (see 3GPP TS 45.002, table 1). + * This mapping is valid for both FACCH/H(0) and FACCH/H(1). + * TDMA frame number of burst 'f' is used as the table index. */ +static const uint8_t sched_tchh_dl_facch_map[26] = { + [15] = 1, /* FACCH/H(0): B0(4,6,8,10,13,15) */ + [16] = 1, /* FACCH/H(1): B0(5,7,9,11,14,16) */ + [23] = 1, /* FACCH/H(0): B1(13,15,17,19,21,23) */ + [24] = 1, /* FACCH/H(1): B1(14,16,18,20,22,24) */ + [6] = 1, /* FACCH/H(0): B2(21,23,0,2,4,6) */ + [7] = 1, /* FACCH/H(1): B2(22,24,1,3,5,7) */ +}; + +/* 3GPP TS 45.002, table 2 in clause 7: Mapping tables for TCH/H2.4 and TCH/H4.8. + * + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * TCH/H(0): B0(0,2,4,6,8,10,13,15,17,19,21,23,0,2,4,6,8,10,13,15,17,19) + * TCH/H(1): B0(1,3,5,7,9,11,14,16,18,20,22,24,1,3,5,7,9,11,14,16,18,20) + * TCH/H(0): B1(8,10,13,15,17,19,21,23,0,2,4,6,8,10,13,15,17,19,21,23,0,2) + * TCH/H(1): B1(9,11,14,16,18,20,22,24,1,3,5,7,9,11,14,16,18,20,22,24,1,3) + * TCH/H(0): B2(17,19,21,23,0,2,4,6,8,10,13,15,17,19,21,23,0,2,4,6,8,10) + * TCH/H(1): B2(18,20,22,24,1,3,5,7,9,11,14,16,18,20,22,24,1,3,5,7,9,11) + * + * TDMA frame number of burst 'a' % 26 is the table index. + * This mapping is valid for both TCH/H(0) and TCH/H(1). */ +static const uint8_t sched_tchh_ul_csd_map[26] = { + [0] = 1, /* TCH/H(0): B0(0 ... 19) */ + [1] = 1, /* TCH/H(1): B0(1 ... 20) */ + [8] = 1, /* TCH/H(0): B1(8 ... 2) */ + [9] = 1, /* TCH/H(1): B1(9 ... 3) */ + [17] = 1, /* TCH/H(0): B2(17 ... 10) */ + [18] = 1, /* TCH/H(1): B2(18 ... 11) */ +}; + +/* TDMA frame number of burst 'v' % 26 is the table index. + * This mapping is valid for both TCH/H(0) and TCH/H(1). */ +static const uint8_t sched_tchh_dl_csd_map[26] = { + [19] = 1, /* TCH/H(0): B0(0 ... 19) */ + [20] = 1, /* TCH/H(1): B0(1 ... 20) */ + [2] = 1, /* TCH/H(0): B1(8 ... 2) */ + [3] = 1, /* TCH/H(1): B1(9 ... 3) */ + [10] = 1, /* TCH/H(0): B2(17 ... 10) */ + [11] = 1, /* TCH/H(1): B2(18 ... 11) */ +}; + +/** + * Can a TCH/H block transmission be initiated / finished + * on a given frame number and a given channel type? + * + * See GSM 05.02, clause 7, table 1 + * + * @param chan channel type (L1SCHED_TCHH_0 or L1SCHED_TCHH_1) + * @param fn the current frame number + * @param ul Uplink or Downlink? + * @param facch FACCH/H or traffic? + * @param start init or end of transmission? + * @return true (yes) or false (no) + */ +bool l1sched_tchh_block_map_fn(enum l1sched_lchan_type chan, + uint32_t fn, bool ul, bool facch, bool start) +{ + uint8_t fn_mf; + int i = 0; + + /* Just to be sure */ + OSMO_ASSERT(chan == L1SCHED_TCHH_0 || chan == L1SCHED_TCHH_1); + + /* Calculate a modulo */ + fn_mf = facch ? (fn % 26) : (fn % 13); + +#define MAP_GET_POS(map) \ + (start ? 0 : ARRAY_SIZE(map[i]) - 1) + +#define BLOCK_MAP_FN(map) \ + do { \ + if (map[i][MAP_GET_POS(map)] == fn_mf) \ + return true; \ + } while (++i < ARRAY_SIZE(map)) + + /* Choose a proper block map */ + if (facch) { + if (ul) { + if (chan == L1SCHED_TCHH_0) + BLOCK_MAP_FN(tch_h0_ul_facch_block_map); + else + BLOCK_MAP_FN(tch_h1_ul_facch_block_map); + } else { + if (chan == L1SCHED_TCHH_0) + BLOCK_MAP_FN(tch_h0_dl_facch_block_map); + else + BLOCK_MAP_FN(tch_h1_dl_facch_block_map); + } + } else { + if (chan == L1SCHED_TCHH_0) + BLOCK_MAP_FN(tch_h0_traffic_block_map); + else + BLOCK_MAP_FN(tch_h1_traffic_block_map); + } + + return false; +} + +static int decode_hr_facch(struct l1sched_lchan_state *lchan) +{ + uint8_t data[GSM_MACBLOCK_LEN]; + int n_errors, n_bits_total; + int rc; + + rc = gsm0503_tch_hr_facch_decode(&data[0], BUFTAIL8(lchan->rx_bursts), + &n_errors, &n_bits_total); + if (rc != GSM_MACBLOCK_LEN) + return rc; + + /* calculate AVG of the measurements (FACCH/H takes 6 bursts) */ + l1sched_lchan_meas_avg(lchan, 6); + + l1sched_lchan_emit_data_ind(lchan, &data[0], GSM_MACBLOCK_LEN, + n_errors, n_bits_total, false); + + return GSM_MACBLOCK_LEN; +} + +int rx_tchh_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi) +{ + int n_errors = -1, n_bits_total = 0, rc; + sbit_t *bursts_p, *burst; + uint8_t tch_data[240]; + size_t tch_data_len; + uint32_t *mask; + int amr = 0; + uint8_t ft; + + /* Set up pointers */ + mask = &lchan->rx_burst_mask; + bursts_p = lchan->rx_bursts; + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "Traffic received: fn=%u bid=%u\n", bi->fn, bi->bid); + + if (bi->bid == 0) { + /* Shift the burst buffer by 2 bursts leftwards */ + memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 2), 20 * BPLEN); + memset(BUFPOS(bursts_p, 20), 0, 2 * BPLEN); + *mask = *mask << 2; + } + + if (*mask == 0x00) { + /* Align to the first burst */ + if (bi->bid > 0) + return 0; + + /* Align reception of the first FACCH/H frame */ + if (lchan->tch_mode == GSM48_CMODE_SIGN) { + if (!l1sched_tchh_facch_start(lchan->type, bi->fn, 0)) + return 0; + } + } + + /* Update mask */ + *mask |= (1 << bi->bid); + + /* Store the measurements */ + l1sched_lchan_meas_push(lchan, bi); + + /* Copy burst to the end of buffer of 24 bursts */ + burst = BUFPOS(bursts_p, 20 + bi->bid); + memcpy(burst, bi->burst + 3, 58); + memcpy(burst + 58, bi->burst + 87, 58); + + /* Wait until the second burst */ + if (bi->bid != 1) + return 0; + + /* Wait for complete set of bursts */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + /* FACCH/H is interleaved over 6 bursts */ + if ((*mask & 0x3f) != 0x3f) + return 0; + break; + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + /* Data (CSD) is interleaved over 22 bursts */ + if ((*mask & 0x3fffff) != 0x3fffff) + return 0; + if (!sched_tchh_dl_csd_map[bi->fn % 26]) + return 0; /* CSD: skip decoding attempt, need 2 more bursts */ + break; + default: + /* Speech is interleaved over 4 bursts */ + if ((*mask & 0x0f) != 0x0f) + return 0; + break; + } + + /* Skip decoding attempt in case of FACCH/H */ + if (lchan->dl_ongoing_facch) { + /* Send BFI (DATA.ind without payload) for the 2nd stolen TCH frame */ + l1sched_lchan_meas_avg(lchan, 4); + l1sched_lchan_emit_data_ind(lchan, NULL, 0, 0, 0, true); + lchan->dl_ongoing_facch = false; + return 0; + } + + /* TCH/H: speech and signalling frames are interleaved over 4 and 6 bursts, + * respectively, while CSD frames are interleaved over 22 bursts. Unless + * we're in CSD mode, decode only the last 6 bursts to avoid introducing + * additional delays. */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + case GSM48_CMODE_SPEECH_V1: /* HR */ + rc = gsm0503_tch_hr_decode(&tch_data[0], BUFTAIL8(bursts_p), + !sched_tchh_dl_facch_map[bi->fn % 26], + &n_errors, &n_bits_total); + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + /* See comment in function rx_tchf_fn() */ + amr = 2; + rc = gsm0503_tch_ahs_decode_dtx(&tch_data[amr], BUFTAIL8(bursts_p), + !sched_tchh_dl_facch_map[bi->fn % 26], + !sched_tchh_dl_amr_cmi_map[bi->fn % 26], + lchan->amr.codec, + lchan->amr.codecs, + &lchan->amr.dl_ft, + &lchan->amr.dl_cmr, + &n_errors, &n_bits_total, + &lchan->amr.last_dtx); + + /* only good speech frames get rtp header */ + if (rc != GSM_MACBLOCK_LEN && rc >= 4) { + if (lchan->amr.last_dtx == AMR_OTHER) { + ft = lchan->amr.codec[lchan->amr.dl_ft]; + } else { + /* SID frames will always get Frame Type Index 8 (AMR_SID) */ + ft = AMR_SID; + } + rc = osmo_amr_rtp_enc(&tch_data[0], + lchan->amr.codec[lchan->amr.dl_cmr], + ft, AMR_GOOD); + if (rc < 0) + LOGP_LCHAND(lchan, LOGL_ERROR, + "osmo_amr_rtp_enc() returned rc=%d\n", rc); + } + break; + /* CSD (TCH/H4.8): 6.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_6k0: + /* FACCH/H does not steal TCH/H4.8 frames, but only disturbs some bits */ + decode_hr_facch(lchan); + rc = gsm0503_tch_hr48_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + break; + /* CSD (TCH/H2.4): 3.6 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_3k6: + /* FACCH/H does not steal TCH/H2.4 frames, but only disturbs some bits */ + decode_hr_facch(lchan); + rc = gsm0503_tch_hr24_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + break; + default: + LOGP_LCHAND(lchan, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode); + return -EINVAL; + } + + /* Check decoding result */ + if (rc < 4) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Received bad frame (rc=%d, ber=%d/%d) at fn=%u\n", + rc, n_errors, n_bits_total, lchan->meas_avg.fn); + + /* Send BFI (DATA.ind without payload) */ + tch_data_len = 0; + } else if (rc == GSM_MACBLOCK_LEN) { + /* Skip decoding of the next 2 stolen bursts */ + lchan->dl_ongoing_facch = true; + + /* Calculate AVG of the measurements (FACCH/H takes 6 bursts) */ + l1sched_lchan_meas_avg(lchan, 6); + + /* FACCH/H received, forward to the higher layers */ + l1sched_lchan_emit_data_ind(lchan, &tch_data[amr], GSM_MACBLOCK_LEN, + n_errors, n_bits_total, false); + + /* Send BFI (DATA.ind without payload) for the 1st stolen TCH frame */ + if (lchan->tch_mode == GSM48_CMODE_SIGN) + return 0; + tch_data_len = 0; + } else { + /* A good TCH frame received */ + tch_data_len = rc; + } + + /* Calculate AVG of the measurements (traffic takes 4 bursts) */ + l1sched_lchan_meas_avg(lchan, 4); + + /* Send a traffic frame to the higher layers */ + return l1sched_lchan_emit_data_ind(lchan, &tch_data[0], tch_data_len, + n_errors, n_bits_total, true); +} + +int tx_tchh_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br) +{ + struct msgb *msg_facch, *msg_tch, *msg; + ubit_t *bursts_p, *burst; + const uint8_t *tsc; + uint32_t *mask; + int rc; + + /* Set up pointers */ + mask = &lchan->tx_burst_mask; + bursts_p = lchan->tx_bursts; + + if (br->bid > 0) { + if ((*mask & 0x01) != 0x01) + return -ENOENT; + goto send_burst; + } + + if (*mask == 0x00) { + /* Align transmission of the first frame */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + if (!l1sched_tchh_facch_start(lchan->type, br->fn, 1)) + return 0; + break; + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + if (!sched_tchh_ul_csd_map[br->fn % 26]) + return 0; + break; + } + } + + /* Shift the burst buffer by 2 bursts leftwards for interleaving */ + memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 2), 20 * BPLEN); + memset(BUFPOS(bursts_p, 20), 0, 2 * BPLEN); + *mask = *mask << 2; + + /* If FACCH/H blocks are still pending */ + if (lchan->ul_facch_blocks > 2) { + struct msgb *msg = l1sched_lchan_prim_dequeue_tch(lchan, false); + msgb_free(msg); /* drop 2nd TCH/HS block */ + goto send_burst; + } + + switch (lchan->tch_mode) { + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + /* CSD: skip dequeueing/encoding, send 2 more bursts */ + if (!sched_tchh_ul_csd_map[br->fn % 26]) + goto send_burst; + break; + } + + /* dequeue a pair of TCH and FACCH frames */ + msg_tch = l1sched_lchan_prim_dequeue_tch(lchan, false); + if (l1sched_tchh_facch_start(lchan->type, br->fn, true)) + msg_facch = l1sched_lchan_prim_dequeue_tch(lchan, true); + else + msg_facch = NULL; + /* prioritize FACCH over TCH */ + msg = (msg_facch != NULL) ? msg_facch : msg_tch; + + /* populate the buffer with bursts */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + if (!l1sched_tchh_facch_start(lchan->type, br->fn, 1)) + goto send_burst; /* XXX: should not happen */ + if (msg == NULL) + msg = l1sched_lchan_prim_dummy_lapdm(lchan); + /* fall-through */ + case GSM48_CMODE_SPEECH_V1: + /* if msg == NULL, transmit a dummy speech block with inverted CRC3 */ + rc = gsm0503_tch_hr_encode(BUFPOS(bursts_p, 0), + msg ? msgb_l2(msg) : NULL, + msg ? msgb_l2len(msg) : 0); + /* confirm traffic sending (pass ownership of the msgb/prim) */ + if (OSMO_LIKELY(rc == 0)) { + if (msg && msgb_l2len(msg) == GSM_MACBLOCK_LEN) + lchan->ul_facch_blocks = 6; + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } else /* unlikely: encoding failed, drop msgb/prim */ + msgb_free(msg); + /* drop the other msgb/prim */ + msgb_free((msg == msg_facch) ? msg_tch : msg_facch); + break; + case GSM48_CMODE_SPEECH_AMR: + { + bool amr_fn_is_cmr = !sched_tchh_ul_amr_cmi_map[br->fn % 26]; + unsigned int offset = 0; + + if (msg != NULL && msg != msg_facch) { /* TCH/AHS: speech */ + if (!l1sched_lchan_amr_prim_is_valid(lchan, msg, amr_fn_is_cmr)) { + msgb_free(msg); + msg_tch = NULL; + msg = NULL; + } + /* pull the AMR header - sizeof(struct amr_hdr) */ + offset = 2; + } + + /* if msg == NULL, transmit a dummy speech block with inverted CRC6 */ + rc = gsm0503_tch_ahs_encode(BUFPOS(bursts_p, 0), + msg ? msgb_l2(msg) + offset : NULL, + msg ? msgb_l2len(msg) - offset : 0, + amr_fn_is_cmr, + lchan->amr.codec, + lchan->amr.codecs, + lchan->amr.ul_ft, + lchan->amr.ul_cmr); + /* confirm traffic sending (pass ownership of the msgb/prim) */ + if (OSMO_LIKELY(rc == 0)) { + if (msg && msgb_l2len(msg) == GSM_MACBLOCK_LEN) + lchan->ul_facch_blocks = 6; + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } else /* unlikely: encoding failed, drop msgb/prim */ + msgb_free(msg); + /* drop the other msgb/prim */ + msgb_free((msg == msg_facch) ? msg_tch : msg_facch); + break; + } + /* CSD (TCH/H4.8): 6.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_6k0: + if ((msg = msg_tch) != NULL) { + OSMO_ASSERT(msgb_l2len(msg) == 4 * 60); + gsm0503_tch_hr48_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm data sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } else { + ubit_t idle[4 * 60]; + memset(&idle[0], 0x01, sizeof(idle)); + gsm0503_tch_hr48_encode(BUFPOS(bursts_p, 0), &idle[0]); + } + if ((msg = msg_facch) != NULL) { + gsm0503_tch_hr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm FACCH sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } + break; + /* CSD (TCH/H2.4): 3.6 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_3k6: + if ((msg = msg_tch) != NULL) { + OSMO_ASSERT(msgb_l2len(msg) == 4 * 36); + gsm0503_tch_hr24_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm data sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } else { + ubit_t idle[4 * 36]; + memset(&idle[0], 0x01, sizeof(idle)); + gsm0503_tch_hr24_encode(BUFPOS(bursts_p, 0), &idle[0]); + } + if ((msg = msg_facch) != NULL) { + gsm0503_tch_hr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm FACCH sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } + break; + default: + LOGP_LCHAND(lchan, LOGL_ERROR, + "TCH mode %s is unknown or not supported\n", + gsm48_chan_mode_name(lchan->tch_mode)); + msgb_free(msg_facch); + msgb_free(msg_tch); + break; + } + +send_burst: + /* Determine which burst should be sent */ + burst = BUFPOS(bursts_p, br->bid); + + /* Update mask */ + *mask |= (1 << br->bid); + + /* Choose proper TSC */ + tsc = l1sched_nb_training_bits[lchan->tsc]; + + /* Compose a new burst */ + memset(br->burst, 0, 3); /* TB */ + memcpy(br->burst + 3, burst, 58); /* Payload 1/2 */ + memcpy(br->burst + 61, tsc, 26); /* TSC */ + memcpy(br->burst + 87, burst + 58, 58); /* Payload 2/2 */ + memset(br->burst + 145, 0, 3); /* TB */ + br->burst_len = GSM_NBITS_NB_GMSK_BURST; + + LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled fn=%u burst=%u\n", br->fn, br->bid); + + /* In case of a FACCH/H frame, one block less */ + if (lchan->ul_facch_blocks) + lchan->ul_facch_blocks--; + + return 0; +} diff --git a/src/host/trxcon/src/sched_lchan_xcch.c b/src/host/trxcon/src/sched_lchan_xcch.c new file mode 100644 index 00000000..52b5d1e6 --- /dev/null +++ b/src/host/trxcon/src/sched_lchan_xcch.c @@ -0,0 +1,181 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: handlers for DL / UL bursts on logical channels + * + * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * Contributions by sysmocom - s.f.m.c. GmbH + * + * 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. + * + */ + +#include <errno.h> +#include <string.h> +#include <stdint.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/bits.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/coding/gsm0503_coding.h> + +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> + +int rx_data_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi) +{ + uint8_t l2[GSM_MACBLOCK_LEN]; + int n_errors, n_bits_total, rc; + sbit_t *bursts_p, *burst; + uint32_t *mask; + + /* Set up pointers */ + mask = &lchan->rx_burst_mask; + bursts_p = lchan->rx_bursts; + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "Data received: fn=%u bid=%u\n", bi->fn, bi->bid); + + /* Align to the first burst of a block */ + if (*mask == 0x00 && bi->bid != 0) + return 0; + + /* Update mask */ + *mask |= (1 << bi->bid); + + /* Store the measurements */ + l1sched_lchan_meas_push(lchan, bi); + + /* Copy burst to buffer of 4 bursts */ + burst = bursts_p + bi->bid * 116; + memcpy(burst, bi->burst + 3, 58); + memcpy(burst + 58, bi->burst + 87, 58); + + /* Wait until complete set of bursts */ + if (bi->bid != 3) + return 0; + + /* Calculate AVG of the measurements */ + l1sched_lchan_meas_avg(lchan, 4); + + /* Check for complete set of bursts */ + if ((*mask & 0xf) != 0xf) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Received incomplete (%s) data frame at fn=%u (%u/%u)\n", + l1sched_burst_mask2str(mask, 4), lchan->meas_avg.fn, + lchan->meas_avg.fn % lchan->ts->mf_layout->period, + lchan->ts->mf_layout->period); + /* NOTE: xCCH has an insane amount of redundancy for error + * correction, so even just 2 valid bursts might be enough + * to reconstruct some L2 frames. This is why we do not + * abort here. */ + } + + /* Keep the mask updated */ + *mask = *mask << 4; + + /* Attempt to decode */ + rc = gsm0503_xcch_decode(l2, bursts_p, &n_errors, &n_bits_total); + if (rc) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Received bad frame (rc=%d, ber=%d/%d) at fn=%u\n", + rc, n_errors, n_bits_total, lchan->meas_avg.fn); + } + + /* Send a L2 frame to the higher layers */ + return l1sched_lchan_emit_data_ind(lchan, l2, rc ? 0 : GSM_MACBLOCK_LEN, + n_errors, n_bits_total, false); +} + +static struct msgb *prim_dequeue_xcch(struct l1sched_lchan_state *lchan) +{ + struct msgb *msg; + + if (L1SCHED_CHAN_IS_SACCH(lchan->type)) + return l1sched_lchan_prim_dequeue_sacch(lchan); + if ((msg = msgb_dequeue(&lchan->tx_prims)) == NULL) + return NULL; + + /* Check the prim payload length */ + if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Primitive has odd length %u (expected %u), so dropping...\n", + msgb_l2len(msg), GSM_MACBLOCK_LEN); + msgb_free(msg); + return NULL; + } + + return msg; +} + +int tx_data_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br) +{ + ubit_t *bursts_p, *burst; + const uint8_t *tsc; + uint32_t *mask; + int rc; + + /* Set up pointers */ + mask = &lchan->tx_burst_mask; + bursts_p = lchan->tx_bursts; + + if (br->bid > 0) { + if ((*mask & 0x01) != 0x01) + return -ENOENT; + goto send_burst; + } + + *mask = *mask << 4; + + struct msgb *msg = prim_dequeue_xcch(lchan); + if (msg == NULL) + msg = l1sched_lchan_prim_dummy_lapdm(lchan); + OSMO_ASSERT(msg != NULL); + + /* Encode payload */ + rc = gsm0503_xcch_encode(bursts_p, msgb_l2(msg)); + if (rc) { + LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%u): %s\n", + msgb_l2len(msg), msgb_hexdump_l2(msg)); + msgb_free(msg); + return -EINVAL; + } + + /* Confirm data sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + +send_burst: + /* Determine which burst should be sent */ + burst = bursts_p + br->bid * 116; + + /* Update mask */ + *mask |= (1 << br->bid); + + /* Choose proper TSC */ + tsc = l1sched_nb_training_bits[lchan->tsc]; + + /* Compose a new burst */ + memset(br->burst, 0, 3); /* TB */ + memcpy(br->burst + 3, burst, 58); /* Payload 1/2 */ + memcpy(br->burst + 61, tsc, 26); /* TSC */ + memcpy(br->burst + 87, burst + 58, 58); /* Payload 2/2 */ + memset(br->burst + 145, 0, 3); /* TB */ + br->burst_len = GSM_NBITS_NB_GMSK_BURST; + + LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled fn=%u burst=%u\n", br->fn, br->bid); + + return 0; +} diff --git a/src/host/trxcon/src/sched_mframe.c b/src/host/trxcon/src/sched_mframe.c new file mode 100644 index 00000000..0d95e0ac --- /dev/null +++ b/src/host/trxcon/src/sched_mframe.c @@ -0,0 +1,2102 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: channel combinations, burst mapping + * + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> + * (C) 2015 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 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 <osmocom/gsm/gsm_utils.h> + +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> + +/* Non-combined CCCH */ +static const struct l1sched_tdma_frame frame_bcch[51] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_RACH, 0 }, +}; + +/* Combined CCCH+SDCCH4 */ +static const struct l1sched_tdma_frame frame_bcch_sdcch4[102] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_3, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_3, 1 }, + { L1SCHED_BCCH, 0, L1SCHED_SDCCH4_3, 2 }, + { L1SCHED_BCCH, 1, L1SCHED_SDCCH4_3, 3 }, + { L1SCHED_BCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_2, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_2, 1 }, + { L1SCHED_CCCH, 2, L1SCHED_SACCH4_2, 2 }, + { L1SCHED_CCCH, 3, L1SCHED_SACCH4_2, 3 }, + { L1SCHED_FCCH, 0, L1SCHED_SACCH4_3, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SACCH4_3, 1 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_3, 2 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_3, 3 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 1, L1SCHED_SDCCH4_0, 0 }, + { L1SCHED_SDCCH4_3, 2, L1SCHED_SDCCH4_0, 1 }, + { L1SCHED_SDCCH4_3, 3, L1SCHED_SDCCH4_0, 2 }, + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_0, 3 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_1, 0 }, + { L1SCHED_SACCH4_0, 0, L1SCHED_SDCCH4_1, 1 }, + { L1SCHED_SACCH4_0, 1, L1SCHED_SDCCH4_1, 2 }, + { L1SCHED_SACCH4_0, 2, L1SCHED_SDCCH4_1, 3 }, + { L1SCHED_SACCH4_0, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_1, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_1, 1, L1SCHED_SDCCH4_2, 0 }, + { L1SCHED_SACCH4_1, 2, L1SCHED_SDCCH4_2, 1 }, + { L1SCHED_SACCH4_1, 3, L1SCHED_SDCCH4_2, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_SDCCH4_2, 3 }, + + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_3, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_3, 1 }, + { L1SCHED_BCCH, 0, L1SCHED_SDCCH4_3, 2 }, + { L1SCHED_BCCH, 1, L1SCHED_SDCCH4_3, 3 }, + { L1SCHED_BCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_0, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_0, 1 }, + { L1SCHED_CCCH, 2, L1SCHED_SACCH4_0, 2 }, + { L1SCHED_CCCH, 3, L1SCHED_SACCH4_0, 3 }, + { L1SCHED_FCCH, 0, L1SCHED_SACCH4_1, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SACCH4_1, 1 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_1, 2 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_1, 3 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 1, L1SCHED_SDCCH4_0, 0 }, + { L1SCHED_SDCCH4_3, 2, L1SCHED_SDCCH4_0, 1 }, + { L1SCHED_SDCCH4_3, 3, L1SCHED_SDCCH4_0, 2 }, + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_0, 3 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_1, 0 }, + { L1SCHED_SACCH4_2, 0, L1SCHED_SDCCH4_1, 1 }, + { L1SCHED_SACCH4_2, 1, L1SCHED_SDCCH4_1, 2 }, + { L1SCHED_SACCH4_2, 2, L1SCHED_SDCCH4_1, 3 }, + { L1SCHED_SACCH4_2, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_3, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_3, 1, L1SCHED_SDCCH4_2, 0 }, + { L1SCHED_SACCH4_3, 2, L1SCHED_SDCCH4_2, 1 }, + { L1SCHED_SACCH4_3, 3, L1SCHED_SDCCH4_2, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_SDCCH4_2, 3 }, +}; + +static const struct l1sched_tdma_frame frame_bcch_sdcch4_cbch[102] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_3, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_3, 1 }, + { L1SCHED_BCCH, 0, L1SCHED_SDCCH4_3, 2 }, + { L1SCHED_BCCH, 1, L1SCHED_SDCCH4_3, 3 }, + { L1SCHED_BCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_IDLE, 1 }, + { L1SCHED_CCCH, 2, L1SCHED_IDLE, 2 }, + { L1SCHED_CCCH, 3, L1SCHED_IDLE, 3 }, + { L1SCHED_FCCH, 0, L1SCHED_SACCH4_3, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SACCH4_3, 1 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_3, 2 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_3, 3 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 1, L1SCHED_SDCCH4_0, 0 }, + { L1SCHED_SDCCH4_3, 2, L1SCHED_SDCCH4_0, 1 }, + { L1SCHED_SDCCH4_3, 3, L1SCHED_SDCCH4_0, 2 }, + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_0, 3 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_1, 0 }, + { L1SCHED_SACCH4_0, 0, L1SCHED_SDCCH4_1, 1 }, + { L1SCHED_SACCH4_0, 1, L1SCHED_SDCCH4_1, 2 }, + { L1SCHED_SACCH4_0, 2, L1SCHED_SDCCH4_1, 3 }, + { L1SCHED_SACCH4_0, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_1, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_1, 1, L1SCHED_IDLE, 0 }, + { L1SCHED_SACCH4_1, 2, L1SCHED_IDLE, 1 }, + { L1SCHED_SACCH4_1, 3, L1SCHED_IDLE, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 3 }, + + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_3, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_3, 1 }, + { L1SCHED_BCCH, 0, L1SCHED_SDCCH4_3, 2 }, + { L1SCHED_BCCH, 1, L1SCHED_SDCCH4_3, 3 }, + { L1SCHED_BCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_0, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_0, 1 }, + { L1SCHED_CCCH, 2, L1SCHED_SACCH4_0, 2 }, + { L1SCHED_CCCH, 3, L1SCHED_SACCH4_0, 3 }, + { L1SCHED_FCCH, 0, L1SCHED_SACCH4_1, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SACCH4_1, 1 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_1, 2 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_1, 3 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 1, L1SCHED_SDCCH4_0, 0 }, + { L1SCHED_SDCCH4_3, 2, L1SCHED_SDCCH4_0, 1 }, + { L1SCHED_SDCCH4_3, 3, L1SCHED_SDCCH4_0, 2 }, + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_0, 3 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_1, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_SDCCH4_1, 1 }, + { L1SCHED_IDLE, 1, L1SCHED_SDCCH4_1, 2 }, + { L1SCHED_IDLE, 2, L1SCHED_SDCCH4_1, 3 }, + { L1SCHED_IDLE, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_3, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_3, 1, L1SCHED_IDLE, 0 }, + { L1SCHED_SACCH4_3, 2, L1SCHED_IDLE, 1 }, + { L1SCHED_SACCH4_3, 3, L1SCHED_IDLE, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 3 }, +}; + +static const struct l1sched_tdma_frame frame_sdcch8[102] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_SDCCH8_0, 0, L1SCHED_SACCH8_5, 0 }, + { L1SCHED_SDCCH8_0, 1, L1SCHED_SACCH8_5, 1 }, + { L1SCHED_SDCCH8_0, 2, L1SCHED_SACCH8_5, 2 }, + { L1SCHED_SDCCH8_0, 3, L1SCHED_SACCH8_5, 3 }, + { L1SCHED_SDCCH8_1, 0, L1SCHED_SACCH8_6, 0 }, + { L1SCHED_SDCCH8_1, 1, L1SCHED_SACCH8_6, 1 }, + { L1SCHED_SDCCH8_1, 2, L1SCHED_SACCH8_6, 2 }, + { L1SCHED_SDCCH8_1, 3, L1SCHED_SACCH8_6, 3 }, + { L1SCHED_SDCCH8_2, 0, L1SCHED_SACCH8_7, 0 }, + { L1SCHED_SDCCH8_2, 1, L1SCHED_SACCH8_7, 1 }, + { L1SCHED_SDCCH8_2, 2, L1SCHED_SACCH8_7, 2 }, + { L1SCHED_SDCCH8_2, 3, L1SCHED_SACCH8_7, 3 }, + { L1SCHED_SDCCH8_3, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 1, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 2, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 3, L1SCHED_SDCCH8_0, 0 }, + { L1SCHED_SDCCH8_4, 0, L1SCHED_SDCCH8_0, 1 }, + { L1SCHED_SDCCH8_4, 1, L1SCHED_SDCCH8_0, 2 }, + { L1SCHED_SDCCH8_4, 2, L1SCHED_SDCCH8_0, 3 }, + { L1SCHED_SDCCH8_4, 3, L1SCHED_SDCCH8_1, 0 }, + { L1SCHED_SDCCH8_5, 0, L1SCHED_SDCCH8_1, 1 }, + { L1SCHED_SDCCH8_5, 1, L1SCHED_SDCCH8_1, 2 }, + { L1SCHED_SDCCH8_5, 2, L1SCHED_SDCCH8_1, 3 }, + { L1SCHED_SDCCH8_5, 3, L1SCHED_SDCCH8_2, 0 }, + { L1SCHED_SDCCH8_6, 0, L1SCHED_SDCCH8_2, 1 }, + { L1SCHED_SDCCH8_6, 1, L1SCHED_SDCCH8_2, 2 }, + { L1SCHED_SDCCH8_6, 2, L1SCHED_SDCCH8_2, 3 }, + { L1SCHED_SDCCH8_6, 3, L1SCHED_SDCCH8_3, 0 }, + { L1SCHED_SDCCH8_7, 0, L1SCHED_SDCCH8_3, 1 }, + { L1SCHED_SDCCH8_7, 1, L1SCHED_SDCCH8_3, 2 }, + { L1SCHED_SDCCH8_7, 2, L1SCHED_SDCCH8_3, 3 }, + { L1SCHED_SDCCH8_7, 3, L1SCHED_SDCCH8_4, 0 }, + { L1SCHED_SACCH8_0, 0, L1SCHED_SDCCH8_4, 1 }, + { L1SCHED_SACCH8_0, 1, L1SCHED_SDCCH8_4, 2 }, + { L1SCHED_SACCH8_0, 2, L1SCHED_SDCCH8_4, 3 }, + { L1SCHED_SACCH8_0, 3, L1SCHED_SDCCH8_5, 0 }, + { L1SCHED_SACCH8_1, 0, L1SCHED_SDCCH8_5, 1 }, + { L1SCHED_SACCH8_1, 1, L1SCHED_SDCCH8_5, 2 }, + { L1SCHED_SACCH8_1, 2, L1SCHED_SDCCH8_5, 3 }, + { L1SCHED_SACCH8_1, 3, L1SCHED_SDCCH8_6, 0 }, + { L1SCHED_SACCH8_2, 0, L1SCHED_SDCCH8_6, 1 }, + { L1SCHED_SACCH8_2, 1, L1SCHED_SDCCH8_6, 2 }, + { L1SCHED_SACCH8_2, 2, L1SCHED_SDCCH8_6, 3 }, + { L1SCHED_SACCH8_2, 3, L1SCHED_SDCCH8_7, 0 }, + { L1SCHED_SACCH8_3, 0, L1SCHED_SDCCH8_7, 1 }, + { L1SCHED_SACCH8_3, 1, L1SCHED_SDCCH8_7, 2 }, + { L1SCHED_SACCH8_3, 2, L1SCHED_SDCCH8_7, 3 }, + { L1SCHED_SACCH8_3, 3, L1SCHED_SACCH8_0, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_0, 1 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_0, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_0, 3 }, + + { L1SCHED_SDCCH8_0, 0, L1SCHED_SACCH8_1, 0 }, + { L1SCHED_SDCCH8_0, 1, L1SCHED_SACCH8_1, 1 }, + { L1SCHED_SDCCH8_0, 2, L1SCHED_SACCH8_1, 2 }, + { L1SCHED_SDCCH8_0, 3, L1SCHED_SACCH8_1, 3 }, + { L1SCHED_SDCCH8_1, 0, L1SCHED_SACCH8_2, 0 }, + { L1SCHED_SDCCH8_1, 1, L1SCHED_SACCH8_2, 1 }, + { L1SCHED_SDCCH8_1, 2, L1SCHED_SACCH8_2, 2 }, + { L1SCHED_SDCCH8_1, 3, L1SCHED_SACCH8_2, 3 }, + { L1SCHED_SDCCH8_2, 0, L1SCHED_SACCH8_3, 0 }, + { L1SCHED_SDCCH8_2, 1, L1SCHED_SACCH8_3, 1 }, + { L1SCHED_SDCCH8_2, 2, L1SCHED_SACCH8_3, 2 }, + { L1SCHED_SDCCH8_2, 3, L1SCHED_SACCH8_3, 3 }, + { L1SCHED_SDCCH8_3, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 1, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 2, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 3, L1SCHED_SDCCH8_0, 0 }, + { L1SCHED_SDCCH8_4, 0, L1SCHED_SDCCH8_0, 1 }, + { L1SCHED_SDCCH8_4, 1, L1SCHED_SDCCH8_0, 2 }, + { L1SCHED_SDCCH8_4, 2, L1SCHED_SDCCH8_0, 3 }, + { L1SCHED_SDCCH8_4, 3, L1SCHED_SDCCH8_1, 0 }, + { L1SCHED_SDCCH8_5, 0, L1SCHED_SDCCH8_1, 1 }, + { L1SCHED_SDCCH8_5, 1, L1SCHED_SDCCH8_1, 2 }, + { L1SCHED_SDCCH8_5, 2, L1SCHED_SDCCH8_1, 3 }, + { L1SCHED_SDCCH8_5, 3, L1SCHED_SDCCH8_2, 0 }, + { L1SCHED_SDCCH8_6, 0, L1SCHED_SDCCH8_2, 1 }, + { L1SCHED_SDCCH8_6, 1, L1SCHED_SDCCH8_2, 2 }, + { L1SCHED_SDCCH8_6, 2, L1SCHED_SDCCH8_2, 3 }, + { L1SCHED_SDCCH8_6, 3, L1SCHED_SDCCH8_3, 0 }, + { L1SCHED_SDCCH8_7, 0, L1SCHED_SDCCH8_3, 1 }, + { L1SCHED_SDCCH8_7, 1, L1SCHED_SDCCH8_3, 2 }, + { L1SCHED_SDCCH8_7, 2, L1SCHED_SDCCH8_3, 3 }, + { L1SCHED_SDCCH8_7, 3, L1SCHED_SDCCH8_4, 0 }, + { L1SCHED_SACCH8_4, 0, L1SCHED_SDCCH8_4, 1 }, + { L1SCHED_SACCH8_4, 1, L1SCHED_SDCCH8_4, 2 }, + { L1SCHED_SACCH8_4, 2, L1SCHED_SDCCH8_4, 3 }, + { L1SCHED_SACCH8_4, 3, L1SCHED_SDCCH8_5, 0 }, + { L1SCHED_SACCH8_5, 0, L1SCHED_SDCCH8_5, 1 }, + { L1SCHED_SACCH8_5, 1, L1SCHED_SDCCH8_5, 2 }, + { L1SCHED_SACCH8_5, 2, L1SCHED_SDCCH8_5, 3 }, + { L1SCHED_SACCH8_5, 3, L1SCHED_SDCCH8_6, 0 }, + { L1SCHED_SACCH8_6, 0, L1SCHED_SDCCH8_6, 1 }, + { L1SCHED_SACCH8_6, 1, L1SCHED_SDCCH8_6, 2 }, + { L1SCHED_SACCH8_6, 2, L1SCHED_SDCCH8_6, 3 }, + { L1SCHED_SACCH8_6, 3, L1SCHED_SDCCH8_7, 0 }, + { L1SCHED_SACCH8_7, 0, L1SCHED_SDCCH8_7, 1 }, + { L1SCHED_SACCH8_7, 1, L1SCHED_SDCCH8_7, 2 }, + { L1SCHED_SACCH8_7, 2, L1SCHED_SDCCH8_7, 3 }, + { L1SCHED_SACCH8_7, 3, L1SCHED_SACCH8_4, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_4, 1 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_4, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_4, 3 }, +}; + +static const struct l1sched_tdma_frame frame_sdcch8_cbch[102] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_SDCCH8_0, 0, L1SCHED_SACCH8_5, 0 }, + { L1SCHED_SDCCH8_0, 1, L1SCHED_SACCH8_5, 1 }, + { L1SCHED_SDCCH8_0, 2, L1SCHED_SACCH8_5, 2 }, + { L1SCHED_SDCCH8_0, 3, L1SCHED_SACCH8_5, 3 }, + { L1SCHED_SDCCH8_1, 0, L1SCHED_SACCH8_6, 0 }, + { L1SCHED_SDCCH8_1, 1, L1SCHED_SACCH8_6, 1 }, + { L1SCHED_SDCCH8_1, 2, L1SCHED_SACCH8_6, 2 }, + { L1SCHED_SDCCH8_1, 3, L1SCHED_SACCH8_6, 3 }, + { L1SCHED_SDCCH8_CBCH, 0, L1SCHED_SACCH8_7, 0 }, + { L1SCHED_SDCCH8_CBCH, 1, L1SCHED_SACCH8_7, 1 }, + { L1SCHED_SDCCH8_CBCH, 2, L1SCHED_SACCH8_7, 2 }, + { L1SCHED_SDCCH8_CBCH, 3, L1SCHED_SACCH8_7, 3 }, + { L1SCHED_SDCCH8_3, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 1, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 2, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 3, L1SCHED_SDCCH8_0, 0 }, + { L1SCHED_SDCCH8_4, 0, L1SCHED_SDCCH8_0, 1 }, + { L1SCHED_SDCCH8_4, 1, L1SCHED_SDCCH8_0, 2 }, + { L1SCHED_SDCCH8_4, 2, L1SCHED_SDCCH8_0, 3 }, + { L1SCHED_SDCCH8_4, 3, L1SCHED_SDCCH8_1, 0 }, + { L1SCHED_SDCCH8_5, 0, L1SCHED_SDCCH8_1, 1 }, + { L1SCHED_SDCCH8_5, 1, L1SCHED_SDCCH8_1, 2 }, + { L1SCHED_SDCCH8_5, 2, L1SCHED_SDCCH8_1, 3 }, + { L1SCHED_SDCCH8_5, 3, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_6, 0, L1SCHED_IDLE, 1 }, + { L1SCHED_SDCCH8_6, 1, L1SCHED_IDLE, 2 }, + { L1SCHED_SDCCH8_6, 2, L1SCHED_IDLE, 3 }, + { L1SCHED_SDCCH8_6, 3, L1SCHED_SDCCH8_3, 0 }, + { L1SCHED_SDCCH8_7, 0, L1SCHED_SDCCH8_3, 1 }, + { L1SCHED_SDCCH8_7, 1, L1SCHED_SDCCH8_3, 2 }, + { L1SCHED_SDCCH8_7, 2, L1SCHED_SDCCH8_3, 3 }, + { L1SCHED_SDCCH8_7, 3, L1SCHED_SDCCH8_4, 0 }, + { L1SCHED_SACCH8_0, 0, L1SCHED_SDCCH8_4, 1 }, + { L1SCHED_SACCH8_0, 1, L1SCHED_SDCCH8_4, 2 }, + { L1SCHED_SACCH8_0, 2, L1SCHED_SDCCH8_4, 3 }, + { L1SCHED_SACCH8_0, 3, L1SCHED_SDCCH8_5, 0 }, + { L1SCHED_SACCH8_1, 0, L1SCHED_SDCCH8_5, 1 }, + { L1SCHED_SACCH8_1, 1, L1SCHED_SDCCH8_5, 2 }, + { L1SCHED_SACCH8_1, 2, L1SCHED_SDCCH8_5, 3 }, + { L1SCHED_SACCH8_1, 3, L1SCHED_SDCCH8_6, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_SDCCH8_6, 1 }, + { L1SCHED_IDLE, 1, L1SCHED_SDCCH8_6, 2 }, + { L1SCHED_IDLE, 2, L1SCHED_SDCCH8_6, 3 }, + { L1SCHED_IDLE, 3, L1SCHED_SDCCH8_7, 0 }, + { L1SCHED_SACCH8_3, 0, L1SCHED_SDCCH8_7, 1 }, + { L1SCHED_SACCH8_3, 1, L1SCHED_SDCCH8_7, 2 }, + { L1SCHED_SACCH8_3, 2, L1SCHED_SDCCH8_7, 3 }, + { L1SCHED_SACCH8_3, 3, L1SCHED_SACCH8_0, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_0, 1 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_0, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_0, 3 }, + + { L1SCHED_SDCCH8_0, 0, L1SCHED_SACCH8_1, 0 }, + { L1SCHED_SDCCH8_0, 1, L1SCHED_SACCH8_1, 1 }, + { L1SCHED_SDCCH8_0, 2, L1SCHED_SACCH8_1, 2 }, + { L1SCHED_SDCCH8_0, 3, L1SCHED_SACCH8_1, 3 }, + { L1SCHED_SDCCH8_1, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_1, 1, L1SCHED_IDLE, 1 }, + { L1SCHED_SDCCH8_1, 2, L1SCHED_IDLE, 2 }, + { L1SCHED_SDCCH8_1, 3, L1SCHED_IDLE, 3 }, + { L1SCHED_SDCCH8_CBCH, 0, L1SCHED_SACCH8_3, 0 }, + { L1SCHED_SDCCH8_CBCH, 1, L1SCHED_SACCH8_3, 1 }, + { L1SCHED_SDCCH8_CBCH, 2, L1SCHED_SACCH8_3, 2 }, + { L1SCHED_SDCCH8_CBCH, 3, L1SCHED_SACCH8_3, 3 }, + { L1SCHED_SDCCH8_3, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 1, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 2, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 3, L1SCHED_SDCCH8_0, 0 }, + { L1SCHED_SDCCH8_4, 0, L1SCHED_SDCCH8_0, 1 }, + { L1SCHED_SDCCH8_4, 1, L1SCHED_SDCCH8_0, 2 }, + { L1SCHED_SDCCH8_4, 2, L1SCHED_SDCCH8_0, 3 }, + { L1SCHED_SDCCH8_4, 3, L1SCHED_SDCCH8_1, 0 }, + { L1SCHED_SDCCH8_5, 0, L1SCHED_SDCCH8_1, 1 }, + { L1SCHED_SDCCH8_5, 1, L1SCHED_SDCCH8_1, 2 }, + { L1SCHED_SDCCH8_5, 2, L1SCHED_SDCCH8_1, 3 }, + { L1SCHED_SDCCH8_5, 3, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_6, 0, L1SCHED_IDLE, 1 }, + { L1SCHED_SDCCH8_6, 1, L1SCHED_IDLE, 2 }, + { L1SCHED_SDCCH8_6, 2, L1SCHED_IDLE, 3 }, + { L1SCHED_SDCCH8_6, 3, L1SCHED_SDCCH8_3, 0 }, + { L1SCHED_SDCCH8_7, 0, L1SCHED_SDCCH8_3, 1 }, + { L1SCHED_SDCCH8_7, 1, L1SCHED_SDCCH8_3, 2 }, + { L1SCHED_SDCCH8_7, 2, L1SCHED_SDCCH8_3, 3 }, + { L1SCHED_SDCCH8_7, 3, L1SCHED_SDCCH8_4, 0 }, + { L1SCHED_SACCH8_4, 0, L1SCHED_SDCCH8_4, 1 }, + { L1SCHED_SACCH8_4, 1, L1SCHED_SDCCH8_4, 2 }, + { L1SCHED_SACCH8_4, 2, L1SCHED_SDCCH8_4, 3 }, + { L1SCHED_SACCH8_4, 3, L1SCHED_SDCCH8_5, 0 }, + { L1SCHED_SACCH8_5, 0, L1SCHED_SDCCH8_5, 1 }, + { L1SCHED_SACCH8_5, 1, L1SCHED_SDCCH8_5, 2 }, + { L1SCHED_SACCH8_5, 2, L1SCHED_SDCCH8_5, 3 }, + { L1SCHED_SACCH8_5, 3, L1SCHED_SDCCH8_6, 0 }, + { L1SCHED_SACCH8_6, 0, L1SCHED_SDCCH8_6, 1 }, + { L1SCHED_SACCH8_6, 1, L1SCHED_SDCCH8_6, 2 }, + { L1SCHED_SACCH8_6, 2, L1SCHED_SDCCH8_6, 3 }, + { L1SCHED_SACCH8_6, 3, L1SCHED_SDCCH8_7, 0 }, + { L1SCHED_SACCH8_7, 0, L1SCHED_SDCCH8_7, 1 }, + { L1SCHED_SACCH8_7, 1, L1SCHED_SDCCH8_7, 2 }, + { L1SCHED_SACCH8_7, 2, L1SCHED_SDCCH8_7, 3 }, + { L1SCHED_SACCH8_7, 3, L1SCHED_SACCH8_4, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_4, 1 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_4, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_4, 3 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts0[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts1[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts2[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts3[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts4[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts5[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts6[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts7[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, +}; + +static const struct l1sched_tdma_frame frame_tchh_ts01[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 0, L1SCHED_SACCHTH_0, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 0, L1SCHED_SACCHTH_1, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 1, L1SCHED_SACCHTH_0, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 1, L1SCHED_SACCHTH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 2, L1SCHED_SACCHTH_0, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 2, L1SCHED_SACCHTH_1, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 3, L1SCHED_SACCHTH_0, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 3, L1SCHED_SACCHTH_1, 3 }, +}; + +static const struct l1sched_tdma_frame frame_tchh_ts23[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 3, L1SCHED_SACCHTH_0, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 3, L1SCHED_SACCHTH_1, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 0, L1SCHED_SACCHTH_0, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 0, L1SCHED_SACCHTH_1, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 1, L1SCHED_SACCHTH_0, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 1, L1SCHED_SACCHTH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 2, L1SCHED_SACCHTH_0, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 2, L1SCHED_SACCHTH_1, 2 }, +}; + +static const struct l1sched_tdma_frame frame_tchh_ts45[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 2, L1SCHED_SACCHTH_0, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 2, L1SCHED_SACCHTH_1, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 3, L1SCHED_SACCHTH_0, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 3, L1SCHED_SACCHTH_1, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 0, L1SCHED_SACCHTH_0, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 0, L1SCHED_SACCHTH_1, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 1, L1SCHED_SACCHTH_0, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 1, L1SCHED_SACCHTH_1, 1 }, +}; + +static const struct l1sched_tdma_frame frame_tchh_ts67[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 1, L1SCHED_SACCHTH_0, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 1, L1SCHED_SACCHTH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 2, L1SCHED_SACCHTH_0, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 2, L1SCHED_SACCHTH_1, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 3, L1SCHED_SACCHTH_0, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 3, L1SCHED_SACCHTH_1, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 0, L1SCHED_SACCHTH_0, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 0, L1SCHED_SACCHTH_1, 0 }, +}; + +static const struct l1sched_tdma_frame frame_pdch[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PTCCH, 0, L1SCHED_PTCCH, 0 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PTCCH, 1, L1SCHED_PTCCH, 1 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PTCCH, 2, L1SCHED_PTCCH, 2 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PTCCH, 3, L1SCHED_PTCCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, +}; + +/* Logical channel mask for a single channel */ +#define M64(x) \ + ((uint64_t) 0x01 << x) + +/* Logical channel mask for BCCH+CCCH */ +#define M64_BCCH_CCCH \ + (uint64_t) 0x00 \ + | M64(L1SCHED_FCCH) \ + | M64(L1SCHED_SCH) \ + | M64(L1SCHED_BCCH) \ + | M64(L1SCHED_RACH) \ + | M64(L1SCHED_CCCH) + +/* Logical channel mask for SDCCH4 (with SACCH, all sub-channels) */ +#define M64_SDCCH4 \ + (uint64_t) 0x00 \ + | M64(L1SCHED_SDCCH4_0) | M64(L1SCHED_SACCH4_0) \ + | M64(L1SCHED_SDCCH4_1) | M64(L1SCHED_SACCH4_1) \ + | M64(L1SCHED_SDCCH4_2) | M64(L1SCHED_SACCH4_2) \ + | M64(L1SCHED_SDCCH4_3) | M64(L1SCHED_SACCH4_3) + +/* Logical channel mask for SDCCH8 (with SACCH, all sub-channels) */ +#define M64_SDCCH8 \ + (uint64_t) 0x00 \ + | M64(L1SCHED_SDCCH8_0) | M64(L1SCHED_SACCH8_0) \ + | M64(L1SCHED_SDCCH8_1) | M64(L1SCHED_SACCH8_1) \ + | M64(L1SCHED_SDCCH8_2) | M64(L1SCHED_SACCH8_2) \ + | M64(L1SCHED_SDCCH8_3) | M64(L1SCHED_SACCH8_3) \ + | M64(L1SCHED_SDCCH8_4) | M64(L1SCHED_SACCH8_4) \ + | M64(L1SCHED_SDCCH8_5) | M64(L1SCHED_SACCH8_5) \ + | M64(L1SCHED_SDCCH8_6) | M64(L1SCHED_SACCH8_6) \ + | M64(L1SCHED_SDCCH8_7) | M64(L1SCHED_SACCH8_7) + +/* Logical channel mask for TCH/F (with SACCH) */ +#define M64_TCHF \ + (uint64_t) 0x00 \ + | M64(L1SCHED_TCHF) | M64(L1SCHED_SACCHTF) + +/* Logical channel mask for TCH/H (with SACCH, all sub-channels) */ +#define M64_TCHH \ + (uint64_t) 0x00 \ + | M64(L1SCHED_TCHH_0) | M64(L1SCHED_SACCHTH_0) \ + | M64(L1SCHED_TCHH_1) | M64(L1SCHED_SACCHTH_1) + +/** + * A few notes about frame count: + * + * 26 frame multiframe - traffic multiframe + * 51 frame multiframe - control multiframe + * + * 102 = 2 x 51 frame multiframe + * 104 = 4 x 26 frame multiframe + */ +static const struct l1sched_tdma_multiframe layouts[] = { + { + GSM_PCHAN_NONE, "NONE", + 0, 0xff, + 0x00, + NULL + }, + { + GSM_PCHAN_CCCH, "BCCH+CCCH", + 51, 0xff, + M64_BCCH_CCCH, + frame_bcch + }, + { + GSM_PCHAN_CCCH_SDCCH4, "BCCH+CCCH+SDCCH/4+SACCH/4", + 102, 0xff, + M64_BCCH_CCCH | M64_SDCCH4, + frame_bcch_sdcch4 + }, + { + GSM_PCHAN_CCCH_SDCCH4_CBCH, "BCCH+CCCH+SDCCH/4+SACCH/4+CBCH", + 102, 0xff, + M64_BCCH_CCCH | M64_SDCCH4 | M64(L1SCHED_SDCCH4_CBCH), + frame_bcch_sdcch4_cbch + }, + { + GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH/8+SACCH/8", + 102, 0xff, + M64_SDCCH8, + frame_sdcch8 + }, + { + GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH/8+SACCH/8+CBCH", + 102, 0xff, + M64_SDCCH8 | M64(L1SCHED_SDCCH8_CBCH), + frame_sdcch8_cbch + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x01, + M64_TCHF, + frame_tchf_ts0 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x02, + M64_TCHF, + frame_tchf_ts1 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x04, + M64_TCHF, + frame_tchf_ts2 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x08, + M64_TCHF, + frame_tchf_ts3 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x10, + M64_TCHF, + frame_tchf_ts4 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x20, + M64_TCHF, + frame_tchf_ts5 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x40, + M64_TCHF, + frame_tchf_ts6 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x80, + M64_TCHF, + frame_tchf_ts7 + }, + { + GSM_PCHAN_TCH_H, "TCH/H+SACCH", + 104, 0x03, + M64_TCHH, + frame_tchh_ts01 + }, + { + GSM_PCHAN_TCH_H, "TCH/H+SACCH", + 104, 0x0c, + M64_TCHH, + frame_tchh_ts23 + }, + { + GSM_PCHAN_TCH_H, "TCH/H+SACCH", + 104, 0x30, + M64_TCHH, + frame_tchh_ts45 + }, + { + GSM_PCHAN_TCH_H, "TCH/H+SACCH", + 104, 0xc0, + M64_TCHH, + frame_tchh_ts67 + }, + { + GSM_PCHAN_PDCH, "PDCH", + 104, 0xff, + M64(L1SCHED_PDTCH) | M64(L1SCHED_PTCCH), + frame_pdch + }, +}; + +const struct l1sched_tdma_multiframe * +l1sched_mframe_layout(enum gsm_phys_chan_config config, uint8_t tn) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(layouts); i++) { + if (layouts[i].chan_config != config) + continue; + if (~layouts[i].slotmask & (1 << tn)) + continue; + return &layouts[i]; + } + + return NULL; +} diff --git a/src/host/trxcon/src/sched_prim.c b/src/host/trxcon/src/sched_prim.c new file mode 100644 index 00000000..49745570 --- /dev/null +++ b/src/host/trxcon/src/sched_prim.c @@ -0,0 +1,411 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: primitive management + * + * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * 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. + * + */ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <talloc.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/prim.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/linuxlist.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> + +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> + +#define L1SCHED_PRIM_HEADROOM 64 +#define L1SCHED_PRIM_TAILROOM 512 + +osmo_static_assert(sizeof(struct l1sched_prim) <= L1SCHED_PRIM_HEADROOM, l1sched_prim_size); + +const struct value_string l1sched_prim_type_names[] = { + { L1SCHED_PRIM_T_DATA, "DATA" }, + { L1SCHED_PRIM_T_RACH, "RACH" }, + { L1SCHED_PRIM_T_SCH, "SCH" }, + { L1SCHED_PRIM_T_PCHAN_COMB, "PCHAN_COMB" }, + { 0, NULL }, +}; + +void l1sched_prim_init(struct msgb *msg, + enum l1sched_prim_type type, + enum osmo_prim_operation op) +{ + struct l1sched_prim *prim; + + msg->l2h = msg->data; + msg->l1h = msgb_push(msg, sizeof(*prim)); + + prim = l1sched_prim_from_msgb(msg); + osmo_prim_init(&prim->oph, 0, type, op, msg); +} + +struct msgb *l1sched_prim_alloc(enum l1sched_prim_type type, + enum osmo_prim_operation op) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(L1SCHED_PRIM_HEADROOM + L1SCHED_PRIM_TAILROOM, + L1SCHED_PRIM_HEADROOM, "l1sched_prim"); + if (msg == NULL) + return NULL; + + l1sched_prim_init(msg, type, op); + + return msg; +} + +/** + * Composes a new primitive from cached RR Measurement Report. + * + * @param lchan lchan to assign a primitive + * @return SACCH primitive to be transmitted + */ +static struct msgb *prim_compose_mr(struct l1sched_lchan_state *lchan) +{ + struct l1sched_prim *prim; + struct msgb *msg; + bool cached; + + /* Allocate a new primitive */ + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->data_req = (struct l1sched_prim_chdr) { + .chan_nr = l1sched_lchan_desc[lchan->type].chan_nr | lchan->ts->index, + .link_id = L1SCHED_CH_LID_SACCH, + }; + + /* Check if the MR cache is populated (verify LAPDm header) */ + cached = (lchan->sacch.mr_cache[2] != 0x00 + && lchan->sacch.mr_cache[3] != 0x00 + && lchan->sacch.mr_cache[4] != 0x00); + if (!cached) { + memcpy(&lchan->sacch.mr_cache[0], + &lchan->ts->sched->sacch_cache[0], + sizeof(lchan->sacch.mr_cache)); + } + + /* Compose a new Measurement Report primitive */ + memcpy(msgb_put(msg, GSM_MACBLOCK_LEN), + &lchan->sacch.mr_cache[0], + GSM_MACBLOCK_LEN); + + /* Inform about the cache usage count */ + if (++lchan->sacch.mr_cache_usage > 5) { + LOGP_LCHAND(lchan, LOGL_NOTICE, + "SACCH MR cache usage count=%u > 5 " + "=> ancient measurements, please fix!\n", + lchan->sacch.mr_cache_usage); + } + + LOGP_LCHAND(lchan, LOGL_NOTICE, "Using cached Measurement Report\n"); + + return msg; +} + +/** + * Dequeues a SACCH primitive from transmit queue, if present. + * Otherwise dequeues a cached Measurement Report (the last + * received one). Finally, if the cache is empty, a "dummy" + * measurement report is used. + * + * According to 3GPP TS 04.08, section 3.4.1, SACCH channel + * accompanies either a traffic or a signaling channel. It + * has the particularity that continuous transmission must + * occur in both directions, so on the Uplink direction + * measurement result messages are sent at each possible + * occasion when nothing else has to be sent. The LAPDm + * fill frames (0x01, 0x03, 0x01, 0x2b, ...) are not + * applicable on SACCH channels! + * + * Unfortunately, 3GPP TS 04.08 doesn't clearly state + * which "else messages" besides Measurement Reports + * can be send by the MS on SACCH channels. However, + * in sub-clause 3.4.1 it's stated that the interval + * between two successive measurement result messages + * shall not exceed one L2 frame. + * + * @param lchan lchan to assign a primitive + * @return SACCH primitive to be transmitted + */ +struct msgb *l1sched_lchan_prim_dequeue_sacch(struct l1sched_lchan_state *lchan) +{ + struct msgb *msg_nmr = NULL; + struct msgb *msg_mr = NULL; + struct msgb *msg; + bool mr_now; + + /* Shall we transmit MR now? */ + mr_now = !lchan->sacch.mr_tx_last; + +#define PRIM_MSGB_IS_MR(msg) \ + (l1sched_prim_data_from_msgb(msg)[5] == GSM48_PDISC_RR && \ + l1sched_prim_data_from_msgb(msg)[6] == GSM48_MT_RR_MEAS_REP) + + /* Iterate over all primitives in the queue */ + llist_for_each_entry(msg, &lchan->tx_prims, list) { + /* Look for a Measurement Report */ + if (!msg_mr && PRIM_MSGB_IS_MR(msg)) + msg_mr = msg; + + /* Look for anything else */ + if (!msg_nmr && !PRIM_MSGB_IS_MR(msg)) + msg_nmr = msg; + + /* Should we look further? */ + if (mr_now && msg_mr) + break; /* MR was found */ + else if (!mr_now && msg_nmr) + break; /* something else was found */ + } + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "SACCH MR selection: mr_tx_last=%d msg_mr=%p msg_nmr=%p\n", + lchan->sacch.mr_tx_last, msg_mr, msg_nmr); + + /* Prioritize non-MR prim if possible */ + if (mr_now && msg_mr) + msg = msg_mr; + else if (!mr_now && msg_nmr) + msg = msg_nmr; + else if (!mr_now && msg_mr) + msg = msg_mr; + else /* Nothing was found */ + msg = NULL; + + /* Have we found what we were looking for? */ + if (msg) /* Dequeue if so */ + llist_del(&msg->list); + else /* Otherwise compose a new MR */ + msg = prim_compose_mr(lchan); + + /* Update the cached report */ + if (msg == msg_mr) { + memcpy(lchan->sacch.mr_cache, msgb_l2(msg), GSM_MACBLOCK_LEN); + lchan->sacch.mr_cache_usage = 0; + + LOGP_LCHAND(lchan, LOGL_DEBUG, "SACCH MR cache has been updated\n"); + } + + /* Update the MR transmission state */ + lchan->sacch.mr_tx_last = PRIM_MSGB_IS_MR(msg); + + LOGP_LCHAND(lchan, LOGL_DEBUG, "SACCH decision: %s\n", + PRIM_MSGB_IS_MR(msg) ? "Measurement Report" : "data frame"); + + return msg; +} + +/** + * Dequeues either a FACCH, or a speech TCH primitive + * of a given channel type (Lm or Bm). + * + * @param lchan logical channel state + * @param facch FACCH (true) or speech (false) prim? + * @return either a FACCH, or a TCH primitive if found, + * otherwise NULL + */ +struct msgb *l1sched_lchan_prim_dequeue_tch(struct l1sched_lchan_state *lchan, bool facch) +{ + struct msgb *msg; + + /** + * There is no need to use the 'safe' list iteration here + * as an item removal is immediately followed by return. + */ + llist_for_each_entry(msg, &lchan->tx_prims, list) { + bool is_facch = msgb_l2len(msg) == GSM_MACBLOCK_LEN; + if (is_facch != facch) + continue; + + llist_del(&msg->list); + return msg; + } + + return NULL; +} + +/** + * Allocate a DATA.req with dummy LAPDm func=UI frame for the given logical channel. + * To be used when no suitable DATA.req is present in the Tx queue. + * + * @param lchan lchan to allocate a dummy primitive for + * @return an msgb with DATA.req primitive, or NULL + */ +struct msgb *l1sched_lchan_prim_dummy_lapdm(const struct l1sched_lchan_state *lchan) +{ + struct l1sched_prim *prim; + struct msgb *msg; + uint8_t *ptr; + + /* LAPDm func=UI is not applicable for SACCH */ + OSMO_ASSERT(!L1SCHED_CHAN_IS_SACCH(lchan->type)); + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->data_req = (struct l1sched_prim_chdr) { + .chan_nr = l1sched_lchan_desc[lchan->type].chan_nr | lchan->ts->index, + .link_id = l1sched_lchan_desc[lchan->type].link_id, + }; + + ptr = msgb_put(msg, GSM_MACBLOCK_LEN); + + /** + * TS 144.006, section 8.4.2.3 "Fill frames" + * A fill frame is a UI command frame for SAPI 0, P=0 + * and with an information field of 0 octet length. + */ + *(ptr++) = 0x01; + *(ptr++) = 0x03; + *(ptr++) = 0x01; + + /** + * TS 144.006, section 5.2 "Frame delimitation and fill bits" + * Except for the first octet containing fill bits which shall + * be set to the binary value "00101011", each fill bit should + * be set to a random value when sent by the network. + */ + *(ptr++) = 0x2b; + while (ptr < msg->tail) + *(ptr++) = (uint8_t)rand(); + + return msg; +} + +int l1sched_lchan_emit_data_ind(struct l1sched_lchan_state *lchan, + const uint8_t *data, size_t data_len, + int n_errors, int n_bits_total, + bool traffic) +{ + const struct l1sched_meas_set *meas = &lchan->meas_avg; + const struct l1sched_lchan_desc *lchan_desc; + struct l1sched_prim *prim; + struct msgb *msg; + + lchan_desc = &l1sched_lchan_desc[lchan->type]; + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_INDICATION); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->data_ind = (struct l1sched_prim_data_ind) { + .chdr = { + .frame_nr = meas->fn, + .chan_nr = lchan_desc->chan_nr | lchan->ts->index, + .link_id = lchan_desc->link_id, + .traffic = traffic, + }, + .toa256 = meas->toa256, + .rssi = meas->rssi, + .n_errors = n_errors, + .n_bits_total = n_bits_total, + }; + + if (data_len > 0) + memcpy(msgb_put(msg, data_len), data, data_len); + + return l1sched_prim_to_user(lchan->ts->sched, msg); +} + +int l1sched_lchan_emit_data_cnf(struct l1sched_lchan_state *lchan, + struct msgb *msg, uint32_t fn) +{ + struct l1sched_prim *prim; + + if (msg == NULL) + return -ENODEV; + + /* convert from DATA.req to DATA.cnf */ + prim = l1sched_prim_from_msgb(msg); + prim->oph.operation = PRIM_OP_CONFIRM; + + switch (prim->oph.primitive) { + case L1SCHED_PRIM_T_DATA: + prim->data_cnf.frame_nr = fn; + break; + case L1SCHED_PRIM_T_RACH: + prim->rach_cnf.chdr.frame_nr = fn; + break; + default: + /* shall not happen */ + OSMO_ASSERT(0); + } + + return l1sched_prim_to_user(lchan->ts->sched, msg); +} + +static int prim_enqeue(struct l1sched_state *sched, struct msgb *msg, + const struct l1sched_prim_chdr *chdr) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + struct l1sched_lchan_state *lchan; + + lchan = l1sched_find_lchan_by_chan_nr(sched, chdr->chan_nr, chdr->link_id); + if (OSMO_UNLIKELY(lchan == NULL || !lchan->active)) { + LOGP_SCHEDD(sched, LOGL_ERROR, + "No [active] lchan for primitive " L1SCHED_PRIM_STR_FMT " " + "(fn=%u, chan_nr=0x%02x, link_id=0x%02x, len=%u): %s\n", + L1SCHED_PRIM_STR_ARGS(prim), + chdr->frame_nr, chdr->chan_nr, chdr->link_id, + msgb_l2len(msg), msgb_hexdump_l2(msg)); + msgb_free(msg); + return -ENODEV; + } + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "Enqueue primitive " L1SCHED_PRIM_STR_FMT " " + "(fn=%u, chan_nr=0x%02x, link_id=0x%02x, len=%u): %s\n", + L1SCHED_PRIM_STR_ARGS(prim), + chdr->frame_nr, chdr->chan_nr, chdr->link_id, + msgb_l2len(msg), msgb_hexdump_l2(msg)); + + msgb_enqueue(&lchan->tx_prims, msg); + return 0; +} + +int l1sched_prim_from_user(struct l1sched_state *sched, struct msgb *msg) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + + LOGP_SCHEDD(sched, LOGL_DEBUG, + "%s(): Rx " L1SCHED_PRIM_STR_FMT "\n", + __func__, L1SCHED_PRIM_STR_ARGS(prim)); + + switch (OSMO_PRIM_HDR(&prim->oph)) { + case OSMO_PRIM(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST): + return prim_enqeue(sched, msg, &prim->data_req); + case OSMO_PRIM(L1SCHED_PRIM_T_RACH, PRIM_OP_REQUEST): + return prim_enqeue(sched, msg, &prim->rach_req.chdr); + default: + LOGP_SCHEDD(sched, LOGL_ERROR, + "%s(): Unhandled primitive " L1SCHED_PRIM_STR_FMT "\n", + __func__, L1SCHED_PRIM_STR_ARGS(prim)); + msgb_free(msg); + return -ENOTSUP; + } +} diff --git a/src/host/trxcon/src/sched_trx.c b/src/host/trxcon/src/sched_trx.c new file mode 100644 index 00000000..f4124003 --- /dev/null +++ b/src/host/trxcon/src/sched_trx.c @@ -0,0 +1,894 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: GSM PHY routines + * + * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * Contributions by sysmocom - s.f.m.c. GmbH + * + * 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. + * + */ + +#include <error.h> +#include <errno.h> +#include <string.h> +#include <talloc.h> +#include <stdbool.h> + +#include <osmocom/gsm/a5.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/core/bits.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/linuxlist.h> + +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> + +/* Logging categories to be used for common/data messages */ +int l1sched_log_cat_common = DLGLOBAL; +int l1sched_log_cat_data = DLGLOBAL; + +/* "Dummy" Measurement Report */ +static const uint8_t meas_rep_dummy[] = { + /* L1 SACCH pseudo-header */ + 0x0f, 0x00, + + /* LAPDm header */ + 0x01, 0x03, 0x49, + + /* RR Management messages, Measurement Report */ + 0x06, 0x15, + + /* Measurement results (see 3GPP TS 44.018, section 10.5.2.20): + * 0... .... = BA-USED: 0 + * .0.. .... = DTX-USED: DTX was not used + * ..11 0110 = RXLEV-FULL-SERVING-CELL: -57 <= x < -56 dBm (54) + * 0... .... = 3G-BA-USED: 0 + * .1.. .... = MEAS-VALID: The measurement results are not valid + * ..11 0110 = RXLEV-SUB-SERVING-CELL: -57 <= x < -56 dBm (54) + * 0... .... = SI23_BA_USED: 0 + * .000 .... = RXQUAL-FULL-SERVING-CELL: BER < 0.2%, Mean value 0.14% (0) + * .... 000. = RXQUAL-SUB-SERVING-CELL: BER < 0.2%, Mean value 0.14% (0) + * .... ...1 11.. .... = NO-NCELL-M: Neighbour cell information not available */ + 0x36, 0x76, 0x01, 0xc0, + + /* 0** -- Padding with zeroes */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static int l1sched_cfg_pchan_comb_ind(struct l1sched_state *sched, + uint8_t tn, enum gsm_phys_chan_config pchan) +{ + struct l1sched_prim *prim; + struct msgb *msg; + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_PCHAN_COMB, PRIM_OP_INDICATION); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->pchan_comb_ind.tn = tn; + prim->pchan_comb_ind.pchan = pchan; + + return l1sched_prim_to_user(sched, msg); +} + +static void l1sched_a5_burst_enc(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br); + +/* Pull an Uplink burst from the scheduler and store it to br->burst[]. + * The TDMA Fn advance must be applied by the caller (if needed). + * The given *br must be initialized by the caller. */ +void l1sched_pull_burst(struct l1sched_state *sched, struct l1sched_burst_req *br) +{ + struct l1sched_ts *ts = sched->ts[br->tn]; + const struct l1sched_tdma_frame *frame; + struct l1sched_lchan_state *lchan; + l1sched_lchan_tx_func *handler; + enum l1sched_lchan_type chan; + unsigned int offset; + + /* Check if the given timeslot is configured */ + if (ts == NULL || ts->mf_layout == NULL) + return; + + /* Get frame from multiframe */ + offset = br->fn % ts->mf_layout->period; + frame = &ts->mf_layout->frames[offset]; + + /* Get required info from frame */ + br->bid = frame->ul_bid; + chan = frame->ul_chan; + handler = l1sched_lchan_desc[chan].tx_fn; + + /* Omit lchans without handler */ + if (handler == NULL) + return; + + /* Make sure that lchan is allocated and active */ + lchan = l1sched_find_lchan_by_type(ts, chan); + if (lchan == NULL || !lchan->active) + return; + + /* Handover RACH needs to be handled regardless of the + * current channel type and the associated handler. */ + struct msgb *msg = llist_first_entry_or_null(&lchan->tx_prims, struct msgb, list); + if (msg && l1sched_prim_type_from_msgb(msg) == L1SCHED_PRIM_T_RACH) + handler = l1sched_lchan_desc[L1SCHED_RACH].tx_fn; + + /* Poke lchan handler */ + handler(lchan, br); + + /* Perform A5/X burst encryption if required */ + if (lchan->a5.algo) + l1sched_a5_burst_enc(lchan, br); +} + +void l1sched_logging_init(int log_cat_common, int log_cat_data) +{ + l1sched_log_cat_common = log_cat_common; + l1sched_log_cat_data = log_cat_data; +} + +struct l1sched_state *l1sched_alloc(void *ctx, const struct l1sched_cfg *cfg, void *priv) +{ + struct l1sched_state *sched; + + sched = talloc(ctx, struct l1sched_state); + if (!sched) + return NULL; + + *sched = (struct l1sched_state) { + .priv = priv, + }; + + memcpy(&sched->sacch_cache[0], &meas_rep_dummy[0], sizeof(meas_rep_dummy)); + + if (cfg->log_prefix == NULL) + sched->log_prefix = talloc_asprintf(sched, "l1sched[0x%p]: ", sched); + else + sched->log_prefix = talloc_strdup(sched, cfg->log_prefix); + + return sched; +} + +void l1sched_free(struct l1sched_state *sched) +{ + unsigned int tn; + + if (sched == NULL) + return; + + LOGP_SCHEDC(sched, LOGL_NOTICE, "Shutdown scheduler\n"); + + /* Free all potentially allocated timeslots */ + for (tn = 0; tn < ARRAY_SIZE(sched->ts); tn++) + l1sched_del_ts(sched, tn); + + talloc_free(sched); +} + +void l1sched_reset(struct l1sched_state *sched, bool reset_clock) +{ + unsigned int tn; + + if (sched == NULL) + return; + + LOGP_SCHEDC(sched, LOGL_NOTICE, "Reset scheduler %s\n", + reset_clock ? "and clock counter" : ""); + + /* Free all potentially allocated timeslots */ + for (tn = 0; tn < ARRAY_SIZE(sched->ts); tn++) + l1sched_del_ts(sched, tn); + + memcpy(&sched->sacch_cache[0], &meas_rep_dummy[0], sizeof(meas_rep_dummy)); +} + +struct l1sched_ts *l1sched_add_ts(struct l1sched_state *sched, int tn) +{ + /* Make sure that ts isn't allocated yet */ + if (sched->ts[tn] != NULL) { + LOGP_SCHEDC(sched, LOGL_ERROR, "Timeslot #%u already allocated\n", tn); + return NULL; + } + + LOGP_SCHEDC(sched, LOGL_NOTICE, "Add a new TDMA timeslot #%u\n", tn); + + sched->ts[tn] = talloc_zero(sched, struct l1sched_ts); + sched->ts[tn]->sched = sched; + sched->ts[tn]->index = tn; + + return sched->ts[tn]; +} + +void l1sched_del_ts(struct l1sched_state *sched, int tn) +{ + struct l1sched_lchan_state *lchan, *lchan_next; + struct l1sched_ts *ts; + + /* Find ts in list */ + ts = sched->ts[tn]; + if (ts == NULL) + return; + + LOGP_SCHEDC(sched, LOGL_NOTICE, "Delete TDMA timeslot #%u\n", tn); + + /* Deactivate all logical channels */ + l1sched_deactivate_all_lchans(ts); + + /* Free channel states */ + llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) { + llist_del(&lchan->list); + talloc_free(lchan); + } + + /* Remove ts from list and free memory */ + sched->ts[tn] = NULL; + talloc_free(ts); + + /* Notify transceiver about that */ + l1sched_cfg_pchan_comb_ind(sched, tn, GSM_PCHAN_NONE); +} + +#define LAYOUT_HAS_LCHAN(layout, lchan) \ + (layout->lchan_mask & ((uint64_t) 0x01 << lchan)) + +int l1sched_configure_ts(struct l1sched_state *sched, int tn, + enum gsm_phys_chan_config config) +{ + struct l1sched_lchan_state *lchan; + enum l1sched_lchan_type type; + struct l1sched_ts *ts; + + /* Try to find specified ts */ + ts = sched->ts[tn]; + if (ts != NULL) { + /* Reconfiguration of existing one */ + l1sched_reset_ts(sched, tn); + } else { + /* Allocate a new one if doesn't exist */ + ts = l1sched_add_ts(sched, tn); + if (ts == NULL) + return -ENOMEM; + } + + /* Choose proper multiframe layout */ + ts->mf_layout = l1sched_mframe_layout(config, tn); + if (!ts->mf_layout) + return -EINVAL; + if (ts->mf_layout->chan_config != config) + return -EINVAL; + + LOGP_SCHEDC(sched, LOGL_NOTICE, + "(Re)configure TDMA timeslot #%u as %s\n", + tn, ts->mf_layout->name); + + /* Init logical channels list */ + INIT_LLIST_HEAD(&ts->lchans); + + /* Allocate channel states */ + for (type = 0; type < _L1SCHED_CHAN_MAX; type++) { + if (!LAYOUT_HAS_LCHAN(ts->mf_layout, type)) + continue; + + /* Allocate a channel state */ + lchan = talloc_zero(ts, struct l1sched_lchan_state); + if (!lchan) + return -ENOMEM; + + /* set backpointer */ + lchan->ts = ts; + + /* Set channel type */ + lchan->type = type; + + /* Init the Tx queue */ + INIT_LLIST_HEAD(&lchan->tx_prims); + + /* Add to the list of channel states */ + llist_add_tail(&lchan->list, &ts->lchans); + + /* Enable channel automatically if required */ + if (l1sched_lchan_desc[type].flags & L1SCHED_CH_FLAG_AUTO) + l1sched_activate_lchan(ts, type); + } + + /* Notify transceiver about TS activation */ + l1sched_cfg_pchan_comb_ind(sched, tn, config); + + return 0; +} + +int l1sched_reset_ts(struct l1sched_state *sched, int tn) +{ + struct l1sched_lchan_state *lchan, *lchan_next; + struct l1sched_ts *ts; + + /* Try to find specified ts */ + ts = sched->ts[tn]; + if (ts == NULL) + return -EINVAL; + + /* Undefine multiframe layout */ + ts->mf_layout = NULL; + + /* Deactivate all logical channels */ + l1sched_deactivate_all_lchans(ts); + + /* Free channel states */ + llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) { + llist_del(&lchan->list); + talloc_free(lchan); + } + + /* Notify transceiver about that */ + l1sched_cfg_pchan_comb_ind(sched, tn, GSM_PCHAN_NONE); + + return 0; +} + +int l1sched_start_ciphering(struct l1sched_ts *ts, uint8_t algo, + const uint8_t *key, uint8_t key_len) +{ + struct l1sched_lchan_state *lchan; + + /* Prevent NULL-pointer deference */ + if (!ts) + return -EINVAL; + + /* Make sure we can store this key */ + if (key_len > MAX_A5_KEY_LEN) + return -ERANGE; + + /* Iterate over all allocated logical channels */ + llist_for_each_entry(lchan, &ts->lchans, list) { + /* Omit inactive channels */ + if (!lchan->active) + continue; + + /* Set key length and algorithm */ + lchan->a5.key_len = key_len; + lchan->a5.algo = algo; + + /* Copy requested key */ + if (key_len) + memcpy(lchan->a5.key, key, key_len); + } + + return 0; +} + +struct l1sched_lchan_state *l1sched_find_lchan_by_type(struct l1sched_ts *ts, + enum l1sched_lchan_type type) +{ + struct l1sched_lchan_state *lchan; + + llist_for_each_entry(lchan, &ts->lchans, list) + if (lchan->type == type) + return lchan; + + return NULL; +} + +struct l1sched_lchan_state *l1sched_find_lchan_by_chan_nr(struct l1sched_state *sched, + uint8_t chan_nr, uint8_t link_id) +{ + const struct l1sched_ts *ts = sched->ts[chan_nr & 0x07]; + const struct l1sched_lchan_desc *lchan_desc; + struct l1sched_lchan_state *lchan; + + if (ts == NULL) + return NULL; + + llist_for_each_entry(lchan, &ts->lchans, list) { + lchan_desc = &l1sched_lchan_desc[lchan->type]; + if (lchan_desc->chan_nr != (chan_nr & RSL_CHAN_NR_MASK)) + continue; + if (lchan_desc->link_id != link_id) + continue; + return lchan; + } + + return NULL; +} + +int l1sched_set_lchans(struct l1sched_ts *ts, uint8_t chan_nr, + int active, uint8_t tch_mode, uint8_t tsc) +{ + const struct l1sched_lchan_desc *lchan_desc; + struct l1sched_lchan_state *lchan; + int rc = 0; + + /* Prevent NULL-pointer deference */ + OSMO_ASSERT(ts != NULL); + + /* Iterate over all allocated lchans */ + llist_for_each_entry(lchan, &ts->lchans, list) { + lchan_desc = &l1sched_lchan_desc[lchan->type]; + + if (lchan_desc->chan_nr == (chan_nr & RSL_CHAN_NR_MASK)) { + if (active) { + rc |= l1sched_activate_lchan(ts, lchan->type); + lchan->tch_mode = tch_mode; + lchan->tsc = tsc; + } else + rc |= l1sched_deactivate_lchan(ts, lchan->type); + } + } + + return rc; +} + +int l1sched_lchan_set_amr_cfg(struct l1sched_lchan_state *lchan, + uint8_t codecs_bitmask, uint8_t start_codec) +{ + int n = 0; + int acum = 0; + int pos; + + while ((pos = ffs(codecs_bitmask)) != 0) { + acum += pos; + LOGP_LCHANC(lchan, LOGL_DEBUG, "AMR codec[%u] = %u\n", n, acum - 1); + lchan->amr.codec[n++] = acum - 1; + codecs_bitmask >>= pos; + } + if (n == 0) { + LOGP_LCHANC(lchan, LOGL_ERROR, "Empty AMR codec mode bitmask!\n"); + return -EINVAL; + } + + lchan->amr.codecs = n; + lchan->amr.dl_ft = start_codec; + lchan->amr.dl_cmr = start_codec; + lchan->amr.ul_ft = start_codec; + lchan->amr.ul_cmr = start_codec; + + return 0; +} + +int l1sched_activate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan) +{ + const struct l1sched_lchan_desc *lchan_desc = &l1sched_lchan_desc[chan]; + struct l1sched_lchan_state *lchan; + + /* Try to find requested logical channel */ + lchan = l1sched_find_lchan_by_type(ts, chan); + if (lchan == NULL) + return -EINVAL; + + if (lchan->active) { + LOGP_LCHANC(lchan, LOGL_ERROR, "is already activated\n"); + return -EINVAL; + } + + LOGP_LCHANC(lchan, LOGL_NOTICE, "activating\n"); + + /* Conditionally allocate memory for bursts */ + if (lchan_desc->rx_fn && lchan_desc->burst_buf_size > 0) { + lchan->rx_bursts = talloc_zero_size(lchan, + lchan_desc->burst_buf_size); + if (lchan->rx_bursts == NULL) + return -ENOMEM; + } + + if (lchan_desc->tx_fn && lchan_desc->burst_buf_size > 0) { + lchan->tx_bursts = talloc_zero_size(lchan, + lchan_desc->burst_buf_size); + if (lchan->tx_bursts == NULL) + return -ENOMEM; + } + + /* Finally, update channel status */ + lchan->active = 1; + + return 0; +} + +static void l1sched_reset_lchan(struct l1sched_lchan_state *lchan) +{ + struct msgb *msg; + + /* Prevent NULL-pointer deference */ + OSMO_ASSERT(lchan != NULL); + + /* Print some TDMA statistics for Downlink */ + if (l1sched_lchan_desc[lchan->type].rx_fn && lchan->active) { + LOGP_LCHANC(lchan, LOGL_DEBUG, "TDMA statistics: " + "%lu DL frames have been processed, " + "%lu lost (compensated), last fn=%u\n", + lchan->tdma.num_proc, + lchan->tdma.num_lost, + lchan->tdma.last_proc); + } + + /* Reset internal state variables */ + lchan->rx_burst_mask = 0x00; + lchan->tx_burst_mask = 0x00; + + /* Free burst memory */ + talloc_free(lchan->rx_bursts); + talloc_free(lchan->tx_bursts); + + lchan->rx_bursts = NULL; + lchan->tx_bursts = NULL; + + /* Flush the queue of pending Tx prims */ + while ((msg = msgb_dequeue(&lchan->tx_prims)) != NULL) { + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + + LOGP_LCHANC(lchan, LOGL_NOTICE, "%s(): dropping Tx prim (fn=%u): %s\n", + __func__, prim->data_req.frame_nr, msgb_hexdump_l2(msg)); + msgb_free(msg); + } + + /* Channel specific stuff */ + if (L1SCHED_CHAN_IS_TCH(lchan->type)) { + lchan->dl_ongoing_facch = 0; + lchan->ul_facch_blocks = 0; + + lchan->tch_mode = GSM48_CMODE_SIGN; + + /* Reset AMR state */ + memset(&lchan->amr, 0x00, sizeof(lchan->amr)); + } else if (L1SCHED_CHAN_IS_SACCH(lchan->type)) { + /* Reset SACCH state */ + memset(&lchan->sacch, 0x00, sizeof(lchan->sacch)); + } + + /* Reset ciphering state */ + memset(&lchan->a5, 0x00, sizeof(lchan->a5)); + + /* Reset TDMA frame statistics */ + memset(&lchan->tdma, 0x00, sizeof(lchan->tdma)); +} + +int l1sched_deactivate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan) +{ + struct l1sched_lchan_state *lchan; + + /* Try to find requested logical channel */ + lchan = l1sched_find_lchan_by_type(ts, chan); + if (lchan == NULL) + return -EINVAL; + + if (!lchan->active) { + LOGP_LCHANC(lchan, LOGL_ERROR, "is already deactivated\n"); + return -EINVAL; + } + + LOGP_LCHANC(lchan, LOGL_DEBUG, "deactivating\n"); + + /* Reset internal state, free memory */ + l1sched_reset_lchan(lchan); + + /* Update activation flag */ + lchan->active = 0; + + return 0; +} + +void l1sched_deactivate_all_lchans(struct l1sched_ts *ts) +{ + struct l1sched_lchan_state *lchan; + + LOGP_SCHEDC(ts->sched, LOGL_DEBUG, + "Deactivating all logical channels on ts=%d\n", + ts->index); + + llist_for_each_entry(lchan, &ts->lchans, list) { + /* Omit inactive channels */ + if (!lchan->active) + continue; + + /* Reset internal state, free memory */ + l1sched_reset_lchan(lchan); + + /* Update activation flag */ + lchan->active = 0; + } +} + +enum gsm_phys_chan_config l1sched_chan_nr2pchan_config(uint8_t chan_nr) +{ + uint8_t cbits = chan_nr >> 3; + + if (cbits == ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs) + return GSM_PCHAN_TCH_F; + else if ((cbits & 0x1e) == ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0)) + return GSM_PCHAN_TCH_H; + else if ((cbits & 0x1c) == ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0)) + return GSM_PCHAN_CCCH_SDCCH4; + else if ((cbits & 0x18) == ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0)) + return GSM_PCHAN_SDCCH8_SACCH8C; + else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH4) + return GSM_PCHAN_CCCH_SDCCH4_CBCH; + else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH8) + return GSM_PCHAN_SDCCH8_SACCH8C_CBCH; + else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH) + return GSM_PCHAN_PDCH; + + return GSM_PCHAN_NONE; +} + +static void l1sched_a5_burst_dec(struct l1sched_lchan_state *lchan, + struct l1sched_burst_ind *bi) +{ + ubit_t ks[114]; + int i; + + /* Generate keystream for a DL burst */ + osmo_a5(lchan->a5.algo, lchan->a5.key, bi->fn, ks, NULL); + + /* Apply keystream over ciphertext */ + for (i = 0; i < 57; i++) { + if (ks[i]) + bi->burst[i + 3] *= -1; + if (ks[i + 57]) + bi->burst[i + 88] *= -1; + } +} + +static void l1sched_a5_burst_enc(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br) +{ + ubit_t ks[114]; + int i; + + /* Generate keystream for an UL burst */ + osmo_a5(lchan->a5.algo, lchan->a5.key, br->fn, NULL, ks); + + /* Apply keystream over plaintext */ + for (i = 0; i < 57; i++) { + br->burst[i + 3] ^= ks[i]; + br->burst[i + 88] ^= ks[i + 57]; + } +} + +static int subst_frame_loss(struct l1sched_lchan_state *lchan, + l1sched_lchan_rx_func *handler, + uint32_t fn) +{ + const struct l1sched_tdma_multiframe *mf; + const struct l1sched_tdma_frame *fp; + int elapsed, i; + + /* Wait until at least one TDMA frame is processed */ + if (lchan->tdma.num_proc == 0) + return -EAGAIN; + + /* Short alias for the current multiframe */ + mf = lchan->ts->mf_layout; + + /* Calculate how many frames elapsed since the last received one. + * The algorithm is based on GSM::FNDelta() from osmo-trx. */ + elapsed = fn - lchan->tdma.last_proc; + if (elapsed >= GSM_TDMA_HYPERFRAME / 2) + elapsed -= GSM_TDMA_HYPERFRAME; + else if (elapsed < -GSM_TDMA_HYPERFRAME / 2) + elapsed += GSM_TDMA_HYPERFRAME; + + /* Check TDMA frame order (wrong order is possible with fake_trx.py, see OS#4658) */ + if (elapsed < 0) { + /* This burst has already been substituted by a dummy burst (all bits set to zero), + * so better drop it. Otherwise we risk to get undefined behavior in handler(). */ + LOGP_LCHAND(lchan, LOGL_ERROR, "Rx burst with fn=%u older than the last " + "processed fn=%u (see OS#4658) => dropping\n", + fn, lchan->tdma.last_proc); + return -EALREADY; + } + + /* Check how many frames we (potentially) need to compensate */ + if (elapsed > mf->period) { + LOGP_LCHANC(lchan, LOGL_NOTICE, + "Too many (>%u) contiguous TDMA frames elapsed (%d) " + "since the last processed fn=%u (current %u)\n", + mf->period, elapsed, lchan->tdma.last_proc, fn); + return -EIO; + } else if (elapsed == 0) { + LOGP_LCHANC(lchan, LOGL_ERROR, + "No TDMA frames elapsed since the last processed " + "fn=%u, must be a bug?\n", lchan->tdma.last_proc); + return -EIO; + } + + struct l1sched_burst_ind bi = { + .fn = lchan->tdma.last_proc, + .tn = lchan->ts->index, + .toa256 = 0, + .rssi = -120, + .burst = { 0 }, + .burst_len = GSM_NBITS_NB_GMSK_BURST, + }; + + /* Traverse from fp till the current frame */ + for (i = 0; i < elapsed - 1; i++) { + fp = &mf->frames[GSM_TDMA_FN_INC(bi.fn) % mf->period]; + if (fp->dl_chan != lchan->type) + continue; + + LOGP_LCHANC(lchan, LOGL_NOTICE, + "Substituting lost TDMA frame fn=%u\n", bi.fn); + + bi.bid = fp->dl_bid; + handler(lchan, &bi); + + /* Update TDMA frame statistics */ + lchan->tdma.last_proc = bi.fn; + lchan->tdma.num_proc++; + lchan->tdma.num_lost++; + } + + return 0; +} + +int l1sched_handle_rx_burst(struct l1sched_state *sched, + struct l1sched_burst_ind *bi) +{ + struct l1sched_lchan_state *lchan; + const struct l1sched_tdma_frame *frame; + struct l1sched_ts *ts = sched->ts[bi->tn]; + + l1sched_lchan_rx_func *handler; + enum l1sched_lchan_type chan; + uint8_t offset; + int rc; + + /* Check whether required timeslot is allocated and configured */ + if (ts == NULL || ts->mf_layout == NULL) { + LOGP_SCHEDD(sched, LOGL_DEBUG, + "Timeslot #%u isn't configured, ignoring burst...\n", bi->tn); + return -EINVAL; + } + + /* Get frame from multiframe */ + offset = bi->fn % ts->mf_layout->period; + frame = ts->mf_layout->frames + offset; + + /* Get required info from frame */ + bi->bid = frame->dl_bid; + chan = frame->dl_chan; + handler = l1sched_lchan_desc[chan].rx_fn; + + /* Omit bursts which have no handler, like IDLE bursts. + * TODO: handle noise indications during IDLE frames. */ + if (!handler) + return -ENODEV; + + /* Find required channel state */ + lchan = l1sched_find_lchan_by_type(ts, chan); + if (lchan == NULL) + return -ENODEV; + + /* Ensure that channel is active */ + if (!lchan->active) + return 0; + + /* Compensate lost TDMA frames (if any) */ + rc = subst_frame_loss(lchan, handler, bi->fn); + if (rc == -EALREADY) + return rc; + + /* Perform A5/X decryption if required */ + if (lchan->a5.algo) + l1sched_a5_burst_dec(lchan, bi); + + /* Put burst to handler */ + handler(lchan, bi); + + /* Update TDMA frame statistics */ + lchan->tdma.last_proc = bi->fn; + + if (++lchan->tdma.num_proc == 0) { + /* Theoretically, we may have an integer overflow of num_proc counter. + * As a consequence, subst_frame_loss() will be unable to compensate + * one (potentionally lost) Downlink burst. On practice, it would + * happen once in 4615 * 10e-6 * (2 ^ 32 - 1) seconds or ~6 years. */ + LOGP_LCHAND(lchan, LOGL_NOTICE, + "Too many TDMA frames have been processed. " + "Are you running trxcon for more than 6 years?!?\n"); + lchan->tdma.num_proc = 1; + } + + return 0; +} + +int l1sched_handle_rx_probe(struct l1sched_state *sched, + struct l1sched_probe *probe) +{ + struct l1sched_ts *ts = sched->ts[probe->tn]; + const struct l1sched_tdma_frame *frame; + struct l1sched_lchan_state *lchan; + unsigned int offset; + + /* Check whether required timeslot is allocated and configured */ + if (ts == NULL || ts->mf_layout == NULL) + return -EINVAL; + + /* Get frame from multiframe */ + offset = probe->fn % ts->mf_layout->period; + frame = &ts->mf_layout->frames[offset]; + + if (l1sched_lchan_desc[frame->dl_chan].rx_fn == NULL) + return -ENODEV; + + /* Find the appropriate logical channel */ + lchan = l1sched_find_lchan_by_type(ts, frame->dl_chan); + if (lchan == NULL) + return -ENODEV; + + if (lchan->active) + probe->flags |= L1SCHED_PROBE_F_ACTIVE; + + return 0; +} + +#define MEAS_HIST_FIRST(hist) \ + (&hist->buf[0]) +#define MEAS_HIST_LAST(hist) \ + (MEAS_HIST_FIRST(hist) + ARRAY_SIZE(hist->buf) - 1) + +/* Add a new set of measurements to the history */ +void l1sched_lchan_meas_push(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi) +{ + struct l1sched_lchan_meas_hist *hist = &lchan->meas_hist; + + /* Find a new position where to store the measurements */ + if (hist->head == MEAS_HIST_LAST(hist) || hist->head == NULL) + hist->head = MEAS_HIST_FIRST(hist); + else + hist->head++; + + *hist->head = (struct l1sched_meas_set) { + .fn = bi->fn, + .toa256 = bi->toa256, + .rssi = bi->rssi, + }; +} + +/* Calculate the AVG of n measurements from the history */ +void l1sched_lchan_meas_avg(struct l1sched_lchan_state *lchan, unsigned int n) +{ + struct l1sched_lchan_meas_hist *hist = &lchan->meas_hist; + struct l1sched_meas_set *meas = hist->head; + int toa256_sum = 0; + int rssi_sum = 0; + int i; + + OSMO_ASSERT(n > 0 && n <= ARRAY_SIZE(hist->buf)); + OSMO_ASSERT(meas != NULL); + + /* Traverse backwards up to n entries, calculate the sum */ + for (i = 0; i < n; i++) { + toa256_sum += meas->toa256; + rssi_sum += meas->rssi; + + /* Do not go below the first burst */ + if (i + 1 == n) + break; + + if (meas == MEAS_HIST_FIRST(hist)) + meas = MEAS_HIST_LAST(hist); + else + meas--; + } + + /* Calculate the AVG */ + lchan->meas_avg.toa256 = toa256_sum / n; + lchan->meas_avg.rssi = rssi_sum / n; + + /* As a bonus, store TDMA frame number of the first burst */ + lchan->meas_avg.fn = meas->fn; +} diff --git a/src/host/trxcon/trx_if.c b/src/host/trxcon/src/trx_if.c index 55d70341..fad10264 100644 --- a/src/host/trxcon/trx_if.c +++ b/src/host/trxcon/src/trx_if.c @@ -4,6 +4,7 @@ * * Copyright (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> * Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com> + * Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * * All Rights Reserved * @@ -38,12 +39,17 @@ #include <osmocom/core/fsm.h> #include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/gsm0502.h> -#include "l1ctl.h" -#include "trxcon.h" -#include "trx_if.h" -#include "logging.h" -#include "scheduler.h" +#include <osmocom/bb/trxcon/trx_if.h> +#include <osmocom/bb/trxcon/logging.h> + +#define TRXDv0_HDR_LEN 8 + +#define S(x) (1 << (x)) + +static void trx_fsm_cleanup_cb(struct osmo_fsm_inst *fi, + enum osmo_fsm_term_cause cause); static struct value_string trx_evt_names[] = { { 0, NULL } /* no events? */ @@ -52,8 +58,8 @@ static struct value_string trx_evt_names[] = { static struct osmo_fsm_state trx_fsm_states[] = { [TRX_STATE_OFFLINE] = { .out_state_mask = ( - GEN_MASK(TRX_STATE_IDLE) | - GEN_MASK(TRX_STATE_RSP_WAIT)), + S(TRX_STATE_IDLE) | + S(TRX_STATE_RSP_WAIT)), .name = "OFFLINE", }, [TRX_STATE_IDLE] = { @@ -62,25 +68,26 @@ static struct osmo_fsm_state trx_fsm_states[] = { }, [TRX_STATE_ACTIVE] = { .out_state_mask = ( - GEN_MASK(TRX_STATE_IDLE) | - GEN_MASK(TRX_STATE_RSP_WAIT)), + S(TRX_STATE_IDLE) | + S(TRX_STATE_RSP_WAIT)), .name = "ACTIVE", }, [TRX_STATE_RSP_WAIT] = { .out_state_mask = ( - GEN_MASK(TRX_STATE_IDLE) | - GEN_MASK(TRX_STATE_ACTIVE) | - GEN_MASK(TRX_STATE_OFFLINE)), + S(TRX_STATE_IDLE) | + S(TRX_STATE_ACTIVE) | + S(TRX_STATE_OFFLINE)), .name = "RSP_WAIT", }, }; static struct osmo_fsm trx_fsm = { - .name = "trx_interface_fsm", + .name = "trx_interface", .states = trx_fsm_states, .num_states = ARRAY_SIZE(trx_fsm_states), - .log_subsys = DTRX, + .log_subsys = DTRXC, .event_names = trx_evt_names, + .cleanup = &trx_fsm_cleanup_cb, }; static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local, @@ -143,13 +150,13 @@ static void trx_ctrl_send(struct trx_instance *trx) tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list); /* Send command */ - LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd); + LOGPFSML(trx->fi, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd); send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0); /* Trigger state machine */ - if (trx->fsm->state != TRX_STATE_RSP_WAIT) { - trx->prev_state = trx->fsm->state; - osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_RSP_WAIT, 0, 0); + if (trx->fi->state != TRX_STATE_RSP_WAIT) { + trx->prev_state = trx->fi->state; + osmo_fsm_inst_state_chg(trx->fi, TRX_STATE_RSP_WAIT, 0, 0); } /* Start expire timer */ @@ -167,13 +174,13 @@ static void trx_ctrl_timer_cb(void *data) if (llist_empty(&trx->trx_ctrl_list)) return; - LOGP(DTRX, LOGL_NOTICE, "No response from transceiver...\n"); + LOGPFSML(trx->fi, LOGL_NOTICE, "No response from transceiver...\n"); tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list); if (++tcm->retry_cnt > 3) { - LOGP(DTRX, LOGL_NOTICE, "Transceiver offline\n"); - osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_OFFLINE, 0, 0); - osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_OFFLINE, trx); + LOGPFSML(trx->fi, LOGL_NOTICE, "Transceiver offline\n"); + osmo_fsm_inst_state_chg(trx->fi, TRX_STATE_OFFLINE, 0, 0); + osmo_fsm_inst_term(trx->fi, OSMO_FSM_TERM_TIMEOUT, NULL); return; } @@ -212,7 +219,7 @@ static int trx_ctrl_cmd(struct trx_instance *trx, int critical, tcm->cmd_len = strlen(cmd); tcm->critical = critical; llist_add_tail(&tcm->list, &trx->trx_ctrl_list); - LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd); + LOGPFSML(trx->fi, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd); /* Send message, if no pending messages */ if (!pending) @@ -242,23 +249,22 @@ static int trx_ctrl_cmd(struct trx_instance *trx, int critical, * RSP POWERON <status> */ -int trx_if_cmd_echo(struct trx_instance *trx) +static int trx_if_cmd_echo(struct trx_instance *trx) { return trx_ctrl_cmd(trx, 1, "ECHO", ""); } -int trx_if_cmd_poweroff(struct trx_instance *trx) +static int trx_if_cmd_poweroff(struct trx_instance *trx) { return trx_ctrl_cmd(trx, 1, "POWEROFF", ""); } -int trx_if_cmd_poweron(struct trx_instance *trx) +static int trx_if_cmd_poweron(struct trx_instance *trx) { - if (trx->powered_up) { - /* FIXME: this should be handled by the FSM, not here! */ - LOGP(DTRX, LOGL_ERROR, "Suppressing POWERON as we're already powered up\n"); +#if 0 + if (trx->powered_up) return -EAGAIN; - } +#endif return trx_ctrl_cmd(trx, 1, "POWERON", ""); } @@ -273,9 +279,25 @@ int trx_if_cmd_poweron(struct trx_instance *trx) * RSP SETSLOT <status> <timeslot> <chantype> */ -int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type) +static int trx_if_cmd_setslot(struct trx_instance *trx, + const struct trxcon_phyif_cmdp_setslot *cmdp) { - return trx_ctrl_cmd(trx, 1, "SETSLOT", "%u %u", tn, type); + /* Values correspond to 'enum ChannelCombination' in osmo-trx.git */ + static const uint8_t chan_types[_GSM_PCHAN_MAX] = { + [GSM_PCHAN_UNKNOWN] = 0, + [GSM_PCHAN_NONE] = 0, + [GSM_PCHAN_CCCH] = 4, + [GSM_PCHAN_CCCH_SDCCH4] = 5, + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 5, + [GSM_PCHAN_TCH_F] = 1, + [GSM_PCHAN_TCH_H] = 3, + [GSM_PCHAN_SDCCH8_SACCH8C] = 7, + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 7, + [GSM_PCHAN_PDCH] = 13, + }; + + return trx_ctrl_cmd(trx, 1, "SETSLOT", "%u %u", + cmdp->tn, chan_types[cmdp->pchan]); } /* @@ -290,28 +312,30 @@ int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type) * RSP (RX/TX)TUNE <status> <kHz> */ -int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn) +static int trx_if_cmd_rxtune(struct trx_instance *trx, + const struct trxcon_phyif_cmdp_setfreq_h0 *cmdp) { uint16_t freq10; /* RX is downlink on MS side */ - freq10 = gsm_arfcn2freq10(band_arfcn, 0); + freq10 = gsm_arfcn2freq10(cmdp->band_arfcn, 0); if (freq10 == 0xffff) { - LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn); + LOGPFSML(trx->fi, LOGL_ERROR, "ARFCN %d not defined\n", cmdp->band_arfcn); return -ENOTSUP; } return trx_ctrl_cmd(trx, 1, "RXTUNE", "%u", freq10 * 100); } -int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn) +static int trx_if_cmd_txtune(struct trx_instance *trx, + const struct trxcon_phyif_cmdp_setfreq_h0 *cmdp) { uint16_t freq10; /* TX is uplink on MS side */ - freq10 = gsm_arfcn2freq10(band_arfcn, 1); + freq10 = gsm_arfcn2freq10(cmdp->band_arfcn, 1); if (freq10 == 0xffff) { - LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn); + LOGPFSML(trx->fi, LOGL_ERROR, "ARFCN %d not defined\n", cmdp->band_arfcn); return -ENOTSUP; } @@ -330,19 +354,16 @@ int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn) * RSP MEASURE <status> <kHz> <dB> */ -int trx_if_cmd_measure(struct trx_instance *trx, - uint16_t band_arfcn_start, uint16_t band_arfcn_stop) +static int trx_if_cmd_measure(struct trx_instance *trx, + const struct trxcon_phyif_cmdp_measure *cmdp) { uint16_t freq10; - /* Update ARFCN range for measurement */ - trx->pm_band_arfcn_start = band_arfcn_start; - trx->pm_band_arfcn_stop = band_arfcn_stop; - /* Calculate a frequency for current ARFCN (DL) */ - freq10 = gsm_arfcn2freq10(band_arfcn_start, 0); + freq10 = gsm_arfcn2freq10(cmdp->band_arfcn, 0); if (freq10 == 0xffff) { - LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn_start); + LOGPFSML(trx->fi, LOGL_ERROR, + "ARFCN %d not defined\n", cmdp->band_arfcn); return -ENOTSUP; } @@ -359,23 +380,22 @@ static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp) sscanf(resp, "%u %d", &freq10, &dbm); freq10 /= 100; - /* Check received ARFCN against expected */ band_arfcn = gsm_freq102arfcn((uint16_t) freq10, 0); - if (band_arfcn != trx->pm_band_arfcn_start) { - LOGP(DTRX, LOGL_ERROR, "Power measurement error: " - "response ARFCN=%u doesn't match expected ARFCN=%u\n", - band_arfcn &~ ARFCN_FLAG_MASK, - trx->pm_band_arfcn_start &~ ARFCN_FLAG_MASK); + if (band_arfcn == 0xffff) { + LOGPFSML(trx->fi, LOGL_ERROR, + "Failed to parse ARFCN from RSP MEASURE: %s\n", resp); return; } - /* Send L1CTL_PM_CONF */ - l1ctl_tx_pm_conf(trx->l1l, band_arfcn, dbm, - band_arfcn == trx->pm_band_arfcn_stop); + const struct trxcon_phyif_rsp rsp = { + .type = TRXCON_PHYIF_CMDT_MEASURE, + .param.measure = { + .band_arfcn = band_arfcn, + .dbm = dbm, + }, + }; - /* Schedule a next measurement */ - if (band_arfcn != trx->pm_band_arfcn_stop) - trx_if_cmd_measure(trx, ++band_arfcn, trx->pm_band_arfcn_stop); + trxcon_phyif_handle_rsp(trx->priv, &rsp); } /* @@ -391,53 +411,70 @@ static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp) * RSP SETTA <status> <TA> */ -int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta) +static int trx_if_cmd_setta(struct trx_instance *trx, + const struct trxcon_phyif_cmdp_setta *cmdp) { - return trx_ctrl_cmd(trx, 0, "SETTA", "%d", ta); + return trx_ctrl_cmd(trx, 0, "SETTA", "%d", cmdp->ta); } /* - * Frequency Hopping parameters indication + * Frequency Hopping parameters indication. + * + * SETFH instructs transceiver to enable frequency hopping mode + * using the given HSN, MAIO, and Mobile Allocation parameters. * - * SETFH instructs transceiver to enable frequency - * hopping mode using the given parameters. - * CMD SETFH <HSN> <MAIO> <CH1> <CH2> [... <CHN>] + * CMD SETFH <HSN> <MAIO> <RXF1> <TXF1> [... <RXFN> <TXFN>] + * + * where <RXFN> and <TXFN> is a pair of Rx/Tx frequencies (in kHz) + * corresponding to one ARFCN the Mobile Allocation. Note that the + * channel list is expected to be sorted in ascending order. */ -int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn, - uint8_t maio, uint16_t *ma, size_t ma_len) +static int trx_if_cmd_setfh(struct trx_instance *trx, + const struct trxcon_phyif_cmdp_setfreq_h1 *cmdp) { - char ma_buf[100]; + /* Reserve some room for CMD SETFH <HSN> <MAIO> */ + char ma_buf[TRXC_BUF_SIZE - 24]; + size_t ma_buf_len = sizeof(ma_buf) - 1; + uint16_t rx_freq, tx_freq; char *ptr; int i, rc; - /* No channels, WTF?!? */ - if (!ma_len) + /* Make sure that Mobile Allocation has at least one ARFCN */ + if (!cmdp->ma_len || cmdp->ma == NULL) { + LOGPFSML(trx->fi, LOGL_ERROR, "Mobile Allocation is empty?!?\n"); return -EINVAL; + } - /** - * Compose a sequence of channels (mobile allocation) - * FIXME: the length of a CTRL command is limited to 128 symbols, - * so we may have some problems if there are many channels... - */ - for (i = 0, ptr = ma_buf; i < ma_len; i++) { - /* Append a channel */ - rc = snprintf(ptr, ma_buf + sizeof(ma_buf) - ptr, "%u ", ma[i]); - if (rc < 0) - return rc; + /* Compose a sequence of Rx/Tx frequencies (mobile allocation) */ + for (i = 0, ptr = ma_buf; i < cmdp->ma_len; i++) { + /* Convert ARFCN to a pair of Rx/Tx frequencies (Hz * 10) */ + rx_freq = gsm_arfcn2freq10(cmdp->ma[i], 0); /* Rx: Downlink */ + tx_freq = gsm_arfcn2freq10(cmdp->ma[i], 1); /* Tx: Uplink */ + if (rx_freq == 0xffff || tx_freq == 0xffff) { + LOGPFSML(trx->fi, LOGL_ERROR, "Failed to convert ARFCN %u " + "to a pair of Rx/Tx frequencies\n", + cmdp->ma[i] & ~ARFCN_FLAG_MASK); + return -EINVAL; + } + + /* Append a pair of Rx/Tx frequencies (in kHz) to the buffer */ + rc = snprintf(ptr, ma_buf_len, "%u %u ", rx_freq * 100, tx_freq * 100); + if (rc < 0 || rc > ma_buf_len) { /* Prevent buffer overflow */ + LOGPFSML(trx->fi, LOGL_ERROR, "Not enough room to encode " + "Mobile Allocation (N=%u)\n", cmdp->ma_len); + return -ENOSPC; + } /* Move pointer */ + ma_buf_len -= rc; ptr += rc; - - /* Prevent buffer overflow */ - if (ptr >= (ma_buf + 100)) - return -EIO; } /* Overwrite the last space */ *(ptr - 1) = '\0'; - return trx_ctrl_cmd(trx, 1, "SETFH", "%u %u %s", hsn, maio, ma_buf); + return trx_ctrl_cmd(trx, 1, "SETFH", "%u %u %s", cmdp->hsn, cmdp->maio, ma_buf); } /* Get response from CTRL socket */ @@ -446,18 +483,18 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) struct trx_instance *trx = ofd->data; struct trx_ctrl_msg *tcm; int resp, rsp_len; - char buf[1500], *p; + char buf[TRXC_BUF_SIZE], *p; ssize_t read_len; read_len = read(ofd->fd, buf, sizeof(buf) - 1); if (read_len <= 0) { - LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", read_len); + LOGPFSML(trx->fi, LOGL_ERROR, "read() failed with rc=%zd\n", read_len); return read_len; } buf[read_len] = '\0'; if (!!strncmp(buf, "RSP ", 4)) { - LOGP(DTRX, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf); + LOGPFSML(trx->fi, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf); return 0; } @@ -465,15 +502,14 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) p = strchr(buf + 4, ' '); rsp_len = p ? p - buf - 4 : strlen(buf) - 4; - LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf); + LOGPFSML(trx->fi, LOGL_INFO, "Response message: '%s'\n", buf); /* Abort expire timer */ - if (osmo_timer_pending(&trx->trx_ctrl_timer)) - osmo_timer_del(&trx->trx_ctrl_timer); + osmo_timer_del(&trx->trx_ctrl_timer); /* Get command for response message */ if (llist_empty(&trx->trx_ctrl_list)) { - LOGP(DTRX, LOGL_NOTICE, "Response message without command\n"); + LOGPFSML(trx->fi, LOGL_NOTICE, "Response message without command\n"); return -EINVAL; } @@ -482,7 +518,7 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) /* Check if response matches command */ if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) { - LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR, + LOGPFSML(trx->fi, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR, "Response message '%s' does not match command " "message '%s'\n", buf, tcm->cmd); goto rsp_error; @@ -491,7 +527,7 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) /* Check for response code */ sscanf(p + 1, "%d", &resp); if (resp) { - LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR, + LOGPFSML(trx->fi, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR, "Transceiver rejected TRX command with " "response: '%s'\n", buf); @@ -502,18 +538,18 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) /* Trigger state machine */ if (!strncmp(tcm->cmd + 4, "POWERON", 7)) { trx->powered_up = true; - osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_ACTIVE, 0, 0); + osmo_fsm_inst_state_chg(trx->fi, TRX_STATE_ACTIVE, 0, 0); } else if (!strncmp(tcm->cmd + 4, "POWEROFF", 8)) { trx->powered_up = false; - osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0); + osmo_fsm_inst_state_chg(trx->fi, TRX_STATE_IDLE, 0, 0); } else if (!strncmp(tcm->cmd + 4, "MEASURE", 7)) trx_if_measure_rsp_cb(trx, buf + 14); else if (!strncmp(tcm->cmd + 4, "ECHO", 4)) - osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0); + osmo_fsm_inst_state_chg(trx->fi, TRX_STATE_IDLE, 0, 0); else - osmo_fsm_inst_state_chg(trx->fsm, trx->prev_state, 0, 0); + osmo_fsm_inst_state_chg(trx->fi, trx->prev_state, 0, 0); /* Remove command from list */ llist_del(&tcm->list); @@ -525,11 +561,53 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) return 0; rsp_error: - /* Notify higher layers about the problem */ - osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_RSP_ERROR, trx); + osmo_fsm_inst_term(trx->fi, OSMO_FSM_TERM_ERROR, NULL); return -EIO; } +int trx_if_handle_phyif_cmd(struct trx_instance *trx, const struct trxcon_phyif_cmd *cmd) +{ + int rc; + + switch (cmd->type) { + case TRXCON_PHYIF_CMDT_RESET: + if ((rc = trx_if_cmd_poweroff(trx)) != 0) + return rc; + rc = trx_if_cmd_echo(trx); + break; + case TRXCON_PHYIF_CMDT_POWERON: + rc = trx_if_cmd_poweron(trx); + break; + case TRXCON_PHYIF_CMDT_POWEROFF: + rc = trx_if_cmd_poweroff(trx); + break; + case TRXCON_PHYIF_CMDT_MEASURE: + rc = trx_if_cmd_measure(trx, &cmd->param.measure); + break; + case TRXCON_PHYIF_CMDT_SETFREQ_H0: + if ((rc = trx_if_cmd_rxtune(trx, &cmd->param.setfreq_h0)) != 0) + return rc; + if ((rc = trx_if_cmd_txtune(trx, &cmd->param.setfreq_h0)) != 0) + return rc; + break; + case TRXCON_PHYIF_CMDT_SETFREQ_H1: + rc = trx_if_cmd_setfh(trx, &cmd->param.setfreq_h1); + break; + case TRXCON_PHYIF_CMDT_SETSLOT: + rc = trx_if_cmd_setslot(trx, &cmd->param.setslot); + break; + case TRXCON_PHYIF_CMDT_SETTA: + rc = trx_if_cmd_setta(trx, &cmd->param.setta); + break; + default: + LOGPFSML(trx->fi, LOGL_ERROR, + "Unhandled PHYIF command type=0x%02x\n", cmd->type); + rc = -ENODEV; + } + + return rc; +} + /* ------------------------------------------------------------------------ */ /* Data interface handlers */ /* ------------------------------------------------------------------------ */ @@ -555,60 +633,93 @@ rsp_error: static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what) { struct trx_instance *trx = ofd->data; - uint8_t buf[256]; - sbit_t bits[148]; - int8_t rssi, tn; - int16_t toa256; - uint32_t fn; + struct trxcon_phyif_burst_ind bi; + uint8_t buf[TRXD_BUF_SIZE]; ssize_t read_len; + sbit_t *burst; read_len = read(ofd->fd, buf, sizeof(buf)); if (read_len <= 0) { - LOGP(DTRXD, LOGL_ERROR, "read() failed with rc=%zd\n", read_len); + strerror_r(errno, (char *)buf, sizeof(buf)); + LOGPFSMSL(trx->fi, DTRXD, LOGL_ERROR, + "read() failed on TRXD with rc=%zd (%s)\n", + read_len, (const char *)buf); return read_len; } - if (read_len != 158) { - LOGP(DTRXD, LOGL_ERROR, "Got data message with invalid " - "length '%zd'\n", read_len); + if (read_len < TRXDv0_HDR_LEN) { + LOGPFSMSL(trx->fi, DTRXD, LOGL_ERROR, + "Got malformed TRXD PDU (short length=%zd)\n", read_len); return -EINVAL; } - tn = buf[0]; - fn = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4]; - rssi = -(int8_t) buf[5]; - toa256 = ((int16_t) (buf[6] << 8) | buf[7]); - - /* Copy and convert bits {254..0} to sbits {-127..127} */ - osmo_ubit2sbit(bits, buf + 8, 148); + if ((buf[0] >> 4) != 0) { + LOGPFSMSL(trx->fi, DTRXD, LOGL_ERROR, + "Got TRXD PDU with unexpected version\n"); + return -ENOTSUP; + } - if (tn >= 8) { - LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn); + read_len -= TRXDv0_HDR_LEN; + switch (read_len) { + /* TRXDv0 PDUs may have 2 dummy bytes at the end */ + case GSM_NBITS_NB_GMSK_BURST + 2: + case GSM_NBITS_NB_8PSK_BURST + 2: + read_len -= 2; + break; + case GSM_NBITS_NB_GMSK_BURST: + case GSM_NBITS_NB_8PSK_BURST: + break; + default: + LOGPFSMSL(trx->fi, DTRXD, LOGL_ERROR, + "Got TRXD PDU unexpected burst length=%zd\n", read_len); return -EINVAL; } - if (fn >= 2715648) { - LOGP(DTRXD, LOGL_ERROR, "Illegal FN %u\n", fn); + burst = (sbit_t *)&buf[8]; + + bi = (struct trxcon_phyif_burst_ind) { + .tn = buf[0] & 0x07, + .fn = osmo_load32be(buf + 1), + .rssi = -(int8_t) buf[5], + .toa256 = (int16_t) (buf[6] << 8) | buf[7], + .burst = burst, /* at least GSM_NBITS_NB_GMSK_BURST */ + .burst_len = read_len, + }; + + /* Convert ubits {254..0} to sbits {-127..127} in-place */ + for (unsigned int i = 0; i < bi.burst_len; i++) { + if (buf[8 + i] == 255) + burst[i] = -127; + else + burst[i] = 127 - buf[8 + i]; + } + + if (bi.fn >= GSM_TDMA_HYPERFRAME) { + LOGPFSMSL(trx->fi, DTRXD, LOGL_ERROR, "Illegal FN %u\n", bi.fn); return -EINVAL; } - LOGP(DTRXD, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%d\n", - tn, fn, rssi, toa256); + LOGPFSMSL(trx->fi, DTRXD, LOGL_DEBUG, + "RX burst tn=%u fn=%u rssi=%d toa=%d\n", + bi.tn, bi.fn, bi.rssi, bi.toa256); - /* Poke scheduler */ - sched_trx_handle_rx_burst(trx, tn, fn, bits, 148, rssi, toa256); + trxcon_phyif_handle_burst_ind(trx->priv, &bi); - /* Correct local clock counter */ - if (fn % 51 == 0) - sched_clck_handle(&trx->sched, fn); + struct trxcon_phyif_rts_ind rts = { + .fn = GSM_TDMA_FN_SUM(bi.fn, trx->fn_advance), + .tn = bi.tn, + }; + + trxcon_phyif_handle_rts_ind(trx->priv, &rts); return 0; } -int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn, - uint8_t pwr, const ubit_t *bits) +int trx_if_handle_phyif_burst_req(struct trx_instance *trx, + const struct trxcon_phyif_burst_req *br) { - uint8_t buf[256]; + uint8_t buf[TRXD_BUF_SIZE]; + size_t length; /** * We must be sure that we have clock, @@ -617,55 +728,62 @@ int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn, * TODO: introduce proper state machines for both * transceiver and its TRXC interface. */ - if (trx->fsm->state != TRX_STATE_ACTIVE) { - LOGP(DTRXD, LOGL_ERROR, "Ignoring TX data, " - "transceiver isn't ready\n"); +#if 0 + if (trx->fi->state != TRX_STATE_ACTIVE) { + LOGPFSMSL(trx->fi, DTRXD, LOGL_ERROR, + "Ignoring TX data, transceiver isn't ready\n"); return -EAGAIN; } +#endif - LOGP(DTRXD, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr); + LOGPFSMSL(trx->fi, DTRXD, LOGL_DEBUG, + "TX burst tn=%u fn=%u pwr=%u\n", + br->tn, br->fn, br->pwr); - buf[0] = tn; - buf[1] = (fn >> 24) & 0xff; - buf[2] = (fn >> 16) & 0xff; - buf[3] = (fn >> 8) & 0xff; - buf[4] = (fn >> 0) & 0xff; - buf[5] = pwr; + buf[0] = br->tn; + osmo_store32be(br->fn, buf + 1); + buf[5] = br->pwr; + length = 6; /* Copy ubits {0,1} */ - memcpy(buf + 6, bits, 148); + if (br->burst_len != 0) { + memcpy(buf + 6, br->burst, br->burst_len); + length += br->burst_len; + } /* Send data to transceiver */ - send(trx->trx_ofd_data.fd, buf, 154, 0); + send(trx->trx_ofd_data.fd, buf, length, 0); return 0; } /* Init TRX interface (TRXC, TRXD sockets and FSM) */ -struct trx_instance *trx_if_open(void *tall_ctx, - const char *local_host, const char *remote_host, - uint16_t base_port) +struct trx_instance *trx_if_open(const struct trx_if_params *params) { + const unsigned int offset = params->instance * 2; struct trx_instance *trx; + struct osmo_fsm_inst *fi; int rc; - LOGP(DTRX, LOGL_NOTICE, "Init transceiver interface " - "(%s:%u)\n", remote_host, base_port); + LOGPFSML(params->parent_fi, LOGL_NOTICE, + "Init transceiver interface (%s:%u/%u)\n", + params->remote_host, params->base_port, + params->instance); - /* Try to allocate memory */ - trx = talloc_zero(tall_ctx, struct trx_instance); - if (!trx) { - LOGP(DTRX, LOGL_ERROR, "Failed to allocate memory\n"); + /* Allocate a new dedicated state machine */ + fi = osmo_fsm_inst_alloc_child(&trx_fsm, params->parent_fi, + params->parent_term_event); + if (fi == NULL) { + LOGPFSML(params->parent_fi, LOGL_ERROR, + "Failed to allocate an instance of FSM '%s'\n", + trx_fsm.name); return NULL; } - /* Allocate a new dedicated state machine */ - trx->fsm = osmo_fsm_inst_alloc(&trx_fsm, trx, - NULL, LOGL_DEBUG, "trx_interface"); - if (trx->fsm == NULL) { - LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance " - "of FSM '%s'\n", trx_fsm.name); - talloc_free(trx); + trx = talloc_zero(fi, struct trx_instance); + if (!trx) { + LOGPFSML(params->parent_fi, LOGL_ERROR, "Failed to allocate memory\n"); + osmo_fsm_inst_free(fi); return NULL; } @@ -673,32 +791,40 @@ struct trx_instance *trx_if_open(void *tall_ctx, INIT_LLIST_HEAD(&trx->trx_ctrl_list); /* Open sockets */ - rc = trx_udp_open(trx, &trx->trx_ofd_ctrl, local_host, - base_port + 101, remote_host, base_port + 1, trx_ctrl_read_cb); + rc = trx_udp_open(trx, &trx->trx_ofd_ctrl, /* TRXC */ + params->local_host, params->base_port + 101 + offset, + params->remote_host, params->base_port + 1 + offset, + trx_ctrl_read_cb); if (rc < 0) goto udp_error; - rc = trx_udp_open(trx, &trx->trx_ofd_data, local_host, - base_port + 102, remote_host, base_port + 2, trx_data_rx_cb); + rc = trx_udp_open(trx, &trx->trx_ofd_data, /* TRXD */ + params->local_host, params->base_port + 102 + offset, + params->remote_host, params->base_port + 2 + offset, + trx_data_rx_cb); if (rc < 0) goto udp_error; + trx->fn_advance = params->fn_advance; + trx->priv = params->priv; + fi->priv = trx; + trx->fi = fi; + return trx; udp_error: - LOGP(DTRX, LOGL_ERROR, "Couldn't establish UDP connection\n"); - osmo_fsm_inst_free(trx->fsm); - talloc_free(trx); + LOGPFSML(params->parent_fi, LOGL_ERROR, "Couldn't establish UDP connection\n"); + osmo_fsm_inst_free(trx->fi); return NULL; } /* Flush pending control messages */ -void trx_if_flush_ctrl(struct trx_instance *trx) +static void trx_if_flush_ctrl(struct trx_instance *trx) { struct trx_ctrl_msg *tcm; /* Reset state machine */ - osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0); + osmo_fsm_inst_state_chg(trx->fi, TRX_STATE_IDLE, 0, 0); /* Clear command queue */ while (!llist_empty(&trx->trx_ctrl_list)) { @@ -711,21 +837,39 @@ void trx_if_flush_ctrl(struct trx_instance *trx) void trx_if_close(struct trx_instance *trx) { + if (trx == NULL || trx->fi == NULL) + return; + osmo_fsm_inst_term(trx->fi, OSMO_FSM_TERM_REQUEST, NULL); +} + +static void trx_fsm_cleanup_cb(struct osmo_fsm_inst *fi, + enum osmo_fsm_term_cause cause) +{ + static const char cmd_poweroff[] = "CMD POWEROFF"; + struct trx_instance *trx = fi->priv; + /* May be unallocated due to init error */ if (!trx) return; - LOGP(DTRX, LOGL_NOTICE, "Shutdown transceiver interface\n"); + LOGPFSML(fi, LOGL_NOTICE, "Shutdown transceiver interface\n"); + + /* Abort TRXC response timer (if pending) */ + osmo_timer_del(&trx->trx_ctrl_timer); /* Flush CTRL message list */ trx_if_flush_ctrl(trx); + /* Power off if the transceiver is up */ + if (trx->powered_up && trx->trx_ofd_ctrl.fd >= 0) + send(trx->trx_ofd_ctrl.fd, &cmd_poweroff[0], sizeof(cmd_poweroff), 0); + /* Close sockets */ trx_udp_close(&trx->trx_ofd_ctrl); trx_udp_close(&trx->trx_ofd_data); /* Free memory */ - osmo_fsm_inst_free(trx->fsm); + trx->fi->priv = NULL; talloc_free(trx); } diff --git a/src/host/trxcon/src/trxcon_fsm.c b/src/host/trxcon/src/trxcon_fsm.c new file mode 100644 index 00000000..95458838 --- /dev/null +++ b/src/host/trxcon/src/trxcon_fsm.c @@ -0,0 +1,822 @@ +/* + * OsmocomBB <-> SDR connection bridge + * The trxcon state machine (see 3GPP TS 44.004, section 5.1) + * + * (C) 2022-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de> + * + * 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. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/logging.h> +#include <osmocom/gsm/gsm0502.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> +#include <osmocom/bb/trxcon/phyif.h> +#include <osmocom/bb/trxcon/l1ctl.h> +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1gprs.h> + +#define S(x) (1 << (x)) + +static void trxcon_allstate_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct trxcon_inst *trxcon = fi->priv; + struct trxcon_phyif_cmd phycmd = { }; + + switch (event) { + case TRXCON_EV_PHYIF_FAILURE: + trxcon->phyif = NULL; + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + break; + case TRXCON_EV_L2IF_FAILURE: + trxcon->l2if = NULL; + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + break; + case TRXCON_EV_RESET_FULL_REQ: + TALLOC_FREE(trxcon->fi_data); + if (fi->state != TRXCON_ST_RESET) + osmo_fsm_inst_state_chg(fi, TRXCON_ST_RESET, 0, 0); + l1sched_reset(trxcon->sched, true); + + /* Reset the L1 parameters */ + trxcon->l1p.band_arfcn = 0xffff; + trxcon->l1p.tx_power = 0; + trxcon->l1p.ta = 0; + + phycmd.type = TRXCON_PHYIF_CMDT_RESET; + trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd); + break; + case TRXCON_EV_RESET_SCHED_REQ: + l1sched_reset(trxcon->sched, false); + break; + case TRXCON_EV_SET_PHY_CONFIG_REQ: + { + const struct trxcon_param_set_phy_config_req *req = data; + + switch (req->type) { + case TRXCON_PHY_CFGT_PCHAN_COMB: + phycmd.type = TRXCON_PHYIF_CMDT_SETSLOT; + phycmd.param.setslot.tn = req->pchan_comb.tn; + phycmd.param.setslot.pchan = req->pchan_comb.pchan; + trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd); + break; + case TRXCON_PHY_CFGT_TX_PARAMS: + if (trxcon->l1p.ta != req->tx_params.timing_advance) { + phycmd.type = TRXCON_PHYIF_CMDT_SETTA; + phycmd.param.setta.ta = req->tx_params.timing_advance; + trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd); + } + trxcon->l1p.tx_power = req->tx_params.tx_power; + trxcon->l1p.ta = req->tx_params.timing_advance; + break; + } + break; + } + case TRXCON_EV_UPDATE_SACCH_CACHE_REQ: + { + const struct trxcon_param_tx_data_req *req = data; + + if (req->link_id != L1SCHED_CH_LID_SACCH) { + LOGPFSML(fi, LOGL_ERROR, "Unexpected link_id=0x%02x\n", req->link_id); + break; + } + if (req->data_len != GSM_MACBLOCK_LEN) { + LOGPFSML(fi, LOGL_ERROR, "Unexpected data length=%zu\n", req->data_len); + break; + } + + memcpy(&trxcon->sched->sacch_cache[0], req->data, req->data_len); + break; + } + default: + OSMO_ASSERT(0); + } +} + +static int trxcon_timer_cb(struct osmo_fsm_inst *fi) +{ + struct trxcon_inst *trxcon = fi->priv; + + switch (fi->state) { + case TRXCON_ST_FBSB_SEARCH: + l1ctl_tx_fbsb_fail(trxcon, trxcon->l1p.band_arfcn); + osmo_fsm_inst_state_chg(fi, TRXCON_ST_RESET, 0, 0); + return 0; + default: + OSMO_ASSERT(0); + } +} + +static void handle_full_power_scan_req(struct osmo_fsm_inst *fi, + const struct trxcon_param_full_power_scan_req *req) +{ + struct trxcon_inst *trxcon = fi->priv; + enum gsm_band band_start, band_stop; + + if (trxcon->fi_data != NULL) { + LOGPFSML(fi, LOGL_ERROR, "Full power scan is already in progress\n"); + return; + } + + /* The start and stop ARFCNs must be in the same band */ + if (gsm_arfcn2band_rc(req->band_arfcn_start, &band_start) != 0 || + gsm_arfcn2band_rc(req->band_arfcn_stop, &band_stop) != 0 || + band_start != band_stop) { + LOGPFSML(fi, LOGL_ERROR, "Full power scan request has invalid params\n"); + return; + } + + trxcon->fi_data = talloc_memdup(fi, req, sizeof(*req)); + OSMO_ASSERT(trxcon->fi_data != NULL); + + /* trxcon_st_full_power_scan_onenter() sends the initial TRXCON_PHYIF_CMDT_MEASURE */ + osmo_fsm_inst_state_chg(fi, TRXCON_ST_FULL_POWER_SCAN, 0, 0); /* TODO: timeout */ +} + +static void trxcon_st_reset_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct trxcon_inst *trxcon = fi->priv; + + switch (event) { + case TRXCON_EV_FBSB_SEARCH_REQ: + { + const struct trxcon_param_fbsb_search_req *req = data; + unsigned long timeout_fns, timeout_ms; + + /* Some PHYs need additional time to tune (in TDMA FNs) */ + timeout_fns = req->timeout_fns + trxcon->phy_quirks.fbsb_extend_fns; + timeout_ms = timeout_fns * GSM_TDMA_FN_DURATION_uS / 1000; + osmo_fsm_inst_state_chg_ms(fi, TRXCON_ST_FBSB_SEARCH, timeout_ms, 0); + + l1sched_configure_ts(trxcon->sched, 0, req->pchan_config); + + /* Only if current ARFCN differs */ + if (trxcon->l1p.band_arfcn != req->band_arfcn) { + const struct trxcon_phyif_cmd phycmd = { + .type = TRXCON_PHYIF_CMDT_SETFREQ_H0, + .param.setfreq_h0 = { + .band_arfcn = req->band_arfcn, + }, + }; + + /* Update current ARFCN */ + trxcon->l1p.band_arfcn = req->band_arfcn; + + /* Tune transceiver to required ARFCN */ + trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd); + } + + const struct trxcon_phyif_cmd phycmd = { .type = TRXCON_PHYIF_CMDT_POWERON }; + trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd); + break; + } + case TRXCON_EV_FULL_POWER_SCAN_REQ: + handle_full_power_scan_req(fi, (const struct trxcon_param_full_power_scan_req *)data); + break; + default: + OSMO_ASSERT(0); + } +} + +static void trxcon_st_full_power_scan_onenter(struct osmo_fsm_inst *fi, + uint32_t prev_state) +{ + const struct trxcon_inst *trxcon = fi->priv; + const struct trxcon_param_full_power_scan_req *req = trxcon->fi_data; + + /* req->band_arfcn_start holds the current ARFCN */ + const struct trxcon_phyif_cmd phycmd = { + .type = TRXCON_PHYIF_CMDT_MEASURE, + .param.measure = { + .band_arfcn = req->band_arfcn_start, + }, + }; + + trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd); +} + +static void trxcon_st_full_power_scan_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct trxcon_inst *trxcon = fi->priv; + + switch (event) { + case TRXCON_EV_FULL_POWER_SCAN_RES: + { + struct trxcon_param_full_power_scan_req *req = trxcon->fi_data; + const struct trxcon_param_full_power_scan_res *res = data; + + if (req == NULL) { + LOGPFSML(fi, LOGL_ERROR, "Rx unexpected power scan result\n"); + break; + } + + /* req->band_arfcn_start holds the expected ARFCN */ + if (res->band_arfcn != req->band_arfcn_start) { + LOGPFSML(fi, LOGL_ERROR, "Rx power scan result " + "with unexpected ARFCN %u (expected %u)\n", + res->band_arfcn & ~ARFCN_FLAG_MASK, + req->band_arfcn_start & ~ARFCN_FLAG_MASK); + break; + } + + if (res->band_arfcn < req->band_arfcn_stop) { + l1ctl_tx_pm_conf(trxcon, res->band_arfcn, res->dbm, false); + /* trxcon_st_full_power_scan_onenter() sends the next TRXCON_PHYIF_CMDT_MEASURE */ + req->band_arfcn_start = res->band_arfcn + 1; + osmo_fsm_inst_state_chg(fi, TRXCON_ST_FULL_POWER_SCAN, 0, 0); /* TODO: timeout */ + } else { + l1ctl_tx_pm_conf(trxcon, res->band_arfcn, res->dbm, true); + LOGPFSML(fi, LOGL_INFO, "Full power scan completed\n"); + TALLOC_FREE(trxcon->fi_data); + } + break; + } + case TRXCON_EV_FULL_POWER_SCAN_REQ: + handle_full_power_scan_req(fi, (const struct trxcon_param_full_power_scan_req *)data); + break; + default: + OSMO_ASSERT(0); + } +} + +static void trxcon_st_fbsb_search_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct trxcon_inst *trxcon = fi->priv; + + switch (event) { + case TRXCON_EV_FBSB_SEARCH_RES: + osmo_fsm_inst_state_chg(fi, TRXCON_ST_BCCH_CCCH, 0, 0); + l1ctl_tx_fbsb_conf(trxcon, trxcon->l1p.band_arfcn, trxcon->sched->bsic); + break; + default: + OSMO_ASSERT(0); + } +} + +static void handle_tx_access_burst_req(struct osmo_fsm_inst *fi, + const struct trxcon_param_tx_access_burst_req *req) +{ + struct trxcon_inst *trxcon = fi->priv; + struct l1sched_prim *prim; + struct msgb *msg; + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_RACH, PRIM_OP_REQUEST); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->rach_req = (struct l1sched_prim_rach) { + .chdr = { + .chan_nr = req->chan_nr, + .link_id = req->link_id, + }, + .synch_seq = req->synch_seq, + .offset = req->offset, + .is_11bit = req->is_11bit, + .ra = req->ra, + }; + + l1sched_prim_from_user(trxcon->sched, msg); +} + +static void handle_dch_est_req(struct osmo_fsm_inst *fi, + const struct trxcon_param_dch_est_req *req) +{ + struct trxcon_inst *trxcon = fi->priv; + enum gsm_phys_chan_config config; + struct l1sched_ts *ts; + int rc; + + config = l1sched_chan_nr2pchan_config(req->chan_nr); + if (config == GSM_PCHAN_NONE) { + LOGPFSML(fi, LOGL_ERROR, "Failed to determine channel config\n"); + return; + } + + if (req->hopping) { + const struct trxcon_phyif_cmd phycmd = { + .type = TRXCON_PHYIF_CMDT_SETFREQ_H1, + .param.setfreq_h1 = { + .hsn = req->h1.hsn, + .maio = req->h1.maio, + .ma = &req->h1.ma[0], + .ma_len = req->h1.n, + }, + }; + + /* Apply the freq. hopping parameters */ + if (trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd) != 0) + return; + + /* Set current ARFCN to an invalid value */ + trxcon->l1p.band_arfcn = 0xffff; + } else { + const struct trxcon_phyif_cmd phycmd = { + .type = TRXCON_PHYIF_CMDT_SETFREQ_H0, + .param.setfreq_h0 = { + .band_arfcn = req->h0.band_arfcn, + }, + }; + + /* Tune transceiver to required ARFCN */ + if (trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd) != 0) + return; + + /* Update current ARFCN */ + trxcon->l1p.band_arfcn = req->h0.band_arfcn; + } + + /* Remove all active timeslots */ + l1sched_reset(trxcon->sched, false); + + rc = l1sched_configure_ts(trxcon->sched, req->chan_nr & 0x07, config); + if (rc) + return; + ts = trxcon->sched->ts[req->chan_nr & 0x07]; + OSMO_ASSERT(ts != NULL); + + l1sched_deactivate_all_lchans(ts); + + /* Activate only requested lchans */ + rc = l1sched_set_lchans(ts, req->chan_nr, 1, req->tch_mode, req->tsc); + if (rc) { + LOGPFSML(fi, LOGL_ERROR, "Failed to activate requested lchans\n"); + return; + } + + /* Store TSC for subsequent PDCH timeslot activation(s) */ + trxcon->l1p.tsc = req->tsc; + + if (config == GSM_PCHAN_PDCH) + osmo_fsm_inst_state_chg(fi, TRXCON_ST_PACKET_DATA, 0, 0); + else + osmo_fsm_inst_state_chg(fi, TRXCON_ST_DEDICATED, 0, 0); +} + +static void trxcon_st_bcch_ccch_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct trxcon_inst *trxcon = fi->priv; + + switch (event) { + case TRXCON_EV_TX_ACCESS_BURST_REQ: + handle_tx_access_burst_req(fi, data); + break; + case TRXCON_EV_TX_ACCESS_BURST_CNF: + l1ctl_tx_rach_conf(trxcon, (const struct trxcon_param_tx_access_burst_cnf *)data); + break; + case TRXCON_EV_SET_CCCH_MODE_REQ: + { + struct trxcon_param_set_ccch_tch_mode_req *req = data; + enum gsm_phys_chan_config chan_config = req->mode; + struct l1sched_ts *ts = trxcon->sched->ts[0]; + + /* Make sure that TS0 is allocated and configured */ + if (ts == NULL || ts->mf_layout == NULL) { + LOGPFSML(fi, LOGL_ERROR, "TS0 is not configured\n"); + return; + } + + /* Do nothing if the current mode matches required */ + if (ts->mf_layout->chan_config != chan_config) + l1sched_configure_ts(trxcon->sched, 0, chan_config); + req->applied = true; + break; + } + case TRXCON_EV_DCH_EST_REQ: + handle_dch_est_req(fi, (const struct trxcon_param_dch_est_req *)data); + break; + case TRXCON_EV_RX_DATA_IND: + l1ctl_tx_dt_ind(trxcon, (const struct trxcon_param_rx_data_ind *)data); + break; + default: + OSMO_ASSERT(0); + } +} + +static void trxcon_st_dedicated_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct trxcon_inst *trxcon = fi->priv; + + switch (event) { + case TRXCON_EV_TX_ACCESS_BURST_REQ: + handle_tx_access_burst_req(fi, data); + break; + case TRXCON_EV_TX_ACCESS_BURST_CNF: + l1ctl_tx_rach_conf(trxcon, (const struct trxcon_param_tx_access_burst_cnf *)data); + break; + case TRXCON_EV_DCH_EST_REQ: + handle_dch_est_req(fi, (const struct trxcon_param_dch_est_req *)data); + break; + case TRXCON_EV_DCH_REL_REQ: + l1sched_reset(trxcon->sched, false); + /* TODO: switch to (not implemented) TRXCON_ST_DCH_TUNING? */ + break; + case TRXCON_EV_SET_TCH_MODE_REQ: + { + struct trxcon_param_set_ccch_tch_mode_req *req = data; + unsigned int tn; + + /* Iterate over timeslot list */ + for (tn = 0; tn < ARRAY_SIZE(trxcon->sched->ts); tn++) { + struct l1sched_ts *ts = trxcon->sched->ts[tn]; + struct l1sched_lchan_state *lchan; + + /* Timeslot is not allocated */ + if (ts == NULL || ts->mf_layout == NULL) + continue; + + /* Iterate over all allocated lchans */ + llist_for_each_entry(lchan, &ts->lchans, list) { + /* Omit inactive channels */ + if (!lchan->active) + continue; + if (req->mode == GSM48_CMODE_SPEECH_AMR) { + int rc = l1sched_lchan_set_amr_cfg(lchan, + req->amr.codecs_bitmask, + req->amr.start_codec); + if (rc) + continue; + } + lchan->tch_mode = req->mode; + req->applied = true; + } + } + break; + } + case TRXCON_EV_CRYPTO_REQ: + { + const struct trxcon_param_crypto_req *req = data; + unsigned int tn = req->chan_nr & 0x07; + struct l1sched_ts *ts; + + /* Make sure that required TS is allocated and configured */ + ts = trxcon->sched->ts[tn]; + if (ts == NULL || ts->mf_layout == NULL) { + LOGPFSML(fi, LOGL_ERROR, "TS%u is not configured\n", tn); + return; + } + + if (l1sched_start_ciphering(ts, req->a5_algo, req->key, req->key_len) != 0) { + LOGPFSML(fi, LOGL_ERROR, "Failed to configure ciphering\n"); + return; + } + break; + } + case TRXCON_EV_TX_DATA_REQ: + { + const struct trxcon_param_tx_data_req *req = data; + struct l1sched_prim *prim; + struct msgb *msg; + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->data_req = (struct l1sched_prim_chdr) { + .chan_nr = req->chan_nr, + .link_id = req->link_id, + .traffic = req->traffic, + }; + + memcpy(msgb_put(msg, req->data_len), req->data, req->data_len); + l1sched_prim_from_user(trxcon->sched, msg); + break; + } + case TRXCON_EV_TX_DATA_CNF: + l1ctl_tx_dt_conf(trxcon, (const struct trxcon_param_tx_data_cnf *)data); + break; + case TRXCON_EV_RX_DATA_IND: + l1ctl_tx_dt_ind(trxcon, (const struct trxcon_param_rx_data_ind *)data); + break; + default: + OSMO_ASSERT(0); + } +} + +static void handle_tbf_cfg_req(struct trxcon_inst *trxcon, uint8_t tn, bool active) +{ + struct l1sched_state *sched = trxcon->sched; + + if (active) { + struct l1sched_lchan_state *lchan; + struct l1sched_ts *ts; + + if (sched->ts[tn] != NULL) /* already enabled */ + return; + if (l1sched_configure_ts(sched, tn, GSM_PCHAN_PDCH) != 0) + return; + OSMO_ASSERT(sched->ts[tn] != NULL); + ts = sched->ts[tn]; + + l1sched_activate_lchan(ts, L1SCHED_PDTCH); + l1sched_activate_lchan(ts, L1SCHED_PTCCH); + llist_for_each_entry(lchan, &ts->lchans, list) + lchan->tsc = trxcon->l1p.tsc; + } else { + l1sched_del_ts(sched, tn); + } +} + +static void trxcon_l1gprs_state_changed_cb(struct l1gprs_pdch *pdch, bool active) +{ + handle_tbf_cfg_req(pdch->gprs->priv, pdch->tn, active); +} + +static void trxcon_st_packet_data_onenter(struct osmo_fsm_inst *fi, + uint32_t prev_state) +{ + struct trxcon_inst *trxcon = fi->priv; + + OSMO_ASSERT(trxcon->gprs == NULL); + trxcon->gprs = l1gprs_state_alloc(trxcon, trxcon->log_prefix, trxcon); + l1gprs_state_set_pdch_changed_cb(trxcon->gprs, trxcon_l1gprs_state_changed_cb); + OSMO_ASSERT(trxcon->gprs != NULL); +} + +static void trxcon_st_packet_data_onleave(struct osmo_fsm_inst *fi, + uint32_t next_state) +{ + struct trxcon_inst *trxcon = fi->priv; + + l1gprs_state_free(trxcon->gprs); + trxcon->gprs = NULL; +} + +static void trxcon_st_packet_data_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct trxcon_inst *trxcon = fi->priv; + + switch (event) { + case TRXCON_EV_TX_ACCESS_BURST_REQ: + handle_tx_access_burst_req(fi, data); + break; + case TRXCON_EV_TX_ACCESS_BURST_CNF: + l1ctl_tx_rach_conf(trxcon, (const struct trxcon_param_tx_access_burst_cnf *)data); + break; + case TRXCON_EV_GPRS_UL_TBF_CFG_REQ: + l1gprs_handle_ul_tbf_cfg_req(trxcon->gprs, (struct msgb *)data); + break; + case TRXCON_EV_GPRS_DL_TBF_CFG_REQ: + l1gprs_handle_dl_tbf_cfg_req(trxcon->gprs, (struct msgb *)data); + break; + case TRXCON_EV_GPRS_UL_BLOCK_REQ: + { + struct l1gprs_prim_ul_block_req block_req; + struct l1sched_prim *prim; + struct msgb *msg = data; + + if (l1gprs_handle_ul_block_req(trxcon->gprs, &block_req, msg) != 0) + return; + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->data_req = (struct l1sched_prim_chdr) { + .frame_nr = block_req.hdr.fn, + .chan_nr = RSL_CHAN_OSMO_PDCH | block_req.hdr.tn, + .link_id = 0x00, + }; + + memcpy(msgb_put(msg, block_req.data_len), block_req.data, block_req.data_len); + l1sched_prim_from_user(trxcon->sched, msg); + break; + } + case TRXCON_EV_TX_DATA_CNF: + { + const struct trxcon_param_tx_data_cnf *cnf = data; + struct msgb *msg; + + msg = l1gprs_handle_ul_block_cnf(trxcon->gprs, + cnf->frame_nr, cnf->chan_nr & 0x07, + cnf->data, cnf->data_len); + if (msg != NULL) + trxcon_l1ctl_send(trxcon, msg); + break; + } + case TRXCON_EV_RX_DATA_IND: + { + const struct trxcon_param_rx_data_ind *ind = data; + struct l1gprs_prim_dl_block_ind block_ind; + struct msgb *msg; + uint8_t usf = 0xff; + + block_ind = (struct l1gprs_prim_dl_block_ind) { + .hdr = { + .fn = ind->frame_nr, + .tn = ind->chan_nr & 0x07, + }, + .meas = { + /* .ber10k is set below */ + .ci_cb = 180, /* 18 dB */ + .rx_lev = dbm2rxlev(ind->rssi), + }, + .data_len = ind->data_len, + .data = ind->data, + }; + + if (ind->n_bits_total == 0) + block_ind.meas.ber10k = 10000; + else + block_ind.meas.ber10k = 10000 * ind->n_errors / ind->n_bits_total; + + msg = l1gprs_handle_dl_block_ind(trxcon->gprs, &block_ind, &usf); + if (msg != NULL) + trxcon_l1ctl_send(trxcon, msg); + /* Every fn % 13 == 12 we have either a PTCCH or an IDLE slot, thus + * every fn % 13 == 8 we add 5 frames, or 4 frames othrwise. The + * resulting value is first fn of the next block. */ + const uint32_t rts_fn = GSM_TDMA_FN_SUM(ind->frame_nr, (ind->frame_nr % 13 == 8) ? 5 : 4); + msg = l1gprs_handle_rts_ind(trxcon->gprs, rts_fn, ind->chan_nr & 0x07, usf); + if (msg != NULL) + trxcon_l1ctl_send(trxcon, msg); + break; + } + case TRXCON_EV_DCH_EST_REQ: + handle_dch_est_req(fi, (const struct trxcon_param_dch_est_req *)data); + break; + case TRXCON_EV_DCH_REL_REQ: + l1sched_reset(trxcon->sched, false); + /* TODO: switch to (not implemented) TRXCON_ST_DCH_TUNING? */ + break; + default: + OSMO_ASSERT(0); + } +} + +static void trxcon_fsm_pre_term_cb(struct osmo_fsm_inst *fi, + enum osmo_fsm_term_cause cause) +{ + struct trxcon_inst *trxcon = fi->priv; + + if (trxcon == NULL) + return; + + /* Shutdown the scheduler */ + if (trxcon->sched != NULL) + l1sched_free(trxcon->sched); + /* Clean up GPRS L1 state */ + l1gprs_state_free(trxcon->gprs); + + /* Close active connections */ + if (trxcon->l2if != NULL) + trxcon_l1ctl_close(trxcon); + if (trxcon->phyif != NULL) + trxcon_phyif_close(trxcon->phyif); + + talloc_free(trxcon); + fi->priv = NULL; +} + +static const struct osmo_fsm_state trxcon_fsm_states[] = { + [TRXCON_ST_RESET] = { + .name = "RESET", + .out_state_mask = S(TRXCON_ST_FBSB_SEARCH) + | S(TRXCON_ST_FULL_POWER_SCAN), + .in_event_mask = S(TRXCON_EV_FBSB_SEARCH_REQ) + | S(TRXCON_EV_FULL_POWER_SCAN_REQ), + .action = &trxcon_st_reset_action, + }, + [TRXCON_ST_FULL_POWER_SCAN] = { + .name = "FULL_POWER_SCAN", + .out_state_mask = S(TRXCON_ST_RESET) + | S(TRXCON_ST_FULL_POWER_SCAN), + .in_event_mask = S(TRXCON_EV_FULL_POWER_SCAN_RES) + | S(TRXCON_EV_FULL_POWER_SCAN_REQ), + .onenter = &trxcon_st_full_power_scan_onenter, + .action = &trxcon_st_full_power_scan_action, + }, + [TRXCON_ST_FBSB_SEARCH] = { + .name = "FBSB_SEARCH", + .out_state_mask = S(TRXCON_ST_RESET) + | S(TRXCON_ST_BCCH_CCCH), + .in_event_mask = S(TRXCON_EV_FBSB_SEARCH_RES), + .action = &trxcon_st_fbsb_search_action, + }, + [TRXCON_ST_BCCH_CCCH] = { + .name = "BCCH_CCCH", + .out_state_mask = S(TRXCON_ST_RESET) + | S(TRXCON_ST_FBSB_SEARCH) + | S(TRXCON_ST_DEDICATED) + | S(TRXCON_ST_PACKET_DATA), + .in_event_mask = S(TRXCON_EV_RX_DATA_IND) + | S(TRXCON_EV_SET_CCCH_MODE_REQ) + | S(TRXCON_EV_TX_ACCESS_BURST_REQ) + | S(TRXCON_EV_TX_ACCESS_BURST_CNF) + | S(TRXCON_EV_DCH_EST_REQ), + .action = &trxcon_st_bcch_ccch_action, + }, + [TRXCON_ST_DEDICATED] = { + .name = "DEDICATED", + .out_state_mask = S(TRXCON_ST_RESET) + | S(TRXCON_ST_FBSB_SEARCH) + | S(TRXCON_ST_DEDICATED) + | S(TRXCON_ST_PACKET_DATA), + .in_event_mask = S(TRXCON_EV_DCH_REL_REQ) + | S(TRXCON_EV_DCH_EST_REQ) + | S(TRXCON_EV_TX_ACCESS_BURST_REQ) + | S(TRXCON_EV_TX_ACCESS_BURST_CNF) + | S(TRXCON_EV_SET_TCH_MODE_REQ) + | S(TRXCON_EV_TX_DATA_REQ) + | S(TRXCON_EV_TX_DATA_CNF) + | S(TRXCON_EV_RX_DATA_IND) + | S(TRXCON_EV_CRYPTO_REQ), + .action = &trxcon_st_dedicated_action, + }, + [TRXCON_ST_PACKET_DATA] = { + .name = "PACKET_DATA", + .out_state_mask = S(TRXCON_ST_RESET) + | S(TRXCON_ST_FBSB_SEARCH) + | S(TRXCON_ST_DEDICATED) + | S(TRXCON_ST_PACKET_DATA), + .in_event_mask = S(TRXCON_EV_DCH_REL_REQ) + | S(TRXCON_EV_DCH_EST_REQ) + | S(TRXCON_EV_TX_ACCESS_BURST_REQ) + | S(TRXCON_EV_TX_ACCESS_BURST_CNF) + | S(TRXCON_EV_GPRS_UL_TBF_CFG_REQ) + | S(TRXCON_EV_GPRS_DL_TBF_CFG_REQ) + | S(TRXCON_EV_GPRS_UL_BLOCK_REQ) + | S(TRXCON_EV_RX_DATA_IND) + | S(TRXCON_EV_TX_DATA_CNF), + .onenter = &trxcon_st_packet_data_onenter, + .onleave = &trxcon_st_packet_data_onleave, + .action = &trxcon_st_packet_data_action, + }, +}; + +static const struct value_string trxcon_fsm_event_names[] = { + OSMO_VALUE_STRING(TRXCON_EV_PHYIF_FAILURE), + OSMO_VALUE_STRING(TRXCON_EV_L2IF_FAILURE), + OSMO_VALUE_STRING(TRXCON_EV_RESET_FULL_REQ), + OSMO_VALUE_STRING(TRXCON_EV_RESET_SCHED_REQ), + OSMO_VALUE_STRING(TRXCON_EV_FULL_POWER_SCAN_REQ), + OSMO_VALUE_STRING(TRXCON_EV_FULL_POWER_SCAN_RES), + OSMO_VALUE_STRING(TRXCON_EV_FBSB_SEARCH_REQ), + OSMO_VALUE_STRING(TRXCON_EV_FBSB_SEARCH_RES), + OSMO_VALUE_STRING(TRXCON_EV_SET_CCCH_MODE_REQ), + OSMO_VALUE_STRING(TRXCON_EV_SET_TCH_MODE_REQ), + OSMO_VALUE_STRING(TRXCON_EV_SET_PHY_CONFIG_REQ), + OSMO_VALUE_STRING(TRXCON_EV_TX_ACCESS_BURST_REQ), + OSMO_VALUE_STRING(TRXCON_EV_TX_ACCESS_BURST_CNF), + OSMO_VALUE_STRING(TRXCON_EV_UPDATE_SACCH_CACHE_REQ), + OSMO_VALUE_STRING(TRXCON_EV_DCH_EST_REQ), + OSMO_VALUE_STRING(TRXCON_EV_DCH_REL_REQ), + OSMO_VALUE_STRING(TRXCON_EV_TX_DATA_REQ), + OSMO_VALUE_STRING(TRXCON_EV_TX_DATA_CNF), + OSMO_VALUE_STRING(TRXCON_EV_RX_DATA_IND), + OSMO_VALUE_STRING(TRXCON_EV_CRYPTO_REQ), + OSMO_VALUE_STRING(TRXCON_EV_GPRS_UL_TBF_CFG_REQ), + OSMO_VALUE_STRING(TRXCON_EV_GPRS_DL_TBF_CFG_REQ), + OSMO_VALUE_STRING(TRXCON_EV_GPRS_UL_BLOCK_REQ), + { 0, NULL } +}; + +struct osmo_fsm trxcon_fsm_def = { + .name = "trxcon", + .states = trxcon_fsm_states, + .num_states = ARRAY_SIZE(trxcon_fsm_states), + .log_subsys = DLGLOBAL, + .event_names = trxcon_fsm_event_names, + .allstate_event_mask = S(TRXCON_EV_PHYIF_FAILURE) + | S(TRXCON_EV_L2IF_FAILURE) + | S(TRXCON_EV_RESET_FULL_REQ) + | S(TRXCON_EV_RESET_SCHED_REQ) + | S(TRXCON_EV_SET_PHY_CONFIG_REQ) + | S(TRXCON_EV_UPDATE_SACCH_CACHE_REQ), + .allstate_action = &trxcon_allstate_action, + .timer_cb = &trxcon_timer_cb, + .pre_term = &trxcon_fsm_pre_term_cb, +}; + +static __attribute__((constructor)) void on_dso_load(void) +{ + OSMO_ASSERT(osmo_fsm_register(&trxcon_fsm_def) == 0); +} diff --git a/src/host/trxcon/src/trxcon_inst.c b/src/host/trxcon/src/trxcon_inst.c new file mode 100644 index 00000000..b7ff5810 --- /dev/null +++ b/src/host/trxcon/src/trxcon_inst.c @@ -0,0 +1,107 @@ +/* + * OsmocomBB <-> SDR connection bridge + * + * (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de> + * + * 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. + * + */ + +#include <stdint.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/talloc.h> + +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> +#include <osmocom/bb/l1gprs.h> + +extern int g_logc_l1c; +extern int g_logc_l1d; + +void trxcon_set_log_cfg(const int *logc, unsigned int logc_num) +{ + int schc = DLGLOBAL; + int schd = DLGLOBAL; + + for (unsigned int i = 0; i < logc_num; i++) { + switch ((enum trxcon_log_cat)i) { + case TRXCON_LOGC_FSM: + trxcon_fsm_def.log_subsys = logc[i]; + break; + case TRXCON_LOGC_L1C: + g_logc_l1c = logc[i]; + break; + case TRXCON_LOGC_L1D: + g_logc_l1d = logc[i]; + break; + case TRXCON_LOGC_SCHC: + schc = logc[i]; + break; + case TRXCON_LOGC_SCHD: + schd = logc[i]; + break; + case TRXCON_LOGC_GPRS: + l1gprs_logging_init(logc[i]); + break; + } + } + + l1sched_logging_init(schc, schd); +} + +struct trxcon_inst *trxcon_inst_alloc(void *ctx, unsigned int id) +{ + struct trxcon_inst *trxcon; + struct osmo_fsm_inst *fi; + + fi = osmo_fsm_inst_alloc(&trxcon_fsm_def, ctx, NULL, LOGL_DEBUG, NULL); + OSMO_ASSERT(fi != NULL); + + trxcon = talloc_zero(fi, struct trxcon_inst); + OSMO_ASSERT(trxcon != NULL); + + fi->priv = trxcon; + trxcon->fi = fi; + + osmo_fsm_inst_update_id_f(fi, "%u", id); + trxcon->id = id; + + /* Logging context to be used by both l1ctl and l1sched modules */ + trxcon->log_prefix = talloc_asprintf(trxcon, "%s: ", osmo_fsm_inst_name(fi)); + + /* Init scheduler */ + const struct l1sched_cfg sched_cfg = { + .log_prefix = trxcon->log_prefix, + }; + + trxcon->sched = l1sched_alloc(trxcon, &sched_cfg, trxcon); + if (trxcon->sched == NULL) { + trxcon_inst_free(trxcon); + return NULL; + } + + trxcon->phy_quirks.fbsb_extend_fns = 0; + + return trxcon; +} + +void trxcon_inst_free(struct trxcon_inst *trxcon) +{ + if (trxcon == NULL || trxcon->fi == NULL) + return; + osmo_fsm_inst_term(trxcon->fi, OSMO_FSM_TERM_REQUEST, NULL); +} diff --git a/src/host/trxcon/src/trxcon_main.c b/src/host/trxcon/src/trxcon_main.c new file mode 100644 index 00000000..3901e336 --- /dev/null +++ b/src/host/trxcon/src/trxcon_main.c @@ -0,0 +1,423 @@ +/* + * OsmocomBB <-> SDR connection bridge + * + * (C) 2016-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * Contributions by sysmocom - s.f.m.c. GmbH + * + * 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. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <getopt.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> +#include <time.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/signal.h> +#include <osmocom/core/select.h> +#include <osmocom/core/application.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/gsmtap.h> + +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> +#include <osmocom/bb/trxcon/phyif.h> +#include <osmocom/bb/trxcon/trx_if.h> +#include <osmocom/bb/trxcon/logging.h> +#include <osmocom/bb/trxcon/l1ctl_server.h> + +#define COPYRIGHT \ + "Copyright (C) 2016-2022 by Vadim Yanitskiy <axilirator@gmail.com>\n" \ + "Contributions by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>\n" \ + "License GPLv2+: GNU GPL version 2 or later " \ + "<http://gnu.org/licenses/gpl.html>\n" \ + "This is free software: you are free to change and redistribute it.\n" \ + "There is NO WARRANTY, to the extent permitted by law.\n\n" + +static struct { + const char *debug_mask; + int daemonize; + int quit; + + /* L1CTL specific */ + unsigned int max_clients; + const char *bind_socket; + + /* TRX specific */ + const char *trx_bind_ip; + const char *trx_remote_ip; + uint16_t trx_base_port; + uint32_t trx_fn_advance; + + /* PHY quirk: FBSB timeout extension (in TDMA FNs) */ + unsigned int phyq_fbsb_extend_fns; + + /* GSMTAP specific */ + struct gsmtap_inst *gsmtap; + const char *gsmtap_ip; +} app_data = { + .max_clients = 1, /* only one L1CTL client by default */ + .bind_socket = "/tmp/osmocom_l2", + .trx_remote_ip = "127.0.0.1", + .trx_bind_ip = "0.0.0.0", + .trx_base_port = 6700, + .trx_fn_advance = 0, + .phyq_fbsb_extend_fns = 0, +}; + +static void *tall_trxcon_ctx = NULL; + +int trxcon_phyif_handle_burst_req(void *phyif, const struct trxcon_phyif_burst_req *br) +{ + return trx_if_handle_phyif_burst_req(phyif, br); +} + +int trxcon_phyif_handle_cmd(void *phyif, const struct trxcon_phyif_cmd *cmd) +{ + return trx_if_handle_phyif_cmd(phyif, cmd); +} + +void trxcon_phyif_close(void *phyif) +{ + trx_if_close(phyif); +} + +void trxcon_l1ctl_close(struct trxcon_inst *trxcon) +{ + /* Avoid use-after-free: both *fi and *trxcon are children of + * the L2IF (L1CTL connection), so we need to re-parent *fi + * to NULL before calling l1ctl_client_conn_close(). */ + talloc_steal(NULL, trxcon->fi); + l1ctl_client_conn_close(trxcon->l2if); +} + +int trxcon_l1ctl_send(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct l1ctl_client *l1c = trxcon->l2if; + + return l1ctl_client_send(l1c, msg); +} + +static int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg) +{ + struct trxcon_inst *trxcon = l1c->priv; + + return trxcon_l1ctl_receive(trxcon, msg); +} + +static void l1ctl_conn_accept_cb(struct l1ctl_client *l1c) +{ + struct trxcon_inst *trxcon; + + trxcon = trxcon_inst_alloc(l1c, l1c->id); + if (trxcon == NULL) { + l1ctl_client_conn_close(l1c); + return; + } + + l1c->log_prefix = talloc_strdup(l1c, trxcon->log_prefix); + l1c->priv = trxcon; + trxcon->l2if = l1c; + + const struct trx_if_params trxcon_phyif_params = { + .local_host = app_data.trx_bind_ip, + .remote_host = app_data.trx_remote_ip, + .base_port = app_data.trx_base_port, + .fn_advance = app_data.trx_fn_advance, + .instance = trxcon->id, + + .parent_fi = trxcon->fi, + .parent_term_event = TRXCON_EV_PHYIF_FAILURE, + .priv = trxcon, + }; + + /* Init transceiver interface */ + trxcon->phyif = trx_if_open(&trxcon_phyif_params); + if (trxcon->phyif == NULL) { + /* TRXCON_EV_PHYIF_FAILURE triggers l1ctl_client_conn_close() */ + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_PHYIF_FAILURE, NULL); + return; + } + + trxcon->gsmtap = app_data.gsmtap; + trxcon->phy_quirks.fbsb_extend_fns = app_data.phyq_fbsb_extend_fns; +} + +static void l1ctl_conn_close_cb(struct l1ctl_client *l1c) +{ + struct trxcon_inst *trxcon = l1c->priv; + + if (trxcon == NULL || trxcon->fi == NULL) + return; + + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_L2IF_FAILURE, NULL); +} + +static void print_usage(const char *app) +{ + printf("Usage: %s\n", app); +} + +static void print_help(void) +{ + printf(" Some help...\n"); + printf(" -h --help this text\n"); + printf(" -d --debug Change debug flags (e.g. DL1C:DSCH)\n"); + printf(" -b --trx-bind TRX bind IP address (default 0.0.0.0)\n"); + printf(" -i --trx-remote TRX remote IP address (default 127.0.0.1)\n"); + printf(" -p --trx-port Base port of TRX instance (default 6700)\n"); + printf(" -f --trx-advance Uplink burst scheduling advance (default 0)\n"); + printf(" -F --fbsb-extend FBSB timeout extension (in TDMA FNs, default 0)\n"); + printf(" -s --socket Listening socket for layer23 (default /tmp/osmocom_l2)\n"); + printf(" -g --gsmtap-ip The destination IP used for GSMTAP (disabled by default)\n"); + printf(" -C --max-clients Maximum number of L1CTL connections (default 1)\n"); + printf(" -D --daemonize Run as daemon\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + char *endptr = NULL; + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"debug", 1, 0, 'd'}, + {"socket", 1, 0, 's'}, + {"trx-bind", 1, 0, 'b'}, + /* NOTE: 'trx-ip' is now an alias for 'trx-remote' + * due to backward compatibility reasons! */ + {"trx-ip", 1, 0, 'i'}, + {"trx-remote", 1, 0, 'i'}, + {"trx-port", 1, 0, 'p'}, + {"trx-advance", 1, 0, 'f'}, + {"fbsb-extend", 1, 0, 'F'}, + {"gsmtap-ip", 1, 0, 'g'}, + {"max-clients", 1, 0, 'C'}, + {"daemonize", 0, 0, 'D'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "d:b:i:p:f:F:s:g:C:Dh", + long_options, &option_index); + if (c == -1) + break; + + errno = 0; + + switch (c) { + case 'h': + print_usage(argv[0]); + print_help(); + exit(0); + break; + case 'd': + app_data.debug_mask = optarg; + break; + case 'b': + app_data.trx_bind_ip = optarg; + break; + case 'i': + app_data.trx_remote_ip = optarg; + break; + case 'p': + app_data.trx_base_port = strtoul(optarg, &endptr, 10); + if (errno || *endptr != '\0') { + fprintf(stderr, "Failed to parse -p/--trx-port=%s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'f': + app_data.trx_fn_advance = strtoul(optarg, &endptr, 10); + if (errno || *endptr != '\0') { + fprintf(stderr, "Failed to parse -f/--trx-advance=%s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'F': + app_data.phyq_fbsb_extend_fns = strtoul(optarg, &endptr, 10); + if (errno || *endptr != '\0') { + fprintf(stderr, "Failed to parse -F/--fbsb-extend=%s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 's': + app_data.bind_socket = optarg; + break; + case 'g': + app_data.gsmtap_ip = optarg; + break; + case 'C': + app_data.max_clients = strtoul(optarg, &endptr, 10); + if (errno || *endptr != '\0') { + fprintf(stderr, "Failed to parse -C/--max-clients=%s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'D': + app_data.daemonize = 1; + break; + default: + break; + } + } +} + +static void signal_handler(int signum) +{ + fprintf(stderr, "signal %u received\n", signum); + + switch (signum) { + case SIGINT: + case SIGTERM: + app_data.quit++; + break; + case SIGABRT: + /* in case of abort, we want to obtain a talloc report and + * then run default SIGABRT handler, who will generate coredump + * and abort the process. abort() should do this for us after we + * return, but program wouldn't exit if an external SIGABRT is + * received. + */ + talloc_report_full(tall_trxcon_ctx, stderr); + signal(SIGABRT, SIG_DFL); + raise(SIGABRT); + break; + case SIGUSR1: + case SIGUSR2: + talloc_report_full(tall_trxcon_ctx, stderr); + break; + default: + break; + } +} + +int main(int argc, char **argv) +{ + struct l1ctl_server_cfg server_cfg; + struct l1ctl_server *server = NULL; + int rc = 0; + + printf("%s", COPYRIGHT); + handle_options(argc, argv); + + /* Track the use of talloc NULL memory contexts */ + talloc_enable_null_tracking(); + + /* Init talloc memory management system */ + tall_trxcon_ctx = talloc_init("trxcon context"); + msgb_talloc_ctx_init(tall_trxcon_ctx, 0); + + /* Setup signal handlers */ + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + osmo_init_ignore_signals(); + + /* Init logging system */ + trxcon_logging_init(tall_trxcon_ctx, app_data.debug_mask); + + /* Configure pretty logging */ + log_set_print_extended_timestamp(osmo_stderr_target, 1); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + log_set_print_level(osmo_stderr_target, 1); + + log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME); + log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END); + + osmo_fsm_log_timeouts(true); + + /* Optional GSMTAP */ + if (app_data.gsmtap_ip != NULL) { + struct log_target *lt; + + app_data.gsmtap = gsmtap_source_init(app_data.gsmtap_ip, GSMTAP_UDP_PORT, 1); + if (!app_data.gsmtap) { + LOGP(DAPP, LOGL_ERROR, "Failed to init GSMTAP Um logging\n"); + goto exit; + } + + lt = log_target_create_gsmtap(app_data.gsmtap_ip, GSMTAP_UDP_PORT, + "trxcon", false, false); + if (lt == NULL) { + LOGP(DAPP, LOGL_ERROR, "Failed to init GSMTAP logging target\n"); + goto exit; + } else { + log_add_target(lt); + } + + /* Suppress ICMP "destination unreachable" errors */ + gsmtap_source_add_sink(app_data.gsmtap); + } + + /* Start the L1CTL server */ + server_cfg = (struct l1ctl_server_cfg) { + .sock_path = app_data.bind_socket, + .num_clients_max = app_data.max_clients, + .conn_read_cb = &l1ctl_rx_cb, + .conn_accept_cb = &l1ctl_conn_accept_cb, + .conn_close_cb = &l1ctl_conn_close_cb, + }; + + server = l1ctl_server_alloc(tall_trxcon_ctx, &server_cfg); + if (server == NULL) { + rc = EXIT_FAILURE; + goto exit; + } + + LOGP(DAPP, LOGL_NOTICE, "Init complete\n"); + + if (app_data.daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + goto exit; + } + } + + /* Initialize pseudo-random generator */ + srand(time(NULL)); + + while (!app_data.quit) + osmo_select_main(0); + +exit: + if (server != NULL) + l1ctl_server_free(server); + + /* Deinitialize logging */ + log_fini(); + + /** + * Print report for the root talloc context in order + * to be able to find and fix potential memory leaks. + */ + talloc_report_full(tall_trxcon_ctx, stderr); + talloc_free(tall_trxcon_ctx); + + /* Make both Valgrind and ASAN happy */ + talloc_report_full(NULL, stderr); + talloc_disable_null_tracking(); + + return rc; +} diff --git a/src/host/trxcon/src/trxcon_shim.c b/src/host/trxcon/src/trxcon_shim.c new file mode 100644 index 00000000..ed2d402e --- /dev/null +++ b/src/host/trxcon/src/trxcon_shim.c @@ -0,0 +1,262 @@ +/* + * OsmocomBB <-> SDR connection bridge + * + * (C) 2022-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de> + * + * 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. + * + */ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/gsm/rsl.h> + +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> +#include <osmocom/bb/trxcon/phyif.h> +#include <osmocom/bb/l1sched/l1sched.h> + +static void trxcon_gsmtap_send(struct trxcon_inst *trxcon, + const struct l1sched_prim_chdr *chdr, + const uint8_t *data, size_t data_len, + int8_t signal_dbm, uint8_t snr, bool uplink) +{ + uint16_t band_arfcn = trxcon->l1p.band_arfcn; + uint8_t chan_type, ss, tn; + + if (uplink) + band_arfcn |= ARFCN_UPLINK; + if (rsl_dec_chan_nr(chdr->chan_nr, &chan_type, &ss, &tn) != 0) + return; + chan_type = chantype_rsl2gsmtap2(chan_type, chdr->link_id, chdr->traffic); + + gsmtap_send(trxcon->gsmtap, band_arfcn, tn, chan_type, ss, + chdr->frame_nr, signal_dbm, snr, + data, data_len); +} + +/* External L1 API for the scheduler */ +int l1sched_handle_burst_req(struct l1sched_state *sched, + const struct l1sched_burst_req *br) +{ + struct trxcon_inst *trxcon = sched->priv; + const struct trxcon_phyif_burst_req phybr = { + .fn = br->fn, + .tn = br->tn, + .pwr = br->pwr, + .burst = &br->burst[0], + .burst_len = br->burst_len, + }; + + return trxcon_phyif_handle_burst_req(trxcon->phyif, &phybr); +} + +/* External L2 API for the scheduler */ +static int handle_prim_data_ind(struct trxcon_inst *trxcon, struct msgb *msg) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + struct trxcon_param_rx_data_ind ind = { + .traffic = prim->data_ind.chdr.traffic, + .chan_nr = prim->data_ind.chdr.chan_nr, + .link_id = prim->data_ind.chdr.link_id, + .band_arfcn = trxcon->l1p.band_arfcn, + .frame_nr = prim->data_ind.chdr.frame_nr, + .toa256 = prim->data_ind.toa256, + .rssi = prim->data_ind.rssi, + .n_errors = prim->data_ind.n_errors, + .n_bits_total = prim->data_ind.n_bits_total, + .data_len = msgb_l2len(msg), + .data = msgb_l2(msg), + }; + + if (trxcon->gsmtap != NULL && ind.data_len > 0) { + trxcon_gsmtap_send(trxcon, &prim->data_ind.chdr, + ind.data, ind.data_len, + ind.rssi, 0, false); + } + + return osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_RX_DATA_IND, &ind); +} + +static int handle_prim_data_cnf(struct trxcon_inst *trxcon, struct msgb *msg) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + struct trxcon_param_tx_data_cnf cnf = { + .traffic = prim->data_cnf.traffic, + .chan_nr = prim->data_cnf.chan_nr, + .link_id = prim->data_cnf.link_id, + .band_arfcn = trxcon->l1p.band_arfcn, + .frame_nr = prim->data_cnf.frame_nr, + .data_len = msgb_l2len(msg), + .data = msgb_l2(msg), + }; + + if (trxcon->gsmtap != NULL) { + trxcon_gsmtap_send(trxcon, &prim->data_cnf, + msgb_l2(msg), msgb_l2len(msg), + 0, 0, true); + } + + return osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_TX_DATA_CNF, &cnf); +} + +static int handle_prim_rach_cnf(struct trxcon_inst *trxcon, struct msgb *msg) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + struct trxcon_param_tx_access_burst_cnf cnf = { + .band_arfcn = trxcon->l1p.band_arfcn, + .frame_nr = prim->rach_cnf.chdr.frame_nr, + }; + + if (trxcon->gsmtap != NULL) { + if (prim->rach_cnf.is_11bit) { + msgb_put_u8(msg, (uint8_t)(prim->rach_cnf.ra >> 3)); + msgb_put_u8(msg, (uint8_t)(prim->rach_cnf.ra & 0x07)); + } else { + msgb_put_u8(msg, (uint8_t)(prim->rach_cnf.ra)); + } + + trxcon_gsmtap_send(trxcon, &prim->rach_cnf.chdr, + msgb_l2(msg), msgb_l2len(msg), + 0, 0, true); + } + + return osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_TX_ACCESS_BURST_CNF, &cnf); +} + +int l1sched_prim_to_user(struct l1sched_state *sched, struct msgb *msg) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + struct trxcon_inst *trxcon = sched->priv; + int rc = 0; + + LOGPFSML(trxcon->fi, LOGL_DEBUG, + "%s(): Rx " L1SCHED_PRIM_STR_FMT "\n", + __func__, L1SCHED_PRIM_STR_ARGS(prim)); + + switch (OSMO_PRIM_HDR(&prim->oph)) { + case OSMO_PRIM(L1SCHED_PRIM_T_DATA, PRIM_OP_INDICATION): + rc = handle_prim_data_ind(trxcon, msg); + break; + case OSMO_PRIM(L1SCHED_PRIM_T_DATA, PRIM_OP_CONFIRM): + rc = handle_prim_data_cnf(trxcon, msg); + break; + case OSMO_PRIM(L1SCHED_PRIM_T_RACH, PRIM_OP_CONFIRM): + rc = handle_prim_rach_cnf(trxcon, msg); + break; + case OSMO_PRIM(L1SCHED_PRIM_T_SCH, PRIM_OP_INDICATION): + if (trxcon->fi->state == TRXCON_ST_FBSB_SEARCH) + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_FBSB_SEARCH_RES, NULL); + break; + case OSMO_PRIM(L1SCHED_PRIM_T_PCHAN_COMB, PRIM_OP_INDICATION): + { + struct trxcon_param_set_phy_config_req req = { + .type = TRXCON_PHY_CFGT_PCHAN_COMB, + .pchan_comb = { + .tn = prim->pchan_comb_ind.tn, + .pchan = prim->pchan_comb_ind.pchan, + }, + }; + + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_SET_PHY_CONFIG_REQ, &req); + break; + } + default: + LOGPFSML(trxcon->fi, LOGL_ERROR, + "%s(): Unhandled primitive " L1SCHED_PRIM_STR_FMT "\n", + __func__, L1SCHED_PRIM_STR_ARGS(prim)); + rc = -ENOTSUP; + } + + msgb_free(msg); + return rc; +} + +/* External L1 API for the PHYIF */ +int trxcon_phyif_handle_rts_ind(void *priv, const struct trxcon_phyif_rts_ind *rts) +{ + struct trxcon_inst *trxcon = priv; + struct l1sched_burst_req br = { + .fn = rts->fn, + .tn = rts->tn, + .burst_len = 0, /* NOPE.ind */ + }; + + l1sched_pull_burst(trxcon->sched, &br); + return l1sched_handle_burst_req(trxcon->sched, &br); +} + +int trxcon_phyif_handle_rtr_ind(void *priv, const struct trxcon_phyif_rtr_ind *ind, + struct trxcon_phyif_rtr_rsp *rsp) +{ + struct trxcon_inst *trxcon = priv; + struct l1sched_probe probe = { + .fn = ind->fn, + .tn = ind->tn, + }; + + l1sched_handle_rx_probe(trxcon->sched, &probe); + + memset(rsp, 0x00, sizeof(*rsp)); + + if (probe.flags & L1SCHED_PROBE_F_ACTIVE) + rsp->flags |= TRXCON_PHYIF_RTR_F_ACTIVE; + + return 0; +} + +int trxcon_phyif_handle_burst_ind(void *priv, const struct trxcon_phyif_burst_ind *phybi) +{ + struct trxcon_inst *trxcon = priv; + struct l1sched_burst_ind bi = { + .fn = phybi->fn, + .tn = phybi->tn, + .toa256 = phybi->toa256, + .rssi = phybi->rssi, + /* .burst[] is populated below */ + .burst_len = phybi->burst_len, + }; + + OSMO_ASSERT(phybi->burst_len <= sizeof(bi.burst)); + memcpy(&bi.burst[0], phybi->burst, phybi->burst_len); + + /* Poke scheduler */ + return l1sched_handle_rx_burst(trxcon->sched, &bi); +} + +int trxcon_phyif_handle_rsp(void *priv, const struct trxcon_phyif_rsp *rsp) +{ + struct trxcon_inst *trxcon = priv; + + switch (rsp->type) { + case TRXCON_PHYIF_CMDT_MEASURE: + { + const struct trxcon_phyif_rspp_measure *meas = &rsp->param.measure; + struct trxcon_param_full_power_scan_res res = { + .band_arfcn = meas->band_arfcn, + .dbm = meas->dbm, + }; + + return osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_FULL_POWER_SCAN_RES, &res); + } + default: + LOGPFSML(trxcon->fi, LOGL_ERROR, + "Unhandled PHYIF response (type 0x%02x)\n", rsp->type); + return -ENODEV; + } +} diff --git a/src/host/trxcon/trx_if.h b/src/host/trxcon/trx_if.h deleted file mode 100644 index a44600d9..00000000 --- a/src/host/trxcon/trx_if.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include <osmocom/core/linuxlist.h> -#include <osmocom/core/select.h> -#include <osmocom/core/timer.h> -#include <osmocom/core/fsm.h> - -#include "scheduler.h" -#include "sched_trx.h" - -/* Forward declaration to avoid mutual include */ -struct l1ctl_link; - -enum trx_fsm_states { - TRX_STATE_OFFLINE = 0, - TRX_STATE_IDLE, - TRX_STATE_ACTIVE, - TRX_STATE_RSP_WAIT, -}; - -struct trx_instance { - struct osmo_fd trx_ofd_ctrl; - struct osmo_fd trx_ofd_data; - - struct osmo_timer_list trx_ctrl_timer; - struct llist_head trx_ctrl_list; - struct osmo_fsm_inst *fsm; - - /* HACK: we need proper state machines */ - uint32_t prev_state; - bool powered_up; - - /* GSM L1 specific */ - uint16_t pm_band_arfcn_start; - uint16_t pm_band_arfcn_stop; - uint16_t band_arfcn; - uint8_t tx_power; - uint8_t bsic; - uint8_t tsc; - int8_t ta; - - /* Scheduler stuff */ - struct trx_sched sched; - struct trx_ts *ts_list[TRX_TS_COUNT]; - - /* Bind L1CTL link */ - struct l1ctl_link *l1l; -}; - -struct trx_ctrl_msg { - struct llist_head list; - char cmd[128]; - int retry_cnt; - int critical; - int cmd_len; -}; - -struct trx_instance *trx_if_open(void *tall_ctx, - const char *local_host, const char *remote_host, uint16_t port); -void trx_if_flush_ctrl(struct trx_instance *trx); -void trx_if_close(struct trx_instance *trx); - -int trx_if_cmd_poweron(struct trx_instance *trx); -int trx_if_cmd_poweroff(struct trx_instance *trx); -int trx_if_cmd_echo(struct trx_instance *trx); - -int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta); - -int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn); -int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn); - -int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type); -int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn, - uint8_t maio, uint16_t *ma, size_t ma_len); - -int trx_if_cmd_measure(struct trx_instance *trx, - uint16_t band_arfcn_start, uint16_t band_arfcn_stop); - -int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn, - uint8_t pwr, const ubit_t *bits); diff --git a/src/host/trxcon/trxcon.c b/src/host/trxcon/trxcon.c deleted file mode 100644 index 8e371df1..00000000 --- a/src/host/trxcon/trxcon.c +++ /dev/null @@ -1,343 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * - * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.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 <stdio.h> -#include <stdlib.h> -#include <stdint.h> -#include <string.h> -#include <getopt.h> -#include <unistd.h> -#include <signal.h> -#include <time.h> - -#include <arpa/inet.h> - -#include <osmocom/core/fsm.h> -#include <osmocom/core/msgb.h> -#include <osmocom/core/talloc.h> -#include <osmocom/core/signal.h> -#include <osmocom/core/select.h> -#include <osmocom/core/application.h> - -#include <osmocom/gsm/gsm_utils.h> - -#include "trxcon.h" -#include "trx_if.h" -#include "logging.h" -#include "l1ctl.h" -#include "l1ctl_link.h" -#include "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" - -#define COPYRIGHT \ - "Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>\n" \ - "License GPLv2+: GNU GPL version 2 or later " \ - "<http://gnu.org/licenses/gpl.html>\n" \ - "This is free software: you are free to change and redistribute it.\n" \ - "There is NO WARRANTY, to the extent permitted by law.\n\n" - -static struct { - const char *debug_mask; - int daemonize; - int quit; - - /* L1CTL specific */ - struct l1ctl_link *l1l; - const char *bind_socket; - - /* TRX specific */ - struct trx_instance *trx; - const char *trx_bind_ip; - const char *trx_remote_ip; - uint16_t trx_base_port; - uint32_t trx_fn_advance; -} app_data; - -static void *tall_trxcon_ctx = NULL; -struct osmo_fsm_inst *trxcon_fsm; - -static void trxcon_fsm_idle_action(struct osmo_fsm_inst *fi, - uint32_t event, void *data) -{ - if (event == L1CTL_EVENT_CONNECT) - osmo_fsm_inst_state_chg(trxcon_fsm, TRXCON_STATE_MANAGED, 0, 0); -} - -static void trxcon_fsm_managed_action(struct osmo_fsm_inst *fi, - uint32_t event, void *data) -{ - switch (event) { - case L1CTL_EVENT_DISCONNECT: - osmo_fsm_inst_state_chg(trxcon_fsm, TRXCON_STATE_IDLE, 0, 0); - - if (app_data.trx->fsm->state != TRX_STATE_OFFLINE) { - /* Reset scheduler and clock counter */ - sched_trx_reset(app_data.trx, true); - - /* TODO: implement trx_if_reset() */ - trx_if_cmd_poweroff(app_data.trx); - trx_if_cmd_echo(app_data.trx); - } - break; - case TRX_EVENT_RSP_ERROR: - case TRX_EVENT_OFFLINE: - /* TODO: notify L2 & L3 about that */ - break; - default: - LOGPFSML(fi, LOGL_ERROR, "Unhandled event %u\n", event); - } -} - -static struct osmo_fsm_state trxcon_fsm_states[] = { - [TRXCON_STATE_IDLE] = { - .in_event_mask = GEN_MASK(L1CTL_EVENT_CONNECT), - .out_state_mask = GEN_MASK(TRXCON_STATE_MANAGED), - .name = "IDLE", - .action = trxcon_fsm_idle_action, - }, - [TRXCON_STATE_MANAGED] = { - .in_event_mask = ( - GEN_MASK(L1CTL_EVENT_DISCONNECT) | - GEN_MASK(TRX_EVENT_RSP_ERROR) | - GEN_MASK(TRX_EVENT_OFFLINE)), - .out_state_mask = GEN_MASK(TRXCON_STATE_IDLE), - .name = "MANAGED", - .action = trxcon_fsm_managed_action, - }, -}; - -static const struct value_string app_evt_names[] = { - OSMO_VALUE_STRING(L1CTL_EVENT_CONNECT), - OSMO_VALUE_STRING(L1CTL_EVENT_DISCONNECT), - OSMO_VALUE_STRING(TRX_EVENT_OFFLINE), - OSMO_VALUE_STRING(TRX_EVENT_RSP_ERROR), - { 0, NULL } -}; - -static struct osmo_fsm trxcon_fsm_def = { - .name = "trxcon_app_fsm", - .states = trxcon_fsm_states, - .num_states = ARRAY_SIZE(trxcon_fsm_states), - .log_subsys = DAPP, - .event_names = app_evt_names, -}; - -static void print_usage(const char *app) -{ - printf("Usage: %s\n", app); -} - -static void print_help(void) -{ - printf(" Some help...\n"); - printf(" -h --help this text\n"); - printf(" -d --debug Change debug flags. Default: %s\n", DEBUG_DEFAULT); - printf(" -b --trx-bind TRX bind IP address (default 0.0.0.0)\n"); - printf(" -i --trx-remote TRX remote IP address (default 127.0.0.1)\n"); - printf(" -p --trx-port Base port of TRX instance (default 6700)\n"); - printf(" -f --trx-advance Scheduler clock advance (default 20)\n"); - printf(" -s --socket Listening socket for layer23 (default /tmp/osmocom_l2)\n"); - printf(" -D --daemonize Run as daemon\n"); -} - -static void handle_options(int argc, char **argv) -{ - while (1) { - int option_index = 0, c; - static struct option long_options[] = { - {"help", 0, 0, 'h'}, - {"debug", 1, 0, 'd'}, - {"socket", 1, 0, 's'}, - {"trx-bind", 1, 0, 'b'}, - /* NOTE: 'trx-ip' is now an alias for 'trx-remote' - * due to backward compatibility reasons! */ - {"trx-ip", 1, 0, 'i'}, - {"trx-remote", 1, 0, 'i'}, - {"trx-port", 1, 0, 'p'}, - {"trx-advance", 1, 0, 'f'}, - {"daemonize", 0, 0, 'D'}, - {0, 0, 0, 0} - }; - - c = getopt_long(argc, argv, "d:b:i:p:f:s:Dh", - long_options, &option_index); - if (c == -1) - break; - - switch (c) { - case 'h': - print_usage(argv[0]); - print_help(); - exit(0); - break; - case 'd': - app_data.debug_mask = optarg; - break; - case 'b': - app_data.trx_bind_ip = optarg; - break; - case 'i': - app_data.trx_remote_ip = optarg; - break; - case 'p': - app_data.trx_base_port = atoi(optarg); - break; - case 'f': - app_data.trx_fn_advance = atoi(optarg); - break; - case 's': - app_data.bind_socket = optarg; - break; - case 'D': - app_data.daemonize = 1; - break; - default: - break; - } - } -} - -static void init_defaults(void) -{ - app_data.bind_socket = "/tmp/osmocom_l2"; - app_data.trx_remote_ip = "127.0.0.1"; - app_data.trx_bind_ip = "0.0.0.0"; - app_data.trx_base_port = 6700; - app_data.trx_fn_advance = 20; - - app_data.debug_mask = NULL; - app_data.daemonize = 0; - app_data.quit = 0; -} - -static void signal_handler(int signal) -{ - fprintf(stderr, "signal %u received\n", signal); - - switch (signal) { - case SIGINT: - app_data.quit++; - break; - case SIGABRT: - case SIGUSR1: - case SIGUSR2: - talloc_report_full(tall_trxcon_ctx, stderr); - break; - default: - break; - } -} - -int main(int argc, char **argv) -{ - int rc = 0; - - printf("%s", COPYRIGHT); - init_defaults(); - handle_options(argc, argv); - - /* Track the use of talloc NULL memory contexts */ - talloc_enable_null_tracking(); - - /* Init talloc memory management system */ - tall_trxcon_ctx = talloc_init("trxcon context"); - msgb_talloc_ctx_init(tall_trxcon_ctx, 0); - - /* Setup signal handlers */ - signal(SIGINT, &signal_handler); - signal(SIGUSR1, &signal_handler); - signal(SIGUSR2, &signal_handler); - osmo_init_ignore_signals(); - - /* Init logging system */ - trx_log_init(tall_trxcon_ctx, app_data.debug_mask); - - /* Allocate the application state machine */ - osmo_fsm_register(&trxcon_fsm_def); - trxcon_fsm = osmo_fsm_inst_alloc(&trxcon_fsm_def, tall_trxcon_ctx, - NULL, LOGL_DEBUG, "main"); - - /* Init L1CTL server */ - app_data.l1l = l1ctl_link_init(tall_trxcon_ctx, - app_data.bind_socket); - if (app_data.l1l == NULL) - goto exit; - - /* Init transceiver interface */ - app_data.trx = trx_if_open(tall_trxcon_ctx, - app_data.trx_bind_ip, app_data.trx_remote_ip, - app_data.trx_base_port); - if (!app_data.trx) - goto exit; - - /* Bind L1CTL with TRX and vice versa */ - app_data.l1l->trx = app_data.trx; - app_data.trx->l1l = app_data.l1l; - - /* Init scheduler */ - rc = sched_trx_init(app_data.trx, app_data.trx_fn_advance); - if (rc) - goto exit; - - LOGP(DAPP, LOGL_NOTICE, "Init complete\n"); - - if (app_data.daemonize) { - rc = osmo_daemonize(); - if (rc < 0) { - perror("Error during daemonize"); - goto exit; - } - } - - /* Initialize pseudo-random generator */ - srand(time(NULL)); - - while (!app_data.quit) - osmo_select_main(0); - -exit: - /* Close active connections */ - l1ctl_link_shutdown(app_data.l1l); - sched_trx_shutdown(app_data.trx); - trx_if_close(app_data.trx); - - /* Shutdown main state machine */ - osmo_fsm_inst_free(trxcon_fsm); - - /* Deinitialize logging */ - log_fini(); - - /** - * Print report for the root talloc context in order - * to be able to find and fix potential memory leaks. - */ - talloc_report_full(tall_trxcon_ctx, stderr); - talloc_free(tall_trxcon_ctx); - - /* Make both Valgrind and ASAN happy */ - talloc_report_full(NULL, stderr); - talloc_disable_null_tracking(); - - return rc; -} diff --git a/src/host/trxcon/trxcon.h b/src/host/trxcon/trxcon.h deleted file mode 100644 index f66a6285..00000000 --- a/src/host/trxcon/trxcon.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#define GEN_MASK(state) (0x01 << state) - -extern struct osmo_fsm_inst *trxcon_fsm; - -enum trxcon_fsm_states { - TRXCON_STATE_IDLE = 0, - TRXCON_STATE_MANAGED, -}; - -enum trxcon_fsm_events { - /* L1CTL specific events */ - L1CTL_EVENT_CONNECT, - L1CTL_EVENT_DISCONNECT, - - /* TRX specific events */ - TRX_EVENT_RSP_ERROR, - TRX_EVENT_OFFLINE, -}; diff --git a/src/host/virt_phy/configure.ac b/src/host/virt_phy/configure.ac index c8012c99..19e4bc7a 100644 --- a/src/host/virt_phy/configure.ac +++ b/src/host/virt_phy/configure.ac @@ -3,6 +3,8 @@ AC_INIT([virtphy], 0.0.0) AM_CONFIG_HEADER([config.h]) AM_INIT_AUTOMAKE([foreign dist-bzip2 subdir-objects]) +CFLAGS="$CFLAGS -std=gnu11" + dnl kernel style compile messages m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) @@ -12,6 +14,8 @@ AC_PROG_CC AC_PROG_INSTALL dnl checks for libraries +dnl TODO: insert libosmocore version with GSMTAP_CHANNEL_VOICE: PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.4.0) +dnl (at time of writing not released yet) PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore) PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm) @@ -20,9 +24,45 @@ AC_HEADER_STDC dnl Checks for typedefs, structures and compiler characteristics +AC_ARG_ENABLE(sanitize, + [AS_HELP_STRING( + [--enable-sanitize], + [Compile with address sanitizer enabled], + )], [sanitize=$enableval], [sanitize="no"]) +if test x"$sanitize" = x"yes" +then + CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined" + CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined" +fi + +AC_ARG_ENABLE(werror, + [AS_HELP_STRING( + [--enable-werror], + [Turn all compiler warnings into errors, with exceptions: + a) deprecation (allow upstream to mark deprecation without breaking builds); + b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds) + ] + )], + [werror=$enableval], [werror="no"]) +if test x"$werror" = x"yes" +then + WERROR_FLAGS="-Werror" + WERROR_FLAGS+=" -Werror=implicit-int -Werror=int-conversion -Werror=old-style-definition" + WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations" + WERROR_FLAGS+=" -Wno-error=cpp" # "#warning" + CFLAGS="$CFLAGS $WERROR_FLAGS" + CPPFLAGS="$CPPFLAGS $WERROR_FLAGS" +fi + +AC_MSG_RESULT([CFLAGS="$CFLAGS"]) +AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"]) + AC_CONFIG_FILES([ Makefile include/Makefile + include/osmocom/Makefile + include/osmocom/bb/Makefile + include/osmocom/bb/virtphy/Makefile src/Makefile ]) AC_OUTPUT diff --git a/src/host/virt_phy/include/Makefile.am b/src/host/virt_phy/include/Makefile.am index 6048a4b3..9d963a02 100644 --- a/src/host/virt_phy/include/Makefile.am +++ b/src/host/virt_phy/include/Makefile.am @@ -1,10 +1,3 @@ -noinst_HEADERS = \ - virtphy/logging.h \ - virtphy/osmo_mcast_sock.h \ - virtphy/l1ctl_sock.h \ - virtphy/virtual_um.h \ - virtphy/gsmtapl1_if.h \ - virtphy/virt_l1_sched.h \ - virtphy/common_util.h \ - virtphy/l1ctl_sap.h \ - virtphy/virt_l1_model.h +SUBDIRS = \ + osmocom \ + $(NULL) diff --git a/src/host/virt_phy/include/osmocom/Makefile.am b/src/host/virt_phy/include/osmocom/Makefile.am new file mode 100644 index 00000000..83c6385c --- /dev/null +++ b/src/host/virt_phy/include/osmocom/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + bb \ + $(NULL) diff --git a/src/host/virt_phy/include/osmocom/bb/Makefile.am b/src/host/virt_phy/include/osmocom/bb/Makefile.am new file mode 100644 index 00000000..9a4b939a --- /dev/null +++ b/src/host/virt_phy/include/osmocom/bb/Makefile.am @@ -0,0 +1,8 @@ +SUBDIRS = \ + virtphy \ + $(NULL) + +noinst_HEADERS = \ + l1ctl_proto.h \ + l1gprs.h \ + $(NULL) diff --git a/src/host/virt_phy/include/osmocom/bb/l1ctl_proto.h b/src/host/virt_phy/include/osmocom/bb/l1ctl_proto.h new file mode 120000 index 00000000..ee19b80e --- /dev/null +++ b/src/host/virt_phy/include/osmocom/bb/l1ctl_proto.h @@ -0,0 +1 @@ +../../../../../../include/l1ctl_proto.h
\ No newline at end of file diff --git a/src/host/virt_phy/include/osmocom/bb/l1gprs.h b/src/host/virt_phy/include/osmocom/bb/l1gprs.h new file mode 120000 index 00000000..3bf85176 --- /dev/null +++ b/src/host/virt_phy/include/osmocom/bb/l1gprs.h @@ -0,0 +1 @@ +../../../../../../include/l1gprs.h
\ No newline at end of file diff --git a/src/host/virt_phy/include/osmocom/bb/virtphy/Makefile.am b/src/host/virt_phy/include/osmocom/bb/virtphy/Makefile.am new file mode 100644 index 00000000..75846096 --- /dev/null +++ b/src/host/virt_phy/include/osmocom/bb/virtphy/Makefile.am @@ -0,0 +1,11 @@ +noinst_HEADERS = \ + logging.h \ + osmo_mcast_sock.h \ + l1ctl_sock.h \ + virtual_um.h \ + gsmtapl1_if.h \ + virt_l1_sched.h \ + common_util.h \ + l1ctl_sap.h \ + virt_l1_model.h \ + $(NULL) diff --git a/src/host/virt_phy/include/virtphy/common_util.h b/src/host/virt_phy/include/osmocom/bb/virtphy/common_util.h index 2585d069..2585d069 100644 --- a/src/host/virt_phy/include/virtphy/common_util.h +++ b/src/host/virt_phy/include/osmocom/bb/virtphy/common_util.h diff --git a/src/host/virt_phy/include/virtphy/gsmtapl1_if.h b/src/host/virt_phy/include/osmocom/bb/virtphy/gsmtapl1_if.h index 8dab6b28..c9e92525 100644 --- a/src/host/virt_phy/include/virtphy/gsmtapl1_if.h +++ b/src/host/virt_phy/include/osmocom/bb/virtphy/gsmtapl1_if.h @@ -2,9 +2,10 @@ #include <osmocom/core/msgb.h> #include <osmocom/core/gsmtap.h> -#include <virtphy/virtual_um.h> -#include <virtphy/l1ctl_sock.h> -#include <virtphy/virt_l1_model.h> + +#include <osmocom/bb/virtphy/virtual_um.h> +#include <osmocom/bb/virtphy/l1ctl_sock.h> +#include <osmocom/bb/virtphy/virt_l1_model.h> void gsmtapl1_init(struct l1_model_ms *model); void gsmtapl1_rx_from_virt_um_inst_cb(struct virt_um_inst *vui, diff --git a/src/host/virt_phy/include/osmocom/bb/virtphy/l1ctl_sap.h b/src/host/virt_phy/include/osmocom/bb/virtphy/l1ctl_sap.h new file mode 100644 index 00000000..e756c140 --- /dev/null +++ b/src/host/virt_phy/include/osmocom/bb/virtphy/l1ctl_sap.h @@ -0,0 +1,85 @@ +#pragma once + +#include <stdint.h> + +#include <osmocom/core/msgb.h> + +#include <osmocom/bb/virtphy/virtual_um.h> +#include <osmocom/bb/virtphy/l1ctl_sock.h> +#include <osmocom/bb/virtphy/virt_l1_model.h> +#include <osmocom/bb/l1ctl_proto.h> + +/* following sizes are used for message allocation */ +/* size of layer 3 header */ +#define L3_MSG_HEAD 4 +/* size of layer 3 payload */ +#define L3_MSG_DATA 200 +#define L3_MSG_SIZE (sizeof(struct l1ctl_hdr) + L3_MSG_HEAD + L3_MSG_DATA) + +/* lchan link ID */ +#define LID_SACCH 0x40 +#define LID_DEDIC 0x00 + +/* signature strengths for the ms */ +#define MIN_SIG_LEV_DBM -110 +#define MAX_SIG_LEV_DBM -63 + +/* Ignore all flags of the arfcn */ +#define ARFCN_NO_FLAGS_MASK 0x0fff + + +void l1ctl_sap_init(struct l1_model_ms *model); +void l1ctl_sap_exit(struct l1_model_ms *model); +void prim_pm_init(struct l1_model_ms *model); +void prim_pm_exit(struct l1_model_ms *model); +void l1ctl_sap_tx_to_l23_inst(struct l1_model_ms *model, struct msgb *msg); +void l1ctl_sap_rx_from_l23_inst_cb(struct l1ctl_sock_client *lsc, struct msgb *msg); +void l1ctl_sap_handler(struct l1_model_ms *ms, struct msgb *msg); + +/* utility methods */ +struct msgb *l1ctl_msgb_alloc(uint8_t msg_type); +struct msgb *l1ctl_create_l2_msg(int msg_type, uint32_t fn, uint16_t snr, + uint16_t arfcn); + +/* receive routines */ +void l1ctl_rx_fbsb_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_dm_est_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_dm_rel_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_param_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_dm_freq_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_crypto_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_rach_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_data_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_pm_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_reset_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_ccch_mode_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_tch_mode_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_neigh_pm_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_traffic_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_sim_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_gprs_uldl_tbf_cfg_req(struct l1_model_ms *ms, struct msgb *msg); +void l1ctl_rx_gprs_ul_block_req(struct l1_model_ms *ms, struct msgb *msg); + +/* transmit routines */ +void l1ctl_tx_reset(struct l1_model_ms *ms, uint8_t msg_type, uint8_t reset_type); +void l1ctl_tx_rach_conf(struct l1_model_ms *ms, uint32_t fn, uint16_t arfcn); +void l1ctl_tx_data_conf(struct l1_model_ms *ms, uint32_t fn, uint16_t snr, uint16_t arfcn); +void l1ctl_tx_data_ind(struct l1_model_ms *ms, struct msgb *msg, uint16_t arfcn, uint8_t link_id, + uint8_t chan_nr, uint32_t fn, uint8_t snr, + uint8_t rxlev, uint8_t num_biterr, + uint8_t fire_crc); +void l1ctl_tx_traffic_conf(struct l1_model_ms *ms, uint32_t fn, uint16_t snr, uint16_t arfcn); +void l1ctl_tx_traffic_ind(struct l1_model_ms *ms, struct msgb *msg, uint16_t arfcn, uint8_t link_id, + uint8_t chan_nr, uint32_t fn, uint8_t snr, + uint8_t rxlev, uint8_t num_biterr, + uint8_t fire_crc); +void l1ctl_tx_pm_conf(struct l1_model_ms *ms, struct l1ctl_pm_req *pm_req); +void l1ctl_tx_fbsb_conf(struct l1_model_ms *ms, uint8_t res, uint16_t arfcn); +void l1ctl_tx_ccch_mode_conf(struct l1_model_ms *ms, uint8_t ccch_mode); +void l1ctl_tx_tch_mode_conf(struct l1_model_ms *ms, uint8_t tch_mode, uint8_t audio_mode); +void l1ctl_tx_gprs_dl_block_ind(struct l1_model_ms *ms, const struct msgb *msg, + uint32_t fn, uint8_t tn, uint8_t rxlev); + +/* scheduler functions */ +uint32_t sched_fn_ul(struct gsm_time cur_time, uint8_t chan_nr, + uint8_t link_id); diff --git a/src/host/virt_phy/include/virtphy/l1ctl_sock.h b/src/host/virt_phy/include/osmocom/bb/virtphy/l1ctl_sock.h index 2c98fa58..2c98fa58 100644 --- a/src/host/virt_phy/include/virtphy/l1ctl_sock.h +++ b/src/host/virt_phy/include/osmocom/bb/virtphy/l1ctl_sock.h diff --git a/src/host/virt_phy/include/virtphy/logging.h b/src/host/virt_phy/include/osmocom/bb/virtphy/logging.h index b22db992..1727453e 100644 --- a/src/host/virt_phy/include/virtphy/logging.h +++ b/src/host/virt_phy/include/osmocom/bb/virtphy/logging.h @@ -7,6 +7,7 @@ enum virtphy_log_cat { DL1C, DL1P, DVIRPHY, + DGPRS, DMAIN }; @@ -15,5 +16,5 @@ enum virtphy_log_cat { extern const struct log_info ms_log_info; -int ms_log_init(char *cat_mask); +int ms_log_init(void *ctx, const char *cat_mask); const char *getL1ctlPrimName(uint8_t type); diff --git a/src/host/virt_phy/include/virtphy/osmo_mcast_sock.h b/src/host/virt_phy/include/osmocom/bb/virtphy/osmo_mcast_sock.h index aa2013c6..aa2013c6 100644 --- a/src/host/virt_phy/include/virtphy/osmo_mcast_sock.h +++ b/src/host/virt_phy/include/osmocom/bb/virtphy/osmo_mcast_sock.h diff --git a/src/host/virt_phy/include/virtphy/virt_l1_model.h b/src/host/virt_phy/include/osmocom/bb/virtphy/virt_l1_model.h index 67c24bb6..94581f61 100644 --- a/src/host/virt_phy/include/virtphy/virt_l1_model.h +++ b/src/host/virt_phy/include/osmocom/bb/virtphy/virt_l1_model.h @@ -2,11 +2,12 @@ /* Per-MS specific state, closely attached to the L1CTL user progran */ -#include <virtphy/virtual_um.h> -#include <virtphy/l1ctl_sock.h> #include <osmocom/gsm/gsm_utils.h> #include <osmocom/core/timer.h> +#include <osmocom/bb/virtphy/virtual_um.h> +#include <osmocom/bb/virtphy/l1ctl_sock.h> + #define L1S_NUM_NEIGH_CELL 6 #define A5_KEY_LEN 8 @@ -15,7 +16,6 @@ enum ms_state { MS_STATE_IDLE_SYNCING, MS_STATE_IDLE_CAMPING, MS_STATE_DEDICATED, - MS_STATE_TBF }; @@ -27,7 +27,7 @@ struct l1_cell_info { uint8_t bsic; /* Combined or non-combined CCCH */ uint8_t ccch_mode; /* enum ccch_mode */ - /* whats the delta of the cells current GSM frame number + /* what's the delta of the cells current GSM frame number * compared to our current local frame number */ int32_t fn_offset; /* how much does the TPU need adjustment (delta) to synchronize @@ -68,22 +68,14 @@ struct l1_state_ms { struct { uint8_t chan_type; // like rsl chantype 08.58 -> Chapter 9.3.1 */ + uint16_t band_arfcn; uint8_t tn; // timeslot number 1-7 uint8_t subslot; // subslot of the dedicated channel, SDCCH/4:[0-3], SDCCH/8:[0-7] - uint8_t scn; // single-hop cellular network? (ununsed in virtual um) - uint8_t tsc; // training sequence code (ununsed in virtual um) - uint8_t h; // hopping enabled flag (ununsed in virtual um) + uint8_t scn; // single-hop cellular network? (unused in virtual um) + uint8_t tsc; // training sequence code (unused in virtual um) + uint8_t h; // hopping enabled flag (unused in virtual um) } dedicated; - struct { - struct { - uint8_t usf[8]; - struct llist_head tx_queue; - } ul; - struct { - uint8_t tfi[8]; - } dl; - } tbf; /* fbsb state */ struct { @@ -99,6 +91,12 @@ struct l1_state_ms { uint8_t arfcn_sig_lev_red_dbm[1024]; struct osmo_timer_list arfcn_sig_lev_timers[1024]; } meas; + struct { + uint16_t band_arfcn_from; + uint16_t band_arfcn_to; + /* timer between receiving PM_REQ and responding with PM_CONF */ + struct osmo_timer_list timer; + } req; } pm; }; @@ -108,6 +106,8 @@ struct l1_model_ms { struct l1ctl_sock_client *lsc; /* pointer to the (shared) GSMTAP/VirtUM socket to talk to BTS(s) */ struct virt_um_inst *vui; + /* GPRS state (MAC layer) */ + struct l1gprs_state *gprs; /* actual per-MS state */ struct l1_state_ms state; }; diff --git a/src/host/virt_phy/include/virtphy/virt_l1_sched.h b/src/host/virt_phy/include/osmocom/bb/virtphy/virt_l1_sched.h index b8d401ff..d5a1630f 100644 --- a/src/host/virt_phy/include/virtphy/virt_l1_sched.h +++ b/src/host/virt_phy/include/osmocom/bb/virtphy/virt_l1_sched.h @@ -1,9 +1,11 @@ #pragma once + #include <osmocom/core/msgb.h> -#include <virtphy/virt_l1_model.h> #include <osmocom/core/linuxlist.h> #include <osmocom/gsm/gsm_utils.h> -#include <virtphy/virt_l1_sched.h> + +#include <osmocom/bb/virtphy/virt_l1_model.h> +#include <osmocom/bb/virtphy/virt_l1_sched.h> struct l1_model_ms; diff --git a/src/host/virt_phy/include/virtphy/virtual_um.h b/src/host/virt_phy/include/osmocom/bb/virtphy/virtual_um.h index 52f2df64..fe060929 100644 --- a/src/host/virt_phy/include/virtphy/virtual_um.h +++ b/src/host/virt_phy/include/osmocom/bb/virtphy/virtual_um.h @@ -17,7 +17,9 @@ #define VIRT_UM_MSGB_SIZE 256 #define DEFAULT_MS_MCAST_GROUP "239.193.23.1" +#define DEFAULT_MS_MCAST_PORT 4729 /* IANA-registered port for GSMTAP */ #define DEFAULT_BTS_MCAST_GROUP "239.193.23.2" +#define DEFAULT_BTS_MCAST_PORT 4729 /* IANA-registered port for GSMTAP */ struct virt_um_inst { void *priv; @@ -27,7 +29,7 @@ struct virt_um_inst { struct virt_um_inst *virt_um_init( void *ctx, char *tx_mcast_group, uint16_t tx_mcast_port, - char *rx_mcast_group, uint16_t rx_mcast_port, + char *rx_mcast_group, uint16_t rx_mcast_port, int ttl, const char *dev_name, void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg)); void virt_um_destroy(struct virt_um_inst *vui); diff --git a/src/host/virt_phy/include/virtphy/l1ctl_sap.h b/src/host/virt_phy/include/virtphy/l1ctl_sap.h deleted file mode 100644 index 94174da4..00000000 --- a/src/host/virt_phy/include/virtphy/l1ctl_sap.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include <stdint.h> -#include <osmocom/core/msgb.h> -#include <l1ctl_proto.h> -#include <virtphy/virtual_um.h> -#include <virtphy/l1ctl_sock.h> -#include <virtphy/virt_l1_model.h> - -/* following sizes are used for message allocation */ -/* size of layer 3 header */ -#define L3_MSG_HEAD 4 -/* size of layer 3 payload */ -#define L3_MSG_DATA 200 -#define L3_MSG_SIZE (sizeof(struct l1ctl_hdr) + L3_MSG_HEAD + L3_MSG_DATA) - -/* lchan link ID */ -#define LID_SACCH 0x40 -#define LID_DEDIC 0x00 - -/* signature strengths for the ms */ -#define MIN_SIG_LEV_DBM -110 -#define MAX_SIG_LEV_DBM -63 - -/* Ignore all flags of the arfcn */ -#define ARFCN_NO_FLAGS_MASK 0x0fff - - -void l1ctl_sap_init(struct l1_model_ms *model); -void l1ctl_sap_exit(struct l1_model_ms *model); -void prim_pm_init(struct l1_model_ms *model); -void prim_pm_exit(struct l1_model_ms *model); -void l1ctl_sap_tx_to_l23_inst(struct l1_model_ms *model, struct msgb *msg); -void l1ctl_sap_rx_from_l23_inst_cb(struct l1ctl_sock_client *lsc, struct msgb *msg); -void l1ctl_sap_rx_from_l23(struct msgb *msg); -void l1ctl_sap_handler(struct l1_model_ms *ms, struct msgb *msg); - -/* utility methods */ -struct msgb *l1ctl_msgb_alloc(uint8_t msg_type); -struct msgb *l1ctl_create_l2_msg(int msg_type, uint32_t fn, uint16_t snr, - uint16_t arfcn); - -/* receive routines */ -void l1ctl_rx_fbsb_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_dm_est_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_dm_rel_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_param_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_dm_freq_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_crypto_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_rach_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_data_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_pm_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_reset_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_ccch_mode_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_tch_mode_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_neigh_pm_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_traffic_req(struct l1_model_ms *, struct msgb *msg); -void l1ctl_rx_sim_req(struct l1_model_ms *, struct msgb *msg); - -/* transmit routines */ -void l1ctl_tx_reset(struct l1_model_ms *, uint8_t msg_type, uint8_t reset_type); -void l1ctl_tx_rach_conf(struct l1_model_ms *, uint32_t fn, uint16_t arfcn); -void l1ctl_tx_data_conf(struct l1_model_ms *, uint32_t fn, uint16_t snr, uint16_t arfcn); -void l1ctl_tx_data_ind(struct l1_model_ms *, struct msgb *msg, uint16_t arfcn, uint8_t link_id, - uint8_t chan_nr, uint32_t fn, uint8_t snr, - uint8_t signal_dbm, uint8_t num_biterr, - uint8_t fire_crc); -void l1ctl_tx_traffic_conf(struct l1_model_ms *, uint32_t fn, uint16_t snr, uint16_t arfcn); -void l1ctl_tx_traffic_ind(struct l1_model_ms *, struct msgb *msg, uint16_t arfcn, uint8_t link_id, - uint8_t chan_nr, uint32_t fn, uint8_t snr, - uint8_t signal_dbm, uint8_t num_biterr, - uint8_t fire_crc); -void l1ctl_tx_pm_conf(struct l1_model_ms *, struct l1ctl_pm_req *pm_req); -void l1ctl_tx_fbsb_conf(struct l1_model_ms *, uint8_t res, uint16_t arfcn); -void l1ctl_tx_ccch_mode_conf(struct l1_model_ms *, uint8_t ccch_mode); -void l1ctl_tx_tch_mode_conf(struct l1_model_ms *, uint8_t tch_mode, uint8_t audio_mode); - -/* scheduler functions */ -uint32_t sched_fn_ul(struct gsm_time cur_time, uint8_t chan_nr, - uint8_t link_id); diff --git a/src/host/virt_phy/src/Makefile.am b/src/host/virt_phy/src/Makefile.am index bfd0dfaa..f10639f5 100644 --- a/src/host/virt_phy/src/Makefile.am +++ b/src/host/virt_phy/src/Makefile.am @@ -1,11 +1,28 @@ AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) -AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_srcdir)/../layer23/include +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include bin_PROGRAMS = virtphy -virtphy_SOURCES = virtphy.c l1ctl_sock.c gsmtapl1_if.c l1ctl_sap.c virt_prim_pm.c virt_prim_fbsb.c virt_prim_rach.c virt_prim_data.c virt_prim_traffic.c virt_l1_sched_simple.c logging.c virt_l1_model.c shared/virtual_um.c shared/osmo_mcast_sock.c -virtphy_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) -virtphy_LDFLAGS = -pthread -# debug output -all: - $(info $$AM_CPPFLAGS is [${AM_CPPFLAGS}]) +virtphy_SOURCES = \ + virtphy.c \ + l1gprs.c \ + logging.c \ + gsmtapl1_if.c \ + l1ctl_sock.c \ + l1ctl_sap.c \ + virt_prim_pm.c \ + virt_prim_fbsb.c \ + virt_prim_rach.c \ + virt_prim_data.c \ + virt_prim_pdch.c \ + virt_prim_traffic.c \ + virt_l1_sched_simple.c \ + virt_l1_model.c \ + shared/virtual_um.c \ + shared/osmo_mcast_sock.c \ + $(NULL) + +virtphy_LDADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) diff --git a/src/host/virt_phy/src/gsmtapl1_if.c b/src/host/virt_phy/src/gsmtapl1_if.c index 2cf9d2dc..ef8d6840 100644 --- a/src/host/virt_phy/src/gsmtapl1_if.c +++ b/src/host/virt_phy/src/gsmtapl1_if.c @@ -20,25 +20,28 @@ * */ +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + #include <osmocom/core/gsmtap.h> #include <osmocom/core/gsmtap_util.h> #include <osmocom/core/utils.h> #include <osmocom/gsm/rsl.h> #include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/core/msgb.h> -#include <stddef.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <l1ctl_proto.h> -#include <virtphy/virtual_um.h> -#include <virtphy/l1ctl_sock.h> -#include <virtphy/virt_l1_model.h> -#include <virtphy/l1ctl_sap.h> -#include <virtphy/gsmtapl1_if.h> -#include <virtphy/logging.h> -#include <virtphy/virt_l1_sched.h> + +#include <osmocom/bb/virtphy/virtual_um.h> +#include <osmocom/bb/virtphy/l1ctl_sock.h> +#include <osmocom/bb/virtphy/virt_l1_model.h> +#include <osmocom/bb/virtphy/l1ctl_sap.h> +#include <osmocom/bb/virtphy/gsmtapl1_if.h> +#include <osmocom/bb/virtphy/logging.h> +#include <osmocom/bb/virtphy/virt_l1_sched.h> +#include <osmocom/bb/l1ctl_proto.h> static char *pseudo_lchan_name(uint16_t arfcn, uint8_t ts, uint8_t ss, uint8_t sub_type) { @@ -48,6 +51,29 @@ static char *pseudo_lchan_name(uint16_t arfcn, uint8_t ts, uint8_t ss, uint8_t s return lname; } +/* Return gsmtap_um_voice_type or -1 on error */ +static int get_um_voice_type(enum gsm48_chan_mode tch_mode, uint8_t rsl_chantype) +{ + switch (tch_mode) { + case GSM48_CMODE_SPEECH_V1: + switch (rsl_chantype) { + case RSL_CHAN_Bm_ACCHs: + return GSMTAP_UM_VOICE_FR; + case RSL_CHAN_Lm_ACCHs: + return GSMTAP_UM_VOICE_HR; + default: + return -1; + } + break; + case GSM48_CMODE_SPEECH_EFR: + return GSMTAP_UM_VOICE_EFR; + case GSM48_CMODE_SPEECH_AMR: + return GSMTAP_UM_VOICE_AMR; + default: + return -1; + } +} + /** * Replace l11 header of given msgb by a gsmtap header and send it over the virt um. */ @@ -57,8 +83,8 @@ void gsmtapl1_tx_to_virt_um_inst(struct l1_model_ms *ms, uint32_t fn, uint8_t tn struct l1ctl_info_ul *ul; struct gsmtap_hdr *gh; struct msgb *outmsg; /* msg to send with gsmtap header prepended */ - uint16_t arfcn = ms->state.serving_cell.arfcn; /* arfcn of the cell we currently camp on */ - uint8_t signal_dbm = 63; /* signal strength */ + uint16_t arfcn; + uint8_t signal_dbm = rxlev2dbm(63); /* signal strength */ uint8_t snr = 63; /* signal noise ratio, 63 is best */ uint8_t *data = msgb_l2(msg); /* data to transmit (whole message without l1 header) */ uint8_t data_len = msgb_l2len(msg); /* length of data */ @@ -68,18 +94,34 @@ void gsmtapl1_tx_to_virt_um_inst(struct l1_model_ms *ms, uint32_t fn, uint8_t tn uint8_t timeslot; /* tdma timeslot to send in (0-7) */ uint8_t gsmtap_chan; /* the gsmtap channel */ + if (ms->state.state == MS_STATE_DEDICATED) + arfcn = ms->state.dedicated.band_arfcn; + else + arfcn = ms->state.serving_cell.arfcn; + switch (l1h->msg_type) { - case L1CTL_DATA_TBF_REQ: - ul = NULL; - rsl_chantype = RSL_CHAN_OSMO_PDCH; + case L1CTL_GPRS_UL_BLOCK_REQ: + gsmtap_chan = GSMTAP_CHANNEL_PDCH; timeslot = tn; subslot = 0; - gsmtap_chan = chantype_rsl2gsmtap(rsl_chantype, 0); + break; + case L1CTL_TRAFFIC_REQ: + ul = (struct l1ctl_info_ul *)l1h->data; + rsl_dec_chan_nr(ul->chan_nr, &rsl_chantype, &subslot, ×lot); + gsmtap_chan = chantype_rsl2gsmtap2(rsl_chantype, 0, true); + /* the first byte indicates the type of voice codec (gsmtap_um_voice_type); + * let's first strip any data in front of the l2 header, then push this extra + * byte to the front and finally adjust the l2h pointer */ + msgb_pull_to_l2(msg); + msgb_push_u8(msg, get_um_voice_type(ms->state.tch_mode, rsl_chantype)); + msg->l2h = msg->data; + data = msgb_l2(msg); + data_len = msgb_l2len(msg); break; default: ul = (struct l1ctl_info_ul *)l1h->data; rsl_dec_chan_nr(ul->chan_nr, &rsl_chantype, &subslot, ×lot); - gsmtap_chan = chantype_rsl2gsmtap(rsl_chantype, ul->link_id); + gsmtap_chan = chantype_rsl2gsmtap2(rsl_chantype, ul->link_id, false); break; } @@ -116,100 +158,34 @@ extern void prim_fbsb_sync(struct l1_model_ms *ms, struct msgb *msg); */ extern uint16_t prim_pm_set_sig_strength(struct l1_model_ms *ms, uint16_t arfcn, int16_t sig_lev); -/* determine if a received Downlink RLC/MAC block matches the current MS configuration */ -static bool gprs_dl_block_matches_ms(struct l1_model_ms *ms, struct msgb *msg, uint8_t timeslot) -{ - uint8_t payload_type; - uint8_t tfi; - - if (msgb_length(msg) < 1) - return false; - - /* FIXME: Ensure this will also work for EGPRS! */ - payload_type = msg->data[0] >> 6; - switch (payload_type) { - case 0: /* RLC Data Block */ - /* forward all RLD Data Blocks destined for TFI of MS */ - tfi = (msg->data[1] >> 1) & 0x1f; - if (ms->state.state == MS_STATE_TBF && ms->state.tbf.dl.tfi[timeslot] == tfi) - return true; - break; - case 1: /* RLC/MAC Control without optional octets */ - /* forward all RLC/MAC control blocks without optional octets, i.e. not adressed - * to a specific TFI */ - return true; - case 2: /* RLC/MAC with optional control octets */ - /* forward all RLD Control Blocks destined for TFI of MS */ - tfi = (msg->data[2] >> 1) & 0x1f; - if (ms->state.state == MS_STATE_TBF && ms->state.tbf.dl.tfi[timeslot] == tfi) - return true; - break; - default: - break; - } - return false; -} - -/* determine if given USF at given timeslot is relevant to given MS or not */ -static bool usf_matches_ms(struct l1_model_ms *ms, uint8_t usf, uint8_t timeslot) -{ - if (ms->state.state == MS_STATE_TBF && ms->state.tbf.ul.usf[timeslot] == usf) - return true; - - return false; -} - -/* extract USF from (E)GPRS RLC/MAC block */ -static uint8_t get_usf_from_block(struct msgb *msg) -{ - /* FIXME: Ensure this will also work for EGPRS! */ - return msg->data[0] & 0x7; -} - -/* MS is authorized to transmit a block in uplink for given USF on timeslot+arfcn at FN */ -static void ms_ul_tbf_may_transmit(struct l1_model_ms *ms, uint16_t arfcn, uint8_t timeslot, - uint32_t fn, uint8_t usf) -{ - struct msgb *msg; - - /* If USF is not for us, bail out */ - if (!usf_matches_ms(ms, usf, timeslot)) - return; - - /* attempt to de-queue pending msgb for this UL TBF and transmit it */ - msg = msgb_dequeue(&ms->state.tbf.ul.tx_queue); - if (!msg) { - printf("FN=%u, TN=%u, USF=%u: empty tx_queue, not transmitting\n", fn, timeslot, usf); - /* FIXME: send some dummy control frame? */ - } else { - printf("FN=%u, TN=%u, USF=%u: transmitting queued msg\n", fn, timeslot, usf); - gsmtapl1_tx_to_virt_um_inst(ms, fn, timeslot, msg); - } -} - static void l1ctl_from_virt_um(struct l1ctl_sock_client *lsc, struct msgb *msg, uint32_t fn, uint16_t arfcn, uint8_t timeslot, uint8_t subslot, uint8_t gsmtap_chantype, uint8_t chan_nr, uint8_t link_id, uint8_t snr_db) { struct l1_model_ms *ms = lsc->priv; - uint8_t signal_dbm = dbm2rxlev(prim_pm_set_sig_strength(ms, arfcn & GSMTAP_ARFCN_MASK, MAX_SIG_LEV_DBM)); /* Power measurement with each received massage */ - uint8_t usf; + uint8_t rxlev = dbm2rxlev(prim_pm_set_sig_strength(ms, arfcn & GSMTAP_ARFCN_MASK, MAX_SIG_LEV_DBM)); gsm_fn2gsmtime(&ms->state.downlink_time, fn); - /* we do not forward messages to l23 if we are in network search state */ - if (ms->state.state == MS_STATE_IDLE_SEARCHING) + switch (ms->state.state) { + case MS_STATE_IDLE_SEARCHING: + /* we do not forward messages to l23 if we are in network search state */ return; - - /* forward downlink msg to fbsb sync routine if we are in sync state */ - if (ms->state.state == MS_STATE_IDLE_SYNCING) { + case MS_STATE_IDLE_SYNCING: + /* forward downlink msg to fbsb sync routine if we are in sync state */ prim_fbsb_sync(ms, msg); return; - } - /* generally ignore all messages coming from another arfcn than the camped one */ - if (ms->state.serving_cell.arfcn != arfcn) { - return; + case MS_STATE_DEDICATED: + /* generally ignore all messages coming from another arfcn than the camped one */ + if (arfcn != ms->state.dedicated.band_arfcn) + return; + break; + default: + /* generally ignore all messages coming from another arfcn than the camped one */ + if (arfcn != ms->state.serving_cell.arfcn) + return; + break; } virt_l1_sched_sync_time(ms, ms->state.downlink_time, 0); @@ -219,44 +195,49 @@ static void l1ctl_from_virt_um(struct l1ctl_sock_client *lsc, struct msgb *msg, switch (gsmtap_chantype & ~GSMTAP_CHANNEL_ACCH & 0xff) { case GSMTAP_CHANNEL_TCH_H: case GSMTAP_CHANNEL_TCH_F: -#if 0 - /* TODO: handle voice */ - if (!facch && !tch_acch) { - l1ctl_tx_traffic_ind(msg, arfcn, link_id, chan_nr, fn, - snr, signal_dbm, 0, 0); - } -#endif + /* This is TCH signalling, for voice frames see GSMTAP_CHANNEL_VOICE */ case GSMTAP_CHANNEL_SDCCH4: case GSMTAP_CHANNEL_SDCCH8: /* only forward messages on dedicated channels to l2, if * the timeslot and subslot is fitting */ if (ms->state.dedicated.tn == timeslot && ms->state.dedicated.subslot == subslot) { - l1ctl_tx_data_ind(ms, msg, arfcn, link_id, chan_nr, fn, snr_db, signal_dbm, 0, 0); + l1ctl_tx_data_ind(ms, msg, arfcn, link_id, chan_nr, fn, snr_db, rxlev, 0, 0); } break; + case GSMTAP_CHANNEL_VOICE_F: + case GSMTAP_CHANNEL_VOICE_H: + /* only forward messages on dedicated channels to l2, if + * the timeslot and subslot is fitting */ + if (ms->state.dedicated.tn == timeslot + && ms->state.dedicated.subslot == subslot) { + l1ctl_tx_traffic_ind(ms, msg, arfcn, link_id, chan_nr, fn, + snr_db, rxlev, 0, 0); + } + break; + case GSMTAP_CHANNEL_CBCH51: + /* only pass CBCH data if the user application actually indicated that a CBCH + * is present */ + if (ms->state.serving_cell.ccch_mode != CCCH_MODE_COMBINED_CBCH) + break; case GSMTAP_CHANNEL_AGCH: case GSMTAP_CHANNEL_PCH: case GSMTAP_CHANNEL_BCCH: - case GSMTAP_CHANNEL_CBCH51: case GSMTAP_CHANNEL_CBCH52: /* save to just forward here, as upper layer ignores messages that * do not fit the current state (e.g. gsm48_rr.c:2159) */ - l1ctl_tx_data_ind(ms, msg, arfcn, link_id, chan_nr, fn, snr_db, signal_dbm, 0, 0); + l1ctl_tx_data_ind(ms, msg, arfcn, link_id, chan_nr, fn, snr_db, rxlev, 0, 0); break; case GSMTAP_CHANNEL_RACH: LOGPMS(DVIRPHY, LOGL_NOTICE, ms, "Ignoring unexpected RACH in downlink ?!?\n"); break; + case GSMTAP_CHANNEL_PTCCH: case GSMTAP_CHANNEL_PACCH: case GSMTAP_CHANNEL_PDCH: - if (gprs_dl_block_matches_ms(ms, msg, timeslot)) - l1ctl_tx_data_ind(ms, msg, arfcn, link_id, chan_nr, fn, snr_db, signal_dbm, 0, 0); - usf = get_usf_from_block(msg); - ms_ul_tbf_may_transmit(ms, arfcn, timeslot, fn, usf); + l1ctl_tx_gprs_dl_block_ind(ms, msg, fn, timeslot, rxlev); break; case GSMTAP_CHANNEL_SDCCH: case GSMTAP_CHANNEL_CCCH: - case GSMTAP_CHANNEL_PTCCH: LOGPMS(DVIRPHY, LOGL_NOTICE, ms, "Ignoring unsupported channel type %s\n", get_value_string(gsmtap_gsm_channel_names, gsmtap_chantype)); break; diff --git a/src/host/virt_phy/src/l1ctl_sap.c b/src/host/virt_phy/src/l1ctl_sap.c index f95a4caf..7e4540df 100644 --- a/src/host/virt_phy/src/l1ctl_sap.c +++ b/src/host/virt_phy/src/l1ctl_sap.c @@ -19,6 +19,9 @@ * */ +#include <stdio.h> +#include <string.h> +#include <netinet/in.h> #include <osmocom/core/linuxlist.h> #include <osmocom/core/msgb.h> @@ -27,21 +30,16 @@ #include <osmocom/gsm/protocol/gsm_08_58.h> #include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/gsm/rsl.h> -#include <osmocom/gprs/gprs_rlc.h> -#include <stdio.h> -#include <l1ctl_proto.h> -#include <netinet/in.h> -#include <string.h> -#include <virtphy/virtual_um.h> -#include <virtphy/l1ctl_sock.h> -#include <virtphy/virt_l1_model.h> -#include <virtphy/l1ctl_sap.h> -#include <virtphy/gsmtapl1_if.h> -#include <virtphy/logging.h> -#include <virtphy/virt_l1_sched.h> -static void l1ctl_rx_tbf_cfg_req(struct l1_model_ms *ms, struct msgb *msg); -static void l1ctl_rx_data_tbf_req(struct l1_model_ms *ms, struct msgb *msg); +#include <osmocom/bb/virtphy/virtual_um.h> +#include <osmocom/bb/virtphy/l1ctl_sock.h> +#include <osmocom/bb/virtphy/virt_l1_model.h> +#include <osmocom/bb/virtphy/l1ctl_sap.h> +#include <osmocom/bb/virtphy/gsmtapl1_if.h> +#include <osmocom/bb/virtphy/logging.h> +#include <osmocom/bb/virtphy/virt_l1_sched.h> +#include <osmocom/bb/l1ctl_proto.h> +#include <osmocom/bb/l1gprs.h> static void l1_model_tch_mode_set(struct l1_model_ms *ms, uint8_t tch_mode) { @@ -59,7 +57,6 @@ static void l1_model_tch_mode_set(struct l1_model_ms *ms, uint8_t tch_mode) void l1ctl_sap_init(struct l1_model_ms *model) { INIT_LLIST_HEAD(&model->state.sched.mframe_items); - INIT_LLIST_HEAD(&model->state.tbf.ul.tx_queue); prim_pm_init(model); } @@ -162,8 +159,8 @@ static bool is_l1ctl_control(uint8_t msg_type) switch (msg_type) { case L1CTL_DATA_REQ: case L1CTL_DATA_CONF: - case L1CTL_DATA_TBF_REQ: - case L1CTL_DATA_TBF_CONF: + case L1CTL_GPRS_UL_BLOCK_REQ: + case L1CTL_GPRS_DL_BLOCK_IND: case L1CTL_TRAFFIC_REQ: case L1CTL_TRAFFIC_CONF: case L1CTL_TRAFFIC_IND: @@ -246,11 +243,12 @@ void l1ctl_sap_handler(struct l1_model_ms *ms, struct msgb *msg) case L1CTL_SIM_REQ: l1ctl_rx_sim_req(ms, msg); break; - case L1CTL_TBF_CFG_REQ: - l1ctl_rx_tbf_cfg_req(ms, msg); + case L1CTL_GPRS_UL_TBF_CFG_REQ: + case L1CTL_GPRS_DL_TBF_CFG_REQ: + l1ctl_rx_gprs_uldl_tbf_cfg_req(ms, msg); break; - case L1CTL_DATA_TBF_REQ: - l1ctl_rx_data_tbf_req(ms, msg); + case L1CTL_GPRS_UL_BLOCK_REQ: + l1ctl_rx_gprs_ul_block_req(ms, msg); goto exit_nofree; } @@ -286,14 +284,22 @@ void l1ctl_rx_dm_est_req(struct l1_model_ms *ms, struct msgb *msg) rsl_dec_chan_nr(ul->chan_nr, &rsl_chantype, &subslot, ×lot); - LOGPMS(DL1C, LOGL_INFO, ms, "Rx L1CTL_DM_EST_REQ (chan_nr=0x%02x, tn=%u, ss=%u)\n", - ul->chan_nr, timeslot, subslot); + LOGPMS(DL1C, LOGL_DEBUG, ms, "Rx L1CTL_DM_EST_REQ (chan_nr=0x%02x, arfcn=%u, tn=%u, ss=%u)\n", + ul->chan_nr, ntohs(est_req->h0.band_arfcn), timeslot, subslot); + OSMO_ASSERT(est_req->h == 0); /* we don't do hopping */ + ms->state.dedicated.band_arfcn = ntohs(est_req->h0.band_arfcn); ms->state.dedicated.chan_type = rsl_chantype; ms->state.dedicated.tn = timeslot; ms->state.dedicated.subslot = subslot; ms->state.state = MS_STATE_DEDICATED; + if (rsl_chantype == RSL_CHAN_OSMO_PDCH) { + OSMO_ASSERT(ms->gprs == NULL); + ms->gprs = l1gprs_state_alloc(ms, NULL, ms); + OSMO_ASSERT(ms->gprs != NULL); + } + /* TCH config */ if (rsl_chantype == RSL_CHAN_Bm_ACCHs || rsl_chantype == RSL_CHAN_Lm_ACCHs) { ms->state.tch_mode = est_req->tch_mode; @@ -313,7 +319,7 @@ void l1ctl_rx_dm_est_req(struct l1_model_ms *ms, struct msgb *msg) * * Handle frequency change in dedicated mode. E.g. used for frequency hopping. * - * Note: Not needed for virtual physical layer as freqency hopping is generally disabled. + * Note: Not needed for virtual physical layer as frequency hopping is generally disabled. */ void l1ctl_rx_dm_freq_req(struct l1_model_ms *ms, struct msgb *msg) { @@ -378,6 +384,9 @@ void l1ctl_rx_dm_rel_req(struct l1_model_ms *ms, struct msgb *msg) ms->state.tch_mode = GSM48_CMODE_SIGN; ms->state.state = MS_STATE_IDLE_CAMPING; + l1gprs_state_free(ms->gprs); + ms->gprs = NULL; + /* TODO: disable ciphering */ /* TODO: disable audio recording / playing */ } @@ -426,6 +435,8 @@ void l1ctl_rx_reset_req(struct l1_model_ms *ms, struct msgb *msg) DEBUGPMS(DL1C, ms, "Rx L1CTL_RESET_REQ (type=FULL)\n"); ms->state.state = MS_STATE_IDLE_SEARCHING; virt_l1_sched_stop(ms); + l1gprs_state_free(ms->gprs); + ms->gprs = NULL; l1ctl_tx_reset(ms, L1CTL_RESET_CONF, reset_req->type); break; case L1CTL_RES_T_SCHED: @@ -484,6 +495,7 @@ void l1ctl_rx_tch_mode_req(struct l1_model_ms *ms, struct msgb *msg) l1_model_tch_mode_set(ms, tch_mode_req->tch_mode); ms->state.audio_mode = tch_mode_req->audio_mode; + /* TODO: Handle AMR codecs from tch_mode_req if tch_mode_req->tch_mode==GSM48_CMODE_SPEECH_AMR */ LOGPMS(DL1C, LOGL_INFO, ms, "Rx L1CTL_TCH_MODE_REQ (tch_mode=0x%02x audio_mode=0x%02x)\n", tch_mode_req->tch_mode, tch_mode_req->audio_mode); @@ -504,7 +516,7 @@ void l1ctl_rx_tch_mode_req(struct l1_model_ms *ms, struct msgb *msg) * The neighbor cell description is one of the info messages sent by the BTS on BCCH. * This method will also enable neighbor measurement in the multiframe scheduler. * - * Note: Not needed for virtual physical layer as we dont maintain neigbors. + * Note: Not needed for virtual physical layer as we don't maintain neighbors. */ void l1ctl_rx_neigh_pm_req(struct l1_model_ms *ms, struct msgb *msg) { @@ -543,143 +555,12 @@ void l1ctl_rx_sim_req(struct l1_model_ms *ms, struct msgb *msg) } -static void l1ctl_tx_tbf_cfg_conf(struct l1_model_ms *ms, const struct l1ctl_tbf_cfg_req *in); - -static void l1ctl_rx_tbf_cfg_req(struct l1_model_ms *ms, struct msgb *msg) -{ - struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; - struct l1ctl_tbf_cfg_req *cfg_req = (struct l1ctl_tbf_cfg_req *) l1h->data; - unsigned int i; - - LOGPMS(DL1C, LOGL_INFO, ms, "Rx L1CTL_TBF_CFG_REQ (tbf_id=%u, dir=%s, " - "usf=[%d,%d,%d,%d,%d,%d,%d,%d]\n", cfg_req->tbf_nr, - cfg_req->is_uplink ? "UL" : "DL", cfg_req->usf[0], cfg_req->usf[1], - cfg_req->usf[2], cfg_req->usf[3], cfg_req->usf[4], cfg_req->usf[5], - cfg_req->usf[6], cfg_req->usf[7]); - - if (cfg_req->tbf_nr != 0) { - LOGPMS(DL1C, LOGL_ERROR, ms, "TBF_NR != 0 not supported yet!\n"); - return; - } - - if (ms->state.state == MS_STATE_DEDICATED) - LOGPMS(DL1C, LOGL_NOTICE, ms, "Hard termination of DEDICATED mode, fix L23!\n"); - - if (cfg_req->is_uplink) { - for (i = 0; i < 8; i++) - ms->state.tbf.ul.usf[i] = cfg_req->usf[i]; - } else { - for (i = 0; i < 8; i++) - ms->state.tbf.dl.tfi[i] = cfg_req->usf[i]; - } - ms->state.state = MS_STATE_TBF; - - l1ctl_tx_tbf_cfg_conf(ms, cfg_req); -} - -static const enum osmo_gprs_cs osmo_cs_by_l1ctl[] = { - [L1CTL_CS_NONE] = OSMO_GPRS_CS_NONE, - [L1CTL_CS1] = OSMO_GPRS_CS1, - [L1CTL_CS2] = OSMO_GPRS_CS2, - [L1CTL_CS3] = OSMO_GPRS_CS3, - [L1CTL_CS4] = OSMO_GPRS_CS4, - [L1CTL_MCS1] = OSMO_GPRS_MCS1, - [L1CTL_MCS2] = OSMO_GPRS_MCS2, - [L1CTL_MCS3] = OSMO_GPRS_MCS3, - [L1CTL_MCS4] = OSMO_GPRS_MCS4, - [L1CTL_MCS5] = OSMO_GPRS_MCS5, - [L1CTL_MCS6] = OSMO_GPRS_MCS6, - [L1CTL_MCS7] = OSMO_GPRS_MCS7, - [L1CTL_MCS8] = OSMO_GPRS_MCS8, - [L1CTL_MCS9] = OSMO_GPRS_MCS9, -}; - -static int get_osmo_cs_by_l1ctl(enum l1ctl_coding_scheme l1) -{ - if (l1 >= ARRAY_SIZE(osmo_cs_by_l1ctl)) - return -1; - return osmo_cs_by_l1ctl[l1]; -} - -static const enum l1ctl_coding_scheme l1ctl_cs_by_osmo[] = { - [OSMO_GPRS_CS_NONE] = L1CTL_CS_NONE, - [OSMO_GPRS_CS1] = L1CTL_CS1, - [OSMO_GPRS_CS2] = L1CTL_CS2, - [OSMO_GPRS_CS3] = L1CTL_CS3, - [OSMO_GPRS_CS4] = L1CTL_CS4, - [OSMO_GPRS_MCS1] = L1CTL_MCS1, - [OSMO_GPRS_MCS2] = L1CTL_MCS2, - [OSMO_GPRS_MCS3] = L1CTL_MCS3, - [OSMO_GPRS_MCS4] = L1CTL_MCS4, - [OSMO_GPRS_MCS5] = L1CTL_MCS5, - [OSMO_GPRS_MCS6] = L1CTL_MCS6, - [OSMO_GPRS_MCS7] = L1CTL_MCS7, - [OSMO_GPRS_MCS8] = L1CTL_MCS8, - [OSMO_GPRS_MCS9] = L1CTL_MCS9, -}; - -static int get_l1ctl_cs_by_osmo(enum osmo_gprs_cs in) -{ - if (in >= ARRAY_SIZE(l1ctl_cs_by_osmo)) - return -1; - return l1ctl_cs_by_osmo[in]; -} - -static void l1ctl_rx_data_tbf_req(struct l1_model_ms *ms, struct msgb *msg) -{ - struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; - struct l1ctl_info_ul_tbf *udt = (struct l1ctl_info_ul_tbf *) l1h->data; - enum osmo_gprs_cs osmo_cs; - int block_size; - - msg->l2h = udt->payload; - - LOGPMS(DL1P, LOGL_ERROR, ms, "Rx L1CTL_DATA_TBF_REQ (tbf_id=%d, data=%s)\n", - udt->tbf_nr, osmo_hexdump(msg->l2h, msgb_l2len(msg))); - if (udt->tbf_nr != 0) { - LOGPMS(DL1C, LOGL_ERROR, ms, "TBF_NR != 0 not supported yet!\n"); - return; - } - - if (ms->state.state != MS_STATE_TBF) { - LOGPMS(DL1P, LOGL_ERROR, ms, "DATA_TBF_REQ in state != TBF\n"); - return; - } - - osmo_cs = get_l1ctl_cs_by_osmo(udt->coding_scheme); - if (osmo_cs < 0) { - LOGPMS(DL1P, LOGL_ERROR, ms, "DATA_RBF_REQ with invalid CS\n"); - return; - } - block_size = osmo_gprs_ul_block_size_bytes(osmo_cs); - - if (msgb_l2len(msg) < block_size) { - int pad_len = block_size - msgb_l2len(msg); - uint8_t *pad = msgb_put(msg, pad_len); - memset(pad, GSM_MACBLOCK_PADDING, pad_len); - } - - msgb_enqueue(&ms->state.tbf.ul.tx_queue, msg); -} - /*************************************************************** * L1CTL TX ROUTINES ******************************************* * For more routines check the respective handler classes ****** * like virt_prim_rach.c *************************************** ***************************************************************/ -static void l1ctl_tx_tbf_cfg_conf(struct l1_model_ms *ms, const struct l1ctl_tbf_cfg_req *in) -{ - struct msgb *msg = l1ctl_msgb_alloc(L1CTL_TBF_CFG_CONF); - struct l1ctl_tbf_cfg_req *out; - - /* copy over the data from the request */ - out = (struct l1ctl_tbf_cfg_req *) msgb_put(msg, sizeof(*out)); - *out = *in; - - l1ctl_sap_tx_to_l23_inst(ms, msg); -} - /** * @brief Transmit L1CTL_RESET_IND or L1CTL_RESET_CONF to layer 23. * diff --git a/src/host/virt_phy/src/l1ctl_sock.c b/src/host/virt_phy/src/l1ctl_sock.c index bf28895f..089d9caf 100644 --- a/src/host/virt_phy/src/l1ctl_sock.c +++ b/src/host/virt_phy/src/l1ctl_sock.c @@ -40,8 +40,8 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/socket.h> -#include <virtphy/l1ctl_sock.h> -#include <virtphy/logging.h> +#include <osmocom/bb/virtphy/l1ctl_sock.h> +#include <osmocom/bb/virtphy/logging.h> #define L1CTL_SOCK_MSGB_SIZE 256 @@ -72,7 +72,7 @@ static int l1ctl_sock_data_cb(struct osmo_fd *ofd, unsigned int what) int rc; /* Check if request is really read request */ - if (!(what & BSC_FD_READ)) + if (!(what & OSMO_FD_READ)) return 0; msg = msgb_alloc(L1CTL_SOCK_MSGB_SIZE, "L1CTL sock rx"); @@ -125,10 +125,7 @@ static int l1ctl_sock_accept_cb(struct osmo_fd *ofd, unsigned int what) } lsc->l1ctl_sock = lsi; - lsc->ofd.fd = fd; - lsc->ofd.when = BSC_FD_READ; - lsc->ofd.cb = l1ctl_sock_data_cb; - lsc->ofd.data = lsc; + osmo_fd_setup(&lsc->ofd, fd, OSMO_FD_READ, l1ctl_sock_data_cb, lsc, 0); if (lsi->accept_cb) { rc = lsi->accept_cb(lsc); if (rc < 0) { @@ -163,9 +160,7 @@ struct l1ctl_sock_inst *l1ctl_sock_init( lsi = talloc_zero(ctx, struct l1ctl_sock_inst); lsi->priv = NULL; - lsi->ofd.data = lsi; - lsi->ofd.when = BSC_FD_READ; - lsi->ofd.cb = l1ctl_sock_accept_cb; + osmo_fd_setup(&lsi->ofd, -1, OSMO_FD_READ, l1ctl_sock_accept_cb, lsi, 0); rc = osmo_sock_unix_init_ofd(&lsi->ofd, SOCK_STREAM, 0, path, OSMO_SOCK_F_BIND); if (rc < 0) { diff --git a/src/host/virt_phy/src/l1gprs.c b/src/host/virt_phy/src/l1gprs.c new file mode 120000 index 00000000..0185f68b --- /dev/null +++ b/src/host/virt_phy/src/l1gprs.c @@ -0,0 +1 @@ +../../../shared/l1gprs.c
\ No newline at end of file diff --git a/src/host/virt_phy/src/logging.c b/src/host/virt_phy/src/logging.c index 7e4e79b1..fc37205f 100644 --- a/src/host/virt_phy/src/logging.c +++ b/src/host/virt_phy/src/logging.c @@ -15,53 +15,49 @@ * 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 <osmocom/core/utils.h> #include <osmocom/core/application.h> -#include <virtphy/logging.h> +#include <osmocom/bb/virtphy/logging.h> static const char* l1ctlPrimNames[] = { - "_L1CTL_NONE", - "L1CTL_FBSB_REQ", - "L1CTL_FBSB_CONF", - "L1CTL_DATA_IND", - "L1CTL_RACH_REQ", - "L1CTL_DM_EST_REQ", - "L1CTL_DATA_REQ", - "L1CTL_RESET_IND", - "L1CTL_PM_REQ", - "L1CTL_PM_CONF", - "L1CTL_ECHO_REQ", - "L1CTL_ECHO_CONF", - "L1CTL_RACH_CONF", - "L1CTL_RESET_REQ", - "L1CTL_RESET_CONF", - "L1CTL_DATA_CONF", - "L1CTL_CCCH_MODE_REQ", - "L1CTL_CCCH_MODE_CONF", - "L1CTL_DM_REL_REQ", - "L1CTL_PARAM_REQ", - "L1CTL_DM_FREQ_REQ", - "L1CTL_CRYPTO_REQ", - "L1CTL_SIM_REQ", - "L1CTL_SIM_CONF", - "L1CTL_TCH_MODE_REQ", - "L1CTL_TCH_MODE_CONF", - "L1CTL_NEIGH_PM_REQ", - "L1CTL_NEIGH_PM_IND", - "L1CTL_TRAFFIC_REQ", - "L1CTL_TRAFFIC_CONF", - "L1CTL_TRAFFIC_IND", - "L1CTL_BURST_IND", - "L1CTL_TBF_CFG_REQ", - "L1CTL_TBF_CFG_CONF", - "L1CTL_DATA_TBF_REQ", - "L1CTL_DATA_TBF_CONF" + "_L1CTL_NONE", + "L1CTL_FBSB_REQ", + "L1CTL_FBSB_CONF", + "L1CTL_DATA_IND", + "L1CTL_RACH_REQ", + "L1CTL_DM_EST_REQ", + "L1CTL_DATA_REQ", + "L1CTL_RESET_IND", + "L1CTL_PM_REQ", + "L1CTL_PM_CONF", + "L1CTL_ECHO_REQ", + "L1CTL_ECHO_CONF", + "L1CTL_RACH_CONF", + "L1CTL_RESET_REQ", + "L1CTL_RESET_CONF", + "L1CTL_DATA_CONF", + "L1CTL_CCCH_MODE_REQ", + "L1CTL_CCCH_MODE_CONF", + "L1CTL_DM_REL_REQ", + "L1CTL_PARAM_REQ", + "L1CTL_DM_FREQ_REQ", + "L1CTL_CRYPTO_REQ", + "L1CTL_SIM_REQ", + "L1CTL_SIM_CONF", + "L1CTL_TCH_MODE_REQ", + "L1CTL_TCH_MODE_CONF", + "L1CTL_NEIGH_PM_REQ", + "L1CTL_NEIGH_PM_IND", + "L1CTL_TRAFFIC_REQ", + "L1CTL_TRAFFIC_CONF", + "L1CTL_TRAFFIC_IND", + "L1CTL_BURST_IND", + "L1CTL_GPRS_UL_TBF_CFG_REQ", + "L1CTL_GPRS_DL_TBF_CFG_REQ", + "L1CTL_GPRS_UL_BLOCK_REQ", + "L1CTL_GPRS_DL_BLOCK_IND", }; static const struct log_info_cat default_categories[] = { @@ -70,28 +66,35 @@ static const struct log_info_cat default_categories[] = { .description = "Layer 1 Control", .color = "\033[1;31m", .enabled = 1, - .loglevel = LOGL_DEBUG, + .loglevel = LOGL_NOTICE, }, [DL1P] = { .name = "DL1P", .description = "Layer 1 Data", .color = "\033[1;31m", .enabled = 1, - .loglevel = LOGL_DEBUG, + .loglevel = LOGL_NOTICE, }, [DVIRPHY] = { .name = "DVIRPHY", .description = "Virtual Layer 1 Interface", .color = "\033[1;31m", .enabled = 1, - .loglevel = LOGL_DEBUG, + .loglevel = LOGL_NOTICE, + }, + [DGPRS] = { + .name = "DGPRS", + .description = "L1 GPRS (MAC leyer)", + .color = "\033[1;31m", + .enabled = 1, + .loglevel = LOGL_NOTICE, }, [DMAIN] = { .name = "DMAIN", .description = "Main Program / Data Structures", .color = "\033[1;32m", .enabled = 1, - .loglevel = LOGL_DEBUG, + .loglevel = LOGL_NOTICE, }, }; @@ -104,24 +107,21 @@ const struct log_info ms_log_info = { /** * Initialize the logging system for the virtual physical layer. */ -int ms_log_init(char *cat_mask) +int ms_log_init(void *ctx, const char *cat_mask) { - struct log_target *stderr_target; + int rc; - log_init(&ms_log_info, NULL); - stderr_target = log_target_create_stderr(); - if (!stderr) - return -1; + rc = osmo_init_logging2(ctx, &ms_log_info); + OSMO_ASSERT(rc == 0); - log_add_target(stderr_target); - log_set_all_filter(stderr_target, 1); - //log_set_log_level(stderr_target, 1); - log_set_print_filename(stderr_target, 1); - log_set_use_color(stderr_target, 0); - log_set_print_timestamp(stderr_target, 1); - log_set_print_category(stderr_target, 1); + //log_set_log_level(osmo_stderr_target, 1); + log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_PATH); + log_set_use_color(osmo_stderr_target, 0); + log_set_print_timestamp(osmo_stderr_target, 1); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); if (cat_mask) - log_parse_category_mask(stderr_target, cat_mask); + log_parse_category_mask(osmo_stderr_target, cat_mask); return 0; } @@ -131,5 +131,5 @@ const char *getL1ctlPrimName(uint8_t type) if (type < ARRAY_SIZE(l1ctlPrimNames)) return l1ctlPrimNames[type]; else - return "Unknwon Primitive"; + return "Unknown Primitive"; } diff --git a/src/host/virt_phy/src/shared/osmo_mcast_sock.c b/src/host/virt_phy/src/shared/osmo_mcast_sock.c index 9a713fcf..d3ae4fe9 100644 --- a/src/host/virt_phy/src/shared/osmo_mcast_sock.c +++ b/src/host/virt_phy/src/shared/osmo_mcast_sock.c @@ -1,14 +1,16 @@ #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> -#include <osmocom/core/socket.h> -#include <osmocom/core/select.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <talloc.h> #include <unistd.h> -#include <virtphy/osmo_mcast_sock.h> + +#include <osmocom/core/socket.h> +#include <osmocom/core/select.h> + +#include <osmocom/bb/virtphy/osmo_mcast_sock.h> /* server socket is what we use for transmission. It is not subscribed * to a multicast group or locally bound, but it is just a normal UDP @@ -43,9 +45,7 @@ int mcast_client_sock_setup(struct osmo_fd *ofd, const char *mcast_group, uint16 int rc; unsigned int flags = OSMO_SOCK_F_BIND | OSMO_SOCK_F_NO_MCAST_ALL | OSMO_SOCK_F_UDP_REUSEADDR; - ofd->cb = fd_rx_cb; - ofd->when = BSC_FD_READ; - ofd->data = osmo_fd_data; + osmo_fd_setup(ofd, -1, OSMO_FD_READ, fd_rx_cb, osmo_fd_data, 0); /* Create mcast client socket */ rc = osmo_sock_init_ofd(ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, diff --git a/src/host/virt_phy/src/shared/virtual_um.c b/src/host/virt_phy/src/shared/virtual_um.c index 9415bfbb..e55bb034 100644 --- a/src/host/virt_phy/src/shared/virtual_um.c +++ b/src/host/virt_phy/src/shared/virtual_um.c @@ -19,15 +19,18 @@ * */ +#include <unistd.h> +#include <errno.h> + #include <osmocom/core/select.h> #include <osmocom/core/utils.h> #include <osmocom/core/socket.h> #include <osmocom/core/gsmtap.h> #include <osmocom/core/msgb.h> #include <osmocom/core/talloc.h> -#include <virtphy/osmo_mcast_sock.h> -#include <virtphy/virtual_um.h> -#include <unistd.h> + +#include <osmocom/bb/virtphy/osmo_mcast_sock.h> +#include <osmocom/bb/virtphy/virtual_um.h> /** * Virtual UM interface file descriptor callback. @@ -37,49 +40,71 @@ static int virt_um_fd_cb(struct osmo_fd *ofd, unsigned int what) { struct virt_um_inst *vui = ofd->data; - // check if the read flag is set - if (what & BSC_FD_READ) { - // allocate message buffer of specified size - struct msgb *msg = msgb_alloc(VIRT_UM_MSGB_SIZE, - "Virtual UM Rx"); + if (what & OSMO_FD_READ) { + struct msgb *msg = msgb_alloc(VIRT_UM_MSGB_SIZE, "Virtual UM Rx"); int rc; - // read message from fd in message buffer - rc = mcast_bidir_sock_rx(vui->mcast_sock, msgb_data(msg), - msgb_tailroom(msg)); - // rc is number of bytes actually read + /* read message from fd into message buffer */ + rc = mcast_bidir_sock_rx(vui->mcast_sock, msgb_data(msg), msgb_tailroom(msg)); if (rc > 0) { msgb_put(msg, rc); msg->l1h = msgb_data(msg); - // call the l1 callback function for a received msg + /* call the l1 callback function for a received msg */ vui->recv_cb(vui, msg); - } else { - // TODO: this kind of error handling might be a bit harsh + } else if (rc == 0) { vui->recv_cb(vui, NULL); - // Unregister fd from select loop - osmo_fd_unregister(ofd); - close(ofd->fd); - ofd->fd = -1; - ofd->when = 0; - } + osmo_fd_close(ofd); + } else + perror("Read from multicast socket"); + } return 0; } -struct virt_um_inst *virt_um_init( - void *ctx, char *tx_mcast_group, uint16_t tx_mcast_port, - char *rx_mcast_group, uint16_t rx_mcast_port, - void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg)) +struct virt_um_inst *virt_um_init(void *ctx, char *tx_mcast_group, uint16_t tx_mcast_port, + char *rx_mcast_group, uint16_t rx_mcast_port, int ttl, const char *dev_name, + void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg)) { struct virt_um_inst *vui = talloc_zero(ctx, struct virt_um_inst); - vui->mcast_sock = mcast_bidir_sock_setup(ctx, tx_mcast_group, - tx_mcast_port, rx_mcast_group, rx_mcast_port, 1, - virt_um_fd_cb, vui); + int rc; + + vui->mcast_sock = mcast_bidir_sock_setup(ctx, tx_mcast_group, tx_mcast_port, + rx_mcast_group, rx_mcast_port, 1, virt_um_fd_cb, vui); + if (!vui->mcast_sock) { + perror("Unable to create VirtualUm multicast socket"); + talloc_free(vui); + return NULL; + } vui->recv_cb = recv_cb; + if (ttl >= 0) { + rc = osmo_sock_mcast_ttl_set(vui->mcast_sock->tx_ofd.fd, ttl); + if (rc < 0) { + perror("Cannot set TTL of Virtual Um transmit socket"); + goto out_close; + } + } + + if (dev_name) { + rc = osmo_sock_mcast_iface_set(vui->mcast_sock->tx_ofd.fd, dev_name); + if (rc < 0) { + perror("Cannot bind multicast tx to given device"); + goto out_close; + } + rc = osmo_sock_mcast_iface_set(vui->mcast_sock->rx_ofd.fd, dev_name); + if (rc < 0) { + perror("Cannot bind multicast rx to given device"); + goto out_close; + } + } + return vui; +out_close: + mcast_bidir_sock_close(vui->mcast_sock); + talloc_free(vui); + return NULL; } void virt_um_destroy(struct virt_um_inst *vui) @@ -97,6 +122,8 @@ int virt_um_write_msg(struct virt_um_inst *vui, struct msgb *msg) rc = mcast_bidir_sock_tx(vui->mcast_sock, msgb_data(msg), msgb_length(msg)); + if (rc < 0) + rc = -errno; msgb_free(msg); return rc; diff --git a/src/host/virt_phy/src/virt_l1_model.c b/src/host/virt_phy/src/virt_l1_model.c index 5738bed6..704a54cd 100644 --- a/src/host/virt_phy/src/virt_l1_model.c +++ b/src/host/virt_phy/src/virt_l1_model.c @@ -19,11 +19,12 @@ * */ -#include <virtphy/virt_l1_model.h> -#include <virtphy/l1ctl_sap.h> -#include <virtphy/logging.h> #include <talloc.h> +#include <osmocom/bb/virtphy/virt_l1_model.h> +#include <osmocom/bb/virtphy/l1ctl_sap.h> +#include <osmocom/bb/virtphy/logging.h> + static uint32_t next_ms_nr; struct l1_model_ms *l1_model_ms_init(void *ctx, struct l1ctl_sock_client *lsc, struct virt_um_inst *vui) diff --git a/src/host/virt_phy/src/virt_l1_sched_simple.c b/src/host/virt_phy/src/virt_l1_sched_simple.c index 4737135c..3cad2ce0 100644 --- a/src/host/virt_phy/src/virt_l1_sched_simple.c +++ b/src/host/virt_phy/src/virt_l1_sched_simple.c @@ -18,13 +18,15 @@ * */ -#include <virtphy/virt_l1_sched.h> -#include <osmocom/core/linuxlist.h> -#include <virtphy/virt_l1_model.h> -#include <virtphy/logging.h> #include <time.h> #include <talloc.h> +#include <osmocom/core/linuxlist.h> + +#include <osmocom/bb/virtphy/virt_l1_sched.h> +#include <osmocom/bb/virtphy/virt_l1_model.h> +#include <osmocom/bb/virtphy/logging.h> + /** * @brief Start scheduler thread based on current gsm time from model */ @@ -97,6 +99,7 @@ void virt_l1_sched_execute(struct l1_model_ms *ms, uint32_t fn) ti_next->handler_cb(ms, mi_next->fn, ti_next->ts, ti_next->msg); /* remove handled tdma sched item */ llist_del(&ti_next->tdma_item_entry); + talloc_free(ti_next); } /* remove handled mframe sched item */ llist_del(&mi_next->mframe_item_entry); @@ -129,12 +132,8 @@ void virt_l1_sched_schedule(struct l1_model_ms *ms, struct msgb *msg, uint32_t f /* list did not contain mframe item with needed fn */ mi_fn = talloc_zero(ms, struct virt_l1_sched_mframe_item); mi_fn->fn = fn; - /* need to manually init the struct content.... no so happy */ - mi_fn->tdma_item_list.prev = &mi_fn->tdma_item_list; - mi_fn->tdma_item_list.next = &mi_fn->tdma_item_list; - - /* TODO: check if we get an error if list is empty... */ - llist_add(&mi_fn->mframe_item_entry, mi_next->mframe_item_entry.prev); + INIT_LLIST_HEAD(&mi_fn->tdma_item_list); + llist_add_tail(&mi_fn->mframe_item_entry, &mi_next->mframe_item_entry); } ti_new = talloc_zero(mi_fn, struct virt_l1_sched_tdma_item); diff --git a/src/host/virt_phy/src/virt_prim_data.c b/src/host/virt_phy/src/virt_prim_data.c index 96534aab..d8897915 100644 --- a/src/host/virt_phy/src/virt_prim_data.c +++ b/src/host/virt_phy/src/virt_prim_data.c @@ -16,10 +16,6 @@ * 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 <stdint.h> @@ -31,12 +27,12 @@ #include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/protocol/gsm_08_58.h> #include <osmocom/core/msgb.h> -#include <virtphy/l1ctl_sap.h> -#include <virtphy/virt_l1_sched.h> -#include <virtphy/logging.h> -#include <virtphy/gsmtapl1_if.h> -#include <l1ctl_proto.h> +#include <osmocom/bb/virtphy/l1ctl_sap.h> +#include <osmocom/bb/virtphy/virt_l1_sched.h> +#include <osmocom/bb/virtphy/logging.h> +#include <osmocom/bb/virtphy/gsmtapl1_if.h> +#include <osmocom/bb/l1ctl_proto.h> /** * @brief Handler callback function for DATA request. @@ -47,7 +43,8 @@ static void virt_l1_sched_handler_cb(struct l1_model_ms *ms, uint32_t fn, uint8_t tn, struct msgb * msg) { gsmtapl1_tx_to_virt_um_inst(ms, fn, tn, msg); - l1ctl_tx_data_conf(ms, fn, 0, ms->state.serving_cell.arfcn); + /* FIXME: get ARFCN from msg payload */ + l1ctl_tx_data_conf(ms, fn, 0, ms->state.dedicated.band_arfcn); } /** @@ -73,15 +70,15 @@ void l1ctl_rx_data_req(struct l1_model_ms *ms, struct msgb *msg) rsl_dec_chan_nr(ul->chan_nr, &rsl_chantype, &subslot, ×lot); msg->l2h = data_ind->data; - LOGPMS(DL1P, LOGL_INFO, ms, "Rx L1CTL_DATA_REQ (chan_nr=0x%02x, link_id=0x%02x) %s\n", + LOGPMS(DL1P, LOGL_DEBUG, ms, "Rx L1CTL_DATA_REQ (chan_nr=0x%02x, link_id=0x%02x) %s\n", ul->chan_nr, ul->link_id, osmo_hexdump(msg->l2h, msgb_l2len(msg))); virt_l1_sched_schedule(ms, msg, fn_sched, timeslot, &virt_l1_sched_handler_cb); } void l1ctl_tx_data_ind(struct l1_model_ms *ms, struct msgb *msg, uint16_t arfcn, uint8_t link_id, - uint8_t chan_nr, uint32_t fn, uint8_t snr, - uint8_t signal_dbm, uint8_t num_biterr, uint8_t fire_crc) + uint8_t chan_nr, uint32_t fn, uint8_t snr, + uint8_t rxlev, uint8_t num_biterr, uint8_t fire_crc) { struct msgb *l1ctl_msg = NULL; struct l1ctl_data_ind * l1di; @@ -97,7 +94,7 @@ void l1ctl_tx_data_ind(struct l1_model_ms *ms, struct msgb *msg, uint16_t arfcn, l1dl->chan_nr = chan_nr; l1dl->frame_nr = htonl(fn); l1dl->snr = snr; - l1dl->rx_level = signal_dbm; + l1dl->rx_level = rxlev; l1dl->num_biterr = 0; /* no biterrors */ l1dl->fire_crc = 0; @@ -105,7 +102,7 @@ void l1ctl_tx_data_ind(struct l1_model_ms *ms, struct msgb *msg, uint16_t arfcn, memcpy(l1di->data, msgb_data(msg), msgb_length(msg)); - LOGPMS(DL1P, LOGL_INFO, ms, "TX L1CTL_DATA_IND (link_id=0x%02x) %s\n", link_id, + LOGPMS(DL1P, LOGL_DEBUG, ms, "TX L1CTL_DATA_IND (link_id=0x%02x) %s\n", link_id, osmo_hexdump(msgb_data(msg), msgb_length(msg))); l1ctl_sap_tx_to_l23_inst(ms, l1ctl_msg); } @@ -123,6 +120,6 @@ void l1ctl_tx_data_conf(struct l1_model_ms *ms, uint32_t fn, uint16_t snr, uint1 struct msgb * l1ctl_msg; l1ctl_msg = l1ctl_create_l2_msg(L1CTL_DATA_CONF, fn, snr, arfcn); /* send confirm to layer23 */ - LOGPMS(DL1P, LOGL_INFO, ms, "Tx L1CTL_DATA_CONF\n"); + LOGPMS(DL1P, LOGL_DEBUG, ms, "Tx L1CTL_DATA_CONF\n"); l1ctl_sap_tx_to_l23_inst(ms, l1ctl_msg); } diff --git a/src/host/virt_phy/src/virt_prim_fbsb.c b/src/host/virt_phy/src/virt_prim_fbsb.c index c14a4485..d012036f 100644 --- a/src/host/virt_phy/src/virt_prim_fbsb.c +++ b/src/host/virt_phy/src/virt_prim_fbsb.c @@ -15,10 +15,6 @@ * 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 <stdint.h> @@ -29,11 +25,12 @@ #include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/protocol/gsm_08_58.h> #include <osmocom/core/msgb.h> -#include <virtphy/l1ctl_sap.h> -#include <virtphy/virt_l1_sched.h> #include <osmocom/core/gsmtap.h> -#include <virtphy/logging.h> -#include <l1ctl_proto.h> + +#include <osmocom/bb/virtphy/l1ctl_sap.h> +#include <osmocom/bb/virtphy/virt_l1_sched.h> +#include <osmocom/bb/virtphy/logging.h> +#include <osmocom/bb/l1ctl_proto.h> static uint16_t sync_count = 0; diff --git a/src/host/virt_phy/src/virt_prim_pdch.c b/src/host/virt_phy/src/virt_prim_pdch.c new file mode 100644 index 00000000..3fa7977b --- /dev/null +++ b/src/host/virt_phy/src/virt_prim_pdch.c @@ -0,0 +1,108 @@ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de> + * + * 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. + * + */ + +#include <stdint.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/bb/virtphy/l1ctl_sap.h> +#include <osmocom/bb/virtphy/virt_l1_sched.h> +#include <osmocom/bb/virtphy/gsmtapl1_if.h> +#include <osmocom/bb/virtphy/logging.h> + +#include <osmocom/bb/l1ctl_proto.h> +#include <osmocom/bb/l1gprs.h> + +void l1ctl_rx_gprs_uldl_tbf_cfg_req(struct l1_model_ms *ms, struct msgb *msg) +{ + const struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + + if (OSMO_UNLIKELY(ms->gprs == NULL)) { + LOGPMS(DL1C, LOGL_ERROR, ms, "l1gprs is not initialized\n"); + return; + } + + msg->l1h = msgb_pull(msg, sizeof(*l1h)); + + if (l1h->msg_type == L1CTL_GPRS_UL_TBF_CFG_REQ) + l1gprs_handle_ul_tbf_cfg_req(ms->gprs, msg); + else + l1gprs_handle_dl_tbf_cfg_req(ms->gprs, msg); +} + +void l1ctl_rx_gprs_ul_block_req(struct l1_model_ms *ms, struct msgb *msg) +{ + const struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1gprs_prim_ul_block_req req; + + if (OSMO_UNLIKELY(ms->gprs == NULL)) { + LOGPMS(DL1P, LOGL_ERROR, ms, "l1gprs is not initialized\n"); + msgb_free(msg); + return; + } + + msg->l1h = (void *)l1h->data; + if (l1gprs_handle_ul_block_req(ms->gprs, &req, msg) != 0) { + msgb_free(msg); + return; + } + msg->l2h = (void *)&req.data[0]; + + virt_l1_sched_schedule(ms, msg, req.hdr.fn, req.hdr.tn, + &gsmtapl1_tx_to_virt_um_inst); +} + +void l1ctl_tx_gprs_dl_block_ind(struct l1_model_ms *ms, const struct msgb *msg, + uint32_t fn, uint8_t tn, uint8_t rxlev) +{ + struct l1gprs_prim_dl_block_ind ind; + struct msgb *nmsg; + uint8_t usf = 0xff; + + if (ms->gprs == NULL) + return; + + ind = (struct l1gprs_prim_dl_block_ind) { + .hdr = { + .fn = fn, + .tn = tn, + }, + .meas = { + .ber10k = 0, /* perfect Um, no errors */ + .ci_cb = 180, /* 18 dB */ + .rx_lev = rxlev, + }, + .data = msgb_data(msg), + .data_len = msgb_length(msg), + }; + + nmsg = l1gprs_handle_dl_block_ind(ms->gprs, &ind, &usf); + if (nmsg != NULL) + l1ctl_sap_tx_to_l23_inst(ms, nmsg); + /* Every fn % 13 == 12 we have either a PTCCH or an IDLE slot, thus + * every fn % 13 == 8 we add 5 frames, or 4 frames othrwise. The + * resulting value is first fn of the next block. */ + const uint32_t rts_fn = GSM_TDMA_FN_SUM(fn, (fn % 13 == 8) ? 5 : 4); + nmsg = l1gprs_handle_rts_ind(ms->gprs, rts_fn, tn, usf); + if (nmsg != NULL) + l1ctl_sap_tx_to_l23_inst(ms, nmsg); +} diff --git a/src/host/virt_phy/src/virt_prim_pm.c b/src/host/virt_phy/src/virt_prim_pm.c index 46370138..5883bbbe 100644 --- a/src/host/virt_phy/src/virt_prim_pm.c +++ b/src/host/virt_phy/src/virt_prim_pm.c @@ -15,10 +15,6 @@ * 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 <stdint.h> @@ -26,14 +22,15 @@ #include <string.h> #include <stdlib.h> -#include <osmocom/gsm/gsm_utils.h> -#include <osmocom/gsm/protocol/gsm_08_58.h> #include <osmocom/core/msgb.h> -#include <virtphy/l1ctl_sap.h> -#include <virtphy/virt_l1_sched.h> #include <osmocom/core/gsmtap.h> -#include <virtphy/logging.h> -#include <l1ctl_proto.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +#include <osmocom/bb/virtphy/l1ctl_sap.h> +#include <osmocom/bb/virtphy/virt_l1_sched.h> +#include <osmocom/bb/virtphy/logging.h> +#include <osmocom/bb/l1ctl_proto.h> /** * @brief Change the signal strength for a given arfcn. @@ -84,38 +81,49 @@ void l1ctl_rx_pm_req(struct l1_model_ms *ms, struct msgb *msg) struct l1_state_ms *l1s = &ms->state; struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_pm_req *pm_req = (struct l1ctl_pm_req *) l1h->data; - struct msgb *resp_msg = l1ctl_msgb_alloc(L1CTL_PM_CONF); - uint16_t arfcn_next; - /* convert to host order */ - pm_req->range.band_arfcn_from = ntohs(pm_req->range.band_arfcn_from); - pm_req->range.band_arfcn_to = ntohs(pm_req->range.band_arfcn_to); + /* just parse the data from the request here */ + l1s->pm.req.band_arfcn_from = ntohs(pm_req->range.band_arfcn_from); + l1s->pm.req.band_arfcn_to = ntohs(pm_req->range.band_arfcn_to); - LOGPMS(DL1C, LOGL_INFO, ms, "Rx L1CTL_PM_REQ TYPE=%u, FROM=%d, TO=%d\n", - pm_req->type, pm_req->range.band_arfcn_from, pm_req->range.band_arfcn_to); + LOGPMS(DL1C, LOGL_DEBUG, ms, "Rx L1CTL_PM_REQ TYPE=%u, FROM=%d, TO=%d\n", + pm_req->type, l1s->pm.req.band_arfcn_from, l1s->pm.req.band_arfcn_to); + + /* generating the response will happen delayed in a timer, as otherwise + * we will respond too fast, and 'mobile' will run havoc in a busy loop issuing + * endless PM_REQ until a cell eventually isfound */ + osmo_timer_schedule(&l1s->pm.req.timer, 0, 300000); +} + +static void pm_conf_timer_cb(void *data) +{ + struct l1_model_ms *ms = data; + struct l1_state_ms *l1s = &ms->state; + struct msgb *resp_msg = l1ctl_msgb_alloc(L1CTL_PM_CONF); + uint16_t arfcn_next; - for (arfcn_next = pm_req->range.band_arfcn_from; - arfcn_next <= pm_req->range.band_arfcn_to; ++arfcn_next) { + for (arfcn_next = l1s->pm.req.band_arfcn_from; + arfcn_next <= l1s->pm.req.band_arfcn_to; ++arfcn_next) { struct l1ctl_pm_conf *pm_conf = (struct l1ctl_pm_conf *) msgb_put(resp_msg, sizeof(*pm_conf)); pm_conf->band_arfcn = htons(arfcn_next); /* set min and max to the value calculated for that * arfcn (IGNORE UPLINKK AND PCS AND OTHER FLAGS) */ pm_conf->pm[0] = dbm2rxlev(l1s->pm.meas.arfcn_sig_lev_dbm[arfcn_next & ARFCN_NO_FLAGS_MASK]); pm_conf->pm[1] = dbm2rxlev(l1s->pm.meas.arfcn_sig_lev_dbm[arfcn_next & ARFCN_NO_FLAGS_MASK]); - if (arfcn_next == pm_req->range.band_arfcn_to) { + if (arfcn_next == l1s->pm.req.band_arfcn_to) { struct l1ctl_hdr *resp_l1h = msgb_l1(resp_msg); resp_l1h->flags |= L1CTL_F_DONE; } - /* no more space to hold mor pm info in msgb, flush to l23 */ + /* no more space to hold more pm info in msgb, flush to l23 */ if (msgb_tailroom(resp_msg) < sizeof(*pm_conf)) { - LOGPMS(DL1C, LOGL_INFO, ms, "Tx L1CTL_PM_CONF\n"); + LOGPMS(DL1C, LOGL_DEBUG, ms, "Tx L1CTL_PM_CONF\n"); l1ctl_sap_tx_to_l23_inst(ms, resp_msg); resp_msg = l1ctl_msgb_alloc(L1CTL_PM_CONF); } } /* transmit the remaining part of pm response to l23 */ if (resp_msg) { - LOGPMS(DL1C, LOGL_INFO, ms, "Tx L1CTL_PM_CONF\n"); + LOGPMS(DL1C, LOGL_DEBUG, ms, "Tx L1CTL_PM_CONF\n"); l1ctl_sap_tx_to_l23_inst(ms, resp_msg); } } @@ -137,6 +145,7 @@ void prim_pm_init(struct l1_model_ms *model) l1s->pm.meas.arfcn_sig_lev_timers[i].cb = prim_pm_timer_cb; l1s->pm.meas.arfcn_sig_lev_timers[i].data = &l1s->pm.meas.arfcn_sig_lev_dbm[i]; } + osmo_timer_setup(&l1s->pm.req.timer, pm_conf_timer_cb, model); } void prim_pm_exit(struct l1_model_ms *model) @@ -146,4 +155,5 @@ void prim_pm_exit(struct l1_model_ms *model) for (i = 0; i < 1024; ++i) osmo_timer_del(&l1s->pm.meas.arfcn_sig_lev_timers[i]); + osmo_timer_del(&l1s->pm.req.timer); } diff --git a/src/host/virt_phy/src/virt_prim_rach.c b/src/host/virt_phy/src/virt_prim_rach.c index 94076cf9..d12e63c6 100644 --- a/src/host/virt_phy/src/virt_prim_rach.c +++ b/src/host/virt_phy/src/virt_prim_rach.c @@ -16,10 +16,6 @@ * 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 <stdint.h> @@ -27,16 +23,17 @@ #include <string.h> #include <stdlib.h> +#include <osmocom/core/msgb.h> #include <osmocom/gsm/rsl.h> #include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/gsm0502.h> #include <osmocom/gsm/protocol/gsm_08_58.h> -#include <osmocom/core/msgb.h> -#include <virtphy/l1ctl_sap.h> -#include <virtphy/virt_l1_sched.h> -#include <virtphy/logging.h> -#include <virtphy/gsmtapl1_if.h> -#include <l1ctl_proto.h> +#include <osmocom/bb/virtphy/l1ctl_sap.h> +#include <osmocom/bb/virtphy/virt_l1_sched.h> +#include <osmocom/bb/virtphy/logging.h> +#include <osmocom/bb/virtphy/gsmtapl1_if.h> +#include <osmocom/bb/l1ctl_proto.h> /* use if we have a combined uplink (RACH, SDCCH, ...) (see * http://www.rfwireless-world.com/Terminology/GSM-combined-channel-configuration.html) @@ -79,36 +76,37 @@ void l1ctl_rx_rach_req(struct l1_model_ms *ms, struct msgb *msg) struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; struct l1ctl_rach_req *rach_req = (struct l1ctl_rach_req *) ul->payload; uint32_t fn_sched; - uint8_t ts = 1; /* FIXME mostly, ts 1 is used for rach, where can i get that info? System info? */ uint16_t offset = ntohs(rach_req->offset); LOGPMS(DL1C, LOGL_INFO, ms, "Rx L1CTL_RACH_REQ (ra=0x%02x, offset=%d combined=%d)\n", rach_req->ra, offset, rach_req->combined); - if (rach_req->ra == 0x03) - fn_sched = 42; - /* set ra data to msg (8bits, the 11bit option is not used for GSM) */ msg->l2h = msgb_put(msg, sizeof(uint8_t)); *msg->l2h = rach_req->ra; - /* chan_nr need to be encoded here, as it is not set by l23 for - * the rach request, but needed by virt um */ - ul->chan_nr = rsl_enc_chan_nr(RSL_CHAN_RACH, 0, ts); - ul->link_id = LID_DEDIC; + /* use the indicated RSL chan_nr/link_id, if provided */ + if (ul->chan_nr == 0x00) { + LOGPMS(DL1C, LOGL_NOTICE, ms, + "The UL info header is empty, assuming RACH is on TS0\n"); + ul->chan_nr = RSL_CHAN_RACH; + ul->link_id = LID_DEDIC; + } /* sched fn calculation if we have a combined ccch channel configuration */ if (rach_req->combined) { /* add elapsed RACH slots to offset */ offset += t3_to_rach_comb[l1s->current_time.t3]; /* offset is the number of RACH slots in the future */ - fn_sched = l1s->current_time.fn - l1s->current_time.t3; + fn_sched = GSM_TDMA_FN_SUB(l1s->current_time.fn, l1s->current_time.t3); fn_sched += offset / 27 * 51; fn_sched += rach_to_t3_comb[offset % 27]; + fn_sched %= GSM_TDMA_HYPERFRAME; } else - fn_sched = l1s->current_time.fn + offset; + fn_sched = GSM_TDMA_FN_SUM(l1s->current_time.fn, offset); - virt_l1_sched_schedule(ms, msg, fn_sched, ts, &virt_l1_sched_handler_cb); + virt_l1_sched_schedule(ms, msg, fn_sched, ul->chan_nr & 0x07, + &virt_l1_sched_handler_cb); } /** diff --git a/src/host/virt_phy/src/virt_prim_traffic.c b/src/host/virt_phy/src/virt_prim_traffic.c index 5f6b273b..778d67a3 100644 --- a/src/host/virt_phy/src/virt_prim_traffic.c +++ b/src/host/virt_phy/src/virt_prim_traffic.c @@ -16,10 +16,6 @@ * 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 <stdint.h> @@ -27,16 +23,16 @@ #include <string.h> #include <stdlib.h> +#include <osmocom/core/msgb.h> #include <osmocom/gsm/rsl.h> #include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/protocol/gsm_08_58.h> -#include <osmocom/core/msgb.h> -#include <virtphy/l1ctl_sap.h> -#include <virtphy/virt_l1_sched.h> -#include <virtphy/logging.h> -#include <virtphy/gsmtapl1_if.h> -#include <l1ctl_proto.h> +#include <osmocom/bb/virtphy/l1ctl_sap.h> +#include <osmocom/bb/virtphy/virt_l1_sched.h> +#include <osmocom/bb/virtphy/logging.h> +#include <osmocom/bb/virtphy/gsmtapl1_if.h> +#include <osmocom/bb/l1ctl_proto.h> /** * @brief Handler callback function for TRAFFIC request. @@ -47,7 +43,8 @@ static void virt_l1_sched_handler_cb(struct l1_model_ms *ms, uint32_t fn, uint8_t tn, struct msgb * msg) { gsmtapl1_tx_to_virt_um_inst(ms, fn, tn, msg); - l1ctl_tx_traffic_conf(ms, fn, 0, ms->state.serving_cell.arfcn); + /* FIXME: get ARFCN from msg payload */ + l1ctl_tx_traffic_conf(ms, fn, 0, ms->state.dedicated.band_arfcn); } /** @@ -78,13 +75,14 @@ void l1ctl_rx_traffic_req(struct l1_model_ms *ms, struct msgb *msg) } void l1ctl_tx_traffic_ind(struct l1_model_ms *ms, struct msgb *msg, uint16_t arfcn, uint8_t link_id, - uint8_t chan_nr, uint32_t fn, uint8_t snr, uint8_t signal_dbm, + uint8_t chan_nr, uint32_t fn, uint8_t snr, uint8_t rxlev, uint8_t num_biterr, uint8_t fire_crc) { struct msgb *l1ctl_msg = NULL; struct l1ctl_traffic_ind * l1ti; struct l1ctl_info_dl * l1dl; - uint8_t *frame, frame_len; + uint8_t *frame; + int frame_len; uint8_t rsl_chan_type, subchan, timeslot; l1ctl_msg = l1ctl_msgb_alloc(L1CTL_TRAFFIC_IND); l1dl = (struct l1ctl_info_dl *) msgb_put(l1ctl_msg, sizeof(*l1dl)); @@ -97,15 +95,20 @@ void l1ctl_tx_traffic_ind(struct l1_model_ms *ms, struct msgb *msg, uint16_t arf l1dl->chan_nr = chan_nr; l1dl->frame_nr = htonl(fn); l1dl->snr = snr; - l1dl->rx_level = signal_dbm; + l1dl->rx_level = rxlev; l1dl->num_biterr = 0; /* no biterrors */ l1dl->fire_crc = 0; - /* TODO: traffic decoding and decryption */ - - frame_len = msgb_length(msg); + /* The first byte indicates the type of voice frame (enum gsmtap_um_voice_type), + * which we simply ignore here and pass on the frame without that byte. + * TODO: Check for consistency with ms->state.tch_mode ? */ + frame_len = msgb_length(msg) - 1; + if (frame_len < 0) { + msgb_free(l1ctl_msg); + return; + } frame = (uint8_t *) msgb_put(l1ctl_msg, frame_len); - memcpy(frame, msgb_data(msg), frame_len); + memcpy(frame, msgb_data(msg)+1, frame_len); DEBUGPMS(DL1P, ms, "Tx L1CTL_TRAFFIC_IND (chan_nr=0x%02x, link_id=0x%02x)\n", chan_nr, link_id); l1ctl_sap_tx_to_l23_inst(ms, l1ctl_msg); diff --git a/src/host/virt_phy/src/virtphy.c b/src/host/virt_phy/src/virtphy.c index d0a2ddbf..0aa21adc 100644 --- a/src/host/virt_phy/src/virtphy.c +++ b/src/host/virt_phy/src/virtphy.c @@ -20,10 +20,6 @@ * */ -#include <osmocom/core/msgb.h> -#include <osmocom/core/select.h> -#include <osmocom/core/gsmtap.h> -#include <osmocom/core/application.h> #include <stdint.h> #include <string.h> #include <stdio.h> @@ -31,15 +27,22 @@ #include <getopt.h> #include <errno.h> #include <signal.h> -#include <virtphy/virtual_um.h> -#include <virtphy/l1ctl_sock.h> -#include <virtphy/virt_l1_model.h> -#include <virtphy/l1ctl_sap.h> -#include <virtphy/gsmtapl1_if.h> -#include <virtphy/logging.h> -#include <virtphy/virt_l1_sched.h> -#define DEFAULT_LOG_MASK "DL1C,2:DL1P,2:DVIRPHY,2:DMAIN,1" +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/core/application.h> + +#include <osmocom/bb/virtphy/virtual_um.h> +#include <osmocom/bb/virtphy/l1ctl_sock.h> +#include <osmocom/bb/virtphy/virt_l1_model.h> +#include <osmocom/bb/virtphy/l1ctl_sap.h> +#include <osmocom/bb/virtphy/gsmtapl1_if.h> +#include <osmocom/bb/virtphy/logging.h> +#include <osmocom/bb/virtphy/virt_l1_sched.h> +#include <osmocom/bb/l1gprs.h> + +#define DEFAULT_LOG_MASK "DL1C,2:DL1P,2:DVIRPHY,2:DGPRS,1:DMAIN,1" /* this exists once in the program, and contains the state that we * only keep once: L1CTL server socket, GSMTAP/VirtUM socket */ @@ -59,13 +62,15 @@ static char *log_mask = DEFAULT_LOG_MASK; static char *l1ctl_sock_path = L1CTL_SOCK_PATH; static char *arfcn_sig_lev_red_mask = NULL; static char *pm_timeout = NULL; +static char *mcast_netdev = NULL; +static int mcast_ttl = -1; -static void print_usage() +static void print_usage(void) { printf("Usage: virtphy\n"); } -static void print_help() +static void print_help(void) { printf(" Some useful help...\n"); printf(" -h --help This text.\n"); @@ -76,6 +81,8 @@ static void print_help() printf(" -s --l1ctl-sock l1ctl socket path path.\n"); printf(" -r --arfcn-sig-lev-red reduce signal level (e.g. 666,12:888,43:176,22).\n"); printf(" -t --pm-timeout power management timeout.\n"); + printf(" -T --mcast-ttl TTL set TTL of Virtual Um GSMTAP multicast frames\n"); + printf(" -D --mcast-deav NETDEV bind to given network device for Virtual Um\n"); } static void handle_options(int argc, char **argv) @@ -91,9 +98,11 @@ static void handle_options(int argc, char **argv) {"l1ctl-sock", required_argument, 0, 's'}, {"arfcn-sig-lev-red", required_argument, 0, 'r'}, {"pm-timeout", required_argument, 0, 't'}, + {"mcast-ttl", required_argument, 0, 'T'}, + {"mcast-dev", required_argument, 0, 'D'}, {0, 0, 0, 0}, }; - c = getopt_long(argc, argv, "hz:y:x:d:s:r:t:", long_options, + c = getopt_long(argc, argv, "hz:y:x:d:s:r:t:T:D:", long_options, &option_index); if (c == -1) break; @@ -124,6 +133,12 @@ static void handle_options(int argc, char **argv) case 't': pm_timeout = optarg; break; + case 'T': + mcast_ttl = atoi(optarg); + break; + case 'D': + mcast_netdev = optarg; + break; default: break; } @@ -227,12 +242,13 @@ int main(int argc, char *argv[]) /* init loginfo */ handle_options(argc, argv); - ms_log_init(log_mask); + ms_log_init(tall_vphy_ctx, log_mask); + l1gprs_logging_init(DGPRS); LOGP(DVIRPHY, LOGL_INFO, "Virtual physical layer starting up...\n"); - g_vphy.virt_um = virt_um_init(tall_vphy_ctx, ul_tx_grp, port, dl_rx_grp, port, - gsmtapl1_rx_from_virt_um_inst_cb); + g_vphy.virt_um = virt_um_init(tall_vphy_ctx, ul_tx_grp, port, dl_rx_grp, port, mcast_ttl, + mcast_netdev, gsmtapl1_rx_from_virt_um_inst_cb); g_vphy.l1ctl_sock = l1ctl_sock_init(tall_vphy_ctx, l1ctl_sap_rx_from_l23_inst_cb, l1ctl_accept_cb, l1ctl_close_cb, l1ctl_sock_path); |