soft_uart: add unit tests for the receiver and transmitter

Change-Id: Icdfa0c644548964d37940c32dc9dcfcfc53c3a19
Related: OS#4396
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 5f4914e..ce9726c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -55,6 +55,7 @@
 		 v110/ra1_test                                          \
 		 gsm44021/frame_csd_test                                \
 		 osmo_io/osmo_io_test					\
+		 soft_uart/soft_uart_test				\
 		 $(NULL)
 
 if ENABLE_MSGFILE
@@ -375,6 +376,8 @@
 
 osmo_io_osmo_io_test_SOURCES = osmo_io/osmo_io_test.c
 
+soft_uart_soft_uart_test_SOURCES = soft_uart/soft_uart_test.c
+
 
 # The `:;' works around a Bash 3.2 bug when the output is not writeable.
 $(srcdir)/package.m4: $(top_srcdir)/configure.ac
@@ -479,6 +482,7 @@
 	     v110/ra1_test.ok \
 	     gsm44021/frame_csd_test.ok \
 	     osmo_io/osmo_io_test.ok osmo_io/osmo_io_test.err \
+	     soft_uart/soft_uart_test.ok \
 	     $(NULL)
 
 if ENABLE_LIBSCTP
@@ -693,6 +697,8 @@
 	osmo_io/osmo_io_test \
 		>$(srcdir)/osmo_io/osmo_io_test.ok \
 		2>$(srcdir)/osmo_io/osmo_io_test.err
+	soft_uart/soft_uart_test \
+		>$(srcdir)/soft_uart/soft_uart.ok
 
 
 check-local: atconfig $(TESTSUITE)
