aboutsummaryrefslogtreecommitdiffstats
path: root/tests/soft_uart
diff options
context:
space:
mode:
Diffstat (limited to 'tests/soft_uart')
-rw-r--r--tests/soft_uart/soft_uart_test.c658
-rw-r--r--tests/soft_uart/soft_uart_test.ok278
2 files changed, 936 insertions, 0 deletions
diff --git a/tests/soft_uart/soft_uart_test.c b/tests/soft_uart/soft_uart_test.c
new file mode 100644
index 00000000..7280bdcd
--- /dev/null
+++ b/tests/soft_uart/soft_uart_test.c
@@ -0,0 +1,658 @@
+/*
+ * (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 <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/soft_uart.h>
+
+#define SUART_TEST_BEGIN \
+ do { \
+ printf("\nExecuting %s\n", __func__); \
+ } while (0)
+
+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 void suart_status_change_cb(void *priv, unsigned int status)
+{
+ fprintf(stdout, "%s(status=0x%08x)\n", __func__, status);
+}
+
+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,
+ .status_change_cb = &suart_status_change_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;
+ int rc;
+
+ switch (input[i]) {
+ case '0':
+ case '1':
+ ubit = input[i] - '0';
+ rc = osmo_soft_uart_rx_ubits(suart, &ubit, 1);
+ OSMO_ASSERT(rc == 0); /* 0 on success */
+ 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_TEST_BEGIN;
+
+ suart = osmo_soft_uart_alloc(NULL, __func__, &suart_test_default_cfg);
+ OSMO_ASSERT(suart != NULL);
+
+ osmo_soft_uart_set_rx(suart, true);
+
+ printf("======== testing 8-N-1 (no data)\n");
+ test_rx_exec(suart, "F11111F11111F");
+
+ printf("======== testing 8-N-1 (fill up flush)\n");
+ 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("======== testing 8-N-1 (HELLO)\n");
+ 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("======== testing 8-N-1 (framing errors)\n");
+ 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("======== testing 8-N-2 (HELLO)\n");
+ 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("======== testing 8-N-2 (framing errors)\n");
+ 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("======== testing 8-E-1 (invalid parity)\n");
+ 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("======== testing 8-E-1 (valid parity)\n");
+ 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("======== testing 8-O-1 (invalid parity)\n");
+ 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("======== testing 8-O-1 (valid parity)\n");
+ 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_rx_flush(void)
+{
+ struct osmo_soft_uart_cfg cfg;
+ struct osmo_soft_uart *suart;
+
+ SUART_TEST_BEGIN;
+
+ suart = osmo_soft_uart_alloc(NULL, __func__, &suart_test_default_cfg);
+ OSMO_ASSERT(suart != NULL);
+
+ printf("calling osmo_soft_uart_flush_rx() while Rx disabled\n");
+ osmo_soft_uart_flush_rx(suart);
+
+ printf("enabling the receiver\n");
+ osmo_soft_uart_set_rx(suart, true);
+
+ printf("calling osmo_soft_uart_flush_rx() while Rx enabled, but no data\n");
+ osmo_soft_uart_flush_rx(suart);
+
+ /* FIXME: this scenario demonstrates a problem that may occur when the user
+ * flushes the Rx buffer manually while the soft-UART state reflects flags
+ * of an incomplete symbol, for which we're waiting the stop bit. */
+ printf("testing corner case: manual flushing during a parity error (8-E-1)\n");
+ 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 01010101 0 1" /* even parity, correct */
+ "0 10101010 0 1" /* even parity, correct */
+ "0 11111111 1" /* odd parity, incorrect, but stop bit is pending */
+ "F" /* manual flush happens before receiving the stop bit */
+ "1" /* finally, the stop bit is received */
+ );
+ /* test_rx_exec() @ 47: flush the Rx buffer
+ * suart_rx_cb(flags=02): aa 55 <--- this is wrong, should be flags=00
+ * suart_rx_cb(flags=02): ff <--- this is expected due to odd parity */
+
+
+ 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 == n_bits_total);
+
+ 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;
+ int rc;
+
+ SUART_TEST_BEGIN;
+
+ suart = osmo_soft_uart_alloc(NULL, __func__, &suart_test_default_cfg);
+ OSMO_ASSERT(suart != NULL);
+
+ /* expect -EAGAIN when the transmitter is not enabled */
+ rc = osmo_soft_uart_tx_ubits(suart, NULL, 42);
+ OSMO_ASSERT(rc == -EAGAIN);
+ /* expect -EAGAIN when the receiver is not enabled */
+ rc = osmo_soft_uart_rx_ubits(suart, NULL, 42);
+ OSMO_ASSERT(rc == -EAGAIN);
+
+ osmo_soft_uart_set_tx(suart, true);
+ osmo_soft_uart_set_rx(suart, true);
+
+ printf("======== testing 8-N-1\n");
+ test_tx_rx_exec(suart, (1 + 8 + 1));
+
+ printf("======== testing 8-N-2\n");
+ 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("======== testing 8-E-1\n");
+ 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("======== testing 8-O-1\n");
+ 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("======== testing 8-M-1\n");
+ cfg = suart_test_default_cfg;
+ cfg.parity_mode = OSMO_SUART_PARITY_MARK;
+ osmo_soft_uart_configure(suart, &cfg);
+ test_tx_rx_exec(suart, (1 + 8 + 1 + 1));
+
+ printf("======== testing 8-S-1\n");
+ cfg = suart_test_default_cfg;
+ cfg.parity_mode = OSMO_SUART_PARITY_SPACE;
+ osmo_soft_uart_configure(suart, &cfg);
+ test_tx_rx_exec(suart, (1 + 8 + 1 + 1));
+
+ printf("======== testing 6-N-1\n");
+ 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);
+}
+
+static void test_tx_rx_pull_n(unsigned int n)
+{
+ struct osmo_soft_uart *suart;
+ ubit_t tx_buf[32];
+ int rc;
+
+ SUART_TEST_BEGIN;
+
+ 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);
+
+ g_tx_cb_cfg.data = (void *)"\x55";
+ g_tx_cb_cfg.data_len = 1;
+
+ printf("======== pulling %lu bits (%u at a time)\n", sizeof(tx_buf), n);
+ for (unsigned int i = 0; i < sizeof(tx_buf); i += n) {
+ rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[i], n);
+ OSMO_ASSERT(rc == n);
+ }
+ printf("%s\n", osmo_ubit_dump(&tx_buf[0], sizeof(tx_buf)));
+
+ printf("======== feeding %lu bits into the receiver\n", sizeof(tx_buf));
+ rc = osmo_soft_uart_rx_ubits(suart, &tx_buf[0], sizeof(tx_buf));
+ OSMO_ASSERT(rc == 0);
+ osmo_soft_uart_flush_rx(suart);
+
+ osmo_soft_uart_free(suart);
+}
+
+static void test_modem_status(void)
+{
+ struct osmo_soft_uart *suart;
+ unsigned int status;
+
+ SUART_TEST_BEGIN;
+
+ suart = osmo_soft_uart_alloc(NULL, __func__, &suart_test_default_cfg);
+ OSMO_ASSERT(suart != NULL);
+
+ printf("initial status=0x%08x\n", osmo_soft_uart_get_status(suart));
+
+ printf("de-asserting DCD, which was not asserted\n");
+ osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_DCD, false);
+ OSMO_ASSERT(osmo_soft_uart_get_status(suart) == 0x00); /* no change */
+
+ printf("asserting both RI and DCD, expecting the callback to be called twice\n");
+ osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_RI, true);
+ osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_DCD, true);
+ status = osmo_soft_uart_get_status(suart);
+ OSMO_ASSERT(status == (OSMO_SUART_STATUS_F_RI | OSMO_SUART_STATUS_F_DCD));
+
+ printf("de-asserting RI, expecting the callback to be called\n");
+ osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_RI, false);
+ status = osmo_soft_uart_get_status(suart);
+ OSMO_ASSERT(status == (OSMO_SUART_STATUS_F_DCD));
+
+ printf("resetting to 0x00, expecting the callback to be called\n");
+ osmo_soft_uart_set_status(suart, 0x00);
+ OSMO_ASSERT(osmo_soft_uart_get_status(suart) == 0x00);
+
+ osmo_soft_uart_free(suart);
+}
+
+static void test_flow_control_dtr_dsr(void)
+{
+ struct osmo_soft_uart_cfg cfg;
+ struct osmo_soft_uart *suart;
+ ubit_t tx_buf[40];
+ int rc;
+
+ SUART_TEST_BEGIN;
+
+ g_tx_cb_cfg.data = (void *)"\x42\x42\x42\x42";
+ g_tx_cb_cfg.data_len = 4;
+
+ cfg = suart_test_default_cfg;
+ cfg.flow_ctrl_mode = OSMO_SUART_FLOW_CTRL_DTR_DSR;
+
+ suart = osmo_soft_uart_alloc(NULL, __func__, &cfg);
+ OSMO_ASSERT(suart != NULL);
+
+ osmo_soft_uart_set_tx(suart, true);
+ osmo_soft_uart_set_rx(suart, true);
+
+ /* expect the initial status to be 0 (all lines de-asserted) */
+ printf("initial status=0x%08x\n", osmo_soft_uart_get_status(suart));
+
+ memset(&tx_buf[0], 1, sizeof(tx_buf)); /* pre-initialize */
+
+ printf("expecting osmo_soft_uart_tx_ubits() to yield nothing\n");
+ rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], sizeof(tx_buf));
+ OSMO_ASSERT(rc == 0);
+
+ printf("expecting osmo_soft_uart_rx_ubits() to yield nothing\n");
+ rc = osmo_soft_uart_rx_ubits(suart, &tx_buf[0], sizeof(tx_buf));
+ OSMO_ASSERT(rc == 0);
+ osmo_soft_uart_flush_rx(suart);
+
+ /* both DTR and DSR are asserted, expect both Rx and Tx to work */
+ printf("======== asserting both DTR and DSR\n");
+ osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_DTR, true);
+ osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_DSR, true);
+
+ memset(&tx_buf[0], 1, sizeof(tx_buf)); /* pre-initialize */
+
+ printf("expecting osmo_soft_uart_tx_ubits() to "
+ "yield %zu bits (requesting %zu bits)\n",
+ sizeof(tx_buf), sizeof(tx_buf));
+ rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], sizeof(tx_buf));
+ OSMO_ASSERT(rc == sizeof(tx_buf));
+ printf("%s\n", osmo_ubit_dump(&tx_buf[0], sizeof(tx_buf)));
+
+ printf("expecting osmo_soft_uart_rx_ubits() to "
+ "consume %zu bits and yield %zu chars\n",
+ sizeof(tx_buf), sizeof(tx_buf) / 10);
+ rc = osmo_soft_uart_rx_ubits(suart, &tx_buf[0], sizeof(tx_buf));
+ OSMO_ASSERT(rc == 0);
+ osmo_soft_uart_flush_rx(suart);
+
+ memset(&tx_buf[0], 1, sizeof(tx_buf)); /* pre-initialize */
+
+ /* make the transmitter consume one char, but pull only 2 bits */
+ printf("expecting osmo_soft_uart_tx_ubits() to "
+ "yield 2 bits (requesting 2 bits)\n");
+ rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], 2);
+ OSMO_ASSERT(rc == 2);
+
+ /* CTS gets de-asserted, the transmitter is shutting down */
+ printf("======== de-asserting DSR\n");
+ osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_DSR, false);
+
+ /* expect only the remaining 8 bits to be pulled out */
+ printf("expecting osmo_soft_uart_tx_ubits() to "
+ "yield 8 bits (requesting %zu bits)\n", sizeof(tx_buf));
+ rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[2], sizeof(tx_buf) - 2);
+ OSMO_ASSERT(rc == 8);
+
+ printf("expecting osmo_soft_uart_rx_ubits() to "
+ "consume %zu bits and yield a pending char\n", sizeof(tx_buf));
+ rc = osmo_soft_uart_rx_ubits(suart, &tx_buf[0], sizeof(tx_buf));
+ OSMO_ASSERT(rc == 0);
+ osmo_soft_uart_flush_rx(suart);
+
+ osmo_soft_uart_free(suart);
+}
+
+static void test_flow_control_rts_cts(void)
+{
+ struct osmo_soft_uart_cfg cfg;
+ struct osmo_soft_uart *suart;
+ ubit_t tx_buf[40];
+ int rc;
+
+ SUART_TEST_BEGIN;
+
+ g_tx_cb_cfg.data = (void *)"\x42\x42\x42\x42";
+ g_tx_cb_cfg.data_len = 4;
+
+ cfg = suart_test_default_cfg;
+ cfg.flow_ctrl_mode = OSMO_SUART_FLOW_CTRL_RTS_CTS;
+
+ suart = osmo_soft_uart_alloc(NULL, __func__, &cfg);
+ OSMO_ASSERT(suart != NULL);
+
+ osmo_soft_uart_set_tx(suart, true);
+ osmo_soft_uart_set_rx(suart, true);
+
+ /* expect the initial status to be 0 (all lines de-asserted) */
+ printf("initial status=0x%08x\n", osmo_soft_uart_get_status(suart));
+
+ memset(&tx_buf[0], 1, sizeof(tx_buf)); /* pre-initialize */
+
+ printf("expecting osmo_soft_uart_tx_ubits() to yield nothing\n");
+ rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], sizeof(tx_buf));
+ OSMO_ASSERT(rc == 0);
+
+ printf("expecting osmo_soft_uart_rx_ubits() to yield nothing\n");
+ rc = osmo_soft_uart_rx_ubits(suart, &tx_buf[0], sizeof(tx_buf));
+ OSMO_ASSERT(rc == 0);
+ osmo_soft_uart_flush_rx(suart);
+
+ /* both RTS/RTR and CTS are asserted, expect both Rx and Tx to work */
+ printf("======== asserting both CTS and RTS/RTR\n");
+ osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_CTS, true);
+ osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_RTS_RTR, true);
+
+ memset(&tx_buf[0], 1, sizeof(tx_buf)); /* pre-initialize */
+
+ printf("expecting osmo_soft_uart_tx_ubits() to "
+ "yield %zu bits (requesting %zu bits)\n",
+ sizeof(tx_buf), sizeof(tx_buf));
+ rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], sizeof(tx_buf));
+ OSMO_ASSERT(rc == sizeof(tx_buf));
+ printf("%s\n", osmo_ubit_dump(&tx_buf[0], sizeof(tx_buf)));
+
+ printf("expecting osmo_soft_uart_rx_ubits() to "
+ "consume %zu bits and yield %zu chars\n",
+ sizeof(tx_buf), sizeof(tx_buf) / 10);
+ rc = osmo_soft_uart_rx_ubits(suart, &tx_buf[0], sizeof(tx_buf));
+ OSMO_ASSERT(rc == 0);
+ osmo_soft_uart_flush_rx(suart);
+
+ memset(&tx_buf[0], 1, sizeof(tx_buf)); /* pre-initialize */
+
+ /* make the transmitter consume one char, but pull only 2 bits */
+ printf("expecting osmo_soft_uart_tx_ubits() to "
+ "yield 2 bits (requesting 2 bits)\n");
+ rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], 2);
+ OSMO_ASSERT(rc == 2);
+
+ /* CTS gets de-asserted, the transmitter is shutting down */
+ printf("======== de-asserting CTS\n");
+ osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_CTS, false);
+
+ /* expect only the remaining 8 bits to be pulled out */
+ printf("expecting osmo_soft_uart_tx_ubits() to "
+ "yield 8 bits (requesting %zu bits)\n", sizeof(tx_buf));
+ rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[2], sizeof(tx_buf) - 2);
+ OSMO_ASSERT(rc == 8);
+
+ printf("expecting osmo_soft_uart_rx_ubits() to "
+ "consume %zu bits and yield a pending char\n", sizeof(tx_buf));
+ rc = osmo_soft_uart_rx_ubits(suart, &tx_buf[0], sizeof(tx_buf));
+ OSMO_ASSERT(rc == 0);
+ osmo_soft_uart_flush_rx(suart);
+
+ osmo_soft_uart_free(suart);
+}
+
+static void test_tx_pull(void)
+{
+ struct osmo_soft_uart *suart;
+ ubit_t tx_buf[25 * 2];
+ int rc;
+
+ SUART_TEST_BEGIN;
+
+ g_tx_cb_cfg.data = (void *)"\x42\x42\x42\x42\x42";
+ g_tx_cb_cfg.data_len = 5;
+
+ suart = osmo_soft_uart_alloc(NULL, __func__, &suart_test_default_cfg);
+ OSMO_ASSERT(suart != NULL);
+
+ osmo_soft_uart_set_tx(suart, true);
+
+ printf("pulling 25 bits (first time) out of the transmitter\n");
+ rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], sizeof(tx_buf) / 2);
+ OSMO_ASSERT(rc == 25);
+
+ printf("pulling 25 bits (second time) out of the transmitter\n");
+ rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[25], sizeof(tx_buf) / 2);
+ OSMO_ASSERT(rc == 25);
+
+ /* FIXME: we pull total 25 + 25 == 50 bits out of the transmitter, which is enough
+ * to fit 5 characters (assuming 8-N-1). However, the current impelementation would
+ * pull only 2 + 2 == characters total, wasting 5 + 5 == 10 bits for padding. */
+
+ osmo_soft_uart_free(suart);
+}
+
+int main(int argc, char **argv)
+{
+ test_rx();
+ test_rx_flush();
+ test_tx_rx();
+
+ /* test pulling small number of bits at a time */
+ test_tx_rx_pull_n(1);
+ test_tx_rx_pull_n(2);
+ test_tx_rx_pull_n(4);
+ test_tx_rx_pull_n(8);
+
+ test_tx_pull();
+
+ /* test flow control */
+ test_modem_status();
+ test_flow_control_dtr_dsr();
+ test_flow_control_rts_cts();
+
+ 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 00000000..dcd7ceb4
--- /dev/null
+++ b/tests/soft_uart/soft_uart_test.ok
@@ -0,0 +1,278 @@
+
+Executing 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
+======== 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
+======== 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
+======== testing 8-N-1 (framing errors)
+suart_rx_cb(flags=01): 00
+suart_rx_cb(flags=01): aa
+test_rx_exec() @ 41: flush the Rx buffer
+suart_rx_cb(flags=00): ff
+======== 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
+======== testing 8-N-2 (framing errors)
+suart_rx_cb(flags=01): 00
+suart_rx_cb(flags=01): aa
+suart_rx_cb(flags=01): 55
+test_rx_exec() @ 57: flush the Rx buffer
+suart_rx_cb(flags=00): ff
+======== testing 8-E-1 (invalid parity)
+suart_rx_cb(flags=02): 00
+suart_rx_cb(flags=02): 01
+suart_rx_cb(flags=02): ff
+test_rx_exec() @ 49: flush the Rx buffer
+======== 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
+======== testing 8-O-1 (invalid parity)
+suart_rx_cb(flags=02): 00
+suart_rx_cb(flags=02): 01
+suart_rx_cb(flags=02): ff
+test_rx_exec() @ 42: flush the Rx buffer
+======== 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
+
+Executing test_rx_flush
+calling osmo_soft_uart_flush_rx() while Rx disabled
+enabling the receiver
+calling osmo_soft_uart_flush_rx() while Rx enabled, but no data
+testing corner case: manual flushing during a parity error (8-E-1)
+test_rx_exec() @ 47: flush the Rx buffer
+suart_rx_cb(flags=02): aa 55
+suart_rx_cb(flags=02): ff
+
+Executing 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
+======== 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
+======== 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
+======== 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
+======== testing 8-M-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 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
+======== testing 8-S-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 01011010101 00111110101 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): 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): 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
+======== testing 6-N-1
+suart_tx_cb(len=4/4): de ad be ef
+suart_rx_cb(flags=00): 1e 2d 3e 2f
+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 2a 15 3f
+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): 01 02 04 08
+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): 10 20 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
+
+Executing test_tx_rx_pull_n
+======== pulling 32 bits (1 at a time)
+suart_tx_cb(len=1/1): 55
+suart_tx_cb(len=1/1): 55
+suart_tx_cb(len=1/1): 55
+suart_tx_cb(len=1/1): 55
+01010101010101010101010101010101
+======== feeding 32 bits into the receiver
+suart_rx_cb(flags=00): 55 55 55
+
+Executing test_tx_rx_pull_n
+======== pulling 32 bits (2 at a time)
+suart_tx_cb(len=1/1): 55
+suart_tx_cb(len=1/1): 55
+suart_tx_cb(len=1/1): 55
+suart_tx_cb(len=1/1): 55
+01010101010101010101010101010101
+======== feeding 32 bits into the receiver
+suart_rx_cb(flags=00): 55 55 55
+
+Executing test_tx_rx_pull_n
+======== pulling 32 bits (4 at a time)
+suart_tx_cb(len=1/1): 55
+suart_tx_cb(len=1/1): 55
+suart_tx_cb(len=1/1): 55
+01010101011101010101011101010101
+======== feeding 32 bits into the receiver
+suart_rx_cb(flags=00): 55 55
+
+Executing test_tx_rx_pull_n
+======== pulling 32 bits (8 at a time)
+suart_tx_cb(len=1/1): 55
+suart_tx_cb(len=1/1): 55
+01010101011111110101010101111111
+======== feeding 32 bits into the receiver
+suart_rx_cb(flags=00): 55 55
+
+Executing test_tx_pull
+pulling 25 bits (first time) out of the transmitter
+suart_tx_cb(len=2/2): 42 42
+pulling 25 bits (second time) out of the transmitter
+suart_tx_cb(len=2/2): 42 42
+
+Executing test_modem_status
+initial status=0x00000000
+de-asserting DCD, which was not asserted
+asserting both RI and DCD, expecting the callback to be called twice
+suart_status_change_cb(status=0x00000008)
+suart_status_change_cb(status=0x0000000a)
+de-asserting RI, expecting the callback to be called
+suart_status_change_cb(status=0x00000002)
+resetting to 0x00, expecting the callback to be called
+suart_status_change_cb(status=0x00000000)
+
+Executing test_flow_control_dtr_dsr
+initial status=0x00000000
+expecting osmo_soft_uart_tx_ubits() to yield nothing
+expecting osmo_soft_uart_rx_ubits() to yield nothing
+======== asserting both DTR and DSR
+suart_status_change_cb(status=0x00000001)
+suart_status_change_cb(status=0x00000005)
+expecting osmo_soft_uart_tx_ubits() to yield 40 bits (requesting 40 bits)
+suart_tx_cb(len=4/4): 42 42 42 42
+0010000101001000010100100001010010000101
+expecting osmo_soft_uart_rx_ubits() to consume 40 bits and yield 4 chars
+suart_rx_cb(flags=00): 42 42 42 42
+expecting osmo_soft_uart_tx_ubits() to yield 2 bits (requesting 2 bits)
+suart_tx_cb(len=1/1): 42
+======== de-asserting DSR
+suart_status_change_cb(status=0x00000001)
+expecting osmo_soft_uart_tx_ubits() to yield 8 bits (requesting 40 bits)
+expecting osmo_soft_uart_rx_ubits() to consume 40 bits and yield a pending char
+suart_rx_cb(flags=00): 42
+
+Executing test_flow_control_rts_cts
+initial status=0x00000000
+expecting osmo_soft_uart_tx_ubits() to yield nothing
+expecting osmo_soft_uart_rx_ubits() to yield nothing
+======== asserting both CTS and RTS/RTR
+suart_status_change_cb(status=0x00000020)
+suart_status_change_cb(status=0x00000030)
+expecting osmo_soft_uart_tx_ubits() to yield 40 bits (requesting 40 bits)
+suart_tx_cb(len=4/4): 42 42 42 42
+0010000101001000010100100001010010000101
+expecting osmo_soft_uart_rx_ubits() to consume 40 bits and yield 4 chars
+suart_rx_cb(flags=00): 42 42 42 42
+expecting osmo_soft_uart_tx_ubits() to yield 2 bits (requesting 2 bits)
+suart_tx_cb(len=1/1): 42
+======== de-asserting CTS
+suart_status_change_cb(status=0x00000010)
+expecting osmo_soft_uart_tx_ubits() to yield 8 bits (requesting 40 bits)
+expecting osmo_soft_uart_rx_ubits() to consume 40 bits and yield a pending char
+suart_rx_cb(flags=00): 42