aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndreas Eversberg <jolly@eversberg.eu>2019-09-01 13:15:04 +0200
committerAndreas Eversberg <jolly@eversberg.eu>2019-11-29 15:58:59 +0100
commit7e25e191af05f467dad7aae4ac643727636c35ba (patch)
tree4fb3e2acd9a40349bf3e4dd33b97bb00234a63e9 /src
parentdb1fee9698f4859eb2f2ec4c71321b305a267928 (diff)
Modem emulator for "Datenklo" with AM7910/AM7911 emulation
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am11
-rw-r--r--src/datenklo/Makefile.am35
-rw-r--r--src/datenklo/am791x.c1266
-rw-r--r--src/datenklo/am791x.h107
-rw-r--r--src/datenklo/datenklo.c1668
-rw-r--r--src/datenklo/datenklo.h88
-rw-r--r--src/datenklo/device.c559
-rw-r--r--src/datenklo/device.h7
-rw-r--r--src/datenklo/fioc.h32
-rw-r--r--src/datenklo/main.c320
-rw-r--r--src/datenklo/uart.c166
-rw-r--r--src/datenklo/uart.h34
-rw-r--r--src/libdebug/debug.c5
-rw-r--r--src/libdebug/debug.h4
14 files changed, 4300 insertions, 2 deletions
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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <math.h>
+#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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <asm-generic/termbits.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <poll.h>
+#include <pthread.h>
+#include <ctype.h>
+#include <fcntl.h>
+#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 <bits/termios.h>. */
+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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define FUSE_USE_VERSION 30
+
+#include <cuse_lowlevel.h>
+#include <fuse_opt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "../libdebug/debug.h"
+#define __USE_GNU
+#include <pthread.h>
+#include <signal.h>
+
+#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 <teheo@suse.de>
+
+ This program can be distributed under the terms of the GNU GPL.
+ See the file COPYING.
+*/
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+
+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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <errno.h>
+#include <math.h>
+#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 <mode>\n\n", arg0);
+ /* - - */
+ printf(" -T --am791x-type 7910 | 7911\n");
+ printf(" Give modem chip type. (Default = 791%d)\n", am791x_type);
+ printf(" -M --mc <mode>\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 <RX rate> <TX rate>\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 <mode> -M <mode>)\n");
+ printf(" -a --audio-device hw:<card>,<device>\n");
+ printf(" Sound card and device number (default = '%s')\n", audiodev);
+ printf(" -s --samplerate <rate>\n");
+ printf(" Sample rate of sound device (default = '%d')\n", samplerate);
+ printf(" -b --buffer <ms>\n");
+ printf(" How many milliseconds are processed in advance (default = '%d')\n", latency);
+ printf(" -l --loopback <type>\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 <file>\n");
+ printf(" Write received audio to given wave file.\n");
+ printf(" --write-tx-wave <file>\n");
+ printf(" Write transmitted audio to given wave file.\n");
+ printf(" --read-rx-wave <file>\n");
+ printf(" Replace received audio by given wave file.\n");
+ printf(" --read-tx-wave <file>\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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#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);