changeset 42:ff94d7fc5891

new program itt-ater-8
author Mychaela Falconia <falcon@freecalypso.org>
date Fri, 30 Aug 2024 19:02:42 +0000
parents 50a72d4ff498
children 55f02d4aee79
files .hgignore ater8/Makefile ater8/activate.c ater8/globals.h ater8/main.c ater8/out_frame.c ater8/out_frame.h ater8/play_cmd.c ater8/read_file.c ater8/read_file.h ater8/read_ts.c ater8/record_ctrl.c ater8/submux.h ater8/subslot_rx.c ater8/tx_func.c ater8/user_cmd.c
diffstat 16 files changed, 816 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri Aug 30 16:42:50 2024 +0000
+++ b/.hgignore	Fri Aug 30 19:02:42 2024 +0000
@@ -4,5 +4,6 @@
 ^config\.defs$
 
 ^ater/itt-ater-16$
+^ater8/itt-ater-8$
 ^pcm/itt-pcm-one$
 ^pcm-br/itt-pcm-br$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/Makefile	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,26 @@
+PROG=	itt-ater-8
+OBJS=	activate.o main.o out_frame.o play_cmd.o read_file.o read_ts.o \
+	record_ctrl.o subslot_rx.o tx_func.o user_cmd.o
+HDRS=	globals.h out_frame.h read_file.h submux.h
+LIBUTIL=../libutil/libutil.a
+LIBHR=	../libhr/libhr.a
+
+include ../config.defs
+
+CPPFLAGS=${OSMO_INCLUDE}
+OSMO_LINK=${OSMO_LPATH} ${OSMO_RPATH} ${OSMO_LIBS}
+LOCAL_LIBS=${LIBUTIL} ${LIBHR}
+
+all:	${PROG}
+
+${OBJS}:	${HDRS}
+
+${PROG}: ${OBJS} ${LOCAL_LIBS}
+	${CC} -o $@ ${OBJS} ${LOCAL_LIBS} ${OSMO_LINK}
+
+install:
+	mkdir -p ${DESTDIR}${bindir}
+	install -c -m 755 ${PROG} ${DESTDIR}${bindir}
+
+clean:
+	rm -f *.o ${PROG} errs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/activate.c	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,89 @@
+/*
+ * Here we implement the operation of activating a new TRAU channel
+ * on a sub-timeslot.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/isdn/i460_mux.h>
+#include <osmocom/trau/trau_frame.h>
+
+#include "globals.h"
+#include "submux.h"
+#include "read_file.h"
+#include "out_frame.h"
+
+void cmd_activate(int argc, char **argv)
+{
+	int nr, rc;
+	bool dtxd;
+	struct ater_subslot *at;
+	int16_t *init_frame;
+	unsigned init_frame_count;
+
+	if (argc < 3 || argc > 4) {
+usage:		fprintf(stderr, "usage: %s 0-7 initial-frame.dec [dtxd]\n",
+			argv[0]);
+		return;
+	}
+	if (argv[1][0] < '0' || argv[1][0] > '7' || argv[1][1])
+		goto usage;
+	nr = argv[1][0] - '0';
+	if (argv[3]) {
+		if (strcmp(argv[3], "dtxd"))
+			goto usage;
+		dtxd = true;
+	} else
+		dtxd = false;
+
+	at = &subslots[nr];
+	if (at->is_active) {
+		fprintf(stderr, "error: subslot %d is already active\n", nr);
+		return;
+	}
+	rc = read_binary_file(argv[2], &init_frame, &init_frame_count);
+	if (rc < 0)
+		return;		/* error msg already printed */
+	if (init_frame_count != 1) {
+		free(init_frame);
+		fprintf(stderr, "error: %s contains more than one frame\n",
+			argv[2]);
+		return;
+	}
+
+	/* good to proceed now */
+	at->is_active = true;
+	init_trau_ul_frame(nr);
+	at->ul_frame.c_bits[8] = dtxd;
+	trau_frame_from_record(init_frame, &at->ul_frame, &at->frame_has_taf);
+	free(init_frame);
+}
+
+void cmd_deact(int argc, char **argv)
+{
+	int nr;
+	struct ater_subslot *at;
+
+	if (argc != 2) {
+usage:		fprintf(stderr, "usage: %s 0-7\n", argv[0]);
+		return;
+	}
+	if (argv[1][0] < '0' || argv[1][0] > '7' || argv[1][1])
+		goto usage;
+	nr = argv[1][0] - '0';
+	at = &subslots[nr];
+	if (!at->is_active) {
+		fprintf(stderr, "error: subslot %d is not active\n", nr);
+		return;
+	}
+	at->is_active = false;
+	if (at->play_buffer) {
+		free(at->play_buffer);
+		at->play_buffer = NULL;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/globals.h	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,22 @@
+/* global vars and intermodule-linkage functions in itt-ater-8 */
+
+#pragma once
+
+extern void *g_ctx;
+extern struct osmo_e1dp_client *g_client;
+extern int ts_fd;
+extern struct osmo_i460_timeslot i460_ts;
+extern uint8_t readbuf[160];
+extern FILE *record_file;
+
+int ts_fd_cb(struct osmo_fd *ofd, unsigned int what);
+void transmit_e1_ts(void);
+
+void handle_user_cmd(int argc, char **argv);
+void cmd_record_start(int argc, char **argv);
+void cmd_record_stop(int argc, char **argv);
+void cmd_print_rx(int argc, char **argv);
+void cmd_activate(int argc, char **argv);
+void cmd_deact(int argc, char **argv);
+void cmd_play_file(int argc, char **argv);
+void cmd_play_stop(int argc, char **argv);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/main.c	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,110 @@
+/*
+ * This C module is the main for itt-ater-8, a program in the icE1 TRAU tester
+ * suite that operates on a single E1 timeslot on the Ater interface.
+ *
+ * This code is based on osmo-e1d-pipe,
+ * (C) 2020-2022 by Harald Welte <laforge@osmocom.org>,
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/application.h>
+#include <osmocom/e1d/proto_clnt.h>
+#include <osmocom/isdn/i460_mux.h>
+
+#include "../libutil/open_ts.h"
+#include "../libutil/stdin_handler.h"
+#include "globals.h"
+#include "submux.h"
+
+void *g_ctx;
+struct osmo_e1dp_client *g_client;
+int ts_fd;
+struct osmo_i460_timeslot i460_ts;
+struct ater_subslot subslots[ATER_SUBSLOTS];
+
+static const char *e1d_socket_path = E1DP_DEFAULT_SOCKET;
+static const char *timeslot_spec;
+static struct osmo_fd ts_ofd, stdin_ofd;
+
+static void process_cmdline(int argc, char **argv)
+{
+	extern int optind;
+	extern char *optarg;
+	int c;
+
+	while ((c = getopt(argc, argv, "p:")) != EOF) {
+		switch (c) {
+		case 'p':
+			e1d_socket_path = optarg;
+			continue;
+		default:
+		usage:
+			fprintf(stderr, "usage: %s [-p socket] intf:line:ts\n",
+				argv[0]);
+			exit(1);
+		}
+	}
+	if (argc != optind + 1)
+		goto usage;
+	timeslot_spec = argv[optind];
+}
+
+static void register_subslots(void)
+{
+	int nr;
+	struct osmo_i460_schan_desc chd;
+
+	memset(&chd, 0, sizeof chd);
+	chd.rate = OSMO_I460_RATE_8k;
+	chd.demux.num_bits = 160;
+	chd.demux.out_cb_bits = i460_rx_func;
+
+	for (nr = 0; nr < ATER_SUBSLOTS; nr++) {
+		subslots[nr].nr = nr;
+		chd.demux.user_data = subslots + nr;
+		chd.mux.user_data = subslots + nr;
+		subslots[nr].schan =
+				osmo_i460_subchan_add(g_ctx, &i460_ts, &chd);
+		OSMO_ASSERT(subslots[nr].schan);
+		chd.bit_offset += 1;
+	}
+}
+
+int main(int argc, char **argv)
+{
+	process_cmdline(argc, argv);
+	g_ctx = talloc_named_const(NULL, 0, "g_ctx");
+	OSMO_ASSERT(g_ctx);
+	osmo_init_logging2(g_ctx, NULL);
+
+	g_client = osmo_e1dp_client_create(g_ctx, e1d_socket_path);
+	if (!g_client) {
+		fprintf(stderr, "error: cannot connect to osmo-e1d at %s\n",
+			e1d_socket_path);
+		exit(1);
+	}
+	ts_fd = open_e1d_ts(g_client, timeslot_spec);
+
+	osmo_i460_ts_init(&i460_ts);
+	register_subslots();
+
+	osmo_fd_setup(&ts_ofd, ts_fd, OSMO_FD_READ, ts_fd_cb, NULL, 0);
+	OSMO_ASSERT(osmo_fd_register(&ts_ofd) == 0);
+
+	osmo_fd_setup(&stdin_ofd, 0, OSMO_FD_READ, stdin_select_cb,
+			handle_user_cmd, 0);
+	OSMO_ASSERT(osmo_fd_register(&stdin_ofd) == 0);
+
+	while (1) {
+		osmo_select_main(0);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/out_frame.c	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,80 @@
+/*
+ * Here we implement our TRAU-UL bit filling function for HRv1 8 kbit/s format.
+ *
+ * This code is based (very loosely) on trau_rtp_conv.c in libosmo-abis.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/crc8gen.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/trau/trau_frame.h>
+#include "../libhr/tw_gsmhr.h"
+
+#include "out_frame.h"
+
+/*
+ * EFR TRAU parity (also used for HR)
+ *
+ * g(x) = x^3 + x^1 + 1
+ */
+static const struct osmo_crc8gen_code gsm0860_efr_crc3 = {
+	.bits = 3,
+	.poly = 0x3,
+	.init = 0x0,
+	.remainder = 0x7,
+};
+
+void trau_frame_from_record(const int16_t *rec, struct osmo_trau_frame *tf,
+			    bool *has_taf)
+{
+	uint8_t ts101318[GSMHR_FRAME_LEN_RPF];
+	int16_t bfi, sid;
+
+	/* payload transformation is straightforward */
+	gsmhr_pack_ts101318(rec, ts101318);
+	osmo_pbit2ubit(tf->d_bits, ts101318, 14 * 8);
+	osmo_crc8gen_set_bits(&gsm0860_efr_crc3, tf->d_bits, 44, tf->crc_bits);
+
+	/* transformation of metadata flags is the messy part */
+	bfi = rec[18];
+	sid = rec[20];
+	switch (sid) {
+	case 0:
+		if (bfi) {
+			tf->xc_bits[1] = 1;
+			tf->xc_bits[2] = 1;
+			*has_taf = true;
+		} else {
+			tf->xc_bits[1] = 0;
+			tf->xc_bits[2] = 0;
+			tf->xc_bits[3] = 0;
+			*has_taf = false;
+		}
+		break;
+	case 1:
+		tf->xc_bits[1] = 1;
+		tf->xc_bits[2] = 0;
+		*has_taf = true;
+		break;
+	case 2:
+		if (bfi) {
+			tf->xc_bits[1] = 1;
+			tf->xc_bits[2] = 0;
+			*has_taf = true;
+		} else {
+			tf->xc_bits[1] = 0;
+			tf->xc_bits[2] = 0;
+			tf->xc_bits[3] = 1;
+			*has_taf = false;
+		}
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+	/* XC5 is always UFI */
+	tf->xc_bits[4] = rec[19];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/out_frame.h	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,15 @@
+/*
+ * This header file defines the interface to the function that
+ * fills struct osmo_trau_frame based on a 22-word decoder input
+ * record we read from an ETSI *.dec file.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/trau/trau_frame.h>
+
+void trau_frame_from_record(const int16_t *rec, struct osmo_trau_frame *fr,
+			    bool *has_taf);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/play_cmd.c	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,68 @@
+/*
+ * Here we implement user commands controlling file play functionality.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocom/core/select.h>
+
+#include "globals.h"
+#include "submux.h"
+#include "read_file.h"
+
+void cmd_play_file(int argc, char **argv)
+{
+	int nr, rc;
+	struct ater_subslot *at;
+
+	if (argc != 3) {
+usage:		fprintf(stderr, "usage: %s 0-7 play-file.dec\n", argv[0]);
+		return;
+	}
+	if (argv[1][0] < '0' || argv[1][0] > '7' || argv[1][1])
+		goto usage;
+	nr = argv[1][0] - '0';
+	at = &subslots[nr];
+	if (!at->is_active) {
+		fprintf(stderr, "error: subslot %d is not active\n", nr);
+		return;
+	}
+	if (at->play_buffer) {
+		fprintf(stderr, "error: file play already in progress\n");
+		return;
+	}
+	rc = read_binary_file(argv[2], &at->play_buffer, &at->play_buf_total);
+	if (rc < 0)
+		return;		/* error msg already printed */
+	at->play_buf_ptr = 0;
+	at->play_wait_align = true;
+}
+
+void cmd_play_stop(int argc, char **argv)
+{
+	int nr;
+	struct ater_subslot *at;
+
+	if (argc != 2) {
+usage:		fprintf(stderr, "usage: %s 0-7\n", argv[0]);
+		return;
+	}
+	if (argv[1][0] < '0' || argv[1][0] > '7' || argv[1][1])
+		goto usage;
+	nr = argv[1][0] - '0';
+	at = &subslots[nr];
+	if (!at->is_active) {
+		fprintf(stderr, "error: subslot %d is not active\n", nr);
+		return;
+	}
+	if (!at->play_buffer) {
+		fprintf(stderr, "error: no file play in progress\n");
+		return;
+	}
+	free(at->play_buffer);
+	at->play_buffer = NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/read_file.c	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,95 @@
+/*
+ * Here we implement the function that reads ETSI *.dec binary files
+ * which we've adopted as our TRAU-UL test input format for GSM-HR.
+ */
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "../libhr/tw_gsmhr.h"
+
+#include "read_file.h"
+
+static int validate_frames(const int16_t *buf, unsigned nframes)
+{
+	unsigned n;
+	int rc;
+	int16_t expect_taf;
+
+	for (n = 0; n < nframes; n++) {
+		rc = gsmhr_check_decoder_params(buf);
+		if (rc < 0)
+			return rc;
+		/* disallow BFI=2 non-ETSI extension */
+		if (buf[18] > 1)
+			return -1;
+		/* enforce TAF matching position */
+		if (n % 12 == 11)
+			expect_taf = 1;
+		else
+			expect_taf = 0;
+		if (buf[21] != expect_taf)
+			return -1;
+		buf += GSMHR_NUM_PARAMS_DEC;
+	}
+	return 0;
+}
+
+int read_binary_file(const char *filename, int16_t **bufret, unsigned *size_ret)
+{
+	int fd, rc;
+	struct stat st;
+	int16_t *buf;
+	unsigned nframes;
+
+	fd = open(filename, O_RDONLY);
+	if (fd < 0) {
+		perror(filename);
+		return -1;
+	}
+	fstat(fd, &st);
+	if (!S_ISREG(st.st_mode)) {
+		close(fd);
+		fprintf(stderr, "error: %s is not a regular file\n", filename);
+		return -1;
+	}
+	if (!st.st_size) {
+		close(fd);
+		fprintf(stderr, "error: %s is an empty file\n", filename);
+		return -1;
+	}
+	if (st.st_size % 44) {
+		close(fd);
+		fprintf(stderr,
+			"error: size of %s is not a multiple of 44 bytes\n",
+			filename);
+		return -1;
+	}
+	buf = malloc(st.st_size);
+	if (!buf) {
+		close(fd);
+		fprintf(stderr, "unable to malloc buffer for %s\n", filename);
+		return -1;
+	}
+	read(fd, buf, st.st_size);
+	close(fd);
+	nframes = st.st_size / 44;
+
+	rc = validate_frames(buf, nframes);
+	if (rc < 0) {
+		free(buf);
+		fprintf(stderr,
+	"error: %s is not a valid GSM-HR dec file, or has wrong endian\n",
+			filename);
+		return -1;
+	}
+	*bufret = buf;
+	*size_ret = nframes;
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/read_file.h	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,12 @@
+/*
+ * This header file defines the interface to the function that reads
+ * ETSI *.dec binary files which we've adopted as our TRAU-UL test
+ * input format for GSM-HR.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+int read_binary_file(const char *filename, int16_t **bufret,
+		     unsigned *size_ret);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/read_ts.c	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,36 @@
+/*
+ * The function in this module gets called from Osmocom select loop
+ * whenever the data socket to osmo-e1d is ready for reading.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/isdn/i460_mux.h>
+
+#include "globals.h"
+
+uint8_t readbuf[160];
+FILE *record_file;
+
+int ts_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	int rc;
+
+	rc = read(ts_fd, readbuf, 160);
+	if (rc != 160) {
+		fprintf(stderr,
+			"error: read from ts returned %d instead of 160\n",
+			rc);
+		exit(1);
+	}
+	if (record_file)
+		fwrite(readbuf, 1, 160, record_file);
+	osmo_i460_demux_in(&i460_ts, readbuf, 160);
+	transmit_e1_ts();
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/record_ctrl.c	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,50 @@
+/*
+ * Here we implement stdin commands that control recording of E1 timeslot
+ * read stream.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <osmocom/core/select.h>
+
+#include "globals.h"
+
+void cmd_record_start(int argc, char **argv)
+{
+	if (argc != 2) {
+		printf("error: record command needs 1 argument\n");
+		return;
+	}
+	if (record_file) {
+		printf("error: recording already in progress\n");
+		return;
+	}
+	record_file = fopen(argv[1], "w");
+	if (!record_file)
+		perror(argv[1]);
+}
+
+void cmd_record_stop(int argc, char **argv)
+{
+	if (!record_file) {
+		printf("error: no recording in progress\n");
+		return;
+	}
+	fclose(record_file);
+	record_file = NULL;
+}
+
+void cmd_print_rx(int argc, char **argv)
+{
+	int i, j, off;
+
+	off = 0;
+	for (i = 0; i < 10; i++) {
+		for (j = 0; j < 16; j++)
+			printf(" %02X", readbuf[off++]);
+		putchar('\n');
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/submux.h	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,36 @@
+/*
+ * The structures and functions defined in this header file deal with
+ * interfacing to the Submultiplexer part of Nokia's Transcoder and
+ * Submultiplexer - the 8 kbit/s version.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/isdn/i460_mux.h>
+#include <osmocom/trau/trau_frame.h>
+
+#define	ATER_SUBSLOTS	8
+
+struct ater_subslot {
+	struct osmo_i460_subchan *schan;
+	int nr;
+	bool is_active;
+	bool frame_has_taf;
+	struct osmo_trau_frame ul_frame;
+	unsigned mfrm_count;
+	int16_t *play_buffer;
+	unsigned play_buf_total;
+	unsigned play_buf_ptr;
+	bool play_wait_align;
+};
+
+extern struct ater_subslot subslots[ATER_SUBSLOTS];
+
+void i460_rx_func(struct osmo_i460_subchan *schan, void *user_data,
+		  const ubit_t *bits, unsigned int num_bits);
+
+void init_trau_ul_frame(int nr);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/subslot_rx.c	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,21 @@
+/*
+ * Here we are going to implement Ater subslot Rx.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/isdn/i460_mux.h>
+
+#include "globals.h"
+#include "submux.h"
+
+void i460_rx_func(struct osmo_i460_subchan *schan, void *user_data,
+		  const ubit_t *bits, unsigned int num_bits)
+{
+	/* to be filled */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/tx_func.c	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,112 @@
+/*
+ * Here we are going to implement Tx on Ater toward the TRAU.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/isdn/i460_mux.h>
+#include <osmocom/trau/trau_frame.h>
+
+#include "globals.h"
+#include "submux.h"
+#include "out_frame.h"
+
+void init_trau_ul_frame(int nr)
+{
+	struct ater_subslot *at = &subslots[nr];
+	struct osmo_trau_frame *fr = &at->ul_frame;
+
+	fr->type = OSMO_TRAU8_SPEECH;
+	fr->dir = OSMO_TRAU_DIR_UL;
+	memset(fr->c_bits + 5, 1, 3);
+	fr->xc_bits[0] = 0;
+	memset(fr->t_bits, 1, 2);
+}
+
+static void handle_play(struct ater_subslot *at)
+{
+	if (at->play_wait_align) {
+		if (at->mfrm_count)
+			return;
+		at->play_wait_align = false;
+	}
+	trau_frame_from_record(at->play_buffer + at->play_buf_ptr * 22,
+				&at->ul_frame, &at->frame_has_taf);
+	at->play_buf_ptr++;
+	if (at->play_buf_ptr < at->play_buf_total)
+		return;
+	free(at->play_buffer);
+	at->play_buffer = NULL;
+	printf("file play finished\n");
+}
+
+/* compute the odd parity bit of the given input bit sequence */
+static ubit_t compute_odd_parity(const ubit_t *in, unsigned int num_bits)
+{
+	int i;
+	unsigned int sum = 0;
+
+	for (i = 0; i < num_bits; i++) {
+		if (in[i])
+			sum++;
+	}
+
+	if (sum & 1)
+		return 0;
+	else
+		return 1;
+}
+
+static void tx_service_subslot(int nr)
+{
+	struct ater_subslot *at = &subslots[nr];
+	struct osmo_trau_frame *fr = &at->ul_frame;
+	ubit_t taf;
+	struct msgb *msg;
+	int len;
+
+	if (!at->is_active)
+		return;
+	if (at->play_buffer)
+		handle_play(at);
+	at->mfrm_count++;
+	if (at->mfrm_count >= 12) {
+		at->mfrm_count = 0;
+		taf = 1;
+	} else {
+		taf = 0;
+	}
+	if (at->frame_has_taf)
+		fr->xc_bits[3] = taf;
+	fr->xc_bits[5] = compute_odd_parity(fr->xc_bits, 5);
+
+	msg = msgb_alloc_c(g_ctx, 320, "TRAU-UL-frame");
+	if (!msg)
+		return;
+	len = osmo_trau_frame_encode(msg->tail, msgb_tailroom(msg), fr);
+	if (len <= 0) {
+		msgb_free(msg);
+		return;
+	}
+	msgb_put(msg, len);
+	osmo_i460_mux_enqueue(at->schan, msg);
+}
+
+void transmit_e1_ts(void)
+{
+	uint8_t buf[160];
+	int nr;
+
+	for (nr = 0; nr < ATER_SUBSLOTS; nr++)
+		tx_service_subslot(nr);
+	osmo_i460_mux_out(&i460_ts, buf, 160);
+	write(ts_fd, buf, 160);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ater8/user_cmd.c	Fri Aug 30 19:02:42 2024 +0000
@@ -0,0 +1,43 @@
+/*
+ * In this module we handle user-issued stdin commands during
+ * itt-ater-8 running session.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocom/core/select.h>
+
+#include "globals.h"
+
+static struct cmdtab {
+	char	*cmd;
+	void	(*func)(int argc, char **argv);
+} cmdtab[] = {
+	{"activ", cmd_activate},
+	{"deact", cmd_deact},
+	{"play", cmd_play_file},
+	{"play-stop", cmd_play_stop},
+	{"print-rx", cmd_print_rx},
+	{"record", cmd_record_start},
+	{"record-stop", cmd_record_stop},
+	/* table search terminator */
+	{NULL, NULL}
+};
+
+void handle_user_cmd(int argc, char **argv)
+{
+	struct cmdtab *tp;
+
+	for (tp = cmdtab; tp->cmd; tp++)
+		if (!strcmp(tp->cmd, argv[0]))
+			break;
+	if (!tp->func) {
+		printf("error: unknown or unimplemented command\n");
+		return;
+	}
+	tp->func(argc, argv);
+}