Compare commits

...

75 Commits

Author SHA1 Message Date
Andreas Eversberg 91ceb56495 Add dial tone detection to disable echo suppressor 2023-03-05 16:39:31 +01:00
Andreas Eversberg 7aa290154a Fixes missing event in LCP FSM 2023-03-05 16:39:28 +01:00
Andreas Eversberg 5268c0fc9b Added echo suppressor option 2023-03-05 16:39:26 +01:00
Andreas Eversberg c2bf2598b1 Add echo suppressor along with echo canceller 2023-03-05 16:39:22 +01:00
Andreas Eversberg f489ce381c Improve management process, to handle startup/blocking/unblocking correctly
This is a big one:

Startup process was hacked. Several state machines do the startup process now:
- system startup
- PSTN startup
- PSTN restart
- 5 state machines to do multiple port unblocking/blocking
2023-03-05 16:39:18 +01:00
Andreas Eversberg dd23410c52 Pulsed signal can have only one byte as value (3 byte IE) 2023-03-03 09:03:43 +01:00
Andreas Eversberg aa07ba4a48 VTY: Show single port; Use 'singular' for ports and links 2023-03-03 09:03:41 +01:00
Andreas Eversberg 40fddb83a7 Send to E1, only if layer 1 is active
This prevents filling the TX queue inside libosmo-abis.
2023-03-03 09:03:38 +01:00
Andreas Eversberg a53c366853 Update configure.ac to current release of libosmocore/libosmo-abis 2023-03-03 09:03:36 +01:00
Andreas Eversberg 56467e746e Link structure of e1inp_line directly with V5 link
This way there is no lookup required, to get e1inp_line from v5
link and vice versa.

The E1 line is configured whenever it is added to the V5 link. This
happens when entering it in the VTY or loading the config. Also the
signaling channel is opened.

If E1 line is removed from the V5 link, all channels are closed.
2023-03-03 09:03:33 +01:00
Andreas Eversberg ebfab5792d Dynamically open and close b-channels depending on the usage 2023-02-24 15:02:42 +01:00
Andreas Eversberg a4483db3af Added echo cancelation option 2023-02-24 15:02:38 +01:00
Andreas Eversberg e59752a1cb Add libg711 to transcode G.711 audio 2023-02-04 16:49:30 +01:00
Andreas Eversberg c6d0c2452c Added libecho, the line-echo-canceler from spandsp 2023-02-04 16:49:26 +01:00
Andreas Eversberg 8b00969b4a Check double use of E1 interface and notify, if not defined
E1 interface may be used by one V5 link only.
Also notify user, if not created and application must be restarted when
created.
2023-01-28 21:42:56 +01:00
Andreas Eversberg 3aee692b1c Added GSMTAP IP option and fixed command line help output format 2023-01-24 18:33:04 +01:00
Andreas Eversberg 687e963fd4 Updated ph-socket interface 2023-01-24 18:07:52 +01:00
Andreas Eversberg 905a1b22cf Add test function to test Sa 7 bit setting and reporting 2023-01-15 20:00:59 +01:00
Andreas Eversberg 85fe6f55bb Add graph of protocol entities 2023-01-15 20:00:58 +01:00
Andreas Eversberg 61d531cedc Work on Makefile.am 2023-01-15 20:00:56 +01:00
Andreas Eversberg 9c0893403d Working on main.c 2023-01-15 20:00:53 +01:00
Andreas Eversberg 36dc03731a Introduce layer1.c to process layer1 data flow and signals 2023-01-15 20:00:52 +01:00
Andreas Eversberg ef33793758 Work on ph_socket.c 2023-01-15 20:00:46 +01:00
Andreas Eversberg 5d972a0077 Work on VTY 2023-01-15 20:00:43 +01:00
Andreas Eversberg a260178387 Work on v5x_protocol.c 2023-01-15 20:00:39 +01:00
Andreas Eversberg 3ebe29e28f Work on v5x_data.c and v5x_internal.h 2023-01-15 20:00:37 +01:00
Andreas Eversberg 5addae556a Work on v5x_le_ctrl_fsm.c 2023-01-15 20:00:34 +01:00
Andreas Eversberg ba46786de7 Work on v5x_le_port_fsm.c 2023-01-15 20:00:32 +01:00
Andreas Eversberg 58937c291b Work on management
Also merged provisioning and management into one source file.
2023-01-15 20:00:29 +01:00
Andreas Eversberg fbb97bdd73 Work on v5x_le_pstn_fsm.c 2023-01-15 20:00:22 +01:00
Andreas Eversberg 920fada1bc Completed implementation of v52_le_pp_fsm.c 2023-01-15 20:00:18 +01:00
Andreas Eversberg 276b423051 Completed implementation of v52_le_bcc_fsm.c 2023-01-15 20:00:17 +01:00
Andreas Eversberg 4994a59962 Completed implementation of v5x_l1_fsm.c 2023-01-15 20:00:14 +01:00
Andreas Eversberg 852990f2e3 Completed implementation of V52_le_lcp_fsm.c 2023-01-08 10:17:58 +01:00
Andreas Eversberg 6a4a58d4b1 Work on logging.c 2023-01-08 10:17:56 +01:00
Andreas Eversberg 513f3bba5a Fixes for lapv5 2023-01-08 10:17:53 +01:00
Andreas Eversberg aeae94c547 Added functions to create and destroy V5.1/V5.2 interface with VTY 2022-12-29 17:14:20 +01:00
Andreas Eversberg 2a23a1c9cf Added destroy function for interface 2022-12-29 17:14:19 +01:00
Andreas Eversberg 65dac95ff0 Move main.c to src/ 2022-12-29 17:14:18 +01:00
Andreas Eversberg b64a17c748 Move v5x_l2_mgmt and v51_le_provisioning to src/
New names are v5x_le_management and v5x_le_provisioning
2022-12-29 17:14:15 +01:00
Andreas Eversberg 7b4561f7cf Move v52_lcp_fsm.c to src/ 2022-12-29 17:14:13 +01:00
Andreas Eversberg ca0d2f6091 Move v5x_le_pstn_fsm.h to src/v5x_le_pstn_fsm.h
Also changed serveral names of functions/structures/defintions.
2022-12-29 17:14:11 +01:00
Andreas Eversberg ff619cc54b Move v52_le_user_port_fsm to src/v5x_le_port_fsm
Also changed serveral names of functions/structures/defintions.
2022-12-29 17:14:09 +01:00
Andreas Eversberg 595d1091bc Move v51_le_ctrl to src/v5x_le_ctrl_fsm
Also changed serveral names of functions/structures/defintions.
2022-12-29 17:14:08 +01:00
Andreas Eversberg f457d272b6 Move v51_l1_fsm.c to src/v5x_l1_fsm.c 2022-12-29 17:14:05 +01:00
Andreas Eversberg 3cabd7f1a6 Move v5x_protocol to src/ 2022-12-29 17:14:03 +01:00
Andreas Eversberg ff5e1d4947 Move lapv5 to src/ 2022-12-29 17:14:00 +01:00
Andreas Eversberg dec8a8adea Move ph_socket to src/ 2022-12-29 17:13:59 +01:00
Andreas Eversberg d91fd97210 Move logging to src/ 2022-12-29 17:13:57 +01:00
Andreas Eversberg 33eea99531 Move v5x_data.c and v5x_internal.h to src/
Also split VTY code into v5x_vty.c and v5x_vty.h

Some VTY code from main.c is moved also.
2022-12-29 17:13:55 +01:00
Andreas Eversberg 83d8cbdd3a Prepared autoconf/automake 2022-12-29 17:13:53 +01:00
Andreas Eversberg 3586180ae2 Jolly's hack on Makefile 2022-12-29 17:13:50 +01:00
Andreas Eversberg f5e54dfa77 Hacking on main 2022-12-29 17:13:48 +01:00
Andreas Eversberg 0325fbedc1 Work on v5x_protocol 2022-12-29 17:13:47 +01:00
Andreas Eversberg afa29770cc Work on v5x_data.c and v5x_internal.h 2022-12-29 17:13:44 +01:00
Andreas Eversberg a8428e073d Work on lapv5 2022-12-29 17:13:43 +01:00
Andreas Eversberg 331c19228e Add PSTN protocol 2022-12-29 17:13:41 +01:00
Andreas Eversberg e066033770 Add L2 management process 2022-12-29 17:13:39 +01:00
Andreas Eversberg a02b14892a Add PH-socket interface
This interface can be used to connect to ISDN/PSTN stacks. It was ported
to work with libosmocore. Still it uses own implementation of socket
handling, because libosmocore does not support abstract socket names
yet.
2022-12-29 17:13:38 +01:00
Andreas Eversberg eeb77df89f Fixed and completed v52_le_user_port_fsm.c for both ISDN and PSTN ports 2022-12-29 17:13:35 +01:00
Andreas Eversberg ae1a7de2b8 Serveral fixes to v51_le_ctrl.c 2022-12-29 17:13:33 +01:00
Andreas Eversberg 537301f656 Fixed primitive handling in v5x_protocol.c 2022-12-29 17:13:30 +01:00
Andreas Eversberg d9cea69a90 Add enum type for user port data structure (PSTN or ISDN) 2022-12-29 17:13:29 +01:00
Andreas Eversberg ee6d366feb Add minimal provisioning protocol (Clause 14.5) 2022-12-29 17:13:27 +01:00
Andreas Eversberg ffeded74af Add more log levels to logging 2022-12-29 17:13:24 +01:00
Harald Welte 65dfe9080b Iniatial gsmtap support
This requires a wireshark with support for passing the new gsmtap V5
format into the existing wireshark dissectors for V5.
2022-12-29 17:13:23 +01:00
Andreas Eversberg 5af684bed2 Work on control protocols 2022-12-29 17:13:20 +01:00
Andreas Eversberg fe6a4b91f8 Hacking on main.c 2022-12-29 17:13:19 +01:00
Andreas Eversberg 1560a36dfc Work on v5x_data.c and v5x_internal.h 2022-12-29 17:13:18 +01:00
Andreas Eversberg 5fd7b682f4 Several fixes for lapv5.c 2022-12-29 17:13:15 +01:00
Andreas Eversberg a77eb4020f Work on v5x_protocol.c 2022-12-29 17:13:14 +01:00
Andreas Eversberg 0bb6303c34 Changes to Makefile 2022-12-29 17:13:13 +01:00
Andreas Eversberg 04e99eb55f Rename SAP functions of lapv5 to be more conclusive 2022-12-29 17:13:10 +01:00
Andreas Eversberg 4f9292b698 Add logging.c/logging.h 2022-12-29 17:13:09 +01:00
Andreas Eversberg 10b269d24c Removed config file 2022-12-29 17:13:03 +01:00
66 changed files with 17046 additions and 2669 deletions

46
.gitignore vendored Normal file
View File

@ -0,0 +1,46 @@
Makefile
Makefile.in
.deps
.libs
*.o
*.lo
*.la
*.pc
aclocal.m4
autom4te.cache
config.h*
config.sub
config.log
config.status
config.guess
configure
compile
depcomp
missing
ltmain.sh
install-sh
stamp-h1
#libosmo-abis-*
tests/*_test
*~
# libtool and e.g. arm-poky-linux-gnueabi-libtool
*libtool
.tarball-version
.version
.dirstamp
# tests
tests/atconfig
tests/package.m4
tests/testsuite
tests/testsuite.log
# vi/vim files
*.sw?
src/libg711/libg711.a
src/libecho/libecho.a
src/osmo-v5-le
tests/answer_detect

View File

@ -1,11 +0,0 @@
LIBS=-lasan -ltalloc -losmocore -losmovty -losmogsm -losmoctrl -losmoabis
CFLAGS=-Wall -DPACKAGE_VERSION=\"0.0\"
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $^
v5le: main.o v5x_data.o lapv5.o
$(CC) -o $@ $^ $(LIBS)
clean:
@rm v5le *.o

8
Makefile.am Normal file
View File

@ -0,0 +1,8 @@
ACLOCAL_AMFLAGS = -I m4
AM_CPPFLAGS = $(all_includes)
SUBDIRS = src tests
dist-hook:
echo $(VERSION) > $(distdir)/.tarball-version

116
configure.ac Normal file
View File

@ -0,0 +1,116 @@
AC_INIT([osmo-v5],
m4_esyscmd([./git-version-gen .tarball-version]),
[openbsc@lists.osmocom.org])
dnl *This* is the root dir, even if an install-sh exists in ../ or ../../
AC_CONFIG_AUX_DIR([.])
AM_INIT_AUTOMAKE([foreign dist-bzip2 no-dist-gzip 1.6 subdir-objects])
AC_CONFIG_TESTDIR(tests)
CFLAGS="$CFLAGS -std=gnu11"
dnl kernel style compile messages
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl include release helper
RELMAKE='-include osmo-release.mk'
AC_SUBST([RELMAKE])
dnl checks for programs
AC_PROG_MAKE_SET
AC_PROG_CC
AC_PROG_INSTALL
LT_INIT([pic-only])
dnl patching ${archive_cmds} to affect generation of file "libtool" to fix linking with clang
AS_CASE(["$LD"],[*clang*],
[AS_CASE(["${host_os}"],
[*linux*],[archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'])])
dnl check for pkg-config (explained in detail in libosmocore/configure.ac)
AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no)
if test "x$PKG_CONFIG_INSTALLED" = "xno"; then
AC_MSG_WARN([You need to install pkg-config])
fi
PKG_PROG_PKG_CONFIG([0.20])
AC_CONFIG_MACRO_DIR([m4])
CFLAGS="$CFLAGS -Wall"
CPPFLAGS="$CPPFLAGS -Wall"
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
# The following test is taken from WebKit's webkit.m4
saved_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS -fvisibility=hidden "
AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])],
[ AC_MSG_RESULT([yes])
SYMBOL_VISIBILITY="-fvisibility=hidden"],
AC_MSG_RESULT([no]))
CFLAGS="$saved_CFLAGS"
AC_SUBST(SYMBOL_VISIBILITY)
dnl Generate the output
AM_CONFIG_HEADER(config.h)
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.8.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.8.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.8.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.8.0)
PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 1.3.0)
PKG_CHECK_MODULES(LIBOSMOISDN, libosmoisdn >= 1.8.0)
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+=" -Wno-error=deprecated -Wno-error=deprecated-declarations"
WERROR_FLAGS+=" -Wno-error=cpp" # "#warning"
CFLAGS="$CFLAGS $WERROR_FLAGS"
CPPFLAGS="$CPPFLAGS $WERROR_FLAGS"
fi
AX_CHECK_X86_FEATURES()
AC_MSG_RESULT([CFLAGS="$CFLAGS"])
AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"])
AC_OUTPUT(
src/libecho/Makefile
src/libg711/Makefile
src/Makefile
tests/Makefile
Makefile)

BIN
docs/protocol entities.odg Normal file

Binary file not shown.

BIN
docs/protocol entities.pdf Normal file

Binary file not shown.

151
git-version-gen Executable file
View File

@ -0,0 +1,151 @@
#!/bin/sh
# Print a version string.
scriptversion=2010-01-28.01
# Copyright (C) 2007-2010 Free Software Foundation, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
# It may be run two ways:
# - from a git repository in which the "git describe" command below
# produces useful output (thus requiring at least one signed tag)
# - from a non-git-repo directory containing a .tarball-version file, which
# presumes this script is invoked like "./git-version-gen .tarball-version".
# In order to use intra-version strings in your project, you will need two
# separate generated version string files:
#
# .tarball-version - present only in a distribution tarball, and not in
# a checked-out repository. Created with contents that were learned at
# the last time autoconf was run, and used by git-version-gen. Must not
# be present in either $(srcdir) or $(builddir) for git-version-gen to
# give accurate answers during normal development with a checked out tree,
# but must be present in a tarball when there is no version control system.
# Therefore, it cannot be used in any dependencies. GNUmakefile has
# hooks to force a reconfigure at distribution time to get the value
# correct, without penalizing normal development with extra reconfigures.
#
# .version - present in a checked-out repository and in a distribution
# tarball. Usable in dependencies, particularly for files that don't
# want to depend on config.h but do want to track version changes.
# Delete this file prior to any autoconf run where you want to rebuild
# files to pick up a version string change; and leave it stale to
# minimize rebuild time after unrelated changes to configure sources.
#
# It is probably wise to add these two files to .gitignore, so that you
# don't accidentally commit either generated file.
#
# Use the following line in your configure.ac, so that $(VERSION) will
# automatically be up-to-date each time configure is run (and note that
# since configure.ac no longer includes a version string, Makefile rules
# should not depend on configure.ac for version updates).
#
# AC_INIT([GNU project],
# m4_esyscmd([build-aux/git-version-gen .tarball-version]),
# [bug-project@example])
#
# Then use the following lines in your Makefile.am, so that .version
# will be present for dependencies, and so that .tarball-version will
# exist in distribution tarballs.
#
# BUILT_SOURCES = $(top_srcdir)/.version
# $(top_srcdir)/.version:
# echo $(VERSION) > $@-t && mv $@-t $@
# dist-hook:
# echo $(VERSION) > $(distdir)/.tarball-version
case $# in
1) ;;
*) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
esac
tarball_version_file=$1
nl='
'
# First see if there is a tarball-only version file.
# then try "git describe", then default.
if test -f $tarball_version_file
then
v=`cat $tarball_version_file` || exit 1
case $v in
*$nl*) v= ;; # reject multi-line output
[0-9]*) ;;
*) v= ;;
esac
test -z "$v" \
&& echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
fi
if test -n "$v"
then
: # use $v
elif
v=`git describe --abbrev=4 HEAD 2>/dev/null \
|| git describe --abbrev=4 --match='v*' HEAD 2>/dev/null` \
&& case $v in
[0-9]*) ;;
v[0-9]*) ;;
*) (exit 1) ;;
esac
then
# Is this a new git that lists number of commits since the last
# tag or the previous older version that did not?
# Newer: v6.10-77-g0f8faeb
# Older: v6.10-g0f8faeb
case $v in
*-*-*) : git describe is okay three part flavor ;;
*-*)
: git describe is older two part flavor
# Recreate the number of commits and rewrite such that the
# result is the same as if we were using the newer version
# of git describe.
vtag=`echo "$v" | sed 's/-.*//'`
numcommits=`git rev-list "$vtag"..HEAD | wc -l`
v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
;;
esac
# Change the first '-' to a '.', so version-comparing tools work properly.
# Remove the "g" in git describe's output string, to save a byte.
v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
else
v=UNKNOWN
fi
v=`echo "$v" |sed 's/^v//'`
# Don't declare a version "dirty" merely because a time stamp has changed.
git status > /dev/null 2>&1
dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
case "$dirty" in
'') ;;
*) # Append the suffix only if there isn't one already.
case $v in
*-dirty) ;;
*) v="$v-dirty" ;;
esac ;;
esac
# Omit the trailing newline, so that m4_esyscmd can use the result directly.
echo "$v" | tr -d '\012'
# Local variables:
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-end: "$"
# End:

21
lapv5.h
View File

@ -1,21 +0,0 @@
#pragma once
#include <osmocom/abis/lapd.h>
struct lapv5_instance {
struct llist_head list; /* list of LAPV5 instances */
bool network_side;
void (*transmit_cb)(struct msgb *msg, void *cbdata);
void *transmit_cbdata;
void (*receive_cb)(struct osmo_dlsap_prim *odp, uint16_t dladdr, void *rx_cbdata);
void *receive_cbdata;
struct lapd_profile profile; /* must be a copy */
struct llist_head sap_list; /* list of SAP/datalinks in this instance */
int pcap_fd; /* PCAP file descriptor */
char *name; /* human-readable name */
};
int lapv5ef_rx(struct v5x_link *link, struct msgb *msg);

View File

@ -0,0 +1,13 @@
AC_DEFUN([AX_CHECK_X86_FEATURES],
[m4_foreach_w(
[ax_x86_feature],
[mmx popcnt sse sse2 sse3 sse4.1 sse4.2 sse4a avx avx2 avx512f fma fma4 bmi bmi2],
[AX_GCC_X86_CPU_SUPPORTS(ax_x86_feature,
[X86_FEATURE_CFLAGS="$X86_FEATURE_CFLAGS -m[]ax_x86_feature"],
[])
])
AC_SUBST([X86_FEATURE_CFLAGS])
m4_ifval([$1],[$1],
[CFLAGS="$CFLAGS $X86_FEATURE_CFLAGS"])
$2
])

View File

@ -0,0 +1,44 @@
AC_DEFUN_ONCE([_AX_GCC_X86_CPU_INIT],
[AC_LANG_PUSH([C])
AC_CACHE_CHECK([for gcc __builtin_cpu_init function],
[ax_cv_gcc_check_x86_cpu_init],
[AC_RUN_IFELSE(
[AC_LANG_PROGRAM([#include <stdlib.h>],
[__builtin_cpu_init ();])
],
[ax_cv_gcc_check_x86_cpu_init=yes],
[ax_cv_gcc_check_x86_cpu_init=no])])
AC_LANG_POP([C])
AS_IF([test "X$ax_cv_gcc_check_x86_cpu_init" = "Xno"],
[AC_MSG_ERROR([Need GCC to support X86 CPU features tests])])
])
AC_DEFUN([AX_GCC_X86_CPU_SUPPORTS],
[AC_REQUIRE([AC_PROG_CC])
AC_REQUIRE([_AX_GCC_X86_CPU_INIT])
AC_LANG_PUSH([C])
AS_VAR_PUSHDEF([gcc_x86_feature], [AS_TR_SH([ax_cv_gcc_x86_cpu_supports_$1])])
AC_CACHE_CHECK([for x86 $1 instruction support],
[gcc_x86_feature],
[AC_RUN_IFELSE(
[AC_LANG_PROGRAM( [#include <stdlib.h> ],
[ __builtin_cpu_init ();
if (__builtin_cpu_supports("$1"))
return 0;
return 1;
])],
[gcc_x86_feature=yes],
[gcc_x86_feature=no]
)]
)
AC_LANG_POP([C])
AS_VAR_IF([gcc_x86_feature],[yes],
[AC_DEFINE(
AS_TR_CPP([HAVE_$1_INSTRUCTIONS]),
[1],
[Define if $1 instructions are supported])
$2],
[$3]
)
AS_VAR_POPDEF([gcc_x86_feature])
])

View File

@ -1,12 +0,0 @@
e1_input
e1_line 0 driver dahdi
e1_line 0 port 7
log stderr
logging filter all 1
logging color 1
logging print category-hex 0
logging print category 1
logging print file 1
logging level llapd debug
logging level linp debug

31
src/Makefile.am Normal file
View File

@ -0,0 +1,31 @@
AM_CPPFLAGS = $(all_includes)
AM_CFLAGS= -Wall -Wextra -g $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS)
AM_LDFLAGS = $(COVERAGE_LDFLAGS)
COMMONLIBS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS)
SUBDIRS = libecho libg711
bin_PROGRAMS = osmo-v5-le
osmo_v5_le_SOURCES = logging.c \
ph_socket.c \
v5x_data.c \
v5le_vty.c \
lapv5.c \
layer1.c \
v5x_protocol.c \
v5x_l1_fsm.c \
v5x_le_ctrl_fsm.c \
v5x_le_port_fsm.c \
v5x_le_pstn_fsm.c \
v52_le_lcp_fsm.c \
v52_le_bcc_fsm.c \
v52_le_pp_fsm.c \
v5x_le_management.c \
main.c
osmo_v5_le_LDADD = $(COMMON_LA) \
$(COMMONLIBS) \
libecho/libecho.a \
libg711/libg711.a \
-lm

View File

@ -22,24 +22,26 @@
*
*/
#include "v5x_internal.h"
#include "v5x_protocol.h"
#include "lapv5.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/timer.h>
#include <osmocom/abis/lapd.h>
#include <osmocom/abis/lapd_pcap.h>
#define LAPD_ADDR2(sapi, cr) ((((sapi) & 0x3f) << 2) | (((cr) & 0x1) << 1))
#define LAPD_ADDR3(tei) ((((tei) & 0x7f) << 1) | 0x1)
#include "v5x_internal.h"
#include "v5x_protocol.h"
#include "v5x_le_port_fsm.h"
#include "lapv5.h"
#include "layer1.h"
#include "logging.h"
#define LAPD_ADDR1(sapi, cr) ((((sapi) & 0x3f) << 2) | (((cr) & 0x1) << 1))
#define LAPD_ADDR2(tei) ((((tei) & 0x7f) << 1) | 0x1)
#define LAPD_ADDR_SAPI(addr) ((addr) >> 2)
#define LAPD_ADDR_CR(addr) (((addr) >> 1) & 0x1)
@ -132,15 +134,16 @@ static struct lapv5_sap *lapv5_sap_alloc(struct lapv5_instance *li, uint16_t v5d
if (!sap)
return NULL;
LOGP(DLLAPD, LOGL_NOTICE, "(%s): LAPV5 Allocating SAP for V5_DLADDR=%u (dl=%p, sap=%p)\n",
LOGP(DLLAPD, LOGL_DEBUG, "(%s): LAPV5 Allocating SAP for V5_DLADDR=%u (dl=%p, sap=%p)\n",
name, v5dladdr, &sap->dl, sap);
sap->li = li;
sap->dladdr = v5dladdr;
dl = &sap->dl;
profile = &li->profile;
k = profile->k[0];
LOGP(DLLAPD, LOGL_NOTICE, "(%s): k=%d N200=%d N201=%d T200=%d.%d T203=%d.%d\n",
LOGP(DLLAPD, LOGL_DEBUG, "(%s): k=%d N200=%d N201=%d T200=%d.%d T203=%d.%d\n",
name, k, profile->n200, profile->n201, profile->t200_sec,
profile->t200_usec, profile->t203_sec, profile->t203_usec);
lapd_dl_init2(dl, k, 128, profile->n201, name);
@ -152,8 +155,8 @@ static struct lapv5_sap *lapv5_sap_alloc(struct lapv5_instance *li, uint16_t v5d
dl->t200_sec = profile->t200_sec; dl->t200_usec = profile->t200_usec;
dl->t203_sec = profile->t203_sec; dl->t203_usec = profile->t203_usec;
dl->lctx.dl = &sap->dl;
dl->lctx.sapi = v5dladdr & 0xff;
dl->lctx.tei = v5dladdr >> 8;
dl->lctx.sapi = v5dladdr >> 7;
dl->lctx.tei = v5dladdr & 0x7f;
dl->lctx.n201 = profile->n201;
lapd_set_mode(&sap->dl, (li->network_side) ? LAPD_MODE_NETWORK : LAPD_MODE_USER);
@ -166,7 +169,7 @@ static struct lapv5_sap *lapv5_sap_alloc(struct lapv5_instance *li, uint16_t v5d
/* Free SAP instance, including the datalink */
static void lapv5_sap_free(struct lapv5_sap *sap)
{
LOGSAP(sap, LOGL_NOTICE, "LAPV5 Freeing SAP for DLADDR=%u (dl=%p, sap=%p)\n", sap->dladdr, &sap->dl, sap);
LOGSAP(sap, LOGL_DEBUG, "LAPV5 Freeing SAP for DLADDR=%u (dl=%p, sap=%p)\n", sap->dladdr, &sap->dl, sap);
/* free datalink structures and timers */
lapd_dl_exit(&sap->dl);
@ -175,39 +178,41 @@ static void lapv5_sap_free(struct lapv5_sap *sap)
talloc_free(sap);
}
/* General input function for any data received for this LAPV5 instance */
int lapv5_receive(struct lapv5_instance *li, struct msgb *msg, int *error)
/* Receive Data (PH-DATA indication) on the given LAPD Instance */
int lapv5_ph_data_ind(struct lapv5_instance *li, struct msgb *msg, int *error)
{
struct lapd_msg_ctx lctx;
struct lapv5_sap *sap;
uint16_t dladdr;
bool is_isdn;
int i;
/* write to PCAP file, if enabled. */
osmo_pcap_lapd_write(li->pcap_fd, OSMO_LAPD_PCAP_INPUT, msg);
if (!li->enabled) {
LOGLI(li, LOGL_DEBUG, "LAPV5 frame ignored, because DL is disabled.\n");
msgb_free(msg);
return -EINVAL;
}
msgb_pull(msg, msg->l2h - msg->data);
LOGLI(li, LOGL_DEBUG, "RX: %s\n", osmo_hexdump(msg->data, msg->len));
if (msg->len < 2) {
LOGLI(li, LOGL_ERROR, "LAPV5 frame receive len %d < 2, ignoring\n", msg->len);
*error = LAPD_ERR_BAD_LEN;
msgb_free(msg);
return -EINVAL;
};
msg->l2h = msg->data;
memset(&lctx, 0, sizeof(lctx));
i = 0;
/* adress field */
dladdr = v51_l3_addr_dec(msg->l2h[0] << 8 | msg->l2h[1], &is_isdn);
if (!is_isdn) {
LOGLI(li, LOGL_ERROR, "LAPV5 frame with single-octet addr not permitted\n");
*error = LAPD_ERR_BAD_LEN;
return -EINVAL;
};
dladdr = ((msg->l2h[0] & 0xfe) << 5) | (msg->l2h[1] >> 1);
lctx.lpd = 0;
lctx.tei = dladdr >> 8;
lctx.sapi = dladdr & 0xff;
lctx.sapi = dladdr >> 7;
lctx.tei = dladdr & 0x7f;
lctx.cr = LAPD_ADDR_CR(msg->l2h[0]);
i += 2;
@ -219,6 +224,7 @@ int lapv5_receive(struct lapv5_instance *li, struct msgb *msg, int *error)
if (msg->len < 4) {
LOGLI(li, LOGL_ERROR, "LAPV5 I frame receive len %d < 4, ignoring\n", msg->len);
*error = LAPD_ERR_BAD_LEN;
msgb_free(msg);
return -EINVAL;
}
lctx.n_recv = LAPD_CTRL_Nr(msg->l2h[i]);
@ -230,6 +236,7 @@ int lapv5_receive(struct lapv5_instance *li, struct msgb *msg, int *error)
if (msg->len < 4 && i == 3) {
LOGLI(li, LOGL_ERROR, "LAPV5 S frame receive len %d < 4, ignoring\n", msg->len);
*error = LAPD_ERR_BAD_LEN;
msgb_free(msg);
return -EINVAL;
}
lctx.n_recv = LAPD_CTRL_Nr(msg->l2h[i]);
@ -270,34 +277,34 @@ int lapv5_receive(struct lapv5_instance *li, struct msgb *msg, int *error)
return lapd_ph_data_ind(msg, &lctx);
}
/* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
int lapv5_sap_start(struct lapv5_instance *li, uint16_t dladdr)
/* Start a (LE-side) SAP for the specified TEI/SAPI on the LAPD instance */
int lapv5_dl_est_req(struct lapv5_instance *li, uint16_t dladdr)
{
struct lapv5_sap *sap;
struct osmo_dlsap_prim dp;
struct msgb *msg;
sap = lapv5_sap_find(li, dladdr);
if (sap)
return -EEXIST;
if (!sap) {
sap = lapv5_sap_alloc(li, dladdr);
if (!sap)
return -ENOMEM;
}
LOGSAP(sap, LOGL_NOTICE, "LAPV5 DL-ESTABLISH request DLADDR=%u\n", dladdr);
LOGSAP(sap, LOGL_DEBUG, "LAPV5 DL-ESTABLISH request DLADDR=%u\n", dladdr);
/* prepare prim */
msg = msgb_alloc_headroom(DLSAP_MSGB_SIZE, DLSAP_MSGB_HEADROOM, "DL EST");
msg->l3h = msg->data;
memset(&dp, 0, sizeof(dp));
osmo_prim_init(&dp.oph, 0, PRIM_DL_EST, PRIM_OP_REQUEST, msg);
/* send to L2 */
return lapd_recv_dlsap(&dp, &sap->dl.lctx);
}
/* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
int lapv5_sap_stop(struct lapv5_instance *li, uint16_t dladdr)
/* Stop a (LE-side) SAP for the specified TEI/SAPI on the LAPD instance */
int lapv5_dl_rel_req(struct lapv5_instance *li, uint16_t dladdr)
{
struct lapv5_sap *sap;
struct osmo_dlsap_prim dp;
@ -307,11 +314,12 @@ int lapv5_sap_stop(struct lapv5_instance *li, uint16_t dladdr)
if (!sap)
return -ENODEV;
LOGSAP(sap, LOGL_NOTICE, "LAPV5 DL-RELEASE request DLADDR=%u\n", dladdr);
LOGSAP(sap, LOGL_DEBUG, "LAPV5 DL-RELEASE request DLADDR=%u\n", dladdr);
/* prepare prim */
msg = msgb_alloc_headroom(DLSAP_MSGB_SIZE, DLSAP_MSGB_HEADROOM, "DL REL");
msg->l3h = msg->data;
memset(&dp, 0, sizeof(dp));
osmo_prim_init(&dp.oph, 0, PRIM_DL_REL, PRIM_OP_REQUEST, msg);
/* send to L2 */
@ -319,24 +327,25 @@ int lapv5_sap_stop(struct lapv5_instance *li, uint16_t dladdr)
}
/* Transmit Data (DL-DATA request) on the given LAPD Instance / DLADDR */
void lapv5_transmit(struct lapv5_instance *li, uint8_t dladdr, struct msgb *msg)
int lapv5_dl_data_req(struct lapv5_instance *li, uint16_t dladdr, struct msgb *msg)
{
struct lapv5_sap *sap;
struct osmo_dlsap_prim dp;
sap = lapv5_sap_find(li, dladdr);
if (!sap) {
LOGLI(li, LOGL_INFO, "LAPV5 Tx on unknown DLADDR=%u\n", dladdr);
LOGLI(li, LOGL_NOTICE, "LAPV5 Tx on unknown DLADDR=%u\n", dladdr);
msgb_free(msg);
return;
return -EINVAL;
}
/* prepare prim */
msg->l3h = msg->data;
memset(&dp, 0, sizeof(dp));
osmo_prim_init(&dp.oph, 0, PRIM_DL_DATA, PRIM_OP_REQUEST, msg);
/* send to L2 */
lapd_recv_dlsap(&dp, &sap->dl.lctx);
return lapd_recv_dlsap(&dp, &sap->dl.lctx);
};
static int send_ph_data_req(struct lapd_msg_ctx *lctx, struct msgb *msg)
@ -368,15 +377,15 @@ static int send_ph_data_req(struct lapd_msg_ctx *lctx, struct msgb *msg)
}
/* address field */
msg->l2h = msgb_push(msg, 2);
msg->l2h[0] = LAPD_ADDR2(lctx->sapi, lctx->cr);
msg->l2h[1] = LAPD_ADDR3(lctx->tei);
msg->l2h[0] = LAPD_ADDR1(lctx->sapi, lctx->cr);
msg->l2h[1] = LAPD_ADDR2(lctx->tei);
/* write to PCAP file, if enabled. */
osmo_pcap_lapd_write(li->pcap_fd, OSMO_LAPD_PCAP_OUTPUT, msg);
/* forward frame to L1 */
LOGDL(dl, LOGL_DEBUG, "TX: %s\n", osmo_hexdump(msg->data, msg->len));
li->transmit_cb(msg, li->transmit_cbdata);
li->ph_data_req_cb(msg, li->ph_data_req_cbdata);
return 0;
}
@ -392,14 +401,14 @@ static int send_dlsap(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
li = sap->li;
dladdr = lctx->tei << 8 | lctx->sapi;
dladdr = lctx->sapi << 7 | lctx->tei;
switch (dp->oph.primitive) {
case PRIM_DL_EST:
LOGDL(dl, LOGL_NOTICE, "LAPD DL-ESTABLISH %s DLADDR=%u\n", op, dladdr);
LOGDL(dl, LOGL_DEBUG, "LAPD DL-ESTABLISH %s DLADDR=%u\n", op, dladdr);
break;
case PRIM_DL_REL:
LOGDL(dl, LOGL_NOTICE, "LAPD DL-RELEASE %s DLADDR=%u\n", op, dladdr);
LOGDL(dl, LOGL_DEBUG, "LAPD DL-RELEASE %s DLADDR=%u\n", op, dladdr);
lapv5_sap_free(sap);
/* note: sap and dl is now gone, don't use it anymore */
break;
@ -407,15 +416,15 @@ static int send_dlsap(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
;
}
li->receive_cb(dp, dladdr, li->receive_cbdata);
li->dl_receive_cb(dp, dladdr, li->dl_receive_cbdata);
return 0;
}
/* Allocate a new LAPV5 instance */
struct lapv5_instance *lapv5_instance_alloc(int network_side,
void (*tx_cb)(struct msgb *msg, void *cbdata), void *tx_cbdata,
void (*rx_cb)(struct osmo_dlsap_prim *odp, uint16_t dladdr, void *rx_cbdata), void *rx_cbdata,
int (*ph_data_req_cb)(struct msgb *msg, void *cbdata), void *ph_data_req_cbdata,
int (*dl_receive_cb)(struct osmo_dlsap_prim *odp, uint16_t dladdr, void *rx_cbdata), void *dl_receive_cbdata,
const struct lapd_profile *profile, const char *name)
{
struct lapv5_instance *li;
@ -425,10 +434,10 @@ struct lapv5_instance *lapv5_instance_alloc(int network_side,
return NULL;
li->network_side = network_side;
li->transmit_cb = tx_cb;
li->transmit_cbdata = tx_cbdata;
li->receive_cb = rx_cb;
li->receive_cbdata = rx_cbdata;
li->ph_data_req_cb = ph_data_req_cb;
li->ph_data_req_cbdata = ph_data_req_cbdata;
li->dl_receive_cb = dl_receive_cb;
li->dl_receive_cbdata = dl_receive_cbdata;
li->pcap_fd = -1;
li->name = talloc_strdup(li, name);
memcpy(&li->profile, profile, sizeof(li->profile));
@ -470,62 +479,121 @@ void lapv5_instance_free(struct lapv5_instance *li)
* while the last octet (before msg->tail) points to the last FCS octet. */
int lapv5ef_rx(struct v5x_link *link, struct msgb *msg)
{
struct lapd_datalink *dl;
struct lapv5_instance *li = NULL;
struct v5x_user_port *v5up = NULL;
uint16_t efaddr, efaddr_enc;
bool is_isdn;
int error;
msg->l1h = msg->data;
if (msgb_length(msg) < 2) {
LOGP(DV5EF, LOGL_ERROR, "Frame too short.\n");
msgb_free(msg);
return -EINVAL;
}
msg->l2h = msg->l1h + 2;
efaddr_enc = msg->l1h[0] << 8 | msg->l1h[1];
efaddr = v51_l3_addr_dec(efaddr_enc, &is_isdn);
efaddr_enc = msg->l2h[0] << 8 | msg->l2h[1];
msg->l2h += 2;
msgb_pull(msg, msg->l2h - msg->data);
efaddr = v5x_l3_addr_dec(efaddr_enc, &is_isdn);
if (!is_isdn) {
/* EFaddr are structured like isdn-type L3 Address */
LOGP(DV5EF, LOGL_ERROR, "EFaddr are structured like isdn-type L3 Address.\n");
msgb_free(msg);
return -EINVAL;
}
switch (efaddr) {
case V51_DLADDR_PSTN:
case V5X_DLADDR_PSTN:
/* hand-over to LAPD-DL instance for PSTN */
dl = &link->interface->pstn.dl;
/* TODO */
li = link->interface->pstn.li;
break;
case V51_DLADDR_CTRL:
case V5X_DLADDR_CTRL:
/* hand-over to LAPD-DL instance for CTRL */
/* TODO */
dl = &link->interface->control.dl;
li = link->interface->control.li;
break;
case V52_DLADDR_BCC:
dl = &link->interface->bcc.dl;
/* TOOD: implement V5.2 */
msgb_free(msg);
/* hand-over to LAPD-DL instance for BCC */
li = link->interface->bcc.li;
break;
case V52_DLADDR_PROTECTION:
dl = &link->interface->protection[0].dl;
/* TOOD: implement V5.2 */
msgb_free(msg);
/* hand-over to LAPD-DL instance for PROTECTION */
if (link == link->interface->primary_link)
li = link->interface->protection.li[0];
if (link == link->interface->secondary_link)
li = link->interface->protection.li[1];
break;
case V52_DLADDR_LCP:
dl = &link->interface->lcp.dl;
/* TOOD: implement V5.2 */
msgb_free(msg);
/* hand-over to LAPD-DL instance for LCP */
li = link->interface->lcp.li;
break;
default:
if (efaddr >= 8176) {
/* reserved as per Section 9.2.2.2 of G.964 */
LOGP(DV5EF, LOGL_ERROR, "No LAPV5 protocol for EFaddr %d.\n", efaddr);
msgb_free(msg);
return -EINVAL;
}
/* relay function for LAPD of user ports */
/* TODO */
v5up = v5x_user_port_find(link->interface, efaddr, true);
if (!v5up) {
LOGP(DV5EF, LOGL_ERROR, "No ISDN user port instance for EFaddr %d created.\n", efaddr);
msgb_free(msg);
return -EINVAL;
}
if (!v5x_le_port_isdn_is_operational(v5up->port_fi)) {
LOGP(DV5EF, LOGL_NOTICE, "Dropping D-channel (AN->LE) message of non-operational ISDN port for EFaddr %d.\n", efaddr);
msgb_free(msg);
return -EIO;
}
LOGP(DV5EF, LOGL_DEBUG, "Recevied frame for EFaddr %d: %s\n", efaddr, osmo_hexdump(msg->data, msg->len));
ph_socket_tx_msg(&v5up->ph_socket, 3, PH_PRIM_DATA_IND, msg->data, msg->len);
msgb_free(msg);
return 0;
}
if (!li) {
LOGP(DV5EF, LOGL_ERROR, "No LAPV5 instance for EFaddr %d created.\n", efaddr);
msgb_free(msg);
return -EINVAL;
}
lapv5_ph_data_ind(li, msg, &error);
return 0;
}
int lapv5ef_tx(struct v5x_user_port *v5up, struct msgb *msg)
{
uint16_t efaddr = v5up->nr, efaddr_enc;
if (!v5x_le_port_isdn_is_operational(v5up->port_fi)) {
LOGP(DV5EF, LOGL_NOTICE, "Dropping D-channel (LE->AN) message of non-operational ISDN port for EFaddr %d.\n", efaddr);
msgb_free(msg);
return -EIO;
}
/* relay function for LAPD of user ports */
LOGP(DV5EF, LOGL_DEBUG, "Sending frame for EFaddr %d: %s\n", efaddr, osmo_hexdump(msg->data, msg->len));
msg->l2h = msgb_push(msg, 2);
efaddr_enc = v5x_l3_addr_enc(efaddr, 1);
msg->l2h[0] = efaddr_enc >> 8;
msg->l2h[1] = efaddr_enc;
ph_data_req_hdlc(msg, v5up->interface);
return 0;
}
/* set enabled state, also free all SAP, so it will disable/enable with a cleanly */
void lapv5_set_enabled(struct lapv5_instance *li, bool enabled)
{
struct lapv5_sap *sap;
li->enabled = enabled;
/* reset DL instances */
llist_for_each_entry(sap, &li->sap_list, list) {
lapd_dl_reset(&sap->dl);
}
}

43
src/lapv5.h Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include <osmocom/abis/lapd.h>
struct lapv5_instance {
struct llist_head list; /* list of LAPV5 instances */
bool network_side;
int (*ph_data_req_cb)(struct msgb *msg, void *cbdata);
void *ph_data_req_cbdata;
int (*dl_receive_cb)(struct osmo_dlsap_prim *odp, uint16_t dladdr, void *rx_cbdata);
void *dl_receive_cbdata;
struct lapd_profile profile; /* must be a copy */
struct llist_head sap_list; /* list of SAP/datalinks in this instance */
int pcap_fd; /* PCAP file descriptor */
char *name; /* human-readable name */
bool enabled; /* only receive messages when enabled */
};
extern const struct lapd_profile lapd_profile_lapv5dl;
int lapv5ef_rx(struct v5x_link *link, struct msgb *msg);
int lapv5_dl_est_req(struct lapv5_instance *li, uint16_t dladdr);
int lapv5_dl_rel_req(struct lapv5_instance *li, uint16_t dladdr);
int lapv5_dl_data_req(struct lapv5_instance *li, uint16_t dladdr, struct msgb *msg);
int lapv5_ph_data_ind(struct lapv5_instance *li, struct msgb *msg, int *error);
struct lapv5_instance *lapv5_instance_alloc(int network_side,
int (*ph_data_req_cb)(struct msgb *msg, void *cbdata), void *ph_data_req_cbdata,
int (*dl_receive_cb)(struct osmo_dlsap_prim *odp, uint16_t dladdr, void *rx_cbdata), void *dl_receive_cbdata,
const struct lapd_profile *profile, const char *name);
void lapv5_instance_set_profile(struct lapv5_instance *li, const struct lapd_profile *profile);
void lapv5_instance_free(struct lapv5_instance *li);
int lapv5ef_rx(struct v5x_link *link, struct msgb *msg);
int lapv5ef_tx(struct v5x_user_port *v5up, struct msgb *msg);
void lapv5_set_enabled(struct lapv5_instance *li, bool enabled);

537
src/layer1.c Normal file
View File

@ -0,0 +1,537 @@
#include <stdio.h>
#include <errno.h>
#include <osmocom/core/gsmtap.h>
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/signal.h>
#include "v5x_internal.h"
#include "v5x_protocol.h"
#include "lapv5.h"
#include "layer1.h"
#include "v5x_l1_fsm.h"
#include "v5x_le_management.h"
#include "logging.h"
#include "libg711/g711.h"
extern int ulaw;
extern int test_sa7;
extern const char *gsmtap_ip;
extern struct v5x_instance *v5i;
static struct gsmtap_inst *g_gti = NULL;
/* only temporarily until this is in libosmocore gsmtap.h */
#ifndef GSMTAP_E1T1_V5EF
#define GSMTAP_E1T1_V5EF 0x06
#endif
/* Callback function to receive L1 signal from E1 port */
static int inp_sig_cb(unsigned int subsys, unsigned int signal, void __attribute__((unused)) *handler_data,
void *signal_data)
{
struct input_signal_data *isd = signal_data;
struct v5x_link *v5l;
if (subsys != SS_L_INPUT)
return 0;
/* not used by any link */
if (!isd->line->ops)
return 0;
v5l = container_of(isd->line->ops, struct v5x_link, e1_line_ops);
switch (signal) {
case S_L_INP_LINE_LOS:
v5x_l1_signal_rcv(v5l, L1_SIGNAL_LOS);
break;
case S_L_INP_LINE_NOLOS:
v5x_l1_signal_rcv(v5l, L1_SIGNAL_NO_LOS);
break;
case S_L_INP_LINE_RAI:
v5x_l1_signal_rcv(v5l, L1_SIGNAL_RAI);
break;
case S_L_INP_LINE_NORAI:
v5x_l1_signal_rcv(v5l, L1_SIGNAL_NO_RAI);
break;
case S_L_INP_LINE_AIS:
v5x_l1_signal_rcv(v5l, L1_SIGNAL_AIS);
break;
case S_L_INP_LINE_NOAIS:
v5x_l1_signal_rcv(v5l, L1_SIGNAL_NO_AIS);
break;
case S_L_INP_LINE_SA_BITS:
if ((isd->sa_bits & 0x40)) {
v5x_l1_signal_rcv(v5l, L1_SIGNAL_SA7_1);
if (test_sa7) {
printf("Currently Sa 7 is set to 1, changing it to 0. (Link ID %d)\n", v5l->id);
v5x_l1_signal_snd(v5l, L1_SIGNAL_SA7_0);
}
} else {
v5x_l1_signal_rcv(v5l, L1_SIGNAL_SA7_0);
if (test_sa7) {
printf("Currently Sa 7 is set to 0, changing it to 1. (Link ID %d)\n", v5l->id);
v5x_l1_signal_snd(v5l, L1_SIGNAL_SA7_1);
}
}
break;
case S_L_INP_LINE_SLIP_RX:
printf("RX slip detected on link %d.\n", v5l->id);
break;
case S_L_INP_LINE_SLIP_TX:
printf("TX slip detected on link %d.\n", v5l->id);
break;
default:
;
}
return 0;
}
/* Send L1 signal to E1 port */
int v5x_l1_signal_snd(struct v5x_link *v5l, enum l1_signal_prim prim)
{
struct e1inp_line *e1_line = v5l->e1_line;
/* no line assigned */
if (!e1_line)
return 0;
switch (prim) {
case L1_SIGNAL_SA7_0:
e1inp_ts_set_sa_bits(e1_line, 0xbf);
break;
case L1_SIGNAL_SA7_1:
e1inp_ts_set_sa_bits(e1_line, 0xff);
break;
default:
;
}
return 0;
}
/* store TX to buffer */
static void echo_tx(struct v5x_echo_proc *ep, uint8_t *data, int len)
{
int in;
int16_t tx;
if (!ep->enabled)
return;
while (len--) {
if (ulaw)
tx = g711_ulaw_flipped_to_linear[*data++];
else
tx = g711_alaw_flipped_to_linear[*data++];
ep->tx_buffer[ep->tx_buffer_in] = tx;
in = (ep->tx_buffer_in + 1) % EC_BUFFER;
/* buffer overflow condition, should not happen, if application syncs to RX */
if (in == ep->tx_buffer_out)
break;
ep->tx_buffer_in = in;
}
}
/* remove echo from RX using TX from buffer */
static void echo_rx(struct v5x_echo_proc *ep, uint8_t *data, int len)
{
struct v5x_user_port *v5up = ep->port;
int16_t tx, rx, rx_can;
int16_t answer_buffer[len];
int i;
int rc;
if (!ep->enabled)
return;
for (i = 0; i < len; i++) {
/* buffer underrun condition, may happen before buffer was filled with first frame */
if (ep->tx_buffer_out == ep->tx_buffer_in)
tx = 0;
else {
tx = ep->tx_buffer[ep->tx_buffer_out];
ep->tx_buffer_out = (ep->tx_buffer_out + 1) % EC_BUFFER;
}
if (ulaw) {
rx = g711_ulaw_flipped_to_linear[*data];
if (ep->port->use_line_echo == USE_ECHO_CANCELER)
rx_can = echo_can_update(ep->ec, tx, rx);
else
rx_can = echo_sup_update(ep->es, tx, rx);
*data++ = g711_linear_to_ulaw_flipped[(uint16_t)rx_can];
} else {
rx = g711_alaw_flipped_to_linear[*data];
if (ep->port->use_line_echo == USE_ECHO_CANCELER)
rx_can = echo_can_update(ep->ec, tx, rx);
else
rx_can = echo_sup_update(ep->es, tx, rx);
*data++ = g711_linear_to_alaw_flipped[(uint16_t)rx_can];
}
answer_buffer[i] = rx + tx; /* yes, may overflow, but then it is no valid tone anyway */
}
rc = answertone_process(&ep->at, answer_buffer, len, (ep->port->use_line_echo == USE_ECHO_CANCELER));
if (rc > 0) {
LOGP(DV5, LOGL_NOTICE, "Detected answer tone, disable echo %s of %s port %d.\n",
(ep->port->use_line_echo == USE_ECHO_CANCELER) ? "canceler" : "suppressor",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
ep->enabled = 0;
}
}
/* data L1 -> L2 from E1 interface */
static void hdlc_rx_cb(struct e1inp_ts *ts, struct msgb *msg)
{
struct v5x_link *v5l;
/* not used by any link */
if (!ts->line->ops) {
msgb_free(msg);
return;
}
v5l = container_of(ts->line->ops, struct v5x_link, e1_line_ops);
LOGP(DLINP, LOGL_DEBUG, "Link %d L1->L2: %s\n", v5l->id, msgb_hexdump(msg));
/* send V5 data via gsmtap so wireshark can receive + decode it */
if (g_gti) {
gsmtap_send_ex(g_gti, GSMTAP_TYPE_E1T1, v5l->id, ts->num, GSMTAP_E1T1_V5EF,
0, 0, 0, 0, msgb_data(msg), msgb_length(msg));
}
lapv5ef_rx(v5l, msg);
}
/* data B-channel data from E1 interface */
static void raw_rx_cb(struct e1inp_ts *ts, struct msgb *msg)
{
struct v5x_link *v5l;
struct v5x_user_port *v5up;
/* not used by any link */
if (!ts->line->ops) {
msgb_free(msg);
return;
}
v5l = container_of(ts->line->ops, struct v5x_link, e1_line_ops);
v5up = v5l->ts[ts->num].v5up;
/* not used by any user port */
if (!v5up) {
msgb_free(msg);
return;
}
/* we want B-channel data flipped */
osmo_revbytebits_buf(msg->data, msg->len);
/* if assigned and active, send B-channel data to socket interface */
if (v5up->ts[0] && v5up->ts[0]->nr == ts->num && v5up->ts[0]->b_activated) {
echo_rx(&v5up->ep[0], msg->data, msg->len);
ph_socket_tx_msg(&v5up->ph_socket, 1, PH_PRIM_DATA_IND, msg->data, msg->len);
}
if (v5up->ts[1] && v5up->ts[1]->nr == ts->num && v5up->ts[1]->b_activated) {
echo_rx(&v5up->ep[1], msg->data, msg->len);
ph_socket_tx_msg(&v5up->ph_socket, 2, PH_PRIM_DATA_IND, msg->data, msg->len);
}
msgb_free(msg);
}
/* send HDLC frame to signaling channel (from ISDN) */
int ph_data_req_hdlc(struct msgb *msg, struct v5x_interface *v5if)
{
struct e1inp_line *e1_line = v5if->cc_link->e1_line;
if (!e1_line) {
msgb_free(msg);
return 0;
}
if (!v5x_l1_is_up(v5if->cc_link->l1)) {
LOGP(DLINP, LOGL_DEBUG, "Link %d is down!\n", v5if->cc_link->id);
msgb_free(msg);
return 0;
}
LOGP(DLINP, LOGL_DEBUG, "Link %d L2->L1: %s\n", v5if->cc_link->id, msgb_hexdump(msg));
struct e1inp_ts *ts = &e1_line->ts[v5if->cc_link->c_channel[0].ts->nr - 1];
/* send V5 data via gsmtap so wireshark can receive + decode it */
if (g_gti) {
gsmtap_send_ex(g_gti, GSMTAP_TYPE_E1T1, v5if->cc_link->id | GSMTAP_ARFCN_F_UPLINK, ts->num,
GSMTAP_E1T1_V5EF, 0, 0, 0, 0, msgb_data(msg), msgb_length(msg));
}
return e1inp_ts_send_hdlc(ts, msg);
}
/* send HDLC frame to signaling channel (from DL) */
int ph_data_req_dl_cc(struct msgb *msg, void *cbdata)
{
struct v5x_interface *v5if = (struct v5x_interface *)cbdata;
struct e1inp_line *e1_line = v5if->cc_link->e1_line;
if (!e1_line) {
msgb_free(msg);
return 0;
}
if (!v5x_l1_is_up(v5if->cc_link->l1)) {
LOGP(DLINP, LOGL_DEBUG, "Link %d is down!\n", v5if->cc_link->id);
msgb_free(msg);
return 0;
}
struct e1inp_ts *ts = &e1_line->ts[v5if->cc_link->c_channel[0].ts->nr - 1];
/* add frame relay header */
msg->l2h = msgb_push(msg, 2);
msg->l2h[0] = msg->l2h[2] & 0xfd;
msg->l2h[1] = msg->l2h[3];
LOGP(DLINP, LOGL_DEBUG, "Link %d L2->L1: %s\n", v5if->cc_link->id, msgb_hexdump(msg));
/* send V5 data via gsmtap so wireshark can receive + decode it */
if (g_gti) {
gsmtap_send_ex(g_gti, GSMTAP_TYPE_E1T1, v5if->cc_link->id | GSMTAP_ARFCN_F_UPLINK, ts->num,
GSMTAP_E1T1_V5EF, 0, 0, 0, 0, msgb_data(msg), msgb_length(msg));
}
return e1inp_ts_send_hdlc(ts, msg);
}
/* send HDLC frame to protection link (from DL) */
int ph_data_req_dl_prot(struct msgb *msg, void *cbdata)
{
struct v5x_link *v5l = (struct v5x_link *)cbdata;
struct e1inp_line *e1_line = v5l->e1_line;
if (!e1_line) {
msgb_free(msg);
return 0;
}
if (!v5x_l1_is_up(v5l->l1)) {
LOGP(DLINP, LOGL_DEBUG, "Link %d is down!\n", v5l->id);
msgb_free(msg);
return 0;
}
struct e1inp_ts *ts = &e1_line->ts[v5l->c_channel[0].ts->nr - 1];
/* add frame relay header */
msg->l2h = msgb_push(msg, 2);
msg->l2h[0] = msg->l2h[2] & 0xfd;
msg->l2h[1] = msg->l2h[3];
LOGP(DLINP, LOGL_DEBUG, "Link %d L2->L1: %s\n", v5l->id, msgb_hexdump(msg));
/* send V5 data via gsmtap so wireshark can receive + decode it */
if (g_gti) {
gsmtap_send_ex(g_gti, GSMTAP_TYPE_E1T1, v5l->id | GSMTAP_ARFCN_F_UPLINK, ts->num, GSMTAP_E1T1_V5EF,
0, 0, 0, 0, msgb_data(msg), msgb_length(msg));
}
return e1inp_ts_send_hdlc(ts, msg);
}
int ph_activate_req(struct v5x_timeslot *ts)
{
struct e1inp_line *e1_line = ts->link->e1_line;
struct e1inp_ts *e1_ts;
int rc;
if (ts->b_activated)
return 0;
if (!e1_line)
return -EINVAL;
e1_ts = &e1_line->ts[ts->nr - 1];
e1inp_ts_config_raw(e1_ts, e1_line, raw_rx_cb);
rc = e1inp_line_update(e1_line);
ts->b_activated = 1;
return rc;
}
int ph_deactivate_req(struct v5x_timeslot *ts)
{
struct e1inp_line *e1_line = ts->link->e1_line;
struct e1inp_ts *e1_ts;
int rc;
if (!ts->b_activated)
return 0;
if (!e1_line)
return -EINVAL;
e1_ts = &e1_line->ts[ts->nr - 1];
e1_ts->type = E1INP_TS_TYPE_NONE;
rc = e1inp_line_update(e1_line);
ts->b_activated = 0;
return rc;
}
/* send raw (B-channel) data to E1 interface */
static int ph_data_req_raw(struct v5x_link *v5l, struct msgb *msg, int ts_nr)
{
struct e1inp_line *e1_line = v5l->e1_line;
/* no line assigned */
if (!e1_line) {
msgb_free(msg);
return 0;
}
/* we want B-channel data flipped */
osmo_revbytebits_buf(msg->data, msg->len);
struct e1inp_ts *ts = &e1_line->ts[ts_nr-1];
return e1inp_ts_send_raw(ts, msg);
}
/* receive message from PH-socket */
void ph_socket_rx_cb(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length)
{
struct v5x_user_port *v5up = s->priv;
switch (prim) {
case PH_PRIM_CTRL_REQ:
/* deactivate channels, if active */
if ((channel == 0 || channel == 3) && length && *data == PH_CTRL_BLOCK) {
v5x_le_channel_unassign(v5up, 1);
v5x_le_channel_unassign(v5up, 2);
}
v5x_le_nat_ph_rcv(v5up, prim, data, length);
break;
case PH_PRIM_ACT_REQ:
if (channel == 1 || channel == 2) {
v5x_le_channel_assign(v5up, channel);
ph_socket_tx_msg(s, channel, PH_PRIM_ACT_IND, NULL, 0);
break;
}
v5x_le_nat_ph_rcv(v5up, prim, data, length);
break;
case PH_PRIM_DACT_REQ:
if (channel == 1 || channel == 2) {
v5x_le_channel_unassign(v5up, channel);
ph_socket_tx_msg(s, channel, PH_PRIM_DACT_IND, NULL, 0);
break;
}
v5x_le_nat_ph_rcv(v5up, prim, data, length);
break;
case PH_PRIM_DATA_REQ:
if (v5up->type == V5X_USER_TYPE_PSTN && channel == 0) {
struct msgb *msg = msgb_alloc_headroom(length + 32, 32, "V5 PSTN MSG");
memcpy(msgb_put(msg, length), data, length);
v5x_le_nat_fe_rcv(v5up, msg);
} else if (v5up->type == V5X_USER_TYPE_ISDN && channel == 3) {
struct msgb *msg = msgb_alloc_headroom(length + 32, 32, "V5 EF MSG");
memcpy(msgb_put(msg, length), data, length);
lapv5ef_tx(v5up, msg);
} else if ((channel == 1 || channel == 2) && v5up->ts[channel - 1] && v5up->ts[channel - 1]->b_activated) {
echo_tx(&v5up->ep[channel - 1], data, length);
struct msgb *msg = msgb_alloc_headroom(length + 32, 32, "B MSG");
memcpy(msgb_put(msg, length), data, length);
ph_data_req_raw(v5up->ts[channel - 1]->link, msg, v5up->ts[channel - 1]->nr);
}
/* always confirm */
ph_socket_tx_msg(s, channel, PH_PRIM_DATA_CNF, NULL, 0);
break;
}
}
//FIXME: we use HDLC
static int v5le_rx_sign(struct msgb *msg)
{
LOGP(DLMI, LOGL_NOTICE, "Rx: %s\n", msgb_hexdump(msg));
msgb_free(msg);
return 0;
}
/* init gsmtap */
int gsmtap_init(void)
{
if (gsmtap_ip) {
g_gti = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 0);
if (!g_gti) {
fprintf(stderr, "Failed to use '%s' as IP for GSMTAP\n", gsmtap_ip);
return -EINVAL;
}
gsmtap_source_add_sink(g_gti);
}
return 0;
}
/* register signal to receive E1 events */
int l1_signal_init(void)
{
osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL);
return 0;
}
/* init given E1 line and return e1inp_line structure pointer */
struct e1inp_line *e1_line_init(struct v5x_link *v5l, int e1_nr)
{
struct e1inp_line *e1_line;
int ts;
int rc;
e1_line = e1inp_line_find(e1_nr);
if (!e1_line)
return NULL;
/* link e1inp_line to v5l and vice versa */
/* must set ops before setting TS */
v5l->e1_line_ops.sign_link = v5le_rx_sign;
e1inp_line_bind_ops(e1_line, &v5l->e1_line_ops);
v5l->e1_line = e1_line;
for (ts = 1; ts <= 31; ts++) {
struct e1inp_ts *e1_ts = &e1_line->ts[ts-1];
if (ts == 16) { // FIXME: make this depending on c_channel
//e1inp_ts_config_sign(e1_ts, e1_line);
//e1inp_sign_link_create(e1_ts, E1INP_SIGN_NONE, NULL, 115/*TEI*/, 0/*SAPI*/);
e1inp_ts_config_hdlc(e1_ts, e1_line, hdlc_rx_cb);
} else
e1_ts->type = E1INP_TS_TYPE_NONE;
}
/* if config fails, remove link between e1inp_line and v5l */
rc = e1inp_line_update(e1_line);
if (rc < 0) {
e1_line_exit(v5l);
return NULL;
}
return e1_line;
}
void e1_line_exit(struct v5x_link *v5l)
{
struct e1inp_line *e1_line = v5l->e1_line;
int ts;
for (ts = 1; ts <= 31; ts++) {
struct e1inp_ts *e1_ts = &e1_line->ts[ts-1];
e1_ts->type = E1INP_TS_TYPE_NONE;
}
e1inp_line_update(e1_line);
e1inp_line_bind_ops(e1_line, NULL);
v5l->e1_line = NULL;
}

23
src/layer1.h Normal file
View File

@ -0,0 +1,23 @@
enum l1_signal_prim {
L1_SIGNAL_LOS,
L1_SIGNAL_NO_LOS,
L1_SIGNAL_RAI,
L1_SIGNAL_NO_RAI,
L1_SIGNAL_AIS,
L1_SIGNAL_NO_AIS,
L1_SIGNAL_SA7_1,
L1_SIGNAL_SA7_0,
};
int v5x_l1_signal_snd(struct v5x_link *v5l, enum l1_signal_prim prim);
int ph_activate_req(struct v5x_timeslot *ts);
int ph_deactivate_req(struct v5x_timeslot *ts);
int ph_data_req_hdlc(struct msgb *msg, struct v5x_interface *v5if);
int ph_data_req_dl_cc(struct msgb *msg, void *cbdata);
int ph_data_req_dl_prot(struct msgb *msg, void *cbdata);
void ph_socket_rx_cb(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length);
int gsmtap_init(void);
int l1_signal_init(void);
struct e1inp_line *e1_line_init(struct v5x_link *v5l, int e1_nr);
void e1_line_exit(struct v5x_link *v5l);

8
src/libecho/Makefile.am Normal file
View File

@ -0,0 +1,8 @@
AM_CPPFLAGS = $(all_includes)
AM_CFLAGS= -Wall -Wextra -g
noinst_LIBRARIES = libecho.a
libecho_a_SOURCES = echo_cancel.c \
echo_suppress.c \
answertone.c

276
src/libecho/answertone.c Normal file
View File

@ -0,0 +1,276 @@
/* An answer tone detection algrotihm using Goertzel to detect level and phase
*
* The answer tone is described in ITU V.8 and is also used to disable echo
* suppression and cancelation in the network. The detection uses phase to
* detect frequencies that are too far off, as well a phase reversal, to
* distinguish the answer tone from other continuous tones.
*
* (C) 2023 by Andreas Eversberg <andreas@eversberg.eu>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include "answertone.h"
#define ANS_FREQUENCY 2100.0
#define WINDOW_DURATION 0.010 /* 10 MS */
#define DETECT_DURATION 0.400 /* < 425 MS */
#define MIN_LEVEL 0.05 /* -26 dBm0 */
#define MAX_NOISE 0.5 /* -6 dB */
#define MAX_FREQUENCY 22 /* frequency error */
#define MIN_REVERSAL 2.967 /* about 10 degrees off 180 (PI) */
// #define DEBUG
// #define HEAVY_DEBUG
void goertzel_init(struct answer_tone *at, float frequency, float samplerate)
{
float omega;
omega = 2.0 * M_PI * frequency / samplerate;
at->sine = sin(omega);
at->cosine = cos(omega);
at->coeff = 2.0 * at->cosine;
}
/* with rectangular window, the frequency response is:
* if it matches the filter frequency: 0 dB
* if it is 0.5/duration off the filter frequency: about -4 dB
* if it is 1/duration off the filter frequency; -INF dB
* if it is 1.5/duration off the filter frequency: about -13.5 dB
*/
void goertzel_calculate(struct answer_tone *at, float *data, int length, float *level_p, float *magnitude_p, float *phase_p)
{
float q0 = 0, q1 = 0, q2 = 0;
float real, imag;
float scaling = (float)length / 2.0;
float upper = 0, lower = 0;
int i;
upper = lower = *data;
for (i = 0; i < length; i++) {
if (*data > upper)
upper = *data;
else if (*data < lower)
lower = *data;
q0 = at->coeff * q1 - q2 + *data++;
q2 = q1;
q1 = q0;
}
real = (q1 * at->cosine - q2) / scaling;
imag = (q1 * at->sine) / scaling;
*level_p = (upper - lower) / 2.0;
*magnitude_p = sqrtf(real*real + imag*imag);
*phase_p = atan2(imag, real);
}
int answertone_init(struct answer_tone *at, int samplerate)
{
memset(at, 0, sizeof(*at));
/* use 10 ms as window size */
at->buffer_size = samplerate * WINDOW_DURATION;
at->buffer = calloc(at->buffer_size, sizeof(*at->buffer));
if (!at->buffer)
return -ENOMEM;
goertzel_init(at, ANS_FREQUENCY, samplerate);
return 0;
}
void answertone_reset(struct answer_tone *at)
{
at->state = AT_STATE_NONE;
}
void answertone_exit(struct answer_tone *at)
{
free(at->buffer);
}
enum at_fail answertone_check(struct answer_tone *at, float *phase_p, float *frequency_p)
{
float level, magnitude, last_phase, shift;
goertzel_calculate(at, at->buffer, at->buffer_size, &level, &magnitude, phase_p);
last_phase = at->last_phase;
at->last_phase = *phase_p;
shift = (*phase_p - last_phase);
if (shift > M_PI)
shift -= M_PI * 2.0;
else if (shift < -M_PI)
shift += M_PI * 2.0;
*frequency_p = shift / M_PI / 2.0 / WINDOW_DURATION;
#if HEAVY_DEBUG
printf("level=%6.3f magnitude=%6.3f snr=%6.3f phase=%9.3f frequency=%8.3f\n",
level, magnitude, magnitude/level, *phase_p / M_PI * 180, *frequency_p);
#endif
/* fails due to low level */
if (magnitude < MIN_LEVEL)
return AT_FAIL_MIN_LEVEL;
/* fails due to bad SNR */
if (1.0 - magnitude/level > MAX_NOISE)
return AT_FAIL_MAX_NOISE;
/* fails due to wrong frequency */
if (fabsf(*frequency_p) > MAX_FREQUENCY)
return AT_FAIL_MAX_FREQUENCY;
return AT_FAIL_NONE;
}
#if DEBUG
static const char *answertone_cause[] = {
"ok",
"level too low",
"too noisy",
"wrong frequency",
};
#endif
static enum at_state answertone_chunk(struct answer_tone *at, bool phase_reversal)
{
float phase, frequency, shift;
enum at_fail fail;
int rc = 0;
fail = answertone_check(at, &phase, &frequency);
switch (at->state) {
case AT_STATE_NONE:
if (fail == AT_FAIL_NONE) {
#if DEBUG
printf("DETECTED TONE\n");
#endif
at->state = AT_STATE_DETECT;
at->tone_duration = WINDOW_DURATION;
/* count frequency values */
at->frequency_sum = 0.0;
at->count = 0;
}
break;
case AT_STATE_DETECT:
if (fail != AT_FAIL_NONE) {
#if DEBUG
printf("LOST TONE AFTER %.3f SECONDS (because %s)\n", at->tone_duration, cause[fail]);
#endif
at->state = AT_STATE_NONE;
break;
}
at->frequency_sum += frequency;
at->count++;
at->tone_duration += WINDOW_DURATION;
at->last2_valid_phase = at->last1_valid_phase;
at->last1_valid_phase = phase;
if (at->tone_duration >= DETECT_DURATION) {
#if DEBUG
printf("LONG TONE VALID WITH FREQUENCY OFFSET %.3f\n", at->frequency_sum / at->count);
#endif
at->state = AT_STATE_TONE;
}
break;
case AT_STATE_TONE:
if (fail != AT_FAIL_NONE) {
if (!phase_reversal) {
#if DEBUG
printf("LONG TONE LOST\n");
#endif
at->state = AT_STATE_TONE;
rc = 1;
break;
}
#if DEBUG
printf("LONG TONE CHANGED, CHECK PHASE REVERSAL\n");
#endif
at->last_valid_frequency = at->frequency_sum / at->count;
at->state = AT_STATE_PAUSE;
/* count pause chunks */
at->count = 1;
break;
}
at->frequency_sum += frequency;
at->count++;
at->tone_duration += WINDOW_DURATION;
at->last2_valid_phase = at->last1_valid_phase;
at->last1_valid_phase = phase;
break;
case AT_STATE_PAUSE:
/* three pause chunks: two may be invalid due to phase reversal, one extra to get valid frequency */
if (at->count++ < 3)
break;
/* phase change over 5 chunks */
shift = (phase - at->last2_valid_phase);
if (shift > M_PI)
shift -= M_PI * 2.0;
else if (shift < -M_PI)
shift += M_PI * 2.0;
/* substract phase change to be expected over 5 chunk */
shift -= at->last_valid_frequency * WINDOW_DURATION * 5.0 * M_PI * 2.0;
if (shift > M_PI)
shift -= M_PI * 2.0;
else if (shift < -M_PI)
shift += M_PI * 2.0;
/* if there is change of less than about 10 degrees off 180 */
if (fabsf(shift) > MIN_REVERSAL) {
#if DEBUG
printf("PHASE REVERSAL VALID (phase shift %.0f deg)\n", shift / M_PI * 180.0);
#endif
at->frequency_sum = 0.0;
at->count = 0;
at->state = AT_STATE_DETECT;
rc = 1;
} else {
#if DEBUG
printf("LOST TONE, NO VALID PHASE REVERSAL\n");
#endif
at->state = AT_STATE_NONE;
}
break;
}
return rc;
}
/* convert stream of int16_t (ISDN) stream into chunks of float (dBm0); return 1, if there was a valid answer tone */
int answertone_process(struct answer_tone *at, int16_t *data, int length, bool phase_reversal)
{
int rc = 0;
while (length--) {
/* convert ISDN data to float with dBm level (1 dBm = -3 dB below full int16_t scale) */
at->buffer[at->buffer_pos++] = (float)(*data++) / 23170.0;
if (at->buffer_pos == at->buffer_size) {
rc |= answertone_chunk(at, phase_reversal);
at->buffer_pos = 0;
}
}
return rc;
}

37
src/libecho/answertone.h Normal file
View File

@ -0,0 +1,37 @@
enum at_fail {
AT_FAIL_NONE = 0,
AT_FAIL_MIN_LEVEL,
AT_FAIL_MAX_NOISE,
AT_FAIL_MAX_FREQUENCY,
};
enum at_state {
AT_STATE_NONE = 0,
AT_STATE_DETECT,
AT_STATE_TONE,
AT_STATE_PAUSE,
};
struct answer_tone {
float sine;
float cosine;
float coeff;
float *buffer;
int buffer_size;
int buffer_pos;
float last_phase;
enum at_state state;
float tone_duration;
float frequency_sum;
int count;
float last1_valid_phase;
float last2_valid_phase;
float last_valid_frequency;
};
int answertone_init(struct answer_tone *at, int samplerate);
void answertone_reset(struct answer_tone *at);
void answertone_exit(struct answer_tone *at);
enum at_fail answertone_check(struct answer_tone *at, float *phase_p, float *frequency_p);
int answertone_process(struct answer_tone *at, int16_t *data, int length, bool phase_reversal);

View File

@ -0,0 +1,253 @@
/*
* SpanDSP - a series of DSP components for telephony
*
* bit_operations.h - Various bit level operations, such as bit reversal
*
* Written by Steve Underwood <steveu@coppice.org>
*
* Copyright (C) 2006 Steve Underwood
*
* 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 version 2, as
* published by the Free Software Foundation.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: bit_operations.h,v 1.11 2006/11/28 15:37:03 steveu Exp $
*/
/*! \file */
#if !defined(_BIT_OPERATIONS_H_)
#define _BIT_OPERATIONS_H_
#ifdef __cplusplus
extern "C" {
#endif
#if defined(__i386__) || defined(__x86_64__)
/*! \brief Find the bit position of the highest set bit in a word
\param bits The word to be searched
\return The bit number of the highest set bit, or -1 if the word is zero. */
static __inline__ int top_bit(unsigned int bits)
{
int res;
__asm__ (" xorl %[res],%[res];\n"
" decl %[res];\n"
" bsrl %[bits],%[res]\n"
: [res] "=&r" (res)
: [bits] "rm" (bits));
return res;
}
/*- End of function --------------------------------------------------------*/
/*! \brief Find the bit position of the lowest set bit in a word
\param bits The word to be searched
\return The bit number of the lowest set bit, or -1 if the word is zero. */
static __inline__ int bottom_bit(unsigned int bits)
{
int res;
__asm__ (" xorl %[res],%[res];\n"
" decl %[res];\n"
" bsfl %[bits],%[res]\n"
: [res] "=&r" (res)
: [bits] "rm" (bits));
return res;
}
/*- End of function --------------------------------------------------------*/
#else
static __inline__ int top_bit(unsigned int bits)
{
int i;
if (bits == 0)
return -1;
i = 0;
if (bits & 0xFFFF0000)
{
bits &= 0xFFFF0000;
i += 16;
}
if (bits & 0xFF00FF00)
{
bits &= 0xFF00FF00;
i += 8;
}
if (bits & 0xF0F0F0F0)
{
bits &= 0xF0F0F0F0;
i += 4;
}
if (bits & 0xCCCCCCCC)
{
bits &= 0xCCCCCCCC;
i += 2;
}
if (bits & 0xAAAAAAAA)
{
bits &= 0xAAAAAAAA;
i += 1;
}
return i;
}
/*- End of function --------------------------------------------------------*/
static __inline__ int bottom_bit(unsigned int bits)
{
int i;
if (bits == 0)
return -1;
i = 32;
if (bits & 0x0000FFFF)
{
bits &= 0x0000FFFF;
i -= 16;
}
if (bits & 0x00FF00FF)
{
bits &= 0x00FF00FF;
i -= 8;
}
if (bits & 0x0F0F0F0F)
{
bits &= 0x0F0F0F0F;
i -= 4;
}
if (bits & 0x33333333)
{
bits &= 0x33333333;
i -= 2;
}
if (bits & 0x55555555)
{
bits &= 0x55555555;
i -= 1;
}
return i;
}
/*- End of function --------------------------------------------------------*/
#endif
/*! \brief Bit reverse a byte.
\param data The byte to be reversed.
\return The bit reversed version of data. */
static __inline__ uint8_t bit_reverse8(uint8_t x)
{
#if defined(__i386__) || defined(__x86_64__)
/* If multiply is fast */
return ((x*0x0802U & 0x22110U) | (x*0x8020U & 0x88440U))*0x10101U >> 16;
#else
/* If multiply is slow, but we have a barrel shifter */
x = (x >> 4) | (x << 4);
x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2);
return ((x & 0xAA) >> 1) | ((x & 0x55) << 1);
#endif
}
/*- End of function --------------------------------------------------------*/
/*! \brief Bit reverse a 16 bit word.
\param data The word to be reversed.
\return The bit reversed version of data. */
uint16_t bit_reverse16(uint16_t data);
/*! \brief Bit reverse a 32 bit word.
\param data The word to be reversed.
\return The bit reversed version of data. */
uint32_t bit_reverse32(uint32_t data);
/*! \brief Bit reverse each of the four bytes in a 32 bit word.
\param data The word to be reversed.
\return The bit reversed version of data. */
uint32_t bit_reverse_4bytes(uint32_t data);
/*! \brief Find the number of set bits in a 32 bit word.
\param x The word to be searched.
\return The number of set bits. */
int one_bits32(uint32_t x);
/*! \brief Create a mask as wide as the number in a 32 bit word.
\param x The word to be searched.
\return The mask. */
uint32_t make_mask32(uint32_t x);
/*! \brief Create a mask as wide as the number in a 16 bit word.
\param x The word to be searched.
\return The mask. */
uint16_t make_mask16(uint16_t x);
/*! \brief Find the least significant one in a word, and return a word
with just that bit set.
\param x The word to be searched.
\return The word with the single set bit. */
static __inline__ uint32_t least_significant_one32(uint32_t x)
{
return (x & (-(int32_t) x));
}
/*- End of function --------------------------------------------------------*/
/*! \brief Find the most significant one in a word, and return a word
with just that bit set.
\param x The word to be searched.
\return The word with the single set bit. */
static __inline__ uint32_t most_significant_one32(uint32_t x)
{
#if defined(__i386__) || defined(__x86_64__)
return 1 << top_bit(x);
#else
x = make_mask32(x);
return (x ^ (x >> 1));
#endif
}
/*- End of function --------------------------------------------------------*/
/*! \brief Find the parity of a byte.
\param x The byte to be checked.
\return 1 for odd, or 0 for even. */
static __inline__ int parity8(uint8_t x)
{
x = (x ^ (x >> 4)) & 0x0F;
return (0x6996 >> x) & 1;
}
/*- End of function --------------------------------------------------------*/
/*! \brief Find the parity of a 16 bit word.
\param x The word to be checked.
\return 1 for odd, or 0 for even. */
static __inline__ int parity16(uint16_t x)
{
x ^= (x >> 8);
x = (x ^ (x >> 4)) & 0x0F;
return (0x6996 >> x) & 1;
}
/*- End of function --------------------------------------------------------*/
/*! \brief Find the parity of a 32 bit word.
\param x The word to be checked.
\return 1 for odd, or 0 for even. */
static __inline__ int parity32(uint32_t x)
{
x ^= (x >> 16);
x ^= (x >> 8);
x = (x ^ (x >> 4)) & 0x0F;
return (0x6996 >> x) & 1;
}
/*- End of function --------------------------------------------------------*/
#ifdef __cplusplus
}
#endif
#endif
/*- End of file ------------------------------------------------------------*/

658
src/libecho/echo_cancel.c Normal file
View File

@ -0,0 +1,658 @@
/*
* SpanDSP - a series of DSP components for telephony
*
* echo.c - A line echo canceller. This code is being developed
* against and partially complies with G168.
*
* Written by Steve Underwood <steveu@coppice.org>
* and David Rowe <david_at_rowetel_dot_com>
*
* Copyright (C) 2001, 2003 Steve Underwood, 2007 David Rowe
*
* Based on a bit from here, a bit from there, eye of toad, ear of
* bat, 15 years of failed attempts by David and a few fried brain
* cells.
*
* 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 version 2, as
* published by the Free Software Foundation.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: echo.c,v 1.20 2006/12/01 18:00:48 steveu Exp $
*/
/*! \file */
/* Implementation Notes
David Rowe
April 2007
This code started life as Steve's NLMS algorithm with a tap
rotation algorithm to handle divergence during double talk. I
added a Geigel Double Talk Detector (DTD) [2] and performed some
G168 tests. However I had trouble meeting the G168 requirements,
especially for double talk - there were always cases where my DTD
failed, for example where near end speech was under the 6dB
threshold required for declaring double talk.
So I tried a two path algorithm [1], which has so far given better
results. The original tap rotation/Geigel algorithm is available
in SVN http://svn.rowetel.com/software/oslec/tags/before_16bit.
It's probably possible to make it work if some one wants to put some
serious work into it.
At present no special treatment is provided for tones, which
generally cause NLMS algorithms to diverge. Initial runs of a
subset of the G168 tests for tones (e.g ./echo_test 6) show the
current algorithm is passing OK, which is kind of surprising. The
full set of tests needs to be performed to confirm this result.
One other interesting change is that I have managed to get the NLMS
code to work with 16 bit coefficients, rather than the original 32
bit coefficents. This reduces the MIPs and storage required.
I evaulated the 16 bit port using g168_tests.sh and listening tests
on 4 real-world samples.
I also attempted the implementation of a block based NLMS update
[2] but although this passes g168_tests.sh it didn't converge well
on the real-world samples. I have no idea why, perhaps a scaling
problem. The block based code is also available in SVN
http://svn.rowetel.com/software/oslec/tags/before_16bit. If this
code can be debugged, it will lead to further reduction in MIPS, as
the block update code maps nicely onto DSP instruction sets (it's a
dot product) compared to the current sample-by-sample update.
Steve also has some nice notes on echo cancellers in echo.h
References:
[1] Ochiai, Areseki, and Ogihara, "Echo Canceller with Two Echo
Path Models", IEEE Transactions on communications, COM-25,
No. 6, June
1977.
http://www.rowetel.com/images/echo/dual_path_paper.pdf
[2] The classic, very useful paper that tells you how to
actually build a real world echo canceller:
Messerschmitt, Hedberg, Cole, Haoui, Winship, "Digital Voice
Echo Canceller with a TMS320020,
http://www.rowetel.com/images/echo/spra129.pdf
[3] I have written a series of blog posts on this work, here is
Part 1: http://www.rowetel.com/blog/?p=18
[4] The source code http://svn.rowetel.com/software/oslec/
[5] A nice reference on LMS filters:
http://en.wikipedia.org/wiki/Least_mean_squares_filter
Credits:
Thanks to Steve Underwood, Jean-Marc Valin, and Ramakrishnan
Muthukrishnan for their suggestions and email discussions. Thanks
also to those people who collected echo samples for me such as
Mark, Pawel, and Pavel.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef __KERNEL__
#include <linux/kernel.h> /* We're doing kernel work */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#define malloc(a) kmalloc((a), GFP_KERNEL)
#define free(a) kfree(a)
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#endif
#include "bit_operations.h"
#include "echo_cancel.h"
#if !defined(NULL)
#define NULL (void *) 0
#endif
#if !defined(FALSE)
#define FALSE 0
#endif
#if !defined(TRUE)
#define TRUE (!FALSE)
#endif
#define MIN_TX_POWER_FOR_ADAPTION 64
#define MIN_RX_POWER_FOR_ADAPTION 64
#define DTD_HANGOVER 600 /* 600 samples, or 75ms */
#define DC_LOG2BETA 3 /* log2() of DC filter Beta */
/*-----------------------------------------------------------------------*\
FUNCTIONS
\*-----------------------------------------------------------------------*/
/* adapting coeffs using the traditional stochastic descent (N)LMS algorithm */
#ifdef __BLACKFIN_ASM__
static void __inline__ lms_adapt_bg(echo_can_state_t *ec, int clean, int shift)
{
int i, j;
int offset1;
int offset2;
int factor;
int exp;
int16_t *phist;
int n;
if (shift > 0)
factor = clean << shift;
else
factor = clean >> -shift;
/* Update the FIR taps */
offset2 = ec->curr_pos;
offset1 = ec->taps - offset2;
phist = &ec->fir_state_bg.history[offset2];
/* st: and en: help us locate the assembler in echo.s */
//asm("st:");
n = ec->taps;
for (i = 0, j = offset2; i < n; i++, j++)
{
exp = *phist++ * factor;
ec->fir_taps16[1][i] += (int16_t) ((exp+(1<<14)) >> 15);
}
//asm("en:");
/* Note the asm for the inner loop above generated by Blackfin gcc
4.1.1 is pretty good (note even parallel instructions used):
R0 = W [P0++] (X);
R0 *= R2;
R0 = R0 + R3 (NS) ||
R1 = W [P1] (X) ||
nop;
R0 >>>= 15;
R0 = R0 + R1;
W [P1++] = R0;
A block based update algorithm would be much faster but the
above can't be improved on much. Every instruction saved in
the loop above is 2 MIPs/ch! The for loop above is where the
Blackfin spends most of it's time - about 17 MIPs/ch measured
with speedtest.c with 256 taps (32ms). Write-back and
Write-through cache gave about the same performance.
*/
}
/*
IDEAS for further optimisation of lms_adapt_bg():
1/ The rounding is quite costly. Could we keep as 32 bit coeffs
then make filter pluck the MS 16-bits of the coeffs when filtering?
However this would lower potential optimisation of filter, as I
think the dual-MAC architecture requires packed 16 bit coeffs.
2/ Block based update would be more efficient, as per comments above,
could use dual MAC architecture.
3/ Look for same sample Blackfin LMS code, see if we can get dual-MAC
packing.
4/ Execute the whole e/c in a block of say 20ms rather than sample
by sample. Processing a few samples every ms is inefficient.
*/
#else
static __inline__ void lms_adapt_bg(echo_can_state_t *ec, int clean, int shift)
{
int i;
int offset1;
int offset2;
int factor;
int exp;
if (shift > 0)
factor = clean << shift;
else
factor = clean >> -shift;
/* Update the FIR taps */
offset2 = ec->curr_pos;
offset1 = ec->taps - offset2;
for (i = ec->taps - 1; i >= offset1; i--)
{
exp = (ec->fir_state_bg.history[i - offset1]*factor);
ec->fir_taps16[1][i] += (int16_t) ((exp+(1<<14)) >> 15);
}
for ( ; i >= 0; i--)
{
exp = (ec->fir_state_bg.history[i + offset2]*factor);
ec->fir_taps16[1][i] += (int16_t) ((exp+(1<<14)) >> 15);
}
}
#endif
/*- End of function --------------------------------------------------------*/
echo_can_state_t *echo_can_create(int len, int adaption_mode)
{
echo_can_state_t *ec;
int i;
int j;
ec = (echo_can_state_t *) malloc(sizeof(*ec));
if (ec == NULL)
return NULL;
memset(ec, 0, sizeof(*ec));
ec->taps = len;
ec->log2taps = top_bit(len);
ec->curr_pos = ec->taps - 1;
for (i = 0; i < 2; i++)
{
if ((ec->fir_taps16[i] = (int16_t *) malloc((ec->taps)*sizeof(int16_t))) == NULL)
{
for (j = 0; j < i; j++)
free(ec->fir_taps16[j]);
free(ec);
return NULL;
}
memset(ec->fir_taps16[i], 0, (ec->taps)*sizeof(int16_t));
}
fir16_create(&ec->fir_state,
ec->fir_taps16[0],
ec->taps);
fir16_create(&ec->fir_state_bg,
ec->fir_taps16[1],
ec->taps);
for(i=0; i<5; i++) {
ec->xvtx[i] = ec->yvtx[i] = ec->xvrx[i] = ec->yvrx[i] = 0;
}
ec->cng_level = 1000;
echo_can_adaption_mode(ec, adaption_mode);
ec->snapshot = (int16_t*)malloc(ec->taps*sizeof(int16_t));
memset(ec->snapshot, 0, sizeof(int16_t)*ec->taps);
ec->cond_met = 0;
ec->Pstates = 0;
ec->Ltxacc = ec->Lrxacc = ec->Lcleanacc = ec->Lclean_bgacc = 0;
ec->Ltx = ec->Lrx = ec->Lclean = ec->Lclean_bg = 0;
ec->tx_1 = ec->tx_2 = ec->rx_1 = ec->rx_2 = 0;
ec->Lbgn = ec->Lbgn_acc = 0;
ec->Lbgn_upper = 200;
ec->Lbgn_upper_acc = ec->Lbgn_upper << 13;
return ec;
}
/*- End of function --------------------------------------------------------*/
void echo_can_free(echo_can_state_t *ec)
{
int i;
fir16_free(&ec->fir_state);
fir16_free(&ec->fir_state_bg);
for (i = 0; i < 2; i++)
free(ec->fir_taps16[i]);
free(ec->snapshot);
free(ec);
}
/*- End of function --------------------------------------------------------*/
void echo_can_adaption_mode(echo_can_state_t *ec, int adaption_mode)
{
ec->adaption_mode = adaption_mode;
}
/*- End of function --------------------------------------------------------*/
void echo_can_flush(echo_can_state_t *ec)
{
int i;
ec->Ltxacc = ec->Lrxacc = ec->Lcleanacc = ec->Lclean_bgacc = 0;
ec->Ltx = ec->Lrx = ec->Lclean = ec->Lclean_bg = 0;
ec->tx_1 = ec->tx_2 = ec->rx_1 = ec->rx_2 = 0;
ec->Lbgn = ec->Lbgn_acc = 0;
ec->Lbgn_upper = 200;
ec->Lbgn_upper_acc = ec->Lbgn_upper << 13;
ec->nonupdate_dwell = 0;
fir16_flush(&ec->fir_state);
fir16_flush(&ec->fir_state_bg);
ec->fir_state.curr_pos = ec->taps - 1;
ec->fir_state_bg.curr_pos = ec->taps - 1;
for (i = 0; i < 2; i++)
memset(ec->fir_taps16[i], 0, ec->taps*sizeof(int16_t));
ec->curr_pos = ec->taps - 1;
ec->Pstates = 0;
}
/*- End of function --------------------------------------------------------*/
void echo_can_snapshot(echo_can_state_t *ec) {
memcpy(ec->snapshot, ec->fir_taps16[0], ec->taps*sizeof(int16_t));
}
/*- End of function --------------------------------------------------------*/
/* Dual Path Echo Canceller ------------------------------------------------*/
int16_t echo_can_update(echo_can_state_t *ec, int16_t tx, int16_t rx)
{
int32_t echo_value;
int clean_bg;
int tmp, tmp1;
/* Input scaling was found be required to prevent problems when tx
starts clipping. Another possible way to handle this would be the
filter coefficent scaling. */
ec->tx = tx; ec->rx = rx;
tx >>=1;
rx >>=1;
/*
Filter DC, 3dB point is 160Hz (I think), note 32 bit precision required
otherwise values do not track down to 0. Zero at DC, Pole at (1-Beta)
only real axis. Some chip sets (like Si labs) don't need
this, but something like a $10 X100P card does. Any DC really slows
down convergence.
Note: removes some low frequency from the signal, this reduces
the speech quality when listening to samples through headphones
but may not be obvious through a telephone handset.
Note that the 3dB frequency in radians is approx Beta, e.g. for
Beta = 2^(-3) = 0.125, 3dB freq is 0.125 rads = 159Hz.
*/
if (ec->adaption_mode & ECHO_CAN_USE_RX_HPF) {
tmp = rx << 15;
#if 1
/* Make sure the gain of the HPF is 1.0. This can still saturate a little under
impulse conditions, and it might roll to 32768 and need clipping on sustained peak
level signals. However, the scale of such clipping is small, and the error due to
any saturation should not markedly affect the downstream processing. */
tmp -= (tmp >> 4);
#endif
ec->rx_1 += -(ec->rx_1>>DC_LOG2BETA) + tmp - ec->rx_2;
/* hard limit filter to prevent clipping. Note that at this stage
rx should be limited to +/- 16383 due to right shift above */
tmp1 = ec->rx_1 >> 15;
if (tmp1 > 16383) tmp1 = 16383;
if (tmp1 < -16383) tmp1 = -16383;
rx = tmp1;
ec->rx_2 = tmp;
}
/* Block average of power in the filter states. Used for
adaption power calculation. */
{
int new, old;
/* efficient "out with the old and in with the new" algorithm so
we don't have to recalculate over the whole block of
samples. */
new = (int)tx * (int)tx;
old = (int)ec->fir_state.history[ec->fir_state.curr_pos] *
(int)ec->fir_state.history[ec->fir_state.curr_pos];
ec->Pstates += ((new - old) + (1<<(ec->log2taps-1))) >> ec->log2taps;
if (ec->Pstates < 0) ec->Pstates = 0;
}
/* Calculate short term average levels using simple single pole IIRs */
ec->Ltxacc += abs(tx) - ec->Ltx;
ec->Ltx = (ec->Ltxacc + (1<<4)) >> 5;
ec->Lrxacc += abs(rx) - ec->Lrx;
ec->Lrx = (ec->Lrxacc + (1<<4)) >> 5;
/* Foreground filter ---------------------------------------------------*/
ec->fir_state.coeffs = ec->fir_taps16[0];
echo_value = fir16(&ec->fir_state, tx);
ec->clean = rx - echo_value;
ec->Lcleanacc += abs(ec->clean) - ec->Lclean;
ec->Lclean = (ec->Lcleanacc + (1<<4)) >> 5;
/* Background filter ---------------------------------------------------*/
echo_value = fir16(&ec->fir_state_bg, tx);
clean_bg = rx - echo_value;
ec->Lclean_bgacc += abs(clean_bg) - ec->Lclean_bg;
ec->Lclean_bg = (ec->Lclean_bgacc + (1<<4)) >> 5;
/* Background Filter adaption -----------------------------------------*/
/* Almost always adap bg filter, just simple DT and energy
detection to minimise adaption in cases of strong double talk.
However this is not critical for the dual path algorithm.
*/
ec->factor = 0;
ec->shift = 0;
if ((ec->nonupdate_dwell == 0)) {
int P, logP, shift;
/* Determine:
f = Beta * clean_bg_rx/P ------ (1)
where P is the total power in the filter states.
The Boffins have shown that if we obey (1) we converge
quickly and avoid instability.
The correct factor f must be in Q30, as this is the fixed
point format required by the lms_adapt_bg() function,
therefore the scaled version of (1) is:
(2^30) * f = (2^30) * Beta * clean_bg_rx/P
factor = (2^30) * Beta * clean_bg_rx/P ----- (2)
We have chosen Beta = 0.25 by experiment, so:
factor = (2^30) * (2^-2) * clean_bg_rx/P
(30 - 2 - log2(P))
factor = clean_bg_rx 2 ----- (3)
To avoid a divide we approximate log2(P) as top_bit(P),
which returns the position of the highest non-zero bit in
P. This approximation introduces an error as large as a
factor of 2, but the algorithm seems to handle it OK.
Come to think of it a divide may not be a big deal on a
modern DSP, so its probably worth checking out the cycles
for a divide versus a top_bit() implementation.
*/
P = MIN_TX_POWER_FOR_ADAPTION + ec->Pstates;
logP = top_bit(P) + ec->log2taps;
shift = 30 - 2 - logP;
ec->shift = shift;
lms_adapt_bg(ec, clean_bg, shift);
}
/* very simple DTD to make sure we dont try and adapt with strong
near end speech */
ec->adapt = 0;
if ((ec->Lrx > MIN_RX_POWER_FOR_ADAPTION) && (ec->Lrx > ec->Ltx))
ec->nonupdate_dwell = DTD_HANGOVER;
if (ec->nonupdate_dwell)
ec->nonupdate_dwell--;
/* Transfer logic ------------------------------------------------------*/
/* These conditions are from the dual path paper [1], I messed with
them a bit to improve performance. */
if ((ec->adaption_mode & ECHO_CAN_USE_ADAPTION) &&
(ec->nonupdate_dwell == 0) &&
(8*ec->Lclean_bg < 7*ec->Lclean) /* (ec->Lclean_bg < 0.875*ec->Lclean) */ &&
(8*ec->Lclean_bg < ec->Ltx) /* (ec->Lclean_bg < 0.125*ec->Ltx) */ )
{
if (ec->cond_met == 6) {
/* BG filter has had better results for 6 consecutive samples */
ec->adapt = 1;
memcpy(ec->fir_taps16[0], ec->fir_taps16[1], ec->taps*sizeof(int16_t));
}
else
ec->cond_met++;
}
else
ec->cond_met = 0;
/* Non-Linear Processing ---------------------------------------------------*/
ec->clean_nlp = ec->clean;
if (ec->adaption_mode & ECHO_CAN_USE_NLP)
{
/* Non-linear processor - a fancy way to say "zap small signals, to avoid
residual echo due to (uLaw/ALaw) non-linearity in the channel.". */
if ((16*ec->Lclean < ec->Ltx))
{
/* Our e/c has improved echo by at least 24 dB (each factor of 2 is 6dB,
so 2*2*2*2=16 is the same as 6+6+6+6=24dB) */
if (ec->adaption_mode & ECHO_CAN_USE_CNG)
{
ec->cng_level = ec->Lbgn;
/* Very elementary comfort noise generation. Just random
numbers rolled off very vaguely Hoth-like. DR: This
noise doesn't sound quite right to me - I suspect there
are some overlfow issues in the filtering as it's too
"crackly". TODO: debug this, maybe just play noise at
high level or look at spectrum.
*/
ec->cng_rndnum = 1664525U*ec->cng_rndnum + 1013904223U;
ec->cng_filter = ((ec->cng_rndnum & 0xFFFF) - 32768 + 5*ec->cng_filter) >> 3;
ec->clean_nlp = (ec->cng_filter*ec->cng_level*8) >> 14;
}
else if (ec->adaption_mode & ECHO_CAN_USE_CLIP)
{
/* This sounds much better than CNG */
if (ec->clean_nlp > ec->Lbgn)
ec->clean_nlp = ec->Lbgn;
if (ec->clean_nlp < -ec->Lbgn)
ec->clean_nlp = -ec->Lbgn;
}
else
{
/* just mute the residual, doesn't sound very good, used mainly
in G168 tests */
ec->clean_nlp = 0;
}
}
else {
/* Background noise estimator. I tried a few algorithms
here without much luck. This very simple one seems to
work best, we just average the level using a slow (1 sec
time const) filter if the current level is less than a
(experimentally derived) constant. This means we dont
include high level signals like near end speech. When
combined with CNG or especially CLIP seems to work OK.
*/
if (ec->Lclean < 40) {
ec->Lbgn_acc += abs(ec->clean) - ec->Lbgn;
ec->Lbgn = (ec->Lbgn_acc + (1<<11)) >> 12;
}
}
}
/* Roll around the taps buffer */
if (ec->curr_pos <= 0)
ec->curr_pos = ec->taps;
ec->curr_pos--;
if (ec->adaption_mode & ECHO_CAN_DISABLE)
ec->clean_nlp = rx;
/* Output scaled back up again to match input scaling */
return (int16_t) ec->clean_nlp << 1;
}
/*- End of function --------------------------------------------------------*/
/* This function is seperated from the echo canceller is it is usually called
as part of the tx process. See rx HP (DC blocking) filter above, it's
the same design.
Some soft phones send speech signals with a lot of low frequency
energy, e.g. down to 20Hz. This can make the hybrid non-linear
which causes the echo canceller to fall over. This filter can help
by removing any low frequency before it gets to the tx port of the
hybrid.
It can also help by removing and DC in the tx signal. DC is bad
for LMS algorithms.
This is one of the classic DC removal filters, adjusted to provide sufficient
bass rolloff to meet the above requirement to protect hybrids from things that
upset them. The difference between successive samples produces a lousy HPF, and
then a suitably placed pole flattens things out. The final result is a nicely
rolled off bass end. The filtering is implemented with extended fractional
precision, which noise shapes things, giving very clean DC removal.
*/
int16_t echo_can_hpf_tx(echo_can_state_t *ec, int16_t tx) {
int tmp, tmp1;
if (ec->adaption_mode & ECHO_CAN_USE_TX_HPF) {
tmp = tx << 15;
#if 1
/* Make sure the gain of the HPF is 1.0. The first can still saturate a little under
impulse conditions, and it might roll to 32768 and need clipping on sustained peak
level signals. However, the scale of such clipping is small, and the error due to
any saturation should not markedly affect the downstream processing. */
tmp -= (tmp >> 4);
#endif
ec->tx_1 += -(ec->tx_1>>DC_LOG2BETA) + tmp - ec->tx_2;
tmp1 = ec->tx_1 >> 15;
if (tmp1 > 32767) tmp1 = 32767;
if (tmp1 < -32767) tmp1 = -32767;
tx = tmp1;
ec->tx_2 = tmp;
}
return tx;
}
/*- End of function --------------------------------------------------------*/
/*- End of file ------------------------------------------------------------*/

230
src/libecho/echo_cancel.h Normal file
View File

@ -0,0 +1,230 @@
/*
* SpanDSP - a series of DSP components for telephony
*
* echo.c - A line echo canceller. This code is being developed
* against and partially complies with G168.
*
* Written by Steve Underwood <steveu@coppice.org>
* and David Rowe <david_at_rowetel_dot_com>
*
* Copyright (C) 2001 Steve Underwood and 2007 David Rowe
*
* 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 version 2, as
* published by the Free Software Foundation.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: echo.h,v 1.9 2006/10/24 13:45:28 steveu Exp $
*/
/*! \file */
#if !defined(_ECHO_H_)
#define _ECHO_H_
/*! \page echo_can_page Line echo cancellation for voice
\section echo_can_page_sec_1 What does it do?
This module aims to provide G.168-2002 compliant echo cancellation, to remove
electrical echoes (e.g. from 2-4 wire hybrids) from voice calls.
\section echo_can_page_sec_2 How does it work?
The heart of the echo cancellor is FIR filter. This is adapted to match the echo
impulse response of the telephone line. It must be long enough to adequately cover
the duration of that impulse response. The signal transmitted to the telephone line
is passed through the FIR filter. Once the FIR is properly adapted, the resulting
output is an estimate of the echo signal received from the line. This is subtracted
from the received signal. The result is an estimate of the signal which originated
at the far end of the line, free from echos of our own transmitted signal.
The least mean squares (LMS) algorithm is attributed to Widrow and Hoff, and was
introduced in 1960. It is the commonest form of filter adaption used in things
like modem line equalisers and line echo cancellers. There it works very well.
However, it only works well for signals of constant amplitude. It works very poorly
for things like speech echo cancellation, where the signal level varies widely.
This is quite easy to fix. If the signal level is normalised - similar to applying
AGC - LMS can work as well for a signal of varying amplitude as it does for a modem
signal. This normalised least mean squares (NLMS) algorithm is the commonest one used
for speech echo cancellation. Many other algorithms exist - e.g. RLS (essentially
the same as Kalman filtering), FAP, etc. Some perform significantly better than NLMS.
However, factors such as computational complexity and patents favour the use of NLMS.
A simple refinement to NLMS can improve its performance with speech. NLMS tends
to adapt best to the strongest parts of a signal. If the signal is white noise,
the NLMS algorithm works very well. However, speech has more low frequency than
high frequency content. Pre-whitening (i.e. filtering the signal to flatten
its spectrum) the echo signal improves the adapt rate for speech, and ensures the
final residual signal is not heavily biased towards high frequencies. A very low
complexity filter is adequate for this, so pre-whitening adds little to the
compute requirements of the echo canceller.
An FIR filter adapted using pre-whitened NLMS performs well, provided certain
conditions are met:
- The transmitted signal has poor self-correlation.
- There is no signal being generated within the environment being cancelled.
The difficulty is that neither of these can be guaranteed.
If the adaption is performed while transmitting noise (or something fairly noise
like, such as voice) the adaption works very well. If the adaption is performed
while transmitting something highly correlative (typically narrow band energy
such as signalling tones or DTMF), the adaption can go seriously wrong. The reason
is there is only one solution for the adaption on a near random signal - the impulse
response of the line. For a repetitive signal, there are any number of solutions
which converge the adaption, and nothing guides the adaption to choose the generalised
one. Allowing an untrained canceller to converge on this kind of narrowband
energy probably a good thing, since at least it cancels the tones. Allowing a well
converged canceller to continue converging on such energy is just a way to ruin
its generalised adaption. A narrowband detector is needed, so adapation can be
suspended at appropriate times.
The adaption process is based on trying to eliminate the received signal. When
there is any signal from within the environment being cancelled it may upset the
adaption process. Similarly, if the signal we are transmitting is small, noise
may dominate and disturb the adaption process. If we can ensure that the
adaption is only performed when we are transmitting a significant signal level,
and the environment is not, things will be OK. Clearly, it is easy to tell when
we are sending a significant signal. Telling, if the environment is generating a
significant signal, and doing it with sufficient speed that the adaption will
not have diverged too much more we stop it, is a little harder.
The key problem in detecting when the environment is sourcing significant energy
is that we must do this very quickly. Given a reasonably long sample of the
received signal, there are a number of strategies which may be used to assess
whether that signal contains a strong far end component. However, by the time
that assessment is complete the far end signal will have already caused major
mis-convergence in the adaption process. An assessment algorithm is needed which
produces a fairly accurate result from a very short burst of far end energy.
\section echo_can_page_sec_3 How do I use it?
The echo cancellor processes both the transmit and receive streams sample by
sample. The processing function is not declared inline. Unfortunately,
cancellation requires many operations per sample, so the call overhead is only a
minor burden.
*/
#include "../../config.h"
#ifdef HAVE_MMX_INSTRUCTIONS
#define USE_MMX
#endif
#ifdef HAVE_SSE2_INSTRUCTIONS
#define USE_SSE2
#endif
#include "fir.h"
/* Mask bits for the adaption mode */
#define ECHO_CAN_USE_ADAPTION 0x01
#define ECHO_CAN_USE_NLP 0x02
#define ECHO_CAN_USE_CNG 0x04
#define ECHO_CAN_USE_CLIP 0x08
#define ECHO_CAN_USE_TX_HPF 0x10
#define ECHO_CAN_USE_RX_HPF 0x20
#define ECHO_CAN_DISABLE 0x40
/*!
G.168 echo canceller descriptor. This defines the working state for a line
echo canceller.
*/
typedef struct
{
int16_t tx,rx;
int16_t clean;
int16_t clean_nlp;
int nonupdate_dwell;
int curr_pos;
int taps;
int log2taps;
int adaption_mode;
int cond_met;
int32_t Pstates;
int16_t adapt;
int32_t factor;
int16_t shift;
/* Average levels and averaging filter states */
int Ltxacc, Lrxacc, Lcleanacc, Lclean_bgacc;
int Ltx, Lrx;
int Lclean;
int Lclean_bg;
int Lbgn, Lbgn_acc, Lbgn_upper, Lbgn_upper_acc;
/* foreground and background filter states */
fir16_state_t fir_state;
fir16_state_t fir_state_bg;
int16_t *fir_taps16[2];
/* DC blocking filter states */
int tx_1, tx_2, rx_1, rx_2;
/* optional High Pass Filter states */
int32_t xvtx[5], yvtx[5];
int32_t xvrx[5], yvrx[5];
/* Parameters for the optional Hoth noise generator */
int cng_level;
int cng_rndnum;
int cng_filter;
/* snapshot sample of coeffs used for development */
int16_t *snapshot;
} echo_can_state_t;
/*! Create a voice echo canceller context.
\param len The length of the canceller, in samples.
\return The new canceller context, or NULL if the canceller could not be created.
*/
echo_can_state_t *echo_can_create(int len, int adaption_mode);
/*! Free a voice echo canceller context.
\param ec The echo canceller context.
*/
void echo_can_free(echo_can_state_t *ec);
/*! Flush (reinitialise) a voice echo canceller context.
\param ec The echo canceller context.
*/
void echo_can_flush(echo_can_state_t *ec);
/*! Set the adaption mode of a voice echo canceller context.
\param ec The echo canceller context.
\param adapt The mode.
*/
void echo_can_adaption_mode(echo_can_state_t *ec, int adaption_mode);
void echo_can_snapshot(echo_can_state_t *ec);
/*! Process a sample through a voice echo canceller.
\param ec The echo canceller context.
\param tx The transmitted audio sample.
\param rx The received audio sample.
\return The clean (echo cancelled) received sample.
*/
int16_t echo_can_update(echo_can_state_t *ec, int16_t tx, int16_t rx);
/*! Process to high pass filter the tx signal.
\param ec The echo canceller context.
\param tx The transmitted auio sample.
\return The HP filtered transmit sample, send this to your D/A.
*/
int16_t echo_can_hpf_tx(echo_can_state_t *ec, int16_t tx);
#endif
/*- End of file ------------------------------------------------------------*/

357
src/libecho/echo_suppress.c Normal file
View File

@ -0,0 +1,357 @@
/*
* (C) 2023 by Andreas Eversberg <andreas@eversberg.eu>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* This Echo Suppressor is based on ITU-T G.164, but it does not conform with it!
*
* The 'receive' signal (received by the interface) will cause an echo what is
* included in the 'send' signal (sent by the interface). To remove echo, the
* 'send' signal is muted when speech is received by the interface.
*
* Figure 6 / G,164 shows a diagram with the states in this code:
*
* The 'silence' state is depicted as X. It is entered when both sides do not
* talk.
*
* The 'suppression' state is depticted as Z. It is entered when speech is
* received by the interface.
*
* The 'break-in' state is depicted as W. It is entered when speech is sent to
* the interface.
*
* The depiced area V is implemented as a 6 dB loss, so that a transition from
* 'break-in' state to 'suppession' state requires 6 dB more level in the
* receive path.
*
* The input is based on an int16_t that has a range of +3 dBm.
*
* Timers are used to enter state (operate) or leave state (hangover).
*
* Six filters are used, to detect the speech level, three in each direction:
*
* A low-pass filter and a high-pass filter are used to limit the band within
* the 500-3400 Hz range.
*
* The the signal is rectified, amplified and filtered with a low-pass filter.
* The resulting level represents the average volume of the input. (The peak
* level of a sine wave at the input will result in same level on the output.)
*
* In the 'suppression' state, the send signal is muted.
*
* In the 'break-in' state, the receive signal is NOT changed. The loss is only
* inserted in the processing, not is the actual signal.
*/
#include <osmocom/core/talloc.h>
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
#include "echo_suppress.h"
#define db2level(db) pow(10, (double)db / 20.0)
#define level2db(level) (20 * log10(level))
#define SUPPRESS_THRESHOLD -31.0
#define RELEASE_SUPPRESS -31.0
#define INSERTION_LOSS 6.0
#define DT_MIN_LEVEL_DB -20
#define DT_MAX_OFFSET_DB 0.2
#define SUPPRESS_OPERATE 0.000
#define SUPPRESS_HANGOVER 0.050
#define BREAK_IN_OPERATE 0.030
#define BREAK_IN_HANGOVER 0.050
#define DT_INTERVAL 0.250
#define LP_FREQUENCY 3400.0
#define HP_FREQUENCY 500.0
#define ENV_FREQUENCY 500.0
#define DT_ENV_FREQUENCY 10.0
//#define DEBUG_ES
/*
* IIR filter
*/
static inline void iir_lowpass_init(iir_filter_t *filter, float frequency, int samplerate)
{
float Fc, Q, K, norm;
Q = pow(sqrt(0.5), 1.0); /* 0.7071 */
Fc = frequency / (float)samplerate;
K = tan(M_PI * Fc);
norm = 1 / (1 + K / Q + K * K);
filter->a0 = K * K * norm;
filter->a1 = 2 * filter->a0;
filter->a2 = filter->a0;
filter->b1 = 2 * (K * K - 1) * norm;
filter->b2 = (1 - K / Q + K * K) * norm;
}
static inline void iir_highpass_init(iir_filter_t *filter, float frequency, int samplerate)
{
float Fc, Q, K, norm;
Q = pow(sqrt(0.5), 1.0); /* 0.7071 */
Fc = frequency / (float)samplerate;
K = tan(M_PI * Fc);
norm = 1 / (1 + K / Q + K * K);
filter->a0 = 1 * norm;
filter->a1 = -2 * filter->a0;
filter->a2 = filter->a0;
filter->b1 = 2 * (K * K - 1) * norm;
filter->b2 = (1 - K / Q + K * K) * norm;
}
static inline void iir_process(iir_filter_t *filter, float *samples, int length)
{
float a0, a1, a2, b1, b2;
float z1, z2;
float in, out;
int i;
/* get states */
a0 = filter->a0;
a1 = filter->a1;
a2 = filter->a2;
b1 = filter->b1;
b2 = filter->b2;
z1 = filter->z1;
z2 = filter->z2;
/* process filter */
for (i = 0; i < length; i++) {
/* add a small value, otherwise this loop will perform really bad on 0-samples */
in = *samples + 0.000000001;
out = in * a0 + z1;
z1 = in * a1 + z2 - b1 * out;
z2 = in * a2 - b2 * out;
in = out;
*samples++ = in;
}
filter->z1 = z1;
filter->z2 = z2;
}
static inline void iir_reset(iir_filter_t *filter)
{
filter->z1 = 0.0;
filter->z2 = 0.0;
}
/*
* echo suppressor
*/
echo_sup_state_t *echo_sup_create(void *ctx, int samplerate)
{
echo_sup_state_t *es;
es = talloc_zero(ctx, struct echo_sup_state);
if (!es)
return NULL;
es->suppress_threshold = db2level(SUPPRESS_THRESHOLD);
es->release_suppress = db2level(RELEASE_SUPPRESS);
es->insertion_loss = db2level(INSERTION_LOSS);
es->suppress_operate = floor(SUPPRESS_OPERATE * (double)samplerate);
es->suppress_hangover = floor(SUPPRESS_HANGOVER * (double)samplerate);
es->break_in_operate = floor(BREAK_IN_OPERATE * (double)samplerate);
es->break_in_hangover = floor(BREAK_IN_HANGOVER * (double)samplerate);
iir_lowpass_init(&es->lp_send, LP_FREQUENCY, samplerate);
iir_lowpass_init(&es->lp_receive, LP_FREQUENCY, samplerate);
iir_highpass_init(&es->hp_send, HP_FREQUENCY, samplerate);
iir_highpass_init(&es->hp_receive, HP_FREQUENCY, samplerate);
iir_lowpass_init(&es->env_send, ENV_FREQUENCY, samplerate);
iir_lowpass_init(&es->env_receive, ENV_FREQUENCY, samplerate);
iir_lowpass_init(&es->dt_receive, DT_ENV_FREQUENCY, samplerate);
es->dt_interval = floor(DT_INTERVAL * (double)samplerate);
return es;
}
void echo_sup_free(echo_sup_state_t *es)
{
talloc_free(es);
}
/* rx is what we received from user interface, aka 'send' */
/* tx is what we sent to user interface, aka 'receive' */
int16_t echo_sup_update(echo_sup_state_t *es, int16_t tx, int16_t rx)
{
float send, receive, dt, offset;
/* convert from integer samples to float (@ 0 dBm scale) */
receive = (float)tx / 23196.0; /* towards user interface */
send = (float)rx / 23196.0; /* from user interface */
/* filter band */
iir_process(&es->lp_send, &send, 1);
iir_process(&es->lp_receive, &receive, 1);
iir_process(&es->hp_send, &send, 1);
iir_process(&es->hp_receive, &receive, 1);
/* get absolute value (rectifying with 3 dB gain) */
send = fabsf(send) * 1.4142136;
receive = dt = fabsf(receive) * 1.4142136;
/* filter envelope */
iir_process(&es->env_send, &send, 1);
iir_process(&es->env_receive, &receive, 1);
iir_process(&es->dt_receive, &dt, 1);
/* detect dial tone and do not suppress while it is detected */
if (++es->dt_timer == es->dt_interval) {
#ifdef DEBUG_ES
printf("dt_level=%.3f dt_offset=%.3f*\n", level2db(es->dt_level), level2db(es->dt_offset));
#endif
if (level2db(es->dt_level) > DT_MIN_LEVEL_DB && level2db(es->dt_offset) < DT_MAX_OFFSET_DB) {
if (!es->dt_disable) {
#ifdef DEBUG_ES
printf("Detected dial tone, disabling suppressor\n");
#endif
es->dt_disable = true;
}
} else {
if (es->dt_disable) {
#ifdef DEBUG_ES
printf("dial tone ceases, enabling suppressor\n");
#endif
es->dt_disable = false;
es->state = SUP_STATE_SILENCE;
es->timer = 0;
}
}
es->dt_timer = 0;
es->dt_level = dt;
es->dt_offset = 0.0;
}
offset = fabsf(es->dt_level / dt);
if (offset > es->dt_offset)
es->dt_offset = offset;
if (es->dt_disable)
return rx;
#ifdef DEBUG_ES
static int debug_interval = 0;
float send_max = 0, receive_max = 0;
if (send > send_max)
send_max = send;
if (receive > receive_max)
receive_max = receive;
if (++debug_interval == 1000) {
printf("Level (Ls=%.0f Lr=%.0f)\n", level2db(send_max), level2db(receive_max));
debug_interval = 0;
send_max = 0;
receive_max = 0;
}
#endif
switch (es->state) {
case SUP_STATE_SILENCE:
if (receive <= es->suppress_threshold && send <= es->suppress_threshold) {
es->timer = 0;
break;
}
if (receive < send) {
if (++es->timer < es->break_in_operate)
break;
#ifdef DEBUG_ES
printf("Change from silence to break-in\n");
#endif
es->state = SUP_STATE_BREAK_IN;
} else {
if (++es->timer < es->suppress_operate)
break;
#ifdef DEBUG_ES
printf("Change from silence to suppression\n");
#endif
es->state = SUP_STATE_SUPPRESSION;
}
es->timer = 0;
break;
case SUP_STATE_SUPPRESSION:
if (receive <= es->release_suppress && send <= es->release_suppress) {
if (++es->timer < es->suppress_hangover)
break;
#ifdef DEBUG_ES
printf("Change from suppression to silence\n");
#endif
es->state = SUP_STATE_SILENCE;
es->timer = 0;
break;
}
if (receive < send) {
if (++es->timer < es->break_in_operate)
break;
#ifdef DEBUG_ES
printf("Change from suppression to break-in\n");
#endif
es->state = SUP_STATE_BREAK_IN;
}
es->timer = 0;
break;
case SUP_STATE_BREAK_IN:
if (receive <= es->suppress_threshold && send <= es->suppress_threshold) {
if (++es->timer < es->break_in_hangover)
break;
#ifdef DEBUG_ES
printf("Change from break-in to silence\n");
#endif
es->state = SUP_STATE_SILENCE;
es->timer = 0;
break;
}
/* insert loss, so that receive level must be 6 dB higher than send level */
if (receive / es->insertion_loss > send) {
if (++es->timer < es->break_in_hangover)
break;
#ifdef DEBUG_ES
printf("Change from break-in to suppression\n");
#endif
es->state = SUP_STATE_SUPPRESSION;
}
es->timer = 0;
break;
}
if (es->state == SUP_STATE_SUPPRESSION)
return 0;
return rx;
}
void echo_sup_flush(echo_sup_state_t *es)
{
es->state = SUP_STATE_SILENCE;
es->timer = 0;
iir_reset(&es->lp_send);
iir_reset(&es->lp_receive);
iir_reset(&es->hp_send);
iir_reset(&es->hp_receive);
iir_reset(&es->env_send);
iir_reset(&es->env_receive);
}

View File

@ -0,0 +1,35 @@
typedef struct iir_filter {
float a0, a1, a2, b1, b2;
float z1, z2;
} iir_filter_t;
enum sup_state {
SUP_STATE_SILENCE,
SUP_STATE_SUPPRESSION,
SUP_STATE_BREAK_IN,
};
typedef struct echo_sup_state {
float suppress_threshold; /* linear value of suppression th */
float release_suppress; /* linear value of release suppression th */
float insertion_loss; /* linear value of insertion loss */
int suppress_operate; /* number of sample until transition */
int suppress_hangover;
int break_in_operate;
int break_in_hangover;
enum sup_state state; /* current state */
int timer; /* timer to delay transition */
iir_filter_t lp_send, lp_receive; /* filter to cut off high frequencies */
iir_filter_t hp_send, hp_receive; /* filter to cut off low frequencies */
iir_filter_t env_send, env_receive; /* filter to get the envelope after rectifying */
iir_filter_t dt_receive; /* filter to detect steady tone (dial tone) */
int dt_timer, dt_interval; /* timer of dial tone detection interval */
float dt_level, dt_offset; /* initial level and max offset during interval */
bool dt_disable; /* true if suppressor is disabled, due to dial tone */
} echo_sup_state_t;
echo_sup_state_t *echo_sup_create(void *ctx, int samplerate);
void echo_sup_free(echo_sup_state_t *es);
int16_t echo_sup_update(echo_sup_state_t *es, int16_t tx, int16_t rx);
void echo_sup_flush(echo_sup_state_t *es);

369
src/libecho/fir.h Normal file
View File

@ -0,0 +1,369 @@
/*
* SpanDSP - a series of DSP components for telephony
*
* fir.h - General telephony FIR routines
*
* Written by Steve Underwood <steveu@coppice.org>
*
* Copyright (C) 2002 Steve Underwood
*
* 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 version 2, as
* published by the Free Software Foundation.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: fir.h,v 1.8 2006/10/24 13:45:28 steveu Exp $
*/
/*! \page fir_page FIR filtering
\section fir_page_sec_1 What does it do?
???.
\section fir_page_sec_2 How does it work?
???.
*/
#if !defined(_FIR_H_)
#define _FIR_H_
/*
Blackfin NOTES & IDEAS:
A simple dot product function is used to implement the filter. This performs
just one MAC/cycle which is inefficient but was easy to implement as a first
pass. The current Blackfin code also uses an unrolled form of the filter
history to avoid 0 length hardware loop issues. This is wasteful of
memory.
Ideas for improvement:
1/ Rewrite filter for dual MAC inner loop. The issue here is handling
history sample offsets that are 16 bit aligned - the dual MAC needs
32 bit aligmnent. There are some good examples in libbfdsp.
2/ Use the hardware circular buffer facility tohalve memory usage.
3/ Consider using internal memory.
Using less memory might also improve speed as cache misses will be
reduced. A drop in MIPs and memory approaching 50% should be
possible.
The foreground and background filters currenlty use a total of
about 10 MIPs/ch as measured with speedtest.c on a 256 TAP echo
can.
*/
#if defined(USE_MMX) || defined(USE_SSE2)
#include "mmx.h"
#endif
/*!
16 bit integer FIR descriptor. This defines the working state for a single
instance of an FIR filter using 16 bit integer coefficients.
*/
typedef struct
{
int taps;
int curr_pos;
const int16_t *coeffs;
int16_t *history;
} fir16_state_t;
/*!
32 bit integer FIR descriptor. This defines the working state for a single
instance of an FIR filter using 32 bit integer coefficients, and filtering
16 bit integer data.
*/
typedef struct
{
int taps;
int curr_pos;
const int32_t *coeffs;
int16_t *history;
} fir32_state_t;
/*!
Floating point FIR descriptor. This defines the working state for a single
instance of an FIR filter using floating point coefficients and data.
*/
typedef struct
{
int taps;
int curr_pos;
const float *coeffs;
float *history;
} fir_float_state_t;
#ifdef __cplusplus
extern "C" {
#endif
static __inline__ const int16_t *fir16_create(fir16_state_t *fir,
const int16_t *coeffs,
int taps)
{
fir->taps = taps;
fir->curr_pos = taps - 1;
fir->coeffs = coeffs;
#if defined(USE_MMX) || defined(USE_SSE2) || defined(__BLACKFIN_ASM__)
if ((fir->history = malloc(2*taps*sizeof(int16_t))))
memset(fir->history, 0, 2*taps*sizeof(int16_t));
#else
if ((fir->history = (int16_t *) malloc(taps*sizeof(int16_t))))
memset(fir->history, 0, taps*sizeof(int16_t));
#endif
return fir->history;
}
/*- End of function --------------------------------------------------------*/
static __inline__ void fir16_flush(fir16_state_t *fir)
{
#if defined(USE_MMX) || defined(USE_SSE2) || defined(__BLACKFIN_ASM__)
memset(fir->history, 0, 2*fir->taps*sizeof(int16_t));
#else
memset(fir->history, 0, fir->taps*sizeof(int16_t));
#endif
}
/*- End of function --------------------------------------------------------*/
static __inline__ void fir16_free(fir16_state_t *fir)
{
free(fir->history);
}
/*- End of function --------------------------------------------------------*/
#ifdef __BLACKFIN_ASM__
static inline int32_t dot_asm(short *x, short *y, int len)
{
int dot;
len--;
__asm__
(
"I0 = %1;\n\t"
"I1 = %2;\n\t"
"A0 = 0;\n\t"
"R0.L = W[I0++] || R1.L = W[I1++];\n\t"
"LOOP dot%= LC0 = %3;\n\t"
"LOOP_BEGIN dot%=;\n\t"
"A0 += R0.L * R1.L (IS) || R0.L = W[I0++] || R1.L = W[I1++];\n\t"
"LOOP_END dot%=;\n\t"
"A0 += R0.L*R1.L (IS);\n\t"
"R0 = A0;\n\t"
"%0 = R0;\n\t"
: "=&d" (dot)
: "a" (x), "a" (y), "a" (len)
: "I0", "I1", "A1", "A0", "R0", "R1"
);
return dot;
}
#endif
/*- End of function --------------------------------------------------------*/
static __inline__ int16_t fir16(fir16_state_t *fir, int16_t sample)
{
int32_t y;
#if defined(USE_MMX)
int i;
mmx_t *mmx_coeffs;
mmx_t *mmx_hist;
fir->history[fir->curr_pos] = sample;
fir->history[fir->curr_pos + fir->taps] = sample;
mmx_coeffs = (mmx_t *) fir->coeffs;
mmx_hist = (mmx_t *) &fir->history[fir->curr_pos];
i = fir->taps;
pxor_r2r(mm4, mm4);
/* 8 samples per iteration, so the filter must be a multiple of 8 long. */
while (i > 0)
{
movq_m2r(mmx_coeffs[0], mm0);
movq_m2r(mmx_coeffs[1], mm2);
movq_m2r(mmx_hist[0], mm1);
movq_m2r(mmx_hist[1], mm3);
mmx_coeffs += 2;
mmx_hist += 2;
pmaddwd_r2r(mm1, mm0);
pmaddwd_r2r(mm3, mm2);
paddd_r2r(mm0, mm4);
paddd_r2r(mm2, mm4);
i -= 8;
}
movq_r2r(mm4, mm0);
psrlq_i2r(32, mm0);
paddd_r2r(mm0, mm4);
movd_r2m(mm4, y);
emms();
#elif defined(USE_SSE2)
int i;
xmm_t *xmm_coeffs;
xmm_t *xmm_hist;
fir->history[fir->curr_pos] = sample;
fir->history[fir->curr_pos + fir->taps] = sample;
xmm_coeffs = (xmm_t *) fir->coeffs;
xmm_hist = (xmm_t *) &fir->history[fir->curr_pos];
i = fir->taps;
pxor_r2r(xmm4, xmm4);
/* 16 samples per iteration, so the filter must be a multiple of 16 long. */
while (i > 0)
{
movdqu_m2r(xmm_coeffs[0], xmm0);
movdqu_m2r(xmm_coeffs[1], xmm2);
movdqu_m2r(xmm_hist[0], xmm1);
movdqu_m2r(xmm_hist[1], xmm3);
xmm_coeffs += 2;
xmm_hist += 2;
pmaddwd_r2r(xmm1, xmm0);
pmaddwd_r2r(xmm3, xmm2);
paddd_r2r(xmm0, xmm4);
paddd_r2r(xmm2, xmm4);
i -= 16;
}
movdqa_r2r(xmm4, xmm0);
psrldq_i2r(8, xmm0);
paddd_r2r(xmm0, xmm4);
movdqa_r2r(xmm4, xmm0);
psrldq_i2r(4, xmm0);
paddd_r2r(xmm0, xmm4);
movd_r2m(xmm4, y);
#elif defined(__BLACKFIN_ASM__)
fir->history[fir->curr_pos] = sample;
fir->history[fir->curr_pos + fir->taps] = sample;
y = dot_asm((int16_t*)fir->coeffs, &fir->history[fir->curr_pos], fir->taps);
#else
int i;
int offset1;
int offset2;
fir->history[fir->curr_pos] = sample;
offset2 = fir->curr_pos;
offset1 = fir->taps - offset2;
y = 0;
for (i = fir->taps - 1; i >= offset1; i--)
y += fir->coeffs[i]*fir->history[i - offset1];
for ( ; i >= 0; i--)
y += fir->coeffs[i]*fir->history[i + offset2];
#endif
if (fir->curr_pos <= 0)
fir->curr_pos = fir->taps;
fir->curr_pos--;
return (int16_t) (y >> 15);
}
/*- End of function --------------------------------------------------------*/
static __inline__ const int16_t *fir32_create(fir32_state_t *fir,
const int32_t *coeffs,
int taps)
{
fir->taps = taps;
fir->curr_pos = taps - 1;
fir->coeffs = coeffs;
fir->history = (int16_t *) malloc(taps*sizeof(int16_t));
if (fir->history)
memset(fir->history, '\0', taps*sizeof(int16_t));
return fir->history;
}
/*- End of function --------------------------------------------------------*/
static __inline__ void fir32_flush(fir32_state_t *fir)
{
memset(fir->history, 0, fir->taps*sizeof(int16_t));
}
/*- End of function --------------------------------------------------------*/
static __inline__ void fir32_free(fir32_state_t *fir)
{
free(fir->history);
}
/*- End of function --------------------------------------------------------*/
static __inline__ int16_t fir32(fir32_state_t *fir, int16_t sample)
{
int i;
int32_t y;
int offset1;
int offset2;
fir->history[fir->curr_pos] = sample;
offset2 = fir->curr_pos;
offset1 = fir->taps - offset2;
y = 0;
for (i = fir->taps - 1; i >= offset1; i--)
y += fir->coeffs[i]*fir->history[i - offset1];
for ( ; i >= 0; i--)
y += fir->coeffs[i]*fir->history[i + offset2];
if (fir->curr_pos <= 0)
fir->curr_pos = fir->taps;
fir->curr_pos--;
return (int16_t) (y >> 15);
}
/*- End of function --------------------------------------------------------*/
#ifndef __KERNEL__
static __inline__ const float *fir_float_create(fir_float_state_t *fir,
const float *coeffs,
int taps)
{
fir->taps = taps;
fir->curr_pos = taps - 1;
fir->coeffs = coeffs;
fir->history = (float *) malloc(taps*sizeof(float));
if (fir->history)
memset(fir->history, '\0', taps*sizeof(float));
return fir->history;
}
/*- End of function --------------------------------------------------------*/
static __inline__ void fir_float_free(fir_float_state_t *fir)
{
free(fir->history);
}
/*- End of function --------------------------------------------------------*/
static __inline__ int16_t fir_float(fir_float_state_t *fir, int16_t sample)
{
int i;
float y;
int offset1;
int offset2;
fir->history[fir->curr_pos] = sample;
offset2 = fir->curr_pos;
offset1 = fir->taps - offset2;
y = 0;
for (i = fir->taps - 1; i >= offset1; i--)
y += fir->coeffs[i]*fir->history[i - offset1];
for ( ; i >= 0; i--)
y += fir->coeffs[i]*fir->history[i + offset2];
if (fir->curr_pos <= 0)
fir->curr_pos = fir->taps;
fir->curr_pos--;
return (int16_t) y;
}
/*- End of function --------------------------------------------------------*/
#endif
#ifdef __cplusplus
}
#endif
#endif
/*- End of file ------------------------------------------------------------*/

288
src/libecho/mmx.h Normal file
View File

@ -0,0 +1,288 @@
/*
* mmx.h
* Copyright (C) 1997-2001 H. Dietz and R. Fisher
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef AVCODEC_I386MMX_H
#define AVCODEC_I386MMX_H
/*
* The type of an value that fits in an MMX register (note that long
* long constant values MUST be suffixed by LL and unsigned long long
* values by ULL, lest they be truncated by the compiler)
*/
typedef union {
long long q; /* Quadword (64-bit) value */
unsigned long long uq; /* Unsigned Quadword */
int d[2]; /* 2 Doubleword (32-bit) values */
unsigned int ud[2]; /* 2 Unsigned Doubleword */
short w[4]; /* 4 Word (16-bit) values */
unsigned short uw[4]; /* 4 Unsigned Word */
char b[8]; /* 8 Byte (8-bit) values */
unsigned char ub[8]; /* 8 Unsigned Byte */
float s[2]; /* Single-precision (32-bit) value */
} mmx_t; /* On an 8-byte (64-bit) boundary */
/* SSE registers */
typedef union {
char b[16];
} xmm_t;
#define mmx_i2r(op,imm,reg) \
__asm__ __volatile__ (#op " %0, %%" #reg \
: /* nothing */ \
: "i" (imm) )
#define mmx_m2r(op,mem,reg) \
__asm__ __volatile__ (#op " %0, %%" #reg \
: /* nothing */ \
: "m" (mem))
#define mmx_r2m(op,reg,mem) \
__asm__ __volatile__ (#op " %%" #reg ", %0" \
: "=m" (mem) \
: /* nothing */ )
#define mmx_r2r(op,regs,regd) \
__asm__ __volatile__ (#op " %" #regs ", %" #regd)
#define emms() __asm__ __volatile__ ("emms")
#define movd_m2r(var,reg) mmx_m2r (movd, var, reg)
#define movd_r2m(reg,var) mmx_r2m (movd, reg, var)
#define movd_r2r(regs,regd) mmx_r2r (movd, regs, regd)
#define movq_m2r(var,reg) mmx_m2r (movq, var, reg)
#define movq_r2m(reg,var) mmx_r2m (movq, reg, var)
#define movq_r2r(regs,regd) mmx_r2r (movq, regs, regd)
#define packssdw_m2r(var,reg) mmx_m2r (packssdw, var, reg)
#define packssdw_r2r(regs,regd) mmx_r2r (packssdw, regs, regd)
#define packsswb_m2r(var,reg) mmx_m2r (packsswb, var, reg)
#define packsswb_r2r(regs,regd) mmx_r2r (packsswb, regs, regd)
#define packuswb_m2r(var,reg) mmx_m2r (packuswb, var, reg)
#define packuswb_r2r(regs,regd) mmx_r2r (packuswb, regs, regd)
#define paddb_m2r(var,reg) mmx_m2r (paddb, var, reg)
#define paddb_r2r(regs,regd) mmx_r2r (paddb, regs, regd)
#define paddd_m2r(var,reg) mmx_m2r (paddd, var, reg)
#define paddd_r2r(regs,regd) mmx_r2r (paddd, regs, regd)
#define paddw_m2r(var,reg) mmx_m2r (paddw, var, reg)
#define paddw_r2r(regs,regd) mmx_r2r (paddw, regs, regd)
#define paddsb_m2r(var,reg) mmx_m2r (paddsb, var, reg)
#define paddsb_r2r(regs,regd) mmx_r2r (paddsb, regs, regd)
#define paddsw_m2r(var,reg) mmx_m2r (paddsw, var, reg)
#define paddsw_r2r(regs,regd) mmx_r2r (paddsw, regs, regd)
#define paddusb_m2r(var,reg) mmx_m2r (paddusb, var, reg)
#define paddusb_r2r(regs,regd) mmx_r2r (paddusb, regs, regd)
#define paddusw_m2r(var,reg) mmx_m2r (paddusw, var, reg)
#define paddusw_r2r(regs,regd) mmx_r2r (paddusw, regs, regd)
#define pand_m2r(var,reg) mmx_m2r (pand, var, reg)
#define pand_r2r(regs,regd) mmx_r2r (pand, regs, regd)
#define pandn_m2r(var,reg) mmx_m2r (pandn, var, reg)
#define pandn_r2r(regs,regd) mmx_r2r (pandn, regs, regd)
#define pcmpeqb_m2r(var,reg) mmx_m2r (pcmpeqb, var, reg)
#define pcmpeqb_r2r(regs,regd) mmx_r2r (pcmpeqb, regs, regd)
#define pcmpeqd_m2r(var,reg) mmx_m2r (pcmpeqd, var, reg)
#define pcmpeqd_r2r(regs,regd) mmx_r2r (pcmpeqd, regs, regd)
#define pcmpeqw_m2r(var,reg) mmx_m2r (pcmpeqw, var, reg)
#define pcmpeqw_r2r(regs,regd) mmx_r2r (pcmpeqw, regs, regd)
#define pcmpgtb_m2r(var,reg) mmx_m2r (pcmpgtb, var, reg)
#define pcmpgtb_r2r(regs,regd) mmx_r2r (pcmpgtb, regs, regd)
#define pcmpgtd_m2r(var,reg) mmx_m2r (pcmpgtd, var, reg)
#define pcmpgtd_r2r(regs,regd) mmx_r2r (pcmpgtd, regs, regd)
#define pcmpgtw_m2r(var,reg) mmx_m2r (pcmpgtw, var, reg)
#define pcmpgtw_r2r(regs,regd) mmx_r2r (pcmpgtw, regs, regd)
#define pmaddwd_m2r(var,reg) mmx_m2r (pmaddwd, var, reg)
#define pmaddwd_r2r(regs,regd) mmx_r2r (pmaddwd, regs, regd)
#define pmulhw_m2r(var,reg) mmx_m2r (pmulhw, var, reg)
#define pmulhw_r2r(regs,regd) mmx_r2r (pmulhw, regs, regd)
#define pmullw_m2r(var,reg) mmx_m2r (pmullw, var, reg)
#define pmullw_r2r(regs,regd) mmx_r2r (pmullw, regs, regd)
#define por_m2r(var,reg) mmx_m2r (por, var, reg)
#define por_r2r(regs,regd) mmx_r2r (por, regs, regd)
#define pslld_i2r(imm,reg) mmx_i2r (pslld, imm, reg)
#define pslld_m2r(var,reg) mmx_m2r (pslld, var, reg)
#define pslld_r2r(regs,regd) mmx_r2r (pslld, regs, regd)
#define psllq_i2r(imm,reg) mmx_i2r (psllq, imm, reg)
#define psllq_m2r(var,reg) mmx_m2r (psllq, var, reg)
#define psllq_r2r(regs,regd) mmx_r2r (psllq, regs, regd)
#define psllw_i2r(imm,reg) mmx_i2r (psllw, imm, reg)
#define psllw_m2r(var,reg) mmx_m2r (psllw, var, reg)
#define psllw_r2r(regs,regd) mmx_r2r (psllw, regs, regd)
#define psrad_i2r(imm,reg) mmx_i2r (psrad, imm, reg)
#define psrad_m2r(var,reg) mmx_m2r (psrad, var, reg)
#define psrad_r2r(regs,regd) mmx_r2r (psrad, regs, regd)
#define psraw_i2r(imm,reg) mmx_i2r (psraw, imm, reg)
#define psraw_m2r(var,reg) mmx_m2r (psraw, var, reg)
#define psraw_r2r(regs,regd) mmx_r2r (psraw, regs, regd)
#define psrld_i2r(imm,reg) mmx_i2r (psrld, imm, reg)
#define psrld_m2r(var,reg) mmx_m2r (psrld, var, reg)
#define psrld_r2r(regs,regd) mmx_r2r (psrld, regs, regd)
#define psrlq_i2r(imm,reg) mmx_i2r (psrlq, imm, reg)
#define psrlq_m2r(var,reg) mmx_m2r (psrlq, var, reg)
#define psrlq_r2r(regs,regd) mmx_r2r (psrlq, regs, regd)
#define psrlw_i2r(imm,reg) mmx_i2r (psrlw, imm, reg)
#define psrlw_m2r(var,reg) mmx_m2r (psrlw, var, reg)
#define psrlw_r2r(regs,regd) mmx_r2r (psrlw, regs, regd)
#define psubb_m2r(var,reg) mmx_m2r (psubb, var, reg)
#define psubb_r2r(regs,regd) mmx_r2r (psubb, regs, regd)
#define psubd_m2r(var,reg) mmx_m2r (psubd, var, reg)
#define psubd_r2r(regs,regd) mmx_r2r (psubd, regs, regd)
#define psubw_m2r(var,reg) mmx_m2r (psubw, var, reg)
#define psubw_r2r(regs,regd) mmx_r2r (psubw, regs, regd)
#define psubsb_m2r(var,reg) mmx_m2r (psubsb, var, reg)
#define psubsb_r2r(regs,regd) mmx_r2r (psubsb, regs, regd)
#define psubsw_m2r(var,reg) mmx_m2r (psubsw, var, reg)
#define psubsw_r2r(regs,regd) mmx_r2r (psubsw, regs, regd)
#define psubusb_m2r(var,reg) mmx_m2r (psubusb, var, reg)
#define psubusb_r2r(regs,regd) mmx_r2r (psubusb, regs, regd)
#define psubusw_m2r(var,reg) mmx_m2r (psubusw, var, reg)
#define psubusw_r2r(regs,regd) mmx_r2r (psubusw, regs, regd)
#define punpckhbw_m2r(var,reg) mmx_m2r (punpckhbw, var, reg)
#define punpckhbw_r2r(regs,regd) mmx_r2r (punpckhbw, regs, regd)
#define punpckhdq_m2r(var,reg) mmx_m2r (punpckhdq, var, reg)
#define punpckhdq_r2r(regs,regd) mmx_r2r (punpckhdq, regs, regd)
#define punpckhwd_m2r(var,reg) mmx_m2r (punpckhwd, var, reg)
#define punpckhwd_r2r(regs,regd) mmx_r2r (punpckhwd, regs, regd)
#define punpcklbw_m2r(var,reg) mmx_m2r (punpcklbw, var, reg)
#define punpcklbw_r2r(regs,regd) mmx_r2r (punpcklbw, regs, regd)
#define punpckldq_m2r(var,reg) mmx_m2r (punpckldq, var, reg)
#define punpckldq_r2r(regs,regd) mmx_r2r (punpckldq, regs, regd)
#define punpcklwd_m2r(var,reg) mmx_m2r (punpcklwd, var, reg)
#define punpcklwd_r2r(regs,regd) mmx_r2r (punpcklwd, regs, regd)
#define pxor_m2r(var,reg) mmx_m2r (pxor, var, reg)
#define pxor_r2r(regs,regd) mmx_r2r (pxor, regs, regd)
/* 3DNOW extensions */
#define pavgusb_m2r(var,reg) mmx_m2r (pavgusb, var, reg)
#define pavgusb_r2r(regs,regd) mmx_r2r (pavgusb, regs, regd)
/* AMD MMX extensions - also available in intel SSE */
#define mmx_m2ri(op,mem,reg,imm) \
__asm__ __volatile__ (#op " %1, %0, %%" #reg \
: /* nothing */ \
: "m" (mem), "i" (imm))
#define mmx_r2ri(op,regs,regd,imm) \
__asm__ __volatile__ (#op " %0, %%" #regs ", %%" #regd \
: /* nothing */ \
: "i" (imm) )
#define mmx_fetch(mem,hint) \
__asm__ __volatile__ ("prefetch" #hint " %0" \
: /* nothing */ \
: "m" (mem))
#define maskmovq(regs,maskreg) mmx_r2ri (maskmovq, regs, maskreg)
#define movntq_r2m(mmreg,var) mmx_r2m (movntq, mmreg, var)
#define pavgb_m2r(var,reg) mmx_m2r (pavgb, var, reg)
#define pavgb_r2r(regs,regd) mmx_r2r (pavgb, regs, regd)
#define pavgw_m2r(var,reg) mmx_m2r (pavgw, var, reg)
#define pavgw_r2r(regs,regd) mmx_r2r (pavgw, regs, regd)
#define pextrw_r2r(mmreg,reg,imm) mmx_r2ri (pextrw, mmreg, reg, imm)
#define pinsrw_r2r(reg,mmreg,imm) mmx_r2ri (pinsrw, reg, mmreg, imm)
#define pmaxsw_m2r(var,reg) mmx_m2r (pmaxsw, var, reg)
#define pmaxsw_r2r(regs,regd) mmx_r2r (pmaxsw, regs, regd)
#define pmaxub_m2r(var,reg) mmx_m2r (pmaxub, var, reg)
#define pmaxub_r2r(regs,regd) mmx_r2r (pmaxub, regs, regd)
#define pminsw_m2r(var,reg) mmx_m2r (pminsw, var, reg)
#define pminsw_r2r(regs,regd) mmx_r2r (pminsw, regs, regd)
#define pminub_m2r(var,reg) mmx_m2r (pminub, var, reg)
#define pminub_r2r(regs,regd) mmx_r2r (pminub, regs, regd)
#define pmovmskb(mmreg,reg) \
__asm__ __volatile__ ("movmskps %" #mmreg ", %" #reg)
#define pmulhuw_m2r(var,reg) mmx_m2r (pmulhuw, var, reg)
#define pmulhuw_r2r(regs,regd) mmx_r2r (pmulhuw, regs, regd)
#define prefetcht0(mem) mmx_fetch (mem, t0)
#define prefetcht1(mem) mmx_fetch (mem, t1)
#define prefetcht2(mem) mmx_fetch (mem, t2)
#define prefetchnta(mem) mmx_fetch (mem, nta)
#define psadbw_m2r(var,reg) mmx_m2r (psadbw, var, reg)
#define psadbw_r2r(regs,regd) mmx_r2r (psadbw, regs, regd)
#define pshufw_m2r(var,reg,imm) mmx_m2ri(pshufw, var, reg, imm)
#define pshufw_r2r(regs,regd,imm) mmx_r2ri(pshufw, regs, regd, imm)
#define sfence() __asm__ __volatile__ ("sfence\n\t")
/* SSE2 */
#define pshufhw_m2r(var,reg,imm) mmx_m2ri(pshufhw, var, reg, imm)
#define pshufhw_r2r(regs,regd,imm) mmx_r2ri(pshufhw, regs, regd, imm)
#define pshuflw_m2r(var,reg,imm) mmx_m2ri(pshuflw, var, reg, imm)
#define pshuflw_r2r(regs,regd,imm) mmx_r2ri(pshuflw, regs, regd, imm)
#define pshufd_r2r(regs,regd,imm) mmx_r2ri(pshufd, regs, regd, imm)
#define movdqa_m2r(var,reg) mmx_m2r (movdqa, var, reg)
#define movdqa_r2m(reg,var) mmx_r2m (movdqa, reg, var)
#define movdqa_r2r(regs,regd) mmx_r2r (movdqa, regs, regd)
#define movdqu_m2r(var,reg) mmx_m2r (movdqu, var, reg)
#define movdqu_r2m(reg,var) mmx_r2m (movdqu, reg, var)
#define movdqu_r2r(regs,regd) mmx_r2r (movdqu, regs, regd)
#define pmullw_r2m(reg,var) mmx_r2m (pmullw, reg, var)
#define pslldq_i2r(imm,reg) mmx_i2r (pslldq, imm, reg)
#define psrldq_i2r(imm,reg) mmx_i2r (psrldq, imm, reg)
#define punpcklqdq_r2r(regs,regd) mmx_r2r (punpcklqdq, regs, regd)
#define punpckhqdq_r2r(regs,regd) mmx_r2r (punpckhqdq, regs, regd)
#endif /* AVCODEC_I386MMX_H */

8
src/libg711/Makefile.am Normal file
View File

@ -0,0 +1,8 @@
AM_CPPFLAGS = $(all_includes)
AM_CFLAGS= -Wall -Wextra -g
noinst_LIBRARIES = libg711.a
libg711_a_SOURCES = \
g711.c

234
src/libg711/g711.c Normal file
View File

@ -0,0 +1,234 @@
/*****************************************************************************\
** **
** PBX4Linux **
** **
**---------------------------------------------------------------------------**
** Copyright: Andreas Eversberg (GPL) **
** **
** audio conversions for alaw and ulaw **
** **
\*****************************************************************************/
#include <stdint.h>
#include "g711.h"
/* ulaw -> signed 16-bit */
static int16_t _ulaw_flipped_to_linear[256] =
{
0x8284, 0x8684, 0x8a84, 0x8e84, 0x9284, 0x9684, 0x9a84, 0x9e84,
0xa284, 0xa684, 0xaa84, 0xae84, 0xb284, 0xb684, 0xba84, 0xbe84,
0xc184, 0xc384, 0xc584, 0xc784, 0xc984, 0xcb84, 0xcd84, 0xcf84,
0xd184, 0xd384, 0xd584, 0xd784, 0xd984, 0xdb84, 0xdd84, 0xdf84,
0xe104, 0xe204, 0xe304, 0xe404, 0xe504, 0xe604, 0xe704, 0xe804,
0xe904, 0xea04, 0xeb04, 0xec04, 0xed04, 0xee04, 0xef04, 0xf004,
0xf0c4, 0xf144, 0xf1c4, 0xf244, 0xf2c4, 0xf344, 0xf3c4, 0xf444,
0xf4c4, 0xf544, 0xf5c4, 0xf644, 0xf6c4, 0xf744, 0xf7c4, 0xf844,
0xf8a4, 0xf8e4, 0xf924, 0xf964, 0xf9a4, 0xf9e4, 0xfa24, 0xfa64,
0xfaa4, 0xfae4, 0xfb24, 0xfb64, 0xfba4, 0xfbe4, 0xfc24, 0xfc64,
0xfc94, 0xfcb4, 0xfcd4, 0xfcf4, 0xfd14, 0xfd34, 0xfd54, 0xfd74,
0xfd94, 0xfdb4, 0xfdd4, 0xfdf4, 0xfe14, 0xfe34, 0xfe54, 0xfe74,
0xfe8c, 0xfe9c, 0xfeac, 0xfebc, 0xfecc, 0xfedc, 0xfeec, 0xfefc,
0xff0c, 0xff1c, 0xff2c, 0xff3c, 0xff4c, 0xff5c, 0xff6c, 0xff7c,
0xff88, 0xff90, 0xff98, 0xffa0, 0xffa8, 0xffb0, 0xffb8, 0xffc0,
0xffc8, 0xffd0, 0xffd8, 0xffe0, 0xffe8, 0xfff0, 0xfff8, 0xffff,
0x7d7c, 0x797c, 0x757c, 0x717c, 0x6d7c, 0x697c, 0x657c, 0x617c,
0x5d7c, 0x597c, 0x557c, 0x517c, 0x4d7c, 0x497c, 0x457c, 0x417c,
0x3e7c, 0x3c7c, 0x3a7c, 0x387c, 0x367c, 0x347c, 0x327c, 0x307c,
0x2e7c, 0x2c7c, 0x2a7c, 0x287c, 0x267c, 0x247c, 0x227c, 0x207c,
0x1efc, 0x1dfc, 0x1cfc, 0x1bfc, 0x1afc, 0x19fc, 0x18fc, 0x17fc,
0x16fc, 0x15fc, 0x14fc, 0x13fc, 0x12fc, 0x11fc, 0x10fc, 0x0ffc,
0x0f3c, 0x0ebc, 0x0e3c, 0x0dbc, 0x0d3c, 0x0cbc, 0x0c3c, 0x0bbc,
0x0b3c, 0x0abc, 0x0a3c, 0x09bc, 0x093c, 0x08bc, 0x083c, 0x07bc,
0x075c, 0x071c, 0x06dc, 0x069c, 0x065c, 0x061c, 0x05dc, 0x059c,
0x055c, 0x051c, 0x04dc, 0x049c, 0x045c, 0x041c, 0x03dc, 0x039c,
0x036c, 0x034c, 0x032c, 0x030c, 0x02ec, 0x02cc, 0x02ac, 0x028c,
0x026c, 0x024c, 0x022c, 0x020c, 0x01ec, 0x01cc, 0x01ac, 0x018c,
0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104,
0x00f4, 0x00e4, 0x00d4, 0x00c4, 0x00b4, 0x00a4, 0x0094, 0x0084,
0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040,
0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000
};
/* alaw -> signed 16-bit */
static int16_t _alaw_flipped_to_linear[256] =
{
0x13fc, 0xec04, 0x0144, 0xfebc, 0x517c, 0xae84, 0x051c, 0xfae4,
0x0a3c, 0xf5c4, 0x0048, 0xffb8, 0x287c, 0xd784, 0x028c, 0xfd74,
0x1bfc, 0xe404, 0x01cc, 0xfe34, 0x717c, 0x8e84, 0x071c, 0xf8e4,
0x0e3c, 0xf1c4, 0x00c4, 0xff3c, 0x387c, 0xc784, 0x039c, 0xfc64,
0x0ffc, 0xf004, 0x0104, 0xfefc, 0x417c, 0xbe84, 0x041c, 0xfbe4,
0x083c, 0xf7c4, 0x0008, 0xfff8, 0x207c, 0xdf84, 0x020c, 0xfdf4,
0x17fc, 0xe804, 0x018c, 0xfe74, 0x617c, 0x9e84, 0x061c, 0xf9e4,
0x0c3c, 0xf3c4, 0x0084, 0xff7c, 0x307c, 0xcf84, 0x030c, 0xfcf4,
0x15fc, 0xea04, 0x0164, 0xfe9c, 0x597c, 0xa684, 0x059c, 0xfa64,
0x0b3c, 0xf4c4, 0x0068, 0xff98, 0x2c7c, 0xd384, 0x02cc, 0xfd34,
0x1dfc, 0xe204, 0x01ec, 0xfe14, 0x797c, 0x8684, 0x07bc, 0xf844,
0x0f3c, 0xf0c4, 0x00e4, 0xff1c, 0x3c7c, 0xc384, 0x03dc, 0xfc24,
0x11fc, 0xee04, 0x0124, 0xfedc, 0x497c, 0xb684, 0x049c, 0xfb64,
0x093c, 0xf6c4, 0x0028, 0xffd8, 0x247c, 0xdb84, 0x024c, 0xfdb4,
0x19fc, 0xe604, 0x01ac, 0xfe54, 0x697c, 0x9684, 0x069c, 0xf964,
0x0d3c, 0xf2c4, 0x00a4, 0xff5c, 0x347c, 0xcb84, 0x034c, 0xfcb4,
0x12fc, 0xed04, 0x0134, 0xfecc, 0x4d7c, 0xb284, 0x04dc, 0xfb24,
0x09bc, 0xf644, 0x0038, 0xffc8, 0x267c, 0xd984, 0x026c, 0xfd94,
0x1afc, 0xe504, 0x01ac, 0xfe54, 0x6d7c, 0x9284, 0x06dc, 0xf924,
0x0dbc, 0xf244, 0x00b4, 0xff4c, 0x367c, 0xc984, 0x036c, 0xfc94,
0x0f3c, 0xf0c4, 0x00f4, 0xff0c, 0x3e7c, 0xc184, 0x03dc, 0xfc24,
0x07bc, 0xf844, 0x0008, 0xfff8, 0x1efc, 0xe104, 0x01ec, 0xfe14,
0x16fc, 0xe904, 0x0174, 0xfe8c, 0x5d7c, 0xa284, 0x05dc, 0xfa24,
0x0bbc, 0xf444, 0x0078, 0xff88, 0x2e7c, 0xd184, 0x02ec, 0xfd14,
0x14fc, 0xeb04, 0x0154, 0xfeac, 0x557c, 0xaa84, 0x055c, 0xfaa4,
0x0abc, 0xf544, 0x0058, 0xffa8, 0x2a7c, 0xd584, 0x02ac, 0xfd54,
0x1cfc, 0xe304, 0x01cc, 0xfe34, 0x757c, 0x8a84, 0x075c, 0xf8a4,
0x0ebc, 0xf144, 0x00d4, 0xff2c, 0x3a7c, 0xc584, 0x039c, 0xfc64,
0x10fc, 0xef04, 0x0114, 0xfeec, 0x457c, 0xba84, 0x045c, 0xfba4,
0x08bc, 0xf744, 0x0018, 0xffe8, 0x227c, 0xdd84, 0x022c, 0xfdd4,
0x18fc, 0xe704, 0x018c, 0xfe74, 0x657c, 0x9a84, 0x065c, 0xf9a4,
0x0cbc, 0xf344, 0x0094, 0xff6c, 0x327c, 0xcd84, 0x032c, 0xfcd4
};
/* Xlaw -> signed 16-bit */
static int16_t _alaw_to_linear[256];
static int16_t _ulaw_to_linear[256];
/* signed 16-bit -> Xlaw */
static uint8_t _linear_to_alaw_flipped[65536];
static uint8_t _linear_to_ulaw_flipped[65536];
static uint8_t _linear_to_alaw[65536];
static uint8_t _linear_to_ulaw[65536];
/* table is used to generate linear_to_alaw */
static int16_t _alaw_relations[] =
{
0x8684, 0x55, 0x8a84, 0xd5, 0x8e84, 0x15, 0x9284, 0x95,
0x9684, 0x75, 0x9a84, 0xf5, 0x9e84, 0x35, 0xa284, 0xb5,
0xa684, 0x45, 0xaa84, 0xc5, 0xae84, 0x05, 0xb284, 0x85,
0xb684, 0x65, 0xba84, 0xe5, 0xbe84, 0x25, 0xc184, 0xa5,
0xc384, 0x5d, 0xc584, 0xdd, 0xc784, 0x1d, 0xc984, 0x9d,
0xcb84, 0x7d, 0xcd84, 0xfd, 0xcf84, 0x3d, 0xd184, 0xbd,
0xd384, 0x4d, 0xd584, 0xcd, 0xd784, 0x0d, 0xd984, 0x8d,
0xdb84, 0x6d, 0xdd84, 0xed, 0xdf84, 0x2d, 0xe104, 0xad,
0xe204, 0x51, 0xe304, 0xd1, 0xe404, 0x11, 0xe504, 0x91,
0xe604, 0x71, 0xe704, 0xf1, 0xe804, 0x31, 0xe904, 0xb1,
0xea04, 0x41, 0xeb04, 0xc1, 0xec04, 0x01, 0xed04, 0x81,
0xee04, 0x61, 0xef04, 0xe1, 0xf004, 0x21, 0xf0c4, 0x59,
0xf0c4, 0xa1, 0xf144, 0xd9, 0xf1c4, 0x19, 0xf244, 0x99,
0xf2c4, 0x79, 0xf344, 0xf9, 0xf3c4, 0x39, 0xf444, 0xb9,
0xf4c4, 0x49, 0xf544, 0xc9, 0xf5c4, 0x09, 0xf644, 0x89,
0xf6c4, 0x69, 0xf744, 0xe9, 0xf7c4, 0x29, 0xf844, 0x57,
0xf844, 0xa9, 0xf8a4, 0xd7, 0xf8e4, 0x17, 0xf924, 0x97,
0xf964, 0x77, 0xf9a4, 0xf7, 0xf9e4, 0x37, 0xfa24, 0xb7,
0xfa64, 0x47, 0xfaa4, 0xc7, 0xfae4, 0x07, 0xfb24, 0x87,
0xfb64, 0x67, 0xfba4, 0xe7, 0xfbe4, 0x27, 0xfc24, 0x5f,
0xfc24, 0xa7, 0xfc64, 0x1f, 0xfc64, 0xdf, 0xfc94, 0x9f,
0xfcb4, 0x7f, 0xfcd4, 0xff, 0xfcf4, 0x3f, 0xfd14, 0xbf,
0xfd34, 0x4f, 0xfd54, 0xcf, 0xfd74, 0x0f, 0xfd94, 0x8f,
0xfdb4, 0x6f, 0xfdd4, 0xef, 0xfdf4, 0x2f, 0xfe14, 0x53,
0xfe14, 0xaf, 0xfe34, 0x13, 0xfe34, 0xd3, 0xfe54, 0x73,
0xfe54, 0x93, 0xfe74, 0x33, 0xfe74, 0xf3, 0xfe8c, 0xb3,
0xfe9c, 0x43, 0xfeac, 0xc3, 0xfebc, 0x03, 0xfecc, 0x83,
0xfedc, 0x63, 0xfeec, 0xe3, 0xfefc, 0x23, 0xff0c, 0xa3,
0xff1c, 0x5b, 0xff2c, 0xdb, 0xff3c, 0x1b, 0xff4c, 0x9b,
0xff5c, 0x7b, 0xff6c, 0xfb, 0xff7c, 0x3b, 0xff88, 0xbb,
0xff98, 0x4b, 0xffa8, 0xcb, 0xffb8, 0x0b, 0xffc8, 0x8b,
0xffd8, 0x6b, 0xffe8, 0xeb, 0xfff8, 0x2b, 0xfff8, 0xab,
0x0008, 0x2a, 0x0008, 0xaa, 0x0018, 0xea, 0x0028, 0x6a,
0x0038, 0x8a, 0x0048, 0x0a, 0x0058, 0xca, 0x0068, 0x4a,
0x0078, 0xba, 0x0084, 0x3a, 0x0094, 0xfa, 0x00a4, 0x7a,
0x00b4, 0x9a, 0x00c4, 0x1a, 0x00d4, 0xda, 0x00e4, 0x5a,
0x00f4, 0xa2, 0x0104, 0x22, 0x0114, 0xe2, 0x0124, 0x62,
0x0134, 0x82, 0x0144, 0x02, 0x0154, 0xc2, 0x0164, 0x42,
0x0174, 0xb2, 0x018c, 0x32, 0x018c, 0xf2, 0x01ac, 0x72,
0x01ac, 0x92, 0x01cc, 0x12, 0x01cc, 0xd2, 0x01ec, 0x52,
0x01ec, 0xae, 0x020c, 0x2e, 0x022c, 0xee, 0x024c, 0x6e,
0x026c, 0x8e, 0x028c, 0x0e, 0x02ac, 0xce, 0x02cc, 0x4e,
0x02ec, 0xbe, 0x030c, 0x3e, 0x032c, 0xfe, 0x034c, 0x7e,
0x036c, 0x9e, 0x039c, 0x1e, 0x039c, 0xde, 0x03dc, 0x5e,
0x03dc, 0xa6, 0x041c, 0x26, 0x045c, 0xe6, 0x049c, 0x66,
0x04dc, 0x86, 0x051c, 0x06, 0x055c, 0xc6, 0x059c, 0x46,
0x05dc, 0xb6, 0x061c, 0x36, 0x065c, 0xf6, 0x069c, 0x76,
0x06dc, 0x96, 0x071c, 0x16, 0x075c, 0xd6, 0x07bc, 0x56,
0x07bc, 0xa8, 0x083c, 0x28, 0x08bc, 0xe8, 0x093c, 0x68,
0x09bc, 0x88, 0x0a3c, 0x08, 0x0abc, 0xc8, 0x0b3c, 0x48,
0x0bbc, 0xb8, 0x0c3c, 0x38, 0x0cbc, 0xf8, 0x0d3c, 0x78,
0x0dbc, 0x98, 0x0e3c, 0x18, 0x0ebc, 0xd8, 0x0f3c, 0x58,
0x0f3c, 0xa0, 0x0ffc, 0x20, 0x10fc, 0xe0, 0x11fc, 0x60,
0x12fc, 0x80, 0x13fc, 0x00, 0x14fc, 0xc0, 0x15fc, 0x40,
0x16fc, 0xb0, 0x17fc, 0x30, 0x18fc, 0xf0, 0x19fc, 0x70,
0x1afc, 0x90, 0x1bfc, 0x10, 0x1cfc, 0xd0, 0x1dfc, 0x50,
0x1efc, 0xac, 0x207c, 0x2c, 0x227c, 0xec, 0x247c, 0x6c,
0x267c, 0x8c, 0x287c, 0x0c, 0x2a7c, 0xcc, 0x2c7c, 0x4c,
0x2e7c, 0xbc, 0x307c, 0x3c, 0x327c, 0xfc, 0x347c, 0x7c,
0x367c, 0x9c, 0x387c, 0x1c, 0x3a7c, 0xdc, 0x3c7c, 0x5c,
0x3e7c, 0xa4, 0x417c, 0x24, 0x457c, 0xe4, 0x497c, 0x64,
0x4d7c, 0x84, 0x517c, 0x04, 0x557c, 0xc4, 0x597c, 0x44,
0x5d7c, 0xb4, 0x617c, 0x34, 0x657c, 0xf4, 0x697c, 0x74,
0x6d7c, 0x94, 0x717c, 0x14, 0x757c, 0xd4, 0x797c, 0x54
};
uint8_t _flip[256];
/* Xlaw -> signed 16-bit */
int16_t *g711_ulaw_flipped_to_linear = _ulaw_flipped_to_linear;
int16_t *g711_alaw_flipped_to_linear = _alaw_flipped_to_linear;
int16_t *g711_alaw_to_linear = _alaw_to_linear;
int16_t *g711_ulaw_to_linear = _ulaw_to_linear;
/* signed 16-bit -> Xlaw */
uint8_t *g711_linear_to_alaw_flipped = _linear_to_alaw_flipped;
uint8_t *g711_linear_to_ulaw_flipped = _linear_to_ulaw_flipped;
uint8_t *g711_linear_to_alaw = _linear_to_ulaw;
uint8_t *g711_linear_to_ulaw = _linear_to_ulaw;
/* generate tables
*/
void g711_init(void)
{
int i, j;
/* flip tables */
for (i = 0; i < 256; i++) {
_flip[i]
= ((i & 1) << 7)
+ ((i & 2) << 5)
+ ((i & 4) << 3)
+ ((i & 8) << 1)
+ ((i & 16) >> 1)
+ ((i & 32) >> 3)
+ ((i & 64) >> 5)
+ ((i & 128) >> 7);
_alaw_to_linear[i] = _alaw_flipped_to_linear[_flip[i]];
_ulaw_to_linear[i] = _ulaw_flipped_to_linear[_flip[i]];
}
/* linear to alaw tables */
i = j = 0;
while(i < 65536) {
if (i - 32768 > _alaw_relations[j << 1])
j++;
if (j > 255)
j = 255;
_linear_to_alaw_flipped[(i - 32768) & 0xffff] = _alaw_relations[(j << 1) | 1];
_linear_to_alaw[(i - 32768) & 0xffff] = _flip[_alaw_relations[(j << 1) | 1]];
i++;
}
/* linear to ulaw tables */
i = j = 0;
while(i < 32768) {
if (i - 32768 > _ulaw_flipped_to_linear[j])
j++;
_linear_to_ulaw_flipped[(i - 32768) & 0xffff] = j;
_linear_to_ulaw[(i - 32768) & 0xffff] = _flip[j];
i++;
}
j = 255;
while(i < 65536) {
if (i - 32768 > _alaw_flipped_to_linear[j])
j--;
_linear_to_ulaw_flipped[(i - 32768) & 0xffff] = j;
_linear_to_ulaw[(i - 32768) & 0xffff] = _flip[j];
i++;
}
}

13
src/libg711/g711.h Normal file
View File

@ -0,0 +1,13 @@
void g711_init(void);
/* Xlaw -> signed 16-bit */
extern int16_t *g711_ulaw_flipped_to_linear;
extern int16_t *g711_alaw_flipped_to_linear;
extern int16_t *g711_alaw_to_linear;
extern int16_t *g711_ulaw_to_linear;
/* signed 16-bit -> Xlaw */
extern uint8_t *g711_linear_to_alaw_flipped;
extern uint8_t *g711_linear_to_ulaw_flipped;
extern uint8_t *g711_linear_to_alaw;
extern uint8_t *g711_linear_to_ulaw;

77
src/logging.c Normal file
View File

@ -0,0 +1,77 @@
#include <osmocom/core/utils.h>
#include "logging.h"
static const struct log_info_cat log_categories[] = {
[DV5] = {
.name = "DV5",
.description = "V5 protocol",
.color = "\033[0;31m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DV5L1] = {
.name = "DV5L1",
.description = "V5 L1 link FSM",
.color = "\033[0;36m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DV5CTRL] = {
.name = "DV5CTRL",
.description = "V5 control protocol",
.color = "\033[1;36m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DV5PORT] = {
.name = "DV5PORT",
.description = "V5 port control protocol",
.color = "\033[1;33m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DV5PSTN] = {
.name = "DV5PSTN",
.description = "V5 PSTN protocol",
.color = "\033[0;37m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DV5LCP] = {
.name = "DV5LCP",
.description = "V5 LCP protocol",
.color = "\033[0;33m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DV5BCC] = {
.name = "DV5BCC",
.description = "V5 BCC protocol",
.color = "\033[1;32m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DV5PP] = {
.name = "DV5PP",
.description = "V5 Protection protocol",
.color = "\033[0;35m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DV5MGMT] = {
.name = "DV5MGMT",
.description = "V5 management",
.color = "\033[1;37m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DV5EF] = {
.name = "DV5EF",
.description = "V5 envelop frame",
.color = "\033[1;31m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DPH] = {
.name = "DPH",
.description = "PH-socket",
.color = "\033[0;33m",
.enabled = 1, .loglevel = LOGL_INFO,
},
};
const struct log_info log_info = {
.cat = log_categories,
.num_cat = ARRAY_SIZE(log_categories),
};

19
src/logging.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <osmocom/core/logging.h>
enum {
DV5,
DV5L1,
DV5CTRL,
DV5PORT,
DV5PSTN,
DV5LCP,
DV5BCC,
DV5PP,
DV5MGMT,
DV5EF,
DPH,
};
extern const struct log_info log_info;

View File

@ -1,5 +1,6 @@
/*
* (C) 2022 by Harald Welte <laforge@osmocom.org>
* (C) 2021 by Andreas Eversberg <andreas@eversberg.eu>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
@ -26,7 +27,6 @@
#include <unistd.h>
#include <errno.h>
#include <osmocom/core/msgb.h>
#include <osmocom/abis/e1_input.h>
#include <osmocom/core/application.h>
#include <osmocom/core/msgb.h>
@ -34,7 +34,6 @@
#include <osmocom/core/select.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/socket.h>
#include <osmocom/ctrl/control_vty.h>
@ -49,90 +48,52 @@
#include <osmocom/abis/abis.h>
#include "v5x_internal.h"
#include "v5x_protocol.h"
#include "layer1.h"
#include "v5x_l1_fsm.h"
#include "lapv5.h"
#include "v5x_le_ctrl_fsm.h"
#include "v5x_le_port_fsm.h"
#include "v5x_le_pstn_fsm.h"
#include "v52_le_lcp_fsm.h"
#include "v52_le_bcc_fsm.h"
#include "v52_le_pp_fsm.h"
#include "v5x_le_management.h"
#include "v5le_vty.h"
#include "logging.h"
#include "libg711/g711.h"
#define _GNU_SOURCE
#include <getopt.h>
/* can be changed once libosmocore 1.4.0 is released */
#ifndef OSMO_CTRL_PORT_MGW
#define OSMO_CTRL_PORT_MGW 4267
#endif
void *tall_v5le_ctx = NULL;
struct v5x_instance *v5i = NULL;
struct v5le_config {
};
struct v5le_config *v5le_config_alloc(void *ctx)
{
struct v5le_config *cfg = talloc_zero(ctx, struct v5le_config);
OSMO_ASSERT(cfg);
return cfg;
}
static int v5le_rx_sign(struct msgb *msg)
{
LOGP(DLMI, LOGL_NOTICE, "Rx: %s\n", msgb_hexdump(msg));
msgb_free(msg);
return 0;
}
static const struct e1inp_line_ops v5le_e1_line_ops = {
.sign_link_up = NULL,
.sign_link_down = NULL,
.sign_link = v5le_rx_sign,
};
static void hdlc_rx_cb(struct e1inp_ts *ts, struct msgb *msg)
{
LOGP(DLINP, LOGL_NOTICE, "HDLC Rx: %s\n", msgb_hexdump(msg));
msgb_free(msg);
}
static int e1_init(void)
{
int line_nr = 1;
struct e1inp_line *e1_line;
int rc;
e1_line = e1inp_line_find(0);
OSMO_ASSERT(e1_line);
e1inp_line_bind_ops(e1_line, &v5le_e1_line_ops);
struct e1inp_ts *sign_ts = &e1_line->ts[16-1];
//e1inp_ts_config_sign(sign_ts, e1_line);
//e1inp_sign_link_create(sign_ts, E1INP_SIGN_NONE, NULL, 115/*TEI*/, 0/*SAPI*/);
e1inp_ts_config_hdlc(sign_ts, e1_line, hdlc_rx_cb);
return e1inp_line_update(e1_line);
}
static struct v5le_config *g_cfg;
static int daemonize = 0;
const char *gsmtap_ip = NULL;
int test_sa7 = 0;
int ulaw = 0;
const char *v5le_copyright =
"Copyright (C) 2022 by Harald Welte\r\n"
"Copyright (C) 2022 by Harald Welte & Andreas Eversberg\r\n"
"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
"This is free software: you are free to change and redistribute it.\r\n"
"There is NO WARRANTY, to the extent permitted by law.\r\n";
static char *config_file = "osmo-v5le.cfg";
static char *config_file = "osmo-v5-le.cfg";
static void print_help()
{
printf("Some useful options:\n");
printf(" -h --help is printing this text.\n");
printf(" -c --config-file filename The config file to use.\n");
printf(" -s --disable-color\n");
printf(" -s --disable-color Disable colors on debug output.\n");
printf(" -D --daemonize Fork the process into a background daemon\n");
printf(" -V --version Print the version number\n");
printf(" -T --gsmtap IP Send GSMTAP messages to ip. (e.g. 224.0.0.1)\n");
printf(" -7 --rest-sa7 Continously toggle Sa7 when received. (Do loop interface.)\n");
printf(" --ulaw Use u-Law instead of a-Law for audio processing\n");
printf("\nVTY reference generation:\n");
printf(" --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n");
@ -158,6 +119,9 @@ static void handle_long_options(const char *prog_name, const int long_option)
get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
exit(0);
case 3:
ulaw = 1;
break;
default:
fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
exit(2);
@ -177,10 +141,13 @@ static void handle_options(int argc, char **argv)
{"disable-color", 0, 0, 's'},
{"vty-ref-mode", 1, &long_option, 1},
{"vty-ref-xml", 0, &long_option, 2},
{"gsmtap", 1, 0, 'T'},
{"test-sa7", 0, 0, '7'},
{"ulaw", 0, &long_option, 3},
{0, 0, 0, 0},
};
c = getopt_long(argc, argv, "hc:sVD", long_options, &option_index);
c = getopt_long(argc, argv, "hc:sVDT:7", long_options, &option_index);
if (c == -1)
break;
@ -206,6 +173,12 @@ static void handle_options(int argc, char **argv)
case 'D':
daemonize = 1;
break;
case 'T':
gsmtap_ip = strdup(optarg);
break;
case '7':
test_sa7 = 1;
break;
default:
/* ignore */
break;
@ -217,50 +190,22 @@ static void handle_options(int argc, char **argv)
}
}
int v5le_vty_is_config_node(struct vty *vty, int node)
static int quit = 0;
static void sighandler(int sigset)
{
switch (node) {
case CONFIG_NODE:
return 0;
default:
return 1;
}
if (sigset == SIGHUP)
return;
if (sigset == SIGPIPE)
return;
fprintf(stderr, "Signal received: %d\n", sigset);
quit = 1;
}
int v5le_vty_go_parent(struct vty *vty)
{
switch (vty->node) {
default:
if (v5le_vty_is_config_node(vty, vty->node))
vty->node = CONFIG_NODE;
else
vty->node = ENABLE_NODE;
vty->index = NULL;
}
return vty->node;
}
static struct vty_app_info vty_info = {
.name = "OsmoV5LE",
.version = PACKAGE_VERSION,
.go_parent_cb = v5le_vty_go_parent,
.is_config_node = v5le_vty_is_config_node,
};
static const struct log_info_cat log_categories[] = {
};
const struct log_info log_info = {
.cat = log_categories,
.num_cat = ARRAY_SIZE(log_categories),
};
int main(int argc, char **argv)
{
unsigned int flags;
int rc;
tall_v5le_ctx = talloc_named_const(NULL, 1, "v5le");
@ -272,18 +217,14 @@ int main(int argc, char **argv)
osmo_init_logging2(tall_v5le_ctx, &log_info);
libosmo_abis_init(tall_v5le_ctx);
g_cfg = v5le_config_alloc(tall_v5le_ctx);
if (!g_cfg)
return -1;
vty_info.copyright = v5le_copyright;
vty_init(&vty_info);
logging_vty_add_cmds();
osmo_talloc_vty_add_cmds();
osmo_stats_vty_add_cmds();
//mgcp_vty_init();
ctrl_vty_init(g_cfg);
v5le_vty_init();
ctrl_vty_init(tall_v5le_ctx);
e1inp_vty_init();
osmo_cpu_sched_vty_init(tall_v5le_ctx);
@ -292,53 +233,42 @@ int main(int argc, char **argv)
rate_ctr_init(tall_v5le_ctx);
osmo_stats_init(tall_v5le_ctx);
/* global inits of protocols */
v5x_l1_init();
v5x_le_ctrl_init();
v5x_le_port_init();
v5x_le_pstn_init();
v52_le_lcp_init();
v52_le_bcc_init();
v52_le_pp_init();
v5x_le_mgmt_init();
/* G.711 init */
g711_init();
/* create v5x instance */
v5i = v5x_instance_alloc(tall_v5le_ctx);
if (!v5i)
return -ENOMEM;
rc = vty_read_config_file(config_file, NULL);
if (rc < 0)
return rc;
rc = e1_init();
OSMO_ASSERT(rc == 0);
/* start telnet after reading config for vty_get_bind_addr() */
rc = telnet_init_dynif(tall_v5le_ctx, NULL,
vty_get_bind_addr(), OSMO_VTY_PORT_MGW);
if (rc < 0)
return rc;
#if 0
cfg->ctrl = v5le_ctrl_interface_setup(cfg, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_MGW);
if (!cfg->ctrl) {
fprintf(stderr, "Failed to init the control interface on %s:%u. Exiting\n",
ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_MGW);
}
/* Set the reset callback function. This functions is called when the
* mgcp-command "RSIP" (Reset in Progress) is received */
cfg->reset_cb = mgcp_rsip_cb;
/* we need to bind a socket */
flags = OSMO_SOCK_F_BIND;
if (strlen(cfg->call_agent_addr))
flags |= OSMO_SOCK_F_CONNECT;
rc = osmo_sock_init2_ofd(&cfg->gw_fd.bfd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
cfg->source_addr, cfg->source_port,
cfg->call_agent_addr, strlen(cfg->call_agent_addr) ? 2727 : 0, flags);
if (rc < 0) {
perror("Gateway failed to bind");
return -1;
fprintf(stderr, "Failed to read config file '%s'.\n", config_file);
return rc;
}
cfg->gw_fd.bfd.cb = read_call_agent;
cfg->gw_fd.bfd.data = msgb_alloc(4096, "mgcp-msg");
if (!cfg->gw_fd.bfd.data) {
fprintf(stderr, "Gateway memory error.\n");
return -1;
}
rc = gsmtap_init();
if (rc < 0)
return rc;
LOGP(DLMGCP, LOGL_NOTICE, "Configured for MGCP, listen on %s:%u\n",
cfg->source_addr, cfg->source_port);
#endif
rc = l1_signal_init();
if (rc < 0)
return rc;
/* start telnet after reading config */
rc = telnet_init_default(tall_v5le_ctx, NULL, OSMO_VTY_PORT_MGW);
if (rc < 0)
return rc;
/* initialisation */
srand(time(NULL));
@ -351,11 +281,34 @@ int main(int argc, char **argv)
}
}
/* catch signals */
signal(SIGINT, sighandler);
signal(SIGHUP, sighandler);
signal(SIGTERM, sighandler);
signal(SIGPIPE, sighandler);
/* start management state machine */
if (!llist_empty(&v5i->interfaces)) {
struct v5x_interface *v5if;
v5if = (struct v5x_interface *)v5i->interfaces.next;
v5x_le_mgmt_start_delay(v5if, SOCKET_RETRY_TIMER + 1);
}
/* main loop */
while (1) {
while (!quit) {
osmo_select_main(0);
}
/* reset signals */
signal(SIGINT, SIG_DFL);
signal(SIGHUP, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
/* destroy the universe */
v5x_instance_free(v5i);
free((char *)gsmtap_ip);
return 0;
}

340
src/ph_socket.c Normal file
View File

@ -0,0 +1,340 @@
/* PH-socket, a lightweight ISDN physical layer interface
*
* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include "logging.h"
#include "ph_socket.h"
static int ph_socket_listen_cb(struct osmo_fd *ofd, unsigned int __attribute__((unused)) what);
static int ph_socket_connect_cb(struct osmo_fd *ofd, unsigned int __attribute__((unused)) what);
static void ph_socket_timeout_cb(void *data);
static void open_connection(ph_socket_t *s)
{
uint8_t enable = PH_CTRL_UNBLOCK;
int rc, flags;
if (s->connect_ofd.fd > 0)
return;
LOGP(DPH, LOGL_DEBUG, "Trying to connect to PH-socket server.\n");
rc = socket(PF_UNIX, SOCK_STREAM, 0);
if (rc < 0) {
LOGP(DPH, LOGL_ERROR, "Failed to create UNIX socket.\n");
osmo_timer_schedule(&s->retry_timer, SOCKET_RETRY_TIMER, 0);
return;
}
s->connect_ofd.fd = rc;
s->connect_ofd.data = s;
s->connect_ofd.when = BSC_FD_READ;
s->connect_ofd.cb = ph_socket_connect_cb;
osmo_fd_register(&s->connect_ofd);
/* set nonblocking io, because we do multiple reads when handling read event */
flags = fcntl(s->connect_ofd.fd, F_GETFL);
flags |= O_NONBLOCK;
fcntl(s->connect_ofd.fd, F_SETFL, flags);
/* connect */
rc = connect(s->connect_ofd.fd, (struct sockaddr *)&s->sock_address, sizeof(s->sock_address));
if (rc < 0 && errno != EAGAIN) {
if (!s->connect_failed)
LOGP(DPH, LOGL_NOTICE, "Failed to connect UNIX socket, retrying...\n");
close(s->connect_ofd.fd);
s->connect_failed = 1;
osmo_fd_unregister(&s->connect_ofd);
s->connect_ofd.fd = 0;
osmo_timer_schedule(&s->retry_timer, SOCKET_RETRY_TIMER, 0);
return;
}
s->connect_failed = 0;
LOGP(DPH, LOGL_INFO, "Connection to PH-socket server.\n");
/* reset rx buffer */
s->rx_header_index = 0;
s->rx_data_index = 0;
/* indicate established socket connection */
s->ph_socket_rx_msg(s, 0, PH_PRIM_CTRL_IND, &enable, 1);
}
static void close_connection(ph_socket_t *s)
{
struct socket_msg_list *ml;
uint8_t disable = PH_CTRL_BLOCK;
if (s->connect_ofd.fd <= 0)
return;
LOGP(DPH, LOGL_INFO, "Connection from PH-socket closed.\n");
/* indicate loss of socket connection */
s->ph_socket_rx_msg(s, 0, (s->listen_ofd.fd > 0) ? PH_PRIM_CTRL_REQ : PH_PRIM_CTRL_IND, &disable, 1);
osmo_fd_unregister(&s->connect_ofd);
close(s->connect_ofd.fd);
s->connect_ofd.fd = 0;
while ((ml = s->tx_list)) {
s->tx_list = ml->next;
free(ml);
}
s->tx_list_tail = &s->tx_list;
if (s->rx_msg) {
free(s->rx_msg);
s->rx_msg = NULL;
}
if (s->listen_ofd.fd <= 0) {
/* set timer, so that retry is delayed */
osmo_timer_schedule(&s->retry_timer, SOCKET_RETRY_TIMER, 0);
}
}
int ph_socket_init(ph_socket_t *s, void (*ph_socket_rx_msg)(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data,
int length), void *priv, const char *socket_name, int server)
{
int rc;
memset(s, 0, sizeof(*s));
s->name = socket_name;
s->ph_socket_rx_msg = ph_socket_rx_msg;
s->priv = priv;
s->tx_list_tail = &s->tx_list;
memset(&s->sock_address, 0, sizeof(s->sock_address));
s->sock_address.sun_family = AF_UNIX;
strcpy(s->sock_address.sun_path+1, socket_name);
s->retry_timer.data = s;
s->retry_timer.cb = ph_socket_timeout_cb;
if (server) {
rc = socket(PF_UNIX, SOCK_STREAM, 0);
if (rc < 0) {
LOGP(DPH, LOGL_ERROR, "Failed to create UNIX socket.\n");
return rc;
}
s->listen_ofd.fd = rc;
s->listen_ofd.data = s;
s->listen_ofd.when = BSC_FD_READ;
s->listen_ofd.cb = ph_socket_listen_cb;
osmo_fd_register(&s->listen_ofd);
rc = bind(s->listen_ofd.fd, (struct sockaddr *)(&s->sock_address), sizeof(s->sock_address));
if (rc < 0) {
LOGP(DPH, LOGL_ERROR, "Failed to bind UNIX socket with path '%s' (errno = %d (%s)).\n",
s->name, errno, strerror(errno));
return rc;
}
rc = listen(s->listen_ofd.fd, 1);
if (rc < 0) {
LOGP(DPH, LOGL_ERROR, "Failed to listen to UNIX socket with path '%s' (errno = %d (%s)).\n",
s->name, errno, strerror(errno));
return rc;
}
} else
open_connection(s);
LOGP(DPH, LOGL_INFO, "Created PH-socket at '%s'.\n", s->name);
return 0;
}
void ph_socket_exit(ph_socket_t *s)
{
LOGP(DPH, LOGL_INFO, "Destroyed PH-socket.\n");
close_connection(s);
if (s->listen_ofd.fd > 0) {
osmo_fd_unregister(&s->listen_ofd);
close(s->listen_ofd.fd);
s->listen_ofd.fd = 0;
}
if (osmo_timer_pending(&s->retry_timer))
osmo_timer_del(&s->retry_timer);
}
static int ph_socket_listen_cb(struct osmo_fd *ofd, unsigned int __attribute__((unused)) what)
{
ph_socket_t *s = ofd->data;
struct sockaddr_un sock_address;
uint8_t enable = PH_CTRL_UNBLOCK;
int rc, flags;
socklen_t sock_len = sizeof(sock_address);
/* see if there is an incoming connection */
rc = accept(s->listen_ofd.fd, (struct sockaddr *)&sock_address, &sock_len);
if (rc > 0) {
if (s->connect_ofd.fd > 0) {
LOGP(DPH, LOGL_ERROR, "Rejecting incoming connection, because we already have a client "
"connected!\n");
close(rc);
} else {
LOGP(DPH, LOGL_INFO, "Connection from PH-socket client.\n");
s->connect_ofd.fd = rc;
s->connect_ofd.data = s;
s->connect_ofd.when = BSC_FD_READ;
s->connect_ofd.cb = ph_socket_connect_cb;
osmo_fd_register(&s->connect_ofd);
/* set nonblocking io, because we do multiple reads when handling read event */
flags = fcntl(s->connect_ofd.fd, F_GETFL);
flags |= O_NONBLOCK;
fcntl(s->connect_ofd.fd, F_SETFL, flags);
/* reset rx buffer */
s->rx_header_index = 0;
s->rx_data_index = 0;
/* indicate established socket connection */
s->ph_socket_rx_msg(s, 0, PH_PRIM_CTRL_REQ, &enable, 1);
}
}
return 0;
}
static int ph_socket_connect_cb(struct osmo_fd *ofd, unsigned __attribute__((unused)) int what)
{
ph_socket_t *s = ofd->data;
int rc;
if (what & BSC_FD_READ) {
rx_again:
if (!s->rx_msg)
s->rx_msg = calloc(1, sizeof(*s->rx_msg));
if (s->rx_header_index < (int)sizeof(s->rx_msg->msg.header)) {
/* read header until complete */
rc = recv(s->connect_ofd.fd, ((uint8_t *)&s->rx_msg->msg.header) + s->rx_header_index,
sizeof(s->rx_msg->msg.header) - s->rx_header_index, 0);
if (rc > 0) {
s->rx_header_index += rc;
goto rx_again;
} else if (rc == 0 || errno != EAGAIN) {
close_connection(s);
return 0;
}
} else if (s->rx_data_index < s->rx_msg->msg.header.length) {
/* read data until complete */
rc = recv(s->connect_ofd.fd, s->rx_msg->msg.data + s->rx_data_index,
s->rx_msg->msg.header.length - s->rx_data_index, 0);
if (rc > 0) {
s->rx_data_index += rc;
goto rx_again;
} else if (rc == 0 || errno != EAGAIN) {
close_connection(s);
return 0;
}
} else {
/* process and free message */
if (s->rx_msg->msg.header.prim != PH_PRIM_DATA_REQ
&& s->rx_msg->msg.header.prim != PH_PRIM_DATA_IND
&& s->rx_msg->msg.header.prim != PH_PRIM_DATA_CNF) {
LOGP(DPH, LOGL_DEBUG, "message 0x%02x channel %d from socket\n",
s->rx_msg->msg.header.prim, s->rx_msg->msg.header.channel);
if (s->rx_msg->msg.header.length)
LOGP(DPH, LOGL_DEBUG, " -> data:%s\n", osmo_hexdump(s->rx_msg->msg.data,
s->rx_msg->msg.header.length));
}
s->ph_socket_rx_msg(s, s->rx_msg->msg.header.channel, s->rx_msg->msg.header.prim,
s->rx_msg->msg.data, s->rx_msg->msg.header.length);
free(s->rx_msg);
s->rx_msg = NULL;
/* reset rx buffer */
s->rx_header_index = 0;
s->rx_data_index = 0;
}
}
if (what & BSC_FD_WRITE) {
if (s->tx_list) {
/* some frame in tx list, so try sending it */
rc = send(s->connect_ofd.fd, ((uint8_t *)&s->tx_list->msg.header),
sizeof(s->tx_list->msg.header) + s->tx_list->msg.header.length, 0);
if (rc > 0) {
struct socket_msg_list *ml;
if (rc != (int)sizeof(s->tx_list->msg.header) + s->tx_list->msg.header.length) {
LOGP(DPH, LOGL_ERROR, "Short write, please fix handling!\n");
}
/* remove list entry */
ml = s->tx_list;
s->tx_list = ml->next;
if (s->tx_list == NULL)
s->tx_list_tail = &s->tx_list;
free(ml);
} else if (rc == 0 || errno != EAGAIN) {
close_connection(s);
return 0;
}
} else
s->connect_ofd.when &= ~BSC_FD_WRITE;
}
return 0;
}
static void ph_socket_timeout_cb(void *data)
{
ph_socket_t *s = data;
open_connection(s);
}
void ph_socket_tx_msg(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length)
{
struct socket_msg_list *tx_msg;
if (prim != PH_PRIM_DATA_REQ
&& prim != PH_PRIM_DATA_IND
&& prim != PH_PRIM_DATA_CNF) {
LOGP(DPH, LOGL_DEBUG, "message 0x%02x channel %d to socket\n", prim, channel);
if (length)
LOGP(DPH, LOGL_DEBUG, " -> data:%s\n", osmo_hexdump(data, length));
}
if (length > (int)sizeof(tx_msg->msg.data)) {
LOGP(DPH, LOGL_NOTICE, "Frame from HDLC process too large for socket, dropping!\n");
return;
}
if (s->connect_ofd.fd <= 0) {
LOGP(DPH, LOGL_NOTICE, "Dropping message for socket, socket is closed!\n");
return;
}
tx_msg = calloc(1, sizeof(*tx_msg));
tx_msg->msg.header.channel = channel;
tx_msg->msg.header.prim = prim;
if (length) {
tx_msg->msg.header.length = length;
memcpy(tx_msg->msg.data, data, length);
}
/* move message to list */
*s->tx_list_tail = tx_msg;
s->tx_list_tail = &tx_msg->next;
s->connect_ofd.when |= BSC_FD_WRITE;
}

107
src/ph_socket.h Normal file
View File

@ -0,0 +1,107 @@
#include <sys/un.h>
/*
* Procedure:
*
* If socket connection is establised, a PH_PRIM_CTRL_REQ message with
* PH_CTRL_ENABLE information is received by the socket server user.
* If the socket connection is lost, a PH_PRIM_CTRL_REQ message with
* PH_CTRL_DISABLE information is received by the user.
*
* If socket connection is establised, a PH_PRIM_CTRL_IND message with
* PH_CTRL_ENABLE information is received by the socket client user.
* If the socket connection is lost, a PH_PRIM_CTRL_IND message with
* PH_CTRL_DISABLE information is received by the user.
*
* The socket server should enable or disable interface depending on
* the PH_CTRL_ENABLE / PH_CTRL_DISABLE information.
*
* The socket client user shall keep track of last PH_PRIM_ACT_IND /
* PH_PRIM_DEACT_IND message and treat a PH_PRIM_CTRL_IND message with
* PH_CTRL_DISABLE information as a deactivation of all channels that
* were activated. Also it shall reject every PH_RIM_ACT_REQ with a
* PH_PRIM_DACT_IND, if the socket is currently unavailable.
*
* PH_PRIM_CTRL_REQ and PH_PRIM_CTRL_IND messages with PH_CTRL_ENABLE
* and PH_CTRL_DISABLE informations are not assoicated with a channel
* number. The socket sender shall set it to 0, the receiver shall
* ignore it.
*
* A missing MODE in PH_PRIM_ACT_REQ is interepreted as default:
* HDLC on D-channel, TRANS on B-channel.
*
* Each packet on the socket shall have the follwoing header:
* uint8_t channel;
* uint8_t prim;
* uint16_t length;
*
* The length shall be in host's endian on UN sockets and in network
* endian on TCP sockets and not being transmitted on UDP sockets.
*
* 0 to 65535 bytes shall follow the header, depending on the length
* information field.
*/
/* all primitives */
#define PH_PRIM_DATA_REQ 0x00 /* any data sent to channel from upper layer */
#define PH_PRIM_DATA_IND 0x01 /* any data received from channel to upper layer */
#define PH_PRIM_DATA_CNF 0x02 /* confirm data sent to channel */
#define PH_PRIM_CTRL_REQ 0x04 /* implementation specific requests towards interface */
#define PH_PRIM_CTRL_IND 0x05 /* implementation specific indications from interface */
#define PH_PRIM_ACT_REQ 0x08 /* activation request of channel, mode is given as payload */
#define PH_PRIM_ACT_IND 0x09 /* activation indication that the channel is now active */
#define PH_PRIM_DACT_REQ 0x0c /* deactivation request of channel */
#define PH_PRIM_DACT_IND 0x0d /* deactivation indication that the channel is now inactive */
/* one byte sent activation request */
#define PH_MODE_TRANS 0x00 /* raw data is sent via B-channel */
#define PH_MODE_HDLC 0x01 /* HDLC transcoding is performed via B-channel */
/* one byte sent with control messages */
#define PH_CTRL_BLOCK 0x00 /* disable (block) interface, when socket is disconnected */
#define PH_CTRL_UNBLOCK 0x01 /* enable (unblock) interface, when socket is connected */
#define PH_CTRL_LOOP_DISABLE 0x04 /* disable loopback */
#define PH_CTRL_LOOP1_ENABLE 0x05 /* enable LT transceier loopback */
#define PH_CTRL_LOOP2_ENABLE 0x06 /* enable NT transceier loopback */
#define PH_CTRL_LOOP_ERROR 0x10 /* frame error report (loopback test) */
#define PH_CTRL_VIOLATION_LT 0x11 /* code violation received by LT */
#define PH_CTRL_VIOLATION_NT 0x12 /* code violation received by NT */
struct socket_msg {
struct {
uint8_t channel;
uint8_t prim;
uint16_t length;
} header;
uint8_t data[65536];
} __attribute__((packed));
struct socket_msg_list {
struct socket_msg_list *next;
struct socket_msg msg;
};
#define SOCKET_RETRY_TIMER 2
typedef struct ph_socket {
const char *name;
void (*ph_socket_rx_msg)(struct ph_socket *s, int channel, uint8_t prim, uint8_t *data, int length);
void *priv;
struct sockaddr_un sock_address;
struct osmo_fd listen_ofd; /* socket to listen to incoming connections */
struct osmo_fd connect_ofd; /* socket of incoming connection */
int connect_failed; /* used to print a failure only once */
struct osmo_timer_list retry_timer; /* timer to connect again */
struct socket_msg_list *tx_list, **tx_list_tail;
struct socket_msg_list *rx_msg;
int rx_header_index;
int rx_data_index;
} ph_socket_t;
int ph_socket_init(ph_socket_t *s, void (*ph_socket_rx_msg)(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length), void *priv, const char *socket_name, int server);
void ph_socket_exit(ph_socket_t *s);
void ph_socket_tx_msg(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length);

817
src/v52_le_bcc_fsm.c Normal file
View File

@ -0,0 +1,817 @@
/* ITu-T G.965 Section 17 V5.2-interface BCC protocol - LE side */
/* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
*
* 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.
*
* 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.
*
*/
/***********************************************************************/
/* internal data structures */
/***********************************************************************/
#include <unistd.h>
#include <stdint.h>
#include <errno.h>
#include <arpa/inet.h>
#include <osmocom/core/utils.h>
#include "v5x_internal.h"
#include "v5x_protocol.h"
#include "v5x_le_management.h"
#include "v52_le_bcc_fsm.h"
#include "logging.h"
#define S(x) (1 << (x))
#define TIMEOUT_Tbcc1 2 // 1500 ms
#define TIMEOUT_Tbcc2 2
#define TIMEOUT_Tbcc3 2
#define TIMEOUT_Tbcc4 1
/***********************************************************************/
/* state names, event names, primitives, ... */
/***********************************************************************/
/* 17.2.1.2 */
enum v52_bcc_fsm_state {
V52_BCCFSM_S_LEBcc0_NULL,
V52_BCCFSM_S_LEBcc1_WAITING_ALLOCATION,
V52_BCCFSM_S_LEBcc2_ALLOCATION_ABORT,
V52_BCCFSM_S_LEBcc3_WAITING_DE_ALLOCATION,
V52_BCCFSM_S_LEBcc4_WAITING_AUDIT,
};
enum v52_bcc_fsm_event {
V52_BCCFSM_E_MDU_BCC_allocation_req,
V52_BCCFSM_E_MDU_BCC_deallocation_req,
V52_BCCFSM_E_MDU_BCC_audit_req,
V52_BCCFSM_E_ALLOCATION_COMPLETE,
V52_BCCFSM_E_ALLOCATION_REJECT,
V52_BCCFSM_E_DE_ALLOCATION_COMPLETE,
V52_BCCFSM_E_DE_ALLOCATION_REJECT,
V52_BCCFSM_E_AUDIT_COMPLETE,
V52_BCCFSM_E_AN_FAULT,
V52_BCCFSM_E_PROTOCOL_ERROR,
V52_BCCFSM_E_TIMEOUT_Tbcc1,
V52_BCCFSM_E_TIMEOUT_Tbcc2,
V52_BCCFSM_E_TIMEOUT_Tbcc3,
V52_BCCFSM_E_TIMEOUT_Tbcc4,
};
static const struct value_string v52_bcc_fsm_event_names[] = {
{ V52_BCCFSM_E_MDU_BCC_allocation_req, "MDU-BCC (Allocation request)" },
{ V52_BCCFSM_E_MDU_BCC_deallocation_req, "MDU-BCC (De-allocation request)" },
{ V52_BCCFSM_E_MDU_BCC_audit_req, "MDU-BCC (Audit request)" },
{ V52_BCCFSM_E_ALLOCATION_COMPLETE, "ALLOCATION COMPLETE" },
{ V52_BCCFSM_E_ALLOCATION_REJECT, "ALLOCATION REJECT" },
{ V52_BCCFSM_E_DE_ALLOCATION_COMPLETE, "DE-ALLOCATION COMPLETE" },
{ V52_BCCFSM_E_DE_ALLOCATION_REJECT, "DE-ALLOCATION REJECT" },
{ V52_BCCFSM_E_AUDIT_COMPLETE, "AUDIT COMPLETE" },
{ V52_BCCFSM_E_AN_FAULT, "AN FAULT" },
{ V52_BCCFSM_E_PROTOCOL_ERROR, "PROTOCOL ERROR" },
{ V52_BCCFSM_E_TIMEOUT_Tbcc1, "Timeout Tbbc1" },
{ V52_BCCFSM_E_TIMEOUT_Tbcc2, "Timeout Tbbc2" },
{ V52_BCCFSM_E_TIMEOUT_Tbcc3, "Timeout Tbbc3" },
{ V52_BCCFSM_E_TIMEOUT_Tbcc4, "Timeout Tbbc4" },
{ 0, NULL }
};
/***********************************************************************
* V5 Message encoding / sending
***********************************************************************/
static inline uint16_t v52_bcc_ref_enc(uint16_t ref, uint8_t source_id)
{
uint8_t low, high;
high = (ref >> 6) | (source_id << 7);
low = ref & 0x3f;
return (high << 8) | low;
}
static inline uint16_t v52_bcc_ref_dec(uint16_t ie, uint8_t *source_id)
{
uint8_t low, high;
high = (ie >> 8) & 0x7f;
low = ie & 0x3f;
*source_id = ie >> 15;
return (high << 6) | low;
}
static inline uint16_t v52_bcc_ts_enc(uint8_t link_id, uint8_t ts, uint8_t override)
{
OSMO_ASSERT(!(override >> 1));
return (link_id << 8) | (override << 5) | ts;
}
static inline void v52_bcc_ts_dec(uint16_t ie, uint8_t *link_id, uint8_t *ts, uint8_t *override)
{
*link_id = ie >> 8;
*override = (ie >> 5) & 0x01;
*ts = ie & 0x1f;
}
static struct v52_bcc_proc *find_bcc_proto_ref(struct v5x_interface *v5if, uint16_t ref, uint8_t source_id)
{
struct v52_bcc_proc *bcc;
llist_for_each_entry(bcc, &v5if->bcc_procs, list) {
if (bcc->ref == ref && bcc->source_id == source_id)
return bcc;
}
return NULL;
}
static struct v52_bcc_proc *find_bcc_proto_ts(struct v5x_interface *v5if, uint8_t link_id, uint8_t ts)
{
struct v52_bcc_proc *bcc;
llist_for_each_entry(bcc, &v5if->bcc_procs, list) {
if (bcc->isdn_multislot)
continue;
if (bcc->link_id == link_id && bcc->ts == ts)
return bcc;
}
return NULL;
}
static struct v52_bcc_proc *find_bcc_proto_multislot(struct v5x_interface *v5if, uint8_t link_id,
uint8_t *isdn_multislot)
{
struct v52_bcc_proc *bcc;
llist_for_each_entry(bcc, &v5if->bcc_procs, list) {
if (!bcc->isdn_multislot)
continue;
if (bcc->link_id == link_id && !memcmp(bcc->isdn_multislot, isdn_multislot, 8))
return bcc;
}
return NULL;
}
/* G.965 Section 17.3.1 / Table 29 */
static struct msgb *v52_enc_allocation(uint16_t ref, uint8_t source_id, uint16_t user_port_id, bool is_isdn,
uint8_t link_id, uint8_t ts, uint8_t override, uint8_t isdn_slot, uint8_t *isdn_multislot,
uint8_t *capability)
{
uint16_t user_port_ie;
uint8_t isdn_ts_ie, capa_ie;
uint16_t v5_ts_ie;
uint8_t multislot_ie[9];
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(v52_bcc_ref_enc(ref, source_id));
l3h->msg_type = V52_CTRL_MSGT_ALLOCATION;
/* user port identification */
user_port_ie = htons(v5x_l3_addr_enc(user_port_id, is_isdn));
msgb_tlv_put(msg, V52_CTRL_IEI_BCC_USER_PORT_ID, 2, (uint8_t *)&user_port_ie);
if (is_isdn) {
/* ISDN port time slot */
isdn_ts_ie = 0x80 | isdn_slot;
msgb_tlv_put(msg, V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID, 1, &isdn_ts_ie);
}
if (!isdn_multislot) {
/* V5 time slot */
v5_ts_ie = htons(v52_bcc_ts_enc(link_id, ts, override));
msgb_tlv_put(msg, V52_CTRL_IEI_BCC_V5_TS_ID, 2, (uint8_t *)&v5_ts_ie);
} else {
/* V5 multi slot */
multislot_ie[0] = link_id;
memcpy(multislot_ie + 1, isdn_multislot, 8);
msgb_tlv_put(msg, V52_CTRL_IEI_BCC_MULTI_TS_MAP, 9, multislot_ie);
}
if (is_isdn && capability) {
/* information transfer capability */
capa_ie = 0x80 | *capability;
msgb_tlv_put(msg, V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY, 1, &capa_ie);
}
return msg;
}
/* G.965 Section 17.3.4 / Table 32 */
static struct msgb *v52_enc_de_allocation(uint16_t ref, uint8_t source_id, uint16_t user_port_id, bool is_isdn,
uint8_t link_id, uint8_t ts, uint8_t override, uint8_t isdn_slot, uint8_t *isdn_multislot)
{
uint16_t user_port_ie;
uint8_t isdn_ts_ie;
uint16_t v5_ts_ie;
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(v52_bcc_ref_enc(ref, source_id));
l3h->msg_type = V52_CTRL_MSGT_DE_ALLOCATION;
/* user port identification */
user_port_ie = htons(v5x_l3_addr_enc(user_port_id, is_isdn));
msgb_tlv_put(msg, V52_CTRL_IEI_BCC_USER_PORT_ID, 2, (uint8_t *)&user_port_ie);
if (is_isdn) {
/* ISDN port time slot */
isdn_ts_ie = 0x80 | isdn_slot;
msgb_tlv_put(msg, V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID, 1, &isdn_ts_ie);
}
if (!isdn_multislot) {
/* V5 time slot */
v5_ts_ie = htons(v52_bcc_ts_enc(link_id, ts, override));
msgb_tlv_put(msg, V52_CTRL_IEI_BCC_V5_TS_ID, 2, (uint8_t *)&v5_ts_ie);
} else {
/* V5 multi slot */
msgb_tlv_put(msg, V52_CTRL_IEI_BCC_MULTI_TS_MAP, 9, isdn_multislot);
}
return msg;
}
/* G.965 Section 17.3.7 / Table 35 */
static struct msgb *v52_enc_audit(uint16_t ref, uint8_t source_id, uint8_t link_id, uint8_t ts, uint8_t override)
{
uint16_t v5_ts_ie;
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(v52_bcc_ref_enc(ref, source_id));
l3h->msg_type = V52_CTRL_MSGT_AUDIT;
/* V5 time slot */
v5_ts_ie = htons(v52_bcc_ts_enc(link_id, ts, override));
msgb_tlv_put(msg, V52_CTRL_IEI_BCC_V5_TS_ID, 2, (uint8_t *)&v5_ts_ie);
return msg;
}
/* G.965 Section 17.3.10 / Table 38 */
static struct msgb *v52_enc_an_fault_ack(uint16_t ref, uint8_t source_id)
{
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(v52_bcc_ref_enc(ref, source_id));
l3h->msg_type = V52_CTRL_MSGT_AN_FAULT_ACK;
return msg;
}
/***********************************************************************/
/* Messages to other layers */
/***********************************************************************/
/* send message to upper (management) layer */
static void rcv_mdu(struct osmo_fsm_inst *fi, enum v5x_mgmt_prim prim, const uint8_t *cause, uint8_t cause_len)
{
struct v52_bcc_proc *bcc = fi->priv;
v52_le_bcc_mdu_rcv(bcc->interface, bcc->link_id, bcc->ts, bcc->isdn_multislot, prim, cause, cause_len);
}
/* message to lower (DL) layer */
static void snd_msg(struct osmo_fsm_inst *fi, struct msgb *msg)
{
struct v52_bcc_proc *bcc = fi->priv;
if (!msg)
return;
v5x_dl_snd(bcc->interface, V52_DLADDR_BCC, msg);
}
/***********************************************************************/
/* LCP state FSM */
/***********************************************************************/
static int v52_le_bcc_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct v52_bcc_proc *bcc = fi->priv;
bcc->expired++;
osmo_fsm_inst_dispatch(fi, bcc->timer, NULL);
return 0;
}
static void bcc_fsm_lebcc0_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct v52_bcc_proc *bcc = fi->priv;
const struct tlv_parsed *tp = data;
uint8_t const *cause;
int cause_len;
switch (event) {
case V52_BCCFSM_E_MDU_BCC_allocation_req:
/* Start Tbcc1 */
bcc->timer = V52_BCCFSM_E_TIMEOUT_Tbcc1;
bcc->expired = 0;
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc1_WAITING_ALLOCATION, TIMEOUT_Tbcc1, 1);
/* Send ALLOCATION */
snd_msg(fi, v52_enc_allocation(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->is_isdn, bcc->link_id,
bcc->ts, bcc->override, bcc->isdn_slot, bcc->isdn_multislot,
(bcc->use_capability) ? &bcc->capability : NULL));
break;
case V52_BCCFSM_E_ALLOCATION_COMPLETE:
/* Send MDU-BCC (Allocation confirmation) */
rcv_mdu(fi, MDU_BCC_allocation_conf, 0, 0);
break;
case V52_BCCFSM_E_ALLOCATION_REJECT:
/* Send MDU-BCC (Allocation rejedct indication) */
cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE);
cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE);
rcv_mdu(fi, MDU_BCC_allocation_reject_ind, cause, cause_len);
break;
case V52_BCCFSM_E_MDU_BCC_deallocation_req:
/* Start Tbcc3 */
bcc->timer = V52_BCCFSM_E_TIMEOUT_Tbcc3;
bcc->expired = 0;
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc3_WAITING_DE_ALLOCATION, TIMEOUT_Tbcc3, 3);
/* Send DE-ALLOCATION */
snd_msg(fi, v52_enc_de_allocation(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->is_isdn,
bcc->link_id, bcc->ts, bcc->override, bcc->isdn_slot, bcc->isdn_multislot));
break;
case V52_BCCFSM_E_DE_ALLOCATION_COMPLETE:
/* Send MDU-BCC (De-allocation confirmation) */
rcv_mdu(fi, MDU_BCC_deallocation_conf, 0, 0);
break;
case V52_BCCFSM_E_DE_ALLOCATION_REJECT:
/* Send MDU-BCC (De-allocation reject indication) */
cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE);
cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE);
rcv_mdu(fi, MDU_BCC_deallocation_reject_ind, cause, cause_len);
break;
case V52_BCCFSM_E_MDU_BCC_audit_req:
/* Start Tbcc4 */
bcc->timer = V52_BCCFSM_E_TIMEOUT_Tbcc4;
bcc->expired = 0;
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc4_WAITING_AUDIT, TIMEOUT_Tbcc4, 4);
/* Send ADUIT */
snd_msg(fi, v52_enc_audit(bcc->ref, bcc->source_id, bcc->link_id, bcc->ts, bcc->override));
break;
case V52_BCCFSM_E_AN_FAULT:
/* Send AN FAULT ACK */
snd_msg(fi, v52_enc_an_fault_ack(bcc->ref, bcc->source_id));
/* Send MDU-BCC (AN Fault indication) */
rcv_mdu(fi, MDU_BCC_AN_fault_ind, 0, 0);
break;
default:
OSMO_ASSERT(0);
}
}
static void bcc_fsm_lebcc1_waiting_alloc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct v52_bcc_proc *bcc = fi->priv;
const struct tlv_parsed *tp = data;
uint8_t const *cause;
int cause_len;
switch (event) {
case V52_BCCFSM_E_ALLOCATION_COMPLETE:
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (Allocation confirmation) */
rcv_mdu(fi, MDU_BCC_allocation_conf, 0, 0);
break;
case V52_BCCFSM_E_ALLOCATION_REJECT:
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (Allocation reject indication) */
cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE);
cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE);
rcv_mdu(fi, MDU_BCC_allocation_reject_ind, cause, cause_len);
break;
case V52_BCCFSM_E_MDU_BCC_deallocation_req:
/* Start Tbcc2 */
bcc->timer = V52_BCCFSM_E_TIMEOUT_Tbcc2;
bcc->expired = 0;
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc2_ALLOCATION_ABORT, TIMEOUT_Tbcc2, 2);
/* Send DE-ALLOCATION */
snd_msg(fi, v52_enc_de_allocation(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->is_isdn,
bcc->link_id, bcc->ts, bcc->override, bcc->isdn_slot, bcc->isdn_multislot));
break;
case V52_BCCFSM_E_TIMEOUT_Tbcc1:
if (bcc->expired == 1) {
/* Restart Tbcc1 */
osmo_timer_schedule(&fi->timer, TIMEOUT_Tbcc1, 0);
/* Send ALLOCATION */
snd_msg(fi, v52_enc_allocation(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->is_isdn,
bcc->link_id, bcc->ts, bcc->override, bcc->isdn_slot, bcc->isdn_multislot,
(bcc->use_capability) ? &bcc->capability : NULL));
break;
}
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (Allocation error indication) */
rcv_mdu(fi, MDU_BCC_allocation_error_ind, 0, 0);
break;
case V52_BCCFSM_E_PROTOCOL_ERROR:
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (Protocol error indication) */
cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE);
cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE);
rcv_mdu(fi, MDU_BCC_protocol_error_ind, cause, cause_len);
break;
default:
OSMO_ASSERT(0);
}
}
static void bcc_fsm_lebcc2_alloc_abort(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct v52_bcc_proc *bcc = fi->priv;
const struct tlv_parsed *tp = data;
uint8_t const *cause;
int cause_len;
switch (event) {
case V52_BCCFSM_E_ALLOCATION_COMPLETE:
/* ignore */
break;
case V52_BCCFSM_E_ALLOCATION_REJECT:
/* ignore */
break;
case V52_BCCFSM_E_DE_ALLOCATION_COMPLETE:
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (De-allocation confirmation) */
rcv_mdu(fi, MDU_BCC_deallocation_conf, 0, 0);
break;
case V52_BCCFSM_E_DE_ALLOCATION_REJECT:
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (De-allocation reject indication) */
cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE);
cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE);
rcv_mdu(fi, MDU_BCC_deallocation_reject_ind, cause, cause_len);
break;
case V52_BCCFSM_E_TIMEOUT_Tbcc2:
if (bcc->expired == 1) {
/* Restart Tbcc2 */
osmo_timer_schedule(&fi->timer, TIMEOUT_Tbcc2, 0);
/* Send DE-ALLOCATION */
snd_msg(fi, v52_enc_de_allocation(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->is_isdn,
bcc->link_id, bcc->ts, bcc->override, bcc->isdn_slot, bcc->isdn_multislot));
break;
}
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (De-allocation error indication) */
rcv_mdu(fi, MDU_BCC_deallocation_error_ind, 0, 0);
break;
case V52_BCCFSM_E_PROTOCOL_ERROR:
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (Protocol error indication) */
cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE);
cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE);
rcv_mdu(fi, MDU_BCC_protocol_error_ind, cause, cause_len);
break;
default:
OSMO_ASSERT(0);
}
}
static void bcc_fsm_lebcc3_waiting_dealloc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct v52_bcc_proc *bcc = fi->priv;
const struct tlv_parsed *tp = data;
uint8_t const *cause;
int cause_len;
switch (event) {
case V52_BCCFSM_E_DE_ALLOCATION_COMPLETE:
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (De-allocation confirmation) */
rcv_mdu(fi, MDU_BCC_deallocation_conf, 0, 0);
break;
case V52_BCCFSM_E_DE_ALLOCATION_REJECT:
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (De-allocation reject indication) */
cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE);
cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE);
rcv_mdu(fi, MDU_BCC_deallocation_reject_ind, cause, cause_len);
break;
case V52_BCCFSM_E_TIMEOUT_Tbcc3:
if (bcc->expired == 1) {
/* Restart Tbcc3 */
osmo_timer_schedule(&fi->timer, TIMEOUT_Tbcc3, 0);
/* Send DE-ALLOCATION */
snd_msg(fi, v52_enc_de_allocation(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->is_isdn,
bcc->link_id, bcc->ts, bcc->override, bcc->isdn_slot, bcc->isdn_multislot));
break;
}
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (De-allocation error indication) */
rcv_mdu(fi, MDU_BCC_deallocation_error_ind, 0, 0);
break;
case V52_BCCFSM_E_PROTOCOL_ERROR:
/* Send MDU-BCC (Protocol error indication) */
cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE);
cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE);
rcv_mdu(fi, MDU_BCC_protocol_error_ind, cause, cause_len);
break;
default:
OSMO_ASSERT(0);
}
}
static void bcc_fsm_lebcc4_waiting_audit(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct v52_bcc_proc *bcc = fi->priv;
const struct tlv_parsed *tp = data;
uint8_t const *cause;
int cause_len;
switch (event) {
case V52_BCCFSM_E_AUDIT_COMPLETE:
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (Audit confirmation) */
rcv_mdu(fi, MDU_BCC_audit_conf, 0, 0);
break;
case V52_BCCFSM_E_TIMEOUT_Tbcc4:
if (bcc->expired == 1) {
/* Restart Tbcc4 */
osmo_timer_schedule(&fi->timer, TIMEOUT_Tbcc4, 0);
/* Send AUDIT */
snd_msg(fi, v52_enc_audit(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->ts, bcc->override));
break;
}
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (Audit error indication) */
rcv_mdu(fi, MDU_BCC_audit_error_ind, 0, 0);
break;
case V52_BCCFSM_E_PROTOCOL_ERROR:
osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0);
/* Send MDU-BCC (Protocol error indication) */
cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE);
cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE);
rcv_mdu(fi, MDU_BCC_protocol_error_ind, cause, cause_len);
break;
default:
OSMO_ASSERT(0);
}
}
/* Table 47/G.965 */
static const struct osmo_fsm_state v52_le_bcc_fsm_states[] = {
[V52_BCCFSM_S_LEBcc0_NULL] = {
.name = "LEBcc0 BCC BULL",
.in_event_mask = S(V52_BCCFSM_E_MDU_BCC_allocation_req) |
S(V52_BCCFSM_E_ALLOCATION_COMPLETE) |
S(V52_BCCFSM_E_ALLOCATION_REJECT) |
S(V52_BCCFSM_E_MDU_BCC_deallocation_req) |
S(V52_BCCFSM_E_DE_ALLOCATION_COMPLETE) |
S(V52_BCCFSM_E_DE_ALLOCATION_REJECT) |
S(V52_BCCFSM_E_MDU_BCC_audit_req) |
S(V52_BCCFSM_E_AN_FAULT) ,
.out_state_mask = S(V52_BCCFSM_S_LEBcc1_WAITING_ALLOCATION) |
S(V52_BCCFSM_S_LEBcc3_WAITING_DE_ALLOCATION) |
S(V52_BCCFSM_S_LEBcc4_WAITING_AUDIT),
.action = bcc_fsm_lebcc0_null,
},
[V52_BCCFSM_S_LEBcc1_WAITING_ALLOCATION] = {
.name = "LEBcc1 BCC WAITING ALLOCATION",
.in_event_mask = S(V52_BCCFSM_E_ALLOCATION_COMPLETE) |
S(V52_BCCFSM_E_ALLOCATION_REJECT) |
S(V52_BCCFSM_E_MDU_BCC_deallocation_req) |
S(V52_BCCFSM_E_TIMEOUT_Tbcc1) |
S(V52_BCCFSM_E_PROTOCOL_ERROR),
.out_state_mask = S(V52_BCCFSM_S_LEBcc0_NULL) |
S(V52_BCCFSM_S_LEBcc2_ALLOCATION_ABORT),
.action = bcc_fsm_lebcc1_waiting_alloc,
},
[V52_BCCFSM_S_LEBcc2_ALLOCATION_ABORT] = {
.name = "LEBcc2 BCC ALLOCATION ABORT",
.in_event_mask = S(V52_BCCFSM_E_ALLOCATION_COMPLETE) |
S(V52_BCCFSM_E_ALLOCATION_REJECT) |
S(V52_BCCFSM_E_DE_ALLOCATION_COMPLETE) |
S(V52_BCCFSM_E_DE_ALLOCATION_REJECT) |
S(V52_BCCFSM_E_TIMEOUT_Tbcc2) |
S(V52_BCCFSM_E_PROTOCOL_ERROR),
.out_state_mask = S(V52_BCCFSM_S_LEBcc0_NULL),
.action = bcc_fsm_lebcc2_alloc_abort,
},
[V52_BCCFSM_S_LEBcc3_WAITING_DE_ALLOCATION] = {
.name = "LEBcc3 BCC WAITING DE-ALLOCATION",
.in_event_mask = S(V52_BCCFSM_E_DE_ALLOCATION_COMPLETE) |
S(V52_BCCFSM_E_DE_ALLOCATION_REJECT) |
S(V52_BCCFSM_E_TIMEOUT_Tbcc3) |
S(V52_BCCFSM_E_PROTOCOL_ERROR),
.out_state_mask = S(V52_BCCFSM_S_LEBcc0_NULL),
.action = bcc_fsm_lebcc3_waiting_dealloc,
},
[V52_BCCFSM_S_LEBcc4_WAITING_AUDIT] = {
.name = "LEBcc4 BCC wAITING AUDIT",
.in_event_mask = S(V52_BCCFSM_E_MDU_BCC_audit_req) |
S(V52_BCCFSM_E_TIMEOUT_Tbcc1) |
S(V52_BCCFSM_E_PROTOCOL_ERROR),
.out_state_mask = S(V52_BCCFSM_S_LEBcc0_NULL),
.action = bcc_fsm_lebcc4_waiting_audit,
},
};
struct osmo_fsm v52_le_bcc_fsm = {
.name = "V52_LE_BCC",
.states = v52_le_bcc_fsm_states,
.num_states = ARRAY_SIZE(v52_le_bcc_fsm_states),
.timer_cb = v52_le_bcc_fsm_timer_cb,
.log_subsys = DV5BCC,
.event_names = v52_bcc_fsm_event_names,
};
static uint16_t bcc_new_ref = 0;
static struct v52_bcc_proc *v52_le_bcc_create(struct v5x_interface *v5if, uint16_t user_port_id, bool is_isdn,
uint8_t link_id, uint8_t ts, uint8_t override, uint8_t isdn_slot, uint8_t *isdn_multislot, uint8_t *capability,
int source_id)
{
struct v52_bcc_proc *bcc;
OSMO_ASSERT(v5if);
LOGP(DV5BCC, LOGL_DEBUG, "Creating BCC instance, initiated by %s.\n", (source_id) ? "AN" : "LE");
bcc = talloc_zero(v5if, struct v52_bcc_proc);
if (!bcc)
return NULL;
bcc->interface = v5if;
bcc->fi = osmo_fsm_inst_alloc(&v52_le_bcc_fsm, bcc, bcc, LOGL_DEBUG, NULL);
if (!bcc->fi) {
talloc_free(bcc);
return NULL;
}
bcc->ref = (bcc_new_ref++) & 0x1fff;
bcc->source_id = source_id;
bcc->user_port_id = user_port_id;
bcc->is_isdn = is_isdn;
bcc->link_id = link_id;
bcc->ts = ts;
bcc->override = override;
bcc->isdn_slot = isdn_slot;
if (isdn_multislot) {
/* copy multislot IE content */
bcc->isdn_multislot = talloc_zero_size(bcc, 9);
if (bcc->isdn_multislot) {
bcc->isdn_multislot[0] = link_id;
memcpy(bcc->isdn_multislot + 1, isdn_multislot, 8);
}
}
if (capability) {
bcc->use_capability = true;
bcc->capability = *capability;
}
llist_add_tail(&bcc->list, &v5if->bcc_procs);
return bcc;
}
void v52_le_bcc_destroy(struct v52_bcc_proc *bcc)
{
LOGP(DV5BCC, LOGL_DEBUG, "Destroying BCC instance.\n");
if (bcc->fi)
osmo_fsm_inst_free(bcc->fi);
llist_del(&bcc->list);
talloc_free(bcc);
}
void v52_le_bcc_init(void)
{
int rc;
rc = osmo_fsm_register(&v52_le_bcc_fsm);
OSMO_ASSERT(!rc);
LOGP(DV5BCC, LOGL_NOTICE, "Using V52 BCC protocol\n");
}
/***********************************************************************
* Message receiving / decoding
***********************************************************************/
/* receive message from lower (DL) layer */
int v52_le_bcc_dl_rcv(struct v5x_interface *v5if, uint16_t l3_addr, uint8_t msg_type, const struct tlv_parsed *tp)
{
struct v52_bcc_proc *bcc;
uint16_t ref;
uint8_t source_id;
/* search for ref */
ref = v52_bcc_ref_dec(l3_addr, &source_id);
bcc = find_bcc_proto_ref(v5if, ref, source_id);
/* handle AN fault here, so there is no need to create a process */
if (msg_type == V52_CTRL_MSGT_AN_FAULT) {
LOGP(DV5BCC, LOGL_NOTICE, "Replying to AN FAULT message. No action is taken here.\n");
/* Send AN FAULT ACK */
v5x_dl_snd(v5if, V52_DLADDR_BCC, v52_enc_an_fault_ack(ref, source_id));
return 0;
}
/* ignoring remote process (may happen due to message repetition in state 0) */
if (!bcc)
return 0;
switch (msg_type) {
case V52_CTRL_MSGT_ALLOCATION_COMPLETE:
osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_ALLOCATION_COMPLETE, (void *)tp);
break;
case V52_CTRL_MSGT_ALLOCATION_REJECT:
osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_ALLOCATION_REJECT, (void *)tp);
break;
case V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE:
osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_DE_ALLOCATION_COMPLETE, (void *)tp);
break;
case V52_CTRL_MSGT_DE_ALLOCATION_REJECT:
osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_DE_ALLOCATION_REJECT, (void *)tp);
break;
case V52_CTRL_MSGT_AUDIT_COMPLETE:
osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_AUDIT_COMPLETE, (void *)tp);
break;
case V52_CTRL_MSGT_AN_FAULT:
osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_AN_FAULT, (void *)tp);
break;
case V52_CTRL_MSGT_PROTOCOL_ERROR:
osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_PROTOCOL_ERROR, (void *)tp);
break;
default:
LOGP(DV5BCC, LOGL_NOTICE, "Invalid BCC protocol message %d receied from AN.\n", msg_type);
return -EINVAL;
}
if (bcc->fi->state == V52_BCCFSM_S_LEBcc0_NULL) {
/* destroy, if NULL state is reached */
v52_le_bcc_destroy(bcc);
}
return 0;
}
/* receive message from upper (management) layer */
int v52_le_bcc_mdu_snd(struct v5x_interface *v5if, uint16_t user_port_id, bool is_isdn, uint8_t link_id, uint8_t ts,
uint8_t override, uint8_t isdn_slot, uint8_t *isdn_multislot, uint8_t *capability, enum v5x_mgmt_prim prim)
{
struct v52_bcc_proc *bcc;
/* search TS */
if (isdn_multislot)
bcc = find_bcc_proto_multislot(v5if, link_id, isdn_multislot);
else
bcc = find_bcc_proto_ts(v5if, link_id, ts);
if (!bcc) {
bcc = v52_le_bcc_create(v5if, user_port_id, is_isdn, link_id, ts, override, isdn_slot, isdn_multislot,
capability, 0 /* LE */);
if (!bcc)
return -ENOMEM;
}
switch (prim) {
case MDU_BCC_allocation_req:
osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_MDU_BCC_allocation_req, NULL);
break;
case MDU_BCC_deallocation_req:
osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_MDU_BCC_deallocation_req, NULL);
break;
case MDU_BCC_audit_req:
osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_MDU_BCC_audit_req, NULL);
break;
default:
LOGP(DV5BCC, LOGL_NOTICE, "Invalid BCC protocol message %d receied from AN.\n", prim);
return -EINVAL;
}
if (bcc->fi->state == V52_BCCFSM_S_LEBcc0_NULL) {
/* destroy, if NULL state is reached */
v52_le_bcc_destroy(bcc);
}
return 0;
}

6
src/v52_le_bcc_fsm.h Normal file
View File

@ -0,0 +1,6 @@
void v52_le_bcc_destroy(struct v52_bcc_proc *bcc);
void v52_le_bcc_init(void);
int v52_le_bcc_dl_rcv(struct v5x_interface *v5if, uint16_t l3_addr, uint8_t msg_type, const struct tlv_parsed *tp);
int v52_le_bcc_mdu_snd(struct v5x_interface *v5if, uint16_t user_port_id, bool is_isdn, uint8_t link_id, uint8_t ts,
uint8_t override, uint8_t isdn_slot, uint8_t *isdn_multislot, uint8_t *capability, enum v5x_mgmt_prim prim);

941
src/v52_le_lcp_fsm.c Normal file
View File

@ -0,0 +1,941 @@
/* ITu-T G.965 Section 16.2 V5.2-interface Link control FSM - LE side */
/* (C) 2021 by Harald Welte <laforge@gnumonks.org>
* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
*
* 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.
*
* 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.
*
*/
/***********************************************************************/
/* internal data structures */
/***********************************************************************/
#include <unistd.h>
#include <stdint.h>
#include <errno.h>
#include <osmocom/core/utils.h>
#include "v5x_internal.h"
#include "v5x_protocol.h"
#include "layer1.h"
#include "v5x_l1_fsm.h"
#include "v5x_le_ctrl_fsm.h"
#include "v5x_le_management.h"
#include "v52_le_lcp_fsm.h"
#include "logging.h"
#define S(x) (1 << (x))
/***********************************************************************/
/* state names, event names, primitives, ... */
/***********************************************************************/
/* 16.2.4.2.2 */
enum v52_lcp_fsm_state {
V52_LCPFSM_S_LE01_NOP_LINK_FAILURE,
V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED,
V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED,
V52_LCPFSM_S_LE11_NOP_LOCAL_LINK_UNBLOCK,
V52_LCPFSM_S_LE12_NOP_REMOTE_LINK_UNBLOCK,
V52_LCPFSM_S_LE20_OP_OPERATIONAL,
V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID,
V52_LCPFSM_S_LE22_OP_LOCAL_LINK_ID,
};
enum v52_lcp_fsm_event {
V52_LCPFSM_E_MPH_AI, /* Activate Indication (L1 link operational) */
V52_LCPFSM_E_MPH_DI, /* Deactivate Indication (L1 link not operational) */
V52_LCPFSM_E_MDU_IDReq, /* Identification Request */
V52_LCPFSM_E_FE_IDAck,
V52_LCPFSM_E_MPH_IDI, /* Identification Indication */
V52_LCPFSM_E_MPH_EIg, /* Identification Failure */
V52_LCPFSM_E_FE_IDReq,
V52_LCPFSM_E_MDU_IDAck, /* Send Link ID ACK */
V52_LCPFSM_E_FE_IDRel,
V52_LCPFSM_E_MDU_IDRej, /* Link ID Reject */
V52_LCPFSM_E_FE_IDRej,
V52_LCPFSM_E_MDU_LUBR, /* Link Unblock Request */
V52_LCPFSM_E_MDU_LBI, /* Link Block Indication */
V52_LCPFSM_E_FE302, /* AN Initiated unblocking */
V52_LCPFSM_E_FE304, /* AN initiated link block */
V52_LCPFSM_E_FE305, /* deferred link block request */
V52_LCPFSM_E_FE306, /* non-deferred link block request */
};
static const struct value_string v52_lcp_fsm_event_names[] = {
{ V52_LCPFSM_E_MPH_AI, "MPH-AI" },
{ V52_LCPFSM_E_MPH_DI, "MPH-DI" },
{ V52_LCPFSM_E_MDU_IDReq, "MDU-IDReq" },
{ V52_LCPFSM_E_FE_IDAck, "FE-IDAck" },
{ V52_LCPFSM_E_MPH_IDI, "MPH-IDI" },
{ V52_LCPFSM_E_MPH_EIg, "MPH-EIg" },
{ V52_LCPFSM_E_FE_IDReq, "FE-IDReq" },
{ V52_LCPFSM_E_MDU_IDAck, "MDU-IDack" },
{ V52_LCPFSM_E_FE_IDRel, "FE-IDRel" },
{ V52_LCPFSM_E_MDU_IDRej, "MDU-IDRej" },
{ V52_LCPFSM_E_FE_IDRej, "FE-IDRej" },
{ V52_LCPFSM_E_MDU_LUBR, "MDU-LUBR" },
{ V52_LCPFSM_E_MDU_LBI, "MDU-LBI" },
{ V52_LCPFSM_E_FE302, "FE302" },
{ V52_LCPFSM_E_FE304, "FE304" },
{ V52_LCPFSM_E_FE305, "FE305" },
{ V52_LCPFSM_E_FE306, "FE306" },
{ 0, NULL }
};
/***********************************************************************/
/* Messages to other layers */
/***********************************************************************/
/* send message to upper (management) layer */
static void rcv_mdu(struct osmo_fsm_inst *fi, enum v5x_mgmt_prim prim)
{
struct v5x_link *v5l = fi->priv;
v52_le_lcp_mdu_rcv(v5l, prim);
}
/* send message to lower (L1 FSM) layer */
static void snd_mph(struct osmo_fsm_inst *fi, enum v5x_mph_prim prim)
{
struct v5x_link *v5l = fi->priv;
v5x_l1_mph_snd(v5l, prim);
}
/* send message to lower (CTRL) layer */
void snd_fe(struct osmo_fsm_inst *fi, enum v52_link_ctrl_func lcf)
{
struct v5x_link *v5l = fi->priv;
v52_le_ctrl_link_snd(v5l, lcf);
}
/***********************************************************************/
/* LCP state FSM */
/***********************************************************************/
static void lcp_fsm_le01_link_failure(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
/* Send MDU-LAI */
rcv_mdu(fi, MDU_LAI);
break;
case V52_LCPFSM_E_MPH_DI:
/* ignore */
break;
case V52_LCPFSM_E_MDU_IDReq:
/* Send MDU-DI */
rcv_mdu(fi, MDU_DI);
break;
case V52_LCPFSM_E_FE_IDReq:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
break;
case V52_LCPFSM_E_FE_IDRel:
case V52_LCPFSM_E_FE_IDRej:
/* ignore */
break;
case V52_LCPFSM_E_MDU_LUBR:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
/* Send MDU-DI */
rcv_mdu(fi, MDU_DI);
break;
case V52_LCPFSM_E_MDU_LBI:
case V52_LCPFSM_E_FE302:
case V52_LCPFSM_E_FE305:
case V52_LCPFSM_E_FE306:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
break;
case V52_LCPFSM_E_FE304:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le02_link_failure_blocked(struct osmo_fsm_inst *fi, uint32_t event,
void __attribute__((unused)) *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send MDU-LBI */
rcv_mdu(fi, MDU_LBI);
/* Send MDU-LAI */
rcv_mdu(fi, MDU_LAI);
break;
case V52_LCPFSM_E_MPH_DI:
/* ignore */
break;
case V52_LCPFSM_E_MDU_IDReq:
/* Send MDU-DI */
rcv_mdu(fi, MDU_DI);
break;
case V52_LCPFSM_E_FE_IDReq:
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
break;
case V52_LCPFSM_E_MDU_LUBR:
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
/* Send MDU-DI */
rcv_mdu(fi, MDU_DI);
break;
case V52_LCPFSM_E_MDU_LBI:
case V52_LCPFSM_E_FE302:
case V52_LCPFSM_E_FE305:
case V52_LCPFSM_E_FE306:
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
break;
case V52_LCPFSM_E_FE304:
/* ignore */
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le10_link_blocked(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* Send MDU-LAI */
rcv_mdu(fi, MDU_LAI);
break;
case V52_LCPFSM_E_MPH_DI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
/* Send MDU-DI */
rcv_mdu(fi, MDU_DI);
break;
case V52_LCPFSM_E_MDU_IDReq:
/* Send MDU-LBI */
rcv_mdu(fi, MDU_LBI);
break;
case V52_LCPFSM_E_FE_IDReq:
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
break;
case V52_LCPFSM_E_FE_IDRel:
case V52_LCPFSM_E_FE_IDRej:
/* ignore */
break;
case V52_LCPFSM_E_MDU_LUBR:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE11_NOP_LOCAL_LINK_UNBLOCK, 0, 0);
/* Send FE301 */
snd_fe(fi, V52_LCP_FE_301_302_LINK_UNBLOCK);
break;
case V52_LCPFSM_E_MDU_LBI:
case V52_LCPFSM_E_FE305:
case V52_LCPFSM_E_FE306:
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
break;
case V52_LCPFSM_E_FE302:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE12_NOP_REMOTE_LINK_UNBLOCK, 0, 0);
/* Send MDL-LUBR */
rcv_mdu(fi, MDU_LUBR);
break;
case V52_LCPFSM_E_FE304:
/* ignore */
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le11_local_link_unblock(struct osmo_fsm_inst *fi, uint32_t event,
__attribute__((unused)) void *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* ignore */
break;
case V52_LCPFSM_E_MPH_DI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
/* Send MDU-DI */
rcv_mdu(fi, MDU_DI);
break;
case V52_LCPFSM_E_MDU_IDReq:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send MDU-LBI */
rcv_mdu(fi, MDU_LBI);
break;
case V52_LCPFSM_E_FE_IDReq:
/* Send FE-IDRej */
snd_fe(fi, V52_LCP_FE_IDRej);
break;
case V52_LCPFSM_E_FE_IDRel:
case V52_LCPFSM_E_FE_IDRej:
/* ignore */
break;
case V52_LCPFSM_E_MDU_LUBR:
/* Send FE301 */
snd_fe(fi, V52_LCP_FE_301_302_LINK_UNBLOCK);
break;
case V52_LCPFSM_E_MDU_LBI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
break;
case V52_LCPFSM_E_FE302:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
/* Send MDU-LUBI */
rcv_mdu(fi, MDU_LUBI);
break;
case V52_LCPFSM_E_FE304:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send MDU-LBI */
rcv_mdu(fi, MDU_LBI);
break;
case V52_LCPFSM_E_FE305:
case V52_LCPFSM_E_FE306:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
/* Send MDU-LBI */
rcv_mdu(fi, MDU_LBI);
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le12_remote_link_unblock(struct osmo_fsm_inst *fi, uint32_t event,
void __attribute__((unused)) *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* ignore */
break;
case V52_LCPFSM_E_MPH_DI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
/* Send MDU-DI */
rcv_mdu(fi, MDU_DI);
break;
case V52_LCPFSM_E_MDU_IDReq:
/* Send MDU-IDRej */
rcv_mdu(fi, MDU_IDRej);
/* Send MDU-LUBR */
rcv_mdu(fi, MDU_LUBR);
break;
case V52_LCPFSM_E_FE_IDReq:
/* Send FE-IDRej */
snd_fe(fi, V52_LCP_FE_IDRej);
break;
case V52_LCPFSM_E_MDU_LUBR:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
/* Send FE301 */
snd_fe(fi, V52_LCP_FE_301_302_LINK_UNBLOCK);
/* Send MDU-LUBI */
rcv_mdu(fi, MDU_LUBI);
break;
case V52_LCPFSM_E_MDU_LBI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
break;
case V52_LCPFSM_E_FE302:
/* Send MDU-LUBR */
rcv_mdu(fi, MDU_LUBR);
break;
case V52_LCPFSM_E_FE304:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send MDU-LBI */
rcv_mdu(fi, MDU_LBI);
break;
case V52_LCPFSM_E_FE305:
case V52_LCPFSM_E_FE306:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
/* Send MDU-LBI */
rcv_mdu(fi, MDU_LBI);
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le20_operational(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* ignore */
break;
case V52_LCPFSM_E_MPH_DI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE01_NOP_LINK_FAILURE, 0, 0);
/* Send MDU-DI */
rcv_mdu(fi, MDU_DI);
break;
case V52_LCPFSM_E_MDU_IDReq:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE22_OP_LOCAL_LINK_ID, 0, 0);
/* Send FE-IDReq */
snd_fe(fi, V52_LCP_FE_IDReq);
break;
case V52_LCPFSM_E_FE_IDReq:
/* Send MDU-IDReq */
rcv_mdu(fi, MDU_IDReq);
break;
case V52_LCPFSM_E_MDU_IDAck:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID, 0, 0);
/* Send FE-IDAck */
snd_fe(fi, V52_LCP_FE_IDAck);
/* Send MPH-ID */
snd_mph(fi, MPH_ID);
break;
case V52_LCPFSM_E_MDU_IDRej:
/* Send FE-IDRej */
snd_fe(fi, V52_LCP_FE_IDRej);
break;
case V52_LCPFSM_E_MDU_LUBR:
/* Send FE301 */
snd_fe(fi, V52_LCP_FE_301_302_LINK_UNBLOCK);
break;
case V52_LCPFSM_E_MDU_LBI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
break;
case V52_LCPFSM_E_FE302:
/* Send MDU-LUBI */
rcv_mdu(fi, MDU_LUBI);
break;
case V52_LCPFSM_E_FE304:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send MDU-LBI */
rcv_mdu(fi, MDU_LBI);
break;
case V52_LCPFSM_E_FE305:
/* Send MDU-LBR */
rcv_mdu(fi, MDU_LBR);
break;
case V52_LCPFSM_E_FE306:
/* Send MDU-LBRN */
rcv_mdu(fi, MDU_LBRN);
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le21_op_remote_link_id(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* ignore */
break;
case V52_LCPFSM_E_MPH_DI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE01_NOP_LINK_FAILURE, 0, 0);
/* Send MPH-NOR */
snd_mph(fi, MPH_NOR);
/* Send MDU-DI */
rcv_mdu(fi, MDU_DI);
break;
case V52_LCPFSM_E_MDU_IDReq:
/* Send MDU-IDRej */
rcv_mdu(fi, MDU_IDRej);
break;
case V52_LCPFSM_E_FE_IDReq:
/* ignore */
break;
case V52_LCPFSM_E_MDU_IDAck:
/* ignore */
break;
case V52_LCPFSM_E_FE_IDRel:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
/* Send MDU-IDRel */
rcv_mdu(fi, MDU_IDRel);
/* Send MPH-NOR */
snd_mph(fi, MPH_NOR);
break;
case V52_LCPFSM_E_MDU_IDRej:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
/* Send FE-IDRej */
rcv_mdu(fi, MDU_IDRej);
/* Send MPH-NOR */
snd_mph(fi, MPH_NOR);
break;
case V52_LCPFSM_E_MDU_LUBR:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
/* Send FE301 */
snd_fe(fi, V52_LCP_FE_301_302_LINK_UNBLOCK);
/* Send MPH-NOR */
snd_mph(fi, MPH_NOR);
break;
case V52_LCPFSM_E_MDU_LBI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
/* Send MPH-NOR */
snd_mph(fi, MPH_NOR);
break;
case V52_LCPFSM_E_FE302:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
/* Send MPH-NOR */
snd_mph(fi, MPH_NOR);
/* Send MDU-IDRel */
rcv_mdu(fi, MDU_IDRel);
/* Send MDU-LUBI */
rcv_mdu(fi, MDU_LUBI);
break;
case V52_LCPFSM_E_FE304:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send MPH-NOR */
snd_mph(fi, MPH_NOR);
/* Send MDU-LBI */
rcv_mdu(fi, MDU_LBI);
break;
case V52_LCPFSM_E_FE305:
/* Send MDU-LBR */
rcv_mdu(fi, MDU_LBR);
break;
case V52_LCPFSM_E_FE306:
/* Send MDU-LBRN */
rcv_mdu(fi, MDU_LBRN);
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le22_op_local_link_id(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* ignore */
break;
case V52_LCPFSM_E_MPH_DI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE01_NOP_LINK_FAILURE, 0, 0);
/* Send FE-IDRel */
snd_fe(fi, V52_LCP_FE_IDRel);
/* Send MDU-DI */
rcv_mdu(fi, MDU_DI);
break;
case V52_LCPFSM_E_MDU_IDReq:
/* ignore */
break;
case V52_LCPFSM_E_FE_IDAck:
/* Send MPH-IDR */
snd_mph(fi, MPH_IDR);
break;
case V52_LCPFSM_E_MPH_IDI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
/* Send FE-IDRel */
snd_fe(fi, V52_LCP_FE_IDRel);
/* Send MDU-AI */
rcv_mdu(fi, MDU_AI);
break;
case V52_LCPFSM_E_MPH_EIg:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
/* Send FE-IDRel */
snd_fe(fi, V52_LCP_FE_IDRel);
/* Send MDU-EIg */
rcv_mdu(fi, MDU_EIg);
break;
case V52_LCPFSM_E_FE_IDReq:
/* Send FE-IDRej */
snd_fe(fi, V52_LCP_FE_IDRej);
break;
case V52_LCPFSM_E_FE_IDRej:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
/* Send MDU-IDRej */
rcv_mdu(fi, MDU_IDRej);
break;
case V52_LCPFSM_E_MDU_LUBR:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
/* Send FE301 */
snd_fe(fi, V52_LCP_FE_301_302_LINK_UNBLOCK);
break;
case V52_LCPFSM_E_MDU_LBI:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send FE303 */
snd_fe(fi, V52_LCP_FE_303_304_LINK_BLOCK);
break;
case V52_LCPFSM_E_FE302:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
/* Send MDU-IDRej */
rcv_mdu(fi, MDU_IDRej);
/* Send MDU-LUBI */
rcv_mdu(fi, MDU_LUBI);
break;
case V52_LCPFSM_E_FE304:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
/* Send MDU-LBI */
rcv_mdu(fi, MDU_LBI);
break;
case V52_LCPFSM_E_FE305:
/* Send MDU-LBR */
rcv_mdu(fi, MDU_LBR);
break;
case V52_LCPFSM_E_FE306:
/* Send MDU-LBRN */
rcv_mdu(fi, MDU_LBRN);
break;
default:
OSMO_ASSERT(0);
}
}
/* Table 17G.965 */
static const struct osmo_fsm_state v52_le_lcp_fsm_states[] = {
[V52_LCPFSM_S_LE01_NOP_LINK_FAILURE] = {
.name = "Link failure (LE0.1)",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_FE_IDRel) |
S(V52_LCPFSM_E_FE_IDRej) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE20_OP_OPERATIONAL) |
S(V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED),
.action = lcp_fsm_le01_link_failure,
},
[V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED] = {
.name = "Link failure and blocked(LE0.2)",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED),
.action = lcp_fsm_le02_link_failure_blocked,
},
[V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED] = {
.name = "Link blocked (LE1.0)",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_FE_IDRel) |
S(V52_LCPFSM_E_FE_IDRej) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED) |
S(V52_LCPFSM_S_LE11_NOP_LOCAL_LINK_UNBLOCK) |
S(V52_LCPFSM_S_LE12_NOP_REMOTE_LINK_UNBLOCK),
.action = lcp_fsm_le10_link_blocked,
},
[V52_LCPFSM_S_LE11_NOP_LOCAL_LINK_UNBLOCK] = {
.name = "Local link unblock (LE1.1)",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_FE_IDRel) |
S(V52_LCPFSM_E_FE_IDRej) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED) |
S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED) |
S(V52_LCPFSM_S_LE20_OP_OPERATIONAL),
.action = lcp_fsm_le11_local_link_unblock,
},
[V52_LCPFSM_S_LE12_NOP_REMOTE_LINK_UNBLOCK] = {
.name = "Remote link unblocke (LE1.2)",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED) |
S(V52_LCPFSM_S_LE20_OP_OPERATIONAL) |
S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED),
.action = lcp_fsm_le12_remote_link_unblock,
},
[V52_LCPFSM_S_LE20_OP_OPERATIONAL] = {
.name = "Link Operational (LE2.0)",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_MDU_IDAck) |
S(V52_LCPFSM_E_MDU_IDRej) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE01_NOP_LINK_FAILURE) |
S(V52_LCPFSM_S_LE22_OP_LOCAL_LINK_ID) |
S(V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID) |
S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED),
.action = lcp_fsm_le20_operational,
},
[V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID] = {
.name = "Remote link identification (LE2.1)",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_MDU_IDAck) |
S(V52_LCPFSM_E_FE_IDRel) |
S(V52_LCPFSM_E_MDU_IDRej) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE01_NOP_LINK_FAILURE) |
S(V52_LCPFSM_S_LE20_OP_OPERATIONAL) |
S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED),
.action = lcp_fsm_le21_op_remote_link_id,
},
[V52_LCPFSM_S_LE22_OP_LOCAL_LINK_ID] = {
.name = "Local link identification (LE2.2)",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDAck) |
S(V52_LCPFSM_E_MPH_IDI) |
S(V52_LCPFSM_E_MPH_EIg) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_FE_IDRej) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE01_NOP_LINK_FAILURE) |
S(V52_LCPFSM_S_LE20_OP_OPERATIONAL) |
S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED),
.action =lcp_fsm_le22_op_local_link_id,
},
};
struct osmo_fsm v52_le_lcp_fsm = {
.name = "V52_LE_LCP",
.states = v52_le_lcp_fsm_states,
.num_states = ARRAY_SIZE(v52_le_lcp_fsm_states),
.timer_cb = NULL,
.log_subsys = DV5LCP,
.event_names = v52_lcp_fsm_event_names,
};
struct osmo_fsm_inst *v52_le_lcp_create(void *ctx, struct v5x_link *v5l, uint8_t id)
{
struct osmo_fsm_inst *fi;
OSMO_ASSERT(v5l);
fi = osmo_fsm_inst_alloc(&v52_le_lcp_fsm, ctx, v5l, LOGL_DEBUG, NULL);
if (!fi)
return NULL;
osmo_fsm_inst_update_id_f(fi, "%d", id);
/* initial state for links that are not in failure state */
fi->state = V52_LCPFSM_S_LE20_OP_OPERATIONAL;
return fi;
}
void v52_le_lcp_destroy(struct osmo_fsm_inst *fi)
{
if (fi)
osmo_fsm_inst_free(fi);
}
bool v52_le_lcp_is_operational(struct osmo_fsm_inst *fi)
{
return (fi->state >= V52_LCPFSM_S_LE20_OP_OPERATIONAL);
}
const char *v52_le_lcp_state_name(struct osmo_fsm_inst *fi)
{
return v52_le_lcp_fsm_states[fi->state].name;
}
void v52_le_lcp_init(void)
{
int rc;
rc = osmo_fsm_register(&v52_le_lcp_fsm);
OSMO_ASSERT(!rc);
LOGP(DV5LCP, LOGL_NOTICE, "Using V52 link control protocol\n");
}
/***********************************************************************
* Messages from other layers
***********************************************************************/
/* receive message from lower (L1 FSM) layer */
int v52_le_lcp_mph_rcv(struct v5x_link *v5l, enum v5x_mph_prim prim)
{
enum v52_lcp_fsm_event event = 9999;
switch (prim) {
case MPH_AI:
LOGP(DV5LCP, LOGL_NOTICE, "Received link activation indication from L1 (link ID %d).\n", v5l->id);
event = V52_LCPFSM_E_MPH_AI;
break;
case MPH_DI:
LOGP(DV5LCP, LOGL_NOTICE, "Received link deactivation indication from L1 (link ID %d).\n", v5l->id);
event = V52_LCPFSM_E_MPH_DI;
break;
case MPH_EIa:
LOGP(DV5LCP, LOGL_DEBUG, "Received link error indication from L1 (link ID %d): LOS\n", v5l->id);
return 0;
case MPH_EIb:
LOGP(DV5LCP, LOGL_DEBUG, "Received link error indication from L1 (link ID %d): RAI\n", v5l->id);
return 0;
case MPH_EIc:
LOGP(DV5LCP, LOGL_DEBUG, "Received link error indication from L1 (link ID %d): AIS\n", v5l->id);
return 0;
case MPH_EId:
LOGP(DV5LCP, LOGL_DEBUG, "Received link error indication from L1 (link ID %d): Internal failure\n",
v5l->id);
return 0;
case MPH_EIe:
LOGP(DV5LCP, LOGL_DEBUG, "Received link error indication from L1 (link ID %d): CRC error\n", v5l->id);
return 0;
case MPH_EIf:
LOGP(DV5LCP, LOGL_DEBUG, "Received link error indication from L1 (link ID %d): "
"Remote CRC error indication\n", v5l->id);
return 0;
case MPH_IDI:
LOGP(DV5LCP, LOGL_NOTICE, "Received link identification indication from L1 (link ID %d).\n", v5l->id);
event = V52_LCPFSM_E_MPH_IDI;
break;
case MPH_EIg:
LOGP(DV5LCP, LOGL_NOTICE, "Received link identification failure from L1 (link ID %d).\n", v5l->id);
event = V52_LCPFSM_E_MPH_EIg;
break;
case MPH_EIdr:
return 0;
case MPH_EIbr:
return 0;
default:
LOGP(DV5LCP, LOGL_NOTICE, "Invalid LCP primitive %d receied from L1/AN (link ID %d).\n", prim, v5l->id);
return -EINVAL;
}
OSMO_ASSERT(event < 9999);
osmo_fsm_inst_dispatch(v5l->fi, event, NULL);
return 0;
}
/* receive message from lower (CTRL) layer */
int v52_le_lcp_fe_rcv(struct v5x_link *v5l, enum v52_link_ctrl_func lcf)
{
enum v52_lcp_fsm_event event;
switch (lcf) {
case V52_LCP_FE_IDReq:
event = V52_LCPFSM_E_FE_IDReq;
break;
case V52_LCP_FE_IDAck:
event = V52_LCPFSM_E_FE_IDAck;
break;
case V52_LCP_FE_IDRel:
event = V52_LCPFSM_E_FE_IDRel;
break;
case V52_LCP_FE_IDRej:
event = V52_LCPFSM_E_FE_IDRej;
break;
case V52_LCP_FE_301_302_LINK_UNBLOCK:
event = V52_LCPFSM_E_FE302;
break;
case V52_LCP_FE_303_304_LINK_BLOCK:
event = V52_LCPFSM_E_FE304;
break;
case V52_LCP_FE_305_DEF_LINK_BLOCK_REQ:
event = V52_LCPFSM_E_FE305;
break;
case V52_LCP_FE_306_NON_DEF_LINK_BLOCK_REQ:
event = V52_LCPFSM_E_FE306;
break;
default:
LOGP(DV5LCP, LOGL_NOTICE, "Invalid link control function primitive %d receied from AN.\n", lcf);
return -EINVAL;
}
osmo_fsm_inst_dispatch(v5l->fi, event, NULL);
return 0;
}
int v52_le_lcp_mdu_snd(struct v5x_link *v5l, enum v5x_mgmt_prim prim)
{
enum v52_lcp_fsm_event event;
switch (prim) {
case MDU_IDReq:
event = V52_LCPFSM_E_MDU_IDReq;
break;
case MDU_IDAck:
event = V52_LCPFSM_E_MDU_IDAck;
break;
case MDU_IDRej:
event = V52_LCPFSM_E_MDU_IDRej;
break;
case MDU_LUBR:
event = V52_LCPFSM_E_MDU_LUBR;
break;
case MDU_LBI:
event = V52_LCPFSM_E_MDU_LBI;
break;
default:
LOGP(DV5LCP, LOGL_NOTICE, "Invalid MDU primitive %d receied from management.\n", prim);
return -EINVAL;
}
osmo_fsm_inst_dispatch(v5l->fi, event, NULL);
return 0;
}

9
src/v52_le_lcp_fsm.h Normal file
View File

@ -0,0 +1,9 @@
struct osmo_fsm_inst *v52_le_lcp_create(void *ctx, struct v5x_link *v5l, uint8_t id);
void v52_le_lcp_destroy(struct osmo_fsm_inst *fi);
bool v52_le_lcp_is_operational(struct osmo_fsm_inst *fi);
const char *v52_le_lcp_state_name(struct osmo_fsm_inst *fi);
void v52_le_lcp_init(void);
int v52_le_lcp_mph_rcv(struct v5x_link *v5l, enum v5x_mph_prim prim);
int v52_le_lcp_fe_rcv(struct v5x_link *v5l, enum v52_link_ctrl_func lcf);
int v52_le_lcp_mdu_snd(struct v5x_link *v5l, enum v5x_mgmt_prim prim);

845
src/v52_le_pp_fsm.c Normal file
View File

@ -0,0 +1,845 @@
/* ITu-T G.965 Section 18 V5.2-interface Protection protocol FSM - LE side */
/* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
*
* 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.
*
* 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.
*
*/
/***********************************************************************/
/* internal data structures */
/***********************************************************************/
#include <unistd.h>
#include <stdint.h>
#include <errno.h>
#include <arpa/inet.h>
#include <osmocom/core/utils.h>
#include "v5x_internal.h"
#include "v5x_protocol.h"
#include "v5x_le_management.h"
#include "v52_le_pp_fsm.h"
#include "logging.h"
/* Table 64/G.965 Timers in the LE */
#define TIMEOUT_TSO1 1500
#define TIMEOUT_TSO2 1500
#define TIMEOUT_TSO4 20000
#define TIMEOUT_TSO5 10000
#define S(x) (1 << (x))
/***********************************************************************/
/* state names, event names, primitives, ... */
/***********************************************************************/
/* 18.3.1.2 */
enum v52_le_pp_fsm_state {
V52_PPFSM_S_SOLE0_NULL,
V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE,
V52_PPFSM_S_SOLE2_SWITCH_OVER_REQ_BY_AN,
};
enum v52_le_pp_fsm_event {
V52_PPFSM_E_MDU_Protection_switch_over_com,
V52_PPFSM_E_MDU_Protection_OS_switch_over_com,
V52_PPFSM_E_MDU_Protection_switch_over_reject,
V52_PPFSM_E_MDU_Protection_reset_SN_req,
V52_PPFSM_E_VP_S_VP_R_misalignment_detected,
V52_PPFSM_E_SWITCH_OVER_REQ,
V52_PPFSM_E_SWITCH_OVER_ACK,
V52_PPFSM_E_SWITCH_OVER_REJECT,
V52_PPFSM_E_PROTOCOL_ERROR,
V52_PPFSM_E_RESET_SN_COM,
V52_PPFSM_E_RESET_SN_ACK,
V52_PPFSM_E_TSO1,
V52_PPFSM_E_TSO2,
V52_PPFSM_E_TSO4,
V52_PPFSM_E_TSO5,
};
static const struct value_string v52_le_pp_fsm_event_names[] = {
{ V52_PPFSM_E_MDU_Protection_switch_over_com, "MDU-Protection (switch_over com)" },
{ V52_PPFSM_E_MDU_Protection_OS_switch_over_com, "MDU-Protection (OS_switch-over com)" },
{ V52_PPFSM_E_MDU_Protection_switch_over_reject, "MDU-Protection (switch-over reject)" },
{ V52_PPFSM_E_MDU_Protection_reset_SN_req, "MDU-Protection (reset SN req)" },
{ V52_PPFSM_E_VP_S_VP_R_misalignment_detected, "VP(S) VP(R) misalignment detected" },
{ V52_PPFSM_E_SWITCH_OVER_REQ, "SWITCH-OVER REQ" },
{ V52_PPFSM_E_SWITCH_OVER_ACK, "SWITCH-OVER ACK" },
{ V52_PPFSM_E_SWITCH_OVER_REJECT, "SWITCH-OVER REJECT" },
{ V52_PPFSM_E_PROTOCOL_ERROR, "PROTOCOL ERROR" },
{ V52_PPFSM_E_RESET_SN_COM, "RESET SN COM" },
{ V52_PPFSM_E_RESET_SN_ACK, "RESET SN ACK" },
{ V52_PPFSM_E_TSO1, "expiry TSO1" },
{ V52_PPFSM_E_TSO2, "expiry TSO2" },
{ V52_PPFSM_E_TSO4, "expiry TSO4" },
{ V52_PPFSM_E_TSO5, "expiry TSO5" },
{ 0, NULL }
};
/***********************************************************************/
/* Messages to other layers */
/***********************************************************************/
static inline void v52_pp_pcc_dec(uint16_t ie, uint8_t *link_id, uint8_t *ts);
/* send message to upper (management) layer */
static void mdu_rcv(struct osmo_fsm_inst *fi, enum v5x_mgmt_prim prim, const struct tlv_parsed *tp)
{
struct v52_pp_proto *pp = fi->priv;
struct v5x_interface *v5if = pp->interface;
const uint8_t *ie, *cause = NULL;
uint8_t ie_len, link_id = 0, ts = 0, cause_len = 0;
if (tp) {
if (TLVP_PRESENT(tp, V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID)) {
ie = TLVP_VAL(tp, V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID);
ie_len = TLVP_LEN(tp, V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID);
if (ie_len == 2)
v52_pp_pcc_dec(ntohs(*((uint16_t *)ie)), &link_id, &ts);
}
if (TLVP_PRESENT(tp, V52_CTRL_IEI_PP_REJECTION_CAUSE)) {
cause = TLVP_VAL(tp, V52_CTRL_IEI_PP_REJECTION_CAUSE);
cause_len = TLVP_LEN(tp, V52_CTRL_IEI_PP_REJECTION_CAUSE);
}
if (TLVP_PRESENT(tp, V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE)) {
cause = TLVP_VAL(tp, V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE);
cause_len = TLVP_LEN(tp, V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE);
}
}
v52_le_pp_mdu_rcv(v5if, prim, link_id, ts, cause, cause_len);
}
/* send message to lower (DL) layer(s) */
static void dl_send(struct osmo_fsm_inst *fi, struct msgb *msg)
{
struct v52_pp_proto *pp = fi->priv;
v5x_dl_snd(pp->interface, V52_DLADDR_PROTECTION, msg);
}
/***********************************************************************
* V5 Message encoding / sending
***********************************************************************/
static inline uint16_t v52_pp_pcc_enc(uint8_t link_id, uint8_t ts)
{
return (link_id << 8) | ts;
}
static inline void v52_pp_pcc_dec(uint16_t ie, uint8_t *link_id, uint8_t *ts)
{
*link_id = ie >> 8;
*ts = ie & 0x1f;
}
/* G.965 Section 18.4.2/18.4.3 / Table 53/54 */
static struct msgb *v52_enc_switch_over_com(struct v52_pp_proto *pp, uint16_t cc_id, uint8_t link_id, uint8_t ts,
uint8_t msg_type)
{
uint8_t seq_ie = pp->vp_s | 0x80;
uint16_t v5_cc_ie;
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(cc_id);
l3h->msg_type = msg_type;
/* Sequence Number */
msgb_tlv_put(msg, V52_CTRL_IEI_PP_SEQUENCE_NR, 1, &seq_ie);
pp->vp_s = (pp->vp_s + 1) & 0x7f;
/* Physical C-channel identification */
v5_cc_ie = htons(v52_pp_pcc_enc(link_id, ts));
msgb_tlv_put(msg, V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID, 2, (uint8_t *)&v5_cc_ie);
return msg;
}
/* G.965 Section 18.4.5 / Table 56 */
static struct msgb *v52_enc_switch_over_rej(struct v52_pp_proto *pp, uint16_t cc_id, uint8_t link_id, uint8_t ts,
uint8_t cause)
{
uint8_t seq_ie = pp->vp_s | 0x80;
uint16_t v5_cc_ie;
uint8_t cause_ie = cause | 0x80;
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(cc_id);
l3h->msg_type = V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT;
/* Sequence Number */
msgb_tlv_put(msg, V52_CTRL_IEI_PP_SEQUENCE_NR, 1, &seq_ie);
pp->vp_s = (pp->vp_s + 1) & 0x7f;
/* Physical C-channel identification */
v5_cc_ie = htons(v52_pp_pcc_enc(link_id, ts));
msgb_tlv_put(msg, V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID, 2, (uint8_t *)&v5_cc_ie);
/* Rejection Cause */
msgb_tlv_put(msg, V52_CTRL_IEI_PP_REJECTION_CAUSE, 1, &cause_ie);
return msg;
}
/* G.965 Section 18.4.6 / Table 57 */
static struct msgb *v52_enc_protocol_error(struct v52_pp_proto *pp, uint16_t cc_id, uint8_t cause, uint8_t *diagnostic,
int diagnostic_len)
{
uint8_t seq_ie = pp->vp_s | 0x80;
uint8_t cause_ie[3] = { cause | 0x80 };
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(cc_id);
l3h->msg_type = V52_CTRL_MSGT_PP_PROTOCOL_ERROR;
/* Sequence Number */
msgb_tlv_put(msg, V52_CTRL_IEI_PP_SEQUENCE_NR, 1, &seq_ie);
pp->vp_s = (pp->vp_s + 1) & 0x7f;
/* Protocol Error Cause */
if (diagnostic && diagnostic_len)
memcpy(cause_ie + 1, diagnostic, diagnostic_len);
msgb_tlv_put(msg, V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE, 1 + diagnostic_len, (uint8_t *)&cause_ie);
return msg;
}
/* G.965 Section 18.4.7/18.4.8 / Table 58/59 */
static struct msgb *v52_enc_reset_sn_xxx(uint16_t cc_id, uint8_t msg_type)
{
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(cc_id);
l3h->msg_type = msg_type;
return msg;
}
/***********************************************************************/
/* Protection Protocol state FSM */
/***********************************************************************/
static void stop_timer(struct osmo_fsm_inst *fi)
{
LOGP(DV5PP, LOGL_DEBUG, "Stop all timers\n");
osmo_timer_del(&fi->timer);
}
static void start_timer(struct osmo_fsm_inst *fi, enum v52_le_pp_fsm_event event, int count, struct v52_pp_mgmt_info *info)
{
struct v52_pp_proto *pp = fi->priv;
int timeout = 0;
const char *timer_name = NULL;
switch (event) {
case V52_PPFSM_E_TSO1:
timer_name = "TSO1";
timeout = TIMEOUT_TSO1;
fi->T = 1;
break;
case V52_PPFSM_E_TSO2:
timer_name = "TSO2";
timeout = TIMEOUT_TSO2;
fi->T = 2;
break;
case V52_PPFSM_E_TSO4:
timer_name = "TSO4";
timeout = TIMEOUT_TSO4;
fi->T = 3;
break;
case V52_PPFSM_E_TSO5:
timer_name = "TSO5";
timeout = TIMEOUT_TSO5;
fi->T = 4;
break;
default:
OSMO_ASSERT(0);
}
if (info)
memcpy(&pp->info, info, sizeof(*info));
LOGP(DV5PP, LOGL_DEBUG, "Start timer %s (count = %d)\n", timer_name, count);
pp->timeout_event = event;
pp->timeout_count = count;
osmo_timer_schedule(&fi->timer, timeout / 1000, timeout % 1000);
}
static bool timer_pending(struct osmo_fsm_inst *fi, enum v52_le_pp_fsm_event event)
{
struct v52_pp_proto *pp = fi->priv;
if (pp->timeout_event != (int)event)
return false;
return osmo_timer_pending(&fi->timer);
}
static int v52_le_pp_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct v52_pp_proto *pp = fi->priv;
osmo_fsm_inst_dispatch(fi, pp->timeout_event, &pp->info);
return 0;
}
static void pp_sole0_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct v52_pp_proto *pp = fi->priv;
struct v52_pp_mgmt_info *info = data;
switch (event) {
case V52_PPFSM_E_MDU_Protection_switch_over_com:
if (!timer_pending(fi, V52_PPFSM_E_TSO4)) {
/* change state to SOLE1 */
osmo_fsm_inst_state_chg_ms(fi, V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE, 0, 0);
/* start TSO1 */
start_timer(fi, V52_PPFSM_E_TSO1, 1, info);
/* send SWITCH-OVER COM */
dl_send(fi, v52_enc_switch_over_com(pp, pp->interface->protection.cc_id, info->link_id, info->ts, V52_CTRL_MSGT_PP_SWITCH_OVER_COM));
} else {
/* send MDU-Prot. (reset SN error ind) */
mdu_rcv(fi, MDU_Protection_reset_SN_error_ind, NULL);
}
break;
case V52_PPFSM_E_MDU_Protection_OS_switch_over_com:
if (!timer_pending(fi, V52_PPFSM_E_TSO4)) {
/* change state to SOLE1 */
osmo_fsm_inst_state_chg_ms(fi, V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE, 0, 0);
/* start TSO2 */
start_timer(fi, V52_PPFSM_E_TSO2, 1, info);
/* send OS SWITCH-OVER COM */
dl_send(fi, v52_enc_switch_over_com(pp, pp->interface->protection.cc_id, info->link_id, info->ts, V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM));
} else {
/* send MDU-Prot. (reset SN error ind) */
mdu_rcv(fi, MDU_Protection_reset_SN_error_ind, NULL);
}
break;
case V52_PPFSM_E_SWITCH_OVER_ACK:
if (!timer_pending(fi, V52_PPFSM_E_TSO4)) {
/* send MDU-Prot. (switch-over ack) */
mdu_rcv(fi, MDU_Protection_switch_over_ack, data);
} else {
/* ignore */
}
break;
case V52_PPFSM_E_SWITCH_OVER_REQ:
if (!timer_pending(fi, V52_PPFSM_E_TSO4)) {
/* change state to SOLE2 */
osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE2_SWITCH_OVER_REQ_BY_AN, 0, 0);
/* send MDU-Prot. (switch-over req) */
mdu_rcv(fi, MDU_Protection_switch_over_req, data);
} else {
/* ignore */
}
break;
case V52_PPFSM_E_SWITCH_OVER_REJECT:
if (!timer_pending(fi, V52_PPFSM_E_TSO4)) {
/* send MDU-Prot. (switch-over reject ind) */
mdu_rcv(fi, MDU_Protection_switch_over_reject_ind, data);
} else {
/* ignore */
}
break;
case V52_PPFSM_E_VP_S_VP_R_misalignment_detected:
case V52_PPFSM_E_MDU_Protection_reset_SN_req:
/* start TSO4 */
start_timer(fi, V52_PPFSM_E_TSO4, 1, NULL);
/* send RESET SN COM */
dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_COM));
/* set VP(S) = VP(R) = 0 */
pp->vp_s = pp->vp_r = 0;
/* send MDU-Prot. (reset SN com) */
mdu_rcv(fi, MDU_Protection_reset_SN_com, NULL);
break;
case V52_PPFSM_E_RESET_SN_COM:
if (!timer_pending(fi, V52_PPFSM_E_TSO5)) {
/* start TSO5 */
start_timer(fi, V52_PPFSM_E_TSO5, 1, NULL);
/* set VP(S) = VP(R) = 0 */
pp->vp_s = pp->vp_r = 0;
/* send RESET SN ACK */
dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_ACK));
/* send MDU-Prot. (reset SN ind) */
mdu_rcv(fi, MDU_Protection_reset_SN_ind, NULL);
} else {
/* ignore */
}
break;
case V52_PPFSM_E_RESET_SN_ACK:
if (!timer_pending(fi, V52_PPFSM_E_TSO4)) {
/* ignore */
} else {
/* stop TSO4 */
stop_timer(fi);
/* send MDU-Prot. (reset SN ack) */
mdu_rcv(fi, MDU_Protection_reset_SN_ack, NULL);
}
break;
case V52_PPFSM_E_TSO4:
if (pp->timeout_count < 2) {
/* restart TSO4 */
start_timer(fi, V52_PPFSM_E_TSO4, 2, NULL);
/* send RESET SN COM */
dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_COM));
/* set VP(S) = VP(R) = 0 */
pp->vp_s = pp->vp_r = 0;
/* send MDU-Prot. (reset SN com) */
mdu_rcv(fi, MDU_Protection_reset_SN_com, NULL);
} else {
/* send MDU-Prot. (reset SN error ind) */
mdu_rcv(fi, MDU_Protection_reset_SN_error_ind, NULL);
}
break;
case V52_PPFSM_E_TSO5:
/* ignore */
break;
case V52_PPFSM_E_PROTOCOL_ERROR:
if (!timer_pending(fi, V52_PPFSM_E_TSO4)) {
/* send MDU-Prot. (Protocol error ind) */
mdu_rcv(fi, MDU_Protection_protocol_error_ind, data);
} else {
/* ignore */
}
break;
default:
OSMO_ASSERT(0);
}
}
static void pp_sole1_switch_over_init_by_le(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct v52_pp_proto *pp = fi->priv;
struct v52_pp_mgmt_info *info = data;
switch (event) {
case V52_PPFSM_E_SWITCH_OVER_ACK:
/* stop timer */
stop_timer(fi);
/* change state to SOLE0, stop timer */
osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0);
/* send MDU-Prot. (switch-over ack) */
mdu_rcv(fi, MDU_Protection_switch_over_ack, data);
break;
case V52_PPFSM_E_SWITCH_OVER_REQ:
/* ignore */
break;
case V52_PPFSM_E_SWITCH_OVER_REJECT:
/* stop timer */
stop_timer(fi);
/* change state to SOLE0 */
osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0);
/* send MDU-Prot. (switch-over reject ind) */
mdu_rcv(fi, MDU_Protection_switch_over_reject_ind, data);
break;
case V52_PPFSM_E_TSO1:
/* first timeout ? */
if (pp->timeout_count < 2) {
/* restart TSO1 */
start_timer(fi, V52_PPFSM_E_TSO1, 2, info);
/* send SWITCH-OVER COM */
dl_send(fi, v52_enc_switch_over_com(pp, pp->interface->protection.cc_id, info->link_id, info->ts, V52_CTRL_MSGT_PP_SWITCH_OVER_COM));
} else {
/* change state to SOLE0 */
osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0);
/* send MDU-Prot. (switch-over error ind) */
mdu_rcv(fi, MDU_Protection_switch_over_error_ind, NULL);
}
break;
case V52_PPFSM_E_TSO2:
if (pp->timeout_count < 2) {
/* restart TSO2 */
start_timer(fi, V52_PPFSM_E_TSO2, 2, info);
/* send OS SWITCH-OVER COM */
dl_send(fi, v52_enc_switch_over_com(pp, pp->interface->protection.cc_id, info->link_id, info->ts, V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM));
} else {
/* change state to SOLE0 */
osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0);
/* send MDU-Prot. (switch-over error ind) */
mdu_rcv(fi, MDU_Protection_switch_over_error_ind, NULL);
}
break;
case V52_PPFSM_E_VP_S_VP_R_misalignment_detected:
case V52_PPFSM_E_MDU_Protection_reset_SN_req:
/* stop timer */
stop_timer(fi);
/* change state to SOLE0, stop timer */
osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 4);
/* start TSO4 */
start_timer(fi, V52_PPFSM_E_TSO4, 1, NULL);
/* send RESET SN COM */
dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_COM));
/* set VP(S) = VP(R) = 0 */
pp->vp_s = pp->vp_r = 0;
/* send MDU-Prot. (reset SN com) */
mdu_rcv(fi, MDU_Protection_reset_SN_com, NULL);
break;
case V52_PPFSM_E_RESET_SN_COM:
/* first timeout ? */
if (pp->timeout_count < 2) {
/* stop timer */
stop_timer(fi);
/* change state to SOLE0 */
osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0);
/* start TSO5 */
start_timer(fi, V52_PPFSM_E_TSO5, 1, NULL);
/* set VP(S) = VP(R) = 0 */
pp->vp_s = pp->vp_r = 0;
/* send RESET SN ACK */
dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_ACK));
/* send MDU-Prot. (reset SN ind) */
mdu_rcv(fi, MDU_Protection_reset_SN_ind, NULL);
} else {
/* ignore */
}
break;
case V52_PPFSM_E_RESET_SN_ACK:
/* ignore */
break;
case V52_PPFSM_E_TSO5:
/* ignore */
break;
case V52_PPFSM_E_PROTOCOL_ERROR:
/* send MDU-Prot. (Protocol error ind) */
mdu_rcv(fi, MDU_Protection_protocol_error_ind, data);
break;
default:
OSMO_ASSERT(0);
}
}
static void pp_sole2_switch_over_req_by_an(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct v52_pp_proto *pp = fi->priv;
struct v52_pp_mgmt_info *info = data;
switch (event) {
case V52_PPFSM_E_MDU_Protection_switch_over_com:
/* change state to SOLE1 */
osmo_fsm_inst_state_chg_ms(fi, V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE, 0, 0);
/* start TSO1 */
start_timer(fi, V52_PPFSM_E_TSO1, 1, info);
/* send SWITCH-OVER COM */
dl_send(fi, v52_enc_switch_over_com(pp, pp->interface->protection.cc_id, info->link_id, info->ts, V52_CTRL_MSGT_PP_SWITCH_OVER_COM));
break;
case V52_PPFSM_E_MDU_Protection_OS_switch_over_com:
/* change state to SOLE1 */
osmo_fsm_inst_state_chg_ms(fi, V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE, 0, 0);
/* start TSO2 */
start_timer(fi, V52_PPFSM_E_TSO2, 1, info);
/* send OS SWITCH-OVER COM */
dl_send(fi, v52_enc_switch_over_com(pp, pp->interface->protection.cc_id, info->link_id, info->ts, V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM));
break;
case V52_PPFSM_E_MDU_Protection_switch_over_reject:
/* change state to SOLE0 */
osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0);
/* send OS SWITCH-OVER REJ */
dl_send(fi, v52_enc_switch_over_rej(pp, pp->interface->protection.cc_id, info->link_id, info->ts, info->cause));
break;
case V52_PPFSM_E_VP_S_VP_R_misalignment_detected:
case V52_PPFSM_E_MDU_Protection_reset_SN_req:
/* change state to SOLE0 */
osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, TIMEOUT_TSO4, 0);
/* start TSO4 */
start_timer(fi, V52_PPFSM_E_TSO4, 1, NULL);
/* send RESET SN COM */
dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_COM));
/* set VP(S) = VP(R) = 0 */
pp->vp_s = pp->vp_r = 0;
/* send MDU-Prot. (reset SN com) */
mdu_rcv(fi, MDU_Protection_reset_SN_com, NULL);
break;
case V52_PPFSM_E_RESET_SN_COM:
/* first timeout ? */
if (pp->timeout_count < 2) {
/* change state to SOLE0, start TSO5 */
osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0);
/* start TSO5 */
start_timer(fi, V52_PPFSM_E_TSO5, 1, NULL);
/* set VP(S) = VP(R) = 0 */
pp->vp_s = pp->vp_r = 0;
/* send RESET SN ACK */
dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_ACK));
/* send MDU-Prot. (reset SN ind) */
mdu_rcv(fi, MDU_Protection_reset_SN_ind, NULL);
} else {
/* ignore */
}
break;
case V52_PPFSM_E_RESET_SN_ACK:
/* ignore */
break;
case V52_PPFSM_E_TSO5:
/* ignore */
break;
case V52_PPFSM_E_PROTOCOL_ERROR:
/* send MDU-Prot. (Protocol error ind) */
mdu_rcv(fi, MDU_Protection_protocol_error_ind, data);
break;
default:
OSMO_ASSERT(0);
}
}
/* Table 66/G.965 LE protection protocol FSM */
static const struct osmo_fsm_state v52_le_pp_fsm_states[] = {
[V52_PPFSM_S_SOLE0_NULL] = {
.name = "NULL (SOLE0)",
.in_event_mask = S(V52_PPFSM_E_MDU_Protection_switch_over_com) |
S(V52_PPFSM_E_MDU_Protection_OS_switch_over_com) |
S(V52_PPFSM_E_SWITCH_OVER_ACK) |
S(V52_PPFSM_E_SWITCH_OVER_REQ) |
S(V52_PPFSM_E_SWITCH_OVER_REJECT) |
S(V52_PPFSM_E_VP_S_VP_R_misalignment_detected) |
S(V52_PPFSM_E_MDU_Protection_reset_SN_req) |
S(V52_PPFSM_E_RESET_SN_COM) |
S(V52_PPFSM_E_RESET_SN_ACK) |
S(V52_PPFSM_E_TSO4) |
S(V52_PPFSM_E_TSO5) |
S(V52_PPFSM_E_PROTOCOL_ERROR),
.out_state_mask = S(V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE) |
S(V52_PPFSM_S_SOLE2_SWITCH_OVER_REQ_BY_AN),
.action = pp_sole0_null,
},
[V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE] = {
.name = "Switch over init by LE (SOLE1)",
.in_event_mask = S(V52_PPFSM_E_SWITCH_OVER_ACK) |
S(V52_PPFSM_E_SWITCH_OVER_REQ) |
S(V52_PPFSM_E_SWITCH_OVER_REJECT) |
S(V52_PPFSM_E_TSO1) |
S(V52_PPFSM_E_TSO2) |
S(V52_PPFSM_E_VP_S_VP_R_misalignment_detected) |
S(V52_PPFSM_E_MDU_Protection_reset_SN_req) |
S(V52_PPFSM_E_RESET_SN_COM) |
S(V52_PPFSM_E_RESET_SN_ACK) |
S(V52_PPFSM_E_TSO5) |
S(V52_PPFSM_E_PROTOCOL_ERROR),
.out_state_mask = S(V52_PPFSM_S_SOLE0_NULL),
.action = pp_sole1_switch_over_init_by_le,
},
[V52_PPFSM_S_SOLE2_SWITCH_OVER_REQ_BY_AN] = {
.name = "Switch over req by AN (SOLE2)",
.in_event_mask = S(V52_PPFSM_E_MDU_Protection_switch_over_com) |
S(V52_PPFSM_E_MDU_Protection_OS_switch_over_com) |
S(V52_PPFSM_E_MDU_Protection_switch_over_reject) |
S(V52_PPFSM_E_VP_S_VP_R_misalignment_detected) |
S(V52_PPFSM_E_MDU_Protection_reset_SN_req) |
S(V52_PPFSM_E_RESET_SN_COM) |
S(V52_PPFSM_E_RESET_SN_ACK) |
S(V52_PPFSM_E_TSO5) |
S(V52_PPFSM_E_PROTOCOL_ERROR),
.out_state_mask = S(V52_PPFSM_S_SOLE0_NULL) |
S(V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE),
.action = pp_sole2_switch_over_req_by_an,
},
};
struct osmo_fsm v52_le_pp_fsm = {
.name = "V52_LE_PP",
.states = v52_le_pp_fsm_states,
.num_states = ARRAY_SIZE(v52_le_pp_fsm_states),
.allstate_event_mask = 0,
.allstate_action = NULL,
.cleanup = NULL,
.timer_cb = v52_le_pp_fsm_timer_cb,
.log_subsys = DV5PP,
.event_names = v52_le_pp_fsm_event_names,
};
struct v52_pp_proto *v52_le_pp_create(struct v5x_interface *v5if)
{
struct v52_pp_proto *pp;
OSMO_ASSERT(v5if);
pp = talloc_zero(v5if, struct v52_pp_proto);
if (!pp)
return NULL;
pp->interface = v5if;
pp->fi = osmo_fsm_inst_alloc(&v52_le_pp_fsm, pp, pp, LOGL_DEBUG, NULL);
if (!pp->fi)
goto error;
return pp;
error:
v52_le_pp_destroy(pp);
return NULL;
}
void v52_le_pp_destroy(struct v52_pp_proto *pp)
{
OSMO_ASSERT(pp);
if (pp->fi) {
osmo_fsm_inst_free(pp->fi);
}
talloc_free(pp);
}
void v52_le_pp_init(void)
{
int rc;
rc = osmo_fsm_register(&v52_le_pp_fsm);
OSMO_ASSERT(!rc);
LOGP(DV5PP, LOGL_NOTICE, "Using V52 Protection protocol\n");
}
/***********************************************************************
* V5 Message receiving / decoding
***********************************************************************/
/* receive message from lower (DL) layer */
int v52_le_pp_dl_rcv(struct v5x_link *v5l, uint16_t cc_id, uint8_t msg_type, const struct tlv_parsed *tp)
{
struct v52_pp_proto *pp = v5l->interface->protection.pp;
enum v52_le_pp_fsm_event event;
int check_sn = 0;
if (!pp) {
LOGP(DV5PP, LOGL_NOTICE, "Received message from DL, but there is no protection on this interface.\n");
return -EIO;
}
switch (msg_type) {
case V52_CTRL_MSGT_PP_SWITCH_OVER_REQ:
event = V52_PPFSM_E_SWITCH_OVER_REQ;
check_sn = 1;
break;
case V52_CTRL_MSGT_PP_SWITCH_OVER_ACK:
event = V52_PPFSM_E_SWITCH_OVER_ACK;
check_sn = 1;
break;
case V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT:
event = V52_PPFSM_E_SWITCH_OVER_REJECT;
check_sn = 1;
break;
case V52_CTRL_MSGT_PP_PROTOCOL_ERROR:
event = V52_PPFSM_E_PROTOCOL_ERROR;
check_sn = 1;
break;
case V52_CTRL_MSGT_PP_RESET_SN_COM:
event = V52_PPFSM_E_RESET_SN_COM;
break;
case V52_CTRL_MSGT_PP_RESET_SN_ACK:
event = V52_PPFSM_E_RESET_SN_ACK;
break;
default:
LOGP(DV5PP, LOGL_NOTICE, "Invalid Protection protocol message %d receied from AN.\n", msg_type);
return -EINVAL;
}
if (cc_id != pp->interface->protection.cc_id) {
LOGP(DV5PP, LOGL_NOTICE, "Protection protocol of AN uses CC-ID %d, but we use CC-ID %d. Please check provisioning.\n", cc_id, pp->interface->protection.cc_id);
dl_send(pp->fi, v52_enc_protocol_error(pp, pp->interface->protection.cc_id, V5X_CAUSE_T_REF_NR_CODING_ERROR, NULL, 0));
return -EINVAL;
}
/* check sequence number (see 18.6.2.2 G.965) */
/* NOTE: If TSO4 is running, there is an ongoing sequence reset, so no check is performed and all received
* message with sequence number are ignored by state machine. */
if (check_sn && !timer_pending(pp->fi, V52_PPFSM_E_TSO4)) {
uint8_t seq_nr = *TLVP_VAL(tp, V52_CTRL_IEI_PP_SEQUENCE_NR) & 0x7f, diff;
/* if we have our first message, we take the serial number */
if (!pp->vp_r_set) {
pp->vp_r = seq_nr;
pp->vp_r_set = true;
}
/* how much is the SN ahead of what we expect */
diff = (seq_nr - pp->vp_r) & 0x7f;
/* if VP(R)-5 <= SN <= VP(R)-1 (SN is 1..5 values lower than we want) */
if (diff >= 128 - 5) {
LOGP(DV5PP, LOGL_DEBUG, "Receiveds message with sequence %d, while we expect %d. Ignore, because it was already received earlier.\n", seq_nr, pp->vp_r);
return 0;
}
/* if VP(R) <= SN <= VP(R) + 4 (SN is 0..4 higher than we want) */
if (diff <= 4) {
/* set received sequence number to the next one expected and deliver message */
pp->vp_r = (seq_nr + 1) & 0x7f;
} else {
LOGP(DV5PP, LOGL_NOTICE, "Receiveds message with sequence %d, while we expect %d. This a a misalignment, perform an SN reset!\n", seq_nr, pp->vp_r);
event = V52_PPFSM_E_VP_S_VP_R_misalignment_detected;
}
}
osmo_fsm_inst_dispatch(pp->fi, event, (void *)tp);
return 0;
}
int v52_le_pp_mdu_snd(struct v5x_interface *v5if, enum v5x_mgmt_prim prim, uint8_t link_id, uint8_t ts, uint8_t cause)
{
enum v52_le_pp_fsm_event event;
struct v52_pp_mgmt_info info = { link_id, ts, cause };
if (!v5if->protection.pp) {
LOGP(DV5PP, LOGL_NOTICE, "Cannot perform action, because there is no protection on this interface.\n");
return -EINVAL;
}
OSMO_ASSERT(v5if->protection.pp);
switch (prim) {
case MDU_Protection_switch_over_com:
event = V52_PPFSM_E_MDU_Protection_switch_over_com;
break;
case MDU_Protection_OS_switch_over_com:
event = V52_PPFSM_E_MDU_Protection_OS_switch_over_com;
break;
case MDU_Protection_switch_over_rej:
event = V52_PPFSM_E_MDU_Protection_switch_over_reject;
break;
case MDU_Protection_reset_SN_req:
event = V52_PPFSM_E_MDU_Protection_reset_SN_req;
break;
default:
LOGP(DV5PP, LOGL_NOTICE, "Invalid MDU primitive %d receied from management.\n", prim);
return -EINVAL;
}
osmo_fsm_inst_dispatch(v5if->protection.pp->fi, event, &info);
return 0;
}

6
src/v52_le_pp_fsm.h Normal file
View File

@ -0,0 +1,6 @@
struct v52_pp_proto *v52_le_pp_create(struct v5x_interface *v5if);
void v52_le_pp_destroy(struct v52_pp_proto *pp);
void v52_le_pp_init(void);
int v52_le_pp_dl_rcv(struct v5x_link *v5l, uint16_t cc_id, uint8_t msg_type, const struct tlv_parsed *tp);
int v52_le_pp_mdu_snd(struct v5x_interface *v5if, enum v5x_mgmt_prim prim, uint8_t link_id, uint8_t ts, uint8_t cause);

1212
src/v5le_vty.c Normal file

File diff suppressed because it is too large Load Diff

4
src/v5le_vty.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
extern struct vty_app_info vty_info;
int v5le_vty_init(void);

546
src/v5x_data.c Normal file
View File

@ -0,0 +1,546 @@
#include <errno.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/tlv.h>
#include "v5x_internal.h"
#include "v5x_protocol.h"
#include "lapv5.h"
#include "layer1.h"
#include "v5x_l1_fsm.h"
#include "v5x_le_ctrl_fsm.h"
#include "v5x_le_port_fsm.h"
#include "v5x_le_pstn_fsm.h"
#include "v52_le_lcp_fsm.h"
#include "v52_le_bcc_fsm.h"
#include "v52_le_pp_fsm.h"
#include "v5x_le_management.h"
#include "logging.h"
static LLIST_HEAD(v5_instances);
struct v5x_instance *v5x_instance_alloc(void *ctx)
{
struct v5x_instance *v5i = talloc_zero(ctx, struct v5x_instance);
if (!v5i)
return NULL;
INIT_LLIST_HEAD(&v5i->interfaces);
llist_add_tail(&v5i->list, &v5_instances);
return v5i;
}
void v5x_instance_free(struct v5x_instance *v5i)
{
struct v5x_interface *v5if, *v5if2;
llist_for_each_entry_safe(v5if, v5if2, &v5i->interfaces, list)
v5x_interface_free(v5if);
talloc_free(v5i);
}
static void v5x_ts_init(struct v5x_timeslot *v5ts, uint8_t nr, struct v5x_link *v5l, bool b_channel)
{
/* Never use slot 16 as B-channel, because some drivers (mISDN) will not allow this. */
if (nr == 16)
b_channel = false;
v5ts->nr = nr;
v5ts->link = v5l;
v5ts->b_channel = b_channel;
}
static void v5x_cchan_init(struct v5x_c_channel *v5cc, struct v5x_link *v5l, struct v5x_timeslot *v5ts, bool active)
{
v5cc->link = v5l;
v5cc->ts = v5ts;
v5cc->active = active;
}
struct v5x_link *v5x_link_create(struct v5x_interface *v5if, uint8_t id)
{
int count;
struct v5x_link *v5l;
unsigned int i;
int rc;
if (v5x_link_find_id(v5if, id)) {
LOGP(DV5, LOGL_ERROR, "Link %d already exists.\n", id);
return NULL;
}
count = v5x_link_count(v5if);
v5l = talloc_zero(v5if, struct v5x_link);
if (!v5l)
return NULL;
llist_add_tail(&v5l->list, &v5if->links);
count++;
v5l->id = id;
v5l->interface = v5if;
/* primary and secondary link will get TS 16 */
if (count <= 2) {
v5x_cchan_init(&v5l->c_channel[0], v5l, &v5l->ts[16], true);
}
for (i = 1; i < ARRAY_SIZE(v5l->ts); i++) {
bool b_channel = true;
if (v5l->c_channel[0].ts && v5l->c_channel[0].ts->nr == i)
b_channel = false;
if (v5l->c_channel[1].ts && v5l->c_channel[1].ts->nr == i)
b_channel = false;
if (v5l->c_channel[2].ts && v5l->c_channel[2].ts->nr == i)
b_channel = false;
v5x_ts_init(&v5l->ts[i], i, v5l, b_channel);
}
v5l->l1 = v5x_l1_fsm_create(v5if, v5l, id);
if (!v5l->l1)
goto error;
if (v5if->dialect == V5X_DIALECT_V52) {
v5l->fi = v52_le_lcp_create(v5if, v5l, id);
if (!v5l->fi)
goto error;
v5l->ctrl = v5x_le_ctrl_create(V5X_CTRL_TYPE_LINK, v5if, v5l, id);
if (!v5l->ctrl)
goto error;
}
if (count == 1) {
v5if->primary_link = v5l;
v5if->cc_link = v5l;
}
if (count == 2) {
v5if->secondary_link = v5l;
/* add PP */
v5if->protection.pp = v52_le_pp_create(v5if);
if (!v5if->protection.pp)
goto error;
v5if->protection.li[0] = lapv5_instance_alloc(1, &ph_data_req_dl_prot, v5if->primary_link, v5x_dl_rcv,
v5if->primary_link, &lapd_profile_lapv5dl, "protection0");
if (!v5if->protection.li[0])
goto error;
v5if->protection.li[1] = lapv5_instance_alloc(1, &ph_data_req_dl_prot, v5if->secondary_link, v5x_dl_rcv,
v5if->secondary_link, &lapd_profile_lapv5dl, "protection1");
if (!v5if->protection.li[1])
goto error;
}
return v5l;
error:
rc = v5x_link_destroy(v5l);
OSMO_ASSERT(rc == 0);
return NULL;
}
int v5x_link_destroy(struct v5x_link *v5l)
{
struct v5x_interface *v5if = v5l->interface;
int count;
struct v5x_user_port *v5up;
int i;
count = v5x_link_count(v5l->interface);
if (count > 1 && v5l->interface->primary_link == v5l) {
LOGP(DV5, LOGL_ERROR, "Link %d is primary. Cannot remove it, while other links exist.\n", v5l->id);
return -EINVAL;
}
if (count > 2 && v5l->interface->secondary_link == v5l) {
LOGP(DV5, LOGL_ERROR, "Link %d is secondary (standby). Cannot remove it, while other (not primary) links exist.\n", v5l->id);
return -EINVAL;
}
/* detach user ports */
for (i = 1; i < (int)ARRAY_SIZE(v5l->ts); i++) {
if ((v5up = v5l->ts[i].v5up)) {
if (v5up->ts[0] == &v5l->ts[i]) {
v5up->ts[0] = NULL;
ph_deactivate_req(&v5l->ts[i]);
}
if (v5up->ts[1] == &v5l->ts[i]) {
v5up->ts[1] = NULL;
ph_deactivate_req(&v5l->ts[i]);
}
}
}
if (v5l->ctrl)
v5x_le_ctrl_destroy(v5l->ctrl);
if (v5l->fi)
v52_le_lcp_destroy(v5l->fi);
if (v5l->l1)
v5x_l1_fsm_destroy(v5l->l1);
if (v5l->interface->primary_link == v5l)
v5l->interface->primary_link = NULL;
if (v5l->interface->secondary_link == v5l)
v5l->interface->secondary_link = NULL;
if (v5l->interface->cc_link == v5l)
v5l->interface->cc_link = NULL;
if (v5l->e1_line)
e1_line_exit(v5l);
llist_del(&v5l->list);
count--;
talloc_free(v5l);
/* if we have only one link left */
if (count == 1) {
/* remove PP */
if (v5if->protection.li[0]) {
lapv5_instance_free(v5if->protection.li[0]);
v5if->protection.li[0] = NULL;
}
if (v5if->protection.li[1]) {
lapv5_instance_free(v5if->protection.li[1]);
v5if->protection.li[1] = NULL;
}
if (v5if->protection.pp) {
v52_le_pp_destroy(v5if->protection.pp);
v5if->protection.pp = NULL;
}
}
return 0;
}
struct v5x_interface *v5x_interface_alloc(struct v5x_instance *v5i, enum v5x_dialect dialect)
{
struct v5x_interface *v5if;
struct v5x_link *v5l;
// struct v5x_c_channel *primary_c_chan;
v5if = talloc_zero(v5i, struct v5x_interface);
if (!v5if)
return NULL;
INIT_LLIST_HEAD(&v5if->links);
INIT_LLIST_HEAD(&v5if->user_ports);
INIT_LLIST_HEAD(&v5if->bcc_procs);
llist_add_tail(&v5if->list, &v5i->interfaces);
v5if->instance = v5i;
v5if->dialect = dialect;
if (v5if->dialect == V5X_DIALECT_V51) {
v5l = v5x_link_create(v5if, 0);
if (!v5l)
goto error;
}
v5if->mgmt = v5x_le_mgmt_create(v5if);
if (!v5if->mgmt)
goto error;
v5if->control.ctrl = v5x_le_ctrl_create(V5X_CTRL_TYPE_COMMON, v5if, v5if, 0);
if (!v5if->control.ctrl)
goto error;
v5if->control.li = lapv5_instance_alloc(1, &ph_data_req_dl_cc, v5if, v5x_dl_rcv, v5if,
&lapd_profile_lapv5dl, "control");
if (!v5if->control.li)
goto error;
v5if->pstn.li = lapv5_instance_alloc(1, &ph_data_req_dl_cc, v5if, v5x_dl_rcv, v5if,
&lapd_profile_lapv5dl, "pstn");
if (!v5if->pstn.li)
goto error;
if (v5if->dialect == V5X_DIALECT_V52) {
v5if->lcp.li = lapv5_instance_alloc(1, &ph_data_req_dl_cc, v5if, v5x_dl_rcv, v5if,
&lapd_profile_lapv5dl, "lcp");
if (!v5if->lcp.li)
goto error;
v5if->bcc.li = lapv5_instance_alloc(1, &ph_data_req_dl_cc, v5if, v5x_dl_rcv, v5if,
&lapd_profile_lapv5dl, "bcc");
if (!v5if->bcc.li)
goto error;
}
return v5if;
error:
v5x_interface_free(v5if);
return NULL;
}
void v5x_interface_free(struct v5x_interface *v5if) {
struct v5x_user_port *v5up, *v5up2;
struct v52_bcc_proc *bcc, *bcc2;
struct v5x_link *v5l;
int rc;
if (!v5if)
return;
llist_for_each_entry_safe(v5up, v5up2, &v5if->user_ports, list)
v5x_user_port_destroy(v5up);
llist_for_each_entry_safe(bcc, bcc2, &v5if->bcc_procs, list)
v52_le_bcc_destroy(bcc);
/* delete reversed safely */
while (!llist_empty(&v5if->links)) {
v5l = llist_last_entry(&v5if->links, struct v5x_link, list);
rc = v5x_link_destroy(v5l);
OSMO_ASSERT(rc == 0);
}
if (v5if->mgmt)
v5x_le_mgmt_destroy(v5if->mgmt);
if (v5if->control.ctrl)
v5x_le_ctrl_destroy(v5if->control.ctrl);
if (v5if->control.li)
lapv5_instance_free(v5if->control.li);
if (v5if->pstn.li)
lapv5_instance_free(v5if->pstn.li);
if (v5if->lcp.li)
lapv5_instance_free(v5if->lcp.li);
if (v5if->bcc.li)
lapv5_instance_free(v5if->bcc.li);
llist_del(&v5if->list);
talloc_free(v5if);
}
struct v5x_user_port *v5x_user_port_create(struct v5x_interface *v5if, uint16_t nr, enum v5x_user_type type,
uint8_t ts1, uint8_t ts2)
{
struct v5x_user_port *v5up;
int i;
int rc;
if (v5x_user_port_find(v5if, nr, (type == V5X_USER_TYPE_ISDN))) {
LOGP(DV5, LOGL_ERROR, "User port %d already exists.\n", nr);
return NULL;
}
if (v5if->dialect == V5X_DIALECT_V51) {
struct v5x_link *v5l;
v5l = llist_first_entry(&v5if->links, struct v5x_link, list);
if (!v5l->ts[ts1].b_channel) {
LOGP(DV5, LOGL_ERROR, "Time slot %d is not a B-channel, select a different one.\n", ts1);
return NULL;
}
if ((v5up = v5l->ts[ts1].v5up)) {
LOGP(DV5, LOGL_ERROR, "Time slot %d is already assigned to another user port.\n", ts1);
return NULL;
}
if (type == V5X_USER_TYPE_ISDN) {
if (!v5l->ts[ts2].b_channel) {
LOGP(DV5, LOGL_ERROR, "Time slot %d is not a B-channel, select a different one.\n",
ts2);
return NULL;
}
if ((v5up = v5l->ts[ts2].v5up)) {
LOGP(DV5, LOGL_ERROR, "Time slot %d is already assigned to another user port.\n", ts2);
return NULL;
}
}
}
v5up = talloc_zero(v5if, struct v5x_user_port);
if (!v5up)
return NULL;
llist_add_tail(&v5up->list, &v5if->user_ports);
v5up->interface = v5if;
v5up->nr = nr;
v5up->type = type;
/* assign ports */
if (v5if->dialect == V5X_DIALECT_V51) {
struct v5x_link *v5l;
v5l = llist_first_entry(&v5if->links, struct v5x_link, list);
OSMO_ASSERT(ts1 >= 1 && ts1 <= 31);
v5l->ts[ts1].v5up = v5up;
v5up->ts[0] = &v5l->ts[ts1];
if (type == V5X_USER_TYPE_ISDN) {
OSMO_ASSERT(ts2 >= 1 && ts2 <= 31);
v5l->ts[ts2].v5up = v5up;
v5up->ts[1] = &v5l->ts[ts2];
}
}
v5up->ctrl = v5x_le_ctrl_create(V5X_CTRL_TYPE_PORT, v5up, v5up, nr);
if (!v5up->ctrl) {
LOGP(DV5, LOGL_ERROR, "Failed to create control protocol\n");
goto error;
}
switch (type) {
case V5X_USER_TYPE_ISDN:
LOGP(DV5, LOGL_NOTICE, "Creating V5 ISDN user port with L3 addr %d\n", nr);
sprintf(v5up->ifname, "isdn-%d", nr);
OSMO_ASSERT(nr <= 8175);
v5up->port_fi = v5x_le_port_isdn_create(v5up, nr);
if (!v5up->port_fi) {
LOGP(DV5, LOGL_ERROR, "Failed to create port control\n");
goto error;
}
break;
case V5X_USER_TYPE_PSTN:
LOGP(DV5, LOGL_NOTICE, "Creating V5 PSTN user port with L3 addr %d\n", nr);
sprintf(v5up->ifname, "pstn-%d", nr);
OSMO_ASSERT(nr <= 32767);
v5up->port_fi = v5x_le_port_pstn_create(v5up, nr);
if (!v5up->port_fi) {
LOGP(DV5, LOGL_ERROR, "Failed to create port control\n");
goto error;
}
v5up->pstn.proto = v5x_le_pstn_create(v5up, nr);
if (!v5up->pstn.proto) {
LOGP(DV5, LOGL_ERROR, "Failed to create PSTN protocol\n");
goto error;
}
/* bring port into service */
v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_blocked);
break;
}
for (i = 0; i < 2; i++) {
v5up->ep[i].port = v5up;
/* EC needs to adapt, NLP causes better results on quick volume changes */
v5up->ep[i].ec = echo_can_create(EC_TAPS, ECHO_CAN_USE_ADAPTION | ECHO_CAN_USE_NLP);
if (!v5up->ep[i].ec) {
LOGP(DV5, LOGL_ERROR, "Failed to create line echo canceler\n");
goto error;
}
v5up->ep[i].es = echo_sup_create(v5up, 8000);
if (!v5up->ep[i].es) {
LOGP(DV5, LOGL_ERROR, "Failed to create line echo suppressor\n");
goto error;
}
rc = answertone_init(&v5up->ep[i].at, 8000);
if (rc < 0) {
LOGP(DV5, LOGL_ERROR, "Failed to create answer tone detector\n");
goto error;
}
if (type != V5X_USER_TYPE_ISDN)
break;
}
rc = ph_socket_init(&v5up->ph_socket, ph_socket_rx_cb, v5up, v5up->ifname, 1);
if (rc < 0) {
LOGP(DV5, LOGL_ERROR, "Failed to create PH-socket\n");
goto error;
}
/* start ctrl FSM for this port */
if (v5if->control.established)
v5x_le_ctrl_start(v5up->ctrl);
return v5up;
error:
v5x_user_port_destroy(v5up);
return NULL;
}
void v5x_user_port_destroy(struct v5x_user_port *v5up)
{
int i;
LOGP(DV5, LOGL_NOTICE, "Destroying V5 user port with L3 addr %d\n", v5up->nr);
/* close first, because it sends messages */
ph_socket_exit(&v5up->ph_socket);
for (i = 0; i < 2; i++) {
/* destory line echo canceler */
if (v5up->ep[i].ec) {
echo_can_free(v5up->ep[i].ec);
v5up->ep[i].ec = NULL;
}
/* destory line echo suppressor */
if (v5up->ep[i].es) {
echo_sup_free(v5up->ep[i].es);
v5up->ep[i].es = NULL;
}
answertone_exit(&v5up->ep[i].at);
/* unassign ports */
if (v5up->ts[i]) {
v5up->ts[i]->v5up = NULL;
v5up->ts[i] = NULL;
}
}
if (v5up->ctrl)
v5x_le_ctrl_destroy(v5up->ctrl);
if (v5up->port_fi)
osmo_fsm_inst_free(v5up->port_fi);
if (v5up->pstn.proto)
v5x_le_pstn_destroy(v5up->pstn.proto);
llist_del(&v5up->list);
talloc_free(v5up);
}
void v5x_echo_reset(struct v5x_echo_proc *ep, int enable)
{
ep->enabled = enable;
answertone_reset(&ep->at);
ep->tx_buffer_in = 0;
ep->tx_buffer_out = 0;
echo_can_flush(ep->ec);
echo_sup_flush(ep->es);
}
struct v5x_user_port *v5x_user_port_find(struct v5x_interface *v5if, uint16_t nr, bool is_isdn)
{
struct v5x_user_port *v5up;
llist_for_each_entry(v5up, &v5if->user_ports, list) {
if (v5up->nr == nr && is_isdn == (v5up->type == V5X_USER_TYPE_ISDN))
return v5up;
}
return NULL;
}
int v5x_link_count(struct v5x_interface *v5if)
{
struct v5x_link *v5l;
int count = 0;
llist_for_each_entry(v5l, &v5if->links, list)
count++;
return count;
}
struct v5x_link *v5x_link_find_id(struct v5x_interface *v5if, uint8_t id)
{
struct v5x_link *v5l;
llist_for_each_entry(v5l, &v5if->links, list) {
if (v5l->id == id)
return v5l;
}
return NULL;
}

397
src/v5x_internal.h Normal file
View File

@ -0,0 +1,397 @@
#pragma once
/* (C) 2021 by Harald Welte <laforge@gnumonks.org>
* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
*
* 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.
*
* 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/utils.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/select.h>
#include <osmocom/gsm/lapd_core.h>
#include <osmocom/abis/e1_input.h>
#include "libecho/echo_cancel.h"
#include "libecho/echo_suppress.h"
#include "libecho/answertone.h"
#include "ph_socket.h"
/* Table 35/G.964 */
enum v5x_mph_prim {
MPH_UBR, /* Unblock (req) */
MPH_UBI, /* Unblock (ind) */
MPH_BR, /* Block (req) */
MPH_BI, /* Block (ind) */
MPH_AR, /* Activate (req) */
MPH_AI, /* Activate (ind) */
MPH_AWI, /* Access activation by user (ind) */
MPH_DSAI, /* DS Activated (ind) */
MPH_DR, /* Deactivate (req) */
MPH_DI, /* Deactivate (ind) */
MPH_GI, /* Grading Information */
MPH_DB, /* Block D-Channel from user port */
MPH_DU, /* Unblock D-Channel from user port */
MPH_ID, /* Identity Sa7=0 signal request (TX) */
MPH_NOR, /* Normal Sa7=1 signal request (TX) */
MPH_IDR, /* Request Sa7 signal (RX) */
MPH_IDI, /* Indicate Sa7=0 signal (RX) */
MPH_EIg, /* Indicate Sa7=1 singal (RX) */
MPH_EIa, /* Indicate LOS */
MPH_EIb, /* RAI */
MPH_EIc, /* AIS */
MPH_EId, /* Internal failure */
MPH_EIe, /* CRC received */
MPH_EIf, /* E-bit received */
MPH_stop, /* See L1 FSM */
MPH_proceed, /* See L1 FSM */
MPH_EIbr, /* See L1 FSM */
MPH_EIdr, /* See L1 FSM */
};
/* Table 3/G.964 */
enum v5x_fe_prim {
FE_establish_req = 0x00,
FE_establish_ack_ind = 0x03,
FE_establish_ind = 0x01,
FE_establish_ack_req = 0x02,
FE_line_signal_req = 0x10,
FE_line_signal_ind = 0x11,
FE_protocol_param_req = 0x20,
FE_disconnect_req = 0x30,
FE_disconnect_compl_req = 0x32,
FE_disconnect_compl_ind = 0x33,
};
enum v5x_mgmt_prim {
/* Table 3/G.964 */
MDU_CTRL_port_blocked,
MDU_CTRL_port_unblocked,
MDU_CTRL_port_restart_req,
MDU_CTRL_port_restart_ack,
MDU_CTRL_port_restart_compl,
MDU_error_ind,
/* Table 14/G.965 */
MDU_AI,
MDU_DI,
MDU_LAI,
MDU_IDReq,
MDU_IDAck,
MDU_IDRel,
MDU_IDRej,
MDU_EIg,
MDU_LUBR,
MDU_LUBI,
MDU_LBI,
MDU_LBR,
MDU_LBRN,
/* Table 26/G.965 */
MDU_BCC_allocation_req,
MDU_BCC_allocation_conf,
MDU_BCC_allocation_reject_ind,
MDU_BCC_allocation_error_ind,
MDU_BCC_deallocation_req,
MDU_BCC_deallocation_conf,
MDU_BCC_deallocation_reject_ind,
MDU_BCC_deallocation_error_ind,
MDU_BCC_audit_req,
MDU_BCC_audit_conf,
MDU_BCC_audit_error_ind,
MDU_BCC_AN_fault_ind,
MDU_BCC_protocol_error_ind,
/* Table 50/G.965 */
MDU_Protection_switch_over_com,
MDU_Protection_OS_switch_over_com,
MDU_Protection_switch_over_ack,
MDU_Protection_switch_over_rej,
MDU_Protection_switch_over_req,
MDU_Protection_switch_over_reject_ind,
MDU_Protection_switch_over_error_ind,
MDU_Protection_reset_SN_ind,
MDU_Protection_reset_SN_com,
MDU_Protection_reset_SN_req,
MDU_Protection_reset_SN_ack,
MDU_Protection_reset_SN_error_ind,
MDU_Protection_protocol_error_ind,
/* Figure 11/G.964 */
MDL_ESTABLISH_req,
MDL_ESTABLISH_cnf,
MDL_ESTABLISH_ind,
MDL_RELEASE_ind,
MDL_ERROR_ind,
MDL_LAYER_1_FAILURE_ind,
};
struct osmo_fsm_inst;
enum v5x_dialect {
V5X_DIALECT_V51 = 1,
V5X_DIALECT_V52 = 2,
};
enum v5x_user_type {
V5X_USER_TYPE_ISDN = 1,
V5X_USER_TYPE_PSTN = 2,
};
enum v5x_ctrl_type {
V5X_CTRL_TYPE_COMMON = 1,
V5X_CTRL_TYPE_PORT = 2,
V5X_CTRL_TYPE_LINK = 3,
};
/* forward-declarations */
struct v5x_interface;
struct v5x_instance;
struct v5x_user_port;
struct v5x_link;
struct v5x_l1_proto;
struct v52_pp_proto;
/* A C-channel is a 64k timeslot used for signalling */
struct v5x_c_channel {
struct v5x_link *link; /* back-pointer */
struct v5x_timeslot *ts; /* E1 link timeslot. NULL = C-channel doesn't exist */
bool active; /* false == standby */
};
/* one physical E1 timeslot used on an E1 interface part of a V5.2 interface */
struct v5x_timeslot {
uint8_t nr;
struct v5x_link *link; /* back-pointer */
struct v5x_user_port *v5up; /* user port that this TS is assigned to */
bool b_channel; /* channel can be used as b-channel */
bool b_activated; /* activated (b-channel) at libosmo-abis */
};
/* one physical E1 interface used within a V5.2 interface */
struct v5x_link {
struct llist_head list; /* interface.links */
uint8_t id;
struct v5x_interface *interface; /* back-pointer */
struct v5x_timeslot ts[32]; /* 32 E1 slots; 0 not available */
struct v5x_c_channel c_channel[3]; /* 64k signaling possible on TS16, TS15 and TS31 */
struct v5x_l1_proto *l1; /* Layer 1 Link FSM protocol */
struct v5x_ctrl_proto *ctrl; /* Link control protocol instance */
struct osmo_fsm_inst *fi; /* Link Control FSM instance */
struct e1inp_line *e1_line; /* E1 line to use or NULL if not */
struct e1inp_line_ops e1_line_ops; /* use e1pinp_line->ops to point back to v5x_link */
};
/* one V5.x interface between AN (Access Network) and LE (Local Exchange) */
struct v5x_interface {
struct llist_head list; /* instance.interfaces */
struct v5x_instance *instance; /* back-pointer */
enum v5x_dialect dialect;
uint32_t id_local, id_remote; /* interface id */
uint8_t variant_local, variant_remote; /* provitioning variant */
bool id_remote_valid;
bool variant_remote_valid;
bool use_capability; /* use bearer transfer capability */
uint8_t capability; /* bearer transfer capability */
struct v5x_mgmt_proto *mgmt; /* management prococol states */
struct {
struct v5x_ctrl_proto *ctrl; /* common control protocol instance */
struct lapv5_instance *li; /* Control data link */
bool established; /* track if link is up or down */
} control;
struct {
struct lapv5_instance *li; /* Link control data link */
bool established; /* track if link is up or down */
} lcp;
struct {
struct lapv5_instance *li; /* PSTN data link */
bool established; /* track if link is up or down */
} pstn;
struct {
struct lapv5_instance *li; /* BCC data link */
bool established; /* track if link is up or down */
} bcc;
struct {
uint16_t cc_id; /* CC-ID of protected C-channel */
struct v52_pp_proto *pp; /* protection protocol instance */
struct lapv5_instance *li[2]; /* Protection data link 1 + 2 */
bool established[2]; /* track if link is up or down */
} protection;
struct llist_head links; /* list of v5x_link */
struct v5x_link *primary_link; /* primary link */
struct v5x_link *secondary_link; /* standby link */
struct v5x_link *cc_link; /* currently used link for signaling (primary on startup) */
struct llist_head user_ports; /* list of v5x_user_port */
struct llist_head bcc_procs; /* list of v52_bcc_procs */
};
struct v5x_l1_proto {
struct v5x_link *v5l; /* back pointer */
struct osmo_fsm_inst *fi; /* L1 FSM */
int los, rai, ais, sa7; /* RX signal states */
};
struct v5x_ctrl_proto {
enum v5x_ctrl_type type; /* type of control protocol: common/port/link */
void *priv; /* back pointer to the user */
struct osmo_fsm_inst *fi; /* control FSM */
struct llist_head tx_queue; /* list of message to be transmitted */
struct msgb *tx_msg; /* copy of unacked message, for second try */
};
struct v5x_pstn_proto {
struct v5x_user_port *v5up; /* back pointer, if port control is used */
struct osmo_fsm_inst *fi; /* control FSM */
uint8_t S_s, S_a, S_r; /* sequence numbers */
struct msgb *tx_msg; /* copy of unacked message, for repitition */
int timeout_event; /* event when timer times out */
int timeout_count; /* how many times the timer was started/timed out */
struct osmo_timer_list timer_Tr; /* extra timer for receive sequence ack */
struct osmo_timer_list timer_Tt; /* extra timer for transmit sequence ack */
};
/* indexes of blocking/unblocking instances */
#define UNBLK_ALL_PSTN_ISDN 0
#define UNBLK_ALL_PSTN 1
#define UNBLK_ALL_ISDN 2
#define BLK_ALL_PSTN 3
#define BLK_ALL_ISDN 4
struct v5x_mgmt_proto {
struct v5x_interface *interface; /* back-pointer to instance we're part of */
bool auto_restart; /* restart automatically after TC8/TC9 timeout */
bool pstn_rs_pending; /* pending restart flag */
bool do_est; /* actively establish datalinks */
bool do_align; /* actively perform alignment */
bool acc_align; /* use accellerated alignment */
struct osmo_fsm_inst *system_fi; /* system startup FSM */
struct osmo_fsm_inst *pstn_dl_fi; /* PSTN DL startup FSM */
struct osmo_fsm_inst *pstn_rs_fi; /* PSTN restart FSM */
struct osmo_fsm_inst *unblk_all_fi[5]; /* unblock/block all FSM (5 instances) */
struct osmo_timer_list timer_system; /* delayed start, so sockets can connect first */
struct osmo_timer_list timer_tr1; /* see Table C.1/G.965 */
struct osmo_timer_list timer_tr2;
struct osmo_timer_list timer_tc1;
struct osmo_timer_list timer_tc2;
struct osmo_timer_list timer_tc3;
struct osmo_timer_list timer_tc7;
struct osmo_timer_list timer_tc8;
struct osmo_timer_list timer_tc9;
struct osmo_timer_list timer_tv1;
};
#define EC_BUFFER 1024
#define EC_TAPS 256
#define USE_ECHO_CANCELER 1
#define USE_ECHO_SUPPRESSOR 2
struct v5x_echo_proc {
struct v5x_user_port *port; /* back pointer to user port */
int enabled; /* line echo canceler enabled (configured and not disabled by ANS) */
echo_can_state_t *ec; /* echo canceler state */
echo_sup_state_t *es; /* echo suppressor state */
int16_t tx_buffer[EC_BUFFER]; /* buffer to store TX audio */
int tx_buffer_in; /* next pos to write to buffer */
int tx_buffer_out; /* next pos to read from buffer */
struct answer_tone at; /* answer tone detector process */
};
/* one user-facing port (subscriber line) */
struct v5x_user_port {
struct llist_head list; /* part of v5x_instance.ports */
struct v5x_interface *interface; /* back-pointer to instance we're part of */
uint16_t nr; /* port-number in decoded form (0..32767) */
char ifname[64]; /* name of interface, also used for PH-socket */
enum v5x_user_type type; /* type of port (ISDN/PSTN) */
struct v5x_ctrl_proto *ctrl; /* port control protocol instance */
struct osmo_fsm_inst *port_fi; /* port FSM */
struct v5x_timeslot *ts[2]; /* two time slots */
struct {
} isdn;
struct {
struct v5x_pstn_proto *proto;
} pstn;
ph_socket_t ph_socket; /* unix socket to connect port to */
bool le_unblocked; /* if port is not blocked by LE */
bool an_unblocked; /* if port is not blocked by AN */
int use_line_echo; /* line echo canceler/suppressor is configured */
struct v5x_echo_proc ep[2]; /* echo canceler process */
};
/* BCC process */
struct v52_bcc_proc {
struct llist_head list; /* part of v5x_instance.ports */
struct v5x_interface *interface; /* back-pointer to instance we're part of */
struct osmo_fsm_inst *fi; /* BCC FSM */
uint16_t ref; /* reference of this process */
uint8_t source_id; /* reference source */
int timer; /* timer used */
int expired; /* number of timeouts */
uint16_t user_port_id; /* port ID */
bool is_isdn; /* user port type */
uint8_t link_id; /* link ID */
uint8_t ts; /* TS nr */
uint8_t override; /* override assigned channel */
uint8_t isdn_slot; /* channel on ISDN interface */
uint8_t *isdn_multislot; /* multiple slot assignment */
bool use_capability; /* use bearer transfer capability */
uint8_t capability; /* bearer transfer capability */
};
struct v52_pp_mgmt_info {
uint8_t link_id;
uint8_t ts;
uint8_t cause;
};
/* PP process */
struct v52_pp_proto {
struct v5x_interface *interface; /* back pointer, if port control is used */
struct osmo_fsm_inst *fi; /* PP FSM */
uint8_t vp_s, vp_r; /* sequence numbers */
bool vp_r_set; /* if we ever received a sequence number */
struct v52_pp_mgmt_info info; /* keep management info for resend after timeout */
int timeout_event; /* event when timer times out */
int timeout_count; /* how many times the timer was started/timed out */
};
struct v5x_instance {
struct llist_head list; /* part of global list of instances */
struct llist_head interfaces; /* v5x_interface.list */
};
struct v5x_instance *v5x_instance_alloc(void *ctx);
void v5x_instance_free(struct v5x_instance *v5i);
struct v5x_interface *v5x_interface_alloc(struct v5x_instance *v5i, enum v5x_dialect dialect);
struct v5x_user_port *v5x_user_port_create(struct v5x_interface *v5if, uint16_t nr, enum v5x_user_type,
uint8_t ts1, uint8_t ts2);
void v5x_interface_free(struct v5x_interface *v5if);
struct v5x_user_port *v5x_user_port_find(struct v5x_interface *v5if, uint16_t nr, bool is_isdn);
void v5x_user_port_destroy(struct v5x_user_port *v5up);
void v5x_echo_reset(struct v5x_echo_proc *ep, int enable);
struct v5x_link *v5x_link_create(struct v5x_interface *v5if, uint8_t id);
int v5x_link_destroy(struct v5x_link *v5l);
int v5x_link_count(struct v5x_interface *v5if);
struct v5x_link *v5x_link_find_id(struct v5x_interface *v5if, uint8_t id);

742
src/v5x_l1_fsm.c Normal file
View File

@ -0,0 +1,742 @@
/* ITU-T G.964 Section 14.3.3 V5.1-intterface Layer 1 FSM - AN and LE */
/* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
*
* 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.
*
* 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.
*
*/
/***********************************************************************/
/* internal data structures */
/***********************************************************************/
#include <errno.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/tlv.h>
#include "v5x_internal.h"
#include "v5x_protocol.h"
#include "v52_le_lcp_fsm.h"
#include "layer1.h"
#include "v5x_l1_fsm.h"
#include "logging.h"
#define S(x) (1 << (x))
#define TIMEOUT 1
/***********************************************************************/
/* state names, event names, primitives, ... */
/***********************************************************************/
enum v5x_l1_fsm_state {
V5X_L1FSM_S_ANLE1_NORMAL,
V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL,
V5X_L1FSM_S_ANLE3_REMOTELY_DET_FAIL,
V5X_L1FSM_S_ANLE4_INTERNAL_FAIL,
V5X_L1FSM_S_ANLE51_LINK_ID_SENDING,
V5X_L1FSM_S_ANLE52_LINK_ID_RECEIVED,
};
enum v5x_l1_fsm_event {
/* V5.x events */
V5X_L1FSM_E_MPH_stop, /* Request to stop with error report */
V5X_L1FSM_E_MPH_proceed,/* Request to proceed with error report */
V5X_L1FSM_E_NORMAL, /* Normal frames, Sa7 == 1 */
V5X_L1FSM_E_LOS, /* Loss of signal/frame */
V5X_L1FSM_E_RAI, /* Remote alarm indication */
V5X_L1FSM_E_AIS, /* Alarm indication signal */
V5X_L1FSM_E_INTERNAL_F, /* Internal failure */
V5X_L1FSM_E_INTERNAL_D, /* Internal failure disappears */
V5X_L1FSM_E_TIMEOUT, /* Persistence check timer fired */
/* V5.2 events */
V5X_L1FSM_E_MPH_ID, /* Send link identification signal */
V5X_L1FSM_E_MPH_NOR, /* Remove link identification signal */
V5X_L1FSM_E_MPH_IDR, /* Link identification request */
V5X_L1FSM_E_NORMAL_Sa7, /* Normal frames, Sa7 == 0 */
};
static const struct value_string v5x_l1_fsm_event_names[] = {
{ V5X_L1FSM_E_MPH_stop, "Request to stop with error report" },
{ V5X_L1FSM_E_MPH_proceed, "Request to proceed with error report" },
{ V5X_L1FSM_E_NORMAL, "Normal frames, Sa7 = ONE" },
{ V5X_L1FSM_E_LOS, "Loss of signal/frame" },
{ V5X_L1FSM_E_RAI, "Remote alarm indication" },
{ V5X_L1FSM_E_AIS, "Alarm indication signal" },
{ V5X_L1FSM_E_INTERNAL_F, "Internal failure" },
{ V5X_L1FSM_E_INTERNAL_D, "Internal failure disappears" },
{ V5X_L1FSM_E_TIMEOUT, "Expiry of persistence check timer" },
{ V5X_L1FSM_E_MPH_ID, "Send link identification signal" },
{ V5X_L1FSM_E_MPH_NOR, "Remove link identification signal" },
{ V5X_L1FSM_E_MPH_IDR, "Link identification request" },
{ V5X_L1FSM_E_NORMAL_Sa7, "Normal frames, Sa7 = ZERO" },
{ 0, NULL }
};
/***********************************************************************/
/* Messages to other layers */
/***********************************************************************/
/* send message to upper (LCP) layer */
static void mph_rcv(struct osmo_fsm_inst *fi, enum v5x_mph_prim prim)
{
struct v5x_l1_proto *l1 = fi->priv;
struct v5x_link *v5l = l1->v5l;
if (v5l->fi)
v52_le_lcp_mph_rcv(v5l, prim);
// FIXME: send to other protocols too
}
static void signal_snd(struct osmo_fsm_inst *fi, enum l1_signal_prim prim)
{
struct v5x_l1_proto *l1 = fi->priv;
struct v5x_link *v5l = l1->v5l;
v5x_l1_signal_snd(v5l, prim);
}
/***********************************************************************/
/* L1 state FSM */
/***********************************************************************/
static int v5x_l1_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
osmo_fsm_inst_dispatch(fi, V5X_L1FSM_E_TIMEOUT, NULL);
return 0;
}
static void l1_fsm_le1_normal(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V5X_L1FSM_E_NORMAL:
/* ignore */
break;
case V5X_L1FSM_E_LOS:
/* Start timer */
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL, TIMEOUT, 0);
/* send MPH-EIa */
mph_rcv(fi, MPH_EIa);
break;
case V5X_L1FSM_E_RAI:
/* Start timer */
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE3_REMOTELY_DET_FAIL, TIMEOUT, 0);
/* send MPH-EIb */
mph_rcv(fi, MPH_EIb);
break;
case V5X_L1FSM_E_AIS:
/* Start timer */
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL, TIMEOUT, 0);
/* send MPH-EIc */
mph_rcv(fi, MPH_EIc);
break;
case V5X_L1FSM_E_INTERNAL_F:
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE4_INTERNAL_FAIL, 0, 0);
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
/* send MPH-EId */
mph_rcv(fi, MPH_EId);
break;
case V5X_L1FSM_E_TIMEOUT:
/* send MPH-AI */
mph_rcv(fi, MPH_AI);
break;
case V5X_L1FSM_E_MPH_ID:
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE51_LINK_ID_SENDING, 0, 0);
/* send Sa7 = ZERO */
signal_snd(fi, L1_SIGNAL_SA7_0);
break;
case V5X_L1FSM_E_MPH_NOR:
/* ignore */
break;
case V5X_L1FSM_E_NORMAL_Sa7:
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE52_LINK_ID_RECEIVED, 0, 0);
break;
case V5X_L1FSM_E_MPH_IDR:
/* send MPH-EIg */
mph_rcv(fi, MPH_EIg);
break;
default:
OSMO_ASSERT(0);
}
}
static void l1_fsm_le2_locally_det_fail(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V5X_L1FSM_E_NORMAL:
/* Start timer */
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE1_NORMAL, TIMEOUT, 0);
break;
case V5X_L1FSM_E_LOS:
/* send MPH-EIa */
mph_rcv(fi, MPH_EIa);
break;
case V5X_L1FSM_E_RAI:
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE3_REMOTELY_DET_FAIL, 0, 0);
/* send MPH-EIdr */
mph_rcv(fi, MPH_EIdr);
/* send MPH-EIb */
mph_rcv(fi, MPH_EIb);
break;
case V5X_L1FSM_E_AIS:
/* send MPH-EIc */
mph_rcv(fi, MPH_EIc);
break;
case V5X_L1FSM_E_INTERNAL_F:
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE4_INTERNAL_FAIL, 0, 0);
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
/* send MPH-EId */
mph_rcv(fi, MPH_EId);
break;
case V5X_L1FSM_E_TIMEOUT:
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
break;
case V5X_L1FSM_E_MPH_ID:
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
break;
case V5X_L1FSM_E_MPH_NOR:
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
break;
case V5X_L1FSM_E_NORMAL_Sa7:
/* Start timer */
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE52_LINK_ID_RECEIVED, TIMEOUT, 0);
break;
case V5X_L1FSM_E_MPH_IDR:
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
break;
default:
OSMO_ASSERT(0);
}
}
static void l1_fsm_le3_remotely_det_fail(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V5X_L1FSM_E_NORMAL:
/* Start timer */
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE1_NORMAL, TIMEOUT, 0);
break;
case V5X_L1FSM_E_LOS:
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL, 0, 0);
/* send MPH-EIa */
mph_rcv(fi, MPH_EIa);
/* send MPH-EIbr */
mph_rcv(fi, MPH_EIbr);
break;
case V5X_L1FSM_E_RAI:
/* ignore */
break;
case V5X_L1FSM_E_AIS:
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL, 0, 0);
/* send MPH-EIc */
mph_rcv(fi, MPH_EIc);
/* send MPH-EIbr */
mph_rcv(fi, MPH_EIbr);
break;
case V5X_L1FSM_E_INTERNAL_F:
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE4_INTERNAL_FAIL, 0, 0);
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
/* send MPH-EId */
mph_rcv(fi, MPH_EId);
break;
case V5X_L1FSM_E_TIMEOUT:
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
break;
case V5X_L1FSM_E_MPH_ID:
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
break;
case V5X_L1FSM_E_MPH_NOR:
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
break;
case V5X_L1FSM_E_NORMAL_Sa7:
/* Start timer */
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE52_LINK_ID_RECEIVED, TIMEOUT, 0);
break;
case V5X_L1FSM_E_MPH_IDR:
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
break;
default:
OSMO_ASSERT(0);
}
}
static void l1_fsm_le4_internal_fail(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
struct v5x_link *v5l = fi->priv;
struct v5x_l1_proto *l1 = v5l->l1;
switch (event) {
case V5X_L1FSM_E_NORMAL:
/* ignore */
break;
case V5X_L1FSM_E_LOS:
/* send MPH-EIa */
mph_rcv(fi, MPH_EIa);
break;
case V5X_L1FSM_E_RAI:
/* ignore */
break;
case V5X_L1FSM_E_AIS:
/* send MPH-EIc */
mph_rcv(fi, MPH_EIc);
break;
case V5X_L1FSM_E_INTERNAL_F:
/* ignore */
break;
case V5X_L1FSM_E_INTERNAL_D:
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE3_REMOTELY_DET_FAIL, 0, 0);
/* send MPH-EIbr */
mph_rcv(fi, MPH_EIbr);
/* Go on to normal state, when no alarm persists.
* We must do this, because there is no frame reception to trigger this.
*/
if (!l1->los && !l1->rai && !l1->ais) {
if (l1->sa7 == 0)
osmo_fsm_inst_dispatch(fi, V5X_L1FSM_E_NORMAL_Sa7, NULL);
else
osmo_fsm_inst_dispatch(fi, V5X_L1FSM_E_NORMAL, NULL);
}
break;
case V5X_L1FSM_E_TIMEOUT:
/* ignore */
break;
case V5X_L1FSM_E_MPH_ID:
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
break;
case V5X_L1FSM_E_MPH_NOR:
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
break;
case V5X_L1FSM_E_NORMAL_Sa7:
/* ignore */
break;
case V5X_L1FSM_E_MPH_IDR:
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
break;
default:
OSMO_ASSERT(0);
}
}
static void l1_fsm_le51_link_id_sending(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V5X_L1FSM_E_NORMAL:
/* ignore */
break;
case V5X_L1FSM_E_LOS:
/* Start timer */
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL, TIMEOUT, 0);
/* send Sa7 = ONE */
signal_snd(fi, L1_SIGNAL_SA7_1);
/* send MPH-EIa */
mph_rcv(fi, MPH_EIa);
break;
case V5X_L1FSM_E_RAI:
/* Start timer */
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE3_REMOTELY_DET_FAIL, TIMEOUT, 0);
/* send Sa7 = ONE */
signal_snd(fi, L1_SIGNAL_SA7_1);
/* send MPH-EIb */
mph_rcv(fi, MPH_EIb);
break;
case V5X_L1FSM_E_AIS:
/* Start timer */
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL, TIMEOUT, 0);
/* send Sa7 = ONE */
signal_snd(fi, L1_SIGNAL_SA7_1);
/* send MPH-EIc */
mph_rcv(fi, MPH_EIc);
break;
case V5X_L1FSM_E_INTERNAL_F:
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE4_INTERNAL_FAIL, 0, 0);
/* send Sa7 = ONE */
signal_snd(fi, L1_SIGNAL_SA7_1);
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
/* send MPH-EId */
mph_rcv(fi, MPH_EId);
break;
case V5X_L1FSM_E_MPH_ID:
/* ignore */
break;
case V5X_L1FSM_E_MPH_NOR:
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE1_NORMAL, 0, 0);
/* send Sa7 = ONE */
signal_snd(fi, L1_SIGNAL_SA7_1);
break;
case V5X_L1FSM_E_NORMAL_Sa7:
/* ignore */
break;
default:
OSMO_ASSERT(0);
}
}
static void l1_fsm_le52_link_id_received(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V5X_L1FSM_E_NORMAL:
break;
case V5X_L1FSM_E_LOS:
/* Start timer */
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL, TIMEOUT, 0);
/* send MPH-EIa */
mph_rcv(fi, MPH_EIa);
break;
case V5X_L1FSM_E_RAI:
/* Start timer */
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE3_REMOTELY_DET_FAIL, TIMEOUT, 0);
/* send MPH-EIb */
mph_rcv(fi, MPH_EIb);
break;
case V5X_L1FSM_E_AIS:
/* Start timer */
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL, TIMEOUT, 0);
/* send MPH-EIc */
mph_rcv(fi, MPH_EIc);
break;
case V5X_L1FSM_E_INTERNAL_F:
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE4_INTERNAL_FAIL, 0, 0);
/* send MPH-DI */
mph_rcv(fi, MPH_DI);
/* send MPH-EId */
mph_rcv(fi, MPH_EId);
break;
case V5X_L1FSM_E_TIMEOUT:
/* send MPH-DI */
mph_rcv(fi, MPH_AI);
break;
case V5X_L1FSM_E_MPH_ID:
osmo_fsm_inst_state_chg(fi, V5X_L1FSM_S_ANLE51_LINK_ID_SENDING, 0, 0);
/* send Sa7 = ZERO */
signal_snd(fi, L1_SIGNAL_SA7_0);
break;
case V5X_L1FSM_E_NORMAL_Sa7:
/* ignore */
break;
case V5X_L1FSM_E_MPH_IDR:
/* send MPH-IDI */
mph_rcv(fi, MPH_IDI);
break;
default:
OSMO_ASSERT(0);
}
}
/* Table 12/G.965 */
static const struct osmo_fsm_state v5x_l1_fsm_states[] = {
[V5X_L1FSM_S_ANLE1_NORMAL] = {
.name = "AN/LE1 Normal",
.in_event_mask = S(V5X_L1FSM_E_NORMAL) |
S(V5X_L1FSM_E_LOS) |
S(V5X_L1FSM_E_RAI) |
S(V5X_L1FSM_E_AIS) |
S(V5X_L1FSM_E_INTERNAL_F) |
S(V5X_L1FSM_E_TIMEOUT) |
S(V5X_L1FSM_E_MPH_ID) |
S(V5X_L1FSM_E_MPH_NOR) |
S(V5X_L1FSM_E_NORMAL_Sa7) |
S(V5X_L1FSM_E_MPH_IDR),
.out_state_mask = S(V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL) |
S(V5X_L1FSM_S_ANLE3_REMOTELY_DET_FAIL) |
S(V5X_L1FSM_S_ANLE4_INTERNAL_FAIL) |
S(V5X_L1FSM_S_ANLE51_LINK_ID_SENDING) |
S(V5X_L1FSM_S_ANLE52_LINK_ID_RECEIVED),
.action = l1_fsm_le1_normal,
},
[V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL] = {
.name = "AN/LE2 Locally detected failure",
.in_event_mask = S(V5X_L1FSM_E_NORMAL) |
S(V5X_L1FSM_E_LOS) |
S(V5X_L1FSM_E_RAI) |
S(V5X_L1FSM_E_AIS) |
S(V5X_L1FSM_E_INTERNAL_F) |
S(V5X_L1FSM_E_TIMEOUT) |
S(V5X_L1FSM_E_MPH_ID) |
S(V5X_L1FSM_E_MPH_NOR) |
S(V5X_L1FSM_E_NORMAL_Sa7) |
S(V5X_L1FSM_E_MPH_IDR),
.out_state_mask = S(V5X_L1FSM_S_ANLE1_NORMAL) |
S(V5X_L1FSM_S_ANLE3_REMOTELY_DET_FAIL) |
S(V5X_L1FSM_S_ANLE4_INTERNAL_FAIL) |
S(V5X_L1FSM_S_ANLE52_LINK_ID_RECEIVED),
.action = l1_fsm_le2_locally_det_fail,
},
[V5X_L1FSM_S_ANLE3_REMOTELY_DET_FAIL] = {
.name = "AN/LE3 Remotely detected failure",
.in_event_mask = S(V5X_L1FSM_E_NORMAL) |
S(V5X_L1FSM_E_LOS) |
S(V5X_L1FSM_E_RAI) |
S(V5X_L1FSM_E_AIS) |
S(V5X_L1FSM_E_INTERNAL_F) |
S(V5X_L1FSM_E_TIMEOUT) |
S(V5X_L1FSM_E_MPH_ID) |
S(V5X_L1FSM_E_MPH_NOR) |
S(V5X_L1FSM_E_NORMAL_Sa7) |
S(V5X_L1FSM_E_MPH_IDR),
.out_state_mask = S(V5X_L1FSM_S_ANLE1_NORMAL) |
S(V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL) |
S(V5X_L1FSM_S_ANLE4_INTERNAL_FAIL) |
S(V5X_L1FSM_S_ANLE52_LINK_ID_RECEIVED),
.action = l1_fsm_le3_remotely_det_fail,
},
[V5X_L1FSM_S_ANLE4_INTERNAL_FAIL] = {
.name = "AN/LE4 Internal failure",
.in_event_mask = S(V5X_L1FSM_E_NORMAL) |
S(V5X_L1FSM_E_LOS) |
S(V5X_L1FSM_E_RAI) |
S(V5X_L1FSM_E_AIS) |
S(V5X_L1FSM_E_INTERNAL_F) |
S(V5X_L1FSM_E_INTERNAL_D) |
S(V5X_L1FSM_E_TIMEOUT) |
S(V5X_L1FSM_E_MPH_ID) |
S(V5X_L1FSM_E_MPH_NOR) |
S(V5X_L1FSM_E_NORMAL_Sa7) |
S(V5X_L1FSM_E_MPH_IDR),
.out_state_mask = S(V5X_L1FSM_S_ANLE3_REMOTELY_DET_FAIL),
.action = l1_fsm_le4_internal_fail,
},
[V5X_L1FSM_S_ANLE51_LINK_ID_SENDING] = {
.name = "AN/LE5.1 Link ID sending",
.in_event_mask = S(V5X_L1FSM_E_NORMAL) |
S(V5X_L1FSM_E_LOS) |
S(V5X_L1FSM_E_RAI) |
S(V5X_L1FSM_E_AIS) |
S(V5X_L1FSM_E_INTERNAL_F) |
S(V5X_L1FSM_E_MPH_ID) |
S(V5X_L1FSM_E_MPH_NOR) |
S(V5X_L1FSM_E_NORMAL_Sa7),
.out_state_mask = S(V5X_L1FSM_S_ANLE1_NORMAL) |
S(V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL) |
S(V5X_L1FSM_S_ANLE3_REMOTELY_DET_FAIL) |
S(V5X_L1FSM_S_ANLE4_INTERNAL_FAIL),
.action = l1_fsm_le51_link_id_sending,
},
[V5X_L1FSM_S_ANLE52_LINK_ID_RECEIVED] = {
.name = "AN/LE5.2 Link ID received",
.in_event_mask = S(V5X_L1FSM_E_NORMAL) |
S(V5X_L1FSM_E_LOS) |
S(V5X_L1FSM_E_RAI) |
S(V5X_L1FSM_E_AIS) |
S(V5X_L1FSM_E_INTERNAL_F) |
S(V5X_L1FSM_E_TIMEOUT) |
S(V5X_L1FSM_E_MPH_ID) |
S(V5X_L1FSM_E_NORMAL_Sa7) |
S(V5X_L1FSM_E_MPH_IDR),
.out_state_mask = S(V5X_L1FSM_S_ANLE1_NORMAL) |
S(V5X_L1FSM_S_ANLE2_LOCALLY_DET_FAIL) |
S(V5X_L1FSM_S_ANLE3_REMOTELY_DET_FAIL) |
S(V5X_L1FSM_S_ANLE4_INTERNAL_FAIL) |
S(V5X_L1FSM_S_ANLE51_LINK_ID_SENDING),
.action = l1_fsm_le52_link_id_received,
},
};
struct osmo_fsm v5x_l1_fsm = {
.name = "V5X_L1",
.states = v5x_l1_fsm_states,
.num_states = ARRAY_SIZE(v5x_l1_fsm_states),
.timer_cb = v5x_l1_fsm_timer_cb,
.log_subsys = DV5L1,
.event_names = v5x_l1_fsm_event_names,
};
struct v5x_l1_proto *v5x_l1_fsm_create(void *ctx, struct v5x_link *v5l, uint8_t id)
{
struct v5x_l1_proto *l1;
OSMO_ASSERT(v5l);
l1 = talloc_zero(ctx, struct v5x_l1_proto);
if (!l1)
return NULL;
l1->v5l = v5l;
l1->sa7 = -1;
l1->fi = osmo_fsm_inst_alloc(&v5x_l1_fsm, l1, l1, LOGL_DEBUG, NULL);
if (!l1->fi)
return NULL;
osmo_fsm_inst_update_id_f(l1->fi, "%d", id);
return l1;
}
void v5x_l1_fsm_destroy(struct v5x_l1_proto *l1)
{
if (l1->fi) {
osmo_fsm_inst_free(l1->fi);
}
talloc_free(l1);
}
const char *v5x_l1_fsm_state_name(struct v5x_l1_proto *l1)
{
return v5x_l1_fsm_states[l1->fi->state].name;
}
void v5x_l1_init(void)
{
int rc;
rc = osmo_fsm_register(&v5x_l1_fsm);
OSMO_ASSERT(!rc);
LOGP(DV5L1, LOGL_NOTICE, "Using V5x L1 protocol\n");
}
bool v5x_l1_is_up(struct v5x_l1_proto *l1)
{
return l1->fi->state == V5X_L1FSM_S_ANLE1_NORMAL
|| l1->fi->state == V5X_L1FSM_S_ANLE51_LINK_ID_SENDING
|| l1->fi->state == V5X_L1FSM_S_ANLE52_LINK_ID_RECEIVED;
}
/***********************************************************************
* Messages from other layers
***********************************************************************/
/* receive primitive from physical E1 layer */
int v5x_l1_signal_rcv(struct v5x_link *v5l, enum l1_signal_prim prim)
{
struct v5x_l1_proto *l1 = v5l->l1;
struct osmo_fsm_inst *fi = l1->fi;
int state_changed = 0;
switch (prim) {
case L1_SIGNAL_LOS:
LOGP(DV5L1, LOGL_DEBUG, "Signal LOS detected on link %d.\n", v5l->id);
if (!l1->los) {
l1->los = 1;
osmo_fsm_inst_dispatch(fi, V5X_L1FSM_E_LOS, NULL);
}
break;
case L1_SIGNAL_NO_LOS:
LOGP(DV5L1, LOGL_DEBUG, "Signal LOS gone on link %d.\n", v5l->id);
if (l1->los) {
l1->los = 0;
state_changed = 1;
}
break;
case L1_SIGNAL_RAI:
LOGP(DV5L1, LOGL_DEBUG, "Signal RAI detected on link %d.\n", v5l->id);
if (!l1->rai) {
l1->rai = 1;
osmo_fsm_inst_dispatch(fi, V5X_L1FSM_E_RAI, NULL);
}
break;
case L1_SIGNAL_NO_RAI:
LOGP(DV5L1, LOGL_DEBUG, "Signal RAI gone on link %d.\n", v5l->id);
if (l1->rai) {
l1->rai = 0;
state_changed = 1;
}
break;
case L1_SIGNAL_AIS:
LOGP(DV5L1, LOGL_DEBUG, "Signal AIS detected on link %d.\n", v5l->id);
if (!l1->ais) {
l1->ais = 1;
osmo_fsm_inst_dispatch(fi, V5X_L1FSM_E_AIS, NULL);
}
break;
case L1_SIGNAL_NO_AIS:
LOGP(DV5L1, LOGL_DEBUG, "Signal AIS gone on link %d.\n", v5l->id);
if (l1->ais) {
l1->ais = 0;
state_changed = 1;
}
break;
case L1_SIGNAL_SA7_0:
if (l1->sa7 != 0) {
LOGP(DV5L1, LOGL_DEBUG, "Bit Sa7=0 on link %d.\n", v5l->id);
l1->sa7 = 0;
state_changed = 1;
}
break;
case L1_SIGNAL_SA7_1:
if (l1->sa7 != 1) {
LOGP(DV5L1, LOGL_DEBUG, "Bit Sa7=1 on link %d.\n", v5l->id);
l1->sa7 = 1;
state_changed = 1;
}
break;
default:
LOGP(DV5L1, LOGL_NOTICE, "Invalid L1 primitive %d receied from physical layer.\n", prim);
return -EINVAL;
}
/* Go on to normal state, when no alarm persists.
* We must do this, because there is no frame reception to trigger this.
*/
if (state_changed && !l1->los && !l1->rai && !l1->ais) {
if (l1->sa7 == 0)
osmo_fsm_inst_dispatch(fi, V5X_L1FSM_E_NORMAL_Sa7, NULL);
else
osmo_fsm_inst_dispatch(fi, V5X_L1FSM_E_NORMAL, NULL);
}
return 0;
}
/* receive message from upper (LCP) layer */
void v5x_l1_mph_snd(struct v5x_link *v5l, enum v5x_mph_prim prim)
{
struct v5x_l1_proto *l1 = v5l->l1;
enum v5x_l1_fsm_event event;
switch (prim) {
case MPH_ID:
event = V5X_L1FSM_E_MPH_ID;
break;
case MPH_NOR:
event = V5X_L1FSM_E_MPH_NOR;
break;
case MPH_IDR:
event = V5X_L1FSM_E_MPH_IDR;
break;
case MPH_stop:
// FIXME: do we need this?
break;
case MPH_proceed:
// FIXME: do we need this?
break;
default:
LOGP(DV5PORT, LOGL_NOTICE, "Got invalid prim %d at this protocol\n", prim);
return;
}
/* send event to FSM */
osmo_fsm_inst_dispatch(l1->fi, event, NULL);
}

8
src/v5x_l1_fsm.h Normal file
View File

@ -0,0 +1,8 @@
void v5x_l1_mph_snd(struct v5x_link *v5i, enum v5x_mph_prim prim);
struct v5x_l1_proto *v5x_l1_fsm_create(void *ctx, struct v5x_link *v5l, uint8_t id);
void v5x_l1_fsm_destroy(struct v5x_l1_proto *l1);
void v5x_l1_init(void);
bool v5x_l1_is_up(struct v5x_l1_proto *l1);
int v5x_l1_signal_rcv(struct v5x_link *v5i, enum l1_signal_prim prim);
int v5x_l1_signal_snd(struct v5x_link *v5i, enum l1_signal_prim prim);

709
src/v5x_le_ctrl_fsm.c Normal file
View File

@ -0,0 +1,709 @@
/* port/common/link CTRL protocol FSM / G.964 14.4 / G.965 16.3 */
/* (C) 2021 by Harald Welte <laforge@gnumonks.org>
* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
*
* 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.
*
* 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.
*
*/
/***********************************************************************/
/* internal data structures */
/***********************************************************************/
#include <unistd.h>
#include <stdint.h>
#include <errno.h>
#include <arpa/inet.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/tlv.h>
#include "v5x_internal.h"
#include "v5x_protocol.h"
#include "v5x_le_ctrl_fsm.h"
#include "v5x_le_port_fsm.h"
#include "v52_le_lcp_fsm.h"
#include "v5x_le_management.h"
#include "logging.h"
#define S(x) (1 << (x))
#define TIMEOUT 1 // T01 T02 TLCT01
/* uncomment to test lost TX messages at the first transmission */
//#define TEST_TX_FAILURE
/***********************************************************************/
/* state names, event names, primitives, ... */
/***********************************************************************/
enum v5x_ctrl_fsm_state {
V5X_CTRL_ST_OUT_OF_SERVICE = 0,
V5X_CTRL_ST_IN_SERVICE,
V5X_CTRL_ST_AWAIT_ACK,
};
enum v5x_ctrl_fsm_event {
V5X_CTRL_EV_MDU_START_TRAFFIC,
V5X_CTRL_EV_MDU_STOP_TRAFFIC,
V5X_CTRL_EV_MDU_CTRL,
V5X_CTRL_EV_RX_CONTROL,
V5X_CTRL_EV_RX_CONTROL_ACK,
};
static const struct value_string v5x_ctrl_fsm_event_names[] = {
{ V5X_CTRL_EV_MDU_START_TRAFFIC, "MDU-start_traffic" },
{ V5X_CTRL_EV_MDU_STOP_TRAFFIC, "MDU-stop_traffic" },
{ V5X_CTRL_EV_MDU_CTRL, "MDU-CTRL" },
{ V5X_CTRL_EV_RX_CONTROL, "CONTROL" },
{ V5X_CTRL_EV_RX_CONTROL_ACK, "CONTROL_ACK" },
{ 0, NULL }
};
static void v5x_ctrl_mdu(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp)
{
struct v5x_ctrl_proto *ctrl = fi->priv;
struct v5x_interface *v5if = ctrl->priv;
struct v5x_user_port *v5up = ctrl->priv;
struct v5x_link *v5l = ctrl->priv;
enum v5x_ctrl_func_id cfi;
uint8_t variant;
uint8_t rej_cause;
uint32_t interface_id;
enum v5x_ctrl_func_id cfe;
uint8_t perf_grading;
enum v52_link_ctrl_func lcf;
switch (ctrl->type) {
case V5X_CTRL_TYPE_COMMON:
cfi = *TLVP_VAL(tp, V5X_CTRL_IEI_CTRL_F_ID) & 0x7f;
variant = 0;
rej_cause = 0;
interface_id = 0;
LOGP(DV5CTRL, LOGL_DEBUG, "MDU-CTRL received.\n");
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_VARIANT))
variant = *TLVP_VAL(tp, V5X_CTRL_IEI_VARIANT) & 0x7f;
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_REJECTION_CAUSE))
rej_cause = *TLVP_VAL(tp, V5X_CTRL_IEI_REJECTION_CAUSE) & 0x0f;
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_INTERFACE_ID))
interface_id = osmo_load32be_ext_2(TLVP_VAL(tp, V5X_CTRL_IEI_INTERFACE_ID), 3);
v5x_le_common_ctrl_rcv(v5if, cfi, variant, rej_cause, interface_id);
break;
case V5X_CTRL_TYPE_PORT:
cfe = *TLVP_VAL(tp, V5X_CTRL_IEI_CTRL_F_ELEMENT) & 0x7f;
perf_grading = 0;
LOGP(DV5CTRL, LOGL_DEBUG, "FE received for address %d.\n", v5up->nr);
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_PERFORMANCE_GRADING))
perf_grading = *TLVP_VAL(tp, V5X_CTRL_IEI_PERFORMANCE_GRADING) & 0x0f;
switch (v5up->type) {
case V5X_USER_TYPE_ISDN:
v5x_le_port_isdn_fe_rcv(v5up, cfe, perf_grading);
break;
case V5X_USER_TYPE_PSTN:
v5x_le_port_pstn_fe_rcv(v5up, cfe);
break;
}
break;
case V5X_CTRL_TYPE_LINK:
lcf = *TLVP_VAL(tp, V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION) & 0x7f;
LOGP(DV5CTRL, LOGL_DEBUG, "FE received for link ID %d.\n", v5l->id);
v52_le_lcp_fe_rcv(v5l, lcf);
break;
}
}
/* display MDU-error_indication */
static void v5x_mdu_error(struct osmo_fsm_inst __attribute__((unused)) *fi, const char *error)
{
LOGP(DV5CTRL, LOGL_NOTICE, "MDU-error_indication: %s\n", error);
}
/* send control towards lower (DL) layer */
static void v5x_ctrl_send(struct v5x_ctrl_proto *ctrl, struct msgb *msg)
{
struct osmo_fsm_inst *fi = ctrl->fi;
struct v5x_interface *v5if = ctrl->priv;
struct v5x_user_port *v5up = ctrl->priv;
struct v5x_link *v5l = ctrl->priv;
switch (fi->state) {
case V5X_CTRL_ST_IN_SERVICE:
LOGP(DV5CTRL, LOGL_DEBUG, "We are in service, so we send our message now.\n");
/* no message, clone current message and store to be repeated */
ctrl->tx_msg = msgb_copy(msg, NULL);
/* go to AWAIT_PENDING_ACK */
osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_AWAIT_ACK, TIMEOUT, 0);
/* send message towards DL */
#ifdef TEST_TX_FAILURE
msgb_free(msg);
#else
switch (ctrl->type) {
case V5X_CTRL_TYPE_COMMON:
v5x_dl_snd(v5if, V5X_DLADDR_CTRL, msg);
break;
case V5X_CTRL_TYPE_PORT:
v5x_dl_snd(v5up->interface, V5X_DLADDR_CTRL, msg);
break;
case V5X_CTRL_TYPE_LINK:
v5x_dl_snd(v5l->interface, V52_DLADDR_LCP, msg);
break;
}
#endif
break;
case V5X_CTRL_ST_AWAIT_ACK:
LOGP(DV5CTRL, LOGL_DEBUG, "We are waiting for ack, so we queue our message.\n");
/* pending message, save message in queue */
msgb_enqueue(&ctrl->tx_queue, msg);
break;
}
}
/* receive acknowledge */
static void v5x_ctrl_ack(struct osmo_fsm_inst *fi, const struct tlv_parsed __attribute__((unused)) *tp)
{
struct v5x_ctrl_proto *ctrl = fi->priv;
struct msgb *msg;
LOGP(DV5CTRL, LOGL_DEBUG, "Received acknowledge, removing pending message, if any.\n");
/* free pending copy of message, if acked before retry */
if (ctrl->tx_msg) {
msgb_free(ctrl->tx_msg);
ctrl->tx_msg = NULL;
}
/* go to IN_SERVICE */
osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_IN_SERVICE, 0, 0);
/* sending next pending message in queue */
msg = msgb_dequeue(&ctrl->tx_queue);
if (msg) {
LOGP(DV5CTRL, LOGL_DEBUG, "Found pending message in queue.\n");
v5x_ctrl_send(ctrl, msg);
}
}
/* stop traffic */
static void v5x_ctrl_stop(struct osmo_fsm_inst *fi)
{
struct v5x_ctrl_proto *ctrl = fi->priv;
struct msgb *msg;
/* flush pending messages */
if (ctrl->tx_msg) {
msgb_free(ctrl->tx_msg);
ctrl->tx_msg = NULL;
}
while ((msg = msgb_dequeue(&ctrl->tx_queue)))
msgb_free(msg);
/* go to OUT_OF_SERVICE */
osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_OUT_OF_SERVICE, 0, 0);
}
/* T01 / T02 */
static int v5x_ctrl_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct v5x_ctrl_proto *ctrl = fi->priv;
struct v5x_interface *v5if = ctrl->priv;
struct v5x_user_port *v5up = ctrl->priv;
struct v5x_link *v5l = ctrl->priv;
struct msgb *msg;
if (ctrl->tx_msg) {
LOGP(DV5CTRL, LOGL_DEBUG, "Timer fired the first time, resending message.\n");
/* first expiry: repeat CONTROL; re-start T01 */
/* send message towards DL */
osmo_timer_schedule(&fi->timer, TIMEOUT, 0);
msg = ctrl->tx_msg;
ctrl->tx_msg = NULL;
switch (ctrl->type) {
case V5X_CTRL_TYPE_COMMON:
v5x_dl_snd(v5if, V5X_DLADDR_CTRL, msg);
break;
case V5X_CTRL_TYPE_PORT:
v5x_dl_snd(v5up->interface, V5X_DLADDR_CTRL, msg);
break;
case V5X_CTRL_TYPE_LINK:
v5x_dl_snd(v5l->interface, V52_DLADDR_LCP, msg);
break;
}
} else {
LOGP(DV5CTRL, LOGL_DEBUG, "Timer fired the second time, indicate an error.\n");
/* second expiry: send MDU-error_ind; go to IN_SERVICE */
v5x_mdu_error(fi, "Second timeout while waiting for CONTROL ACK.");
osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_IN_SERVICE, 0, 0);
/* sending next pending message in queue */
msg = msgb_dequeue(&ctrl->tx_queue);
if (msg) {
LOGP(DV5CTRL, LOGL_DEBUG, "Found pending message in queue.\n");
v5x_ctrl_send(ctrl, msg);
}
}
return 0;
}
/* events in state OUT OF SERVICE */
static void v5x_ctrl_fsm_oos(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V5X_CTRL_EV_MDU_START_TRAFFIC:
/* go to IN_SERVICE */
osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_IN_SERVICE, 0, 0);
break;
case V5X_CTRL_EV_MDU_STOP_TRAFFIC:
v5x_mdu_error(fi, "Got MDU-stop, but traffic not started.");
break;
case V5X_CTRL_EV_MDU_CTRL:
msgb_free(data);
v5x_mdu_error(fi, "Got MDU-CTRL, but traffic not started.");
break;
case V5X_CTRL_EV_RX_CONTROL:
v5x_mdu_error(fi, "Received CONTROL, but traffic not started.");
break;
case V5X_CTRL_EV_RX_CONTROL_ACK:
v5x_mdu_error(fi, "Received CONTROL ACK, but traffic not started.");
break;
default:
OSMO_ASSERT(0);
}
}
/* events in state IN SERVICE */
static void v5x_ctrl_fsm_ins(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V5X_CTRL_EV_MDU_START_TRAFFIC:
break;
case V5X_CTRL_EV_MDU_STOP_TRAFFIC:
v5x_ctrl_stop(fi);
break;
case V5X_CTRL_EV_MDU_CTRL:
v5x_ctrl_send(fi->priv, data);
break;
case V5X_CTRL_EV_RX_CONTROL:
v5x_ctrl_mdu(fi, data);
break;
default:
OSMO_ASSERT(0);
}
}
/* events in state AWAIT PORT/COMMON ACK */
static void v5x_ctrl_fsm_aa(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V5X_CTRL_EV_MDU_START_TRAFFIC:
break;
case V5X_CTRL_EV_MDU_STOP_TRAFFIC:
v5x_ctrl_stop(fi);
break;
case V5X_CTRL_EV_MDU_CTRL:
v5x_ctrl_send(fi->priv, data);
break;
case V5X_CTRL_EV_RX_CONTROL:
v5x_ctrl_mdu(fi, data);
break;
case V5X_CTRL_EV_RX_CONTROL_ACK:
v5x_ctrl_ack(fi, data);
break;
default:
OSMO_ASSERT(0);
}
}
static const struct osmo_fsm_state v5x_ctrl_fsm_states[] = {
[V5X_CTRL_ST_OUT_OF_SERVICE] = {
.name = "OUT_OF_SERVICE",
.in_event_mask = S(V5X_CTRL_EV_MDU_START_TRAFFIC) |
S(V5X_CTRL_EV_MDU_STOP_TRAFFIC) |
S(V5X_CTRL_EV_MDU_CTRL) |
S(V5X_CTRL_EV_RX_CONTROL) |
S(V5X_CTRL_EV_RX_CONTROL_ACK),
.out_state_mask = S(V5X_CTRL_ST_IN_SERVICE),
.action = v5x_ctrl_fsm_oos,
},
[V5X_CTRL_ST_IN_SERVICE] = {
.name = "IN_SERVICE",
.in_event_mask = S(V5X_CTRL_EV_MDU_START_TRAFFIC) |
S(V5X_CTRL_EV_MDU_STOP_TRAFFIC) |
S(V5X_CTRL_EV_MDU_CTRL) |
S(V5X_CTRL_EV_RX_CONTROL),
.out_state_mask = S(V5X_CTRL_ST_OUT_OF_SERVICE) |
S(V5X_CTRL_ST_AWAIT_ACK),
.action = v5x_ctrl_fsm_ins,
},
[V5X_CTRL_ST_AWAIT_ACK] = {
.name = "AWAIT_ACK",
.in_event_mask = S(V5X_CTRL_EV_MDU_START_TRAFFIC) |
S(V5X_CTRL_EV_MDU_STOP_TRAFFIC) |
S(V5X_CTRL_EV_MDU_CTRL) |
S(V5X_CTRL_EV_RX_CONTROL) |
S(V5X_CTRL_EV_RX_CONTROL_ACK),
.out_state_mask = S(V5X_CTRL_ST_OUT_OF_SERVICE) |
S(V5X_CTRL_ST_IN_SERVICE),
.action = v5x_ctrl_fsm_aa,
},
};
struct osmo_fsm v5x_ctrl_fsm = {
.name = "V5X_CTRL",
.states = v5x_ctrl_fsm_states,
.num_states = ARRAY_SIZE(v5x_ctrl_fsm_states),
.allstate_event_mask = 0,
.allstate_action = NULL,
.cleanup = NULL,
.timer_cb = v5x_ctrl_fsm_timer_cb,
.log_subsys = DV5CTRL,
.event_names = v5x_ctrl_fsm_event_names,
};
void v5x_le_ctrl_init(void)
{
int rc;
rc = osmo_fsm_register(&v5x_ctrl_fsm);
OSMO_ASSERT(!rc);
LOGP(DV5CTRL, LOGL_NOTICE, "Using V5x control protocol\n");
}
struct v5x_ctrl_proto *v5x_le_ctrl_create(enum v5x_ctrl_type type, void *ctx, void *priv, uint16_t nr)
{
struct v5x_ctrl_proto *ctrl;
OSMO_ASSERT(priv);
ctrl = talloc_zero(ctx, struct v5x_ctrl_proto);
if (!ctrl)
return NULL;
ctrl->type = type;
ctrl->priv = priv;
INIT_LLIST_HEAD(&ctrl->tx_queue);
ctrl->fi = osmo_fsm_inst_alloc(&v5x_ctrl_fsm, ctrl, ctrl, LOGL_DEBUG, NULL);
if (!ctrl->fi) {
v5x_le_ctrl_destroy(ctrl);
return NULL;
}
osmo_fsm_inst_update_id_f(ctrl->fi, "%d", nr);
return ctrl;
}
void v5x_le_ctrl_destroy(struct v5x_ctrl_proto *ctrl)
{
/* get rid of pending messages */
msgb_queue_free(&ctrl->tx_queue);
if (ctrl->tx_msg)
msgb_free(ctrl->tx_msg);
if (ctrl->fi)
osmo_fsm_inst_free(ctrl->fi);
talloc_free(ctrl);
}
/***********************************************************************
* V5 Message encoding / sending
***********************************************************************/
/* G.964 Section 14.4.1.1 / Table 48 */
static struct msgb *v5x_enc_ctrl_port(struct v5x_user_port *v5up, enum v5x_ctrl_func_el cfe, bool is_isdn)
{
uint8_t cfe_ie = cfe | 0x80;
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(v5x_l3_addr_enc(v5up->nr, is_isdn));
l3h->msg_type = V5X_CTRL_MSGT_PORT_CTRL;
msgb_tlv_put(msg, V5X_CTRL_IEI_CTRL_F_ELEMENT, 1, &cfe_ie);
return msg;
}
/* G.964 Section 14.4.1.2 / Table 49 */
static struct msgb *v5x_enc_ctrl_port_ack(struct v5x_user_port *v5up, enum v5x_ctrl_func_el cfe, bool is_isdn)
{
uint8_t cfe_ie = cfe | 0x80;
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(v5x_l3_addr_enc(v5up->nr, is_isdn));
l3h->msg_type = V5X_CTRL_MSGT_PORT_CTRL_ACK;
msgb_tlv_put(msg, V5X_CTRL_IEI_CTRL_F_ELEMENT, 1, &cfe_ie);
return msg;
}
/* G.964 Section 14.4.1.3 / Table 50 */
static struct msgb *v5x_enc_ctrl_common(enum v5x_ctrl_func_id cfi, uint8_t *variant, uint8_t *rej_cause,
uint32_t *interface_id)
{
uint8_t cfi_ie = cfi | 0x80;
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(v5x_l3_addr_enc(V5X_DLADDR_CTRL, true));
l3h->msg_type = V5X_CTRL_MSGT_COMMON_CTRL;
msgb_tlv_put(msg, V5X_CTRL_IEI_CTRL_F_ID, 1, &cfi_ie);
if (variant) {
/* Conditional: Variant */
uint8_t variant_ie = *variant | 0x80;
msgb_tlv_put(msg, V5X_CTRL_IEI_VARIANT, 1, &variant_ie);
}
if (rej_cause) {
/* Conditional: Rejection Cause */
msgb_put_u8(msg, 0xF0 | (*rej_cause & 0x0F));
}
if (interface_id) {
/* Conditional: Interface Id */
uint8_t iid_ie[3];
osmo_store32be_ext(*interface_id, iid_ie, 3);
msgb_tlv_put(msg, V5X_CTRL_IEI_INTERFACE_ID, sizeof(iid_ie), iid_ie);
}
return msg;
}
/* G.964 Section 14.4.1.4 / Table 51 */
static struct msgb *v5x_enc_ctrl_common_ack(enum v5x_ctrl_func_id cfi)
{
uint8_t cfi_ie = cfi | 0x80;
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(v5x_l3_addr_enc(V5X_DLADDR_CTRL, true));
l3h->msg_type = V5X_CTRL_MSGT_COMMON_CTRL_ACK;
msgb_tlv_put(msg, V5X_CTRL_IEI_CTRL_F_ID, 1, &cfi_ie);
return msg;
}
/* G.965 Section 16.3.1.1 / Table 19 */
static struct msgb *v52_enc_ctrl_link_ack(struct v5x_link *v5l, enum v52_link_ctrl_func lcf)
{
uint8_t lcf_ie = lcf | 0x80;
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(v5l->id);
l3h->msg_type = V52_CTRL_MSGT_LCP_LINK_CTRL_ACK;
msgb_tlv_put(msg, V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION, 1, &lcf_ie);
return msg;
}
/* G.965 Section 16.3.1.2 / Table 20 */
static struct msgb *v52_enc_ctrl_link(struct v5x_link *v5l, enum v52_link_ctrl_func lcf)
{
uint8_t lcf_ie = lcf | 0x80;
struct v5x_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V5X_CTRL_PDISC;
l3h->l3_addr = htons(v5l->id);
l3h->msg_type = V52_CTRL_MSGT_LCP_LINK_CTRL;
msgb_tlv_put(msg, V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION, 1, &lcf_ie);
return msg;
}
/***********************************************************************
* V5 Message receiving / decoding
***********************************************************************/
static int v5x_rcv_ctrl_port(struct v5x_user_port *v5up, uint8_t msg_type, const struct tlv_parsed *tp)
{
enum v5x_ctrl_func_el cfe = *TLVP_VAL(tp, V5X_CTRL_IEI_CTRL_F_ELEMENT) & 0x7f;
switch (msg_type) {
case V5X_CTRL_MSGT_PORT_CTRL:
/* send ACK to AN */
v5x_dl_snd(v5up->interface, V5X_DLADDR_CTRL, v5x_enc_ctrl_port_ack(v5up, cfe, v5up->type == V5X_USER_TYPE_ISDN));
/* FIXME: send event to FSM */
osmo_fsm_inst_dispatch(v5up->ctrl->fi, V5X_CTRL_EV_RX_CONTROL, (void *)tp);
return 0;
case V5X_CTRL_MSGT_PORT_CTRL_ACK:
osmo_fsm_inst_dispatch(v5up->ctrl->fi, V5X_CTRL_EV_RX_CONTROL_ACK, (void *)tp);
default:
return -EINVAL;
}
}
static int v5x_rcv_ctrl_common(struct v5x_interface *v5if, uint8_t msg_type, const struct tlv_parsed *tp)
{
enum v5x_ctrl_func_id cfi = *TLVP_VAL(tp, V5X_CTRL_IEI_CTRL_F_ID) & 0x7f;
switch (msg_type) {
case V5X_CTRL_MSGT_COMMON_CTRL:
v5x_dl_snd(v5if, V5X_DLADDR_CTRL, v5x_enc_ctrl_common_ack(cfi));
/* send event to FSM */
osmo_fsm_inst_dispatch(v5if->control.ctrl->fi, V5X_CTRL_EV_RX_CONTROL, (void *)tp);
/* send ACK to AN */
return 0;
case V5X_CTRL_MSGT_COMMON_CTRL_ACK:
/* send event to FSM */
osmo_fsm_inst_dispatch(v5if->control.ctrl->fi, V5X_CTRL_EV_RX_CONTROL_ACK, (void *)tp);
return 0;
default:
return -EINVAL;
}
}
static int v52_rcv_ctrl_link(struct v5x_link *v5l, uint8_t msg_type, const struct tlv_parsed *tp)
{
enum v52_link_ctrl_func lcf = *TLVP_VAL(tp, V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION) & 0x7f;
switch (msg_type) {
case V52_CTRL_MSGT_LCP_LINK_CTRL:
v5x_dl_snd(v5l->interface, V52_DLADDR_LCP, v52_enc_ctrl_link_ack(v5l, lcf));
/* send event to FSM */
osmo_fsm_inst_dispatch(v5l->ctrl->fi, V5X_CTRL_EV_RX_CONTROL, (void *)tp);
/* send ACK to AN */
return 0;
case V52_CTRL_MSGT_LCP_LINK_CTRL_ACK:
/* send event to FSM */
osmo_fsm_inst_dispatch(v5l->ctrl->fi, V5X_CTRL_EV_RX_CONTROL_ACK, (void *)tp);
return 0;
default:
return -EINVAL;
}
}
/* receive message from lower (DL) layer */
int v5x_le_ctrl_dl_rcv(struct v5x_interface *v5if, uint16_t l3_addr, bool is_isdn, uint8_t msg_type,
const struct tlv_parsed *tp)
{
struct v5x_user_port *v5up;
struct v5x_link *v5l;
switch (msg_type) {
case V5X_CTRL_MSGT_PORT_CTRL:
case V5X_CTRL_MSGT_PORT_CTRL_ACK:
v5up = v5x_user_port_find(v5if, l3_addr, is_isdn);
if (!v5up) {
LOGP(DV5CTRL, LOGL_ERROR, "Received %s port control message with unknown layer 3 address %d. "
"Please check provisioning!\n", is_isdn ? "ISDN" : "PSTN", l3_addr);
return -ENODEV;
}
return v5x_rcv_ctrl_port(v5up, msg_type, tp);
case V5X_CTRL_MSGT_COMMON_CTRL:
case V5X_CTRL_MSGT_COMMON_CTRL_ACK:
if (l3_addr != V5X_DLADDR_CTRL)
return -EINVAL;
return v5x_rcv_ctrl_common(v5if, msg_type, tp);
case V52_CTRL_MSGT_LCP_LINK_CTRL:
case V52_CTRL_MSGT_LCP_LINK_CTRL_ACK:
v5l = v5x_link_find_id(v5if, l3_addr);
if (!v5l) {
LOGP(DV5CTRL, LOGL_ERROR, "Received link control message with unknown link ID %d. Please "
"check provisioning!\n", l3_addr);
return -ENODEV;
}
return v52_rcv_ctrl_link(v5l, msg_type, tp);
}
return -EINVAL;
}
/***********************************************************************
* V5 Message sending / encoding
***********************************************************************/
/* send common message from upper layer */
int v5x_le_ctrl_common_snd(struct v5x_interface *v5if, enum v5x_ctrl_func_id cfi,
uint8_t *rej_cause, uint8_t *variant, uint32_t *interface_id)
{
LOGP(DV5CTRL, LOGL_DEBUG, "Sending MDU-CTRL (from common control).\n");
osmo_fsm_inst_dispatch(v5if->control.ctrl->fi, V5X_CTRL_EV_MDU_CTRL,
v5x_enc_ctrl_common(cfi, variant, rej_cause, interface_id));
return 0;
}
/* send port message from upper layer */
int v5x_le_ctrl_port_snd(struct v5x_user_port *v5up, enum v5x_ctrl_func_el cfe)
{
LOGP(DV5CTRL, LOGL_DEBUG, "Sending FE (from port).\n");
osmo_fsm_inst_dispatch(v5up->ctrl->fi, V5X_CTRL_EV_MDU_CTRL,
v5x_enc_ctrl_port(v5up, cfe, v5up->type == V5X_USER_TYPE_ISDN));
return 0;
}
/* send link message from upper layer */
int v52_le_ctrl_link_snd(struct v5x_link *v5l, enum v52_link_ctrl_func lcf)
{
LOGP(DV5CTRL, LOGL_DEBUG, "Sending FE (from LCP).\n");
osmo_fsm_inst_dispatch(v5l->ctrl->fi, V5X_CTRL_EV_MDU_CTRL,
v52_enc_ctrl_link(v5l, lcf));
return 0;
}
void v5x_le_ctrl_start(struct v5x_ctrl_proto *ctrl)
{
osmo_fsm_inst_dispatch(ctrl->fi, V5X_CTRL_EV_MDU_START_TRAFFIC, NULL);
}
void v5x_le_ctrl_stop(struct v5x_ctrl_proto *ctrl)
{
if (ctrl->fi->state == V5X_CTRL_ST_OUT_OF_SERVICE)
return;
osmo_fsm_inst_dispatch(ctrl->fi, V5X_CTRL_EV_MDU_STOP_TRAFFIC, NULL);
}
bool v5x_le_ctrl_is_in_service(struct v5x_ctrl_proto *ctrl)
{
return (ctrl->fi->state >= V5X_CTRL_ST_IN_SERVICE);
}

13
src/v5x_le_ctrl_fsm.h Normal file
View File

@ -0,0 +1,13 @@
void v5x_le_ctrl_init(void);
struct v5x_ctrl_proto *v5x_le_ctrl_create(enum v5x_ctrl_type type, void *ctx, void *priv, uint16_t nr);
void v5x_le_ctrl_destroy(struct v5x_ctrl_proto *ctrl);
int v5x_le_ctrl_dl_rcv(struct v5x_interface *v5if, uint16_t l3_addr, bool is_isdn, uint8_t msg_type,
const struct tlv_parsed *tp);
int v5x_le_ctrl_common_snd(struct v5x_interface *v5if, enum v5x_ctrl_func_id cfi,
uint8_t *rej_cause, uint8_t *variant, uint32_t *interface_id);
int v5x_le_ctrl_port_snd(struct v5x_user_port *v5up, enum v5x_ctrl_func_el cfe);
int v52_le_ctrl_link_snd(struct v5x_link *v5l, enum v52_link_ctrl_func lcf);
void v5x_le_ctrl_start(struct v5x_ctrl_proto *ctrl);
void v5x_le_ctrl_stop(struct v5x_ctrl_proto *ctrl);
bool v5x_le_ctrl_is_in_service(struct v5x_ctrl_proto *ctrl);

2543
src/v5x_le_management.c Normal file

File diff suppressed because it is too large Load Diff

31
src/v5x_le_management.h Normal file
View File

@ -0,0 +1,31 @@
const char *v5x_le_system_fsm_state_name(struct osmo_fsm_inst *fi);
const char *v5x_le_pstn_dl_fsm_state_name(struct osmo_fsm_inst *fi);
const char *v5x_le_pstn_rs_fsm_state_name(struct osmo_fsm_inst *fi);
const char *v5x_le_unblk_all_fsm_state_name(struct osmo_fsm_inst *fi);
void v5x_le_mgmt_init(void);
struct v5x_mgmt_proto *v5x_le_mgmt_create(struct v5x_interface *v5if);
void v5x_le_mgmt_destroy(struct v5x_mgmt_proto *mgmt);
void v5x_le_mgmt_start_delay(struct v5x_interface *v5if, int delay);
int v5x_le_mgmt_start(struct v5x_interface *v5if);
int v5x_le_pstn_restart(struct v5x_interface *v5if);
int v5x_le_align_ports(struct v5x_interface *v5if, int accelerated);
void v5x_le_mdl_rcv(struct v5x_interface *v5if, struct v5x_link *v5l, enum v5x_mgmt_prim prim, uint16_t dladdr);
void v5x_le_common_ctrl_rcv(struct v5x_interface *v5if, uint8_t cfi, uint8_t variant, uint8_t rej_cause,
uint32_t interface_id);
void v5x_le_provisioning_start(struct v5x_interface *v5if);
void v5x_le_mgmt_port_unblock(struct v5x_user_port *v5up);
void v5x_le_mgmt_port_block(struct v5x_user_port *v5up);
void v5x_le_nat_ph_rcv(struct v5x_user_port *v5up, uint8_t prim, uint8_t *data, int length);
void v5x_le_port_mph_rcv(struct v5x_user_port *v5up, enum v5x_mph_prim prim, uint8_t perf_grading);
void v5x_le_nat_fe_rcv(struct v5x_user_port *v5up, struct msgb *msg);
void v5x_le_pstn_fe_rcv(struct v5x_user_port *v5up, enum v5x_fe_prim prim, struct msgb *msg);
void v5x_le_pstn_mdu_rcv(struct v5x_user_port *v5up, enum v5x_mgmt_prim prim);
void v52_le_lcp_mdu_rcv(struct v5x_link *v5l, enum v5x_mgmt_prim prim);
int v52_le_pp_mdu_rcv(struct v5x_interface *v5if, enum v5x_mgmt_prim prim, uint8_t link_id, uint8_t ts,
const uint8_t *cause, uint8_t cause_len);
void v5x_le_channel_assign(struct v5x_user_port *v5up, int channel);
void v5x_le_channel_unassign(struct v5x_user_port *v5up, int channel);
int v52_le_bcc_mdu_rcv(struct v5x_interface *v5if, uint8_t link_id, uint8_t ts,
uint8_t __attribute__((unused)) *isdn_multislot, enum v5x_mgmt_prim prim, const uint8_t *cause,
uint8_t cause_len);

887
src/v5x_le_port_fsm.c Normal file
View File

@ -0,0 +1,887 @@
/* ITU-T G.964 Section 14.1.3.2.2 ISDN user port FSM - LE */
/***********************************************************************/
/* internal data structures */
/***********************************************************************/
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/tlv.h>
#include "v5x_internal.h"
#include "v5x_protocol.h"
#include "v5x_le_ctrl_fsm.h"
#include "v5x_le_management.h"
#include "logging.h"
#define S(x) (1 << (x))
/***********************************************************************/
/* state names, event names, primitives, ... */
/***********************************************************************/
/* 14.1.3.2.2 ISDN user port FSM - LE(ISDN port) */
enum v5x_le_isdn_port_state {
V5X_LE_UP_I_S_LE10_NOP_BLOCKED, /* LE1.0 */
V5X_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK, /* LE1.1 */
V5X_LE_UP_I_S_LE12_NOP_REMOTE_UNBLOCK, /* LE1.2 */
V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, /* LE2.0 */
V5X_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED, /* LE2.1 */
V5X_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED, /* LE2.2 */
};
/* 14.2.3.2.2 PSTN user port FSM - LE(PSTN port) */
enum v5x_le_pstn_port_state {
V5X_LE_UP_P_S_LE10_NOP_BLOCKED, /* LE1.0 */
V5X_LE_UP_P_S_LE11_NOP_LOCAL_UNBLOCK, /* LE1.1 */
V5X_LE_UP_P_S_LE12_NOP_REMOTE_UNBLOCK, /* LE1.2 */
V5X_LE_UP_P_S_LE20_OPERATIONAL, /* LE2.0 */
};
enum v5x_le_ctrl_port_fsm_event {
/* inbound function elements from AN (Table 34/G.964) */
V5X_CUP_LE_FE102_ACTIV_INIT_USER_IND,
V5X_CUP_LE_FE103_DS_ACTIVATED_IND,
V5X_CUP_LE_FE104_ACCESS_ACTIVATED_IND,
V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND,
V5X_CUP_LE_FE202_UNBLOCK,
V5X_CUP_LE_FE204_BLOCK_CMD,
V5X_CUP_LE_FE205_BLOCK_REQ,
V5X_CUP_LE_FE206_PREFORMANCE_GRADING_IND,
/* inbound primitives from Mgmt (Table 35/G.964) */
V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ,
V5X_CUP_LE_MPH_BI_BLOCK_CMD,
V5X_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ,
V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ,
V5X_CUP_LE_MPH_DB_BLOCK_DCHAN_REQ,
V5X_CUP_LE_MPH_DU_BLOCK_DCHAN_REQ,
};
static const struct value_string v5x_le_ctrl_port_fsm_event_names[] = {
{ V5X_CUP_LE_FE102_ACTIV_INIT_USER_IND, "FE102 Activation by user indication" },
{ V5X_CUP_LE_FE103_DS_ACTIVATED_IND, "FE103 DS Activated indication" },
{ V5X_CUP_LE_FE104_ACCESS_ACTIVATED_IND, "FE104 Access activated indication" },
{ V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND, "FE106 Access deactivated indication" },
{ V5X_CUP_LE_FE202_UNBLOCK, "FE202 Unblock request or ack" },
{ V5X_CUP_LE_FE204_BLOCK_CMD, "FE204 Block command" },
{ V5X_CUP_LE_FE205_BLOCK_REQ, "FE205 Block request" },
{ V5X_CUP_LE_FE206_PREFORMANCE_GRADING_IND, "FE206 Performance grading info" },
{ V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ, "MPH-UBR Unblock request" },
{ V5X_CUP_LE_MPH_BI_BLOCK_CMD, "MPH-BI Block command" },
{ V5X_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ, "MPH-AR Activate access" },
{ V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ, "MPH-DR Deactivate access" },
{ V5X_CUP_LE_MPH_DB_BLOCK_DCHAN_REQ, "MPH-DB Block D-channel" },
{ V5X_CUP_LE_MPH_DU_BLOCK_DCHAN_REQ, "MPH-DU Unblock D-channel" },
{ 0, NULL }
};
/***********************************************************************/
/* Messages to other layers */
/***********************************************************************/
/* send message to management layer */
static void mph_rcv(struct osmo_fsm_inst *fi, enum v5x_mph_prim prim, uint8_t perf_grading)
{
struct v5x_user_port *v5up = fi->priv;
v5x_le_port_mph_rcv(v5up, prim, perf_grading);
}
static void ctrl_snd(struct osmo_fsm_inst *fi, enum v5x_ctrl_func_el prim)
{
struct v5x_user_port *v5up = fi->priv;
v5x_le_ctrl_port_snd(v5up, prim);
}
/***********************************************************************/
/* ISDN port state FSM */
/***********************************************************************/
static void isdn_up_le10_blocked(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V5X_CUP_LE_FE102_ACTIV_INIT_USER_IND:
case V5X_CUP_LE_FE103_DS_ACTIVATED_IND:
case V5X_CUP_LE_FE104_ACCESS_ACTIVATED_IND:
case V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ:
case V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND:
/* ignore */
break;
case V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK, 0, 0);
/* Send FE201 */
ctrl_snd(fi, V5X_CTRL_FE201_UNBLOCK);
break;
case V5X_CUP_LE_MPH_BI_BLOCK_CMD:
/* Send FE203 */
ctrl_snd(fi, V5X_CTRL_FE203_BLOCK);
break;
case V5X_CUP_LE_FE202_UNBLOCK:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE12_NOP_REMOTE_UNBLOCK, 0, 0);
/* Send MPU-UBR */
mph_rcv(fi, MPH_UBR, 0);
break;
case V5X_CUP_LE_FE204_BLOCK_CMD:
case V5X_CUP_LE_FE205_BLOCK_REQ:
/* ignore */
break;
default:
OSMO_ASSERT(0);
}
}
static void isdn_up_le11_local_unblock(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V5X_CUP_LE_FE102_ACTIV_INIT_USER_IND:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED, 0, 0);
/* send MPG-AWI */
mph_rcv(fi, MPH_AWI, 0);
break;
case V5X_CUP_LE_FE103_DS_ACTIVATED_IND:
/* ignore */
break;
case V5X_CUP_LE_FE104_ACCESS_ACTIVATED_IND:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED, 0, 0);
/* send MPH-AI */
mph_rcv(fi, MPH_AI, 0);
break;
case V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ:
case V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND:
/* ignore */
break;
case V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ:
/* send FE201 */
ctrl_snd(fi, V5X_CTRL_FE201_UNBLOCK);
break;
case V5X_CUP_LE_MPH_BI_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
/* send FE203 */
ctrl_snd(fi, V5X_CTRL_FE203_BLOCK);
break;
case V5X_CUP_LE_FE202_UNBLOCK:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0);
/* send MPH-UBI */
mph_rcv(fi, MPH_UBI, 0);
break;
case V5X_CUP_LE_FE204_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
/* send MPH-BI */
mph_rcv(fi, MPH_BI, 0);
break;
case V5X_CUP_LE_FE205_BLOCK_REQ:
/* ignore */
break;
default:
OSMO_ASSERT(0);
}
}
static void isdn_up_le12_remote_unblock(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V5X_CUP_LE_FE103_DS_ACTIVATED_IND:
/* ignore */
break;
case V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ:
case V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND:
/* ignore */
break;
case V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0);
/* send FE201 */
ctrl_snd(fi, V5X_CTRL_FE201_UNBLOCK);
/* send MPH-UBI */
mph_rcv(fi, MPH_UBI, 0);
break;
case V5X_CUP_LE_MPH_BI_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
/* send FE203 */
ctrl_snd(fi, V5X_CTRL_FE203_BLOCK);
break;
case V5X_CUP_LE_FE202_UNBLOCK:
/* send MPH-UBR */
mph_rcv(fi, MPH_UBR, 0);
break;
case V5X_CUP_LE_FE204_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
/* send MPH-BI */
mph_rcv(fi, MPH_BI, 0);
break;
case V5X_CUP_LE_FE205_BLOCK_REQ:
/* ignore */
break;
default:
OSMO_ASSERT(0);
}
}
/* LE 2.0 Operational deactivated */
static void isdn_up_le20_op_deact(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V5X_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED, 0, 0);
/* Send FE101 */
ctrl_snd(fi, V5X_CTRL_FE101_ACTIVATE_ACCESS);
break;
case V5X_CUP_LE_FE102_ACTIV_INIT_USER_IND:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED, 0, 0);
/* Send MPH-AWI */
mph_rcv(fi, MPH_AWI, 0);
break;
case V5X_CUP_LE_FE103_DS_ACTIVATED_IND:
/* ignore */
break;
case V5X_CUP_LE_FE104_ACCESS_ACTIVATED_IND:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED, 0, 0);
/* Send MPH-AI */
mph_rcv(fi, MPH_AI, 0);
break;
case V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ:
/* Send FE105 */
ctrl_snd(fi, V5X_CTRL_FE105_DEACTIVATE_ACCESS);
break;
case V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND:
/* Send MPH-DI */
mph_rcv(fi, MPH_DI, 0);
break;
case V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ:
/* Send FE201 */
ctrl_snd(fi, V5X_CTRL_FE201_UNBLOCK);
break;
case V5X_CUP_LE_MPH_BI_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
/* Send FE203 */
ctrl_snd(fi, V5X_CTRL_FE203_BLOCK);
break;
case V5X_CUP_LE_FE202_UNBLOCK:
/* Send MPH-UBI */
mph_rcv(fi, MPH_UBI, 0);
break;
case V5X_CUP_LE_FE204_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
/* Send MPH-BI */
mph_rcv(fi, MPH_BI, 0);
break;
case V5X_CUP_LE_FE205_BLOCK_REQ:
/* Send MPH-BR */
mph_rcv(fi, MPH_BR, 0);
break;
case V5X_CUP_LE_FE206_PREFORMANCE_GRADING_IND:
/* Send MPH-GI */
mph_rcv(fi, MPH_GI, *(uint8_t *)data);
break;
default:
OSMO_ASSERT(0);
}
}
/* LE 2.1 Access initiated */
static void isdn_up_le21_op_act_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V5X_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ:
case V5X_CUP_LE_FE102_ACTIV_INIT_USER_IND:
/* ignore */
break;
case V5X_CUP_LE_FE103_DS_ACTIVATED_IND:
/* Send MPH-DSAI */
mph_rcv(fi, MPH_DSAI, 0);
break;
case V5X_CUP_LE_FE104_ACCESS_ACTIVATED_IND:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED, 0, 0);
/* Send MPH-AI */
mph_rcv(fi, MPH_AI, 0);
break;
case V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0);
/* Send FE105 */
ctrl_snd(fi, V5X_CTRL_FE105_DEACTIVATE_ACCESS);
/* Send MPH-DI */
mph_rcv(fi, MPH_DI, 0);
break;
case V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0);
/* Send MPH-DI */
mph_rcv(fi, MPH_DI, 0);
break;
case V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ:
/* Send FE201 */
ctrl_snd(fi, V5X_CTRL_FE201_UNBLOCK);
break;
case V5X_CUP_LE_MPH_BI_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
/* Send FE203 */
ctrl_snd(fi, V5X_CTRL_FE203_BLOCK);
break;
case V5X_CUP_LE_FE202_UNBLOCK:
/* Send MPH-UBI */
mph_rcv(fi, MPH_UBI, 0);
break;
case V5X_CUP_LE_FE204_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
/* Send MPH-BI */
mph_rcv(fi, MPH_BI, 0);
/* Send MPH-DI */
mph_rcv(fi, MPH_DI, 0);
break;
case V5X_CUP_LE_FE205_BLOCK_REQ:
/* Send MPH-BR */
mph_rcv(fi, MPH_BR, 0);
break;
case V5X_CUP_LE_FE206_PREFORMANCE_GRADING_IND:
/* Send MPH-GI */
mph_rcv(fi, MPH_GI, *(uint8_t *)data);
break;
default:
OSMO_ASSERT(0);
}
}
/* LE 2.2 Access activated */
static void isdn_up_le22_op_acc_act(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V5X_CUP_LE_FE104_ACCESS_ACTIVATED_IND:
/* ignore */
break;
case V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0);
/* Send FE105 */
ctrl_snd(fi, V5X_CTRL_FE105_DEACTIVATE_ACCESS);
/* Send MPH-DI */
mph_rcv(fi, MPH_DI, 0);
break;
case V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0);
/* Send MPH-DI */
mph_rcv(fi, MPH_DI, 0);
break;
case V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ:
/* Send FE201 */
ctrl_snd(fi, V5X_CTRL_FE201_UNBLOCK);
/* Send MPH-AI */
mph_rcv(fi, MPH_AI, 0);
break;
case V5X_CUP_LE_MPH_BI_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
/* Send FE203 */
ctrl_snd(fi, V5X_CTRL_FE203_BLOCK);
break;
case V5X_CUP_LE_FE202_UNBLOCK:
/* Send MPH-UBI */
mph_rcv(fi, MPH_UBI, 0);
break;
case V5X_CUP_LE_FE204_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
/* Send MPH-BI */
mph_rcv(fi, MPH_BI, 0);
/* Send MPH-DI */
mph_rcv(fi, MPH_DI, 0);
break;
case V5X_CUP_LE_FE205_BLOCK_REQ:
/* Send MPH-BR */
mph_rcv(fi, MPH_BR, 0);
break;
case V5X_CUP_LE_FE206_PREFORMANCE_GRADING_IND:
/* Send MPH-GI */
mph_rcv(fi, MPH_GI, *(uint8_t *)data);
break;
case V5X_CUP_LE_MPH_DB_BLOCK_DCHAN_REQ:
/* Send FE207 */
ctrl_snd(fi, V5X_CTRL_FE207_D_CHANNEL_BLOCK);
break;
case V5X_CUP_LE_MPH_DU_BLOCK_DCHAN_REQ:
/* Send FE208 */
ctrl_snd(fi, V5X_CTRL_FE208_D_CHANNEL_UNBLOCK);
break;
default:
OSMO_ASSERT(0);
}
}
/* Table 38/G.964 LE (ISDN port) FSM for ISDN basic access user ports */
static const struct osmo_fsm_state v5x_le_ctrl_isdn_port_fsm_states[] = {
[V5X_LE_UP_I_S_LE10_NOP_BLOCKED] = {
.name = "Blocked (LE1.0)",
.in_event_mask = S(V5X_CUP_LE_FE102_ACTIV_INIT_USER_IND) |
S(V5X_CUP_LE_FE103_DS_ACTIVATED_IND) |
S(V5X_CUP_LE_FE104_ACCESS_ACTIVATED_IND) |
S(V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) |
S(V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) |
S(V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V5X_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V5X_CUP_LE_FE202_UNBLOCK) |
S(V5X_CUP_LE_FE204_BLOCK_CMD) |
S(V5X_CUP_LE_FE205_BLOCK_REQ),
.out_state_mask = S(V5X_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK) |
S(V5X_LE_UP_I_S_LE12_NOP_REMOTE_UNBLOCK),
.action = isdn_up_le10_blocked,
},
[V5X_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK] = {
.name = "Local unblock (LE1.1)",
.in_event_mask = S(V5X_CUP_LE_FE102_ACTIV_INIT_USER_IND) |
S(V5X_CUP_LE_FE103_DS_ACTIVATED_IND) |
S(V5X_CUP_LE_FE104_ACCESS_ACTIVATED_IND) |
S(V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) |
S(V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) |
S(V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V5X_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V5X_CUP_LE_FE202_UNBLOCK) |
S(V5X_CUP_LE_FE204_BLOCK_CMD) |
S(V5X_CUP_LE_FE205_BLOCK_REQ),
.out_state_mask = S(V5X_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED) |
S(V5X_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED) |
S(V5X_LE_UP_I_S_LE10_NOP_BLOCKED) |
S(V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED),
.action = isdn_up_le11_local_unblock,
},
[V5X_LE_UP_I_S_LE12_NOP_REMOTE_UNBLOCK] = {
.name = "Remote unblock (LE1.2)",
.in_event_mask = S(V5X_CUP_LE_FE103_DS_ACTIVATED_IND) |
S(V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) |
S(V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) |
S(V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V5X_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V5X_CUP_LE_FE202_UNBLOCK) |
S(V5X_CUP_LE_FE204_BLOCK_CMD) |
S(V5X_CUP_LE_FE205_BLOCK_REQ),
.out_state_mask = S(V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED) |
S(V5X_LE_UP_I_S_LE10_NOP_BLOCKED),
.action = isdn_up_le12_remote_unblock,
},
[V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED] = {
.name = "Operational deactivated (LE2.0)",
.in_event_mask = S(V5X_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ) |
S(V5X_CUP_LE_FE102_ACTIV_INIT_USER_IND) |
S(V5X_CUP_LE_FE103_DS_ACTIVATED_IND) |
S(V5X_CUP_LE_FE104_ACCESS_ACTIVATED_IND) |
S(V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) |
S(V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) |
S(V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V5X_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V5X_CUP_LE_FE202_UNBLOCK) |
S(V5X_CUP_LE_FE204_BLOCK_CMD) |
S(V5X_CUP_LE_FE205_BLOCK_REQ) |
S(V5X_CUP_LE_FE206_PREFORMANCE_GRADING_IND),
.out_state_mask = S(V5X_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED) |
S(V5X_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED) |
S(V5X_LE_UP_I_S_LE10_NOP_BLOCKED),
.action = isdn_up_le20_op_deact,
},
[V5X_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED] = {
.name = "Activation initiated (LE2.1)",
.in_event_mask = S(V5X_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ) |
S(V5X_CUP_LE_FE102_ACTIV_INIT_USER_IND) |
S(V5X_CUP_LE_FE103_DS_ACTIVATED_IND) |
S(V5X_CUP_LE_FE104_ACCESS_ACTIVATED_IND) |
S(V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) |
S(V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) |
S(V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V5X_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V5X_CUP_LE_FE202_UNBLOCK) |
S(V5X_CUP_LE_FE204_BLOCK_CMD) |
S(V5X_CUP_LE_FE205_BLOCK_REQ),
.out_state_mask = S(V5X_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED) |
S(V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED) |
S(V5X_LE_UP_I_S_LE10_NOP_BLOCKED),
.action = isdn_up_le21_op_act_init,
},
[V5X_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED] = {
.name = "Access activated (LE2.2)",
.in_event_mask = S(V5X_CUP_LE_FE104_ACCESS_ACTIVATED_IND) |
S(V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) |
S(V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) |
S(V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V5X_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V5X_CUP_LE_FE202_UNBLOCK) |
S(V5X_CUP_LE_FE204_BLOCK_CMD) |
S(V5X_CUP_LE_FE205_BLOCK_REQ) |
S(V5X_CUP_LE_FE206_PREFORMANCE_GRADING_IND) |
S(V5X_CUP_LE_MPH_DB_BLOCK_DCHAN_REQ) |
S(V5X_CUP_LE_MPH_DU_BLOCK_DCHAN_REQ),
.out_state_mask = S(V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED) |
S(V5X_LE_UP_I_S_LE10_NOP_BLOCKED),
.action = isdn_up_le22_op_acc_act,
},
};
struct osmo_fsm v5x_le_ctrl_isdn_port_fsm = {
.name = "V5X_CTRL_ISDN",
.states = v5x_le_ctrl_isdn_port_fsm_states,
.num_states = ARRAY_SIZE(v5x_le_ctrl_isdn_port_fsm_states),
.allstate_event_mask = 0,
.allstate_action = NULL,
.cleanup = NULL,
.log_subsys = DV5PORT,
.event_names = v5x_le_ctrl_port_fsm_event_names,
};
struct osmo_fsm_inst *v5x_le_port_isdn_create(struct v5x_user_port *v5up, uint16_t nr)
{
struct osmo_fsm_inst *fi;
OSMO_ASSERT(v5up);
fi = osmo_fsm_inst_alloc(&v5x_le_ctrl_isdn_port_fsm, v5up, v5up, LOGL_DEBUG, NULL);
if (!fi)
return NULL;
osmo_fsm_inst_update_id_f(fi, "%d", nr);
return fi;
}
bool v5x_le_port_isdn_is_operational(struct osmo_fsm_inst *fi)
{
return (fi->state >= V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED);
}
void v5x_le_port_isdn_block(struct osmo_fsm_inst *fi)
{
LOGP(DV5PORT, LOGL_DEBUG, "Put ISDN port into blocked state\n");
fi->state = V5X_LE_UP_I_S_LE10_NOP_BLOCKED;
}
void v5x_le_port_isdn_unblock(struct osmo_fsm_inst *fi)
{
LOGP(DV5PORT, LOGL_DEBUG, "Put ISDN port into unblocked state\n");
fi->state = V5X_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED;
}
const char *v5x_le_port_isdn_state_name(struct osmo_fsm_inst *fi)
{
return v5x_le_ctrl_isdn_port_fsm_states[fi->state].name;
}
/***********************************************************************/
/* PSTN port state FSM */
/***********************************************************************/
static void pstn_up_le10_blocked(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_P_S_LE11_NOP_LOCAL_UNBLOCK, 0, 0);
/* Send FE201 */
ctrl_snd(fi, V5X_CTRL_FE201_UNBLOCK);
break;
case V5X_CUP_LE_MPH_BI_BLOCK_CMD:
/* Send FE203 */
ctrl_snd(fi, V5X_CTRL_FE203_BLOCK);
break;
case V5X_CUP_LE_FE202_UNBLOCK:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_P_S_LE12_NOP_REMOTE_UNBLOCK, 0, 0);
/* Send MPU-UBR */
mph_rcv(fi, MPH_UBR, 0);
break;
case V5X_CUP_LE_FE204_BLOCK_CMD:
case V5X_CUP_LE_FE205_BLOCK_REQ:
/* ignore */
break;
default:
OSMO_ASSERT(0);
}
}
static void pstn_up_le11_local_unblock(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ:
/* send FE201 */
ctrl_snd(fi, V5X_CTRL_FE201_UNBLOCK);
break;
case V5X_CUP_LE_MPH_BI_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_P_S_LE10_NOP_BLOCKED, 0, 0);
/* send FE203 */
ctrl_snd(fi, V5X_CTRL_FE203_BLOCK);
break;
case V5X_CUP_LE_FE202_UNBLOCK:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_P_S_LE20_OPERATIONAL, 0, 0);
/* send MPH-UBI */
mph_rcv(fi, MPH_UBI, 0);
break;
case V5X_CUP_LE_FE204_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_P_S_LE10_NOP_BLOCKED, 0, 0);
/* send MPH-BI */
mph_rcv(fi, MPH_BI, 0);
break;
case V5X_CUP_LE_FE205_BLOCK_REQ:
/* ignore */
break;
default:
OSMO_ASSERT(0);
}
}
static void pstn_up_le12_remote_unblock(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_P_S_LE20_OPERATIONAL, 0, 0);
/* send FE201 */
ctrl_snd(fi, V5X_CTRL_FE201_UNBLOCK);
/* send MPH-UBI */
mph_rcv(fi, MPH_UBI, 0);
break;
case V5X_CUP_LE_MPH_BI_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_P_S_LE10_NOP_BLOCKED, 0, 0);
/* send FE203 */
ctrl_snd(fi, V5X_CTRL_FE203_BLOCK);
break;
case V5X_CUP_LE_FE202_UNBLOCK:
/* send MPH-UBR */
mph_rcv(fi, MPH_UBR, 0);
break;
case V5X_CUP_LE_FE204_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_P_S_LE10_NOP_BLOCKED, 0, 0);
/* send MPH-BI */
mph_rcv(fi, MPH_BI, 0);
break;
case V5X_CUP_LE_FE205_BLOCK_REQ:
/* ignore */
break;
default:
OSMO_ASSERT(0);
}
}
/* LE 2.0 Operational deactivated */
static void pstn_up_le20_operational(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data)
{
switch (event) {
case V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ:
/* Send FE201 */
ctrl_snd(fi, V5X_CTRL_FE201_UNBLOCK);
break;
case V5X_CUP_LE_MPH_BI_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_P_S_LE10_NOP_BLOCKED, 0, 0);
/* Send FE203 */
ctrl_snd(fi, V5X_CTRL_FE203_BLOCK);
break;
case V5X_CUP_LE_FE202_UNBLOCK:
/* Send MPH-UBI */
mph_rcv(fi, MPH_UBI, 0);
break;
case V5X_CUP_LE_FE204_BLOCK_CMD:
osmo_fsm_inst_state_chg(fi, V5X_LE_UP_P_S_LE10_NOP_BLOCKED, 0, 0);
/* Send MPH-BI */
mph_rcv(fi, MPH_BI, 0);
break;
case V5X_CUP_LE_FE205_BLOCK_REQ:
/* Send MPH-BR */
mph_rcv(fi, MPH_BR, 0);
break;
default:
OSMO_ASSERT(0);
}
}
/* Table 43/G.964 LE (ISDN port) FSM for PSTN user ports */
static const struct osmo_fsm_state v5x_le_ctrl_pstn_port_fsm_states[] = {
[V5X_LE_UP_P_S_LE10_NOP_BLOCKED] = {
.name = "Blocked (LE1.0)",
.in_event_mask = S(V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V5X_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V5X_CUP_LE_FE202_UNBLOCK) |
S(V5X_CUP_LE_FE204_BLOCK_CMD) |
S(V5X_CUP_LE_FE205_BLOCK_REQ),
.out_state_mask = S(V5X_LE_UP_P_S_LE11_NOP_LOCAL_UNBLOCK) |
S(V5X_LE_UP_P_S_LE12_NOP_REMOTE_UNBLOCK),
.action = pstn_up_le10_blocked,
},
[V5X_LE_UP_P_S_LE11_NOP_LOCAL_UNBLOCK] = {
.name = "Local unblock (LE1.1)",
.in_event_mask = S(V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V5X_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V5X_CUP_LE_FE202_UNBLOCK) |
S(V5X_CUP_LE_FE204_BLOCK_CMD) |
S(V5X_CUP_LE_FE205_BLOCK_REQ),
.out_state_mask = S(V5X_LE_UP_P_S_LE10_NOP_BLOCKED) |
S(V5X_LE_UP_P_S_LE20_OPERATIONAL),
.action = pstn_up_le11_local_unblock,
},
[V5X_LE_UP_P_S_LE12_NOP_REMOTE_UNBLOCK] = {
.name = "Remote unblock (LE1.2)",
.in_event_mask = S(V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V5X_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V5X_CUP_LE_FE202_UNBLOCK) |
S(V5X_CUP_LE_FE204_BLOCK_CMD) |
S(V5X_CUP_LE_FE205_BLOCK_REQ),
.out_state_mask = S(V5X_LE_UP_P_S_LE20_OPERATIONAL) |
S(V5X_LE_UP_P_S_LE10_NOP_BLOCKED),
.action = pstn_up_le12_remote_unblock,
},
[V5X_LE_UP_P_S_LE20_OPERATIONAL] = {
.name = "Operational (LE2.0)",
.in_event_mask = S(V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V5X_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V5X_CUP_LE_FE202_UNBLOCK) |
S(V5X_CUP_LE_FE204_BLOCK_CMD) |
S(V5X_CUP_LE_FE205_BLOCK_REQ),
.out_state_mask = S(V5X_LE_UP_P_S_LE10_NOP_BLOCKED),
.action = pstn_up_le20_operational,
},
};
struct osmo_fsm v5x_le_ctrl_pstn_port_fsm = {
.name = "V5X_CTRL_PSTN",
.states = v5x_le_ctrl_pstn_port_fsm_states,
.num_states = ARRAY_SIZE(v5x_le_ctrl_pstn_port_fsm_states),
.allstate_event_mask = 0,
.allstate_action = NULL,
.cleanup = NULL,
.log_subsys = DV5PORT,
.event_names = v5x_le_ctrl_port_fsm_event_names,
};
struct osmo_fsm_inst *v5x_le_port_pstn_create(struct v5x_user_port *v5up, uint16_t nr)
{
struct osmo_fsm_inst *fi;
OSMO_ASSERT(v5up);
fi = osmo_fsm_inst_alloc(&v5x_le_ctrl_pstn_port_fsm, v5up, v5up, LOGL_DEBUG, NULL);
if (!fi)
return NULL;
osmo_fsm_inst_update_id_f(fi, "%d", nr);
return fi;
}
void v5x_le_port_init(void)
{
int rc;
rc = osmo_fsm_register(&v5x_le_ctrl_isdn_port_fsm);
OSMO_ASSERT(!rc);
rc = osmo_fsm_register(&v5x_le_ctrl_pstn_port_fsm);
OSMO_ASSERT(!rc);
LOGP(DV5PORT, LOGL_NOTICE, "Using V5x port control protocol\n");
}
bool v5x_le_port_pstn_is_operational(struct osmo_fsm_inst *fi)
{
return (fi->state >= V5X_LE_UP_P_S_LE20_OPERATIONAL);
}
void v5x_le_port_pstn_block(struct osmo_fsm_inst *fi)
{
LOGP(DV5PORT, LOGL_DEBUG, "Put PSTN port into blocked state\n");
fi->state = V5X_LE_UP_P_S_LE10_NOP_BLOCKED;
}
void v5x_le_port_pstn_unblock(struct osmo_fsm_inst *fi)
{
LOGP(DV5PORT, LOGL_DEBUG, "Put PSTN port into unblocked state\n");
fi->state = V5X_LE_UP_P_S_LE20_OPERATIONAL;
}
const char *v5x_le_port_pstn_state_name(struct osmo_fsm_inst *fi)
{
return v5x_le_ctrl_pstn_port_fsm_states[fi->state].name;
}
/***********************************************************************
* Messages from other layers
***********************************************************************/
/* message from DL layer */
void v5x_le_port_pstn_fe_rcv(struct v5x_user_port *v5up, uint8_t cfe)
{
struct osmo_fsm_inst *fi = v5up->port_fi;
enum v5x_le_ctrl_port_fsm_event event;
switch (cfe) {
case V5X_CTRL_FE201_UNBLOCK: // actually FE202
event = V5X_CUP_LE_FE202_UNBLOCK;
break;
case V5X_CTRL_FE203_BLOCK: // actually FE204
event = V5X_CUP_LE_FE204_BLOCK_CMD;
break;
case V5X_CTRL_FE205_BLOCK_REQ:
event = V5X_CUP_LE_FE205_BLOCK_REQ;
break;
default:
LOGP(DV5PORT, LOGL_NOTICE, "Received cfe %d not valid at this protocol\n", cfe);
}
/* send event to FSM */
osmo_fsm_inst_dispatch(fi, event, NULL);
}
/* message from DL layer */
void v5x_le_port_isdn_fe_rcv(struct v5x_user_port *v5up, uint8_t cfe, uint8_t perf_grading)
{
enum v5x_le_ctrl_port_fsm_event event;
switch (cfe) {
case V5X_CTRL_FE102_ACT_INIT_BY_USER:
event = V5X_CUP_LE_FE102_ACTIV_INIT_USER_IND;
break;
case V5X_CTRL_FE103_DS_ACTIVATED:
event = V5X_CUP_LE_FE103_DS_ACTIVATED_IND;
break;
case V5X_CTRL_FE104_ACCESS_ACTIVATED:
event = V5X_CUP_LE_FE104_ACCESS_ACTIVATED_IND;
break;
case V5X_CTRL_FE106_ACCESS_DEACTIVATED:
event = V5X_CUP_LE_FE106_ACCESS_DEACTIVATED_IND;
break;
case V5X_CTRL_FE201_UNBLOCK: // actually FE202
event = V5X_CUP_LE_FE202_UNBLOCK;
break;
case V5X_CTRL_FE203_BLOCK: // actually FE204
event = V5X_CUP_LE_FE204_BLOCK_CMD;
break;
case V5X_CTRL_FE205_BLOCK_REQ:
event = V5X_CUP_LE_FE205_BLOCK_REQ;
break;
case V5X_CTRL_FE206_PERFORMANCE_GRADING:
event = V5X_CUP_LE_FE206_PREFORMANCE_GRADING_IND;
break;
default:
LOGP(DV5PORT, LOGL_NOTICE, "Received cfe %d not valid at this protocol\n", cfe);
break;
}
/* send event to FSM */
osmo_fsm_inst_dispatch(v5up->port_fi, event, &perf_grading);
}
/* message from management layer */
void v5x_port_mph_snd(struct v5x_user_port *v5up, enum v5x_mph_prim prim)
{
struct osmo_fsm_inst *fi = v5up->port_fi;
enum v5x_le_ctrl_port_fsm_event event;
switch (prim) {
case MPH_UBR:
event = V5X_CUP_LE_MPH_UBR_UNBLOCK_REQ;
break;
case MPH_BI:
event = V5X_CUP_LE_MPH_BI_BLOCK_CMD;
break;
case MPH_AR:
event = V5X_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ;
break;
case MPH_DR:
event = V5X_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ;
break;
case MPH_DB:
event = V5X_CUP_LE_MPH_DB_BLOCK_DCHAN_REQ;
break;
case MPH_DU:
event = V5X_CUP_LE_MPH_DU_BLOCK_DCHAN_REQ;
break;
default:
LOGP(DV5PORT, LOGL_NOTICE, "Got invalid prim %d at this protocol\n", prim);
return;
}
/* send event to FSM */
osmo_fsm_inst_dispatch(fi, event, NULL);
}

18
src/v5x_le_port_fsm.h Normal file
View File

@ -0,0 +1,18 @@
void v5x_port_mph_snd(struct v5x_user_port *v5up, enum v5x_mph_prim prim);
struct osmo_fsm_inst *v5x_le_port_isdn_create(struct v5x_user_port *v5up, uint16_t nr);
struct osmo_fsm_inst *v5x_le_port_pstn_create(struct v5x_user_port *v5up, uint16_t nr);
bool v5x_le_port_isdn_is_operational(struct osmo_fsm_inst *fi);
void v5x_le_port_isdn_block(struct osmo_fsm_inst *fi);
void v5x_le_port_isdn_unblock(struct osmo_fsm_inst *fi);
bool v5x_le_port_pstn_is_operational(struct osmo_fsm_inst *fi);
void v5x_le_port_pstn_block(struct osmo_fsm_inst *fi);
void v5x_le_port_pstn_unblock(struct osmo_fsm_inst *fi);
const char *v5x_le_port_isdn_state_name(struct osmo_fsm_inst *fi);
const char *v5x_le_port_pstn_state_name(struct osmo_fsm_inst *fi);
void v5x_le_port_isdn_fe_rcv(struct v5x_user_port *v5up, uint8_t cfe, uint8_t perf_grading);
void v5x_le_port_pstn_fe_rcv(struct v5x_user_port *v5up, uint8_t cfe);
void v5x_le_port_init(void);

1602
src/v5x_le_pstn_fsm.c Normal file

File diff suppressed because it is too large Load Diff

9
src/v5x_le_pstn_fsm.h Normal file
View File

@ -0,0 +1,9 @@
struct v5x_pstn_proto *v5x_le_pstn_create(struct v5x_user_port *v5up, uint16_t nr);
void v5x_le_pstn_destroy(struct v5x_pstn_proto *pstn);
const char *v5x_le_pstn_state_name(struct v5x_pstn_proto *pstn);
const char *v5x_l1_fsm_state_name(struct v5x_l1_proto *l1);
void v5x_le_pstn_init(void);
void v5x_le_pstn_fe_snd(struct v5x_user_port *v5up, enum v5x_fe_prim prim, struct msgb *msg);
void v5x_le_pstn_mdu_snd(struct v5x_user_port *v5up, enum v5x_mgmt_prim prim);
int v5x_le_pstn_dl_rcv(struct v5x_user_port *v5up, uint8_t msg_type, const struct tlv_parsed *tp);

619
src/v5x_protocol.c Normal file
View File

@ -0,0 +1,619 @@
/* (C) 2021 by Harald Welte <laforge@gnumonks.org>
*
* 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.
*
* 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>
#include <stdint.h>
#include <errno.h>
#include <arpa/inet.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/gsm/lapd_core.h>
#include "logging.h"
#include "v5x_internal.h"
#include "v5x_protocol.h"
#include "lapv5.h"
#include "v5x_le_ctrl_fsm.h"
#include "v5x_le_port_fsm.h"
#include "v5x_le_pstn_fsm.h"
#include "v52_le_bcc_fsm.h"
#include "v52_le_pp_fsm.h"
#include "v5x_le_management.h"
const struct tlv_definition v5x_ctrl_tlv_def = {
.def = {
/* single byte: PSTN / G.964 Table 17 */
[V5X_CTRL_IEI_PULSE_NOTIFICATION] = { TLV_TYPE_SINGLE_TV, 0 },
[V5X_CTRL_IEI_LINE_INFORMATION] = { TLV_TYPE_SINGLE_TV, 0 },
[V5X_CTRL_IEI_STATE] = { TLV_TYPE_SINGLE_TV, 0 },
[V5X_CTRL_IEI_AUTONOMOUS_SIG_SEQ] = { TLV_TYPE_SINGLE_TV, 0 },
[V5X_CTRL_IEI_SEQUENCE_RESPONSE] = { TLV_TYPE_SINGLE_TV, 0 },
/* single byte: ISDN / G.964 Table 53 */
[V5X_CTRL_IEI_PERFORMANCE_GRADING] = { TLV_TYPE_SINGLE_TV, 0 },
[V5X_CTRL_IEI_REJECTION_CAUSE] = { TLV_TYPE_SINGLE_TV, 0 },
/* variable length: PSTN / G.964 Table 17 */
[V5X_CTRL_IEI_SEQUENCE_NR] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_CADENCED_RINGING] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_PULSED_SIGNAL] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_STEADY_SIGNAL] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_DIGIT_SIGNAL] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_RECOGNITION_TIME] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_ENABLE_AUTONOMOUS_ACK] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_DISABLE_AUTONOMOUS_ACK] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_CAUSE] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_RESOURCE_UNAVAILABLE] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_ENABLE_METERING] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_METERING_REPORT] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_ATTENUATION] = { TLV_TYPE_TLV, 0 },
/* variable length: ISDN / G.964 Table 53 */
[V5X_CTRL_IEI_CTRL_F_ELEMENT] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_CTRL_F_ID] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_VARIANT] = { TLV_TYPE_TLV, 0 },
[V5X_CTRL_IEI_INTERFACE_ID] = { TLV_TYPE_TLV, 0 },
/* variable length: LCP / G.965 Table FIXME */
[V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION] = { TLV_TYPE_TLV, 0 },
/* variable length: BCC */
[V52_CTRL_IEI_BCC_USER_PORT_ID] = { TLV_TYPE_TLV, 0 },
[V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID] = { TLV_TYPE_TLV, 0 },
[V52_CTRL_IEI_BCC_V5_TS_ID] = { TLV_TYPE_TLV, 0 },
[V52_CTRL_IEI_BCC_MULTI_TS_MAP] = { TLV_TYPE_TLV, 0 },
[V52_CTRL_IEI_BCC_REJECT_CAUSE] = { TLV_TYPE_TLV, 0 },
[V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE] = { TLV_TYPE_TLV, 0 },
[V52_CTRL_IEI_BCC_CONNECTION_INCOMPLETE] = { TLV_TYPE_TLV, 0 },
[V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY] = { TLV_TYPE_TLV, 0 },
/* variable-length: Protection */
[V52_CTRL_IEI_PP_SEQUENCE_NR] = { TLV_TYPE_TLV, 0 },
[V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID] = { TLV_TYPE_TLV, 0 },
[V52_CTRL_IEI_PP_REJECTION_CAUSE] = { TLV_TYPE_TLV, 0 },
[V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE] = { TLV_TYPE_TLV, 0 },
},
};
const struct value_string v5x_ctrl_msg_typ_str[] = {
{ V5X_CTRL_MSGT_ESTABLISH, "ESTABLISH" },
{ V5X_CTRL_MSGT_ESTABLISH_ACK, "ESTABLISH_ACK" },
{ V5X_CTRL_MSGT_SIGNAL, "SIGNAL" },
{ V5X_CTRL_MSGT_SIGNAL_ACK, "SIGNAL_ACK" },
{ V5X_CTRL_MSGT_DISCONNECT, "DISCONNECT" },
{ V5X_CTRL_MSGT_DISCONNECT_COMPLETE, "DISCONNECT_COMPLETE" },
{ V5X_CTRL_MSGT_STATUS_ENQUIRY, "STATUS_ENQUIRY" },
{ V5X_CTRL_MSGT_STATUS, "STATUS" },
{ V5X_CTRL_MSGT_PROTOCOL_PARAMETER, "PROTOCOL_PARAMETER" },
{ V5X_CTRL_MSGT_PORT_CTRL, "PORT_CTRL" },
{ V5X_CTRL_MSGT_PORT_CTRL_ACK, "PORT_CTRL_ACK" },
{ V5X_CTRL_MSGT_COMMON_CTRL, "COMMON_CTRL" },
{ V5X_CTRL_MSGT_COMMON_CTRL_ACK, "COMMON_CTRL_ACK" },
{ V52_CTRL_MSGT_PP_SWITCH_OVER_REQ, "SWITCH_OVER_REQ" },
{ V52_CTRL_MSGT_PP_SWITCH_OVER_COM, "SWITCH_OVER_COM" },
{ V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM, "OS_SWITCH_OVER_COM" },
{ V52_CTRL_MSGT_PP_SWITCH_OVER_ACK, "SWITCH_OVER_ACK" },
{ V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT, "SWITCH_OVER_REJECT" },
{ V52_CTRL_MSGT_PP_PROTOCOL_ERROR, "PROTOCOL_ERROR" },
{ V52_CTRL_MSGT_PP_RESET_SN_COM, "RESET_SN_COM" },
{ V52_CTRL_MSGT_PP_RESET_SN_ACK, "RESET_SN_ACK" },
{ V52_CTRL_MSGT_ALLOCATION, "ALLOCATION" },
{ V52_CTRL_MSGT_ALLOCATION_COMPLETE, "ALLOCATION_COMPLETE" },
{ V52_CTRL_MSGT_ALLOCATION_REJECT, "ALLOCATION_REJECT" },
{ V52_CTRL_MSGT_DE_ALLOCATION, "DE_ALLOCATION" },
{ V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE, "DE_ALLOCATION_COMPLETE" },
{ V52_CTRL_MSGT_DE_ALLOCATION_REJECT, "DE_ALLOCATION_REJECT" },
{ V52_CTRL_MSGT_AUDIT, "AUDIT" },
{ V52_CTRL_MSGT_AUDIT_COMPLETE, "AUDIT_COMPLETE" },
{ V52_CTRL_MSGT_AN_FAULT, "AN_FAULT" },
{ V52_CTRL_MSGT_AN_FAULT_ACK, "AN_FAULT_ACKNOWLEDGE" },
{ V52_CTRL_MSGT_PROTOCOL_ERROR, "PROTOCOL_ERROR" },
{ V52_CTRL_MSGT_LCP_LINK_CTRL, "LINK_CTRL" },
{ V52_CTRL_MSGT_LCP_LINK_CTRL_ACK, "LINK_CTRL_ACK" },
{ 0, NULL }
};
const struct value_string v5x_ctrl_iei_str[] = {
{ V5X_CTRL_IEI_PULSE_NOTIFICATION, "PULSE_NOTIFICATION" },
{ V5X_CTRL_IEI_LINE_INFORMATION, "LINE_INFORMATION" },
{ V5X_CTRL_IEI_STATE, "STATE" },
{ V5X_CTRL_IEI_AUTONOMOUS_SIG_SEQ, "AUTONOMOUS_SIG_SEQ" },
{ V5X_CTRL_IEI_SEQUENCE_RESPONSE, "SEQUENCE_RESPONSE" },
{ V5X_CTRL_IEI_PERFORMANCE_GRADING, "PERFORMANCE_GRADING" },
{ V5X_CTRL_IEI_REJECTION_CAUSE, "REJECTION_CAUSE" },
{ V5X_CTRL_IEI_SEQUENCE_NR, "SEQUENCE_NR" },
{ V5X_CTRL_IEI_CADENCED_RINGING, "CADENCED_RINGING" },
{ V5X_CTRL_IEI_PULSED_SIGNAL, "PULSED_SIGNAL" },
{ V5X_CTRL_IEI_STEADY_SIGNAL, "STEADY_SIGNAL" },
{ V5X_CTRL_IEI_DIGIT_SIGNAL, "DIGIT_SIGNAL" },
{ V5X_CTRL_IEI_RECOGNITION_TIME, "RECOGNITION_TIME" },
{ V5X_CTRL_IEI_ENABLE_AUTONOMOUS_ACK, "ENABLE_AUTONOMOUS_ACK" },
{ V5X_CTRL_IEI_DISABLE_AUTONOMOUS_ACK, "DISABLE_AUTONOMOUS_ACK" },
{ V5X_CTRL_IEI_CAUSE, "CAUSE" },
{ V5X_CTRL_IEI_RESOURCE_UNAVAILABLE, "RESOURCE_UNAVAILABLE" },
{ V5X_CTRL_IEI_ENABLE_METERING, "ENABLE_METERING" },
{ V5X_CTRL_IEI_METERING_REPORT, "METERING_REPORT" },
{ V5X_CTRL_IEI_ATTENUATION, "ATTENUATION" },
{ V5X_CTRL_IEI_CTRL_F_ELEMENT, "CTRL_F_ELEMENT" },
{ V5X_CTRL_IEI_CTRL_F_ID, "CTRL_F_ID" },
{ V5X_CTRL_IEI_VARIANT, "VARIANT" },
{ V5X_CTRL_IEI_INTERFACE_ID, "INTERFACE_ID" },
{ V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION, "LCP_LINK_CTRL_FUNCTION" },
{ V52_CTRL_IEI_BCC_USER_PORT_ID, "BCC_USER_PORT_ID" },
{ V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID, "BCC_ISDN_PORT_TS_ID" },
{ V52_CTRL_IEI_BCC_V5_TS_ID, "BCC_V5_TS_ID" },
{ V52_CTRL_IEI_BCC_MULTI_TS_MAP, "BCC_MULTI_TS_MAP" },
{ V52_CTRL_IEI_BCC_REJECT_CAUSE, "BCC_REJECT_CAUSE" },
{ V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE, "BCC_PROTOCOL_ERROR_CAUSE" },
{ V52_CTRL_IEI_BCC_CONNECTION_INCOMPLETE, "BCC_CONNECTION_INCOMPLETE" },
{ V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY, "BCC_INFO_TRANSFER_CAPABILITY" },
{ V52_CTRL_IEI_PP_SEQUENCE_NR, "PP_SEQUENCE_NR" },
{ V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID, "PP_PHYSICAL_C_CHAN_ID" },
{ V52_CTRL_IEI_PP_REJECTION_CAUSE, "PP_REJECTION_CAUSE" },
{ V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE, "PP_PROTOCOL_ERROR_CAUSE" },
{ 0, NULL }
};
const struct value_string v5x_ctrl_func_el_str[] = {
{ V5X_CTRL_FE101_ACTIVATE_ACCESS, "FE101_ACTIVATE_ACCESS" },
{ V5X_CTRL_FE102_ACT_INIT_BY_USER, "FE102_ACT_INIT_BY_USER" },
{ V5X_CTRL_FE103_DS_ACTIVATED, "FE103_DS_ACTIVATED" },
{ V5X_CTRL_FE104_ACCESS_ACTIVATED, "FE104_ACCESS_ACTIVATED" },
{ V5X_CTRL_FE105_DEACTIVATE_ACCESS, "FE105_DEACTIVATE_ACCESS" },
{ V5X_CTRL_FE106_ACCESS_DEACTIVATED, "FE106_ACCESS_DEACTIVATED" },
{ V5X_CTRL_FE201_UNBLOCK, "FE201_UNBLOCK" },
{ V5X_CTRL_FE203_BLOCK, "FE203_BLOCK" },
{ V5X_CTRL_FE205_BLOCK_REQ, "FE205_BLOCK_REQ" },
{ V5X_CTRL_FE206_PERFORMANCE_GRADING, "FE206_PERFORMANCE_GRADING" },
{ V5X_CTRL_FE207_D_CHANNEL_BLOCK, "FE207_D_CHANNEL_BLOCK" },
{ V5X_CTRL_FE208_D_CHANNEL_UNBLOCK, "FE208_D_CHANNEL_UNBLOCK" },
{ 0, NULL }
};
const struct value_string v5x_ctrl_func_id_str[] = {
{ V5X_CTRL_ID_VERIFY_RE_PROVISIONING, "VERIFY_RE_PROVISIONING" },
{ V5X_CTRL_ID_READY_FOR_RE_PROVISIONING, "READY_FOR_RE_PROVISIONING" },
{ V5X_CTRL_ID_NOT_READY_FOR_RE_PROVISIONING, "NOT_READY_FOR_RE_PROVISIONING" },
{ V5X_CTRL_ID_SWITCH_OVER_TO_NEW_VARIANT, "SWITCH_OVER_TO_NEW_VARIANT" },
{ V5X_CTRL_ID_RE_PROVISIONING_STARTED, "RE_PROVISIONING_STARTED" },
{ V5X_CTRL_ID_CANNOT_RE_PROVISION, "CANNOT_RE_PROVISION" },
{ V5X_CTRL_ID_REQUEST_VARIANT_AND_INTERFACE_ID, "REQUEST_VARIANT_AND_INTERFACE_ID" },
{ V5X_CTRL_ID_VARIANT_AND_INTERFACE_ID, "VARIANT_AND_INTERFACE_ID" },
{ V5X_CTRL_ID_BLOCKING_STARTED, "BLOCKING_STARTED" },
{ V5X_CTRL_ID_RESTART_REQUEST, "RESTART_REQUEST" },
{ V5X_CTRL_ID_RESTART_COMPLETE, "RESTART_COMPLETE" },
{ 0, NULL }
};
const struct value_string v5x_cause_type_str[] = {
{ V5X_CAUSE_T_RESP_TO_STATUS_ENQ, "Response to status enquiry" },
{ V5X_CAUSE_T_PROTOCOL_DISC_ERROR, "Protocol discriminator error" },
{ V5X_CAUSE_T_MSG_TYPE_UNRECOGNIZED, "Message type unrecognized" },
{ V5X_CAUSE_T_OUT_OF_SEQUENCE_IE, "Out of sequence information element" },
{ V5X_CAUSE_T_REPEATED_OPT_IE, "Repeated optional information element" },
{ V5X_CAUSE_T_MAND_IE_MISSING, "Mandatory information element missing" },
{ V5X_CAUSE_T_UNRECOGNIZED_IE, "Unrecognized information element" },
{ V5X_CAUSE_T_MAND_IE_CONTENT_ERROR, "Mandatory information element content error" },
{ V5X_CAUSE_T_OPT_IE_CONTENT_ERROR, "Optional information element content error" },
{ V5X_CAUSE_T_MSG_INCOMP_STATE, "Message not compatible with protocol state" },
{ V5X_CAUSE_T_REPEATED_MAND_IE, "Repeated mandatory information element" },
{ V5X_CAUSE_T_TOO_MANY_IE, "Too many information elements" },
{ V5X_CAUSE_T_REF_NR_CODING_ERROR, "Reference Number coding error" },
{ 0, NULL }
};
const struct value_string v52_bcc_reject_cause_type_str[] = {
{ V52_BCC_REJECT_CAUSE_T_UNSPECIFIED, "Unspecified" },
{ V52_BCC_REJECT_CAUSE_T_ACCESS_NET_FAUL, "Access network fault" },
{ V52_BCC_REJECT_CAUSE_T_ACCESS_NET_BLOCKED, "Access network blocked (internally)" },
{ V52_BCC_REJECT_CAUSE_T_CONN_PRESENT_PSTN,
"Connection already present at the PSTN user port to a different V5-time slot" },
{ V52_BCC_REJECT_CAUSE_T_CONN_PRESENT_V5_TS,
"Connection already present at the V5-time slot(s) to a different port or ISDN user port time slot" },
{ V52_BCC_REJECT_CAUSE_T_CONN_PRESENT_ISDN,
"Connection already present at the ISDN user port time slot(s) to a different V5-time slot(s)" },
{ V52_BCC_REJECT_CAUSE_T_USER_PORT_UNAVAILABLE, "User port unavailable (blocked)" },
{ V52_BCC_REJECT_CAUSE_T_DEALLOC_INCOMPAT_DATA,
"De-allocation cannot be completed due to incompatible data content" },
{ V52_BCC_REJECT_CAUSE_T_DEALLOC_V5_TS_DATA,
"De-allocation cannot be completed due to V5-time slot(s) data incompatibility" },
{ V52_BCC_REJECT_CAUSE_T_DEALLOC_PORT_DATA,
"De-allocation cannot be completed due to port data incompatibility" },
{ V52_BCC_REJECT_CAUSE_T_DEALLOC_USER_TS_DATA,
"De-allocation cannot be completed due to user port time slot(s) data incompatibility" },
{ V52_BCC_REJECT_CAUSE_T_USER_NOT_PROVISIONED, "User port not provisioned" },
{ V52_BCC_REJECT_CAUSE_T_INVALID_V5_TS_IDENT, "Invalid V5-time slot(s) identification(s)" },
{ V52_BCC_REJECT_CAUSE_T_INVALID_V5_LINK_IDENT, "Invalid V5 2048 kbit/s link identification" },
{ V52_BCC_REJECT_CAUSE_T_INVALID_USER_TS_IDENT, "Invalid user port time slot(s) identification(s)" },
{ V52_BCC_REJECT_CAUSE_T_V5_TS_USED_CCHANNEL, "V5-time slot(s) being used as physical C-channel(s)" },
{ V52_BCC_REJECT_CAUSE_T_V5_LINK_UNAVAILABLE, "V5-link unavailable (blocked)" },
{ 0, NULL }
};
const struct value_string v52_pp_reject_cause_type_str[] = {
{ V52_PP_REJECT_CAUSE_T_NO_STANDBY_CC_AVAILABLE,"No standby C-channel available" },
{ V52_PP_REJECT_CAUSE_T_TARGET_CC_NOT_OPER, "Target physical C-channel not operational" },
{ V52_PP_REJECT_CAUSE_T_TARGET_CC_NOT_PROV, "Target physical C-channel not provisioned" },
{ V52_PP_REJECT_CAUSE_T_PROT_SWITCHING_IMPOSS, "Protection switching impossible (AN/LE failure)" },
{ V52_PP_REJECT_CAUSE_T_PROT_GROUP_MISMATCH, "Protection group mismatch" },
{ V52_PP_REJECT_CAUSE_T_ALLOC_EXISTS_ALREADY, "Requested allocation exists already" },
{ V52_PP_REJECT_CAUSE_T_TARGET_CC_ALREADY_CC, "Target physical C-channel already has logical C-channel" },
{ 0, NULL }
};
static const uint8_t signal_mand_ies[] = { V5X_CTRL_IEI_SEQUENCE_NR };
static const uint8_t signal_ack_mand_ies[] = { V5X_CTRL_IEI_SEQUENCE_NR };
static const uint8_t status_mand_ies[] = { V5X_CTRL_IEI_STATE, V5X_CTRL_IEI_CAUSE };
static const uint8_t prot_par_mand_ies[] = { V5X_CTRL_IEI_SEQUENCE_NR };
static const uint8_t port_ctrl_mand_ies[] = { V5X_CTRL_IEI_CTRL_F_ELEMENT };
static const uint8_t common_ctrl_mand_ies[] = { V5X_CTRL_IEI_CTRL_F_ID };
static const uint8_t lcp_mand_ies[] = { V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION };
static const uint8_t alloc_mand_ies[] = { V52_CTRL_IEI_BCC_USER_PORT_ID };
static const uint8_t alloc_rej_mand_ies[] = { V52_CTRL_IEI_BCC_REJECT_CAUSE };
static const uint8_t prot_err_mand_ies[] = { V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE };
static const uint8_t pp_swo_mand_ies[] = {
V52_CTRL_IEI_PP_SEQUENCE_NR,
V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID
};
static const uint8_t pp_swo_rej_mand_ies[] = {
V52_CTRL_IEI_PP_SEQUENCE_NR,
V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID,
V52_CTRL_IEI_PP_REJECTION_CAUSE,
};
static const uint8_t pp_perr_mand_ies[] = {
V52_CTRL_IEI_PP_SEQUENCE_NR,
V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE,
};
const struct osmo_tlv_prot_def v5x_ctrl_msg_tlv = {
.name = "V5X_CTRL",
.tlv_def = &v5x_ctrl_tlv_def,
.msg_def = {
/* G.964 Section 13.3 */
[V5X_CTRL_MSGT_ESTABLISH] = MSG_DEF("ESTABLISH", NULL, 0),
[V5X_CTRL_MSGT_ESTABLISH_ACK] = MSG_DEF("ESTABLISH", NULL, 0),
[V5X_CTRL_MSGT_SIGNAL] = MSG_DEF("SIGNAL", signal_mand_ies, 0),
[V5X_CTRL_MSGT_SIGNAL_ACK] = MSG_DEF("SIGNAL_ACK", signal_ack_mand_ies, 0),
[V5X_CTRL_MSGT_STATUS] = MSG_DEF("STATUS", status_mand_ies, 0),
[V5X_CTRL_MSGT_STATUS_ENQUIRY] = MSG_DEF("STATUS_ENQUIRY", NULL, 0),
[V5X_CTRL_MSGT_DISCONNECT] = MSG_DEF("DISCONNECT", NULL, 0),
[V5X_CTRL_MSGT_DISCONNECT_COMPLETE] = MSG_DEF("DISCONNECT_COMPLETE", NULL, 0),
[V5X_CTRL_MSGT_PROTOCOL_PARAMETER] = MSG_DEF("PROTOCOL_PARAMETER", prot_par_mand_ies, 0),
/* G.964 Section 14.4 */
[V5X_CTRL_MSGT_PORT_CTRL] = MSG_DEF("PORT_CTRL", port_ctrl_mand_ies, 0),
[V5X_CTRL_MSGT_PORT_CTRL_ACK] = MSG_DEF("PORT_CTRL_ACK", port_ctrl_mand_ies, 0),
[V5X_CTRL_MSGT_COMMON_CTRL] = MSG_DEF("COMMON_CTRL", common_ctrl_mand_ies, 0),
[V5X_CTRL_MSGT_COMMON_CTRL_ACK] = MSG_DEF("COMMON_CTRL_ACK", common_ctrl_mand_ies, 0),
/* G.965 Section 16.3 LCP */
[V52_CTRL_MSGT_LCP_LINK_CTRL] = MSG_DEF("LINK_CONTROL", lcp_mand_ies, 0),
[V52_CTRL_MSGT_LCP_LINK_CTRL_ACK] = MSG_DEF("LINK_CONTROL_ACK", lcp_mand_ies, 0),
/* G.965 Section 17.3 BCC */
[V52_CTRL_MSGT_ALLOCATION] = MSG_DEF("ALLOCATION", alloc_mand_ies, 0),
[V52_CTRL_MSGT_ALLOCATION_COMPLETE] = MSG_DEF("ALLOCATION_COMPLETE", NULL, 0),
[V52_CTRL_MSGT_ALLOCATION_REJECT] = MSG_DEF("ALLOCATION_REJECT", alloc_rej_mand_ies, 0),
[V52_CTRL_MSGT_DE_ALLOCATION] = MSG_DEF("DE_ALLOCATION", alloc_mand_ies, 0),
[V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE] = MSG_DEF("DE_ALLOCATION_COMPLETE", NULL, 0),
[V52_CTRL_MSGT_DE_ALLOCATION_REJECT] = MSG_DEF("DE_ALLOCATION_REJECT", alloc_rej_mand_ies, 0),
[V52_CTRL_MSGT_AUDIT] = MSG_DEF("AUDIT", NULL, 0),
[V52_CTRL_MSGT_AUDIT_COMPLETE] = MSG_DEF("AUDIT_COMPLETE", NULL, 0),
[V52_CTRL_MSGT_AN_FAULT] = MSG_DEF("AN_FAULT", NULL, 0),
[V52_CTRL_MSGT_AN_FAULT_ACK] = MSG_DEF("AN_FAULT_ACK", NULL, 0),
[V52_CTRL_MSGT_PROTOCOL_ERROR] = MSG_DEF("PROTOCOL_ERROR", prot_err_mand_ies, 0),
/* G.965 Section 18.3 PP */
[V52_CTRL_MSGT_PP_SWITCH_OVER_REQ] = MSG_DEF("SWITCH_OVER_REQ", pp_swo_mand_ies, 0),
[V52_CTRL_MSGT_PP_SWITCH_OVER_COM] = MSG_DEF("SWITCH_OVER_COM", pp_swo_mand_ies, 0),
[V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM] = MSG_DEF("OS_SWITCH_OVER_COM", pp_swo_mand_ies, 0),
[V52_CTRL_MSGT_PP_SWITCH_OVER_ACK] = MSG_DEF("SWITCH_OVER_ACK", pp_swo_mand_ies, 0),
[V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT] = MSG_DEF("SWITCH_OVER_REJECT", pp_swo_rej_mand_ies, 0),
[V52_CTRL_MSGT_PP_PROTOCOL_ERROR] = MSG_DEF("PROTOCOL_ERROR", pp_perr_mand_ies, 0),
[V52_CTRL_MSGT_PP_RESET_SN_COM] = MSG_DEF("RESET_SN_COM", NULL, 0),
[V52_CTRL_MSGT_PP_RESET_SN_ACK] = MSG_DEF("RESET_SN_ACK", NULL, 0),
},
.ie_def = {
/* single byte, only upper nibble matches IEI */
[V5X_CTRL_IEI_PULSE_NOTIFICATION] = { 0, "PULSE_NOTIFICATION" },
[V5X_CTRL_IEI_LINE_INFORMATION] = { 0, "LINE_INFORMATION" },
[V5X_CTRL_IEI_STATE] = { 0, "STATE" },
[V5X_CTRL_IEI_AUTONOMOUS_SIG_SEQ] = { 0, "AUTONOMOUS_SIG_SEQ" },
[V5X_CTRL_IEI_SEQUENCE_RESPONSE] = { 0, "SEQUENCE_RESPONSE" },
/* single byte: ISDN */
[V5X_CTRL_IEI_PERFORMANCE_GRADING] = { 0, "PERFORMANCE_GRADING" },
[V5X_CTRL_IEI_REJECTION_CAUSE] = { 0, "REJECTION_CAUSE" },
/* variable length: PSTN */
[V5X_CTRL_IEI_SEQUENCE_NR] = { 1, "SEQUENCE_NR" },
[V5X_CTRL_IEI_CADENCED_RINGING] = { 1, "CADENCED_RINGING" },
[V5X_CTRL_IEI_PULSED_SIGNAL] = { 1, "PULSED_SIGNAL" },
[V5X_CTRL_IEI_STEADY_SIGNAL] = { 1, "STEADY_SIGNAL" },
[V5X_CTRL_IEI_DIGIT_SIGNAL] = { 1, "DIGIT_SIGNAL" },
[V5X_CTRL_IEI_RECOGNITION_TIME] = { 2, "RECOGNITION_TIME" },
[V5X_CTRL_IEI_ENABLE_AUTONOMOUS_ACK] = { 2, "ENABLE_AUTONOMOUS_ACK" },
[V5X_CTRL_IEI_DISABLE_AUTONOMOUS_ACK] = { 1, "DISABLE_AUTONOMOUS_ACK" },
[V5X_CTRL_IEI_CAUSE] = { 1, "CAUSE" },
[V5X_CTRL_IEI_RESOURCE_UNAVAILABLE] = { 1, "RESOURCE_UNAVAILABLE" },
[V5X_CTRL_IEI_ENABLE_METERING] = { 1, "ENABLE_METERING" },
[V5X_CTRL_IEI_METERING_REPORT] = { 2, "METERING_REPORT" },
[V5X_CTRL_IEI_ATTENUATION] = { 1, "ATTENUATION" },
/* variable length: ISDN */
[V5X_CTRL_IEI_CTRL_F_ELEMENT] = { 1, "CTRL_F_ELEMENT" },
[V5X_CTRL_IEI_CTRL_F_ID] = { 1, "CTRL_F_ID" },
[V5X_CTRL_IEI_VARIANT] = { 1, "VARIANT" },
[V5X_CTRL_IEI_INTERFACE_ID] = { 3, "INTERFACE_ID" },
/* variable length: LCP */
[V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION] = { 1, "LINK_CTRL_FUNCTION" },
/* variable length: BCC */
[V52_CTRL_IEI_BCC_USER_PORT_ID] = { 2, "USER_PORT_ID" },
[V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID] = { 1, "ISDN_PORT_TS_ID" },
[V52_CTRL_IEI_BCC_V5_TS_ID] = { 2, "V5_TS_ID" },
[V52_CTRL_IEI_BCC_MULTI_TS_MAP] = { 9, "MULTI_TS_MAP" },
[V52_CTRL_IEI_BCC_REJECT_CAUSE] = { 1, "REJECT_CAUSE" },
[V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE] = { 1, "PROTOCOL_ERR_CAUSE" },
[V52_CTRL_IEI_BCC_CONNECTION_INCOMPLETE]= { 1, "CONNECTION_INCOMPLETE" },
[V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY] = { 1, "INFO_TRANSFER_CAPABILITY" },
/* variable length: PP */
[V52_CTRL_IEI_PP_SEQUENCE_NR] = { 1, "SEQUENCE_NR" },
[V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID] = { 2, "PHYSICAL_C_CHAN_ID" },
[V52_CTRL_IEI_PP_REJECTION_CAUSE] = { 1, "REJECTION_CAUSE" },
[V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE] = { 1, "PROTOCOL_ERR_CAUSE" },
},
.msgt_names = v5x_ctrl_msg_typ_str,
};
/* main entry point for received V5 messages */
int v5x_dl_rcv(struct osmo_dlsap_prim *odp, uint16_t dladdr, void *rx_cbdata)
{
struct v5x_interface *v5if = rx_cbdata;
struct v5x_link *v5l = rx_cbdata;
struct msgb *msg = odp->oph.msg;
struct v5x_l3_hdr *l3h;
struct tlv_parsed tp;
uint16_t l3_addr;
bool is_isdn;
struct v5x_user_port *v5up;
int rc;
switch (odp->oph.primitive) {
case PRIM_DL_DATA:
LOGP(DV5, LOGL_DEBUG, "DL-DATA indication received (ldaddr=%d)\n", dladdr);
break;
case PRIM_DL_EST:
LOGP(DV5, LOGL_DEBUG, "DL-EST indication received (ldaddr=%d)\n", dladdr);
switch (dladdr) {
case V5X_DLADDR_CTRL:
case V5X_DLADDR_PSTN:
case V52_DLADDR_LCP:
case V52_DLADDR_BCC:
v5x_le_mdl_rcv(v5if, NULL, MDL_ESTABLISH_ind, dladdr);
break;
case V52_DLADDR_PROTECTION:
v5x_le_mdl_rcv(v5l->interface, v5l, MDL_ESTABLISH_ind, dladdr);
break;
}
goto out;
case PRIM_DL_REL:
LOGP(DV5, LOGL_DEBUG, "DL-REL indication received (ldaddr=%d)\n", dladdr);
switch (dladdr) {
case V5X_DLADDR_CTRL:
case V5X_DLADDR_PSTN:
case V52_DLADDR_LCP:
case V52_DLADDR_BCC:
v5x_le_mdl_rcv(v5if, NULL, MDL_RELEASE_ind, dladdr);
break;
case V52_DLADDR_PROTECTION:
v5x_le_mdl_rcv(v5l->interface, v5l, MDL_RELEASE_ind, dladdr);
break;
}
goto out;
default:
LOGP(DV5, LOGL_NOTICE, "Unhandled prim=%d (ldaddr=%d)\n", odp->oph.primitive, dladdr);
rc = -EINVAL;
goto out;
}
l3h = msgb_l3(msg);
if (msgb_l3len(msg) < sizeof(*l3h)) {
LOGP(DV5, LOGL_ERROR, "Received short message (%u bytes < %lu)\n", msgb_l3len(msg), sizeof(*l3h));
rc = -EINVAL;
goto out;
}
if (l3h->pdisc != V5X_CTRL_PDISC) {
LOGP(DV5, LOGL_ERROR, "Received unsupported protocol discriminator 0x%02x\n", l3h->pdisc);
rc = -EINVAL;
goto out;
}
rc = osmo_tlv_prot_parse(&v5x_ctrl_msg_tlv, &tp, 1, l3h->msg_type, msgb_l3(msg) + sizeof(*l3h),
msgb_l3len(msg) - sizeof(*l3h), 0, 0, DV5, __func__);
if (rc < 0)
goto out;
LOGP(DV5, LOGL_DEBUG, "Received message from AN with msg_type %d\n", l3h->msg_type);
switch (dladdr) {
case V5X_DLADDR_PSTN:
/* PSTN signaling protocol */
switch (l3h->msg_type) {
case V5X_CTRL_MSGT_ESTABLISH:
case V5X_CTRL_MSGT_ESTABLISH_ACK:
case V5X_CTRL_MSGT_SIGNAL:
case V5X_CTRL_MSGT_SIGNAL_ACK:
case V5X_CTRL_MSGT_DISCONNECT:
case V5X_CTRL_MSGT_DISCONNECT_COMPLETE:
case V5X_CTRL_MSGT_STATUS_ENQUIRY:
case V5X_CTRL_MSGT_STATUS:
case V5X_CTRL_MSGT_PROTOCOL_PARAMETER:
/* look-up user port based on L3 addr */
l3_addr = v5x_l3_addr_dec(ntohs(l3h->l3_addr), &is_isdn);
v5up = v5x_user_port_find(v5if, l3_addr, is_isdn);
if (!v5up) {
rc = -ENODEV;
break;
}
rc = v5x_le_pstn_dl_rcv(v5up, l3h->msg_type, &tp);
break;
default:
rc = -ENOTSUP;
LOGP(DV5, LOGL_ERROR, "Received unsupported PSTN message type %s\n",
osmo_tlv_prot_msg_name(&v5x_ctrl_msg_tlv, l3h->msg_type));
}
break;
case V5X_DLADDR_CTRL:
/* control protocol (Section 14 G.964) */
switch (l3h->msg_type) {
case V5X_CTRL_MSGT_PORT_CTRL:
case V5X_CTRL_MSGT_PORT_CTRL_ACK:
case V5X_CTRL_MSGT_COMMON_CTRL:
case V5X_CTRL_MSGT_COMMON_CTRL_ACK:
/* look-up user port based on L3 addr */
l3_addr = v5x_l3_addr_dec(ntohs(l3h->l3_addr), &is_isdn);
rc = v5x_le_ctrl_dl_rcv(v5if, l3_addr, is_isdn, l3h->msg_type, &tp);
break;
default:
rc = -ENOTSUP;
LOGP(DV5, LOGL_ERROR, "Received unsupported CTRL message type %s\n",
osmo_tlv_prot_msg_name(&v5x_ctrl_msg_tlv, l3h->msg_type));
}
break;
case V52_DLADDR_BCC:
/* protection protocol (Section 18 G.965) */
switch (l3h->msg_type) {
case V52_CTRL_MSGT_ALLOCATION:
case V52_CTRL_MSGT_ALLOCATION_COMPLETE:
case V52_CTRL_MSGT_ALLOCATION_REJECT:
case V52_CTRL_MSGT_DE_ALLOCATION:
case V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE:
case V52_CTRL_MSGT_DE_ALLOCATION_REJECT:
case V52_CTRL_MSGT_AUDIT:
case V52_CTRL_MSGT_AUDIT_COMPLETE:
case V52_CTRL_MSGT_AN_FAULT:
case V52_CTRL_MSGT_AN_FAULT_ACK:
case V52_CTRL_MSGT_PROTOCOL_ERROR:
rc = v52_le_bcc_dl_rcv(v5if, ntohs(l3h->l3_addr), l3h->msg_type, &tp);
break;
default:
rc = -ENOTSUP;
LOGP(DV5, LOGL_ERROR, "Received unsupported BCC message type %s\n",
osmo_tlv_prot_msg_name(&v5x_ctrl_msg_tlv, l3h->msg_type));
}
break;
case V52_DLADDR_PROTECTION:
/* BCC protocol (Section 17 G.965) */
switch (l3h->msg_type) {
case V52_CTRL_MSGT_PP_SWITCH_OVER_REQ:
case V52_CTRL_MSGT_PP_SWITCH_OVER_COM:
case V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM:
case V52_CTRL_MSGT_PP_SWITCH_OVER_ACK:
case V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT:
case V52_CTRL_MSGT_PP_PROTOCOL_ERROR:
case V52_CTRL_MSGT_PP_RESET_SN_COM:
case V52_CTRL_MSGT_PP_RESET_SN_ACK:
rc = v52_le_pp_dl_rcv(v5l, ntohs(l3h->l3_addr), l3h->msg_type, &tp);
break;
default:
rc = -ENOTSUP;
LOGP(DV5, LOGL_ERROR, "Received unsupported PROTECTION message type %s\n",
osmo_tlv_prot_msg_name(&v5x_ctrl_msg_tlv, l3h->msg_type));
}
break;
case V52_DLADDR_LCP:
/* Link control protocol (Section 16 G.965) */
switch (l3h->msg_type) {
case V52_CTRL_MSGT_LCP_LINK_CTRL:
case V52_CTRL_MSGT_LCP_LINK_CTRL_ACK:
rc = v5x_le_ctrl_dl_rcv(v5if, ntohs(l3h->l3_addr), 0, l3h->msg_type, &tp);
break;
default:
rc = -ENOTSUP;
LOGP(DV5, LOGL_ERROR, "Received unsupported LCP message type %s\n",
osmo_tlv_prot_msg_name(&v5x_ctrl_msg_tlv, l3h->msg_type));
}
break;
default:
rc = -ENOTSUP;
LOGP(DV5, LOGL_ERROR, "Received unsupported message type %s\n",
osmo_tlv_prot_msg_name(&v5x_ctrl_msg_tlv, l3h->msg_type));
}
out:
if (msg)
msgb_free(msg);
return rc;
}
/* send V5 messages to DL instance, given by dladdr */
int v5x_dl_snd(struct v5x_interface *v5if, uint16_t dladdr, struct msgb *msg)
{
struct lapv5_instance *li = NULL, *li2 = NULL;
switch (dladdr) {
case V5X_DLADDR_PSTN:
li = v5if->pstn.li;
break;
case V5X_DLADDR_CTRL:
li = v5if->control.li;
break;
case V52_DLADDR_BCC:
li = v5if->bcc.li;
break;
case V52_DLADDR_PROTECTION:
li = v5if->protection.li[0];
li2 = v5if->protection.li[1];
break;
case V52_DLADDR_LCP:
li = v5if->lcp.li;
break;
}
if (!li) {
LOGP(DV5, LOGL_ERROR, "No instance for dladdr %d.\n", dladdr);
msgb_free(msg);
return -EINVAL;
}
if (li2)
lapv5_dl_data_req(li2, dladdr, msgb_copy(msg, "V5 MSG clone"));
return lapv5_dl_data_req(li, dladdr, msg);
}
// FIXME
#define V5_MSGB_SIZE 128
#define V5_MSGB_HEADROOM 56
struct msgb *msgb_alloc_v5x(void)
{
struct msgb *msg;
msg = msgb_alloc_headroom(V5_MSGB_SIZE, V5_MSGB_HEADROOM, "V5 MSG");
if (!msg)
return NULL;
msg->l3h = msg->data;
return msg;
}

314
src/v5x_protocol.h Normal file
View File

@ -0,0 +1,314 @@
#pragma once
#include <stdint.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/tlv.h>
/* Definitions related to the V5.1 and V5.2 interface between AN (access
* network) and LE (local exchange) as per ITU-T G.964 + G.965.
*
* (C) 2021 by Harald Welte <laforge@gnumonks.org>
*/
/***********************************************************************/
/* protocol wire format */
/***********************************************************************/
/* Table 14/G.964 */
#define V5X_CTRL_PDISC 0x48
/* Table 1/G.965 + Table 1/G.964 */
#define V5X_DLADDR_PSTN 8176
#define V5X_DLADDR_CTRL 8177
#define V52_DLADDR_BCC 8178
#define V52_DLADDR_PROTECTION 8179
#define V52_DLADDR_LCP 8180
/* G.965 Section 13.1 */
struct v5x_l3_hdr {
uint8_t pdisc;
uint16_t l3_addr; /* with C/R and EA bits! */
uint8_t msg_type;
uint8_t payload[0];
} __attribute__ ((packed));
/* G.964 Section 14.4.2.3 Figure 33 + 34 */
static inline bool v5x_l3_addr_is_isdn(uint16_t in)
{
/* extract two bytes */
uint8_t b1 = in >> 8;
uint8_t b2 = in & 0xff;
if ((b1 & 0x03) == 0x00 && (b2 & 0x01))
return true;
return false;
}
static inline uint16_t v5x_l3_addr_dec(uint16_t in, bool *is_isdn)
{
/* extract two bytes */
uint8_t b1 = in >> 8;
uint8_t b2 = in & 0xff;
if (v5x_l3_addr_is_isdn(in)) {
/* remove EA/C/R bits */
uint8_t upper = b1 >> 2;
uint8_t lower = b2 >> 1;
if (is_isdn)
*is_isdn = true;
/* shift them together */
return lower | (upper << 7);
} else{
uint8_t upper = b1 >> 1;
uint8_t lower = b2;
if (is_isdn)
*is_isdn = false;
return lower | (upper << 8);
}
}
static inline uint16_t v5x_l3_addr_enc(uint16_t in, bool is_isdn)
{
uint8_t b1, b2;
if (is_isdn) {
uint8_t upper = in >> 7;
uint8_t lower = in & 0x7f;
b1 = upper << 2;
b2 = (lower << 1) | 0x01;
} else {
uint8_t upper = in >> 8;
uint8_t lower = in & 0xff;
b1 = (upper << 1) | 0x01;
b2 = lower;
}
return (b1 << 8) | b2;
}
/* Table 15 + 52/G.964 */
enum v5x_ctrl_msg_type {
/* 000xxxx: PSTN protocol message types */
V5X_CTRL_MSGT_ESTABLISH = 0x00,
V5X_CTRL_MSGT_ESTABLISH_ACK = 0x01,
V5X_CTRL_MSGT_SIGNAL = 0x02,
V5X_CTRL_MSGT_SIGNAL_ACK = 0x03,
V5X_CTRL_MSGT_DISCONNECT = 0x08,
V5X_CTRL_MSGT_DISCONNECT_COMPLETE = 0x09,
V5X_CTRL_MSGT_STATUS_ENQUIRY = 0x0c,
V5X_CTRL_MSGT_STATUS = 0x0d,
V5X_CTRL_MSGT_PROTOCOL_PARAMETER = 0x0e,
/* 0010xxx: Control protocol message types */
V5X_CTRL_MSGT_PORT_CTRL = 0x10,
V5X_CTRL_MSGT_PORT_CTRL_ACK = 0x11,
V5X_CTRL_MSGT_COMMON_CTRL = 0x12,
V5X_CTRL_MSGT_COMMON_CTRL_ACK = 0x13,
/* 0011xxx: Protection protocol message types */
V52_CTRL_MSGT_PP_SWITCH_OVER_REQ = 0x18,
V52_CTRL_MSGT_PP_SWITCH_OVER_COM = 0x19,
V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM = 0x1a,
V52_CTRL_MSGT_PP_SWITCH_OVER_ACK = 0x1b,
V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT = 0x1c,
V52_CTRL_MSGT_PP_PROTOCOL_ERROR = 0x1d,
V52_CTRL_MSGT_PP_RESET_SN_COM = 0x1e,
V52_CTRL_MSGT_PP_RESET_SN_ACK = 0x1f,
/* 010xxxx: BCC protocol message types */
V52_CTRL_MSGT_ALLOCATION = 0x20,
V52_CTRL_MSGT_ALLOCATION_COMPLETE = 0x21,
V52_CTRL_MSGT_ALLOCATION_REJECT = 0x22,
V52_CTRL_MSGT_DE_ALLOCATION = 0x23,
V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE = 0x24,
V52_CTRL_MSGT_DE_ALLOCATION_REJECT = 0x25,
V52_CTRL_MSGT_AUDIT = 0x26,
V52_CTRL_MSGT_AUDIT_COMPLETE = 0x27,
V52_CTRL_MSGT_AN_FAULT = 0x28,
V52_CTRL_MSGT_AN_FAULT_ACK = 0x29,
V52_CTRL_MSGT_PROTOCOL_ERROR = 0x2a,
/* 0110xxx: Link control protocol message types */
V52_CTRL_MSGT_LCP_LINK_CTRL = 0x30,
V52_CTRL_MSGT_LCP_LINK_CTRL_ACK = 0x31,
};
extern const struct value_string v5x_ctrl_msg_typ_str[];
/* Table 17 + 53/G.964 */
enum v5x_ctrl_iei {
/* single byte, only upper nibble matches IEI */
V5X_CTRL_IEI_PULSE_NOTIFICATION = 0xc0,
V5X_CTRL_IEI_LINE_INFORMATION = 0x80,
V5X_CTRL_IEI_STATE = 0x90,
V5X_CTRL_IEI_AUTONOMOUS_SIG_SEQ = 0xa0,
V5X_CTRL_IEI_SEQUENCE_RESPONSE = 0xb0,
/* single byte: ISDN */
V5X_CTRL_IEI_PERFORMANCE_GRADING = 0xe0,
V5X_CTRL_IEI_REJECTION_CAUSE = 0xf0,
/* variable length: PSTN */
V5X_CTRL_IEI_SEQUENCE_NR = 0x00,
V5X_CTRL_IEI_CADENCED_RINGING = 0x01,
V5X_CTRL_IEI_PULSED_SIGNAL = 0x02,
V5X_CTRL_IEI_STEADY_SIGNAL = 0x03,
V5X_CTRL_IEI_DIGIT_SIGNAL = 0x04,
V5X_CTRL_IEI_RECOGNITION_TIME = 0x10,
V5X_CTRL_IEI_ENABLE_AUTONOMOUS_ACK = 0x11,
V5X_CTRL_IEI_DISABLE_AUTONOMOUS_ACK = 0x12,
V5X_CTRL_IEI_CAUSE = 0x13,
V5X_CTRL_IEI_RESOURCE_UNAVAILABLE = 0x14,
V5X_CTRL_IEI_ENABLE_METERING = 0x32,
V5X_CTRL_IEI_METERING_REPORT = 0x33,
V5X_CTRL_IEI_ATTENUATION = 0x34,
/* variable length: ISDN */
V5X_CTRL_IEI_CTRL_F_ELEMENT = 0x20,
V5X_CTRL_IEI_CTRL_F_ID = 0x21,
V5X_CTRL_IEI_VARIANT = 0x22,
V5X_CTRL_IEI_INTERFACE_ID = 0x23,
/* variable length: LCP */
V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION = 0x30,
/* variable length: BCC */
V52_CTRL_IEI_BCC_USER_PORT_ID = 0x40,
V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID = 0x41,
V52_CTRL_IEI_BCC_V5_TS_ID = 0x42,
V52_CTRL_IEI_BCC_MULTI_TS_MAP = 0x43,
V52_CTRL_IEI_BCC_REJECT_CAUSE = 0x44,
V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE = 0x45,
V52_CTRL_IEI_BCC_CONNECTION_INCOMPLETE = 0x46,
V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY = 0x47,
/* variable-length: Protection */
V52_CTRL_IEI_PP_SEQUENCE_NR = 0x50,
V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID = 0x51,
V52_CTRL_IEI_PP_REJECTION_CAUSE = 0x52,
V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE = 0x53,
};
extern const struct value_string v5x_ctrl_iei_str[];
extern const struct tlv_definition v5x_ctrl_tlv_def;
extern const struct osmo_tlv_prot_def v5x_ctrl_msg_tlv;
/* 14.4.2.5.4 Table 56/G.964 - Coding of Control Function Element */
enum v5x_ctrl_func_el {
V5X_CTRL_FE101_ACTIVATE_ACCESS = 0x01,
V5X_CTRL_FE102_ACT_INIT_BY_USER = 0x02,
V5X_CTRL_FE103_DS_ACTIVATED = 0x03,
V5X_CTRL_FE104_ACCESS_ACTIVATED = 0x04,
V5X_CTRL_FE105_DEACTIVATE_ACCESS = 0x05,
V5X_CTRL_FE106_ACCESS_DEACTIVATED = 0x06,
V5X_CTRL_FE201_UNBLOCK = 0x11,
V5X_CTRL_FE203_BLOCK = 0x13,
V5X_CTRL_FE205_BLOCK_REQ = 0x15,
V5X_CTRL_FE206_PERFORMANCE_GRADING = 0x16,
V5X_CTRL_FE207_D_CHANNEL_BLOCK = 0x17,
V5X_CTRL_FE208_D_CHANNEL_UNBLOCK = 0x18,
};
extern const struct value_string v5x_ctrl_func_el_str[];
enum v5x_ctrl_func_id {
/* 14.4.2.5.5 Table 57/G.964 - Coding of Control Function ID */
V5X_CTRL_ID_VERIFY_RE_PROVISIONING = 0x00,
V5X_CTRL_ID_READY_FOR_RE_PROVISIONING = 0x01,
V5X_CTRL_ID_NOT_READY_FOR_RE_PROVISIONING = 0x02,
V5X_CTRL_ID_SWITCH_OVER_TO_NEW_VARIANT = 0x03,
V5X_CTRL_ID_RE_PROVISIONING_STARTED = 0x04,
V5X_CTRL_ID_CANNOT_RE_PROVISION = 0x05,
V5X_CTRL_ID_REQUEST_VARIANT_AND_INTERFACE_ID = 0x06,
V5X_CTRL_ID_VARIANT_AND_INTERFACE_ID = 0x07,
V5X_CTRL_ID_BLOCKING_STARTED = 0x08,
V5X_CTRL_ID_RESTART_REQUEST = 0x10,
V5X_CTRL_ID_RESTART_COMPLETE = 0x11,
/* 15.4.2 Table 9a/G.965 - Coding of Control Function ID */
V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_REQUEST = 0x12,
V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_ACCEPTED = 0x13,
V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_REJECTED = 0x14,
V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_COMPLETED = 0x15,
V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_REQUEST = 0x16,
V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ACCEPTED = 0x17,
V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_REJECTED = 0x18,
V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_COMPLETED = 0x19,
V5X_CTRL_ID_UNBLK_ALL_REL_ISDN_REQUEST = 0x1a,
V5X_CTRL_ID_UNBLK_ALL_REL_ISDN_ACCEPTED = 0x1b,
V5X_CTRL_ID_UNBLK_ALL_REL_ISDN_REJECTED = 0x1c,
V5X_CTRL_ID_UNBLK_ALL_REL_ISDN_COMPLETED = 0x1d,
V5X_CTRL_ID_BLK_ALL_PSTN_REQUEST = 0x1e,
V5X_CTRL_ID_BLK_ALL_PSTN_ACCEPTED = 0x1f,
V5X_CTRL_ID_BLK_ALL_PSTN_REJECTED = 0x20,
V5X_CTRL_ID_BLK_ALL_PSTN_COMPLETED = 0x21,
V5X_CTRL_ID_BLK_ALL_ISDN_REQUEST = 0x22,
V5X_CTRL_ID_BLK_ALL_ISDN_ACCEPTED = 0x23,
V5X_CTRL_ID_BLK_ALL_ISDN_REJECTED = 0x24,
V5X_CTRL_ID_BLK_ALL_ISDN_COMPLETED = 0x25,
};
extern const struct value_string v5x_ctrl_func_id_str[];
/* 13.4.7.9 Cause / 17.4.2.6 Table 43/G.965 / 18.5.5 Table 62/G.965 */
enum v5x_ctrl_cause_type {
V5X_CAUSE_T_RESP_TO_STATUS_ENQ = 0x00,
V5X_CAUSE_T_PROTOCOL_DISC_ERROR = 0x01,
V5X_CAUSE_T_L3_ADDRESS_ERROR = 0x03,
V5X_CAUSE_T_MSG_TYPE_UNRECOGNIZED = 0x04,
V5X_CAUSE_T_OUT_OF_SEQUENCE_IE = 0x05,
V5X_CAUSE_T_REPEATED_OPT_IE = 0x06,
V5X_CAUSE_T_MAND_IE_MISSING = 0x07,
V5X_CAUSE_T_UNRECOGNIZED_IE = 0x08,
V5X_CAUSE_T_MAND_IE_CONTENT_ERROR = 0x09,
V5X_CAUSE_T_OPT_IE_CONTENT_ERROR = 0x0a,
V5X_CAUSE_T_MSG_INCOMP_STATE = 0x0b,
V5X_CAUSE_T_REPEATED_MAND_IE = 0x0c,
V5X_CAUSE_T_TOO_MANY_IE = 0x0d,
V5X_CAUSE_T_REF_NR_CODING_ERROR = 0x0f,
};
/* 17.4.2.5 Table 41/G.965 - Reject cause type */
enum v52_bcc_reject_cause_type {
V52_BCC_REJECT_CAUSE_T_UNSPECIFIED = 0x00,
V52_BCC_REJECT_CAUSE_T_ACCESS_NET_FAUL = 0x01,
V52_BCC_REJECT_CAUSE_T_ACCESS_NET_BLOCKED = 0x02,
V52_BCC_REJECT_CAUSE_T_CONN_PRESENT_PSTN = 0x03,
V52_BCC_REJECT_CAUSE_T_CONN_PRESENT_V5_TS = 0x04,
V52_BCC_REJECT_CAUSE_T_CONN_PRESENT_ISDN = 0x05,
V52_BCC_REJECT_CAUSE_T_USER_PORT_UNAVAILABLE = 0x06,
V52_BCC_REJECT_CAUSE_T_DEALLOC_INCOMPAT_DATA = 0x07,
V52_BCC_REJECT_CAUSE_T_DEALLOC_V5_TS_DATA = 0x08,
V52_BCC_REJECT_CAUSE_T_DEALLOC_PORT_DATA = 0x09,
V52_BCC_REJECT_CAUSE_T_DEALLOC_USER_TS_DATA = 0x0a,
V52_BCC_REJECT_CAUSE_T_USER_NOT_PROVISIONED = 0x0b,
V52_BCC_REJECT_CAUSE_T_INVALID_V5_TS_IDENT = 0x0c,
V52_BCC_REJECT_CAUSE_T_INVALID_V5_LINK_IDENT = 0x0d,
V52_BCC_REJECT_CAUSE_T_INVALID_USER_TS_IDENT = 0x0e,
V52_BCC_REJECT_CAUSE_T_V5_TS_USED_CCHANNEL = 0x0f,
V52_BCC_REJECT_CAUSE_T_V5_LINK_UNAVAILABLE = 0x10,
};
/* 18.5.4 Table 61/G.965 - Reject cause type */
enum v52_pp_reject_cause_type {
V52_PP_REJECT_CAUSE_T_NO_STANDBY_CC_AVAILABLE = 0x00,
V52_PP_REJECT_CAUSE_T_TARGET_CC_NOT_OPER = 0x01,
V52_PP_REJECT_CAUSE_T_TARGET_CC_NOT_PROV = 0x02,
V52_PP_REJECT_CAUSE_T_PROT_SWITCHING_IMPOSS = 0x03,
V52_PP_REJECT_CAUSE_T_PROT_GROUP_MISMATCH = 0x04,
V52_PP_REJECT_CAUSE_T_ALLOC_EXISTS_ALREADY = 0x05,
V52_PP_REJECT_CAUSE_T_TARGET_CC_ALREADY_CC = 0x06,
};
extern const struct value_string v5x_cause_type_str[];
extern const struct value_string v52_bcc_reject_cause_type_str[];
extern const struct value_string v52_pp_reject_cause_type_str[];
//extern const struct osmo_tlv_prot_def v51_ctrl_msg_tlv;
/* 16.3.2.2 Table 22/G.965 */
enum v52_link_ctrl_func {
V52_LCP_FE_IDReq = 0,
V52_LCP_FE_IDAck = 1,
V52_LCP_FE_IDRel = 2,
V52_LCP_FE_IDRej = 3,
V52_LCP_FE_301_302_LINK_UNBLOCK = 4,
V52_LCP_FE_303_304_LINK_BLOCK = 5,
V52_LCP_FE_305_DEF_LINK_BLOCK_REQ = 6,
V52_LCP_FE_306_NON_DEF_LINK_BLOCK_REQ = 7,
};
int v5x_dl_rcv(struct osmo_dlsap_prim *odp, uint16_t dladdr, void *rx_cbdata);
int v5x_dl_snd(struct v5x_interface *v5if, uint16_t dladdr, struct msgb *msg);
struct msgb *msgb_alloc_v5x(void);

13
tests/Makefile.am Normal file
View File

@ -0,0 +1,13 @@
AM_CPPFLAGS = $(all_includes)
AM_CFLAGS= -Wall -Wextra -g $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS)
AM_LDFLAGS = $(COVERAGE_LDFLAGS)
COMMONLIBS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS)
noinst_PROGRAMS = answer_detect
answer_detect_SOURCES = answer_detect.c
answer_detect_LDADD = $(COMMON_LA) \
$(COMMONLIBS) \
../src/libecho/libecho.a \
-lm

179
tests/answer_detect.c Normal file
View File

@ -0,0 +1,179 @@
/* Testing the answer tone algorithm of libecho
*
* (C) 2023 by Andreas Eversberg <andreas@eversberg.eu>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <math.h>
#include "../src/libecho/answertone.h"
#define db2level(db) pow(10, (double)(db) / 20.0)
#define ANS_dB -13.0
#define ANS_DURATION 4
#define ANS_PHASE_REV 0.450
#define SAMPLERATE 8000
#define BANDWIDTH 100.0
float tone[SAMPLERATE * ANS_DURATION];
int16_t tone_int[SAMPLERATE * ANS_DURATION];
static void gen_ans(float frequency, float level)
{
double phase = 0.0, rev = 0.0;
int i;
for (i = 0; i < SAMPLERATE * ANS_DURATION; i++) {
/* given tone */
tone[i] = sin(2.0 * M_PI * frequency * (double)i / (double)SAMPLERATE + phase);
/* 15 Hz amplitude modulation */
tone[i] *= 1.0 + sin(2.0 * M_PI * 15.0 * (double)i / (double)SAMPLERATE) * 0.2;
/* given level */
tone[i] *= level;
tone_int[i] = tone[i] * 32767.0;
/* apply phase reversal */
rev += 1.0 / (double)SAMPLERATE;
if (rev >= ANS_PHASE_REV) {
rev -= ANS_PHASE_REV;
phase += M_PI;
}
}
}
static enum at_fail perform_test(struct answer_tone *at, float frequency, float level, float *phase1_p, float *phase2_p)
{
enum at_fail fail;
int offset = 0;
float _frequency;
gen_ans(frequency, level);
answertone_init(at, SAMPLERATE);
memcpy(at->buffer, tone + offset, sizeof(*at->buffer) * at->buffer_size);
offset += at->buffer_size;
answertone_check(at, phase1_p, &_frequency);
memcpy(at->buffer, tone + offset, sizeof(*at->buffer) * at->buffer_size);
offset += at->buffer_size;
fail = answertone_check(at, phase2_p, &_frequency);
switch (fail) {
case AT_FAIL_MIN_LEVEL:
printf("failed, level too low\n");
break;
case AT_FAIL_MAX_NOISE:
printf("failed, too noisy\n");
break;
case AT_FAIL_MAX_FREQUENCY:
printf("failed, frequency too much off\n");
break;
case AT_FAIL_NONE:
printf("ok\n");
}
return fail;
}
static void _assert(bool what)
{
if (what) {
printf("The result of this test was as expected.\n\n");
return;
}
printf("*\nThe result of this test was not as expected, please FIX!.\n*\n");
exit(-1);
}
int main(void)
{
struct answer_tone answer_tone, *at = &answer_tone;
int offset;
enum at_fail fail;
float phase1, phase2, _frequency;
int rc;
printf("Test chunk with perfect tone: ");
fail = perform_test(at, 2100, db2level(ANS_dB), &phase1, &phase2);
_assert(!fail);
printf("Check phase: %.3f == %.3f\n", phase1, phase2);
_assert(fabsf(phase1 - phase2) < 0.001);
printf("Test chunk with 2101 Hz tone: ");
fail = perform_test(at, 2101, db2level(ANS_dB), &phase1, &phase2);
_assert(!fail);
printf("Check phase: %.3f != %.3f\n", phase1, phase2);
_assert(fabsf(phase1 - phase2) > 0.001);
printf("Test chunk with 2121 Hz tone: ");
fail = perform_test(at, 2121, db2level(ANS_dB), &phase1, &phase2);
_assert(!fail);
printf("Test chunk with -26 dBm0 tone: ");
fail = perform_test(at, 2100, db2level(-26), &phase1, &phase2);
_assert(!fail);
printf("Test chunk with 2123 Hz tone: ");
fail = perform_test(at, 2123, db2level(ANS_dB), &phase1, &phase2);
_assert(fail);
printf("Test chunk with -28 dBm0 tone: ");
fail = perform_test(at, 2100, db2level(-28), &phase1, &phase2);
_assert(fail);
printf("Test chunk with long sequence of the tone: ");
gen_ans(2100+10, db2level(ANS_dB-8));
answertone_init(at, SAMPLERATE);
for (offset = 0; offset <= (double)SAMPLERATE * ANS_PHASE_REV - at->buffer_size; offset += at->buffer_size) {
memcpy(at->buffer, tone + offset, sizeof(*at->buffer) * at->buffer_size);
fail = answertone_check(at, &phase1, &_frequency);
switch (fail) {
case AT_FAIL_MIN_LEVEL:
printf("level too low\n");
_assert(false);
break;
case AT_FAIL_MAX_NOISE:
printf("too noisy\n");
_assert(false);
break;
case AT_FAIL_MAX_FREQUENCY:
if (offset == 0)
break;
printf("frequency too much off\n");
_assert(false);
break;
case AT_FAIL_NONE:
;
}
}
printf("ok\n");
_assert(true);
printf("Test complete process with long sequence of the tone: ");
gen_ans(2100+10, db2level(ANS_dB-8));
answertone_init(at, SAMPLERATE);
rc = answertone_process(at, tone_int, SAMPLERATE * ANS_DURATION, true);
if (rc > 0)
printf("tone detected\n");
else
printf("no tone detected\n");
answertone_exit(at);
_assert(rc > 0);
return 0;
}

View File

@ -1,35 +0,0 @@
/* ITU-T G.964 Section 14.3.3 V5.1-intterface Layer 1 FSM - AN and LE */
/* (C) 2021 by Harald Welte <laforge@gnumonks.org>
*
* 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.
*
* 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 "v5x_internal.h"
enum v51_l1_fsm_state {
V51_L1FSM_S_LE1_NORMAL,
V51_L1FSM_S_LE2_LOCALLY_DET_FAIL,
V51_L1FSM_S_LE3_REMOTELY_DET_FAIL,
V51_L1FSM_S_LE4_INTERNAL_FAIL,
};
/* TODO */

View File

@ -1,371 +0,0 @@
/* (C) 2021 by Harald Welte <laforge@gnumonks.org>
*
* 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.
*
* 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 <unitstd.h>
#include <stdint.h>
#include <errno.h>
#include <osmocom/core/util.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/tlv.h>
#include "v5x_internal.h"
/* PSTN Protocol FSM */
enum v51_le_ctrl_port_fsm_state {
V51_PSTN_PROT_S_OUT_OF_SERVICE, /* LE0 */
V51_PSTN_PROT_S_NULL /* LE1 */
V51_PSTN_PROT_S_PATH_INIT_BY_LE /* LE2 */
V51_PSTN_PROT_S_PATH_INIT_BY_AN /* LE3 */
V51_PSTN_PROT_S_PATH_ACTIVE /* LE4 */
V51_PSTN_PROT_S_PATH_DISC_REQ /* LE5 */
V51_PSTN_PROT_S_PORT_BLOCKED /* LE6 */
};
/***********************************************************************
* user port CTRL protocol FSM / G.964 Section 14
***********************************************************************/
enum v51_ctrl_port_fsm_state {
V51_CTRL_PORT_ST_OUT_OF_SERVICE,
V51_CTRL_PORT_ST_IN_SERVICE,
V51_CTRL_PORT_AWAIT_PORT_ACK,
};
enum v51_ctrl_port_fsm_event {
V51_CTRL_PE_MDU_START_TRAFFIC,
V51_CTRL_PE_MDU_STOP_TRAFFIC,
V51_CTRL_PE_MDU_CTRL,
V51_CTRL_PE_RX_PORT_CONTROL,
V51_CTRL_PE_RX_PORT_CONTROL_ACK,
};
static const struct value_string v51_ctrl_port_fsm_event_names[] = {
{ V51_CTRL_PE_MDU_START_TRAFFIC, "MDU-start_traffic" },
{ V51_CTRL_PE_MDU_STOP_TRAFFIC, "MDU-stop_traffic" },
{ V51_CTRL_PE_MDU_CTRL, "MDU-CTRL" },
{ V51_CTRL_PE_RX_PORT_CONTROL, "PORT_CONTROL" },
{ V51_CTRL_PE_RX_PORT_CONTROL_ACK, "PORT_CONTROL_ACK" },
{ 0, NULL }
};
static int v51_ctrl_port_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
if () {
/* first expiry: repeat PORT_CONTROL; re-start T01 */
} else {
/* second expiry: send MDU-error_ind; go to IN_SERVICE */
osmo_fsm_inst_state_chg(fi, V51_CTRL_PORT_ST_IN_SERVICE);
}
}
static const struct osmo_fsm_state v51_ctrl_port_fsm_states[] = {
[V51_CTRL_PORT_ST_OUT_OF_SERVICE] = {
.name = "OUT_OF_SERVICE",
.in_event_mask = S(V51_CTRL_PE_MDU_START_TRAFFIC) |
S(V51_CTRL_PE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_PE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_PE_RX_PORT_CONTROL) |
S(V51_CTRL_PE_RX_PORT_CONTROL_ACK),
.out_state_mask = S(V51_CTRL_PORT_ST_IN_SERVICE),
},
[V51_CTRL_PORT_ST_IN_SERVICE] = {
.name = "IN_SERVICE",
.in_event_mask = S(V51_CTRL_PE_MDU_START_TRAFFIC) |
S(V51_CTRL_PE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_PE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_PE_RX_PORT_CONTROL),
.out_state_mask = S(V51_CTRL_PORT_ST_OUT_OF_SERVICE) |
S(V51_CTRL_PORT_AWAIT_PORT_ACK),
},
[V51_CTRL_PORT_AWAIT_PORT_ACK] = {
.name = "AWAIT_PORT_ACK",
.in_event_mask = S(V51_CTRL_PE_MDU_START_TRAFFIC) |
S(V51_CTRL_PE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_PE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_PE_RX_PORT_CONTROL) |
S(V51_CTRL_PE_RX_PORT_CONTROL_ACK),
.out_state_mask = S(V51_CTRL_PORT_ST_OUT_OF_SERVICE) |
S(V51_CTRL_PORT_ST_IN_SERVICE),
},
};
static struct osmo_fsm v51_ctrl_port_fsm = {
.name = "V51_CTRL_PORT",
.states = v51_ctrl_port_fsm_states,
.num_states = ARRAY_SIZE(v51_ctrl_port_fsm_states),
.allstate_event_mask = 0,
.allstate_action = NULL,
.cleanup = NULL,
.timer_cb = v51_ctrl_port_fsm_timer_cb,
.log_subsys = DV5CTRL,
.event_names = v51_ctrl_port_fsm_event_names,
};
/***********************************************************************
* common CTRL protocol FSM / G.964 Section 14
***********************************************************************/
enum v51_ctrl_common_fsm_state {
V51_CTRL_COM_ST_OUT_OF_SERVICE,
V51_CTRL_COM_ST_IN_SERVICE,
V51_CTRL_COM_AWAIT_COMMON_ACK,
};
enum v51_ctrl_common_fsm_event {
V51_CTRL_CE_MDU_START_TRAFFIC,
V51_CTRL_CE_MDU_STOP_TRAFFIC,
V51_CTRL_CE_MDU_CTRL,
V51_CTRL_CE_RX_COMMON_CONTROL,
V51_CTRL_CE_RX_COMMON_CONTROL_ACK,
};
static const struct value_string v51_ctrl_common_fsm_event_names[] = {
{ V51_CTRL_CE_MDU_START_TRAFFIC, "MDU-start_traffic" },
{ V51_CTRL_CE_MDU_STOP_TRAFFIC, "MDU-stop_traffic" },
{ V51_CTRL_CE_MDU_CTRL, "MDU-CTRL" },
{ V51_CTRL_CE_RX_COMMON_CONTROL, "COMMON_CONTROL" },
{ V51_CTRL_CE_RX_COMMON_CONTROL_ACK, "COMMON_CONTROL_ACK" },
{ 0, NULL }
};
static int v51_ctrl_common_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
if () {
/* first expiry: repeat COMMON_CONTROL; re-start T02 */
} else {
/* second expiry: send MDU-error_ind; go to IN_SERVICE */
osmo_fsm_inst_state_chg(fi, V51_CTRL_COM_ST_IN_SERVICE);
}
}
static const struct osmo_fsm_state v51_ctrl_common_fsm_states[] = {
[V51_CTRL_COM_ST_OUT_OF_SERVICE] = {
.name = "OUT_OF_SERVICE",
.in_event_mask = S(V51_CTRL_CE_MDU_START_TRAFFIC) |
S(V51_CTRL_CE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_CE_MDU_CTRL) |
S(V51_CTRL_CE_RX_COMMON_CONTROL) |
S(V51_CTRL_CE_RX_COMMON_CONTROL_ACK),
.out_state_mask = S(V51_CTRL_COM_ST_IN_SERVICE),
},
[V51_CTRL_COM_ST_IN_SERVICE] = {
.name = "IN_SERVICE",
.in_event_mask = S(V51_CTRL_CE_MDU_START_TRAFFIC) |
S(V51_CTRL_CE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_CE_MDU_CTRL) |
S(V51_CTRL_CE_RX_COMMON_CONTROL),
.out_state_mask = S(V51_CTRL_COM_ST_OUT_OF_SERVICE) |
S(V51_CTRL_COM_AWAIT_COMMON_ACK),
},
[V51_CTRL_COM_AWAIT_COMMON_ACK] = {
.name = "AWAIT_COMMON_ACK",
.in_event_mask = S(V51_CTRL_CE_MDU_START_TRAFFIC) |
S(V51_CTRL_CE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_CE_MDU_CTRL) |
S(V51_CTRL_CE_RX_COMMON_CONTROL) |
S(V51_CTRL_CE_RX_COMMON_CONTROL_ACK),
.out_state_mask = S(V51_CTRL_COM_ST_OUT_OF_SERVICE) |
S(V51_CTRL_COM_ST_IN_SERVICE),
},
};
static struct osmo_fsm v51_ctrl_common_fsm = {
.name = "V51_CTRL_COMMON",
.states = v51_ctrl_common_fsm_states,
.num_states = ARRAY_SIZE(v51_ctrl_common_fsm_states),
.allstate_event_mask = 0,
.allstate_action = NULL,
.cleanup = NULL,
.timer_cb = v51_ctrl_common_fsm_timer_cb,
.log_subsys = DV5CTRL,
.event_names = v51_ctrl_common_fsm_event_names,
};
/***********************************************************************
* V5 Message encoding / sending
***********************************************************************/
/* G.964 Section 14.4.1.1 / Table 48 */
struct mgsb *v51_enc_ctrl_port(struct v5x_user_port *v5up, enum v51_ctrl_func_el cfe)
{
uint8_t cfe_ie = cfe;
struct v51_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V51_CTRL_PDISC;
l3h->l3_addr = v51_l3_addr_enc(v5up->nr, true);
l3h->msg_type = V51_CTRL_MSGT_PORT_CTRL;
msgb_tlv_put(msg, V51_CTRL_IEI_CTRL_F_ELEMENT, 1, &cfe_ie);
return msg;
}
/* G.964 Section 14.4.1.2 / Table 49 */
static struct msgb *v51_enc_ctrl_port_ack(struct v5x_user_port *v5up, enum v51_ctrl_func_el cfe)
{
uint8_t cfe_ie = cfe;
struct v51_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V51_CTRL_PDISC;
l3h->l3_addr = v51_l3_addr_enc(v5up->nr, true);
l3h->msg_type = V51_CTRL_MSGT_PORT_CTRL_ACK;
msgb_tlv_put(msg, V51_CTRL_IEI_CTRL_F_ELEMENT, 1, &cfe_ie);
return msg;
}
/* G.964 Section 14.4.1.3 / Table 50 */
static struct msgb *v51_enc_ctrl_common(struct v5x_instance *v5i, enum v51_ctrl_func_id cfi,
uint8_t *rej_cause, uint8_t *variant, uint32_t *interface_id)
{
uint8_t cfi_ie = cfi;
struct v51_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V51_CTRL_PDISC;
l3h->l3_addr = v51_l3_addr_enc(V51_DLADDR_CTRL, true);
l3h->msg_type = V51_CTRL_MSGT_COMMON_CTRL;
msgb_tlv_put(msg, V51_CTRL_IEI_CTRL_F_ID, 1, &cfi_ie);
if (variant) {
/* Conditional: Variant */
msgb_tlv_put(msg, V51_CTRL_IEI_VARIANT, 1, variant);
}
if (rej_cause) {
/* Conditional: Rejection Cause */
msgb_put_u8(0xF0 | (*rej_cause & 0x0F));
}
if (interface_id) {
/* Conditional: Interface Id */
uint8_t iid_ie[3];
osmo_store32be_ext(*interface_id, iid_ie, 3);
msgb_tlv_put(msg, V51_CTRL_IEI_INTERFACE_ID, sizeof(iid_ie), iid_ie);
}
return msg;
}
/* G.964 Section 14.4.1.4 / Table 51 */
static struct msgb *v51_enc_ctrl_common_ack(struct v5x_instance *v5i, enum v51_ctrl_func_id cfi)
{
uint8_t cfi_ie = cfi;
struct v51_l3_hdr *l3h;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V51_CTRL_PDISC;
l3h->l3_addr = v51_l3_addr_enc(V51_DLADDR_CTRL, true);
l3h->msg_type = V51_CTRL_MSGT_COMMON_CTRL_ACK;
msgb_tlv_put(msg, V51_CTRL_IEI_CTRL_F_ID, 1, &cfi_ie);
return msg;
}
/***********************************************************************
* V5 Message receiving / decoding
***********************************************************************/
static int v51_rcv_ctrl_port(struct v5x_user_port *v5up, struct msgb *msg, const struct tlv_parsed *tp)
{
uint16_t l3_addr;
enum v51_ctrl_func_el cfe = *TLVP_VAL(tp, V51_CTRL_IEI_CTRL_F_ELEMENT);
struct v5x_user_port *port = FIXME;
switch (l3h->msg_type) {
case V51_CTRL_MSGT_PORT_CTRL:
/* FIXME: send event to FSM */
osmo_fsm_inst_dispatch(port->ctrl_fi, V51_CTRL_PE_RX_PORT_CONTROL, tp);
/* send ACK to AN */
return v51_tx(v5up->inst, v51_enc_ctrl_port_ack(v5up, cfe));
case V51_CTRL_MSGT_PORT_CTRL_ACK:
osmo_fsm_inst_dispatch(port->ctrl_fi, V51_CTRL_PE_RX_PORT_CONTROL_ACK, tp);
default:
return -EINVAL;
}
}
static int v51_rcv_ctrl_common(struct v5x_instance *v5i, struct msgb *msg, const struct tlv_parsed *tp)
{
uint16_t l3_addr;
enum v51_ctrl_func_id cfi = *TLVP_VAL(tp, V51_CTRL_IEI_CTRL_F_ID);
switch (l3h->msg_type) {
case V51_CTRL_MSGT_COMMON_CTRL:
/* send event to FSM */
osmo_fsm_inst_dispatch(v5i->ctrl_fi, V51_CTRL_CE_RX_COMMON_CONTROL, tp);
/* send ACK to AN */
return v51_tx(v5up->inst, v51_enc_ctrl_common_ack(v5i, cfi));
case V51_CTRL_MSGT_COMMON_CTRL_ACK:
/* send event to FSM */
osmo_fsm_inst_dispatch(v5i->ctrl_fi, V51_CTRL_CE_RX_COMMON_CONTROL_ACK, tp);
break;
default:
return -EINVAL;
}
}
static int v51_rcv_ctrl(struct v5x_instance *v5i, struct msgb *msg, const struct tlv_parsed *tp)
{
struct v51_l3_hdr *l3h = msgb_l3(msg);
uint16_t l3_addr;
bool is_isdn;
int rc;
l3_addr = v51_l3_addr_dec(l3h->l3_addr, &is_isdn);
if (!is_isdn)
return -EINVAL;
switch (l3h->msg_type) {
case V51_CTRL_MSGT_PORT_CTRL:
case V51_CTRL_MSGT_PORT_CTRL_ACK:
v5up = v5x_user_port_find(v5i, l4_addr);
if (!v5up)
return -ENODEV;
return v5x_rcv_ctrl_port(v5up, msg, tp);
case V51_CTRL_MSGT_COMMON_CTRL:
case V51_CTRL_MSGT_COMMON_CTRL_ACK:
if (l3_addr != V51_DLADDR_CTRL)
return -EINVAL;
return v5x_rcv_ctrl_common(v5i, msg, tp);
}
return rc;
}

View File

@ -1,612 +0,0 @@
/* ITu-T G.965 Section 16.2 V5.2-interface Link control FSM - LE side */
#include "v5x_internal.h"
/* 16.2.4.2.2 */
enum v52_lcp_fsm_state {
V52_LCPFSM_S_LE01_NOP_LINK_FAILURE,
V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED,
V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED,
V52_LCPFSM_S_LE11_NOP_LOCAL_LINK_UNBLOCK,
V52_LCPFSM_S_LE12_NOP_REMOTE_LINK_UNBLOCK,
V52_LCPFSM_S_LE20_OP_OPERATIONAL,
V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID,
V52_LCPFSM_S_LE22_OP_LOCAL_LINK_ID,
};
enum v52_lcp_event {
V52_LCPFSM_E_MPH_AI, /* Activate Indication (L1 link operational) */
V52_LCPFSM_E_MPH_DI, /* Deactivate Indication (L1 link not operational) */
V52_LCPFSM_E_MDU_IDReq, /* Identification Request */
V52_LCPFSM_E_FE_IDAck,
V52_LCPFSM_E_MPH_IDI, /* Identification Indication */
V52_LCPFSM_E_MPH_EIg, /* Identification Failure */
V52_LCPFSM_E_FE_IDReq,
V52_LCPFSM_E_MDU_IDAck, /* Send Link ID ACK */
V52_LCPFSM_E_FE_IDRel,
V52_LCPFSM_E_MDU_IDRej, /* Link ID Reject */
V52_LCPFSM_E_FE_IDRej,
V52_LCPFSM_E_MDU_LUBR, /* Link Unblock Request */
V52_LCPFSM_E_MDU_LBI, /* Link Block Indication */
V52_LCPFSM_E_FE302, /* AN Initiated unblocking */
V52_LCPFSM_E_FE304, /* AN initiated link block */
V52_LCPFSM_E_FE305, /* deferred link block request */
V52_LCPFSM_E_FE306, /* non-deferred link block request */
};
static struct msgb *v51_enc_link_control(uint8_t link_id, enum v52_link_ctrl_func lcf)
{
struct v51_l3_hdr *l3h;
uint8_t lcf_enc = lcf;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V51_LCP_PDISC;
l3h->l3_addr = link_id
l3h->msg_type = V52_CTRL_MSGT_LCP_LINK_CTRL;
msgb_tlv_put(msg, V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION, 1, &lcf_enc);
return msg;
}
static struct msgb *v51_enc_link_control_ack(uint8_t link_id, enum v52_link_ctrl_func lcf)
{
struct v51_l3_hdr *l3h;
uint8_t lcf_enc = lcf;
struct msgb *msg = msgb_alloc_v5x();
if (!msg)
return NULL;
l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h));
l3h->pdisc = V51_LCP_PDISC;
l3h->l3_addr = link_id;
l3h->msg_type = V52_CTRL_MSGT_LCP_LINK_CTRL_ACK;
msgb_tlv_put(msg, V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION, 1, &lcf_enc);
return msg;
}
static void lcp_fsm_le01_link_failure(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* TODO: Send MDU-LAI */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
break;
case V52_LCPFSM_E_MPH_DI:
/* ignore */
break;
case V52_LCPFSM_E_MDU_IDReq:
/* TODO: Send MDU-DI */
break;
case V52_LCPFSM_E_FE_IDReq:
/* TODO: Send FE303 */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_FE_IDRel:
case V52_LCPFSM_E_FE_IDRej:
/* ignore */
break;
case V52_LCPFSM_E_MDU_LUBR:
/* TODO: Send MDU-DI */
/* TODO: Send FE303 */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_MDU_LBI:
case V52_LCPFSM_E_FE302:
case V52_LCPFSM_E_FE305:
case V52_LCPFSM_E_FE306:
/* TODO: Send FE303 */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_FE304:
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le02_link_failure_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* TODO: Send MDU-LAI */
/* TODO: Send MDU-LBl */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_MPH_DI:
/* ignore */
break;
case V52_LCPFSM_E_MDU_IDReq:
/* TODO: Send MDU-DI */
break;
case V52_LCPFSM_E_FE_IDReq:
/* TODO: Send FE303 */
break;
case V52_LCPFSM_E_MDU_LUBR:
/* TODO: Send MDU-DI */
/* TODO: Send FE303 */
break;
case V52_LCPFSM_E_MDU_LBI:
case V52_LCPFSM_E_FE302:
case V52_LCPFSM_E_FE305:
case V52_LCPFSM_E_FE306:
/* TODO: Send FE303 */
break;
case V52_LCPFSM_E_FE304:
/* ignore */
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le10_link_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* TODO: Send MDU-LAI */
break;
case V52_LCPFSM_E_MPH_DI:
/* TODO: Send MDU-DI */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_MDU_IDReq:
/* TODO: Send MDU-LBl */
break;
case V52_LCPFSM_E_FE_IDReq:
/* TODO: Send FE303 */
break;
case V52_LCPFSM_E_FE_IDRel:
case V52_LCPFSM_E_FE_IDRej:
/* ignore */
break;
case V52_LCPFSM_E_MDU_LUBR:
/* TODO: Send FE303 */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE11_NOP_LOCAL_LINK_UNBLOCK, 0, 0);
break;
case V52_LCPFSM_E_FE302:
/* TODO: Send MDL-LUBR */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE12_NOP_REMOTE_LINK_UNBLOCK, 0, 0);
break;
case V52_LCPFSM_E_MDU_LBI:
case V52_LCPFSM_E_FE305:
case V52_LCPFSM_E_FE306:
/* TODO: Send FE303 */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_FE304:
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le11_local_link_unblock(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* ignore */
break;
case V52_LCPFSM_E_MPH_DI:
/* TODO: Send MDU-DI */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_MDU_IDReq:
/* TODO: Send MDU-LBl */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_FE_IDReq:
/* TODO: Send FE-IDRej */
break;
case V52_LCPFSM_E_FE_IDRel:
case V52_LCPFSM_E_FE_IDRej:
/* ignore */
break;
case V52_LCPFSM_E_MDU_LUBR:
/* TODO: Send FE301 */
break;
case V52_LCPFSM_E_MDU_LBI:
/* TODO: Send FE303 */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_FE302:
/* TODO: Send MDU-LUBl */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
break;
case V52_LCPFSM_E_FE304:
/* TODO: Send MDU-LBl */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_FE305:
case V52_LCPFSM_E_FE306:
/* TODO: Send FE303 */
/* TODO: Send MDU-LBl */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le12_remote_link_unblock(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* ignore */
break;
case V52_LCPFSM_E_MPH_DI:
/* TODO: Send MDU-Dl */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_MDU_IDReq:
/* TODO: Send MDU-LUBR */
/* TODO: Send MDU-IDRej */
break;
case V52_LCPFSM_E_FE_IDReq:
/* TODO: Send FE-IDRej */
break;
case V52_LCPFSM_E_MDU_LUBR:
/* TODO: Send FE301 */
/* TODO: Send MDU-LUBl */
break;
case V52_LCPFSM_E_MDU_LBI:
/* TODO: Send FE303 */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
break;
case V52_LCPFSM_E_FE302:
/* TODO: Send MDU-LUBR */
break;
case V52_LCPFSM_E_FE304:
/* TODO: Send MDU-LBl */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_FE305:
case V52_LCPFSM_E_FE306:
/* TODO: Send FE303 */
/* TODO: Send MDU-LBl */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le20_operational(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* ignore */
break;
case V52_LCPFSM_E_MPH_DI:
/* TODO: Send MDU-DI */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE01_NOP_LINK_FAILURE, 0, 0);
break;
case V52_LCPFSM_E_MDU_IDReq:
/* TODO: Send FE-IDReq */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE22_OP_LOCAL_LINK_ID, 0, 0);
break;
case V52_LCPFSM_E_FE_IDReq:
/* TODO: Send MDU-IDReq */
break;
case V52_LCPFSM_E_MDU_IDAck:
/* TODO: Send MPH-ID */
/* TODO: Send FE-IDAck */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID, 0, 0);
break;
case V52_LCPFSM_E_MDU_IDRej:
/* TODO: Send FE-IDRej */
break;
case V52_LCPFSM_E_MDU_LUBR:
/* TODO: Send FE301 */
break;
case V52_LCPFSM_E_MDU_LBI:
/* TODO: Send FE303 */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_FE302:
/* TODO: Send MDU-LUBI */
break;
case V52_LCPFSM_E_FE304:
/* TODO: Send MDU-LBI */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_FE305:
/* TODO: Send MDU-LBR */
break;
case V52_LCPFSM_E_FE306:
/* TODO: Send MDU-LBRN */
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le21_op_remote_link_id(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* ignore */
break;
case V52_LCPFSM_E_MPH_DI:
/* TODO: Send MDU-DI */
/* TODO: Send MPH-NOR */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE01_NOP_LINK_FAILURE, 0, 0);
break;
case V52_LCPFSM_E_MDU_IDReq:
/* TODO: Send MDU-IDRej */
break;
case V52_LCPFSM_E_MDU_IDAck:
/* ignore */
break;
case V52_LCPFSM_E_FE_IDRel:
/* TODO: Send MDU-IDRel */
/* TODO: Sen MPH-NOR */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
break;
case V52_LCPFSM_E_MDU_IDRej:
/* TODO: Send FE-IDRej */
/* TODO: Sen MPH-NOR */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
break;
case V52_LCPFSM_E_MDU_LUBR:
/* TODO: Send FE301 */
/* TODO: Sen MPH-NOR */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
break;
case V52_LCPFSM_E_MDU_LBI:
/* TODO: Send FE303 */
/* TODO: Sen MPH-NOR */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_FE302:
/* TODO: Send MDU-IDRel */
/* TODO: Send MDU-LUBI */
/* TODO: Sen MPH-NOR */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
break;
case V52_LCPFSM_E_FE304:
/* TODO: Send MDU-LBI */
/* TODO: Sen MPH-NOR */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_FE305:
/* TODO: Send MDU-LBR */
break;
case V52_LCPFSM_E_FE306:
/* TODO: Send MDU-LBRN */
break;
default:
OSMO_ASSERT(0);
}
}
static void lcp_fsm_le22_op_local_link_id(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V52_LCPFSM_E_MPH_AI:
/* ignore */
break;
case V52_LCPFSM_E_MPH_DI:
/* TODO: Send MDU-DI */
/* TODO: Send FE-IdRel */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE01_NOP_LINK_FAILURE, 0, 0);
break;
case V52_LCPFSM_E_MDU_IDReq:
/* ignore */
break;
case V52_LCPFSM_E_MDU_IDAck:
/* TODO: Send MPH-IDR */
break;
case V52_LCPFSM_E_MPH_IDI:
/* TODO: Send MDU-AI */
/* TODO: Send FE-IDRel */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
break;
case V52_LCPFSM_E_MPH_EIg:
/* TODO: Send FE-IDRel */
/* TODO: Send MDU-ELg */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
break;
case V52_LCPFSM_E_FE_IDRej:
/* TODO: Send MDU-IDRej */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
break;
case V52_LCPFSM_E_MDU_LUBR:
/* TODO: Send FE301 */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
break;
case V52_LCPFSM_E_MDU_LBI:
/* TODO: Send FE303 */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_FE302:
/* TODO: Send MDU-IDRej */
/* TODO: Send MDU-LUBI */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0);
break;
case V52_LCPFSM_E_FE304:
/* TODO: Send MDU-LBI */
osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0);
break;
case V52_LCPFSM_E_FE305:
/* TODO: Send MDU-LBR */
break;
case V52_LCPFSM_E_FE306:
/* TODO: Send MDU-LBRN */
break;
default:
OSMO_ASSERT(0);
}
}
/* Table 17G.965 */
static const struct osmo_fsm_state v52_lcp_le_fsm_states[] = {
[V52_LCPFSM_S_LE01_NOP_LINK_FAILURE] = {
.name = "LE0.1_LINK_FAILURE",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_FE_IDRel) |
S(V52_LCPFSM_E_FE_IDRej) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE20_OP_OPERATIONAL) |
S(V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED),
.action = lcp_fsm_le01_link_failure,
},
[V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED] = {
.name = "LE0.2_LINK_FAILURE_AND_BLOCKED",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED),
.action = lcp_fsm_le02_link_failure_blocked,
},
[V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED] = {
.name = "LE1.0_NOP_LINK_BLOCKED",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_FE_IDRel) |
S(V52_LCPFSM_E_FE_IDRej) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED) |
S(V52_LCPFSM_S_LE11_NOP_LOCAL_LINK_UNBLOCK) |
S(V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID),
.action = lcp_fsm_le10_link_blocked,
},
[V52_LCPFSM_S_LE11_NOP_LOCAL_LINK_UNBLOCK] = {
.name = "LE1.1_NOP_LOCAL_LINK_UNBLOCK",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_FE_IDRel) |
S(V52_LCPFSM_E_FE_IDRej) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED) |
S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED) |
S(V52_LCPFSM_S_LE20_OP_OPERATIONAL),
.action = lcp_fsm_le11_local_link_unblock,
},
[V52_LCPFSM_S_LE12_NOP_REMOTE_LINK_UNBLOCK] = {
.name = "LE1.2_NOP_REMOTE_LINK_UNBLOCK",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED) |
S(V52_LCPFSM_S_LE20_OP_OPERATIONAL) |
S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED),
.action = lcp_fsm_le12_remote_link_unblock,
},
[V52_LCPFSM_S_LE20_OP_OPERATIONAL] = {
.name = "LE2.0_OP_OPERATIONAL",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_MDU_IDAck) |
S(V52_LCPFSM_E_MDU_IDRej) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE01_NOP_LINK_FAILURE) |
S(V52_LCPFSM_S_LE22_OP_LOCAL_LINK_ID) |
S(V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID) |
S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED),
.action = lcp_fsm_le20_operational,
},
[V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID] = {
.name = "LE2.1_OP_REMOTE_LINK_ID",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_MDU_IDAck) |
S(V52_LCPFSM_E_MDU_IDRel) |
S(V52_LCPFSM_E_MDU_IDRej) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE01_NOP_LINK_FAILURE) |
S(V52_LCPFSM_S_LE20_OP_OPERATIONAL) |
S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED),
.action = lcp_fsm_le21_op_remote_link_id,
},
[V52_LCPFSM_S_LE22_OP_LOCAL_LINK_ID] = {
.name = "LE2.2_OP_LOCAL_LINK_ID",
.in_event_mask = S(V52_LCPFSM_E_MPH_AI) |
S(V52_LCPFSM_E_MPH_DI) |
S(V52_LCPFSM_E_MDU_IDReq) |
S(V52_LCPFSM_E_MDU_IDAck) |
S(V52_LCPFSM_E_MPH_IDI) |
S(V52_LCPFSM_E_MPH_EIg) |
S(V52_LCPFSM_E_FE_IDReq) |
S(V52_LCPFSM_E_MDU_LUBR) |
S(V52_LCPFSM_E_MDU_LBI) |
S(V52_LCPFSM_E_FE302) |
S(V52_LCPFSM_E_FE304) |
S(V52_LCPFSM_E_FE305) |
S(V52_LCPFSM_E_FE306),
.out_state_mask = S(V52_LCPFSM_S_LE01_NOP_LINK_FAILURE) |
S(V52_LCPFSM_S_LE20_OP_OPERATIONAL) |
S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED),
.action =lcp_fsm_le22_op_local_link_id,
},
};
struct osmo_fsm v52_lcp_le_fsm = {
.name = "V52_LCP_LE",
.states = v52_lcp_le_fsm_states,
.num_states = ARRAY_SIZE(v52_lcp_le_fsm_states),
.timer_cb = NULL,
.log_subsys = 0,
.event_names = v52_lcp_le_fsm_event_names,
};

View File

@ -1,448 +0,0 @@
/* ITU-T G.964 Section 14.1.3.2.2 ISDN user port FSM - LE */
/***********************************************************************/
/* internal data structures */
/***********************************************************************/
#include <osmocom/core/linuxlist.h>
#include "v5x_internal.h"
/***********************************************************************/
/* state names, event names, primitives, ... */
/***********************************************************************/
/* Table 35/G.964 */
enum v51_mph_prim {
V51_LE_MPH_UB, /* Unblock (req, ind)*/
V51_LE_MPH_B, /* Block (req, ind) */
V51_LE_MPH_A, /* Activate (req, ind) */
V51_LE_MPH_AW, /* Access activation by user (ind) */
V51_LE_MPH_DSA, /* DS Activated (ind) */
V51_LE_MPH_D, /* Deactivate (req, ind) */
V51_LE_MPH_G, /* Grading Information */
V51_LE_MPH_DB, /* Block D-Channel from user port */
V51_LE_MPH_DU, /* Unblock D-Channel from user port */
};
/* 14.1.3.2.2 ISDN user port FSM - LE(ISDN port) */
enum v51_le_isdn_port_state {
V51_LE_UP_I_S_LE10_NOP_BLOCKED, /* LE1.0 */
V51_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK, /* LE1.1 */
V51_LE_UP_I_S_LE12_NOP_REMOTE_UNBLOCK, /* LE1.2 */
V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, /* LE2.0 */
V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED, /* LE2.1 */
V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED, /* LE2.2 */
};
/* 14.2.3.2.2 PSTN user port FSM - LE(PSTN port) */
enum v51_le_pstn_port_state {
V51_LE_UP_P_S_LE10_NOP_BLOCKED, /* LE1.0 */
V51_LE_UP_P_S_LE11_NOP_LOCAL_UNBLOCK, /* LE1.1 */
V51_LE_UP_P_S_LE12_NOP_REMOTE_UNBLOCK, /* LE1.2 */
V51_LE_UP_P_S_LE22_OPERATIONAL, /* LE2.0 */
};
enum v51_ctrl_le_port_fsm_event {
/* inbound function elements from AN (Table 34/G.964) */
V51_CUP_LE_FE102_ACTIV_INIT_USER_IND,
V51_CUP_LE_FE103_DS_ACTIVATED_IND,
V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND,
V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND,
V51_CUP_LE_FE202_UNBLOCK_REQ,
V51_CUP_LE_FE202_UNBLOCK_ACK,
V51_CUP_LE_FE204_BLOCK_CMD,
V51_CUP_LE_FE205_BLOCK_REQ,
V51_CUP_LE_FE206_PREFORMANCE_GRADING_IND,
/* inbound primitives from Mgmt (Table 35/G.964) */
V51_CUP_LE_MPH_UBR_UNBLOCK_REQ,
V51_CUP_LE_MPH_BI_BLOCK_CMD,
V51_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ,
V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ,
V51_CUP_LE_MPH_DB_BLOCK_DCHAN_REQ,
V51_CUP_LE_MPH_DU_BLOCK_DCHAN_REQ,
};
static void isdn_up_le10_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V51_CUP_LE_FE102_ACTIV_INIT_USER_IND:
case V51_CUP_LE_FE103_DS_ACTIVATED_IND:
case V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND:
case V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ:
case V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND:
/* ignore */
break;
case V51_CUP_LE_MPH_UBR_UNBLOCK_REQ:
/* Send FE201 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE201_UNBLOCK));
osmo_fsm_inst_state_change(fi, V51_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK, 0, 0);
break;
case V51_CUP_LE_MPH_BI_BLOCK_CMD:
/* Send FE203 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE203_BLOCK));
break;
case V51_CUP_LE_FE202_UNBLOCK_ACK:
/* TODO: Send MPU-UBR */
osmo_fsm_inst_state_change(fi, V51_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK, 0, 0);
break;
case V51_CUP_LE_FE204_BLOCK_CMD:
case V51_CUP_LE_FE205_BLOCK_REQ:
/* ignore */
break;
default:
OSMO_ASSERT(0);
}
}
static void isdn_up_le11_local_unblock(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V51_CUP_LE_FE102_ACTIV_INIT_USER_IND:
/* TODO: send MPG-AWI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED, 0, 0);
break;
case V51_CUP_LE_FE103_DS_ACTIVATED_IND:
/* ignore */
break;
case V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND:
/* TODO: send MPH-AI*/
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED, 0, 0);
break;
case V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ:
case V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND:
/* ignore */
break;
case V51_CUP_LE_MPH_UBR_UNBLOCK_REQ:
/* send FE201 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE201_UNBLOCK));
break;
case V51_CUP_LE_MPH_BI_BLOCK_CMD:
/* send FE203 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE203_BLOCK));
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
break;
case V51_CUP_LE_FE202_UNBLOCK_ACK:
/* TODO: send MPH-UBI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0);
break;
case V51_CUP_LE_FE204_BLOCK_CMD:
/* TODO: send MPH-BI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
break;
case V51_CUP_LE_FE205_BLOCK_REQ:
/* ignore */
break;
default:
OSMO_ASSERT(0);
}
}
static void isdn_up_le12_remote_unblock(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V51_CUP_LE_FE103_DS_ACTIVATED_IND:
/* ignore */
break;
case V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ:
case V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND:
/* ignore */
break;
case V51_CUP_LE_MPH_UBR_UNBLOCK_REQ:
/* TODO: send MPH-UBI */
/* send FE201 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE201_UNBLOCK));
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0);
break;
case V51_CUP_LE_MPH_BI_BLOCK_CMD:
/* send FE203 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE203_BLOCK));
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
break;
case V51_CUP_LE_FE202_UNBLOCK_ACK:
/* TODO: send MPH-UBR */
break;
case V51_CUP_LE_FE204_BLOCK_CMD:
/* TODO: send MPH-BI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
break;
case V51_CUP_LE_FE205_BLOCK_REQ:
/* ignore */
break;
default:
OSMO_ASSERT(0);
}
}
/* LE 2.0 Operational deactivated */
static void isdn_up_le20_op_deact(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V51_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ:
/* Send FE101 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE101_ACTIVATE_ACCESS));
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED, 0, 0);
break;
case V51_CUP_LE_FE102_ACTIV_INIT_USER_IND:
/* TODO: Send MPH-AWI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED, 0, 0);
break;
case V51_CUP_LE_FE103_DS_ACTIVATED_IND:
/* ignore */
break;
case V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND:
/* TODO: Send MPH-AI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED, 0, 0);
break;
case V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ:
/* Send FE105 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE105_DEACTIVATE_ACCESS));
break;
case V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND:
/* TODO: Send MPH-DI */
break;
case V51_CUP_LE_MPH_UBR_UNBLOCK_REQ:
/* Send FE201 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE201_UNBLOCK));
break;
case V51_CUP_LE_MPH_BI_BLOCK_CMD:
/* Send FE203 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE203_BLOCK));
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
break;
case V51_CUP_LE_FE202_UNBLOCK_ACK:
/* TODO: Send MPH-UBI */
break;
case V51_CUP_LE_FE204_BLOCK_CMD:
/* TODO: Send MPH-BI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
break;
case V51_CUP_LE_FE205_BLOCK_REQ:
/* TODO: Send MPH-BR */
break;
case V51_CUP_LE_FE206_PREFORMANCE_GRADING_IND:
/* TODO: Send MPH-GI */
break;
default:
OSMO_ASSERT(0);
}
}
/* LE 2.1 Access initiated */
static void isdn_up_le21_op_act_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V51_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ:
case V51_CUP_LE_FE102_ACTIV_INIT_USER_IND:
/* ignore */
break;
case V51_CUP_LE_FE103_DS_ACTIVATED_IND:
/* TODO: Send MPH-DSAI */
break;
case V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND:
/* TODO: Send MPH-AI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED, 0, 0);
break;
case V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ:
/* Send FE105 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE105_DEACTIVATE_ACCESS));
/* TODO: Send MPH-DI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0);
break;
case V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND:
/* TODO: Send MPH-DI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0);
break;
case V51_CUP_LE_MPH_UBR_UNBLOCK_REQ:
/* Send FE201 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE201_UNBLOCK));
break;
case V51_CUP_LE_MPH_BI_BLOCK_CMD:
/* Send FE203 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE203_BLOCK));
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
break;
case V51_CUP_LE_FE202_UNBLOCK_ACK:
/* TODO: Send MPH-UBI */
break;
case V51_CUP_LE_FE204_BLOCK_CMD:
/* TODO: Send MPH-BI */
/* TODO: Send MPH-DI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
break;
case V51_CUP_LE_FE205_BLOCK_REQ:
/* TODO: Send MPH-BR */
break;
case V51_CUP_LE_FE206_PREFORMANCE_GRADING_IND:
/* TODO: Send MPH-GI */
break;
default:
OSMO_ASSERT(0);
}
}
/* LE 2.2 Access activated */
static void isdn_up_le22_op_acc_act(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND:
/* ignore */
break;
case V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ:
/* Send FE105 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE105_DEACTIVATE_ACCESS));
/* TODO: Send MPH-DI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0);
break;
case V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND:
/* TODO: Send MPH-DI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0);
break;
case V51_CUP_LE_MPH_UBR_UNBLOCK_REQ:
/* TODO: Send MPH-AI */
/* Send FE201 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE201_UNBLOCK));
break;
case V51_CUP_LE_MPH_BI_BLOCK_CMD:
/* Send FE203 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE203_BLOCK));
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
break;
case V51_CUP_LE_FE202_UNBLOCK_ACK:
/* TODO: Send MPH-UBI */
break;
case V51_CUP_LE_FE204_BLOCK_CMD:
/* TODO: Send MPH-BI */
/* TODO: Send MPH-DI */
osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0);
break;
case V51_CUP_LE_FE205_BLOCK_REQ:
/* TODO: Send MPH-BR */
break;
case V51_CUP_LE_FE206_PREFORMANCE_GRADING_IND:
/* TODO: Send MPH-GI */
break;
case V51_CUP_LE_MPH_DB_BLOCK_DCHAN_REQ:
/* Send FE207 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE207_D_CHANNEL_BLOCK));
break;
case V51_CUP_LE_MPH_DU_BLOCK_DCHAN_REQ:
/* Send FE208 */
v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE208_D_CHANNEL_UNBLOCK));
break;
default:
OSMO_ASSERT(0);
}
}
/* Table 38/G.964 LE (ISDN port) FSM for ISDN basic access user ports */
static const struct osmo_fsm_state v51_ctrl_le_port_fsm_states[] = {
[V51_LE_UP_I_S_LE10_NOP_BLOCKED] = {
.name = "LE1.0_BLOCKED",
.in_event_mask = S(V51_CUP_LE_FE102_ACTIV_INIT_USER_IND) |
S(V51_CUP_LE_FE103_DS_ACTIVATED_IND) |
S(V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND) |
S(V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) |
S(V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) |
S(V51_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V51_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V51_CUP_LE_FE202_UNBLOCK_ACK) |
S(V51_CUP_LE_FE204_BLOCK_CMD) |
S(V51_CUP_LE_FE205_BLOCK_REQ),
.out_state_mask = S(V51_LE_UP_I_S_LE10_NOP_BLOCKED) |
S(V51_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK) |
S(V51_LE_UP_I_S_LE12_NOP_REMOTE_UNBLOCK),
.action = isdn_up_le10_blocked,
},
[V51_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK] = {
.name = "LE1.1_LOCAL_UNBLOCK",
.in_event_mask = S(V51_CUP_LE_FE102_ACTIV_INIT_USER_IND) |
S(V51_CUP_LE_FE103_DS_ACTIVATED_IND) |
S(V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND) |
S(V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) |
S(V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) |
S(V51_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V51_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V51_CUP_LE_FE202_UNBLOCK_ACK) |
S(V51_CUP_LE_FE204_BLOCK_CMD) |
S(V51_CUP_LE_FE205_BLOCK_REQ),
.out_state_mask = S(V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED) |
S(V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED) |
S(V51_LE_UP_I_S_LE10_NOP_BLOCKED) |
S(V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED),
.action = isdn_up_le11_local_unblock,
},
[V51_LE_UP_I_S_LE12_NOP_REMOTE_UNBLOCK] = {
.name = "LE1.2_REMOTE_UNBLOCK",
.in_event_mask = S(V51_CUP_LE_FE103_DS_ACTIVATED_IND) |
S(V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) |
S(V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) |
S(V51_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V51_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V51_CUP_LE_FE202_UNBLOCK_ACK) |
S(V51_CUP_LE_FE204_BLOCK_CMD) |
S(V51_CUP_LE_FE205_BLOCK_REQ),
.out_state_mask = S(V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED) |
S(V51_LE_UP_I_S_LE10_NOP_BLOCKED),
.action = isdn_up_le12_remote_unblock,
},
[V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED] = {
.name = "LE2.0_OPERATIONAL_DEACTIVATED",
.in_event_mask = S(V51_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ) |
S(V51_CUP_LE_FE102_ACTIV_INIT_USER_IND) |
S(V51_CUP_LE_FE103_DS_ACTIVATED_IND) |
S(V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND) |
S(V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) |
S(V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) |
S(V51_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V51_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V51_CUP_LE_FE202_UNBLOCK_ACK) |
S(V51_CUP_LE_FE204_BLOCK_CMD) |
S(V51_CUP_LE_FE205_BLOCK_REQ) |
S(V51_CUP_LE_FE206_PREFORMANCE_GRADING_IND),
.out_state_mask = S(V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED) |
S(V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED) |
S(V51_LE_UP_I_S_LE10_NOP_BLOCKED),
.action = isdn_up_le21_op_deact,
},
[V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED] = {
.name = "LE2.1_ACTIVATION_INITIATED",
.in_event_mask = S(V51_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ) |
S(V51_CUP_LE_FE102_ACTIV_INIT_USER_IND) |
S(V51_CUP_LE_FE103_DS_ACTIVATED_IND) |
S(V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND) |
S(V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) |
S(V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) |
S(V51_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V51_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V51_CUP_LE_FE202_UNBLOCK_ACK) |
S(V51_CUP_LE_FE204_BLOCK_CMD) |
S(V51_CUP_LE_FE205_BLOCK_REQ),
.out_state_mask = S(V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED) |
S(V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED) |
S(V51_LE_UP_I_S_LE10_NOP_BLOCKED),
.action = isdn_up_le21_op_act_init,
},
[V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED] = {
.name = "LE2.2_ACCESS_ACTIVATED",
.in_event_mask = S(V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND) |
S(V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) |
S(V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) |
S(V51_CUP_LE_MPH_UBR_UNBLOCK_REQ) |
S(V51_CUP_LE_MPH_BI_BLOCK_CMD) |
S(V51_CUP_LE_FE202_UNBLOCK_ACK) |
S(V51_CUP_LE_FE204_BLOCK_CMD) |
S(V51_CUP_LE_FE205_BLOCK_REQ) |
S(V51_CUP_LE_FE206_PREFORMANCE_GRADING_IND) |
S(V51_CUP_LE_MPH_DB_BLOCK_DCHAN_REQ) |
S(V51_CUP_LE_MPH_DU_BLOCK_DCHAN_REQ),
.out_state_mask = S(V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED) |
S(V51_LE_UP_I_S_LE10_NOP_BLOCKED),
.action = isdn_up_le22_op_acc_act,
},
};

View File

@ -1,92 +0,0 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/linuxlist.h>
#include "v5x_internal.h"
static LLIST_HEAD(v5_instances);
struct v5x_instance *v5x_instance_alloc(void *ctx)
{
struct v5x_instance *v5i = talloc_zero(ctx, struct v5x_instance);
if (!v5i)
return NULL;
INIT_LLIST_HEAD(&v5i->interfaces);
INIT_LLIST_HEAD(&v5i->user_ports);
/* TODO: allocate ctrl_fi */
llist_add_tail(&v5i->list, &v5_instances);
return v5i;
}
static void v5x_ts_init(struct v5x_timeslot *v5ts, uint8_t nr, struct v5x_link *v5l, uint16_t l3_addr)
{
v5ts->nr = nr;
v5ts->link = v5l;
v5ts->l3_address = l3_addr;
}
static void v5x_cchan_init(struct v5x_c_channel *v5cc, struct v5x_link *v5l, struct v5x_timeslot *v5ts, bool active)
{
v5cc->link = v5l;
v5cc->ts = v5ts;
v5cc->active = active;
}
static void v5x_link_init(struct v5x_link *v5l, struct v5x_interface *v5if, uint8_t id)
{
unsigned int i;
v5l->id = id;
v5l->interface = v5if;
for (i = 1; i < ARRAY_SIZE(v5l->ts); i++) {
v5x_ts_init(&v5l->ts[i], i, v5l, 0 /*l3_addr*/);
}
/* primary c-channel must always be present */
v5x_cchan_init(&v5l->c_channel[0], v5l, &v5l->ts[16], true);
}
struct v5x_interface *v5x_interface_alloc(struct v5x_instance *v5i, enum v5x_dialect dialect)
{
struct v5x_interface *v5if = talloc_zero(v5i, struct v5x_interface);
//struct v5x_link *v5l;
struct v5x_c_channel *primary_c_chan;
v5if->instance = v5i;
v5if->dialect = dialect;
/* primary link must alwasy be present */
v5x_link_init(&v5if->links[0], v5if, 0);
v5if->primary_link = &v5if->links[0];
primary_c_chan = &v5if->primary_link->c_channel[0];
/* TODO: allocate fi */
//lapd_dl_init2(&v5if->control.dl, k, v_rnage, maxf, "control");
v5if->control.c_chan = primary_c_chan;
//lapd_dl_init2(&v5if->pstn.dl, k, v_rnage, maxf, "pstn");
v5if->pstn.c_chan = primary_c_chan;
if (v5if->dialect == V5X_DIALECT_V52) {
//lapd_dl_init2(&v5if->lcp.dl, k, v_rnage, maxf, "lcp");
v5if->lcp.c_chan = primary_c_chan;
//lapd_dl_init2(&v5if->bcc.dl, k, v_rnage, maxf, "bcc");
v5if->bcc.c_chan = primary_c_chan;
//lapd_dl_init2(&v5if->protection[0].dl, k, v_rnage, maxf, "protection0");
v5if->protection[0].c_chan = primary_c_chan;
//protection[1] ?
}
llist_add_tail(&v5if->list, &v5i->interfaces);
return v5if;
}

View File

@ -1,123 +0,0 @@
#pragma once
/* (C) 2021 by Harald Welte <laforge@gnumonks.org>
*
* 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.
*
* 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/utils.h>
#include <osmocom/gsm/lapd_core.h>
struct osmo_fsm_inst;
enum v5x_dialect {
V5X_DIALECT_V51 = 1,
V5X_DIALECT_V52 = 2,
};
/* forward-declarations */
struct v5x_interface;
struct v5x_instance;
struct v5x_link;
/* A C-channel is a 64k timeslot used for signalling */
struct v5x_c_channel {
struct v5x_link *link; /* back-pointer */
struct v5x_timeslot *ts; /* E1 link timeslot. NULL = C-channel doesn't exist */
bool active; /* false == standby */
};
/* one physical E1 timeslot used on an E1 interface part of a V5.2 interface */
struct v5x_timeslot {
uint8_t nr;
struct v5x_link *link; /* back-pointer */
uint16_t l3_address;
};
/* one physical E1 interface used within a V5.2 interface */
struct v5x_link {
uint8_t id;
struct v5x_interface *interface; /* back-pointer */
struct v5x_timeslot ts[32]; /* 32 E1 slots; 0 not available */
struct v5x_c_channel c_channel[3]; /* 64k signaling possible on TS16, TS15 and TS31 */
struct osmo_fsm_inst *l1_link_fi; /* Layer 1 Link FSM instance */
};
/* one V5.x interface between AN (Access Network) and LE (Local Exchange) */
struct v5x_interface {
struct llist_head list; /* instance.interfaces */
struct v5x_instance *instance; /* back-pointer */
enum v5x_dialect dialect;
struct v5x_link *primary_link; /* one of the links below */
struct v5x_link *secondary_link; /* one of the links below */
/* 1..16 links in one interface */
struct v5x_link links[16];
struct osmo_fsm_inst *fi; /* Interface FSM instance */
struct {
struct lapd_datalink dl; /* Control data link */
struct v5x_c_channel *c_chan; /* pointer to active C-channel */
} control;
struct {
struct lapd_datalink dl; /* Link control data link */
struct v5x_c_channel *c_chan; /* pointer to active C-channel */
struct osmo_fsm_inst *fi; /* Link Control FSM instance */
} lcp;
struct {
struct lapd_datalink dl; /* PSTN data link */
struct v5x_c_channel *c_chan; /* pointer to active C-channel */
} pstn;
struct {
struct lapd_datalink dl; /* BCC data link */
struct v5x_c_channel *c_chan; /* pointer to active C-channel */
} bcc;
struct {
struct lapd_datalink dl; /* Protection data link 1 + 2 */
struct v5x_c_channel *c_chan; /* pointer to active C-channel */
} protection[2];
};
/* one user-facing port (subscriber line) */
struct v5x_user_port {
struct llist_head list; /* part of v5x_instance.ports */
struct v5x_instance *inst; /* back-pointer to instance we're part of */
uint16_t nr; /* port-number in decoded form (0..32767) */
bool is_isdn; /* is this port an ISDN port? */
struct {
struct osmo_fsm_inst *ctrl_fi; /* control protocol FSM instance */
struct osmo_fsm_inst *fi; /* port state FSM instance */
} isdn;
struct {
} pstn;
};
struct v5x_instance {
struct llist_head list; /* part of global list of instances */
struct llist_head interfaces; /* v5x_interface.list */
struct llist_head user_ports; /* list of v5x_user_port */
struct osmo_fsm_inst *ctrl_fi; /* common control FSM */
};
struct v5x_instance *v5x_instance_alloc(void *ctx);
struct v5x_interface *v5x_interface_alloc(struct v5x_instance *v5i, enum v5x_dialect dialect);

View File

@ -1,458 +0,0 @@
/* (C) 2021 by Harald Welte <laforge@gnumonks.org>
*
* 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.
*
* 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 <unitstd.h>
#include <stdint.h>
#include <errno.h>
#include <osmocom/core/util.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/tlv.h>
#include "v5x_protocol.h"
const struct tlv_definition v51_ctrl_tlv_def[] = {
/* single byte: PSTN / G.964 Table 17 */
[V51_CTRL_IEI_PULSE_NOTIFICATION] = TLV_TYPE_SINGLE_TV,
[V51_CTLR_IEI_LINE_NOTIFICATION] = TLV_TYPE_SINGLE_TV,
[V51_CTLR_IEI_STATE] = TLV_TYPE_SINGLE_TV,
[V51_CTLR_IEI_AUTONOMOUS_SIG_SEQ] = TLV_TYPE_SINGLE_TV,
[V51_CTLR_IEI_SEQUENCE_RESPONSE] = TLV_TYPE_SINGLE_TV,
/* single byte: ISDN / G.964 Table 53 */
[V51_CTRL_IEI_PERFORMANCE_GRADING] = TLV_TYPE_SINGLE_TV,
[V51_CTRL_IEI_REJECTION_CAUSE] = TLV_TYPE_SINGLE_TV,
/* variable length: PSTN / G.964 Table 17 */
[V51_CTRL_IEI_SEQUENCE_NR] = TLV_TYPE_TLV,
[V51_CTRL_IEI_CADENCED_RINGING] = TLV_TYPE_TLV,
[V51_CTRL_IEI_PULSED_SIGNAL] = TLV_TYPE_TLV,
[V51_CTRL_IEI_STEADY_SIGNAL] = TLV_TYPE_TLV,
[V51_CTRL_IEI_DIGIT_SIGNAL] = TLV_TYPE_TLV,
[V51_CTRL_IEI_RECOGNITION_TIME] = TLV_TYPE_TLV,
[V51_CTRL_IEI_ENABLE_AUTONOMOUS_ACK] = TLV_TYPE_TLV,
[V51_CTRL_IEI_DISABLE_AUTONOMOUS_ACK] = TLV_TYPE_TLV,
[V51_CTRL_IEI_CAUSE] = TLV_TYPE_TLV,
[V51_CTRL_IEI_RESOURCE_UNAVAILABLE] = TLV_TYPE_TLV,
[V51_CTRL_IEI_ENABLE_METERING] = TLV_TYPE_TLV,
[V51_CTRL_IEI_METERING_REPORT] = TLV_TYPE_TLV,
[V51_CTRL_IEI_ATTENUATION] = TLV_TYPE_TLV,
/* variable length: ISDN / G.964 Table 53 */
[V51_CTRL_IEI_CTRL_F_ELEMENT] = TLV_TYPE_TLV,
[V51_CTRL_IEI_CTRL_F_ID] = TLV_TYPE_TLV,
[V51_CTRL_IEI_VARIANT] = TLV_TYPE_TLV,
[V51_CTRL_IEI_INTERFACE_ID] = TLV_TYPE_TLV,
/* variable length: LCP / G.965 Table FIXME */
[V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION] = TLV_TYPE_TLV,
/* variable length: BCC */
[V52_CTRL_IEI_BCC_USER_PORT_ID] = TLV_TYPE_TLV,
[V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID] = TLV_TYPE_TLV,
[V52_CTRL_IEI_BCC_V5_TS_ID] = TLV_TYPE_TLV,
[V52_CTRL_IEI_BCC_MULTI_TS_MAP] = TLV_TYPE_TLV,
[V52_CTRL_IEI_BCC_REJECT_CAUSE] = TLV_TYPE_TLV,
[V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE] = TLV_TYPE_TLV,
[V52_CTRL_IEI_BCC_CONNECTION_INCOMPLETE] = TLV_TYPE_TLV,
[V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY] = TLV_TYPE_TLV,
/* variable-length: Protection */
[V52_CTRL_IEI_PP_SEQUENCE_NR] = TLV_TYPE_TLV,
[V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID] = TLV_TYPE_TLV,
[V52_CTRL_IEI_PP_REJECTION_CAUSE] = TLV_TYPE_TLV,
[V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE] = TLV_TYPE_TLV,
};
const struct value_string v51_ctrl_msg_typ_str[] = {
{ V51_CTRL_MSGT_ESTABLISH, "ESTABLISH" },
{ V51_CTRL_MSGT_ESTABLISH_ACK, "ESTABLISH_ACK" },
{ V51_CTRL_MSGT_SIGNAL, "SIGNAL" },
{ V51_CTRL_MSGT_SIGNAL_ACK, "SIGNAL_ACK" },
{ V51_CTRL_MSGT_DISCONNECT, "DISCONNECT" },
{ V51_CTRL_MSGT_DISCONNECT_ACK, "DISCONNECT_ACK" },
{ V51_CTRL_MSGT_STATUS_ENQUIRY, "STATUS_ENQUIRY" },
{ V51_CTRL_MSGT_STATUS, "STATUS" },
{ V51_CTRL_MSGT_PROTOCOL_PARAMETER, "PROTOCOL_PARAMETER" },
{ V51_CTRL_MSGT_PORT_CTRL, "PORT_CTRL" },
{ V51_CTRL_MSGT_PORT_CTRL_ACK, "PORT_CTRL_ACK" },
{ V51_CTRL_MSGT_COMMON_CTRL, "COMMON_CTRL" },
{ V51_CTRL_MSGT_COMMON_CTRL_ACK, "COMMON_CTRL_ACK" },
{ V52_CTRL_MSGT_SWITCH_OVER_REQ, "SWITCH_OVER_REQ" },
{ V52_CTRL_MSGT_SWITCH_OVER_COM, "SWITCH_OVER_COM" },
{ V52_CTRL_MSGT_OS_SWITCH_OVER_COM, "OS_SWITCH_OVER_COM" },
{ V52_CTRL_MSGT_SWITCH_OVER_ACK, "SWITCH_OVER_ACK" },
{ V52_CTRL_MSGT_SWITCH_OVER_REJECT, "SWITCH_OVER_REJECT" },
{ V52_CTRL_MSGT_RESET_SN_COM, "RESET_SN_COM" },
{ V52_CTRL_MSGT_RESET_SN_ACK, "RESET_SN_ACK" },
{ V52_CTRL_MSGT_ALLOCATION, "ALLOCATION" },
{ V52_CTRL_MSGT_ALLOCATION_COMPLETE, "ALLOCATION_COMPLETE" },
{ V52_CTRL_MSGT_ALLOCATION_REJECT, "ALLOCATION_REJECT" },
{ V52_CTRL_MSGT_DE_ALLOCATION, "DE_ALLOCATION" },
{ V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE, "DE_ALLOCATION_COMPLETE" },
{ V52_CTRL_MSGT_DE_ALLOCATION_REJECT, "DE_ALLOCATION_REJECT" },
{ V52_CTRL_MSGT_AUDIT, "AUDIT" },
{ V52_CTRL_MSGT_AUDIT_COMPLETE, "AUDIT_COMPLETE" },
{ V52_CTRL_MSGT_AN_FAULT, "AN_FAULT" },
{ V52_CTRL_MSGT_AN_FAULT_ACKNOWLEDGE "AN_FAULT_ACKNOWLEDGE" },
{ V52_CTRL_MSGT_PROTOCOL_ERROR "PROTOCOL_ERROR" },
{ V52_CTRL_MSGT_LINK_CTRL, "LINK_CTRL" },
{ V52_CTRL_MSGT_LINK_CTRL_ACK, "LINK_CTRL_ACK" },
{ 0, NULL }
};
const struct value_string v51_ctrl_iei_str[] = {
{ V51_CTRL_IEI_PULSE_NOTIFICATION, "PULSE_NOTIFICATION" },
{ V51_CTLR_IEI_LINE_NOTIFICATION, "LINE_NOTIFICATION" },
{ V51_CTLR_IEI_STATE, "STATE" },
{ V51_CTLR_IEI_AUTONOMOUS_SIG_SEQ, "AUTONOMOUS_SIG_SEQ" },
{ V51_CTLR_IEI_SEQUENCE_RESPONSE, "SEQUENCE_RESPONSE" },
{ V51_CTRL_IEI_PERFORMANCE_GRADING, "PERFORMANCE_GRADING" },
{ V51_CTRL_IEI_REJECTION_CAUSE, "REJECTION_CAUSE" },
{ V51_CTRL_IEI_SEQUENCE_NR, "SEQUENCE_NR" },
{ V51_CTRL_IEI_CADENCED_RINGING, "CADENCED_RINGING" },
{ V51_CTRL_IEI_PULSED_SIGNAL, "PULSED_SIGNAL" },
{ V51_CTRL_IEI_STEADY_SIGNAL, "STEADY_SIGNAL" },
{ V51_CTRL_IEI_DIGIT_SIGNAL, "DIGIT_SIGNAL" },
{ V51_CTRL_IEI_RECOGNITION_TIME, "RECOGNITION_TIME" },
{ V51_CTRL_IEI_ENABLE_AUTONOMOUS_ACK, "ENABLE_AUTONOMOUS_ACK" },
{ V51_CTRL_IEI_DISABLE_AUTONOMOUS_ACK, "DISABLE_AUTONOMOUS_ACK" },
{ V51_CTRL_IEI_CAUSE, "CAUSE" },
{ V51_CTRL_IEI_RESOURCE_UNAVAILABLE, "RESOURCE_UNAVAILABLE" },
{ V51_CTRL_IEI_ENABLE_METERING, "ENABLE_METERING" },
{ V51_CTRL_IEI_METERING_REPORT, "METERING_REPORT" },
{ V51_CTRL_IEI_ATTENUATION, "ATTENUATION" },
{ V51_CTRL_IEI_CTRL_F_ELEMENT, "CTRL_F_ELEMENT" },
{ V51_CTRL_IEI_CTRL_F_ID, "CTRL_F_ID" },
{ V51_CTRL_IEI_VARIANT, "VARIANT" },
{ V51_CTRL_IEI_INTERFACE_ID, "INTERFACE_ID" },
{ V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION, "LCP_LINK_CTRL_FUNCTION" },
{ V52_CTRL_IEI_BCC_USER_PORT_ID "BCC_USER_PORT_ID" },
{ V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID, "BCC_ISDN_PORT_TS_ID" },
{ V52_CTRL_IEI_BCC_V5_TS_ID, "BCC_V5_TS_ID" },
{ V52_CTRL_IEI_BCC_MULTI_TS_MAP, "BCC_MULTI_TS_MAP" },
{ V52_CTRL_IEI_BCC_REJECT_CAUSE, "BCC_REJECT_CAUSE" },
{ V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE, "BCC_PROTOCOL_ERROR_CAUSE" },
{ V52_CTRL_IEI_BCC_CONNECTION_INCOMPLETE, "BCC_CONNECTION_INCOMPLETE" },
{ V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY, "BCC_INFO_TRANSFER_CAPABILITY" },
{ V52_CTRL_IEI_PP_SEQUENCE_NR, "PP_SEQUENCE_NR" },
{ V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID, "PP_PHYSICAL_C_CHAN_ID" },
{ V52_CTRL_IEI_PP_REJECTION_CAUSE, "PP_REJECTION_CAUSE" },
{ V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE, "PP_PROTOCOL_ERROR_CAUSE" },
{ 0, NULL }
};
const struct value_string v51_ctrl_func_el_str[] = {
{ V51_CTRL_FE101_ACTIVATE_ACCESS, "FE101_ACTIVATE_ACCESS" },
{ V51_CTRL_FE102_ACT_INIT_BY_USER, "FE102_ACT_INIT_BY_USER" },
{ V51_CTRL_FE103_DS_ACTIVATED, "FE103_DS_ACTIVATED" },
{ V51_CTRL_FE104_ACCESS_ACTIVATED, "FE104_ACCESS_ACTIVATED" },
{ V51_CTRL_FE105_DEACTIVATE_ACCESS, "FE105_DEACTIVATE_ACCESS" },
{ V51_CTRL_FE106_ACCESS_DEACTIVATED, "FE106_ACCESS_DEACTIVATED" },
{ V51_CTRL_FE201_UNBLOCK, "FE201_UNBLOCK" },
{ V51_CTRL_FE203_BLOCK, "FE203_BLOCK" },
{ V51_CTRL_FE205_BLOCK_REQ, "FE205_BLOCK_REQ" },
{ V51_CTRL_FE206_PERFORMANCE_GRADING, "FE206_PERFORMANCE_GRADING" },
{ V51_CTRL_FE206_D_CHANNEL_BLOCK, "FE206_D_CHANNEL_BLOCK" },
{ V51_CTRL_FE206_D_CHANNEL_UNBLOCK, "FE206_D_CHANNEL_UNBLOCK" }
{ 0, NULL }
};
const struct value_string v51_ctrl_func_id_str[] = {
{ V51_CTRL_ID_VERIFY_RE_PROVISIONING, "VERIFY_RE_PROVISIONING" },
{ V51_CTRL_ID_READY_FOR_RE_PROVISIONING, "READY_FOR_RE_PROVISIONING" },
{ V51_CTRL_ID_NOT_READY_FOR_RE_PROVISIONING, "NOT_READY_FOR_RE_PROVISIONING" },
{ V51_CTRL_ID_SWITCH_OVER_TO_NEW_VARIANT, "SWITCH_OVER_TO_NEW_VARIANT" },
{ V51_CTRL_ID_RE_PROVISIONING_STARTED, "RE_PROVISIONING_STARTED" },
{ V51_CTRL_ID_CANNOT_RE_PROVISION, "CANNOT_RE_PROVISION" },
{ V51_CTRL_ID_REQUEST_VARIANT_AND_INTERFACE_ID, "REQUEST_VARIANT_AND_INTERFACE_ID" },
{ V51_CTRL_ID_VARIANT_AND_INTERFACE_ID, "VARIANT_AND_INTERFACE_ID" },
{ V51_CTRL_ID_BLOCKING_STARTED, "BLOCKING_STARTED" },
{ V51_CTRL_ID_RESTART_REQUEST, "RESTART_REQUEST" },
{ V51_CTRL_ID_RESTART_COMPLETE, "RESTART_COMPLETE" },
{ 0, NULL }
};
const struct value_string v51_cause_type_str[] = {
{ V51_CTRL_CAUSE_T_RESP_TO_STATUS_ENQ, "RESP_TO_STATUS_ENQ" },
{ V51_CTRL_CAUSE_T_L3_ADDRESS_ERROR, "L3_ADDRESS_ERROR" },
{ V51_CTRL_CAUSE_T_MSG_TYPE_UNRECOGNIZED, "MSG_TYPE_UNRECOGNIZED" },
{ V51_CTRL_CAUSE_T_OUT_OF_SEQUENCE_IE, "OUT_OF_SEQUENCE_IE" },
{ V51_CTRL_CAUSE_T_REPEATED_OPT_IE, "REPEATED_OPT_IE" },
{ V51_CTRL_CAUSE_T_MAND_IE_MISSING, "MAND_IE_MISSING" },
{ V51_CTRL_CAUSE_T_UNRECOGNIZED_IE, "UNRECOGNIZED_IE" },
{ V51_CTRL_CAUSE_T_MAND_IE_CONTENT_ERROR, "MAND_IE_CONTENT_ERROR" },
{ V51_CTRL_CAUSE_T_OPT_IE_CONTENT_ERROR, "OPT_IE_CONTENT_ERROR" },
{ V51_CTRL_CAUSE_T_MSG_INCOMP_PATH_STATE, "MSG_INCOMP_PATH_STATE" },
{ V51_CTRL_CAUSE_T_REPEATED_MAND_IE, "REPEATED_MAND_IE" },
{ V51_CTRL_CAUSE_T_TOO_MANY_IE, "TOO_MANY_IE" },
{ 0, NULL }
};
static const uint8_t signal_mand_ies[] = { V51_CTRL_IEI_SEQUENCE_NR };
static const uint8_t signal_ack_mand_ies[] = { V51_CTRL_IEI_SEQUENCE_NR };
static const uint8_t status_mand_ies[] = { V51_CTRL_IEI_STATE, V51_CTRL_IEI_CAUSE };
static const uint8_t prot_par_mand_ies[] = { V51_CTRL_IEI_SEQUENCE_NR };
static const uint8_t port_ctrl_mand_ies[] = { V51_CTRL_IEI_CTRL_F_ELEMENT };
static const uint8_t lcp_mand_ies[] = { V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION };
static const uint8_t alloc_mand_ies[] = { V52_CTRL_IEI_BCC_USER_PORT_ID };
static const uint8_t alloc_rej_mand_ies[] = { V52_CTRL_IEI_BCC_REJECT_CAUSE };
static const uint8_t prot_err_mand_ies[] = { V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE };
static const uint8_t pp_swo_mand_ies[] = {
V52_CTRL_IEI_PP_SEQUENCE_NR,
V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID
};
static const uint8_t pp_swo_rej_mand_ies[] = {
V52_CTRL_IEI_PP_SEQUENCE_NR,
V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID,
V52_CTRL_IEI_PP_REJECTION_CAUSE,
};
static const uint8_t pp_perr_mand_ies[] = {
V52_CTRL_IEI_PP_SEQUENCE_NR,
V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE,
};
const struct osmo_tlv_prot_def v51_ctrl_msg_tlv = {
.name = "V51_CTRL",
.tlv_def = v51_ctrl_tlv_def,
.msg_def = {
/* G.964 Section 13.3 */
[V51_CTRL_MSGT_ESTABLISH] = MSG_DEF("ESTABLISH", NULL, 0),
[V51_CTRL_MSGT_ESTABLISH_ACK] = MSG_DEF("ESTABLISH", NULL, 0),
[V51_CTRL_MSGT_SIGNAL] = MSG_DEF("SIGNAL", signal_mand_ies, 0),
[V51_CTRL_MSGT_SIGNAL_ACK] = MSG_DEF("SIGNAL_ACK", signal_ack_mand_ies, 0),
[V51_CTRL_MSGT_STATUS] = MSG_DEF("STATUS", status_mand_ies, 0),
[V51_CTRL_MSGT_STATUS_ENQUIRY] = MSG_DEF("STATUS_ENQUIRY", NULL, 0),
[V51_CTRL_MSGT_DISCONNECT] = MSG_DEF("DISCONNECT", NULL, 0),
[V51_CTRL_MSGT_DISCONNECT_ACK] = MSG_DEF("DISCONNECT_ACK", NULL, 0),
[V51_CTRL_MSGT_PROTOCOL_PARAMETER] = MSG_DEF("PROTOCOL_PARAMETER", prot_par_mand_ies, 0),
/* G.964 Section 14.4 */
[V51_CTRL_MSGT_PORT_CTRL] = MSG_DEF("PORT_CTRL", port_ctrl_mand_ies, 0),
[V51_CTRL_MSGT_PORT_CTRL_ACK] = MSG_DEF("PORT_CTRL_ACK", port_ctrl_mand_ies, 0),
[V51_CTRL_MSGT_COMMON_CTRL] = MSG_DEF("COMMON_CTRL", port_ctrl_mand_ies, 0),
[V51_CTRL_MSGT_COMMON_CTRL_ACK] = MSG_DEF("COMMON_CTRL_ACK", port_ctrl_mand_ies, 0),
/* G.965 Section 16.3 LCP */
[V52_CTRL_MSGT_LCP_LINK_CTRL] = MSG_DEF("LINK_CONTROL", lcp_mand_ies, 0),
[V52_CTRL_MSGT_LCP_LINK_CTRL_ACK] = MSG_DEF("LINK_CONTROL_ACK", lcp_mand_ies, 0),
/* G.965 Section 17.3 BCC */
[V52_CTRL_MSGT_ALLOCATION] = MSG_DEF("ALLOCATION", alloc_mand_ies, 0),
[V52_CTRL_MSGT_ALLOCATION_COMPLETE] = MSG_DEF("ALLOCATION_COMPLETE", NULL, 0),
[V52_CTRL_MSGT_ALLOCATION_REJECT] = MSG_DEF("ALLOCATION_REJECT", alloc_rej_mand_ies, 0),
[V52_CTRL_MSGT_DE_ALLOCATION] = MSG_DEF("DE_ALLOCATION", alloc_mand_ies, 0),
[V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE] = MSG_DEF("DE_ALLOCATION_COMPLETE", NULL, 0),
[V52_CTRL_MSGT_DE_ALLOCATION_REJECT] = MSG_DEF("DE_ALLOCATION_REJECT", alloc_rej_mand_ies, 0),
[V52_CTRL_MSGT_AUDIT] = MSG_DEF("AUDIT", NULL, 0),
[V52_CTRL_MSGT_AUDIT_COMPLETE] = MSG_DEF("AUDIT_COMPLETE", NULL, 0),
[V52_CTRL_MSGT_AN_FAULT] = MSG_DEF("AN_FAULT", NULL, 0),
[V52_CTRL_MSGT_AN_FAULT_ACK] = MSG_DEF("AN_FAULT_ACK", NULL, 0),
[V52_CTRL_MSGT_PROTOCOL_ERROR] = MSG_DEF("PROTOCOL_ERROR", prot_err_mand_ies, 0),
/* G.965 Section 18.3 PP */
[V52_CTRL_MSGT_PP_SWITCH_OVER_REQ] = MSG_DEF("SWITCH_OVER_REQ", pp_swo_mand_ies, 0),
[V52_CTRL_MSGT_PP_SWITCH_OVER_COM] = MSG_DEF("SWITCH_OVER_COM", pp_swo_mand_ies, 0),
[V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM] = MSG_DEF("OS_SWITCH_OVER_COM", pp_swo_mand_ies, 0),
[V52_CTRL_MSGT_PP_SWITCH_OVER_ACK] = MSG_DEF("SWITCH_OVER_ACK", pp_swo_mand_ies, 0),
[V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT] = MSG_DEF("SWITCH_OVER_REJECT", pp_swo_rej_mand_ies, 0),
[V52_CTRL_MSGT_PP_PROTOCOL_ERROR] = MSG_DEF("PROTOCOL_ERROR", pp_perr_mand_ies, 0),
[V52_CTRL_MSGT_PP_RESET_SN_COM] = MSG_DEF("RESET_SN_COM", NULL, 0),
[V52_CTRL_MSGT_PP_RESET_SN_ACK] = MSG_DEF("RESET_SN_ACK", NULL, 0),
},
.ie_def = {
/* single byte, only upper nibble matches IEI */
[V51_CTRL_IEI_PULSE_NOTIFICATION] = { 0, "PULSE_NOTIFICATION" },
[V51_CTLR_IEI_LINE_NOTIFICATION] = { 0, "LINE_NOTIFICATION" },
[V51_CTLR_IEI_STATE] = { 0, "STATE" },
[V51_CTLR_IEI_AUTONOMOUS_SIG_SEQ] = { 0, "AUTONOMOUS_SIG_SEQ" },
[V51_CTLR_IEI_SEQUENCE_RESPONSE] = { 0, "SEQUENCE_RESPONSE" },
/* single byte: ISDN */
[V51_CTRL_IEI_PERFORMANCE_GRADING] = { 0, "PERFORMANCE_GRADING" },
[V51_CTRL_IEI_REJECTION_CAUSE] = { 0, "REJECTION_CAUSE" },
/* variable length: PSTN */
[V51_CTRL_IEI_SEQUENCE_NR] = { 1, "SEQUENCE_NR" },
[V51_CTRL_IEI_CADENCED_RINGING] = { 1, "CADENCED_RINGING" },
[V51_CTRL_IEI_PULSED_SIGNAL] = { 2, "PULSED_SIGNAL" },
[V51_CTRL_IEI_STEADY_SIGNAL] = { 1, "STEADY_SIGNAL" },
[V51_CTRL_IEI_DIGIT_SIGNAL] = { 1, "DIGIT_SIGNAL" },
[V51_CTRL_IEI_RECOGNITION_TIME] = { 2, "RECOGNITION_TIME" },
[V51_CTRL_IEI_ENABLE_AUTONOMOUS_ACK] = { 2, "ENABLE_AUTONOMOUS_ACK" },
[V51_CTRL_IEI_DISABLE_AUTONOMOUS_ACK] = { 1, "DISABLE_AUTONOMOUS_ACK" },
[V51_CTRL_IEI_CAUSE] = { 2, "CAUSE" },
[V51_CTRL_IEI_RESOURCE_UNAVAILABLE] = { 1, "RESOURCE_UNAVAILABLE" },
[V51_CTRL_IEI_ENABLE_METERING] = { 1, "ENABLE_METERING" },
[V51_CTRL_IEI_METERING_REPORT] = { 2, "METERING_REPORT" },
[V51_CTRL_IEI_ATTENUATION] = { 1, "ATTENUATION" },
/* variable length: ISDN */
[V51_CTRL_IEI_CTRL_F_ELEMENT] = { 1, "CTRL_F_ELEMENT" },
[V51_CTRL_IEI_CTRL_F_ID] = { 1, "CTRL_F_ID" },
[V51_CTRL_IEI_VARIANT] = { 1, "VARIANT" },
[V51_CTRL_IEI_INTERFACE_ID] = { 3, "INTERFACE_ID" },
/* variable length: LCP */
[V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION] = { 1, "LINK_CTRL_FUNCTION" },
/* variable length: BCC */
[V52_CTRL_IEI_BCC_USER_PORT_ID] = { 2, "USER_PORT_ID" },
[V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID] = { 1, "ISDN_PORT_TS_ID" },
[V52_CTRL_IEI_BCC_V5_TS_ID] = { 2, "V5_TS_ID" },
[V52_CTRL_IEI_BCC_MULTI_TS_MAP] = { 9, "MULTI_TS_MAP" },
[V52_CTRL_IEI_BCC_REJECT_CAUSE] = { 1, "REJECT_CAUSE" },
[V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE] = { 1, "PROTOCOL_ERR_CAUSE" },
[V52_CTRL_IEI_BCC_CONNECTION_INCOMPLETE]= { 1, "CONNECTION_INCOMPLETE" },
[V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY] = { 1, "INFO_TRANSFER_CAPABILITY" },
/* variable length: PP */
[V52_CTRL_IEI_PP_SEQUENCE_NR] = { 1, "SEQUENCE_NR" },
[V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID] = { 2, "PHYSICAL_C_CHAN_ID" },
[V52_CTRL_IEI_PP_REJECTION_CAUSE] = { 1, "REJECTION_CAUSE" },
[V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE] = { 1, "PROTOCOL_ERR_CAUSE" },
},
.msgt_names = v51_ctrl_msg_typ_str,
};
struct v5x_user_port *v5x_user_port_find(struct v5x_instance *v5i, uint16_t nr)
{
struct v5x_user_port *v5up;
llist_for_each_entry(v5up, &v5i->user_ports, list) {
if (v5up->nr == l3_addr)
return v5up;
}
return NULL;
}
static int v51_rcv_pstn(struct v5x_user_port *v5i, struct msgb *msg, const struct tlv_parsed *tp)
{
struct v51_l3_hdr *l3h = msgb_l3(msg);
switch (l3h->msg_type) {
case V51_CTRL_MSGT_ESTABLISH:
case V51_CTRL_MSGT_ESTABLISH_ACK:
case V51_CTRL_MSGT_SIGNAL:
case V51_CTRL_MSGT_SIGNAL_ACK:
case V51_CTRL_MSGT_DISCONNECT:
case V51_CTRL_MSGT_DISCONNECT_ACK:
case V51_CTRL_MSGT_STATUS_ENQUIRY:
case V51_CTRL_MSGT_STATUS:
case V51_CTRL_MSGT_PROTOCOL_PARAMETER:
return -EINVAL;
}
}
static int v52_rcv_pp(struct v5x_instance *v5i, struct msgb *msg, const struct tlv_parsed *tp)
{
struct v51_l3_hdr *l3h = msgb_l3(msg);
}
static int v52_rcv_bcc(struct v5x_instance *v5i, struct msgb *msg, const struct tlv_parsed *tp)
{
struct v51_l3_hdr *l3h = msgb_l3(msg);
}
static int v52_rcv_lcp(struct v5x_instance *v5i, struct msgb *msg, const struct tlv_parsed *tp)
{
struct v51_l3_hdr *l3h = msgb_l3(msg);
}
/* main entry point for received V5 messages */
int v5x_rcv(struct v5x_instance *v5i, struct msgb *msg)
{
struct v51_l3_hdr *l3h = msgb_l3(msg);
struct tlv_parsed tp;
uint16_t l3_addr;
if (msgb_l3len(msg) < sizeof(*l3h)) {
LOGP(DV5, LOGL_ERROR, "Received short message (%u bytes < %u)\n", msgb_l3len(msg), sizeof(*l3h));
return -EINVAL;
}
if (l3h->pdisc != V51_CTRL_PDISC) {
LOGP(DV5, LOGL_ERROR, "Received unsupported protocol discriminator 0x%02x\n", l3h->pdisc);
return -EINVAL;
}
rc = osmo_tlv_prot_parse(v51_ctrl_msg_tlv, &tp, 1, l3h->msg_type, msgb_l3(msg) + sizeof(*l3h),
msgb_l3len(msg) - sizeof(*l3h), 0, 0, DV5, __func__);
if (rc < 0)
return -EINVAL;
l3_addr = v51_l3_addr_dec(l3h->l3_addr);
switch (l3h->msg_type) {
/* PSTN signaling protocol */
case V51_CTRL_MSGT_ESTABLISH:
case V51_CTRL_MSGT_ESTABLISH_ACK:
case V51_CTRL_MSGT_SIGNAL:
case V51_CTRL_MSGT_SIGNAL_ACK:
case V51_CTRL_MSGT_DISCONNECT:
case V51_CTRL_MSGT_DISCONNECT_ACK:
case V51_CTRL_MSGT_STATUS_ENQUIRY:
case V51_CTRL_MSGT_STATUS:
case V51_CTRL_MSGT_PROTOCOL_PARAMETER:
/* look-up user port based on L3 addr? */
v5up = v5x_user_port_find(v5i, l3_addr);
if (!v5up)
return -ENODEV;
rc = v51_rcv_pstn(v5up, msg, &tp);
break;
/* control protocol (Section 14 G.964) */
case V51_CTRL_MSGT_PORT_CTRL:
case V51_CTRL_MSGT_PORT_CTRL_ACK:
case V51_CTRL_MSGT_COMMON_CTRL:
case V51_CTRL_MSGT_COMMON_CTRL_ACK:
rc = v51_rcv_ctrl(v5i, msg, &tp);
break;
/* protection protocol (Section 18 G.965) */
case V52_CTRL_MSGT_PP_SWITCH_OVER_REQ:
case V52_CTRL_MSGT_PP_SWITCH_OVER_COM:
case V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM:
case V52_CTRL_MSGT_PP_SWITCH_OVER_ACK:
case V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT:
case V52_CTRL_MSGT_PP_PROTOCOL_ERROR:
case V52_CTRL_MSGT_PP_RESET_SN_COM:
case V52_CTRL_MSGT_PP_RESET_SN_ACK:
rc = v52_rcv_pp(v5i, msg, &tp);
break;
/* BCC protocol (Section 17 G.965) */
case V52_CTRL_MSGT_ALLOCATION:
case V52_CTRL_MSGT_ALLOCATION_COMPLETE:
case V52_CTRL_MSGT_ALLOCATION_REJECT:
case V52_CTRL_MSGT_DE_ALLOCATION:
case V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE:
case V52_CTRL_MSGT_DE_ALLOCATION_REJECT:
case V52_CTRL_MSGT_AUDIT:
case V52_CTRL_MSGT_AUDIT_COMPLETE:
case V52_CTRL_MSGT_AN_FAULT:
case V52_CTRL_MSGT_AN_FAULT_ACK:
case V52_CTRL_MSGT_PROTOCOL_ERROR:
rc = v52_rcv_bcc(v5i, msg, &tp);
break;
/* Link control protocol (Section 16 G.965) */
case V52_CTRL_MSGT_LCP_LINK_CTRL:
case V52_CTRL_MSGT_LCP_LINK_CTRL_ACK:
rc = v52_rcv_lcp(v5i, msg, &tp);
break;
default:
LOGP(DV5, LOGL_ERROR, "Received unsupported message type %s\n",
osmo_tlv_prot_msg_name(v51_ctrl_msg_tlv, l3h->msg_type);
}
}

View File

@ -1,252 +0,0 @@
#pragma once
#include <stdint.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/tlv.h>
/* Definitions related to the V5.1 and V5.2 interface between AN (access
* network) and LE (local exchange) as per ITU-T G.964 + G.965.
*
* (C) 2021 by Harald Welte <laforge@gnumonks.org>
*/
/***********************************************************************/
/* protocol wire format */
/***********************************************************************/
/* Table 14/G.964 */
#define V51_CTRL_PDISC 0x48
/* Table 1/G.965 + Table 1/G.964 */
#define V51_DLADDR_PSTN 8176
#define V51_DLADDR_CTRL 8177
#define V52_DLADDR_BCC 8178
#define V52_DLADDR_PROTECTION 8179
#define V52_DLADDR_LCP 8180
/* G.965 Section 13.1 */
struct v51_l3_hdr {
uint8_t pdisc;
uint16_t l3_addr; /* with C/R and EA bits! */
uint8_t msg_type;
uint8_t payload[0];
} __attribute__ ((packed));
/* G.964 Section 14.4.2.3 Figure 33 + 34 */
static inline bool v51_l3_addr_is_isdn(uint16_t in)
{
/* extract two bytes */
uint8_t b1 = in >> 8;
uint8_t b2 = in & 0xff;
if ((b1 & 0x03) == 0x00 && (b2 & 0x01))
return true;
return false;
}
static inline uint16_t v51_l3_addr_dec(uint16_t in, bool *is_isdn)
{
/* extract two bytes */
uint8_t b1 = in >> 8;
uint8_t b2 = in & 0xff;
if (v51_l3_addr_is_isdn(in)) {
/* remove EA/C/R bits */
uint8_t upper = b1 >> 2;
uint8_t lower = b2 >> 1;
if (is_isdn)
*is_isdn = true;
/* shift them together */
return lower | (upper << 7);
} else{
uint8_t upper = b1 >> 1;
uint8_t lower = b2;
if (is_isdn)
*is_isdn = false;
return lower | (upper << 8);
}
}
static inline uint16_t v51_l3_addr_enc(uint16_t in, bool is_isdn)
{
uint8_t b1, b2;
if (is_isdn) {
uint8_t upper = in >> 7;
uint8_t lower = in & 0x7f;
b1 = upper << 2;
b2 = (lower << 1) | 0x01;
} else {
uint8_t upper = in >> 8;
uint8_t lower = in & 0xff;
b1 = (upper << 1) | 0x01;
b2 = lower;
}
return (b1 << 8) | b2;
}
/* Table 15 + 52/G.964 */
enum v51_ctrl_msg_type {
/* 000xxxx: PSTN protocol message types */
V51_CTRL_MSGT_ESTABLISH = 0x00,
V51_CTRL_MSGT_ESTABLISH_ACK = 0x01,
V51_CTRL_MSGT_SIGNAL = 0x02,
V51_CTRL_MSGT_SIGNAL_ACK = 0x03,
V51_CTRL_MSGT_DISCONNECT = 0x08,
V51_CTRL_MSGT_DISCONNECT_ACK = 0x09,
V51_CTRL_MSGT_STATUS_ENQUIRY = 0x0c,
V51_CTRL_MSGT_STATUS = 0x0d,
V51_CTRL_MSGT_PROTOCOL_PARAMETER = 0x0e,
/* 0010xxx: Control protocol message types */
V51_CTRL_MSGT_PORT_CTRL = 0x10,
V51_CTRL_MSGT_PORT_CTRL_ACK = 0x11,
V51_CTRL_MSGT_COMMON_CTRL = 0x12,
V51_CTRL_MSGT_COMMON_CTRL_ACK = 0x13,
/* 0011xxx: Protection protocol message types */
V52_CTRL_MSGT_PP_SWITCH_OVER_REQ = 0x18,
V52_CTRL_MSGT_PP_SWITCH_OVER_COM = 0x19,
V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM = 0x1a,
V52_CTRL_MSGT_PP_SWITCH_OVER_ACK = 0x1b,
V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT = 0x1c,
V52_CTRL_MSGT_PP_PROTOCOL_ERROR = 0x1d,
V52_CTRL_MSGT_PP_RESET_SN_COM = 0x1e,
V52_CTRL_MSGT_PP_RESET_SN_ACK = 0x1f,
/* 010xxxx: BCC protocol message types */
V52_CTRL_MSGT_ALLOCATION = 0x20,
V52_CTRL_MSGT_ALLOCATION_COMPLETE = 0x21,
V52_CTRL_MSGT_ALLOCATION_REJECT = 0x22,
V52_CTRL_MSGT_DE_ALLOCATION = 0x23,
V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE = 0x24,
V52_CTRL_MSGT_DE_ALLOCATION_REJECT = 0x25,
V52_CTRL_MSGT_AUDIT = 0x26,
V52_CTRL_MSGT_AUDIT_COMPLETE = 0x27,
V52_CTRL_MSGT_AN_FAULT = 0x28,
V52_CTRL_MSGT_AN_FAULT_ACK = 0x29,
V52_CTRL_MSGT_PROTOCOL_ERROR = 0x2a,
/* 0110xxx: Link control protocol message types */
V52_CTRL_MSGT_LCP_LINK_CTRL = 0x30,
V52_CTRL_MSGT_LCP_LINK_CTRL_ACK = 0x31,
};
extern const struct value_string v51_ctrl_msg_typ_str[];
/* Table 17 + 53/G.964 */
enum v51_ctrl_iei {
/* single byte, only upper nibble matches IEI */
V51_CTRL_IEI_PULSE_NOTIFICATION = 0xc0,
V51_CTLR_IEI_LINE_NOTIFICATION = 0x80,
V51_CTLR_IEI_STATE = 0x90,
V51_CTLR_IEI_AUTONOMOUS_SIG_SEQ = 0xa0,
V51_CTLR_IEI_SEQUENCE_RESPONSE = 0xb0,
/* single byte: ISDN */
V51_CTRL_IEI_PERFORMANCE_GRADING = 0xe0,
V51_CTRL_IEI_REJECTION_CAUSE = 0xf0,
/* variable length: PSTN */
V51_CTRL_IEI_SEQUENCE_NR = 0x00,
V51_CTRL_IEI_CADENCED_RINGING = 0x01,
V51_CTRL_IEI_PULSED_SIGNAL = 0x02,
V51_CTRL_IEI_STEADY_SIGNAL = 0x03,
V51_CTRL_IEI_DIGIT_SIGNAL = 0x04,
V51_CTRL_IEI_RECOGNITION_TIME = 0x10,
V51_CTRL_IEI_ENABLE_AUTONOMOUS_ACK = 0x11,
V51_CTRL_IEI_DISABLE_AUTONOMOUS_ACK = 0x12,
V51_CTRL_IEI_CAUSE = 0x13,
V51_CTRL_IEI_RESOURCE_UNAVAILABLE = 0x14,
V51_CTRL_IEI_ENABLE_METERING = 0x32,
V51_CTRL_IEI_METERING_REPORT = 0x33,
V51_CTRL_IEI_ATTENUATION = 0x34,
/* variable length: ISDN */
V51_CTRL_IEI_CTRL_F_ELEMENT = 0x20,
V51_CTRL_IEI_CTRL_F_ID = 0x21,
V51_CTRL_IEI_VARIANT = 0x22,
V51_CTRL_IEI_INTERFACE_ID = 0x23,
/* variable length: LCP */
V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION = 0x30,
/* variable length: BCC */
V52_CTRL_IEI_BCC_USER_PORT_ID = 0x40,
V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID = 0x41,
V52_CTRL_IEI_BCC_V5_TS_ID = 0x42,
V52_CTRL_IEI_BCC_MULTI_TS_MAP = 0x43,
V52_CTRL_IEI_BCC_REJECT_CAUSE = 0x44,
V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE = 0x45,
V52_CTRL_IEI_BCC_CONNECTION_INCOMPLETE = 0x46,
V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY = 0x47,
/* variable-length: Protection */
V52_CTRL_IEI_PP_SEQUENCE_NR = 0x50,
V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID = 0x51,
V52_CTRL_IEI_PP_REJECTION_CAUSE = 0x52,
V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE = 0x53,
};
extern const struct value_string v51_ctrl_iei_str[];
extern const struct tlv_definition v51_ctrl_tlv_def[];
extern const struct osmo_tlv_prot_def v51_ctrl_msg_tlv;
/* 14.4.2.5.4 Table 56/G.964 - Coding of Control Function Element */
enum v51_ctrl_func_el {
V51_CTRL_FE101_ACTIVATE_ACCESS = 0x01,
V51_CTRL_FE102_ACT_INIT_BY_USER = 0x02,
V51_CTRL_FE103_DS_ACTIVATED = 0x03,
V51_CTRL_FE104_ACCESS_ACTIVATED = 0x04,
V51_CTRL_FE105_DEACTIVATE_ACCESS = 0x05,
V51_CTRL_FE106_ACCESS_DEACTIVATED = 0x06,
V51_CTRL_FE201_UNBLOCK = 0x11,
V51_CTRL_FE203_BLOCK = 0x13,
V51_CTRL_FE205_BLOCK_REQ = 0x15,
V51_CTRL_FE206_PERFORMANCE_GRADING = 0x16,
V51_CTRL_FE207_D_CHANNEL_BLOCK = 0x17,
V51_CTRL_FE208_D_CHANNEL_UNBLOCK = 0x18,
};
extern const struct value_string v51_ctrl_func_el_str[];
/* 14.4.2.5.5 Table 57/G.964 - Coding of Control Function ID */
enum v51_ctrl_func_id {
V51_CTRL_ID_VERIFY_RE_PROVISIONING = 0x00,
V51_CTRL_ID_READY_FOR_RE_PROVISIONING = 0x01,
V51_CTRL_ID_NOT_READY_FOR_RE_PROVISIONING = 0x02,
V51_CTRL_ID_SWITCH_OVER_TO_NEW_VARIANT = 0x03,
V51_CTRL_ID_RE_PROVISIONING_STARTED = 0x04,
V51_CTRL_ID_CANNOT_RE_PROVISION = 0x05,
V51_CTRL_ID_REQUEST_VARIANT_AND_INTERFACE_ID = 0x06,
V51_CTRL_ID_VARIANT_AND_INTERFACE_ID = 0x07,
V51_CTRL_ID_BLOCKING_STARTED = 0x08,
V51_CTRL_ID_RESTART_REQUEST = 0x10,
V51_CTRL_ID_RESTART_COMPLETE = 0x11,
};
extern const struct value_string v51_ctrl_func_id_str[];
/* 13.4.7.9 Cause */
enum v51_cause_type {
V51_CTRL_CAUSE_T_RESP_TO_STATUS_ENQ = 0x00,
V51_CTRL_CAUSE_T_L3_ADDRESS_ERROR = 0x03,
V51_CTRL_CAUSE_T_MSG_TYPE_UNRECOGNIZED = 0x04,
V51_CTRL_CAUSE_T_OUT_OF_SEQUENCE_IE = 0x05,
V51_CTRL_CAUSE_T_REPEATED_OPT_IE = 0x06,
V51_CTRL_CAUSE_T_MAND_IE_MISSING = 0x07,
V51_CTRL_CAUSE_T_UNRECOGNIZED_IE = 0x08,
V51_CTRL_CAUSE_T_MAND_IE_CONTENT_ERROR = 0x09,
V51_CTRL_CAUSE_T_OPT_IE_CONTENT_ERROR = 0x0a,
V51_CTRL_CAUSE_T_MSG_INCOMP_PATH_STATE = 0x0b,
V51_CTRL_CAUSE_T_REPEATED_MAND_IE = 0x0c,
V51_CTRL_CAUSE_T_TOO_MANY_IE = 0x0d,
};
extern const struct value_string v51_cause_type_str[];
const struct osmo_tlv_prot_def v51_ctrl_msg_tlv;
/* 16.3.2.2 Table 22/G.965 */
enum v52_link_ctrl_func {
V52_LCP_FE_IDReq = 0,
V52_LCP_FE_IDAck = 1,
V52_LCP_FE_IDRel = 2,
V52_LCP_FE_IDRej = 3,
V52_LCP_FE_301_302_LINK_UNBLOCK = 4,
V52_LCP_FE_303_304_LINK_BLOCK = 5,
V52_LCP_FE_305_DEF_LINK_BLOCK_REQ = 6,
V52_LCP_FE_306_NON_DEF_LINK_BLOCK_REQ = 7,
};