diff --git a/tests/soft_uart/soft_uart_test.c b/tests/soft_uart/soft_uart_test.c
new file mode 100644
index 0000000..f5597f3
--- /dev/null
+++ b/tests/soft_uart/soft_uart_test.c
@@ -0,0 +1,302 @@
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/soft_uart.h>
+
+static struct {
+	size_t data_len;
+	const uint8_t *data;
+} g_tx_cb_cfg;
+
+static void suart_rx_cb(void *priv, struct msgb *msg, unsigned int flags)
+{
+	fprintf(stdout, "%s(flags=%02x): %s\n",
+		__func__, flags, msgb_hexdump(msg));
+	msgb_free(msg);
+}
+
+static void suart_tx_cb(void *priv, struct msgb *msg)
+{
+	size_t n_bytes;
+
+	n_bytes = OSMO_MIN(g_tx_cb_cfg.data_len, msg->data_len);
+	if (g_tx_cb_cfg.data != NULL && n_bytes > 0)
+		memcpy(msgb_put(msg, n_bytes), g_tx_cb_cfg.data, n_bytes);
+
+	fprintf(stdout, "%s(len=%u/%u): %s\n",
+		__func__, msg->len, msg->data_len, msgb_hexdump(msg));
+}
+
+static const struct osmo_soft_uart_cfg suart_test_default_cfg = {
+	.num_data_bits = 8,
+	.num_stop_bits = 1,
+	.parity_mode = OSMO_SUART_PARITY_NONE,
+	.rx_buf_size = 128,
+	.rx_cb = &suart_rx_cb,
+	.tx_cb = &suart_tx_cb,
+};
+
+static void test_rx_exec(struct osmo_soft_uart *suart,
+			 const char *input)
+{
+	for (unsigned int i = 0; input[i] != '\0'; i++) {
+		ubit_t ubit;
+
+		switch (input[i]) {
+		case '0':
+		case '1':
+			ubit = input[i] - '0';
+			osmo_soft_uart_rx_ubits(suart, &ubit, 1);
+			break;
+		case 'F':
+			printf("%s() @ %u: flush the Rx buffer\n", __func__, i);
+			osmo_soft_uart_flush_rx(suart);
+			break;
+		case ' ': /* padding */
+			continue;
+		default:
+			printf("%s() @ %u: unknown opcode '%c'\n",
+			       __func__, i, input[i]);
+			break;
+		}
+	}
+}
+
+static void test_rx(void)
+{
+	struct osmo_soft_uart_cfg cfg;
+	struct osmo_soft_uart *suart;
+
+	suart = osmo_soft_uart_alloc(NULL, __func__, &suart_test_default_cfg);
+	OSMO_ASSERT(suart != NULL);
+
+	osmo_soft_uart_set_rx(suart, true);
+
+	printf("======== %s(): testing 8-N-1 (no data)\n", __func__);
+	test_rx_exec(suart, "F11111F11111F");
+
+	printf("======== %s(): testing 8-N-1 (fill up flush)\n", __func__);
+	cfg = suart_test_default_cfg;
+	cfg.rx_buf_size = 4;
+	osmo_soft_uart_configure(suart, &cfg);
+	test_rx_exec(suart, "11111" /* no data */
+		     "0 01111011 1"
+		     "0 10110101 1"
+		     "0 01111101 1"
+		     "0 11110111 1" /* filled up, expect flush */
+		     "0 00000000 1"
+		     "0 01010101 1"
+		     "0 10101010 1"
+		     "0 11111111 1" /* filled up, expect flush */
+		     "F" /* flush! (for sanity) */
+		     );
+
+	printf("======== %s(): testing 8-N-1 (HELLO)\n", __func__);
+	cfg = suart_test_default_cfg;
+	cfg.num_stop_bits = 1;
+	osmo_soft_uart_configure(suart, &cfg);
+	test_rx_exec(suart, "111111" /* no data */
+		     "0 00010010 1F" /* 'H', flush! */
+		     "0 10100010 1F" /* 'E', flush! */
+		     "1111111111111" /* no data */
+		     "0 00110010 1F" /* 'L', flush! */
+		     "0 00110010 1F" /* 'L', flush! */
+		     "1111111111111" /* no data */
+		     "0 11110010 1F" /* 'O', flush! */
+		     );
+
+	printf("======== %s(): testing 8-N-1 (framing errors)\n", __func__);
+	test_rx_exec(suart, "11111" /* no data */
+		     "0 00000000 0" /* stop bit != 1, expect flush */
+		     "0 01010101 0" /* stop bit != 1, expect flush */
+		     "0 11111111 1" /* stop bit == 1, recovery */
+		     "F" /* flush! */
+		     );
+
+	printf("======== %s(): testing 8-N-2 (HELLO)\n", __func__);
+	cfg = suart_test_default_cfg;
+	cfg.num_stop_bits = 2;
+	osmo_soft_uart_configure(suart, &cfg);
+	test_rx_exec(suart, "11111111" /* no data */
+		     "0 00010010 1F1F" /* 'H', flush! */
+		     "0 10100010 1F1F" /* 'E', flush! */
+		     "111111111111111" /* no data */
+		     "0 00110010 1F1F" /* 'L', flush! */
+		     "0 00110010 1F1F" /* 'L', flush! */
+		     "111111111111111" /* no data */
+		     "0 11110010 1F1F" /* 'O', flush! */
+		     );
+
+	printf("======== %s(): testing 8-N-2 (framing errors)\n", __func__);
+	test_rx_exec(suart, "11111" /* no data */
+		     "0 00000000 00" /* stop bit != 1, expect flush */
+		     "0 01010101 01" /* stop bit != 1, expect flush */
+		     "0 10101010 10" /* stop bit != 1, expect flush */
+		     "0 11111111 11" /* stop bit == 1, recovery */
+		     "F" /* flush! (for sanity) */
+		     );
+
+
+	printf("======== %s(): testing 8-E-1 (invalid parity)\n", __func__);
+	cfg = suart_test_default_cfg;
+	cfg.parity_mode = OSMO_SUART_PARITY_EVEN;
+	osmo_soft_uart_configure(suart, &cfg);
+	test_rx_exec(suart, "1111111" /* no data */
+		     "0 00000000 1 1" /* odd parity, expect flush */
+		     "0 10000000 0 1" /* odd parity, expect flush */
+		     "0 11111111 1 1" /* odd parity, expect flush */
+		     "F" /* flush! (for sanity) */
+		     );
+	printf("======== %s(): testing 8-E-1 (valid parity)\n", __func__);
+	test_rx_exec(suart, "1111111" /* no data */
+		     "0 00000000 0 1"
+		     "0 11111111 0 1"
+		     "0 01010101 0 1"
+		     "0 10101010 0 1"
+		     "F" /* flush! */
+		     "0 00000001 1 1"
+		     "0 00000111 1 1"
+		     "0 00011111 1 1"
+		     "0 01111111 1 1"
+		     "F" /* flush! */
+		     );
+
+	printf("======== %s(): testing 8-O-1 (invalid parity)\n", __func__);
+	cfg = suart_test_default_cfg;
+	cfg.parity_mode = OSMO_SUART_PARITY_ODD;
+	osmo_soft_uart_configure(suart, &cfg);
+	test_rx_exec(suart,
+		     "0 00000000 0 1" /* even parity, expect flush */
+		     "0 10000000 1 1" /* even parity, expect flush */
+		     "0 11111111 0 1" /* even parity, expect flush */
+		     "F" /* flush! (for sanity) */
+		     );
+	printf("======== %s(): testing 8-O-1 (valid parity)\n", __func__);
+	test_rx_exec(suart, "1111111" /* no data */
+		     "0 00000000 1 1"
+		     "0 11111111 1 1"
+		     "0 01010101 1 1"
+		     "0 10101010 1 1"
+		     "F" /* flush! */
+		     "0 00000001 0 1"
+		     "0 00000111 0 1"
+		     "0 00011111 0 1"
+		     "0 01111111 0 1"
+		     "F" /* flush! */
+		     );
+
+	osmo_soft_uart_free(suart);
+}
+
+static void test_tx_rx_exec_one(struct osmo_soft_uart *suart,
+				size_t n_bits_total, size_t n_bits_frame)
+{
+	ubit_t tx_buf[n_bits_total];
+	ubit_t *ptr = &tx_buf[0];
+	int rc;
+
+	rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], n_bits_total);
+	OSMO_ASSERT(rc == 0);
+
+	rc = osmo_soft_uart_rx_ubits(suart, &tx_buf[0], n_bits_total);
+	OSMO_ASSERT(rc == 0);
+	osmo_soft_uart_flush_rx(suart);
+
+	printf("%s(n_bits_total=%zu):", __func__, n_bits_total);
+	while (n_bits_total > 0) {
+		size_t n_bits = OSMO_MIN(n_bits_frame, n_bits_total);
+		printf(" %s", osmo_ubit_dump(ptr, n_bits));
+		n_bits_total -= n_bits;
+		ptr += n_bits;
+	}
+	printf("\n");
+}
+
+static void test_tx_rx_exec(struct osmo_soft_uart *suart, size_t n_bits_frame)
+{
+	const uint8_t tx_data[][4] = {
+		{ 0xde, 0xad, 0xbe, 0xef },
+		{ 0x00, 0xaa, 0x55, 0xff },
+		{ 0x01, 0x02, 0x04, 0x08 },
+		{ 0x10, 0x20, 0x40, 0x80 },
+	};
+
+	for (size_t i = 0; i < ARRAY_SIZE(tx_data); i++) {
+		g_tx_cb_cfg.data_len = 4;
+		g_tx_cb_cfg.data = tx_data[i];
+		test_tx_rx_exec_one(suart, 4 * n_bits_frame, n_bits_frame);
+	}
+
+	g_tx_cb_cfg.data_len = 0;
+	g_tx_cb_cfg.data = NULL;
+	test_tx_rx_exec_one(suart, 4 * n_bits_frame, n_bits_frame);
+}
+
+static void test_tx_rx(void)
+{
+	struct osmo_soft_uart_cfg cfg;
+	struct osmo_soft_uart *suart;
+
+	suart = osmo_soft_uart_alloc(NULL, __func__, &suart_test_default_cfg);
+	OSMO_ASSERT(suart != NULL);
+
+	osmo_soft_uart_set_tx(suart, true);
+	osmo_soft_uart_set_rx(suart, true);
+
+	printf("======== %s(): testing 8-N-1\n", __func__);
+	test_tx_rx_exec(suart, (1 + 8 + 1));
+
+	printf("======== %s(): testing 8-N-2\n", __func__);
+	cfg = suart_test_default_cfg;
+	cfg.num_stop_bits = 2;
+	osmo_soft_uart_configure(suart, &cfg);
+	test_tx_rx_exec(suart, (1 + 8 + 2));
+
+	printf("======== %s(): testing 8-E-1\n", __func__);
+	cfg = suart_test_default_cfg;
+	cfg.parity_mode = OSMO_SUART_PARITY_EVEN;
+	osmo_soft_uart_configure(suart, &cfg);
+	test_tx_rx_exec(suart, (1 + 8 + 1 + 1));
+
+	printf("======== %s(): testing 8-O-1\n", __func__);
+	cfg = suart_test_default_cfg;
+	cfg.parity_mode = OSMO_SUART_PARITY_ODD;
+	osmo_soft_uart_configure(suart, &cfg);
+	test_tx_rx_exec(suart, (1 + 8 + 1 + 1));
+
+	printf("======== %s(): testing 6-N-1\n", __func__);
+	cfg = suart_test_default_cfg;
+	cfg.num_data_bits = 6;
+	osmo_soft_uart_configure(suart, &cfg);
+	test_tx_rx_exec(suart, (1 + 6 + 1));
+
+	osmo_soft_uart_free(suart);
+}
+
+int main(int argc, char **argv)
+{
+	test_rx();
+	test_tx_rx();
+
+	return 0;
+}
diff --git a/tests/soft_uart/soft_uart_test.ok b/tests/soft_uart/soft_uart_test.ok
new file mode 100644
index 0000000..7453d66
--- /dev/null
+++ b/tests/soft_uart/soft_uart_test.ok
@@ -0,0 +1,136 @@
+======== test_rx(): testing 8-N-1 (no data)
+test_rx_exec() @ 0: flush the Rx buffer
+test_rx_exec() @ 6: flush the Rx buffer
+test_rx_exec() @ 12: flush the Rx buffer
+======== test_rx(): testing 8-N-1 (fill up flush)
+suart_rx_cb(flags=00): de ad be ef 
+suart_rx_cb(flags=00): 00 aa 55 ff 
+test_rx_exec() @ 101: flush the Rx buffer
+======== test_rx(): testing 8-N-1 (HELLO)
+test_rx_exec() @ 18: flush the Rx buffer
+suart_rx_cb(flags=00): 48 
+test_rx_exec() @ 31: flush the Rx buffer
+suart_rx_cb(flags=00): 45 
+test_rx_exec() @ 57: flush the Rx buffer
+suart_rx_cb(flags=00): 4c 
+test_rx_exec() @ 70: flush the Rx buffer
+suart_rx_cb(flags=00): 4c 
+test_rx_exec() @ 96: flush the Rx buffer
+suart_rx_cb(flags=00): 4f 
+======== test_rx(): testing 8-N-1 (framing errors)
+suart_rx_cb(flags=01): 00 aa 
+test_rx_exec() @ 41: flush the Rx buffer
+suart_rx_cb(flags=00): ff 
+======== test_rx(): testing 8-N-2 (HELLO)
+test_rx_exec() @ 20: flush the Rx buffer
+test_rx_exec() @ 22: flush the Rx buffer
+suart_rx_cb(flags=00): 48 
+test_rx_exec() @ 35: flush the Rx buffer
+test_rx_exec() @ 37: flush the Rx buffer
+suart_rx_cb(flags=00): 45 
+test_rx_exec() @ 65: flush the Rx buffer
+test_rx_exec() @ 67: flush the Rx buffer
+suart_rx_cb(flags=00): 4c 
+test_rx_exec() @ 80: flush the Rx buffer
+test_rx_exec() @ 82: flush the Rx buffer
+suart_rx_cb(flags=00): 4c 
+test_rx_exec() @ 110: flush the Rx buffer
+test_rx_exec() @ 112: flush the Rx buffer
+suart_rx_cb(flags=00): 4f 
+======== test_rx(): testing 8-N-2 (framing errors)
+suart_rx_cb(flags=01): 00 aa 
+test_rx_exec() @ 57: flush the Rx buffer
+suart_rx_cb(flags=00): 55 ff 
+======== test_rx(): testing 8-E-1 (invalid parity)
+suart_rx_cb(flags=02): 00 01 
+test_rx_exec() @ 49: flush the Rx buffer
+suart_rx_cb(flags=02): ff 
+======== test_rx(): testing 8-E-1 (valid parity)
+test_rx_exec() @ 63: flush the Rx buffer
+suart_rx_cb(flags=00): 00 ff aa 55 
+test_rx_exec() @ 120: flush the Rx buffer
+suart_rx_cb(flags=00): 80 e0 f8 fe 
+======== test_rx(): testing 8-O-1 (invalid parity)
+suart_rx_cb(flags=02): 00 01 
+test_rx_exec() @ 42: flush the Rx buffer
+suart_rx_cb(flags=02): ff 
+======== test_rx(): testing 8-O-1 (valid parity)
+test_rx_exec() @ 63: flush the Rx buffer
+suart_rx_cb(flags=00): 00 ff aa 55 
+test_rx_exec() @ 120: flush the Rx buffer
+suart_rx_cb(flags=00): 80 e0 f8 fe 
+======== test_tx_rx(): testing 8-N-1
+suart_tx_cb(len=4/4): de ad be ef 
+suart_rx_cb(flags=00): de ad be ef 
+test_tx_rx_exec_one(n_bits_total=40): 0011110111 0101101011 0011111011 0111101111
+suart_tx_cb(len=4/4): 00 aa 55 ff 
+suart_rx_cb(flags=00): 00 aa 55 ff 
+test_tx_rx_exec_one(n_bits_total=40): 0000000001 0010101011 0101010101 0111111111
+suart_tx_cb(len=4/4): 01 02 04 08 
+suart_rx_cb(flags=00): 01 02 04 08 
+test_tx_rx_exec_one(n_bits_total=40): 0100000001 0010000001 0001000001 0000100001
+suart_tx_cb(len=4/4): 10 20 40 80 
+suart_rx_cb(flags=00): 10 20 40 80 
+test_tx_rx_exec_one(n_bits_total=40): 0000010001 0000001001 0000000101 0000000011
+suart_tx_cb(len=0/4): 
+test_tx_rx_exec_one(n_bits_total=40): 1111111111 1111111111 1111111111 1111111111
+======== test_tx_rx(): testing 8-N-2
+suart_tx_cb(len=4/4): de ad be ef 
+suart_rx_cb(flags=00): de ad be ef 
+test_tx_rx_exec_one(n_bits_total=44): 00111101111 01011010111 00111110111 01111011111
+suart_tx_cb(len=4/4): 00 aa 55 ff 
+suart_rx_cb(flags=00): 00 aa 55 ff 
+test_tx_rx_exec_one(n_bits_total=44): 00000000011 00101010111 01010101011 01111111111
+suart_tx_cb(len=4/4): 01 02 04 08 
+suart_rx_cb(flags=00): 01 02 04 08 
+test_tx_rx_exec_one(n_bits_total=44): 01000000011 00100000011 00010000011 00001000011
+suart_tx_cb(len=4/4): 10 20 40 80 
+suart_rx_cb(flags=00): 10 20 40 80 
+test_tx_rx_exec_one(n_bits_total=44): 00000100011 00000010011 00000001011 00000000111
+suart_tx_cb(len=0/4): 
+test_tx_rx_exec_one(n_bits_total=44): 11111111111 11111111111 11111111111 11111111111
+======== test_tx_rx(): testing 8-E-1
+suart_tx_cb(len=4/4): de ad be ef 
+suart_rx_cb(flags=00): de ad be ef 
+test_tx_rx_exec_one(n_bits_total=44): 00111101101 01011010111 00111110101 01111011111
+suart_tx_cb(len=4/4): 00 aa 55 ff 
+suart_rx_cb(flags=00): 00 aa 55 ff 
+test_tx_rx_exec_one(n_bits_total=44): 00000000001 00101010101 01010101001 01111111101
+suart_tx_cb(len=4/4): 01 02 04 08 
+suart_rx_cb(flags=00): 01 02 04 08 
+test_tx_rx_exec_one(n_bits_total=44): 01000000011 00100000011 00010000011 00001000011
+suart_tx_cb(len=4/4): 10 20 40 80 
+suart_rx_cb(flags=00): 10 20 40 80 
+test_tx_rx_exec_one(n_bits_total=44): 00000100011 00000010011 00000001011 00000000111
+suart_tx_cb(len=0/4): 
+test_tx_rx_exec_one(n_bits_total=44): 11111111111 11111111111 11111111111 11111111111
+======== test_tx_rx(): testing 8-O-1
+suart_tx_cb(len=4/4): de ad be ef 
+suart_rx_cb(flags=00): de ad be ef 
+test_tx_rx_exec_one(n_bits_total=44): 00111101111 01011010101 00111110111 01111011101
+suart_tx_cb(len=4/4): 00 aa 55 ff 
+suart_rx_cb(flags=00): 00 aa 55 ff 
+test_tx_rx_exec_one(n_bits_total=44): 00000000011 00101010111 01010101011 01111111111
+suart_tx_cb(len=4/4): 01 02 04 08 
+suart_rx_cb(flags=00): 01 02 04 08 
+test_tx_rx_exec_one(n_bits_total=44): 01000000001 00100000001 00010000001 00001000001
+suart_tx_cb(len=4/4): 10 20 40 80 
+suart_rx_cb(flags=00): 10 20 40 80 
+test_tx_rx_exec_one(n_bits_total=44): 00000100001 00000010001 00000001001 00000000101
+suart_tx_cb(len=0/4): 
+test_tx_rx_exec_one(n_bits_total=44): 11111111111 11111111111 11111111111 11111111111
+======== test_tx_rx(): testing 6-N-1
+suart_tx_cb(len=4/4): de ad be ef 
+suart_rx_cb(flags=00): 78 b4 f8 bc 
+test_tx_rx_exec_one(n_bits_total=32): 00111101 01011011 00111111 01111011
+suart_tx_cb(len=4/4): 00 aa 55 ff 
+suart_rx_cb(flags=00): 00 a8 54 fc 
+test_tx_rx_exec_one(n_bits_total=32): 00000001 00101011 01010101 01111111
+suart_tx_cb(len=4/4): 01 02 04 08 
+suart_rx_cb(flags=00): 04 08 10 20 
+test_tx_rx_exec_one(n_bits_total=32): 01000001 00100001 00010001 00001001
+suart_tx_cb(len=4/4): 10 20 40 80 
+suart_rx_cb(flags=00): 40 80 00 00 
+test_tx_rx_exec_one(n_bits_total=32): 00000101 00000011 00000001 00000001
+suart_tx_cb(len=0/4): 
+test_tx_rx_exec_one(n_bits_total=32): 11111111 11111111 11111111 11111111
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 73c3cdc..aacdad7 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -513,3 +513,9 @@
 cat $abs_srcdir/osmo_io/osmo_io_test.err > experr
 AT_CHECK([$abs_top_builddir/tests/osmo_io/osmo_io_test], [0], [expout], [experr])
 AT_CLEANUP
+
+AT_SETUP([soft_uart])
+AT_KEYWORDS([soft_uart])
+cat $abs_srcdir/soft_uart/soft_uart_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/soft_uart/soft_uart_test], [0], [expout], [ignore])
+AT_CLEANUP