From 7e25e191af05f467dad7aae4ac643727636c35ba Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sun, 1 Sep 2019 13:15:04 +0200 Subject: Modem emulator for "Datenklo" with AM7910/AM7911 emulation --- .gitignore | 1 + configure.ac | 9 + docs/tv.html | 5 + src/Makefile.am | 11 +- src/datenklo/Makefile.am | 35 + src/datenklo/am791x.c | 1266 +++++++++++++++++++++++++++++++++++ src/datenklo/am791x.h | 107 +++ src/datenklo/datenklo.c | 1668 ++++++++++++++++++++++++++++++++++++++++++++++ src/datenklo/datenklo.h | 88 +++ src/datenklo/device.c | 559 ++++++++++++++++ src/datenklo/device.h | 7 + src/datenklo/fioc.h | 32 + src/datenklo/main.c | 320 +++++++++ src/datenklo/uart.c | 166 +++++ src/datenklo/uart.h | 34 + src/libdebug/debug.c | 5 +- src/libdebug/debug.h | 4 + 17 files changed, 4315 insertions(+), 2 deletions(-) create mode 100644 src/datenklo/Makefile.am create mode 100644 src/datenklo/am791x.c create mode 100644 src/datenklo/am791x.h create mode 100644 src/datenklo/datenklo.c create mode 100644 src/datenklo/datenklo.h create mode 100644 src/datenklo/device.c create mode 100644 src/datenklo/device.h create mode 100644 src/datenklo/fioc.h create mode 100644 src/datenklo/main.c create mode 100644 src/datenklo/uart.c create mode 100644 src/datenklo/uart.h diff --git a/.gitignore b/.gitignore index 8dc08ca..237ea73 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,7 @@ src/imts/imts-dialer src/jolly/jollycom src/tv/osmotv src/radio/osmoradio +src/datenklo/datenklo sim/cnetz_sim src/test/test_filter src/test/test_sendevolumenregler diff --git a/configure.ac b/configure.ac index ed78ff0..f2ab98f 100644 --- a/configure.ac +++ b/configure.ac @@ -32,19 +32,27 @@ AC_ARG_WITH([alsa], [AS_HELP_STRING([--with-alsa], [compile with Alsa driver @<: AC_ARG_WITH([uhd], [AS_HELP_STRING([--with-uhd], [compile with UHD driver @<:@default=check@:>@]) ], [], [with_uhd="check"]) AC_ARG_WITH([soapy], [AS_HELP_STRING([--with-soapy], [compile with SoapySDR driver @<:@default=check@:>@]) ], [], [with_soapy="check"]) AC_ARG_WITH([imagemagick], [AS_HELP_STRING([--with-imagemagick], [compile with ImageMagick support @<:@default=check@:>@]) ], [], [with_imagemagick="check"]) +AC_ARG_WITH([fuse], [AS_HELP_STRING([--with-fuse], [compile with FUSE support @<:@default=check@:>@]) ], [], [with_fuse="check"]) AS_IF([test "x$with_alsa" != xno], [PKG_CHECK_MODULES(ALSA, alsa >= 1.0, with_alsa=yes, with_alsa=no)]) AS_IF([test "x$with_uhd" != xno], [PKG_CHECK_MODULES(UHD, uhd >= 3.0.0, with_sdr=yes with_uhd=yes, with_uhd=no)]) AS_IF([test "x$with_soapy" != xno], [PKG_CHECK_MODULES(SOAPY, SoapySDR >= 0.5.0, with_sdr=yes with_soapy=yes, with_soapy=no)]) AS_IF([test "x$with_imagemagick" != xno], [PKG_CHECK_MODULES(IMAGEMAGICK, ImageMagick >= 7.0.0, with_imagemagick=yes, with_imagemagick=no)]) +AS_IF([test "x$with_fuse" != xno], with_fuse=check) +AS_IF([test "x$with_fuse" == xcheck], [PKG_CHECK_MODULES(FUSE, fuse3 >= 0.30.0, with_fuse=yes, with_fuse=check)]) +AS_IF([test "x$with_fuse" == xcheck], [PKG_CHECK_MODULES(FUSE, fuse2 >= 0.29.0, with_fuse=yes, with_fuse=check)]) +AS_IF([test "x$with_fuse" == xcheck], [PKG_CHECK_MODULES(FUSE, fuse >= 0.29.0, with_fuse=yes, with_fuse=check)]) +AS_IF([test "x$with_fuse" == xcheck], with_fuse=no) AM_CONDITIONAL(HAVE_ALSA, test "x$with_alsa" == "xyes" ) AM_CONDITIONAL(HAVE_UHD, test "x$with_uhd" == "xyes" ) AM_CONDITIONAL(HAVE_SOAPY, test "x$with_soapy" == "xyes" ) AM_CONDITIONAL(HAVE_SDR, test "x$with_sdr" == "xyes" ) AM_CONDITIONAL(HAVE_MAGICK, test "x$with_imagemagick" == "xyes" ) +AM_CONDITIONAL(HAVE_FUSE, test "x$with_fuse" == "xyes" ) AS_IF([test "x$with_alsa" == "xyes"],[AC_MSG_NOTICE( Compiling with Alsa support )], [AC_MSG_NOTICE( Alsa sound card not supported. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )]) AS_IF([test "x$with_uhd" == "xyes"],[AC_MSG_NOTICE( Compiling with UHD SDR support )], [AC_MSG_NOTICE( UHD SDR not supported. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )]) AS_IF([test "x$with_soapy" == "xyes"],[AC_MSG_NOTICE( Compiling with SoapySDR support )], [AC_MSG_NOTICE( SoapySDR not supported. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )]) AS_IF([test "x$with_imagemagick" == "xyes"],[AC_MSG_NOTICE( Compiling with ImageMagick )],[AC_MSG_NOTICE( ImageMagick not supported. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )]) +AS_IF([test "x$with_fuse" == "xyes"],[AC_MSG_NOTICE( Compiling with FUSE )],[AC_MSG_NOTICE( FUSE not supported. There will be no analog modem support. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )]) AS_IF([test "x$with_alsa" != "xyes" -a "x$with_sdr" != "xyes"],[AC_MSG_FAILURE( Without sound nor SDR support this project does not make sense. Please support sound card for analog transceivers or better SDR!" )],[]) @@ -88,6 +96,7 @@ AC_OUTPUT( src/jolly/Makefile src/tv/Makefile src/radio/Makefile + src/datenklo/Makefile src/test/Makefile src/Makefile sim/Makefile diff --git a/docs/tv.html b/docs/tv.html index 3aa48d2..81f7122 100644 --- a/docs/tv.html +++ b/docs/tv.html @@ -26,6 +26,11 @@ The following test signals are supported:

+

+Importaint: SDR is required! It must be capable of about 15 Mega samples per second. +

+If you use LimeSDR, you MUST use USB 3.0 to have enough bandwidth! +


[Back to main page]

diff --git a/src/Makefile.am b/src/Makefile.am index 3950a81..fe0ec97 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -49,6 +49,15 @@ SUBDIRS += \ imts \ jolly \ tv \ - radio \ + radio + +if HAVE_SDR +if HAVE_FUSE +SUBDIRS += \ + datenklo +endif +endif + +SUBDIRS += \ test diff --git a/src/datenklo/Makefile.am b/src/datenklo/Makefile.am new file mode 100644 index 0000000..05ce78e --- /dev/null +++ b/src/datenklo/Makefile.am @@ -0,0 +1,35 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) -fstack-check $(FUSE_CFLAGS) + +bin_PROGRAMS = \ + datenklo + +datenklo_SOURCES = \ + am791x.c \ + uart.c \ + device.c \ + datenklo.c \ + main.c +datenklo_LDADD = \ + $(COMMON_LA) \ + $(top_builddir)/src/liboptions/liboptions.a \ + $(top_builddir)/src/libdebug/libdebug.a \ + $(top_builddir)/src/libfsk/libfsk.a \ + $(top_builddir)/src/libtimer/libtimer.a \ + $(top_builddir)/src/libfm/libfm.a \ + $(top_builddir)/src/libfilter/libfilter.a \ + $(top_builddir)/src/libsound/libsound.a \ + $(top_builddir)/src/libwave/libwave.a \ + $(top_builddir)/src/libdisplay/libdisplay.a \ + $(top_builddir)/src/libsample/libsample.a \ + $(ALSA_LIBS) \ + $(FUSE_LIBS) \ + -lm + +if HAVE_ALSA +AM_CPPFLAGS += -DHAVE_ALSA +endif + +if HAVE_FUSE +AM_CPPFLAGS += -DHAVE_FUSE +endif + diff --git a/src/datenklo/am791x.c b/src/datenklo/am791x.c new file mode 100644 index 0000000..07d6364 --- /dev/null +++ b/src/datenklo/am791x.c @@ -0,0 +1,1266 @@ +/* AM7910 / AM7911 modem chip emulation and signal processing + * + * (C) 2019 by Andreas Eversberg + * 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 . + */ + +/* + * Not implemented: + * - auto answer + * - Bell 202 5 bps back channel + * - equalizer + */ + +/* + * Implementation is done according to the AM7910/AM7911 datasheet. This is + * only a (short and bad) summary, so read the datasheet!!! + * + * DTR state: + * When DTR is off, state is clamped to INIT state. + * When DTR becomes on, RX and TX state machines begin to run. + * When DTR becomes off, state machines change to INIT state immidiately. + * + * (B)RTS state: + * When (B)RTS becomes on, data transmission is enabled. + * Then (B)CTS becomes on, when the timer (B)RCON is complete. + * This means that data transmission is now allowed by upper layer. + * When (B)RTS becomes off, data transmission is disabled. + * Then (B)CTS becomes off, when the timer (B)RCOFF is complete. + * + * (B)CD state: + * When carrier is detected, timer (B)CDON is started. + * When carrier is stable and timer is complete, (B)CD becomes on and data + * reception is enabled. + * When carrier is lost, timer (B)CDOFF is started. + * When carrier timer is complete, (B)CD becomes off and data reception is + * disabled. + * While transmitting half duplex mode, (B)CD will be blocked to prevent + * carrier detection from loopback of audio signal. + * + * (B)TD data: + * When transmitting, data is requested from upper layer and forwarded into + * modulator. + * When not transmitting, this data is blocked, meaning that '1' (MARK) is + * transmitted into the modulator, regardless of the upper layer data. + * STO (soft turn off) and/or silence is sent after transmission is over. + * + * (B)RD data: + * When data reception is not blocked, data is received from the demodulator + * and forwarded towards upper layer. + * While receiving in half duplex mode, (B)RD is blocked, meaning that '1' + * (MARK) is forwarded toward upper layer, regardless fo the data from the + * demodulator. + * Squelch (mute receive audio) is used to prevent noise when turning off + * half duplex transmission. + * + * Audio level is based on milliwatts (at 600 Ohms), which is a value of 1. + */ + +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "../libtimer/timer.h" +#include "../libsample/sample.h" +#include "../libfsk/fsk.h" +#include "am791x.h" + +#define db2level(db) pow(10, (double)(db) / 20.0) +#define level2db(level) (20 * log10(level)) + +/* levels used (related to dBm0 (1 mW)) */ + +#define TX_LEVEL -3 /* according to datasheet (at 600 Ohms) */ +#define RX_CD_ON_7910 -40.5 /* according to datasheet (at 600 Ohms) */ +#define RX_CD_ON_7911 -42.0 /* according to datasheet (at 600 Ohms) */ +#define RX_CD_OFF_7910 -45.0 /* according to datasheet (at 600 Ohms) */ +#define RX_CD_OFF_7911 -47.5 /* according to datasheet (at 600 Ohms) */ +#define RX_QUALITY 0.1 /* FIXME: minium quality */ +#define BIT_ADJUST 0.5 /* must be 0.5 to completely sync each bit */ + +/* frequencies used */ + +/* standard f0, f1 (tx) f0, f1 (rx) f0, f1 (back tx+rx) */ +#define BELL_103_ORIG 1070, 1270, 2025, 2225, 0, 0, +#define BELL_103_ANS 2025, 2225, 1070, 1270, 0, 0, +#define BELL_103_ORIG_L 1070, 1270, 1070, 1270, 0, 0, +#define BELL_103_ANS_L 2025, 2225, 2025, 2225, 0, 0, +#define CCITT_V21_ORIG 1180, 980, 1850, 1650, 0, 0, +#define CCITT_V21_ANS 1850, 1650, 1180, 980, 0, 0, +#define CCITT_V21_ORG_L 1180, 980, 1180, 980, 0, 0, +#define CCITT_V21_ANS_L 1850, 1650, 1850, 1650, 0, 0, +#define CCITT_V23_M1 1700, 1300, 1700, 1300, 0, 0, +#define CCITT_V23_M1B 1700, 1300, 1700, 1300, 450, 390, +#define CCITT_V23_M2 2100, 1300, 2100, 1300, 0, 0, +#define CCITT_V23_M2B 2100, 1300, 2100, 1300, 450, 390, +#define BELL_202 2200, 1200, 2200, 1200, 0, 0, +#define BELL_202B 2200, 1200, 2200, 1200, 487, 387, +#define CCITT_V23_BACK 0, 0, 0, 0, 450, 390, +#define BELL_202_BACK 0, 0, 0, 0, 487, 387, +#define RESERVED 0, 0, 0, 0, 0, 0, + +/* timings used */ + +/* timer/std 7910 7911 */ +#define T_RCON_B103 0.2083, 0.025, +#define T_RCOFF_B103 0.0004, 0.00052, +#define T_BRCON_B103 NAN, NAN, +#define T_BRCOFF_B103 NAN, NAN, +#define T_CDON_B103 0.0291, 0.010, +#define T_CDOFF_B103 0.021, 0.007, +#define T_BCDON_B103 NAN, NAN, +#define T_BCDOFF_B103 NAN, NAN, +#define T_AT_B103 1.9, 1.9, +#define T_SIL1_B103 1.3, 2.0, +#define T_SIL2_B103 NAN, NAN, +#define T_SQ_B103 NAN, NAN, +#define T_STO_B103 NAN, NAN, + +#define T_B103 \ + T_RCON_B103 T_RCOFF_B103 T_BRCON_B103 T_BRCOFF_B103 \ + T_CDON_B103 T_CDOFF_B103 T_BCDON_B103 T_BCDOFF_B103 \ + T_AT_B103 T_SIL1_B103 T_SIL2_B103 T_SQ_B103 T_STO_B103 + +/* timer/std 7910 7911 */ +#define T_RCON_V21 0.400, 0.025, +#define T_RCOFF_V21 0.0004, 0.00052, +#define T_BRCON_V21 NAN, NAN, +#define T_BRCOFF_V21 NAN, NAN, +#define T_CDON_V21 0.301, 0.010, +#define T_CDOFF_V21 0.021, 0.007, +#define T_BCDON_V21 NAN, NAN, +#define T_BCDOFF_V21 NAN, NAN, +#define T_AT_V21 3.0, 3.0, +#define T_SIL1_V21 1.9, 2.0, +#define T_SIL2_V21 NAN, 0.075, +#define T_SQ_V21 NAN, NAN, +#define T_STO_V21 NAN, NAN, + +#define T_V21 \ + T_RCON_V21 T_RCOFF_V21 T_BRCON_V21 T_BRCOFF_V21 \ + T_CDON_V21 T_CDOFF_V21 T_BCDON_V21 T_BCDOFF_V21 \ + T_AT_V21 T_SIL1_V21 T_SIL2_V21 T_SQ_V21 T_STO_V21 + +/* timer/std 7910 7911 */ +#define T_RCON_V23 0.2083, 0.008, +#define T_RCOFF_V23 0.0004, 0.00052, +#define T_BRCON_V23 0.0823, 0.0823, +#define T_BRCOFF_V23 0.0004, 0.0005, +#define T_CDON_V23 0.011, 0.003, +#define T_CDOFF_V23 0.0035, 0.002, +#define T_BCDON_V23 0.017, 0.018, +#define T_BCDOFF_V23 0.021, 0.022, +#define T_AT_V23 3.0, 3.0, +#define T_SIL1_V23 1.9, 2.0, +#define T_SIL2_V23 NAN, 0.075, +#define T_SQ_V23 0.1563, 0.009, +#define T_STO_V23 NAN, 0.008, + +#define T_V23 \ + T_RCON_V23 T_RCOFF_V23 T_BRCON_V23 T_BRCOFF_V23 \ + T_CDON_V23 T_CDOFF_V23 T_BCDON_V23 T_BCDOFF_V23 \ + T_AT_V23 T_SIL1_V23 T_SIL2_V23 T_SQ_V23 T_STO_V23 + +/* timer/std 7910 7911 */ +#define T_RCON_B202 0.1833, 0.008, +#define T_RCOFF_B202 0.0004, 0.00052, +#define T_BRCON_B202 0.0823, 0.0823, +#define T_BRCOFF_B202 0.0004, 0.0005, +#define T_CDON_B202 0.018, 0.003, +#define T_CDOFF_B202 0.012, 0.002, +#define T_BCDON_B202 0.017, 0.018, +#define T_BCDOFF_B202 0.021, 0.022, +#define T_AT_B202 1.9, 1.9, +#define T_SIL1_B202 1.3, 2.0, +#define T_SIL2_B202 NAN, NAN, +#define T_SQ_B202 0.1563, 0.009, +#define T_STO_B202 0.024, 0.008, + +#define T_B202 \ + T_RCON_B202 T_RCOFF_B202 T_BRCON_B202 T_BRCOFF_B202 \ + T_CDON_B202 T_CDOFF_B202 T_BCDON_B202 T_BCDOFF_B202 \ + T_AT_B202 T_SIL1_B202 T_SIL2_B202 T_SQ_B202 T_STO_B202 + +#define T_RES \ + NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, \ + NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, \ + NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, + +/* mode definition */ + +static struct am791x_mode { + int sup_7910, sup_7911; /* supported */ + int f0_tx, f1_tx; /* frequencies */ + int f0_rx, f1_rx; + int f0_back, f1_back; + double t_rcon_7910, t_rcon_7911; /* timers */ + double t_rcoff_7910, t_rcoff_7911; + double t_brcon_7910, t_brcon_7911; + double t_brcoff_7910, t_brcoff_7911; + double t_cdon_7910, t_cdon_7911; + double t_cdoff_7910, t_cdoff_7911; + double t_bcdon_7910, t_bcdon_7911; + double t_bcdoff_7910, t_bcdoff_7911; + double t_at_7910, t_at_7911; + double t_sil1_7910, t_sil1_7911; + double t_sil2_7910, t_sil2_7911; + double t_sq_7910, t_sq_7911; + double t_sto_7910, t_sto_7911; + int fullduplex; /* duplex */ + int loopback_main, loopback_back; /* loopback */ + int equalizer, sto; /* equalizer, soft turn off */ + int bell_202; /* is BELL 202 mode */ + double max_baud; /* maximum baud rate */ + const char *description; /* description */ +} am791x_modes[32] = { + /*sup frequencies timers duplex loop EQ,STO BELL202 maxBAUD description */ + /* normal modes */ + { 1, 1, BELL_103_ORIG T_B103 1, 0, 0, 0, 0, 0, 300, "Bell 103 originate (300 bps full-duplex)" }, + { 1, 1, BELL_103_ANS T_B103 1, 0, 0, 0, 0, 1, 300, "Bell 103 answer (300 bps full-duplex)" }, + { 1, 1, BELL_202 T_B202 0, 0, 0, 0, 0, 1, 1200, "Bell 202 (1200 bps half-duplex)" }, + { 1, 1, BELL_202 T_B202 0, 0, 0, 1, 0, 1, 1200, "Bell 202 with equalizer (1200 bps half-duplex)" }, + { 1, 1, CCITT_V21_ORIG T_V21 1, 0, 0, 0, 0, 0, 300, "CCITT V.21 originate (300 bps full-duplex)" }, + { 1, 1, CCITT_V21_ANS T_V21 1, 0, 0, 0, 0, 0, 300, "CCITT V.21 answer (300 bps full-duplex)" }, + { 1, 1, CCITT_V23_M2 T_V23 0, 0, 0, 0, 0, 0, 1200, "CCITT V.23 mode 2 (1200 bps half-duplex)" }, + { 1, 1, CCITT_V23_M2 T_V23 0, 0, 0, 1, 0, 0, 1200, "CCITT V.23 mode 2 with equalizer (1200 bps half-duplex)" }, + { 1, 1, CCITT_V23_M1B T_V23 0, 0, 0, 0, 0, 0, 1200, "CCITT V.23 mode 1 (600/75 bps half-duplex)" }, + { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, + { 0, 1, BELL_202B T_B202 0, 0, 0, 0, 0, 1, 1200, "Bell 202 (1200/150 bps half-duplex)" }, + { 0, 1, BELL_202B T_B202 0, 0, 0, 1, 0, 1, 1200, "Bell 202 with equalizer (1200/150 bps half-duplex)" }, + { 0, 1, CCITT_V23_M1B T_V23 0, 0, 0, 0, 1, 0, 1200, "CCITT V.23 mode 1 with soft turn-off (600/75 bps half-duplex)" }, + { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, + { 0, 1, CCITT_V23_M2B T_V23 0, 0, 0, 0, 1, 0, 1200, "CCITT V.23 mode 2 with soft turn-off (1200/75 bps half-duplex)" }, + { 0, 1, CCITT_V23_M2B T_V23 0, 0, 0, 1, 1, 0, 1200, "CCITT V.23 mode 2 with soft turn-off and equalizer (1200/75 bps half-duplex)" }, + /* loopback modes */ + { 1, 1, BELL_103_ORIG_L T_B103 0, 1, 0, 0, 0, 0, 300, "Bell 103 orig loopback (300 bps)" }, + { 1, 1, BELL_103_ANS_L T_B103 0, 1, 0, 0, 0, 0, 300, "Bell 103 answer loopback (300 bps)" }, + { 1, 1, BELL_202 T_B202 0, 1, 0, 0, 0, 1, 1200, "Bell 202 main loopback (1200 bps)" }, + { 1, 1, BELL_202 T_B202 0, 1, 0, 1, 0, 1, 1200, "Bell 202 main loopback with equalizer (1200 bps)" }, + { 1, 1, CCITT_V21_ORG_L T_V21 0, 1, 0, 0, 0, 0, 300, "CCITT V.21 originate loopback (300 bps)" }, + { 1, 1, CCITT_V21_ANS_L T_V21 0, 1, 0, 0, 0, 0, 300, "CCITT V.21 answer loopback (300 bps)" }, + { 1, 1, CCITT_V23_M2 T_V23 0, 1, 0, 0, 0, 0, 1200, "CCITT V.23 mode 2 main loopback (1200 bps)" }, + { 1, 1, CCITT_V23_M2 T_V23 0, 1, 0, 1, 0, 0, 1200, "CCITT V.23 mode 2 main loopback with equalizer (1200 bps)" }, + { 1, 1, CCITT_V23_M1 T_V23 0, 1, 0, 0, 0, 0, 1200, "CCITT V.23 mode 1 main loopback (600 bps)" }, + { 1, 1, CCITT_V23_BACK T_V23 0, 0, 1, 0, 0, 0, 150, "CCITT V.23 back loopback (75/150 bps)" }, + { 0, 1, BELL_202_BACK T_B202 0, 0, 1, 0, 0, 1, 150, "Bell 202 back loopback (150 bps)" }, + { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, + { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, + { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, + { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, + { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, +}; + +const char *am791x_state_names[] = { + "INIT", + "RCON", + "CDON", + "DATA", + "RCOFF", + "CDOFF", + "STO_OFF", + "SQ_OFF", + "BRCON", + "BCDON", + "BDATA", + "BRCOFF", + "BCDOFF", +}; + +/* list all modes */ +void am791x_list_mc(enum am791x_type type) +{ + int i; + const char *description; + + for (i = 0; i < 32; i++) { + if ((!type && am791x_modes[i].sup_7910) || (type && am791x_modes[i].sup_7911)) + description = am791x_modes[i].description; + else + description = am791x_modes[31].description; + printf("mc %d: %s\n", i, description); + } +} + +/* init STO signal */ +void init_sto(am791x_t *am791x) +{ + am791x->sto_phaseshift65536 = 900 / (double)am791x->samplerate * 65536.0; +} + +/* transmit STO signal, use phase from FSK modulator, to avoid phase jumps */ +int send_sto(am791x_t *am791x, sample_t *sample, int length) +{ + fsk_mod_t *fsk = &am791x->fsk_tx; + int count = 0; + double phase, phaseshift; + + phase = fsk->tx_phase65536; + + /* modulate STO */ + phaseshift = am791x->sto_phaseshift65536; + while (count < length && fsk->tx_bitpos < 1.0) { + sample[count++] = fsk->sin_tab[(uint16_t)phase]; + phase += phaseshift; + if (phase >= 65536.0) + phase -= 65536.0; + } + + fsk->tx_phase65536 = phase; + + return count; +} + +/* send audio from FSK modulator */ +void am791x_send(am791x_t *am791x, sample_t *samples, int length) +{ + if (am791x->tx_sto) + send_sto(am791x, samples, length); + else { + if (am791x->f0_tx) { + /* if filter set (even if tx_silence, we want to request bits from upper layer) */ + fsk_mod_send(&am791x->fsk_tx, samples, length, 0); + } + if (!am791x->f0_tx || am791x->tx_silence) { + /* if filter not set, or silence */ + memset(samples, 0, length * sizeof(*samples)); + } + } +} + +/* receive audio and feed into FSK demodulator */ +void am791x_receive(am791x_t *am791x, sample_t *samples, int length) +{ + if (!am791x->f0_rx) { + /* no mode set */ + memset(samples, 0, length * sizeof(*samples)); + return; + } + if (am791x->squelch) { + /* handle squelch, but then demod... */ + memset(samples, 0, length * sizeof(*samples)); + } + if (am791x->f0_rx) { + /* handle RX audio */ + fsk_demod_receive(&am791x->fsk_rx, samples, length); + } +} + +/* provide bit to FSK modulator */ +static int fsk_send_bit(void *inst) +{ + am791x_t *am791x = (am791x_t *)inst; + int bit, bbit; + + bit = am791x->td_cb(am791x->inst); + bbit = am791x->btd_cb(am791x->inst); + + /* main channel returns TD */ + if (!am791x->block_td) { +#ifdef HEAVY_DEBUG + PDEBUG(DDSP, DEBUG_DEBUG, "Modulating bit '%d' for MAIN channel\n", bit); +#endif + return bit; + } + /* back channel returns BTD */ + if (!am791x->block_btd) { +#ifdef HEAVY_DEBUG + PDEBUG(DDSP, DEBUG_DEBUG, "Modulating bit '%d' for BACK channel\n", bbit); +#endif + return bbit; + } +#ifdef HEAVY_DEBUG + PDEBUG(DDSP, DEBUG_DEBUG, "Modulating bit '1', because TD & BTD is ignored\n"); +#endif + return 1; +} + +static void handle_rx_state(am791x_t *am791x); + +/* get bit from FSK demodulator */ +static void fsk_receive_bit(void *inst, int bit, double quality, double level) +{ + am791x_t *am791x = (am791x_t *)inst; + int *block, *cd; + +#ifdef HEAVY_DEBUG + PDEBUG(DDSP, DEBUG_DEBUG, "Demodulated bit '%d' (level = %.0f dBm, quality = %%%.0f)\n", bit, level2db(level), quality * 100.0); +#endif + + if (!am791x->rx_back_channel) { + block = &am791x->block_cd; + cd = &am791x->cd; + } else { + block = &am791x->block_bcd; + cd = &am791x->bcd; + } + + /* detection/loss of carrier */ + if (*block && *cd) { + *cd = 0; + handle_rx_state(am791x); + } else + if (!(*block) && !(*cd) && level > am791x->cd_on && quality >= RX_QUALITY) { + PDEBUG(DDSP, DEBUG_DEBUG, "Good quality (level = %.0f dBm, quality = %%%.0f)\n", level2db(level), quality * 100.0); + *cd = 1; + handle_rx_state(am791x); + } else + if (*cd && (level < am791x->cd_off || quality < RX_QUALITY)) { + PDEBUG(DDSP, DEBUG_DEBUG, "Bad quality (level = %.0f dBm, quality = %%%.0f)\n", level2db(level), quality * 100.0); + *cd = 0; + handle_rx_state(am791x); + } + + /* assume 1 if no carrier */ + if (!(*cd)) { + bit = 1; + quality = 0; + level = 0; + } + + + /* main channel forwards bit to RD */ + if (!am791x->block_rd) { +#ifdef HEAVY_DEBUG + PDEBUG(DDSP, DEBUG_DEBUG, " -> Forwarding bit '%d' to MAIN channel\n", bit); +#endif + am791x->rd_cb(am791x->inst, bit, quality * 100.0, level2db(level)); + } else { +#ifdef HEAVY_DEBUG + PDEBUG(DDSP, DEBUG_DEBUG, " -> Forwarding bit '1' to MAIN channel, because RD is set to MARK\n"); +#endif + am791x->rd_cb(am791x->inst, 1, NAN, NAN); + } + /* main channel forwards bit to RD */ + if (!am791x->block_brd) { +#ifdef HEAVY_DEBUG + PDEBUG(DDSP, DEBUG_DEBUG, " -> Forwarding bit '%d' to BACK channel\n", bit); +#endif + am791x->brd_cb(am791x->inst, bit, quality * 100.0, level2db(level)); + } else { +#ifdef HEAVY_DEBUG + PDEBUG(DDSP, DEBUG_DEBUG, " -> Forwarding bit '1' to BACK channel, because BRD is set to MARK\n"); +#endif + am791x->brd_cb(am791x->inst, 1, NAN, NAN); + } +} + +/* setup FSK */ +static void set_filters(am791x_t *am791x) +{ + const char *name_tx = NULL, *name_rx = NULL; + int f0_tx = -1, f1_tx = -1; + int f0_rx = -1, f1_rx = -1; + uint8_t mc = am791x->mc; + + /* not supported */ + if (!((am791x->type) ? am791x_modes[mc].sup_7911 : am791x_modes[mc].sup_7910)) { + f0_tx = 0; + f1_tx = 0; + f0_rx = 0; + f1_rx = 0; + } else + switch (am791x->tx_state) { + case AM791X_STATE_INIT: + /* when RTS and BRTS are not asserted */ + f0_tx = 0; // TX !! + f1_tx = 0; + name_tx = "MAIN"; + if (am791x->loopback_back) { + /* loopback (BACK): always listens to back channel */ + f0_rx = am791x_modes[mc].f0_back; // RX !! + f1_rx = am791x_modes[mc].f1_back; + name_rx = "BACK"; + am791x->rx_back_channel = 1; + } else { + /* listen to main channel's RX frequencies */ + f0_rx = am791x_modes[mc].f0_rx; // RX !! + f1_rx = am791x_modes[mc].f1_rx; + name_rx = "MAIN"; + am791x->rx_back_channel = 0; + } + break; + case AM791X_STATE_RCON: + /* when asserting RTS */ + f0_tx = am791x_modes[mc].f0_tx; + f1_tx = am791x_modes[mc].f1_tx; + name_tx = "MAIN"; + if (!am791x->loopback_main && am791x_modes[mc].f0_back && am791x_modes[mc].f1_back) { + /* switch receiver to back channel, (if not in loopback mode) */ + f0_rx = am791x_modes[mc].f0_back; + f1_rx = am791x_modes[mc].f1_back; + name_rx = "BACK"; + am791x->rx_back_channel = 1; + } + break; + case AM791X_STATE_BRCON: + /* when asserting BRTS */ + f0_tx = am791x_modes[mc].f0_back; + f1_tx = am791x_modes[mc].f1_back; + name_tx = "BACK"; + break; + default: + /* keep current frequencies in other state */ + return; + } + + /* transmitter not used anymore or has changed */ + if (f0_tx >= 0 && am791x->f0_tx && (f0_tx != am791x->f0_tx || f1_tx != am791x->f1_tx)) { + /* disable transmitter */ + fsk_mod_cleanup(&am791x->fsk_tx); + am791x->f0_tx = 0; + am791x->f1_tx = 0; + } + + /* transmitter used */ + if (f0_tx > 0 && am791x->f0_tx == 0) { + PDEBUG(DDSP, DEBUG_DEBUG, "Setting modulator to %s channel's frequencies (F0 = %d, F1 = %d), baudrate %.0f\n", name_tx, f0_tx, f1_tx, am791x->tx_baud); + if (fsk_mod_init(&am791x->fsk_tx, am791x, fsk_send_bit, am791x->samplerate, am791x->tx_baud, (double)f0_tx, (double)f1_tx, am791x->tx_level, 0, 1) < 0) + PDEBUG(DDSP, DEBUG_ERROR, "FSK RX init failed!\n"); + else { + am791x->f0_tx = f0_tx; + am791x->f1_tx = f1_tx; + } + } + + /* receiver has changed */ + if (f0_rx >= 0 && am791x->f0_rx && (f0_rx != am791x->f0_rx || f1_rx != am791x->f1_rx)) { + /* disable receiver */ + fsk_demod_cleanup(&am791x->fsk_rx); + am791x->f0_rx = 0; + am791x->f1_rx = 0; + } + + /* receiver used */ + if (f0_rx > 0 && am791x->f0_rx == 0) { + PDEBUG(DDSP, DEBUG_DEBUG, "Setting demodulator to %s channel's frequencies (F0 = %d, F1 = %d), baudrate %.0f\n", name_rx, f0_rx, f1_rx, am791x->rx_baud); + if (fsk_demod_init(&am791x->fsk_rx, am791x, fsk_receive_bit, am791x->samplerate, am791x->rx_baud, (double)f0_rx, (double)f1_rx, BIT_ADJUST) < 0) + PDEBUG(DDSP, DEBUG_ERROR, "FSK RX init failed!\n"); + else { + am791x->f0_rx = f0_rx; + am791x->f1_rx = f1_rx; + } + } +} + +/* new state */ +static void new_tx_state(am791x_t *am791x, enum am791x_st state) +{ + if (am791x->tx_state != state) + PDEBUG(DAM791X, DEBUG_DEBUG, "Change TX state %s -> %s\n", am791x_state_names[am791x->tx_state], am791x_state_names[state]); + am791x->tx_state = state; +} + +static void new_rx_state(am791x_t *am791x, enum am791x_st state) +{ + if (am791x->rx_state != state) + PDEBUG(DAM791X, DEBUG_DEBUG, "Change RX state %s -> %s\n", am791x_state_names[am791x->rx_state], am791x_state_names[state]); + am791x->rx_state = state; +} + +/* new flags */ +static void set_flag(int *flag_p, int value, const char *name) +{ + if (*flag_p != value) { + PDEBUG(DAM791X, DEBUG_DEBUG, " -> %s\n", name); + *flag_p = value; + } +} + +/* + * state machine according to datasheet + */ + +static void go_main_channel_tx(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Enable transmitter on main channel\n"); + + /* only block RD, if not full duplex and not 4-wire (loopback mode) */ + if (!am791x->fullduplex && !am791x->loopback_main) { + set_flag(&am791x->block_rd, 1, "RD = MARK"); + set_flag(&am791x->block_cd, 1, "SET CD HIGH"); + } + + /* activate TD now and set CTS timer (RCON) */ + set_flag(&am791x->block_td, 0, "TD RELEASED"); + set_flag(&am791x->tx_silence, 0, "RESET SILENCE"); + timer_start(&am791x->tx_timer, am791x->t_rcon); + new_tx_state(am791x, AM791X_STATE_RCON); + set_filters(am791x); + /* check CD to be blocked */ + if (!am791x->fullduplex && !am791x->loopback_main) { + if (am791x->line_cd) { + /* send CD off */ + am791x->cd_cb(am791x->inst, 0); + } + } +} + +static void rcon_release_rts(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "RTS was released\n"); + + set_flag(&am791x->block_td, 1, "TD IGNORED"); + set_flag(&am791x->tx_silence, 1, "SET SILENCE"); + set_flag(&am791x->block_cd, 0, "RELEASE CD"); + new_tx_state(am791x, AM791X_STATE_INIT); + set_filters(am791x); + /* check CD to be released */ + if (am791x->line_cd) { + /* send CD on */ + am791x->cd_cb(am791x->inst, 1); + } +} + +static void rcon_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Transmission started\n"); + + new_tx_state(am791x, AM791X_STATE_DATA); + /* CTS on */ + am791x->cts_cb(am791x->inst, 1); +} + +static void tx_data_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "RTS was released\n"); + + new_tx_state(am791x, AM791X_STATE_RCOFF); + set_flag(&am791x->block_td, 1, "TD IGNORED"); + if (am791x->sto) { + set_flag(&am791x->tx_sto, 1, "start STO"); + } else { + set_flag(&am791x->tx_silence, 1, "SET SILENCE (if not STO)"); + } + if (!am791x->fullduplex) { + set_flag(&am791x->squelch, 1, "SET SQUELCH (ON)"); + } + timer_start(&am791x->tx_timer, am791x->t_rcoff); +} + +static void rcoff_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Transmission over\n"); + + /* CTS off */ + am791x->cts_cb(am791x->inst, 0); + if (am791x->fullduplex) { + new_tx_state(am791x, AM791X_STATE_INIT); + set_filters(am791x); + return; + } + if (!am791x->sto) { + timer_start(&am791x->tx_timer, am791x->t_sq - am791x->t_rcoff); + new_tx_state(am791x, AM791X_STATE_SQ_OFF); + return; + } + timer_start(&am791x->tx_timer, am791x->t_sto - am791x->t_rcoff); + new_tx_state(am791x, AM791X_STATE_STO_OFF); +} + +static void sq_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Squelch over\n"); + + set_flag(&am791x->block_cd, 0, "CD RELEASED"); + new_tx_state(am791x, AM791X_STATE_INIT); + set_filters(am791x); + /* SET SQUELCH OFF */ + set_flag(&am791x->squelch, 0, "SET SQUELCH OFF"); + if (am791x->line_cd) { + /* send CD on */ + am791x->cd_cb(am791x->inst, 1); + } +} + +static void sto_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "STO over\n"); + + set_flag(&am791x->tx_sto, 0, "stop STO"); + timer_start(&am791x->tx_timer, am791x->t_sq - am791x->t_sto); + new_tx_state(am791x, AM791X_STATE_SQ_OFF); +} + +static void go_back_channel_tx(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Enable transmitter on back channel\n"); + + if (!am791x->loopback_back) { + set_flag(&am791x->block_brd, 1, "BRD = MARK"); + set_flag(&am791x->block_bcd, 1, "SET BCD HIGH"); + } + + /* activate BTD now and set BCTS timer (BRCON) */ + set_flag(&am791x->block_btd, 0, "BTD RELEASED"); + set_flag(&am791x->tx_silence, 0, "RESET SILENCE"); + timer_start(&am791x->tx_timer, am791x->t_brcon); + new_tx_state(am791x, AM791X_STATE_BRCON); + set_filters(am791x); + /* check BCD to be blocked */ + if (!am791x->loopback_back) { + if (am791x->line_bcd) { + /* send BCD off */ + am791x->bcd_cb(am791x->inst, 0); + } + } +} + +static void brcon_release_brts(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "BRTS was released\n"); + + set_flag(&am791x->tx_silence, 1, "SET SILENCE"); + new_tx_state(am791x, AM791X_STATE_INIT); + set_filters(am791x); +} + +static void brcon_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Transmission started\n"); + + new_tx_state(am791x, AM791X_STATE_BDATA); + /* BCTS on */ + am791x->bcts_cb(am791x->inst, 1); +} + +static void tx_bdata_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "BRTS was released\n"); + + set_flag(&am791x->block_btd, 1, "BTD IGNORED"); + set_flag(&am791x->tx_silence, 1, "SET SILENCE"); + timer_start(&am791x->tx_timer, am791x->t_brcoff); +} + +static void brcoff_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Transmission over\n"); + + /* BCTS off */ + am791x->bcts_cb(am791x->inst, 0); + set_flag(&am791x->block_bcd, 0, "BCD RELEASED"); + new_tx_state(am791x, AM791X_STATE_INIT); + set_filters(am791x); + /* check BCD to be released */ + if (am791x->line_bcd) { + /* send BCD on */ + am791x->bcd_cb(am791x->inst, 1); + } +} + +static void handle_tx_state(am791x_t *am791x) +{ + switch (am791x->tx_state) { + /* depending on the states we change to "main TX " or to "back TX" state */ + case AM791X_STATE_INIT: + /* select TX on main or back channel, according to states */ + if (am791x->line_brts) { + if (am791x->loopback_back) { + go_back_channel_tx(am791x); + break; + } + if (!am791x->line_rts && !am791x->loopback_main && !am791x->fullduplex) { + go_back_channel_tx(am791x); + break; + } + } + if (!am791x->loopback_back && am791x->line_rts) { + go_main_channel_tx(am791x); + break; + } + break; + /* all main channel states ... */ + case AM791X_STATE_RCON: + if (!am791x->line_rts) { + rcon_release_rts(am791x); + break; + } + if (!timer_running(&am791x->tx_timer)) { + rcon_done(am791x); + break; + } + break; + case AM791X_STATE_DATA: + if (!am791x->line_rts) { + tx_data_done(am791x); + break; + } + break; + case AM791X_STATE_RCOFF: + if (!timer_running(&am791x->tx_timer)) { + rcoff_done(am791x); + break; + } + break; + case AM791X_STATE_STO_OFF: + if (!timer_running(&am791x->tx_timer)) { + sto_done(am791x); + break; + } + break; + case AM791X_STATE_SQ_OFF: + if (!timer_running(&am791x->tx_timer)) { + sq_done(am791x); + break; + } + break; + /* all back channel states */ + case AM791X_STATE_BRCON: + if (!am791x->line_brts) { + brcon_release_brts(am791x); + break; + } + if (!timer_running(&am791x->tx_timer)) { + brcon_done(am791x); + break; + } + break; + case AM791X_STATE_BDATA: + if (!am791x->line_brts) { + tx_bdata_done(am791x); + break; + } + break; + case AM791X_STATE_BRCOFF: + if (!timer_running(&am791x->tx_timer)) { + brcoff_done(am791x); + break; + } + break; + default: + PDEBUG(DAM791X, DEBUG_ERROR, "State %s not handled!\n", am791x_state_names[am791x->rx_state]); + } +} + +static void go_main_channel_rx(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Enable receiver on main channel\n"); + + timer_start(&am791x->rx_timer, am791x->t_cdon); + new_rx_state(am791x, AM791X_STATE_CDON); +} + +static void cdon_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Reception started\n"); + + set_flag(&am791x->block_rd, 0, "RD RELEASED"); + new_rx_state(am791x, AM791X_STATE_DATA); + set_flag(&am791x->line_cd, 1, "set CD"); + /* check CD not blocked */ + if (!am791x->block_cd) { + /* send CD on */ + am791x->cd_cb(am791x->inst, 1); + } +} + +static void cdon_no_cd(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier is gone\n"); + + timer_stop(&am791x->rx_timer); + new_rx_state(am791x, AM791X_STATE_INIT); +} + +static void rx_data_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier lost\n"); + + timer_start(&am791x->rx_timer, am791x->t_cdoff); + new_rx_state(am791x, AM791X_STATE_CDOFF); +} + +static void cdoff_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Reception finished\n"); + + set_flag(&am791x->block_rd, 1, "RD = MARK"); + new_rx_state(am791x, AM791X_STATE_INIT); + set_flag(&am791x->line_cd, 0, "release CD"); + /* check CD not blocked */ + if (!am791x->block_cd) { + /* send CD off */ + am791x->cd_cb(am791x->inst, 0); + } +} + +static void cdoff_cd(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier recovered\n"); + + timer_stop(&am791x->rx_timer); + new_rx_state(am791x, AM791X_STATE_DATA); +} + +static void go_back_channel_rx(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Enable receiver on back channel\n"); + + timer_start(&am791x->rx_timer, am791x->t_bcdon); + new_rx_state(am791x, AM791X_STATE_BCDON); +} + +static void bcdon_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier was detected\n"); + + set_flag(&am791x->block_brd, 0, "BRD RELEASED"); + new_rx_state(am791x, AM791X_STATE_BDATA); + set_flag(&am791x->line_bcd, 1, "set BCD"); + /* check BCD not blocked */ + if (!am791x->block_bcd) { + /* send BCD on */ + am791x->bcd_cb(am791x->inst, 1); + } +} + +static void bcdon_no_cd(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier is gone\n"); + + timer_stop(&am791x->rx_timer); + new_rx_state(am791x, AM791X_STATE_INIT); +} + +static void rx_bdata_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier lost\n"); + + timer_start(&am791x->rx_timer, am791x->t_bcdoff); + new_rx_state(am791x, AM791X_STATE_BCDOFF); +} + +static void bcdoff_done(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Reception finished\n"); + + if (!am791x->bell_202) + set_flag(&am791x->block_brd, 1, "BRD = MARK"); + new_rx_state(am791x, AM791X_STATE_INIT); + set_flag(&am791x->line_bcd, 0, "release BCD"); + /* check BCD not blocked */ + if (!am791x->block_bcd) { + /* send BCD off */ + am791x->bcd_cb(am791x->inst, 0); + } +} + +static void bcdoff_cd(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier recovered\n"); + + timer_stop(&am791x->rx_timer); + new_rx_state(am791x, AM791X_STATE_BDATA); +} + +static void handle_rx_state(am791x_t *am791x) +{ + switch (am791x->rx_state) { + case AM791X_STATE_INIT: + /* select RX on main or back channel, according to states */ + if (!am791x->loopback_back) { + if (am791x->cd) { + go_main_channel_rx(am791x); + break; + } + if (!am791x->loopback_main && !am791x->fullduplex && am791x->bcd) { + go_back_channel_rx(am791x); + break; + } + } else { + if (am791x->bcd) { + go_back_channel_rx(am791x); + break; + } + } + break; + /* all main channel states ... */ + case AM791X_STATE_CDON: + if (!timer_running(&am791x->rx_timer)) { + cdon_done(am791x); + break; + } + if (!am791x->cd) { + cdon_no_cd(am791x); + break; + } + break; + case AM791X_STATE_DATA: + if (!am791x->cd) { + rx_data_done(am791x); + break; + } + break; + case AM791X_STATE_CDOFF: + if (!timer_running(&am791x->rx_timer)) { + cdoff_done(am791x); + break; + } + if (am791x->cd) { + cdoff_cd(am791x); + break; + } + break; + /* all back channel states ... */ + case AM791X_STATE_BCDON: + if (!timer_running(&am791x->rx_timer)) { + bcdon_done(am791x); + break; + } + if (!am791x->bcd) { + bcdon_no_cd(am791x); + break; + } + break; + case AM791X_STATE_BDATA: + if (!am791x->bcd) { + rx_bdata_done(am791x); + break; + } + break; + case AM791X_STATE_BCDOFF: + if (!timer_running(&am791x->rx_timer)) { + bcdoff_done(am791x); + break; + } + if (am791x->bcd) { + bcdoff_cd(am791x); + break; + } + break; + default: + PDEBUG(DAM791X, DEBUG_ERROR, "State %s not handled!\n", am791x_state_names[am791x->rx_state]); + } +} + +/* handle both (rx and tx) states */ +static void handle_state(am791x_t *am791x) +{ + /* DTR blocks all */ + if (!am791x->line_dtr) { + /* do reset of all states, if not in INIT state */ + if (am791x->tx_state != AM791X_STATE_INIT || am791x->rx_state != AM791X_STATE_INIT) + am791x_reset(am791x); + return; + } + + /* handle states if DTR is on */ + handle_tx_state(am791x); + handle_rx_state(am791x); +} + +/* timeout events */ +static void tx_timeout(struct timer *timer) +{ + am791x_t *am791x = (am791x_t *)timer->priv; + + handle_tx_state(am791x); +} + +static void rx_timeout(struct timer *timer) +{ + am791x_t *am791x = (am791x_t *)timer->priv; + + handle_rx_state(am791x); +} + +/* init routine, must be called before anything else */ +int am791x_init(am791x_t *am791x, void *inst, enum am791x_type type, uint8_t mc, int samplerate, double tx_baud, double rx_baud, void (*cts)(void *inst, int cts), void (*bcts)(void *inst, int cts), void (*cd)(void *inst, int cd), void (*bcd)(void *inst, int cd), int (*td)(void *inst), int (*btd)(void *inst), void (*rd)(void *inst, int bit, double quality, double level), void (*brd)(void *inst, int bit, double quality, double level)) +{ + memset(am791x, 0, sizeof(*am791x)); + + /* init timers */ + timer_init(&am791x->tx_timer, tx_timeout, am791x); + timer_init(&am791x->rx_timer, rx_timeout, am791x); + + PDEBUG(DAM791X, DEBUG_DEBUG, "Initializing instance of AM791%d:\n", type); + + am791x->inst = inst; + am791x->type = type; + am791x->samplerate = samplerate; + am791x->cts_cb = cts; + am791x->bcts_cb = bcts; + am791x->cd_cb = cd; + am791x->bcd_cb = bcd; + am791x->td_cb = td; + am791x->btd_cb = btd; + am791x->rd_cb = rd; + am791x->brd_cb = brd; + + /* levels */ + am791x->tx_level = db2level(TX_LEVEL); + am791x->cd_on = db2level((am791x->type) ? RX_CD_ON_7911 : RX_CD_ON_7910); + am791x->cd_off = db2level((am791x->type) ? RX_CD_OFF_7911 : RX_CD_OFF_7910); + + init_sto(am791x); + + /* set initial mode and reset */ + am791x_mc(am791x, mc, samplerate, tx_baud, rx_baud); + + return 0; +} + +/* exit routine, must be called when exit */ +void am791x_exit(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Exit instance of AM791%d:\n", am791x->type); + + /* bring to reset state, be sure to clean FSK processes */ + am791x_reset(am791x); + + timer_exit(&am791x->tx_timer); + timer_exit(&am791x->rx_timer); +} + +/* get some default baud rate for each mode, before IOCTL sets it (if it sets it) */ +double am791x_max_baud(uint8_t mc) +{ + if (am791x_modes[mc].max_baud) + return am791x_modes[mc].max_baud; + else + return 300; /* useless modem. just report something to the caller */ +} + +/* change mode on the fly, may be called any time by upper layer */ +int am791x_mc(am791x_t *am791x, uint8_t mc, int samplerate, double tx_baud, double rx_baud) +{ + int rc = 0; + + /* prevent out of range value */ + if (mc >= 32) + mc = 0; + + if (!((am791x->type) ? am791x_modes[mc].sup_7911 : am791x_modes[mc].sup_7910)) + rc = -EINVAL; + + PDEBUG(DAM791X, DEBUG_INFO, "Setting mode %d: %s\n", mc, am791x_modes[mc].description); + PDEBUG(DAM791X, DEBUG_DEBUG, " -> Baud rate: %.1f/%.1f\n", rx_baud, tx_baud); + + am791x->mc = mc; + am791x->samplerate = samplerate; + am791x->tx_baud = tx_baud; + am791x->rx_baud = rx_baud; + + am791x->t_rcon = (am791x->type) ? am791x_modes[mc].t_rcon_7911 : am791x_modes[mc].t_rcon_7910; + am791x->t_rcoff = (am791x->type) ? am791x_modes[mc].t_rcoff_7911 : am791x_modes[mc].t_rcoff_7910; + am791x->t_brcon = (am791x->type) ? am791x_modes[mc].t_brcon_7911 : am791x_modes[mc].t_brcon_7910; + am791x->t_brcoff = (am791x->type) ? am791x_modes[mc].t_brcoff_7911 : am791x_modes[mc].t_brcoff_7910; + am791x->t_cdon = (am791x->type) ? am791x_modes[mc].t_cdon_7911 : am791x_modes[mc].t_cdon_7910; + am791x->t_cdoff = (am791x->type) ? am791x_modes[mc].t_cdoff_7911 : am791x_modes[mc].t_cdoff_7910; + am791x->t_bcdon = (am791x->type) ? am791x_modes[mc].t_bcdon_7911 : am791x_modes[mc].t_bcdon_7910; + am791x->t_bcdoff = (am791x->type) ? am791x_modes[mc].t_bcdoff_7911 : am791x_modes[mc].t_bcdoff_7910; + am791x->t_at = (am791x->type) ? am791x_modes[mc].t_at_7911 : am791x_modes[mc].t_at_7910; + am791x->t_sil1 = (am791x->type) ? am791x_modes[mc].t_sil1_7911 : am791x_modes[mc].t_sil1_7910; + am791x->t_sil2 = (am791x->type) ? am791x_modes[mc].t_sil2_7911 : am791x_modes[mc].t_sil2_7910; + am791x->t_sq = (am791x->type) ? am791x_modes[mc].t_sq_7911 : am791x_modes[mc].t_sq_7910; + am791x->t_sto = (am791x->type) ? am791x_modes[mc].t_sto_7911 : am791x_modes[mc].t_sto_7910; + am791x->fullduplex = am791x_modes[mc].fullduplex; + am791x->loopback_main = am791x_modes[mc].loopback_main; + am791x->loopback_back = am791x_modes[mc].loopback_back; + am791x->equalizer = am791x_modes[mc].equalizer; + am791x->sto = am791x_modes[mc].sto; + am791x->bell_202 = am791x_modes[mc].bell_202; + + /* changing mode causes a reset */ + am791x_reset(am791x); + + /* Return error on invalid mode. The emualtion still works, but no TX/RX possible. */ + return rc; +} + +/* reset at any time, may be called any time by upper layer */ +void am791x_reset(am791x_t *am791x) +{ + PDEBUG(DAM791X, DEBUG_INFO, "Reset!\n"); + + timer_stop(&am791x->tx_timer); + timer_stop(&am791x->rx_timer); + + if (am791x->f0_tx) { + fsk_mod_cleanup(&am791x->fsk_tx); + am791x->f0_tx = 0; + am791x->f1_tx = 0; + } + if (am791x->f0_rx) { + fsk_demod_cleanup(&am791x->fsk_rx); + am791x->f0_rx = 0; + am791x->f1_rx = 0; + } + + /* initial states */ + am791x->tx_state = AM791X_STATE_INIT; + am791x->rx_state = AM791X_STATE_INIT; + am791x->tx_silence = 1; /* state machine implies that silence is sent */ + am791x->tx_sto = 0; /* state machine implies that STO is not sent */ + am791x->block_td = 1; /* state machine implies that TD is MARK (1) */ + am791x->block_rd = 1; /* state machine implies that RD is ignored */ + am791x->line_cd = 0; + am791x->block_cd = 0; /* state machine implies that CD is released */ + am791x->block_btd = 1; /* state machine implies that BTD is MARK (1) */ + am791x->block_brd = 1; /* state machine implies that BTD is ignored */ + am791x->line_bcd = 0; + am791x->block_bcd = 0; /* state machine implies that BCD is released */ + am791x->squelch = 0; /* state machine implies that squelch is off */ + + /* set filters, if DTR is on */ + if (am791x->line_dtr) + set_filters(am791x); + + handle_state(am791x); +} + +/* change input lines */ +void am791x_dtr(am791x_t *am791x, int dtr) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Terminal is%s ready!\n", (dtr) ? "" : " not"); + + /* set filters, if DTR becomes on */ + if (!am791x->line_dtr && dtr) { + am791x->line_dtr = dtr; + set_filters(am791x); + } else + am791x->line_dtr = dtr; + + handle_state(am791x); +} + +void am791x_rts(am791x_t *am791x, int rts) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Terminal %s RTS.\n", (rts) ? "sets" : "clears"); + + am791x->line_rts = rts; + handle_state(am791x); +} + +void am791x_brts(am791x_t *am791x, int rts) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Terminal %s BRTS.\n", (rts) ? "sets" : "clears"); + + am791x->line_brts = rts; + handle_state(am791x); +} + +void am791x_ring(am791x_t *am791x, int ring) +{ + PDEBUG(DAM791X, DEBUG_DEBUG, "Terminal %s RING.\n", (ring) ? "sets" : "clears"); + + am791x->line_ring = ring; + handle_state(am791x); +} + diff --git a/src/datenklo/am791x.h b/src/datenklo/am791x.h new file mode 100644 index 0000000..a75f736 --- /dev/null +++ b/src/datenklo/am791x.h @@ -0,0 +1,107 @@ + +enum am791x_type { + AM791X_TYPE_7910 = 0, + AM791X_TYPE_7911 = 1, +}; + +enum am791x_st { + AM791X_STATE_INIT = 0, + AM791X_STATE_RCON, + AM791X_STATE_CDON, + AM791X_STATE_DATA, + AM791X_STATE_RCOFF, + AM791X_STATE_CDOFF, + AM791X_STATE_STO_OFF, + AM791X_STATE_SQ_OFF, + AM791X_STATE_BRCON, + AM791X_STATE_BCDON, + AM791X_STATE_BDATA, + AM791X_STATE_BRCOFF, + AM791X_STATE_BCDOFF, +}; + +typedef struct am791x { + /* settings */ + void *inst; /* upper layer instance */ + enum am791x_type type; + int samplerate; + double tx_baud, rx_baud; + + /* callbacks */ + void (*cts_cb)(void *inst, int cts); + void (*bcts_cb)(void *inst, int cts); + void (*cd_cb)(void *inst, int cd); + void (*bcd_cb)(void *inst, int cd); + int (*td_cb)(void *inst); + int (*btd_cb)(void *inst); + void (*rd_cb)(void *inst, int bit, double quality, double level); + void (*brd_cb)(void *inst, int bit, double quality, double level); + + /* modes */ + uint8_t mc; /* current mode setting */ + int fullduplex; /* duplex */ + int loopback_main, loopback_back; /* loopback */ + int equalizer, sto; /* equalizer & STO */ + int bell_202; /* is BELL 202 */ + + /* states */ + enum am791x_st tx_state, rx_state; + int tx_silence; /* no audio transmitted */ + int tx_sto; /* no STO transmitted */ + int block_td; /* "TD IGNORED" */ + int block_rd; /* "RD = MARK" */ + int line_cd; /* 1 = CD is low */ + int block_cd; /* "SET CD HIGH" (CD is ignored) */ + int block_btd; /* "BTD IGNORED" */ + int block_brd; /* "BRD = MARK" */ + int line_bcd; /* 1 = BCD is low */ + int block_bcd; /* "SET BCD HIGH" (BCD is ignored) */ + int squelch; /* "SQUELCH" (mute received audio) */ + int line_dtr; /* 1 = DTR is low */ + int line_rts; /* 1 = RTS is low */ + int line_brts; /* 1 = BRTS is low */ + int line_ring; /* 1 = ring is low */ + + /* frequencies */ + int f0_tx, f1_tx; + int f0_rx, f1_rx; + + /* timers */ + struct timer tx_timer, rx_timer; + double t_rcon; + double t_rcoff; + double t_brcon; + double t_brcoff; + double t_cdon; + double t_cdoff; + double t_bcdon; + double t_bcdoff; + double t_at; + double t_sil1; + double t_sil2; + double t_sq; + double t_sto; + + /* FSK/STO signal */ + int rx_back_channel; /* indikates if receiver is tuned to back channel */ + fsk_mod_t fsk_tx; /* FSK modulator */ + fsk_demod_t fsk_rx; /* FSK demodulator */ + double tx_level; /* level of TX */ + double cd_on, cd_off; /* levels for CD */ + int cd, bcd; /* carrier detected */ + double sto_phaseshift65536; /* STO tone phase shift */ +} am791x_t; + +void am791x_send(am791x_t *am791x, sample_t *samples, int length); +void am791x_receive(am791x_t *am791x, sample_t *samples, int length); +void am791x_list_mc(enum am791x_type type); +int am791x_init(am791x_t *am791x, void *inst, enum am791x_type type, uint8_t mc, int samplerate, double tx_baud, double rx_baud, void (*cts)(void *inst, int cts), void (*bcts)(void *inst, int cts), void (*cd)(void *inst, int cd), void (*bcd)(void *inst, int cd), int (*td)(void *inst), int (*btd)(void *inst), void (*rd)(void *inst, int bit, double quality, double level), void (*brd)(void *inst, int bit, double quality, double level)); +void am791x_exit(am791x_t *am791x); +double am791x_max_baud(uint8_t mc); +int am791x_mc(am791x_t *am791x, uint8_t mc, int samplerate, double tx_baud, double rx_baud); +void am791x_reset(am791x_t *am791x); +void am791x_dtr(am791x_t *am791x, int dtr); +void am791x_rts(am791x_t *am791x, int rts); +void am791x_brts(am791x_t *am791x, int brts); +void am791x_ring(am791x_t *am791x, int ring); + diff --git a/src/datenklo/datenklo.c b/src/datenklo/datenklo.c new file mode 100644 index 0000000..12d7ce4 --- /dev/null +++ b/src/datenklo/datenklo.c @@ -0,0 +1,1668 @@ +/* osmo datenklo, the "datenklo" emulator + * + * (C) 2019 by Andreas Eversberg + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libtimer/timer.h" +#include "../libfsk/fsk.h" +#include "../libsound/sound.h" +#include "../libwave/wave.h" +#include "../libdisplay/display.h" +#include "../libdebug/debug.h" +#include "device.h" +#include "am791x.h" +#include "uart.h" +#include "datenklo.h" + +/* Put the state of FD into *TERMIOS_P. */ +extern int tcgetattr (int __fd, struct termios *__termios_p) __THROW; + +/* Set the state of FD to *TERMIOS_P. + Values for OPTIONAL_ACTIONS (TCSA*) are in . */ +extern int tcsetattr (int __fd, int __optional_actions, + const struct termios *__termios_p) __THROW; + +static int quit = 0; + +pthread_mutex_t mutex; + +static tcflag_t baud2cflag(double _baudrate) +{ + tcflag_t cflag; + int baudrate = (int)_baudrate; + + switch (baudrate) { + case 0: + cflag = B0; + break; + case 50: + cflag = B50; + break; + case 75: + cflag = B75; + break; + case 110: + cflag = B110; + break; + case 134: + cflag = B134; + break; + case 150: + cflag = B150; + break; + case 200: + cflag = B200; + break; + case 300: + cflag = B300; + break; + case 600: + cflag = B600; + break; + default: + cflag = B1200; + } + + return cflag; +} + +static double cflag2baud(tcflag_t cflag) +{ + double baudrate; + + switch ((cflag & CBAUD)) { + case B0: + baudrate = 0; + break; + case B50: + baudrate = 50; + break; + case B75: + baudrate = 75; + break; + case B110: + baudrate = 110; + break; + case B134: + baudrate = 134.5; + break; + case B150: + baudrate = 150; + break; + case B200: + baudrate = 200; + break; + case B300: + baudrate = 300; + break; + case B600: + baudrate = 600; + break; + default: + baudrate = 1200; + } + + return baudrate; +} + +static int cflag2databits(tcflag_t cflag) +{ + int databits; + + switch ((cflag & CSIZE)) { + case CS5: + databits = 5; + break; + case CS6: + databits = 6; + break; + case CS7: + databits = 7; + break; + default: + databits = 8; + } + + return databits; +} + +static enum uart_parity cflag2parity(tcflag_t cflag) +{ + enum uart_parity parity; + + if (!(cflag & PARENB)) + parity = UART_PARITY_NONE; + else + if (!(cflag & PARODD)) { + if (!(cflag & CMSPAR)) + parity = UART_PARITY_EVEN; + else + parity = UART_PARITY_SPACE; + } else { + if (!(cflag & CMSPAR)) + parity = UART_PARITY_ODD; + else + parity = UART_PARITY_MARK; + } + + return parity; +} + +static char parity2char(enum uart_parity parity) +{ + switch (parity) { + case UART_PARITY_NONE: + return 'N'; + case UART_PARITY_EVEN: + return 'E'; + case UART_PARITY_ODD: + return 'O'; + case UART_PARITY_MARK: + return 'M'; + case UART_PARITY_SPACE: + return 'S'; + } + return ' '; +} + +static int cflag2stopbits(tcflag_t cflag) +{ + int stopbits; + + if ((cflag & CSTOPB)) + stopbits = 2; + else + stopbits = 1; + + return stopbits; +} + +/* modem changes CTS state */ +static void cts(void *inst, int cts) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + + if (datenklo->tx_back) + return; + + if (datenklo->auto_rts) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Received CTS=%d in Automatic RTS Mode.\n", cts); + datenklo->auto_rts_cts = cts; + return; + } + + if (cts) + datenklo->lines |= TIOCM_CTS; + else + datenklo->lines &= ~TIOCM_CTS; + PDEBUG(DDATENKLO, DEBUG_INFO, "Indicating to terminal that CTS is %s\n", (cts) ? "on" : "off"); +} + +/* modem changes CTS state (back channel) */ +static void bcts(void *inst, int cts) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + + if (!datenklo->tx_back) + return; + + if (datenklo->auto_rts) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Received BCTS=%d in Automatic RTS Mode.\n", cts); + datenklo->auto_rts_cts = cts; + return; + } + + if (cts) + datenklo->lines |= TIOCM_CTS; + else + datenklo->lines &= ~TIOCM_CTS; + PDEBUG(DDATENKLO, DEBUG_INFO, "Indicating to terminal that BCTS is %s\n", (cts) ? "on" : "off"); +} + +/* modem changes CD state */ +static void cd(void *inst, int cd) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + + if (datenklo->rx_back) + return; + + if (datenklo->auto_rts) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Received CD=%d in Automatic RTS Mode.\n", cd); + datenklo->auto_rts_cd = cd; + return; + } + + if (cd) + datenklo->lines |= TIOCM_CD; + else + datenklo->lines &= ~TIOCM_CD; + PDEBUG(DDATENKLO, DEBUG_INFO, "Indicating to terminal that CD is %s\n", (cd) ? "on" : "off"); +} + +/* modem changes CD state (back channel) */ +static void bcd(void *inst, int cd) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + + if (!datenklo->rx_back) + return; + + if (datenklo->auto_rts) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Received BCD=%d in Automatic RTS Mode.\n", cd); + datenklo->auto_rts_cd = cd; + return; + } + + if (cd) + datenklo->lines |= TIOCM_CD; + else + datenklo->lines &= ~TIOCM_CD; + PDEBUG(DDATENKLO, DEBUG_INFO, "Indicating to terminal that BCD is %s\n", (cd) ? "on" : "off"); +} + +/* modem request bit */ +static int td(void *inst) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + + if (datenklo->tx_back) + return 1; + + if (!uart_is_tx(&datenklo->uart)) { + if (datenklo->break_bits) { + --datenklo->break_bits; + return 0; + } + if (datenklo->break_on) { + return 0; + } + } + + return uart_tx_bit(&datenklo->uart); +} + +/* modem request bit (back channel) */ +static int btd(void *inst) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + + if (!datenklo->tx_back) + return 1; + + if (!uart_is_tx(&datenklo->uart)) { + if (datenklo->break_bits) { + --datenklo->break_bits; + return 0; + } + if (datenklo->break_on) { + return 0; + } + } + + return uart_tx_bit(&datenklo->uart); +} + +/* modem received bit */ +static void rd(void *inst, int bit, double quality, double level) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + + if (datenklo->rx_back) + return; + + /* only show level+quality when bit has not changed */ + if (datenklo->last_bit == bit) { + display_measurements_update(datenklo->dmp_level, level, 0.0); + display_measurements_update(datenklo->dmp_quality, quality, 0.0); + } + datenklo->last_bit = bit; + + uart_rx_bit(&datenklo->uart, bit); +} + +/* modem received bit (back channel) */ +static void brd(void *inst, int bit, double quality, double level) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + + if (!datenklo->rx_back) + return; + + /* only show level+quality when bit has not changed */ + if (datenklo->last_bit == bit) { + display_measurements_update(datenklo->dmp_level, level, 0.0); + display_measurements_update(datenklo->dmp_quality, quality, 0.0); + } + datenklo->last_bit = bit; + + uart_rx_bit(&datenklo->uart, bit); +} + +static void set_termios(datenklo_t *datenklo, const void *buf); + +/* helper to flush tx buffer and all tx states */ +static void flush_tx(datenklo_t *datenklo) +{ + datenklo->tx_fifo_out = datenklo->tx_fifo_in; + datenklo->onlcr_char = 0; +} + +/* helper to flush rx buffer */ +static void flush_rx(datenklo_t *datenklo) +{ + datenklo->rx_fifo_out = datenklo->rx_fifo_in; +} + +/* UART requests byte to transmit */ +static int tx(void *inst) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + size_t fill; + int data; + + if (datenklo->output_off) + return -1; + + if (!(datenklo->lines & TIOCM_RTS) || !(datenklo->lines & TIOCM_CTS)) + return -1; + + if (datenklo->auto_rts && !datenklo->auto_rts_cts) + return -1; + + /* nl -> cr+nl mode */ + if (datenklo->onlcr_char) { + datenklo->onlcr_char = 0; + data = '\n'; + PDEBUG(DDATENKLO, DEBUG_DEBUG, "ONLCR: sending NL\n"); + goto out; + } + +again: + fill = (datenklo->tx_fifo_in - datenklo->tx_fifo_out + datenklo->tx_fifo_size) % datenklo->tx_fifo_size; + + if (fill == (size_t)datenklo->tx_fifo_full) { + /* tell cuse to write again */ + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Set POLLOUT!\n"); + datenklo->revents |= POLLOUT; + device_set_poll_events(datenklo->device, datenklo->revents); + } + + if (!fill) { + if (datenklo->auto_rts) + datenklo->auto_rts_on = 0; + + if (datenklo->tcsetsw) { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Transmission finished, applying termios now.\n"); + memcpy(&datenklo->termios, &datenklo->tcsetsw_termios, sizeof(datenklo->termios)); + if (datenklo->tcsetsw == 2) { + flush_rx(datenklo); + if ((datenklo->revents & POLLIN)) { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Reset POLLIN (flushed)\n"); + datenklo->revents &= ~POLLIN; + device_set_poll_events(datenklo->device, datenklo->revents); + } + } + datenklo->tcsetsw = 0; + set_termios(datenklo, &datenklo->tcsetsw_termios); + } + return -1; + } + + data = datenklo->tx_fifo[datenklo->tx_fifo_out++]; + datenklo->tx_fifo_out %= datenklo->tx_fifo_size; + fill--; + + /* in case of blocking: check if there is enough space to write */ + device_write_available(datenklo->device); + + /* process output features */ + if (datenklo->opost) { + if (datenklo->olcuc) { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "OLCUC: 0x%02x -> 0x%02x\n", data, toupper(data)); + data = toupper(data); + } + if (datenklo->onlret && data == '\r') { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "ONLRET: ignore CR\n"); + goto again; + } + if (datenklo->ocrnl && data == '\r') { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "OCRNL: CR -> NL\n"); + data = '\n'; + } + if (datenklo->onlcr && data == '\n') { + datenklo->onlcr_char = 1; + data = '\r'; + PDEBUG(DDATENKLO, DEBUG_DEBUG, "ONLCR: sending CR\n"); + } + } + +out: + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Transmitting byte 0x%02x to UART.\n", data); + + return data; +} + +/* UART receives complete byte */ +static void rx(void *inst, int data, uint32_t __attribute__((unused)) flags) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + size_t space; + + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Received byte 0x%02x ('%c') from UART.\n", data, (data >= 32 && data <= 126) ? data : '.'); + + /* process input features */ + if (datenklo->ignbrk && (flags & UART_BREAK)) { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "IGNBRK: ignore BREAK\n"); + return; + } + if (datenklo->istrip && (data & 0x80)) { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "ISTRIP: 0x%02x -> 0x%02x\n", data, data & 0x7f); + data &= 0x7f; + } + if (datenklo->inlcr && data == '\n') { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "INLCR: NL -> CR\n"); + data = '\r'; + } + if (datenklo->igncr && data == '\r') { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "IGNCR: ignore CR\n"); + return; + } + if (datenklo->icrnl && data == '\r') { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "ICRNL: CR -> NL\n"); + data = '\n'; + } + if (datenklo->iuclc) { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "IUCLC: 0x%02x -> 0x%02x\n", data, tolower(data)); + data = tolower(data); + } + if (datenklo->echo) { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "ECHO: write to output\n"); + space = (datenklo->tx_fifo_out - datenklo->tx_fifo_in - 1 + datenklo->tx_fifo_size) % datenklo->tx_fifo_size; + if (space) { + datenklo->tx_fifo[datenklo->tx_fifo_in++] = data; + datenklo->tx_fifo_in %= datenklo->tx_fifo_size; + } + } + + /* empty buffer gets data */ + if (datenklo->rx_fifo_out == datenklo->rx_fifo_in) { + /* tell cuse to read again */ + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Set POLLIN!\n"); + datenklo->revents |= POLLIN; + device_set_poll_events(datenklo->device, datenklo->revents); + } + + space = (datenklo->rx_fifo_out - datenklo->rx_fifo_in - 1 + datenklo->rx_fifo_size) % datenklo->rx_fifo_size; + + if (!space) { + err_overflow: + PDEBUG(DDATENKLO, DEBUG_NOTICE, "RX buffer overflow, dropping!\n"); + return; + } + + if (datenklo->parmrk) { + if ((flags & (UART_BREAK | UART_PARITY_ERROR))) { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "PARMRK: 0x%02x -> 0xff,0x00,0x%02x\n", data, data); + if (space < 3) + goto err_overflow; + datenklo->rx_fifo[datenklo->rx_fifo_in++] = 0xff; + datenklo->rx_fifo_in %= datenklo->rx_fifo_size; + space--; + datenklo->rx_fifo[datenklo->rx_fifo_in++] = 0x00; + datenklo->rx_fifo_in %= datenklo->rx_fifo_size; + space--; + } else if (data == 0xff) { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "PARMRK: 0xff -> 0xff,0xff\n"); + if (space < 2) + goto err_overflow; + datenklo->rx_fifo[datenklo->rx_fifo_in++] = 0xff; + datenklo->rx_fifo_in %= datenklo->rx_fifo_size; + space--; + } + } + + datenklo->rx_fifo[datenklo->rx_fifo_in++] = data; + datenklo->rx_fifo_in %= datenklo->rx_fifo_size; + space--; + + /* in case of blocking: check if there is enough data to read */ + device_read_available(datenklo->device); +} + +/* helper to set line states of modem */ +static void set_lines(datenklo_t *datenklo, int new) +{ + int old = datenklo->lines; + + if (!(old & TIOCM_DTR) && (new & TIOCM_DTR)) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Terminal turns DTR on\n"); + flush_tx(datenklo); + flush_rx(datenklo); + am791x_dtr(&datenklo->am791x, 1); + } + if ((old & TIOCM_DTR) && !(new & TIOCM_DTR)) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Terminal turns DTR off\n"); + am791x_dtr(&datenklo->am791x, 0); + } + + if (!(old & TIOCM_RTS) && (new & TIOCM_RTS)) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Terminal turns RTS on\n"); + if (datenklo->auto_rts) + new |= TIOCM_CTS | TIOCM_CD; + else { + if (!datenklo->tx_back) + am791x_rts(&datenklo->am791x, 1); + else + am791x_brts(&datenklo->am791x, 1); + } + } + if ((old & TIOCM_RTS) && !(new & TIOCM_RTS)) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Terminal turns RTS off\n"); + if (datenklo->auto_rts) + new &= ~(TIOCM_CTS | TIOCM_CD); + else { + if (!datenklo->tx_back) + am791x_rts(&datenklo->am791x, 0); + else + am791x_brts(&datenklo->am791x, 0); + } + } + + datenklo->lines = new; +} + + /* process Auto RTS */ +static void process_auto_rts(datenklo_t *datenklo) +{ + if (!datenklo->auto_rts) + return; + if (datenklo->auto_rts_on && !datenklo->auto_rts_rts && !datenklo->auto_rts_cd) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Automatically raising RTS.\n"); + datenklo->auto_rts_rts = 1; + if (!datenklo->tx_back) + am791x_rts(&datenklo->am791x, 1); + else + am791x_brts(&datenklo->am791x, 1); + } + if (!datenklo->auto_rts_on && datenklo->auto_rts_rts) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Automatically dropping RTS.\n"); + datenklo->auto_rts_rts = 0; + if (!datenklo->tx_back) + am791x_rts(&datenklo->am791x, 0); + else + am791x_brts(&datenklo->am791x, 0); + } +} + +/* tty performs all IOCTLs that requests states */ +static ssize_t dk_ioctl_get(void *inst, int cmd, void *buf, size_t out_bufsz) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + int status; + ssize_t rc = 0; + +#ifdef HEAVY_DEBUG + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Device has been read for ioctl (cmd = %d, size = %zu).\n", cmd, out_bufsz); +#endif + + switch (cmd) { + case TCGETS: + rc = sizeof(datenklo->termios); + if (!out_bufsz) + break; +#ifdef HEAVY_DEBUG + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal requests termios.\n"); +#endif + memcpy(buf, &datenklo->termios, rc); + break; + case TIOCMGET: + rc = sizeof(status); + if (!out_bufsz) + break; +#ifdef HEAVY_DEBUG + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal requests line states.\n"); +#endif + status = datenklo->lines | TIOCM_LE | TIOCM_DSR; + memcpy(buf, &status, rc); + break; + case TIOCGWINSZ: + rc = sizeof(struct winsize); + if (!out_bufsz) + break; +#ifdef HEAVY_DEBUG + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal requests window size.\n"); +#endif + struct winsize *winsize = (struct winsize *)buf; + winsize->ws_row = 25; + winsize->ws_col = 80; + winsize->ws_xpixel = 640; + winsize->ws_ypixel = 200; + break; + case FIONREAD: + rc = sizeof(status); + if (!out_bufsz) + break; + status = (datenklo->rx_fifo_in - datenklo->rx_fifo_out + datenklo->rx_fifo_size) % datenklo->rx_fifo_size; + memcpy(buf, &status, rc); +#ifdef HEAVY_DEBUG + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal requests RX buffer fill states.\n"); +#endif + break; + case TIOCOUTQ: + rc = sizeof(status); + if (!out_bufsz) + break; +#ifdef HEAVY_DEBUG + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal requests TX buffer fill states.\n"); +#endif + status = (datenklo->tx_fifo_in - datenklo->tx_fifo_out + datenklo->tx_fifo_size) % datenklo->tx_fifo_size; + memcpy(buf, &status, rc); + break; + default: + rc = -EINVAL; + } + + return rc; +} + +static double tx_baud_rate(datenklo_t *datenklo) +{ + double baudrate; + + if (datenklo->force_tx_baud) + baudrate = datenklo->force_tx_baud; + else + baudrate = datenklo->baudrate; + if (baudrate > datenklo->max_baud) + baudrate = datenklo->max_baud; + + return baudrate; +} + +static double rx_baud_rate(datenklo_t *datenklo) +{ + double baudrate; + + if (datenklo->force_rx_baud) + baudrate = datenklo->force_rx_baud; + else + baudrate = datenklo->baudrate; + if (baudrate > datenklo->max_baud) + baudrate = datenklo->max_baud; + + return baudrate; +} + +/* helper to set termios */ +static void set_termios(datenklo_t *datenklo, const void *buf) +{ + double old_baud, new_baud; + int old_databits, new_databits; + enum uart_parity old_parity, new_parity; + int old_stopbits, new_stopbits; + int old_ignbrk, new_ignbrk; + int old_parmrk, new_parmrk; + int old_istrip, new_istrip; + int old_inlcr, new_inlcr; + int old_igncr, new_igncr; + int old_icrnl, new_icrnl; + int old_iuclc, new_iuclc; + int old_opost, new_opost; + int old_onlcr, new_onlcr; + int old_ocrnl, new_ocrnl; + int old_onlret, new_onlret; + int old_olcuc, new_olcuc; + int old_echo, new_echo; + int rc; + + old_baud = cflag2baud(datenklo->termios.c_cflag & CBAUD); + old_databits = cflag2databits(datenklo->termios.c_cflag); + old_parity = cflag2parity(datenklo->termios.c_cflag); + old_stopbits = cflag2stopbits(datenklo->termios.c_cflag); + old_ignbrk = !!(datenklo->termios.c_iflag & IGNBRK); + old_parmrk = !!(datenklo->termios.c_iflag & PARMRK); + old_istrip = !!(datenklo->termios.c_iflag & ISTRIP); + old_inlcr = !!(datenklo->termios.c_iflag & INLCR); + old_igncr = !!(datenklo->termios.c_iflag & IGNCR); + old_icrnl = !!(datenklo->termios.c_iflag & ICRNL); + old_iuclc = !!(datenklo->termios.c_iflag & IUCLC); + old_opost = !!(datenklo->termios.c_oflag & OPOST); + old_onlcr = !!(datenklo->termios.c_oflag & ONLCR); + old_ocrnl = !!(datenklo->termios.c_oflag & OCRNL); + old_onlret = !!(datenklo->termios.c_oflag & ONLRET); + old_olcuc = !!(datenklo->termios.c_oflag & OLCUC); + old_echo = !!(datenklo->termios.c_lflag & ECHO); + + memcpy(&datenklo->termios, buf, sizeof(datenklo->termios)); + + new_baud = cflag2baud(datenklo->termios.c_cflag & CBAUD); + new_databits = cflag2databits(datenklo->termios.c_cflag); + new_parity = cflag2parity(datenklo->termios.c_cflag); + new_stopbits = cflag2stopbits(datenklo->termios.c_cflag); + new_ignbrk = !!(datenklo->termios.c_iflag & IGNBRK); + new_parmrk = !!(datenklo->termios.c_iflag & PARMRK); + new_istrip = !!(datenklo->termios.c_iflag & ISTRIP); + new_inlcr = !!(datenklo->termios.c_iflag & INLCR); + new_igncr = !!(datenklo->termios.c_iflag & IGNCR); + new_icrnl = !!(datenklo->termios.c_iflag & ICRNL); + new_iuclc = !!(datenklo->termios.c_iflag & IUCLC); + new_opost = !!(datenklo->termios.c_oflag & OPOST); + new_onlcr = !!(datenklo->termios.c_oflag & ONLCR); + new_ocrnl = !!(datenklo->termios.c_oflag & OCRNL); + new_onlret = !!(datenklo->termios.c_oflag & ONLRET); + new_olcuc = !!(datenklo->termios.c_oflag & OLCUC); + new_echo = !!(datenklo->termios.c_lflag & ECHO); + + if (old_baud != new_baud && (!datenklo->force_tx_baud || !datenklo->force_rx_baud)) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Terminal changes baud rate to %.1f Baud.\n", new_baud); + if ((datenklo->lines & TIOCM_DTR) && !new_baud) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Baudrate is set to 0, we drop DTR\n"); + am791x_dtr(&datenklo->am791x, 0); + } + datenklo->baudrate = new_baud; + am791x_mc(&datenklo->am791x, datenklo->mc, datenklo->samplerate, tx_baud_rate(datenklo), rx_baud_rate(datenklo)); + if ((datenklo->lines & TIOCM_DTR) && !old_baud) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Baudrate is set from 0, we raise DTR\n"); + am791x_dtr(&datenklo->am791x, 1); + } + } + + if (old_databits != new_databits + || old_parity != new_parity + || old_stopbits != new_stopbits) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Terminal changes serial mode to %d%c%d.\n", cflag2databits(datenklo->termios.c_cflag), parity2char(cflag2parity(datenklo->termios.c_cflag)), cflag2stopbits(datenklo->termios.c_cflag)); + rc = uart_init(&datenklo->uart, datenklo, cflag2databits(datenklo->termios.c_cflag), cflag2parity(datenklo->termios.c_cflag), cflag2stopbits(datenklo->termios.c_cflag), tx, rx); + if (rc < 0) + PDEBUG(DDATENKLO, DEBUG_ERROR, "Failed to initialize UART.\n"); + } + + if (old_stopbits != new_stopbits + || old_ignbrk != new_ignbrk + || old_parmrk != new_parmrk + || old_istrip != new_istrip + || old_inlcr != new_inlcr + || old_igncr != new_igncr + || old_icrnl != new_icrnl + || old_iuclc != new_iuclc + || old_opost != new_opost + || old_onlcr != new_onlcr + || old_ocrnl != new_ocrnl + || old_onlret != new_onlret + || old_olcuc != new_olcuc + || old_echo != new_echo) { + datenklo->ignbrk = new_ignbrk; + datenklo->parmrk = new_parmrk; + datenklo->istrip = new_istrip; + datenklo->inlcr = new_inlcr; + datenklo->igncr = new_igncr; + datenklo->icrnl = new_icrnl; + datenklo->iuclc = new_iuclc; + datenklo->opost = new_opost; + datenklo->onlcr = new_onlcr; + datenklo->ocrnl = new_ocrnl; + datenklo->onlret = new_onlret; + datenklo->olcuc = new_olcuc; + datenklo->echo = new_echo; + PDEBUG(DDATENKLO, DEBUG_INFO, "Terminal sets serial flags:\n"); + PDEBUG(DDATENKLO, DEBUG_INFO, "%cignbrk %cparmrk %cistrip %cinlcr %cigncr %cicrnl %ciuclc %copost %conlcr %cocrnl %conlret %colcuc %cecho\n", + (datenklo->ignbrk) ? '+' : '-', + (datenklo->parmrk) ? '+' : '-', + (datenklo->istrip) ? '+' : '-', + (datenklo->inlcr) ? '+' : '-', + (datenklo->igncr) ? '+' : '-', + (datenklo->icrnl) ? '+' : '-', + (datenklo->iuclc) ? '+' : '-', + (datenklo->opost) ? '+' : '-', + (datenklo->onlcr) ? '+' : '-', + (datenklo->ocrnl) ? '+' : '-', + (datenklo->onlret) ? '+' : '-', + (datenklo->olcuc) ? '+' : '-', + (datenklo->echo) ? '+' : '-'); + } +} + +/* tty performs all IOCTLs that sets states or performs actions */ +static ssize_t dk_ioctl_set(void *inst, int cmd, const void *buf, size_t in_bufsz) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + int status; + ssize_t rc = 0; + size_t space; + +#ifdef HEAVY_DEBUG + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Device has been written for ioctl (cmd = %d, size = %zu).\n", cmd, in_bufsz); +#endif + + switch (cmd) { + case TCSETS: + rc = sizeof(datenklo->termios); + if (!in_bufsz) + break; + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal sets termios now.\n"); + set_termios(datenklo, buf); + break; + case TCSETSW: + case TCSETSF: + rc = sizeof(datenklo->termios); + if (!in_bufsz) + break; + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal sets termios after draining output buffer.\n"); + if (1 || datenklo->tx_fifo_out == datenklo->tx_fifo_in) { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Output buffer empty, applying termios now.\n"); + set_termios(datenklo, buf); + break; + } + memcpy(&datenklo->tcsetsw_termios, buf, rc); + if (cmd == TCSETSW) + datenklo->tcsetsw = 1; + else + datenklo->tcsetsw = 2; + break; + case TCFLSH: + rc = sizeof(status); + if (!in_bufsz) + break; + memcpy(&status, buf, rc); + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal flushes buffer (status = %d).\n", status); + if (status == TCIOFLUSH || status == TCOFLUSH) { + flush_tx(datenklo); + if (!(datenklo->revents & POLLOUT)) { + /* tell cuse to write again */ + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Set POLLOUT (flushed)\n"); + datenklo->revents |= POLLOUT; + device_set_poll_events(datenklo->device, datenklo->revents); + } + } + if (status == TCIOFLUSH || status == TCIFLUSH) { + flush_rx(datenklo); + if ((datenklo->revents & POLLIN)) { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Reset POLLIN (flushed)\n"); + datenklo->revents &= ~POLLIN; + device_set_poll_events(datenklo->device, datenklo->revents); + } + } + break; + case TCSBRK: + rc = 0; + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal sends break\n"); + datenklo->break_bits = tx_baud_rate(datenklo) * 3 / 10; + break; + case TCSBRKP: + rc = sizeof(status); + if (!in_bufsz) + break; + memcpy(&status, buf, rc); + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal sends break (duration = %d).\n", status); + if (status == 0) + status = 3; + if (status > 30) + status = 30; + datenklo->break_bits = tx_baud_rate(datenklo) * status / 10; + break; + case TIOCSBRK: + rc = 0; + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal turns break on\n"); + datenklo->break_on = 1; + break; + case TIOCCBRK: + rc = 0; + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal turns break off\n"); + datenklo->break_on = 0; + break; + case TIOCMBIS: + rc = sizeof(status); + if (!in_bufsz) + break; + memcpy(&status, buf, rc); + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal sets line status (0x%x).\n", status); + status = datenklo->lines | status; + set_lines(datenklo, status); + break; + case TIOCMBIC: + rc = sizeof(status); + if (!in_bufsz) + break; + memcpy(&status, buf, rc); + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal clears line status (0x%x).\n", status); + status = datenklo->lines & ~status; + set_lines(datenklo, status); + break; + case TIOCMSET: + rc = sizeof(status); + if (!in_bufsz) + break; + memcpy(&status, buf, rc); + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal specifies line status (0x%x).\n", status); + set_lines(datenklo, status); + break; + case TIOCGSID: + PDEBUG(DDATENKLO, DEBUG_DEBUG, "TIOGSID -> ENOTTY\n"); + rc = -ENOTTY; + break; + case TIOCGPGRP: + PDEBUG(DDATENKLO, DEBUG_DEBUG, "TIOCGPGRP -> ENOTTY\n"); + rc = -ENOTTY; + break; + case TIOCSCTTY: + PDEBUG(DDATENKLO, DEBUG_DEBUG, "TIOCSCTTY -> ENOTTY\n"); + rc = -ENOTTY; + break; + case TIOCSPGRP: + PDEBUG(DDATENKLO, DEBUG_DEBUG, "TIOCSPGRP -> ENOTTY\n"); + rc = -ENOTTY; + break; + case TIOCSWINSZ: + rc = sizeof(struct winsize); + if (!in_bufsz) + break; + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal sets window size.\n"); + break; + case TCXONC: + rc = sizeof(status); + if (!in_bufsz) + break; + memcpy(&status, buf, rc); + switch (status) { + case TCOOFF: + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal turns off output.\n"); + datenklo->output_off = 1; + break; + case TCOON: + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal turns on output.\n"); + datenklo->output_off = 1; + break; + case TCIOFF: + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal turns off input.\n"); + space = (datenklo->rx_fifo_out - datenklo->rx_fifo_in - 1 + datenklo->rx_fifo_size) % datenklo->rx_fifo_size; + if (space < 1) + break; + datenklo->rx_fifo[datenklo->rx_fifo_in++] = datenklo->termios.c_cc[VSTOP]; + datenklo->rx_fifo_in %= datenklo->rx_fifo_size; + break; + case TCION: + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal turns on input.\n"); + space = (datenklo->rx_fifo_out - datenklo->rx_fifo_in - 1 + datenklo->rx_fifo_size) % datenklo->rx_fifo_size; + if (space < 1) + break; + datenklo->rx_fifo[datenklo->rx_fifo_in++] = datenklo->termios.c_cc[VSTART]; + datenklo->rx_fifo_in %= datenklo->rx_fifo_size; + break; + } + break; + default: + rc = -EINVAL; + } + + return rc; +} + +/* tty has been opened */ +static int dk_open(void *inst, int flags) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + + if (datenklo->open_count) { + PDEBUG(DDATENKLO, DEBUG_NOTICE, "Device is busy.\n"); + return -EBUSY; + } + datenklo->open_count++; + datenklo->flags = flags; + PDEBUG(DDATENKLO, DEBUG_INFO, "Device has been opened.\n"); + int status = datenklo->lines | TIOCM_DTR | TIOCM_RTS; + set_lines(datenklo, status); + + return 0; +} + +/* tty has been closed */ +static void dk_close(void *inst) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + + PDEBUG(DDATENKLO, DEBUG_INFO, "Device has been closed.\n"); + datenklo->open_count--; + int status = datenklo->lines & ~(TIOCM_DTR | TIOCM_RTS); + set_lines(datenklo, status); + datenklo->output_off = 0; +} + +/* helper to debug buffer content */ +static void debug_data(const char *buf, int count) +{ + char text[41]; + size_t i; + + while (count) { + for (i = 0; count && i < sizeof(text) - 1; i++) { + text[i] = (*buf >= 32 && *buf <= 126) ? *buf : '.'; + buf++; + count--; + } + text[i] = '\0'; + PDEBUG(DDATENKLO, DEBUG_DEBUG, " \"%s\"\n", text); + } +} + +/* tty performs read */ +static ssize_t dk_read(void *inst, char *buf, size_t size, int flags) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + size_t fill, space; + size_t i, count; + unsigned char vtime = datenklo->termios.c_cc[VTIME]; + unsigned char vmin = datenklo->termios.c_cc[VMIN]; + + fill = (datenklo->rx_fifo_in - datenklo->rx_fifo_out + datenklo->rx_fifo_size) % datenklo->rx_fifo_size; + space = (datenklo->tx_fifo_out - datenklo->tx_fifo_in - 1 + datenklo->tx_fifo_size) % datenklo->tx_fifo_size; + + /* both MIN and TIME are nonzero */ + if (vmin && vtime) { + /* first: start timer */ + if (!datenklo->vtimeout) + timer_start(&datenklo->vtimer, (double)vtime * 0.1); + /* no data: block (in blocking IO) */ + if (fill == 0) { + /* special value to tell device there is no data right now, we have to block */ + return -EAGAIN; + } + /* not enough data and no timeout and blocking IO: block */ + if (fill < vmin && !datenklo->vtimeout && !(flags & O_NONBLOCK)) { + /* special value to tell device there is no data right now, we have to block */ + return -EAGAIN; + } + /* enough data or timeout or nonblocking IO: stop timer and return what we have */ + datenklo->vtimeout = 0; + timer_stop(&datenklo->vtimer); + } + /* both MIN and TIME are zero */ + if (!vmin && !vtime) { + /* no data: return 0 */ + if (fill == 0) + return 0; + /* data: return what we have */ + } + /* MIN is zero, TIME is nonzero */ + if (!vmin && vtime) { + /* first: start timer */ + if (!datenklo->vtimeout) + timer_start(&datenklo->vtimer, (double)vtime * 0.1); + if (fill == 0) { + /* no data and no timeout: block (in blocking IO) */ + if (!datenklo->vtimeout) { + /* special value to tell device there is no data right now, we have to block */ + return -EAGAIN; + } + /* no data and timeout: return 0 */ + datenklo->vtimeout = 0; + return 0; + } + /* data: stop timer and return what we have */ + datenklo->vtimeout = 0; + timer_stop(&datenklo->vtimer); + } + /* MIN is nonzero, TIME is zero */ + if (vmin && !vtime) { + /* less data than vmin (or buffer full): block (in blocking IO) */ + if (fill < vmin || !space) { + /* special value to tell device there is no data right now, we have to block */ + return -EAGAIN; + } + /* enough data in buffer: return what we have */ + } + + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Device has been read from. (fill = %zu)\n", fill); + + /* get data from fifo */ + count = 0; + for (i = 0; i < size; i++) { + if (!fill) + break; + fill--; + buf[i] = datenklo->rx_fifo[datenklo->rx_fifo_out++]; + datenklo->rx_fifo_out %= datenklo->rx_fifo_size; + count++; + } + + debug_data(buf, count); + + if (!fill) { + /* tell cuse not to read anymore */ + if ((datenklo->revents & POLLIN)) { + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Reset POLLIN (now empty)!\n"); + datenklo->revents &= ~POLLIN; + device_set_poll_events(datenklo->device, datenklo->revents); + } + } + + return count; +} + +/* tty performs write */ +static ssize_t dk_write(void *inst, const char *buf, size_t size, int __attribute__((unused)) flags) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + size_t space, fill; + size_t i; + + if (!(datenklo->lines & TIOCM_DTR)) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Dropping data, DTR is off!\n"); + return -EIO; + } + + if (!(datenklo->lines & TIOCM_RTS)) { + PDEBUG(DDATENKLO, DEBUG_INFO, "Dropping data, RTS is off!\n"); + return -EIO; + } + + if (size > (size_t)datenklo->tx_fifo_size - 1) { + PDEBUG(DDATENKLO, DEBUG_NOTICE, "Device sends us too many data. (size = %zu)\n", size); + return -EIO; + } + + space = (datenklo->tx_fifo_out - datenklo->tx_fifo_in - 1 + datenklo->tx_fifo_size) % datenklo->tx_fifo_size; + + /* block if not enough space AND buffer is not completely empty */ + if (space < size && datenklo->tx_fifo_out != datenklo->tx_fifo_in) { + /* special value to tell device there is no data right now, we have to block */ + return -EAGAIN; + } + + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Device has been written to. (space = %zu)\n", space); + debug_data(buf, size); + + if (datenklo->auto_rts) + datenklo->auto_rts_on = 1; + + /* put data to fifo */ + for (i = 0; i < size; i++) { + datenklo->tx_fifo[datenklo->tx_fifo_in++] = buf[i]; + datenklo->tx_fifo_in %= datenklo->tx_fifo_size; + } + + fill = (datenklo->tx_fifo_in - datenklo->tx_fifo_out + datenklo->tx_fifo_size) % datenklo->tx_fifo_size; + + if ((datenklo->revents & POLLOUT) && fill >= (size_t)datenklo->tx_fifo_full) { + /* tell cuse not to write */ + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Reset POLLOUT (buffer full)\n"); + datenklo->revents &= ~POLLOUT; + device_set_poll_events(datenklo->device, datenklo->revents); + } + + return size; +} + +static void dk_flush_tx(void *inst) +{ + datenklo_t *datenklo = (datenklo_t *)inst; + + PDEBUG(DDATENKLO, DEBUG_INFO, "Terminal sends interrupt while writing, flushing TX buffer\n"); + flush_tx(datenklo); +} + +/* tty locks main thread to call our functions */ +static void dk_lock(void) +{ + pthread_mutex_lock(&mutex); +} + +/* tty unlocks main thread */ +static void dk_unlock(void) +{ + pthread_mutex_unlock(&mutex); +} + +/* signal handler to exit */ +void sighandler(int sigset) +{ + if (sigset == SIGHUP) + return; + if (sigset == SIGPIPE) + return; + + printf("Signal received: %d\n", sigset); + + quit = 1; +} + +/* vtimer */ +static void vtime_timeout(struct timer *timer) +{ + datenklo_t *datenklo = (datenklo_t *)timer->priv; + + /* check if there is enough data to read */ + datenklo->vtimeout = 1; + device_read_available(datenklo->device); +} + +/* global init is required for the mutex */ +void datenklo_init_global(void) +{ + if (pthread_mutex_init(&mutex, NULL)) { + PDEBUG(DDATENKLO, DEBUG_ERROR, "Failed to init mutex.\n"); + exit(0); + } +} + +/* init function */ +int datenklo_init(datenklo_t *datenklo, const char *dev_name, enum am791x_type am791x_type, uint8_t mc, int auto_rts, double force_tx_baud, double force_rx_baud, int samplerate, int loopback) +{ + int rc = 0; + tcflag_t flag; + cc_t *cc; + + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Creating Datenklo instance.\n"); + + memset(datenklo, 0, sizeof(*datenklo)); + + datenklo->samplerate = samplerate; + datenklo->loopback = loopback; + + datenklo->mc = mc; + datenklo->auto_rts = auto_rts; + datenklo->max_baud = am791x_max_baud(datenklo->mc); + datenklo->baudrate = datenklo->max_baud; + datenklo->force_tx_baud = force_tx_baud; + datenklo->force_rx_baud = force_rx_baud; + if ((force_tx_baud && force_tx_baud <= 150) || datenklo->max_baud <= 150) + datenklo->tx_back = 1; + if ((force_rx_baud && force_rx_baud <= 150) || datenklo->max_baud <= 150) + datenklo->rx_back = 1; + + /* default termios */ + flag = 0; + flag |= baud2cflag(datenklo->baudrate); + flag |= CS8; + flag |= CREAD; + datenklo->termios.c_cflag = flag; + flag = 0; + flag |= OPOST | ONLCR; + datenklo->termios.c_oflag = flag; + cc = datenklo->termios.c_cc; + cc[VDISCARD] = 017; +// cc[VDSUSP] = 031; + cc[VEOF] = 004; + cc[VEOL] = 0; + cc[VEOL2] = 0; + cc[VERASE] = 0177; + cc[VINTR] = 003; + cc[VKILL] = 025; + cc[VLNEXT] = 026; + cc[VMIN] = 0; + cc[VQUIT] = 034; + cc[VREPRINT] = 022; + cc[VSTART] = 021; +// cc[VSTATUS] = 024; + cc[VSTOP] = 023; + cc[VSUSP] = 032; + cc[VSWTC] = 0; + cc[VTIME] = 0; + cc[VWERASE] = 027; + + datenklo->device = device_init(datenklo, dev_name, dk_open, dk_close, dk_read, dk_write, dk_ioctl_get, dk_ioctl_set, dk_flush_tx, dk_lock, dk_unlock); + if (!datenklo->device) { + PDEBUG(DDATENKLO, DEBUG_ERROR, "Failed to attach virtual device '%s' using cuse.\n", dev_name); + rc = -errno; + goto error; + } + datenklo->revents = POLLOUT; + device_set_poll_events(datenklo->device, datenklo->revents); + + datenklo->baudrate = cflag2baud(datenklo->termios.c_cflag); + rc = am791x_init(&datenklo->am791x, datenklo, am791x_type, datenklo->mc, datenklo->samplerate, tx_baud_rate(datenklo), rx_baud_rate(datenklo), cts, bcts, cd, bcd, td, btd, rd, brd); + if (rc < 0) { + PDEBUG(DDATENKLO, DEBUG_ERROR, "Failed to initialize AM791X modem chip.\n"); + goto error; + } + + datenklo->tx_fifo_size = 4097; + datenklo->tx_fifo_full = 4097; /* poll events disabled if same as size */ + datenklo->rx_fifo_size = 4097; + datenklo->tx_fifo = calloc(datenklo->tx_fifo_size, 1); + datenklo->rx_fifo = calloc(datenklo->rx_fifo_size, 1); + if (!datenklo->tx_fifo || !datenklo->rx_fifo) { + PDEBUG(DDATENKLO, DEBUG_ERROR, "No mem!\n"); + rc = -ENOMEM; + goto error; + } + + timer_init(&datenklo->vtimer, vtime_timeout, datenklo); + + rc = uart_init(&datenklo->uart, datenklo, cflag2databits(datenklo->termios.c_cflag), cflag2parity(datenklo->termios.c_cflag), cflag2stopbits(datenklo->termios.c_cflag), tx, rx); + if (rc < 0) { + PDEBUG(DDATENKLO, DEBUG_ERROR, "Failed to initialize UART.\n"); + goto error; + } + + display_wave_init(&datenklo->dispwav, samplerate, dev_name); + display_measurements_init(&datenklo->dispmeas, samplerate, dev_name); + + datenklo->dmp_level = display_measurements_add(&datenklo->dispmeas, "Level", "%.1f dBm", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, -50.0, 5.0, 0.0); + datenklo->dmp_quality = display_measurements_add(&datenklo->dispmeas, "Quality", "%.1f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 100.0, 100.0); + + return 0; + +error: + datenklo_exit(datenklo); + return rc; +} + +/* open audio device of one or two datenlo_t instance */ +int datenklo_open_audio(datenklo_t *datenklo, const char *audiodev, int latency, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave) +{ + int channels = 1; + int rc; + + /* stereo */ + if (datenklo->slave) + channels = 2; + + /* latency of send buffer in samples */ + datenklo->latspl = datenklo->samplerate * latency / 1000; + +#ifdef HAVE_ALSA + /* init sound */ + datenklo->audio = sound_open(audiodev, NULL, NULL, channels, 0.0, datenklo->samplerate, datenklo->latspl, 1.0, 4000.0); + if (!datenklo->audio) { + PDEBUG(DDATENKLO, DEBUG_ERROR, "No sound device!\n"); + return -EIO; + } +#endif + + if (write_rx_wave) { + rc = wave_create_record(&datenklo->wave_rx_rec, write_rx_wave, datenklo->samplerate, channels, 1.0); + if (rc < 0) { + PDEBUG(DDATENKLO, DEBUG_ERROR, "Failed to create WAVE recoding instance!\n"); + return rc; + } + } + if (write_tx_wave) { + rc = wave_create_record(&datenklo->wave_tx_rec, write_tx_wave, datenklo->samplerate, channels, 1.0); + if (rc < 0) { + PDEBUG(DDATENKLO, DEBUG_ERROR, "Failed to create WAVE recoding instance!\n"); + return rc; + } + } + if (read_rx_wave) { + rc = wave_create_playback(&datenklo->wave_rx_play, read_rx_wave, &datenklo->samplerate, &channels, 1.0); + if (rc < 0) { + PDEBUG(DDATENKLO, DEBUG_ERROR, "Failed to create WAVE playback instance!\n"); + return rc; + } + } + if (read_tx_wave) { + rc = wave_create_playback(&datenklo->wave_tx_play, read_tx_wave, &datenklo->samplerate, &channels, 1.0); + if (rc < 0) { + PDEBUG(DDATENKLO, DEBUG_ERROR, "Failed to create WAVE playback instance!\n"); + return rc; + } + } + + return 0; +} + +static int get_char() +{ + struct timeval tv = {0, 0}; + fd_set fds; + char c = 0; + int __attribute__((__unused__)) rc; + + FD_ZERO(&fds); + FD_SET(0, &fds); + select(0+1, &fds, NULL, NULL, &tv); + if (FD_ISSET(0, &fds)) { + rc = read(0, &c, 1); + return c; + } else + return -1; +} + +/* main loop */ +void datenklo_main(datenklo_t *datenklo, int loopback) +{ + int num_chan = 1; + int interval = 1; + double begin_time, now, sleep; + struct termios term, term_orig; + int c; + int i; + + /* stereo */ + if (datenklo->slave) + num_chan = 2; + + sample_t buff[num_chan][datenklo->latspl], *samples[num_chan]; + uint8_t pbuff[num_chan][datenklo->latspl], *power[num_chan]; + for (i = 0; i < num_chan; i++) { + samples[i] = buff[i]; + power[i] = pbuff[i]; + } + double rf_level_db[num_chan]; + int count; + int __attribute__((unused)) rc; + + pthread_mutex_lock(&mutex); + + /* prepare terminal */ + tcgetattr(0, &term_orig); + term = term_orig; + term.c_lflag &= ~(ISIG|ICANON|ECHO); + term.c_cc[VMIN]=1; + term.c_cc[VTIME]=2; + tcsetattr(0, TCSANOW, &term); + + /* catch signals */ + signal(SIGINT, sighandler); + signal(SIGHUP, sighandler); + signal(SIGTERM, sighandler); + signal(SIGPIPE, sighandler); + + sound_start(datenklo->audio); + + while (!quit) { + begin_time = get_time(); + + process_auto_rts(datenklo); + /* process Auto RTS */ + if (num_chan > 1) + process_auto_rts(datenklo->slave); + + /* process timers */ + process_timer(); + +#ifdef HAVE_ALSA + count = sound_read(datenklo->audio, samples, datenklo->latspl, num_chan, rf_level_db); + if (count < 0) { + PDEBUG(DDSP, DEBUG_ERROR, "Failed to read RX data from audio device (rc = %d)\n", count); + if (count == -EPIPE) { + PDEBUG(DDATENKLO, DEBUG_ERROR, "Trying to recover!\n"); + continue; + } + break; + } +#endif + + /* record received audio to wave */ + if (datenklo->wave_rx_rec.fp) + wave_write(&datenklo->wave_rx_rec, samples, count); + + /* replace received audio from wave */ + if (datenklo->wave_rx_play.fp) + wave_read(&datenklo->wave_rx_play, samples, count); + + /* put audio into modem */ + if (!loopback) { + am791x_receive(&datenklo->am791x, samples[0], count); + display_wave(&datenklo->dispwav, samples[0], count, 1); + if (num_chan > 1) { + am791x_receive(&datenklo->slave->am791x, samples[1], count); + display_wave(&datenklo->slave->dispwav, samples[1], count, 1); + } + } + +#ifdef HAVE_ALSA + count = sound_get_tosend(datenklo->audio, datenklo->latspl); +#else + count = samplerate / 1000; +#endif + if (count < 0) { + PDEBUG(DDSP, DEBUG_ERROR, "Failed to get number of samples in buffer (rc = %d)!\n", count); + if (count == -EPIPE) { + PDEBUG(DDATENKLO, DEBUG_ERROR, "Trying to recover!\n"); + continue; + } + break; + } + + /* get audio from modem */ + am791x_send(&datenklo->am791x, samples[0], count); + if (num_chan > 1) { + am791x_send(&datenklo->slave->am791x, samples[1], count); + } + if (loopback) { + /* copy buffer to preserve original audio for later use */ + sample_t lbuff[num_chan][datenklo->latspl]; + memcpy(lbuff, buff, sizeof(lbuff)); + if (loopback == 2 && num_chan == 2) { + /* swap */ + samples[0] = lbuff[1]; + samples[1] = lbuff[0]; + } else { + samples[0] = lbuff[0]; + samples[1] = lbuff[1]; + } + am791x_receive(&datenklo->am791x, samples[0], count); + display_wave(&datenklo->dispwav, samples[0], count, 1); + if (num_chan > 1) { + am791x_receive(&datenklo->slave->am791x, samples[1], count); + display_wave(&datenklo->slave->dispwav, samples[1], count, 1); + } + samples[0] = buff[0]; + samples[1] = buff[1]; + } + memset(power[0], 1, count); + + /* write generated audio to wave */ + if (datenklo->wave_tx_rec.fp) + wave_write(&datenklo->wave_tx_rec, samples, count); + + /* replace generated audio from wave */ + if (datenklo->wave_tx_play.fp) + wave_read(&datenklo->wave_tx_play, samples, count); + +#ifdef HAVE_ALSA + /* write audio */ + rc = sound_write(datenklo->audio, samples, power, count, NULL, NULL, num_chan); + if (rc < 0) { + PDEBUG(DDSP, DEBUG_ERROR, "Failed to write TX data to audio device (rc = %d)\n", rc); + if (rc == -EPIPE) { + PDEBUG(DDATENKLO, DEBUG_ERROR, "Trying to recover!\n"); + continue; + } + break; + } +#endif + +next_char: + c = get_char(); + switch (c) { + case 3: + /* quit */ + if (clear_console_text) + clear_console_text(); + printf("CTRL+c received, quitting!\n"); + quit = 1; + goto next_char; + case 'w': + /* toggle wave display */ + display_measurements_on(0); + display_wave_on(-1); + goto next_char; + case 'm': + /* toggle measurements display */ + display_wave_on(0); + display_measurements_on(-1); + goto next_char; + } + + display_measurements((double)interval / 1000.0); + + now = get_time(); + + /* sleep interval */ + sleep = ((double)interval / 1000.0) - (now - begin_time); + + pthread_mutex_unlock(&mutex); + if (sleep > 0) + usleep(sleep * 1000000.0); + pthread_mutex_lock(&mutex); + } + + /* get rid of last entry */ + if (clear_console_text) + clear_console_text(); + + /* reset terminal */ + tcsetattr(0, TCSANOW, &term_orig); + + /* reset signals */ + signal(SIGINT, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + + pthread_mutex_unlock(&mutex); +} + +/* cleanup function */ +void datenklo_exit(datenklo_t *datenklo) +{ + PDEBUG(DDATENKLO, DEBUG_DEBUG, "Destroying Datenklo instance.\n"); + + timer_exit(&datenklo->vtimer); + + /* exit device */ + if (datenklo->device) + device_exit(datenklo->device); + +#ifdef HAVE_ALSA + /* exit sound */ + if (datenklo->audio) + sound_close(datenklo->audio); +#endif + + wave_destroy_record(&datenklo->wave_rx_rec); + wave_destroy_record(&datenklo->wave_tx_rec); + wave_destroy_playback(&datenklo->wave_rx_play); + wave_destroy_playback(&datenklo->wave_tx_play); +} + diff --git a/src/datenklo/datenklo.h b/src/datenklo/datenklo.h new file mode 100644 index 0000000..1089da3 --- /dev/null +++ b/src/datenklo/datenklo.h @@ -0,0 +1,88 @@ + +enum datenklo_auto_mc { + DATENKLO_AUTO_MC_NONE = 0, + DATENKLO_AUTO_MC_BELL_ORIGINATE, + DATENKLO_AUTO_MC_BELL_ANSWER, + DATENKLO_AUTO_MC_BELL_4WIRE, + DATENKLO_AUTO_MC_CCITT_ORIGINATE, + DATENKLO_AUTO_MC_CCITT_ANSWER, + DATENKLO_AUTO_MC_CCITT_4WIRE, +}; + +typedef struct datenklo { + struct datenklo *slave; + + /* settings */ + uint8_t mc; /* modem chip mode */ + int auto_rts; /* automatic RTS controling for half duplex */ + double max_baud; /* limit to what the mode supports */ + double force_tx_baud, force_rx_baud; /* override IOCTL */ + int tx_back, rx_back; /* set if back channel is used for path */ + int samplerate; /* audio sample rate */ + int latspl; /* latenc */ + int loopback; /* loopback mode */ + + /* states */ + int flags; /* open() flags */ + struct termios termios; /* current termios */ + double baudrate; /* current baud rate */ + int lines; /* state of lines (from IOCTL) */ + int break_on; /* currently sending a break */ + int break_bits; /* counts bits while sending a break signal */ + int tcsetsw; /* send new termios after TX buffer is flused */ + struct termios tcsetsw_termios; /* new termios after TX buffer is flused */ + int ignbrk; /* IGNBRK option enabled */ + int parmrk; /* PARMRK option enabled */ + int istrip; /* ISTRIP option enabled */ + int inlcr; /* INLCR option enabled */ + int igncr; /* IGNCR option enabled */ + int icrnl; /* ICRNL option enabled */ + int iuclc; /* IUCLC option enabled */ + int opost; /* OPOST option to enable all post options */ + int onlcr; /* ONLCR option enabled */ + int onlcr_char; /* CR transmitted, next up is NL */ + int ocrnl; /* OCRNL option enabled */ + int onlret; /* ONLRET option enabled */ + int olcuc; /* OLCUC option enabled */ + int echo; /* ECHO option enabled */ + short revents; /* current set of poll reply events */ + int open_count; /* to see if device is in use */ + int auto_rts_on; /* Data available */ + int auto_rts_rts; /* RTS was raised */ + int auto_rts_cts; /* CTS was indicated */ + int auto_rts_cd; /* CD was indicated */ + int output_off; /* output stopped by flow control */ + struct timer vtimer; /* VTIME timer */ + int vtimeout; /* when timeout has fired */ + + /* data fifos */ + uint8_t *tx_fifo; + int tx_fifo_in, tx_fifo_out; + int tx_fifo_size; + int tx_fifo_full; /* watermark to change POLLOUT flag */ + uint8_t *rx_fifo; + int rx_fifo_in, rx_fifo_out; + int rx_fifo_size; + + /* instances */ + am791x_t am791x; /* da great modem IC */ + uart_t uart; /* soft uart */ + void *audio; /* sound interface */ + void *device; /* CUSE device */ + wave_rec_t wave_rx_rec; /* wave recording (from RX) */ + wave_rec_t wave_tx_rec; /* wave recording (from TX) */ + wave_play_t wave_rx_play; /* wave playback (as RX) */ + wave_play_t wave_tx_play; /* wave playback (as TX) */ + dispwav_t dispwav; /* wave display */ + dispmeas_t dispmeas; /* measurements display */ + dispmeasparam_t *dmp_level; + dispmeasparam_t *dmp_quality; + int last_bit; /* to check if we have valid quality */ +} datenklo_t; + +void datenklo_main(datenklo_t *datenklo, int loopback); +int datenklo_init(datenklo_t *datenklo, const char *dev_name, enum am791x_type am791x_type, uint8_t mc, int auto_rts, double force_tx_baud, double force_rx_baud, int samplerate, int loopback); +int datenklo_open_audio(datenklo_t *datenklo, const char *audiodev, int latency, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave); +void datenklo_exit(datenklo_t *datenklo); +void datenklo_init_global(void); + diff --git a/src/datenklo/device.c b/src/datenklo/device.c new file mode 100644 index 0000000..462c08e --- /dev/null +++ b/src/datenklo/device.c @@ -0,0 +1,559 @@ +/* character device link to libfuse + * + * (C) 2019 by Andreas Eversberg + * 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 . + */ + +#define FUSE_USE_VERSION 30 + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#define __USE_GNU +#include +#include + +#include "fioc.h" +#include "device.h" + +/* enable to heavily debug poll process */ +//#define DEBUG_POLL + +typedef struct device { + struct device *next; + void *inst; + pthread_t thread; + int thread_started, thread_stopped; + const char *name; + int major, minor; + int (*open_cb)(void *inst, int flags); + void (*close_cb)(void *inst); + ssize_t (*read_cb)(void *inst, char *buf, size_t size, int flags); + ssize_t (*write_cb)(void *inst, const char *buf, size_t size, int flags); + ssize_t (*ioctl_get_cb)(void *inst, int cmd, void *buf, size_t out_bufsz); + ssize_t (*ioctl_set_cb)(void *inst, int cmd, const void *buf, size_t in_bufsz); + void (*flush_tx)(void *inst); + void (*lock_cb)(void); + void (*unlock_cb)(void); + short poll_revents; + struct fuse_pollhandle *poll_handle; + /* handle read blocking */ + fuse_req_t read_req; + size_t read_size; + int read_flags; + int read_locked; + /* handle write blocking */ + fuse_req_t write_req; + size_t write_size; + char *write_buf; + int write_flags; + int write_locked; +} device_t; + +static device_t *device_list = NULL; + +static device_t *get_device_by_thread(void) +{ + device_t *device = device_list; + pthread_t thread = pthread_self(); + + while (device) { + if (device->thread == thread) + return device; + device = device->next; + } + + fprintf(stderr, "Our thread is unknown, please fix!\n"); + abort(); +} + +static void cuse_device_open(fuse_req_t req, struct fuse_file_info *fi) +{ + (void)fi; + int rc; + device_t *device = get_device_by_thread(); + + device->lock_cb(); + rc = device->open_cb(device->inst, fi->flags); + device->unlock_cb(); + + if (rc < 0) + fuse_reply_err(req, -rc); + else + fuse_reply_open(req, fi); +} + +static void cuse_device_release(fuse_req_t req, struct fuse_file_info *fi) +{ + (void)fi; + device_t *device = get_device_by_thread(); + + device->lock_cb(); + device->close_cb(device->inst); + device->unlock_cb(); + + fuse_reply_err(req, 0); +} + +static void cuse_read_interrupt(fuse_req_t req, void *data) +{ + (void)req; + device_t *device = (device_t *)data; + + if (!device->read_locked) + device->lock_cb(); + + PDEBUG(DDEVICE, DEBUG_DEBUG, "%s received interrupt from client!\n", device->name); + + if (device->read_req) { + device->read_req = NULL; + fuse_reply_err(req, EINTR); + } + + if (!device->read_locked) + device->unlock_cb(); +} + +void device_read_available(void *inst) +{ + device_t *device = (device_t *)inst; + ssize_t count; + + // we are locked by caller + + /* if enough data or if buffer is full */ + if (device->read_req) { + char buf[device->read_size]; + count = device->read_cb(device->inst, buf, device->read_size, device->read_flags); + /* still blocking, waiting for more... */ + if (count == -EAGAIN) + return; + fuse_reply_buf(device->read_req, buf, count); + device->read_req = NULL; + } +} + +static void cuse_device_read(fuse_req_t req, size_t size, off_t off, struct fuse_file_info *fi) +{ + (void)off; + (void)fi; + ssize_t count; + device_t *device = get_device_by_thread(); + + if (size > 65536) + size = 65536; + char buf[size]; + + device->lock_cb(); + + if (device->read_req) { + device->unlock_cb(); + PDEBUG(DDEVICE, DEBUG_ERROR, "%s: Got another read(), while first read() has not been replied, please fix.\n", device->name); + fuse_reply_err(req, EBUSY); + return; + } + +#ifdef DEBUG_POLL + puts("read: before fn"); +#endif + count = device->read_cb(device->inst, buf, size, fi->flags); +#ifdef DEBUG_POLL + puts("read: after fn"); +#endif + + /* this means that we block until modem's read() returns 0 or positive value (in nonblocking io, we return -EAGAIN) */ + if (!(fi->flags & O_NONBLOCK) && count == -EAGAIN) { + PDEBUG(DDEVICE, DEBUG_DEBUG, "%s has no data available, waiting for data, timer or interrupt.\n", device->name); + + device->read_req = req; + device->read_size = size; + device->read_flags = fi->flags; + /* to prevent race condition, tell cuse_write_interrupt that we are already locked. + * (interrupt may have come before and will be processed by fuse_req_interrupt_func()) + */ + device->read_locked = 1; + fuse_req_interrupt_func(req, cuse_read_interrupt, device); + device->read_locked = 0; + device->unlock_cb(); + return; + } + + device->unlock_cb(); + + if (count < 0) + fuse_reply_err(req, -count); + else + fuse_reply_buf(req, buf, count); +#ifdef DEBUG_POLL + puts("read: after reply"); +#endif +} + +static void cuse_write_interrupt(fuse_req_t req, void *data) +{ + (void)req; + device_t *device = (device_t *)data; + + if (!device->write_locked) + device->lock_cb(); + + PDEBUG(DDEVICE, DEBUG_DEBUG, "%s received interrupt from client!\n", device->name); + + if (device->write_req) { + device->write_req = NULL; + free(device->write_buf); + device->write_buf = NULL; + /* flushing TX buffer */ + device->flush_tx(device->inst); + fuse_reply_err(req, EINTR); + } + + if (!device->write_locked) + device->unlock_cb(); +} + +void device_write_available(void *inst) +{ + device_t *device = (device_t *)inst; + ssize_t count; + + // we are locked by caller + + /* if enough space or buffer empty */ + if (device->write_req) { + count = device->write_cb(device->inst, device->write_buf, device->write_size, device->write_flags); + /* still blocking, waiting for more... */ + if (count == -EAGAIN) + return; + fuse_reply_write(device->write_req, count); + device->write_req = NULL; + free(device->write_buf); + device->write_buf = NULL; + } +} + +static void cuse_device_write(fuse_req_t req, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) +{ + (void)off; + (void)fi; + ssize_t count; + device_t *device = get_device_by_thread(); + + device->lock_cb(); + + if (device->write_req) { + device->unlock_cb(); + PDEBUG(DDEVICE, DEBUG_ERROR, "%s: Got another write(), while first write() has not been replied, please fix.\n", device->name); + fuse_reply_err(req, EBUSY); + return; + } + + count = device->write_cb(device->inst, buf, size, fi->flags); + + /* this means that we block until modem's write() returns 0 or positive value (in nonblocking io, we return -EAGAIN) */ + if (!(fi->flags & O_NONBLOCK) && count == -EAGAIN) { + PDEBUG(DDEVICE, DEBUG_DEBUG, "%s has no buffer space available, waiting for space or interrupt.\n", device->name); + + device->write_req = req; + device->write_size = size; + device->write_buf = malloc(size); + if (!buf) { + PDEBUG(DDEVICE, DEBUG_ERROR, "No memory!\n"); + exit(0); + } + memcpy(device->write_buf, buf, size); + device->write_flags = fi->flags; + /* to prevent race condition, tell cuse_write_interrupt that we are already locked. + * (interrupt may have come before and will be processed by fuse_req_interrupt_func()) + */ + device->write_locked = 1; + fuse_req_interrupt_func(req, cuse_write_interrupt, device); + device->write_locked = 0; + device->unlock_cb(); + return; + } + + device->unlock_cb(); + + if (count < 0) + fuse_reply_err(req, -count); + else + fuse_reply_write(req, count); +} + +static void cuse_device_ioctl(fuse_req_t req, int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz) +{ + (void)fi; + ssize_t rc; + char out_buf[out_bufsz]; + device_t *device = get_device_by_thread(); + + if (flags & FUSE_IOCTL_COMPAT) { + fuse_reply_err(req, ENOSYS); + return; + } + + switch (cmd) { + case TCGETS: + case TIOCMGET: + case TIOCGWINSZ: + case FIONREAD: + case TIOCOUTQ: + device->lock_cb(); + rc = device->ioctl_get_cb(device->inst, cmd, out_buf, out_bufsz); + device->unlock_cb(); + if (rc < 0) { + fuse_reply_err(req, -rc); + break; + } + if (rc == 0) { + // do we need this ? + fuse_reply_ioctl(req, 0, NULL, 0); + break; + } + if (!out_bufsz) { + struct iovec iov = { arg, rc }; + + fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1); + } else { + fuse_reply_ioctl(req, 0, out_buf, rc); + } + break; + case TCSETS: + case TCSETSW: + case TCSETSF: + case TIOCMBIS: + case TIOCMBIC: + case TIOCMSET: + case TCFLSH: + case TCSBRK: + case TCSBRKP: + case TIOCSBRK: + case TIOCCBRK: + case TIOCGSID: + case TIOCGPGRP: + case TIOCSCTTY: + case TIOCSPGRP: + case TIOCSWINSZ: + case TCXONC: + device->lock_cb(); + rc = device->ioctl_set_cb(device->inst, cmd, in_buf, in_bufsz); + device->unlock_cb(); + if (rc < 0) { + fuse_reply_err(req, -rc); + break; + } + if (rc == 0) { + /* empty control is replied */ + fuse_reply_ioctl(req, 0, NULL, 0); + break; + } + if (!in_bufsz) { + struct iovec iov = { arg, rc }; + + fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0); + } else { + fuse_reply_ioctl(req, 0, NULL, 0); + } + break; + default: + PDEBUG(DDEVICE, DEBUG_NOTICE, "%s: receives unknown ioctl: 0x%x\n", device->name, cmd); + fuse_reply_err(req, EINVAL); + } +} + +void device_set_poll_events(void *inst, short revents) +{ + device_t *device = (device_t *)inst; + + // we are locked by caller + + if (revents == device->poll_revents) + return; + +#ifdef DEBUG_POLL + printf("new revents 0x%x\n", revents); +#endif + device->poll_revents = revents; + if (device->poll_handle) { +#ifdef DEBUG_POLL + printf("notify with handle %p\n", device->poll_handle); +#endif + fuse_lowlevel_notify_poll(device->poll_handle); + } +} + +static void cuse_device_poll(fuse_req_t req, struct fuse_file_info *fi, struct fuse_pollhandle *ph) +{ + (void)fi; + device_t *device = get_device_by_thread(); + +#ifdef DEBUG_POLL + printf("poll %p %p\n", ph, device->poll_handle); +#endif + if (ph) { + device->lock_cb(); + if (device->poll_handle) + fuse_pollhandle_destroy(device->poll_handle); + device->poll_handle = ph; +#ifdef DEBUG_POLL + printf("storing %p\n", device->poll_handle); +#endif + device->unlock_cb(); + } + +#ifdef DEBUG_POLL + printf("sending revents 0x%x\n", device->poll_revents); +#endif + fuse_reply_poll(req, device->poll_revents); +} + +static void cuse_device_flush(fuse_req_t req, struct fuse_file_info *fi) +{ + (void)req; + (void)fi; + device_t *device = get_device_by_thread(); + PDEBUG(DDEVICE, DEBUG_NOTICE, "%s: unhandled flush\n", device->name); +} + +static void cuse_device_fsync(fuse_req_t req, int datasync, struct fuse_file_info *fi) +{ + (void)req; + (void)datasync; + (void)fi; + device_t *device = get_device_by_thread(); + PDEBUG(DDEVICE, DEBUG_NOTICE, "%s: unhandled fsync\n", device->name); +} + + +static const struct cuse_lowlevel_ops cuse_device_clop = { + .open = cuse_device_open, + .release = cuse_device_release, + .read = cuse_device_read, + .write = cuse_device_write, + .ioctl = cuse_device_ioctl, + .poll = cuse_device_poll, + .fsync = cuse_device_fsync, + .flush = cuse_device_flush, +}; + +static void *device_child(void *arg) +{ + device_t *device = (device_t *)arg; + + int argc = 3; + /* use -f to run without debug, but -d to debug */ + char *argv[3] = { "datenklo", "-f", "-s" }; + char dev_name[128] = "DEVNAME="; + const char *dev_info_argv[] = { dev_name }; + struct cuse_info ci; + + strncat(dev_name, device->name, sizeof(dev_name) - strlen(device->name) - 1); + + memset(&ci, 0, sizeof(ci)); + ci.dev_major = device->major; + ci.dev_minor = device->minor; + ci.dev_info_argc = 1; + ci.dev_info_argv = dev_info_argv; + ci.flags = CUSE_UNRESTRICTED_IOCTL; + + device->thread_started = 1; + + PDEBUG(DDEVICE, DEBUG_INFO, "Device '%s' started.\n", device->name); + cuse_lowlevel_main(argc, argv, &ci, &cuse_device_clop, NULL); + PDEBUG(DDEVICE, DEBUG_INFO, "Device '%s' terminated.\n", device->name); + + device->thread_stopped = 1; + + return NULL; +} + +void *device_init(void *inst, const char *name, int (*open)(void *inst, int flags), void (*close)(void *inst), ssize_t (*read)(void *inst, char *buf, size_t size, int flags), ssize_t (*write)(void *inst, const char *buf, size_t size, int flags), ssize_t ioctl_get(void *inst, int cmd, void *buf, size_t out_bufsz), ssize_t ioctl_set(void *inst, int cmd, const void *buf, size_t in_bufsz), void (*flush_tx)(void *inst), void (*lock)(void), void (*unlock)(void)) +{ + int rc = -EINVAL; + char tname[64]; + device_t *device = NULL; + device_t **devicep; + + device = calloc(1, sizeof(*device)); + if (!device) { + PDEBUG(DDEVICE, DEBUG_ERROR, "No memory!\n"); + errno = ENOMEM; + goto error; + } + device->inst = inst; + device->name = name; + device->open_cb = open; + device->close_cb = close; + device->read_cb = read; + device->write_cb = write; + device->ioctl_get_cb = ioctl_get; + device->ioctl_set_cb = ioctl_set; + device->flush_tx = flush_tx; + device->lock_cb = lock; + device->unlock_cb = unlock; + + rc = pthread_create(&device->thread, NULL, device_child, device); + if (rc < 0) { + PDEBUG(DDEVICE, DEBUG_ERROR, "Failed to create device thread!\n"); + errno = -rc; + goto error; + } + + pthread_getname_np(device->thread, tname, sizeof(tname)); + strncat(tname, "-device", sizeof(tname) - 7 - 1); + tname[sizeof(tname) - 1] = '\0'; + pthread_setname_np(device->thread, tname); + + while (!device->thread_started) + usleep(100); + + /* attach to list */ + devicep = &device_list; + while (*devicep) + devicep = &((*devicep)->next); + *devicep = device; + + return device; + +error: + device_exit(device); + return NULL; +} + +void device_exit(void *inst) +{ + device_t *device = (device_t *)inst; + device_t **devicep; + + /* detach from list */ + devicep = &device_list; + while (*devicep && *devicep != device) + devicep = &((*devicep)->next); + if (*devicep) + *devicep = device->next; + + /* the device-thread is terminated when the program terminates, so no kill required (REALLY????) */ + + free(device); +} + diff --git a/src/datenklo/device.h b/src/datenklo/device.h new file mode 100644 index 0000000..e862b6d --- /dev/null +++ b/src/datenklo/device.h @@ -0,0 +1,7 @@ + +void *device_init(void *inst, const char *name, int (*open)(void *inst, int flags), void (*close)(void *inst), ssize_t (*read)(void *inst, char *buf, size_t size, int flags), ssize_t (*write)(void *inst, const char *buf, size_t size, int flags), ssize_t ioctl_get(void *inst, int cmd, void *buf, size_t out_bufsz), ssize_t ioctl_set(void *inst, int cmd, const void *buf, size_t in_bufsz), void (*flush_tx)(void *inst), void (*lock)(void), void (*unlock)(void)); +void device_exit(void *inst); +void device_set_poll_events(void *inst, short revents); +void device_read_available(void *inst); +void device_write_available(void *inst); + diff --git a/src/datenklo/fioc.h b/src/datenklo/fioc.h new file mode 100644 index 0000000..ec1a39d --- /dev/null +++ b/src/datenklo/fioc.h @@ -0,0 +1,32 @@ +/* + FUSE-ioctl: ioctl support for FUSE + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#include +#include +#include + +enum { + FIOC_GET_SIZE = _IOR('E', 0, size_t), + FIOC_SET_SIZE = _IOW('E', 1, size_t), + + /* + * The following two ioctls don't follow usual encoding rules + * and transfer variable amount of data. + */ + FIOC_READ = _IO('E', 2), + FIOC_WRITE = _IO('E', 3), +}; + +struct fioc_rw_arg { + off_t offset; + void *buf; + size_t size; + size_t prev_size; /* out param for previous total size */ + size_t new_size; /* out param for new total size */ +}; diff --git a/src/datenklo/main.c b/src/datenklo/main.c new file mode 100644 index 0000000..1a8be00 --- /dev/null +++ b/src/datenklo/main.c @@ -0,0 +1,320 @@ +/* osmo datenklo main file + * + * (C) 2019 by Andreas Eversberg + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libtimer/timer.h" +#include "../liboptions/options.h" +#include "../libdebug/debug.h" +#include "../libfsk/fsk.h" +#include "../libwave/wave.h" +#include "../libdisplay/display.h" +#include "am791x.h" +#include "uart.h" +#include "datenklo.h" + +#define MAX_DEVICES 2 + +#define OPT_ARRAY(num_name, name, value) \ +{ \ + if (num_name == MAX_DEVICES) { \ + fprintf(stderr, "Too many devices defined!\n"); \ + exit(0); \ + } \ + name[num_name++] = value; \ +} + +/* dummy functions */ +int num_kanal = 1; /* only one channel used for debugging */ +void *get_sender_by_empfangsfrequenz() { return "void"; } + +static datenklo_t datenklo[MAX_DEVICES]; +static enum am791x_type am791x_type = AM791X_TYPE_7911; +static int num_mc = 0; +static uint8_t mc[MAX_DEVICES] = { 0 }; +static int auto_rts = 0; +static int num_tx_baudrate = 0, num_rx_baudrate = 0; +static int tx_baudrate[MAX_DEVICES] = { 0 }, rx_baudrate[MAX_DEVICES] = { 0 }; +static int num_ttydev = 0; +static const char *ttydev[MAX_DEVICES] = { "/dev/ttyDATENKLO0" }; +static const char *audiodev = "hw:0,0"; +static int samplerate = 48000; +static int latency = 50; +static int stereo = 0; +static int loopback = 0; +static int fast_math = 0; +const char *write_tx_wave = NULL; +const char *write_rx_wave = NULL; +const char *read_tx_wave = NULL; +const char *read_rx_wave = NULL; + +void print_help(const char *arg0) +{ + printf("Usage: %s [options] -M \n\n", arg0); + /* - - */ + printf(" -T --am791x-type 7910 | 7911\n"); + printf(" Give modem chip type. (Default = 791%d)\n", am791x_type); + printf(" -M --mc \n"); + printf(" Give mode setting of AM7910/AM71911, use 'list' to list all modes.\n"); + printf(" -A --auto-rts\n"); + printf(" Automatically rais and drop modem's RTS line for half duplex operation.\n"); + printf(" TX data will be queued while remote carrier is detected.\n"); + printf(" -B --baudrate \n"); + printf(" Given baud rate will override the baud rate given by ioctl.\n"); + printf(" A baud rate of <= 150 Bps for TX and/or RX will attach TX and/or RX to\n"); + printf(" the back channel instead of the main channel.\n"); + printf(" -D --device\n"); + printf(" Device name. The prefix '/dev/ is automatically added, if not given.\n"); + printf(" (default = '%s')\n", ttydev[0]); + printf(" -S --stereo\n"); + printf(" Generate two devices. One device connects to the left and the other to\n"); + printf(" the right channel of the audio device. The device number in the device\n"); + printf(" name is automatically increased by one. You must also define the mode\n"); + printf(" twice. (-M -M )\n"); + printf(" -a --audio-device hw:,\n"); + printf(" Sound card and device number (default = '%s')\n", audiodev); + printf(" -s --samplerate \n"); + printf(" Sample rate of sound device (default = '%d')\n", samplerate); + printf(" -b --buffer \n"); + printf(" How many milliseconds are processed in advance (default = '%d')\n", latency); + printf(" -l --loopback \n"); + printf(" Perform audio loopback to test modem.\n"); + printf(" type 1: Audio from transmitter is fed into receiver (analog loopback)\n"); + printf(" type 2: Audio is crossed between two modem instances. (use with -S)\n"); + printf(" --fast-math\n"); + printf(" Use fast math approximation for slow CPU / ARM based systems.\n"); + printf(" --write-rx-wave \n"); + printf(" Write received audio to given wave file.\n"); + printf(" --write-tx-wave \n"); + printf(" Write transmitted audio to given wave file.\n"); + printf(" --read-rx-wave \n"); + printf(" Replace received audio by given wave file.\n"); + printf(" --read-tx-wave \n"); + printf(" Replace transmitted audio by given wave file.\n"); +} + +#define OPT_WRITE_RX_WAVE 1001 +#define OPT_WRITE_TX_WAVE 1002 +#define OPT_READ_RX_WAVE 1003 +#define OPT_READ_TX_WAVE 1004 +#define OPT_MNCC_NAME 1006 +#define OPT_FAST_MATH 1007 + +static void add_options(void) +{ + option_add('h', "help", 0); + option_add('v', "debug", 1); + option_add('T', "am791x_type", 1); + option_add('M', "mc", 1); + option_add('A', "auto-rts", 0); + option_add('B', "baudrate", 2); + option_add('D', "device", 1); + option_add('S', "stereo", 0); + option_add('a', "audio-device", 1); + option_add('s', "samplerate", 1); + option_add('b', "buffer", 1); + option_add('l', "loopback", 1); + option_add(OPT_WRITE_RX_WAVE, "write-rx-wave", 1); + option_add(OPT_WRITE_TX_WAVE, "write-tx-wave", 1); + option_add(OPT_READ_RX_WAVE, "read-rx-wave", 1); + option_add(OPT_READ_TX_WAVE, "read-tx-wave", 1); + option_add(OPT_FAST_MATH, "fast-math", 0); +} + +static int handle_options(int short_option, int argi, char **argv) +{ + int rc; + + switch (short_option) { + case 'h': + print_help(argv[0]); + return 0; + case 'v': + if (!strcasecmp(argv[argi], "list")) { + debug_list_cat(); + return 0; + } + rc = parse_debug_opt(argv[argi]); + if (rc < 0) { + fprintf(stderr, "Failed to parse debug option, please use -h for help.\n"); + return rc; + } + break; + case 'T': + am791x_type = atoi(argv[argi]); + if (am791x_type < 0 || am791x_type > 1) { + fprintf(stderr, "Given type parameter '%s' is invalid, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + break; + case 'M': + if (!strcasecmp(argv[argi], "list")) { + am791x_list_mc(am791x_type); + return 0; + } else + if (argv[argi][0] >= '0' && argv[argi][0] <= '9') { + OPT_ARRAY(num_mc, mc, atoi(argv[argi])) + if (mc[num_mc - 1] > 31) + goto mc_inval; + } else + { + mc_inval: + fprintf(stderr, "Given mode parameter '%s' is invalid, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + break; + case 'A': + auto_rts = 1; + break; + case 'B': + OPT_ARRAY(num_rx_baudrate, rx_baudrate, atoi(argv[argi])) + OPT_ARRAY(num_tx_baudrate, tx_baudrate, atoi(argv[argi + 1])) + break; + case 'D': + OPT_ARRAY(num_ttydev, ttydev, strdup(argv[argi])) + break; + case 'S': + stereo = 1; + break; + case 'a': + audiodev = strdup(argv[argi]); + break; + case 's': + samplerate = atoi(argv[argi]); + break; + case 'b': + latency = atoi(argv[argi]); + break; + case 'l': + loopback = atoi(argv[argi]); + break; + case OPT_FAST_MATH: + fast_math = 1; + break; + case OPT_WRITE_RX_WAVE: + write_rx_wave = strdup(argv[argi]); + break; + case OPT_WRITE_TX_WAVE: + write_tx_wave = strdup(argv[argi]); + break; + case OPT_READ_RX_WAVE: + read_rx_wave = strdup(argv[argi]); + break; + case OPT_READ_TX_WAVE: + read_tx_wave = strdup(argv[argi]); + break; + } + + return 1; +} + +const char *inc_dev_name(const char *dev_name) +{ + char *new_name, *number; + int integer; + + /* clone */ + new_name = malloc(256); + strcpy(new_name, dev_name); + + /* find number and remove, if any */ + number = new_name; + while(*number < '0' || *number > '9') + number++; + if (!(*number)) + integer = 2; + else + integer = atoi(number) + 1; + + /* change number */ + sprintf(number, "%d", integer); + + return new_name; +} + +int main(int argc, char *argv[]) +{ + int rc, argi; + int i; + + /* handle options / config file */ + add_options(); + rc = options_config_file("~/.osmocom/analog/datenklo.conf", handle_options); + if (rc < 0) + return 0; + argi = options_command_line(argc, argv, handle_options); + if (argi <= 0) + return argi; + + /* inits */ + datenklo_init_global(); + fm_init(fast_math); + + if (stereo) { + num_kanal = 2; + } + if (num_mc == 0) { + fprintf(stderr, "You need to set the mode of the modem chip. See '--help'.\n"); + exit(0); + } + if (num_mc < num_kanal) { + fprintf(stderr, "You need to specify as many mode settings as you have channels.\n"); + exit(0); + } + + /* create modem instance */ + for (i = 0; i < num_kanal; i++) { + /* remove /dev/ */ + if (ttydev[i] && !strncmp(ttydev[i], "/dev/", 5)) + ttydev[i] += 5; + /* increment last name */ + if (i && ttydev[i] == NULL) + ttydev[i] = inc_dev_name(ttydev[i - 1]); + rc = datenklo_init(&datenklo[i], ttydev[i], am791x_type, mc[i], auto_rts, tx_baudrate[i], rx_baudrate[i], samplerate, loopback); + if (rc < 0) { + fprintf(stderr, "Failed to create \"Datenklo\" instance. Quitting!\n"); + goto fail; + } + if (i) + datenklo[i - 1].slave = &datenklo[i]; + printf("Datenklo on device '/dev/%s' ready. (using sound device '%s')\n", ttydev[i], audiodev); + } + + rc = datenklo_open_audio(&datenklo[0], audiodev, latency, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave); + if (rc < 0) { + fprintf(stderr, "Failed to initialize audio. Quitting!\n"); + goto fail; + } + + datenklo_main(&datenklo[0], loopback); + +fail: + for (i = 0; i < num_kanal; i++) + datenklo_exit(&datenklo[i]); + + return 0; +} + diff --git a/src/datenklo/uart.c b/src/datenklo/uart.c new file mode 100644 index 0000000..1521410 --- /dev/null +++ b/src/datenklo/uart.c @@ -0,0 +1,166 @@ +/* Software UART + * + * (C) 2019 by Andreas Eversberg + * 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 . + */ + +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "uart.h" + +static uint32_t calc_parity(uint32_t data, uint8_t data_bits, enum uart_parity parity) +{ + int i; + + for (i = 0; i < data_bits; i++) + parity |= (data >> i); + parity &= 1; + + switch (parity) { + case UART_PARITY_NONE: + case UART_PARITY_SPACE: + return 0; + case UART_PARITY_MARK: + return 1; + case UART_PARITY_EVEN: + return parity; + case UART_PARITY_ODD: + return parity ^ 1; + } + + return 0; /* never reached */ +} + +int uart_init(uart_t *uart, void *inst, uint8_t data_bits, enum uart_parity parity, uint8_t stop_bits, int (*tx_cb)(void *inst), void (*rx_cb)(void *inst, int data, uint32_t flags)) +{ + memset(uart, 0, sizeof(*uart)); + + uart->inst = inst; + uart->tx_cb = tx_cb; + uart->rx_cb = rx_cb; + uart->data_bits = data_bits; + if (uart->data_bits > 9) { + PDEBUG(DUART, DEBUG_ERROR, "Illegal number of data bits, please fix!\n"); + abort(); + } + uart->parity = parity; + uart->stop_bits = stop_bits; + if (uart->stop_bits < 1 || uart->stop_bits > 2) { + PDEBUG(DUART, DEBUG_ERROR, "Illegal number of stop bits, please fix!\n"); + abort(); + } + uart->tx_pos = -1; + uart->rx_pos = -1; + uart->length = uart->stop_bits + !!uart->parity + uart->data_bits; + + return 0; +} + +/* called by modulator to get next bit from uart */ +int uart_tx_bit(uart_t *uart) +{ + uint32_t bit, parity; + + if (uart->tx_pos < 0) { + /* no transmission, get data */ + uart->tx_data = uart->tx_cb(uart->inst); + /* return 1, if no data has not be sent */ + if (uart->tx_data > 0x7fffffff) + return 1; + /* all bits after data are stop bits */ + uart->tx_data |= 0xffffffff << uart->data_bits; + /* calculate parity */ + if (uart->parity) + parity = calc_parity(uart->tx_data, uart->data_bits, uart->parity); + /* add parity bit */ + if (uart->parity) { + /* erase bit for parity */ + uart->tx_data ^= 1 << uart->data_bits; + /* put parity bit */ + uart->tx_data |= parity << uart->data_bits; + } + /* start with the first bit */ + uart->tx_pos = 0; + /* return start bit */ + return 0; + } + /* get bit to be send */ + bit = (uart->tx_data >> uart->tx_pos) & 1; + /* go to next bit and set tx_pos to -1, if there is no more bit */ + if (++uart->tx_pos == uart->length) + uart->tx_pos = -1; + /* return bit */ + return bit; +} + +int uart_is_tx(uart_t *uart) +{ + if (uart->tx_pos >= 0) + return 1; + return 0; +} + +/* called by demodulator to indicate bit for uart */ +void uart_rx_bit(uart_t *uart, int bit) +{ + uint32_t flags = 0; + uint32_t parity; + + bit &= 1; + + /* if no data is receivd, check for start bit */ + if (uart->rx_pos < 0) { + /* if no start bit */ + if (bit != 0 || uart->last_bit != 1) + goto out; + /* start bit */ + uart->rx_data = 0; + uart->rx_pos = 0; + return; + } + /* shift bit */ + uart->rx_data |= bit << (uart->rx_pos); + /* end of transmission */ + if (++uart->rx_pos == uart->length) { + /* turn off reception */ + uart->rx_pos = -1; + /* check if parity is invalid */ + if (uart->parity) { + parity = calc_parity(uart->rx_data, uart->data_bits, uart->parity); + if (((uart->rx_data >> uart->data_bits) & 1) != parity) + flags |= UART_PARITY_ERROR; + } + /* check if last stop bit is invalid */ + if (((uart->rx_data >> (uart->length - 1)) & 1) == 0) { + flags |= UART_CODE_VIOLATION; + } + /* check if all bits are 0 */ + if (!uart->rx_data) { + flags |= UART_BREAK; + } + /* clear all bits after data */ + uart->rx_data &= ~(0xffffffff << uart->data_bits); + uart->rx_cb(uart->inst, uart->rx_data, flags); + } + +out: + /* remember last bit for start bit detection (1 -> 0 transition) */ + uart->last_bit = bit; +} + diff --git a/src/datenklo/uart.h b/src/datenklo/uart.h new file mode 100644 index 0000000..2a0104a --- /dev/null +++ b/src/datenklo/uart.h @@ -0,0 +1,34 @@ + +enum uart_parity { + UART_PARITY_NONE, + UART_PARITY_EVEN, + UART_PARITY_ODD, + UART_PARITY_MARK, + UART_PARITY_SPACE, +}; + +/* uart flags */ +#define UART_PARITY_ERROR (1 << 0) +#define UART_CODE_VIOLATION (1 << 1) +#define UART_BREAK (1 << 2) + +typedef struct uart { + void *inst; + int (*tx_cb)(void *inst); + void (*rx_cb)(void *inst, int data, uint32_t flags); + uint8_t data_bits; + enum uart_parity parity; + uint8_t stop_bits; + int last_bit; + uint32_t tx_data; + uint32_t rx_data; + int tx_pos; + int rx_pos; + int length; +} uart_t; + +int uart_init(uart_t *uart, void *inst, uint8_t data_bits, enum uart_parity parity, uint8_t stop_bits, int (*tx_cb)(void *inst), void (*rx_cb)(void *inst, int data, uint32_t flags)); +int uart_tx_bit(uart_t *uart); +int uart_is_tx(uart_t *uart); +void uart_rx_bit(uart_t *uart, int bit); + diff --git a/src/libdebug/debug.c b/src/libdebug/debug.c index f2235f0..554bfe0 100644 --- a/src/libdebug/debug.c +++ b/src/libdebug/debug.c @@ -65,6 +65,10 @@ struct debug_cat { { "soapy", "\033[1;35m" }, { "wave", "\033[1;33m" }, { "radio", "\033[1;34m" }, + { "am791x", "\033[0;31m" }, + { "uart", "\033[0;32m" }, + { "device", "\033[0;33m" }, + { "datenklo", "\033[1;34m" }, { NULL, NULL } }; @@ -124,7 +128,6 @@ void _printdebug(const char *file, const char __attribute__((unused)) *function, while ((p = strchr(file, '/'))) file = p + 1; - if (clear_console_text) clear_console_text(); if (debug_limit_scroll) { diff --git a/src/libdebug/debug.h b/src/libdebug/debug.h index c399c4b..6f9dc13 100644 --- a/src/libdebug/debug.h +++ b/src/libdebug/debug.h @@ -28,6 +28,10 @@ #define DSOAPY 21 #define DWAVE 22 #define DRADIO 23 +#define DAM791X 24 +#define DUART 25 +#define DDEVICE 26 +#define DDATENKLO 27 void get_win_size(int *w, int *h); -- cgit v1.2.3