soft_uart: implement modem status lines and flow control

Change-Id: I26b93ce76f2f6b6fbf017f2684312007db3c6d48
Related: OS#4396
diff --git a/include/osmocom/core/soft_uart.h b/include/osmocom/core/soft_uart.h
index 17a7c67..afc6ad6 100644
--- a/include/osmocom/core/soft_uart.h
+++ b/include/osmocom/core/soft_uart.h
@@ -4,6 +4,7 @@
  *  Software UART implementation. */
 /*
  * (C) 2022 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
  *
  * All Rights Reserved
  *
@@ -22,6 +23,8 @@
  */
 
 #include <stdint.h>
+#include <stdbool.h>
+
 #include <osmocom/core/bits.h>
 #include <osmocom/core/msgb.h>
 
@@ -43,12 +46,31 @@
 	OSMO_SUART_F_BREAK		= (1 << 2),	/*!< Break condition (not implemented) */
 };
 
-#if 0
+/*! Modem status "line" flags.
+ * https://en.wikipedia.org/wiki/RS-232#Data_and_control_signals */
 enum osmo_soft_uart_status {
-	/* RTS, CTS, ... */
-	_fixme,
+	OSMO_SUART_STATUS_F_DTR		= (1 << 0),	/*!< Data Terminal Ready */
+	OSMO_SUART_STATUS_F_DCD		= (1 << 1),	/*!< Data Carrier Detect */
+	OSMO_SUART_STATUS_F_DSR		= (1 << 2),	/*!< Data Set Ready */
+	OSMO_SUART_STATUS_F_RI		= (1 << 3),	/*!< Ring Indicator */
+	OSMO_SUART_STATUS_F_RTS_RTR	= (1 << 4),	/*!< Request To Send or Ready To Receive */
+	OSMO_SUART_STATUS_F_CTS		= (1 << 5),	/*!< Clear To Send */
 };
-#endif
+
+/*! Flow control mode.
+ * https://en.wikipedia.org/wiki/Flow_control_(data)#Hardware_flow_control */
+enum osmo_soft_uart_flow_ctrl_mode {
+	/*! No flow control */
+	OSMO_SUART_FLOW_CTRL_NONE,
+	/*! DTR/DSR flow control: Tx if DSR is active and drop DTR if cannot Rx anymore. */
+	OSMO_SUART_FLOW_CTRL_DTR_DSR,
+	/*! RTS/CTS flow control: Tx if CTS is active and drop RTS if cannot Rx anymore.
+	 * The technically correct name would be RTR/CTS, because the RTS signal actually
+	 * indicates readiness to *receive* data (Ready To Receive), and not really used
+	 * to request a transmission (Request To Send) nowadays.  Alternatively, the RTS
+	 * signal can be interpreted as "Request To Send to me". */
+	OSMO_SUART_FLOW_CTRL_RTS_CTS,
+};
 
 /*! Configuration for a soft-UART. */
 struct osmo_soft_uart_cfg {
@@ -92,12 +114,12 @@
 	void (*tx_cb)(void *priv, struct msgb *tx_data);
 
 	/*! Modem status line change call-back.
-	 *
-	 * FIXME: flow control is not implemented, so it's never called.
-	 *
 	 * \param[in] priv opaque application-private data.
-	 * \param[in] status bit-mask of osmo_soft_uart_status. */
+	 * \param[in] status updated status; bit-mask of OSMO_SUART_STATUS_F_*. */
 	void (*status_change_cb)(void *priv, unsigned int status);
+
+	/*! "Hardware" flow control mode. */
+	enum osmo_soft_uart_flow_ctrl_mode flow_ctrl_mode;
 };
 
 extern const struct osmo_soft_uart_cfg osmo_soft_uart_default_cfg;
@@ -118,5 +140,10 @@
 int osmo_soft_uart_rx_ubits(struct osmo_soft_uart *suart, const ubit_t *ubits, size_t n_ubits);
 int osmo_soft_uart_tx_ubits(struct osmo_soft_uart *suart, ubit_t *ubits, size_t n_ubits);
 
+unsigned int osmo_soft_uart_get_status(const struct osmo_soft_uart *suart);
 int osmo_soft_uart_set_status(struct osmo_soft_uart *suart, unsigned int status);
+void osmo_soft_uart_set_status_line(struct osmo_soft_uart *suart,
+				    enum osmo_soft_uart_status line,
+				    bool active);
+
 void osmo_soft_uart_flush_rx(struct osmo_soft_uart *suart);
diff --git a/src/core/libosmocore.map b/src/core/libosmocore.map
index 4ae5108..fc81650 100644
--- a/src/core/libosmocore.map
+++ b/src/core/libosmocore.map
@@ -450,7 +450,9 @@
 osmo_soft_uart_set_tx;
 osmo_soft_uart_rx_ubits;
 osmo_soft_uart_tx_ubits;
+osmo_soft_uart_get_status;
 osmo_soft_uart_set_status;
+osmo_soft_uart_set_status_line;
 osmo_soft_uart_flush_rx;
 osmo_stat_item_dec;
 osmo_stat_item_flush;
diff --git a/src/core/soft_uart.c b/src/core/soft_uart.c
index 5750282..6cc8ab4 100644
--- a/src/core/soft_uart.c
+++ b/src/core/soft_uart.c
@@ -40,6 +40,8 @@
 struct osmo_soft_uart {
 	struct osmo_soft_uart_cfg cfg;
 	const char *name;
+	/* modem status (bitmask of OSMO_SUART_STATUS_F_*) */
+	unsigned int status;
 	struct {
 		bool running;
 		uint8_t bit_count;
@@ -47,7 +49,6 @@
 		struct msgb *msg;
 		ubit_t parity_bit; /* 0 (even) / 1 (odd) */
 		unsigned int flags;
-		unsigned int status;
 		struct osmo_timer_list timer;
 		enum suart_flow_state flow_state;
 	} rx;
@@ -67,6 +68,7 @@
 	.parity_mode = OSMO_SUART_PARITY_NONE,
 	.rx_buf_size = 1024,
 	.rx_timeout_ms = 100,
+	.flow_ctrl_mode = OSMO_SUART_FLOW_CTRL_NONE,
 };
 
 /*************************************************************************
@@ -273,11 +275,25 @@
 	return tx_bit;
 }
 
+/* pull pending bits out of the UART */
+static size_t suart_tx_pending(struct osmo_soft_uart *suart, ubit_t *ubits, size_t n_ubits)
+{
+	size_t i;
+
+	for (i = 0; i < n_ubits; i++) {
+		if (suart->tx.flow_state == SUART_FLOW_ST_IDLE)
+			break;
+		ubits[i] = suart_tx_bit(suart, NULL);
+	}
+
+	return i;
+}
+
 /*! Pull a number of unpacked bits out of the soft-UART transmitter.
  * \param[in] suart soft-UART instance to pull the bits from.
  * \param[out] ubits pointer to a buffer where to store pulled bits.
  * \param[in] n_ubits number of unpacked bits to be pulled.
- * \returns number of bits pulled; negative on error.
+ * \returns number of bits pulled (may be less than n_ubits); negative on error.
  *          -EAGAIN indicates that the transmitter is disabled. */
 int osmo_soft_uart_tx_ubits(struct osmo_soft_uart *suart, ubit_t *ubits, size_t n_ubits)
 {
@@ -291,6 +307,24 @@
 	if (!suart->tx.running)
 		return -EAGAIN;
 
+	switch (suart->cfg.flow_ctrl_mode) {
+	case OSMO_SUART_FLOW_CTRL_DTR_DSR:
+		/* if DSR is de-asserted, Tx pending bits and suspend */
+		if (~suart->status & OSMO_SUART_STATUS_F_DSR)
+			return suart_tx_pending(suart, ubits, n_ubits);
+		/* else: keep transmitting as usual */
+		break;
+	case OSMO_SUART_FLOW_CTRL_RTS_CTS:
+		/* if CTS is de-asserted, Tx pending bits and suspend */
+		if (~suart->status & OSMO_SUART_STATUS_F_CTS)
+			return suart_tx_pending(suart, ubits, n_ubits);
+		/* else: keep transmitting as usual */
+		break;
+	case OSMO_SUART_FLOW_CTRL_NONE:
+	default:
+		break;
+	}
+
 	/* calculate UART frame size for the effective config */
 	n_frame_bits = 1 + cfg->num_data_bits + cfg->num_stop_bits;
 	if (cfg->parity_mode != OSMO_SUART_PARITY_NONE)
@@ -321,16 +355,49 @@
 	return n_ubits;
 }
 
-/*! Set the modem status lines of the given soft-UART.
- * \param[in] suart soft-UART instance to update the modem status.
- * \param[in] status mask of osmo_soft_uart_status.
+/*! Get the modem status bitmask of the given soft-UART.
+ * \param[in] suart soft-UART instance to get the modem status.
+ * \returns bitmask of OSMO_SUART_STATUS_F_*. */
+unsigned int osmo_soft_uart_get_status(const struct osmo_soft_uart *suart)
+{
+	return suart->status;
+}
+
+/*! Set the modem status bitmask of the given soft-UART.
+ * \param[in] suart soft-UART instance to set the modem status.
+ * \param[in] status bitmask of OSMO_SUART_STATUS_F_*.
  * \returns 0 on success; negative on error. */
 int osmo_soft_uart_set_status(struct osmo_soft_uart *suart, unsigned int status)
 {
-	/* FIXME: Tx */
+	const struct osmo_soft_uart_cfg *cfg = &suart->cfg;
+
+	if (cfg->status_change_cb != NULL) {
+		if (suart->status != status)
+			cfg->status_change_cb(cfg->priv, status);
+	}
+
+	suart->status = status;
 	return 0;
 }
 
+/*! Activate/deactivate a modem status line of the given soft-UART.
+ * \param[in] suart soft-UART instance to update the modem status.
+ * \param[in] line a modem status line, one of OSMO_SUART_STATUS_F_*.
+ * \param[in] active activate (true) or deactivate (false) the line. */
+void osmo_soft_uart_set_status_line(struct osmo_soft_uart *suart,
+				    enum osmo_soft_uart_status line,
+				    bool active)
+{
+	unsigned int status = suart->status;
+
+	if (active) /* assert the given line */
+		status |= line;
+	else /* de-assert the given line */
+		status &= ~line;
+
+	osmo_soft_uart_set_status(suart, status);
+}
+
 
 /*************************************************************************
  * Management / Initialization
diff --git a/tests/soft_uart/soft_uart_test.c b/tests/soft_uart/soft_uart_test.c
index ef64855..0e84d5a 100644
--- a/tests/soft_uart/soft_uart_test.c
+++ b/tests/soft_uart/soft_uart_test.c
@@ -48,6 +48,11 @@
 		__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,
@@ -55,6 +60,7 @@
 	.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,
@@ -344,6 +350,203 @@
 	osmo_soft_uart_free(suart);
 }
 
+static void test_modem_status(void)
+{
+	struct osmo_soft_uart *suart;
+	unsigned int status;
+
+	suart = osmo_soft_uart_alloc(NULL, __func__, &suart_test_default_cfg);
+	OSMO_ASSERT(suart != NULL);
+
+	printf("======== %s(): initial status=0x%08x\n",
+	       __func__, 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;
+
+	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("======== %s(): initial status=0x%08x\n",
+	       __func__, 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("======== %s(): asserting both DTR and DSR\n", __func__);
+	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("======== %s(): de-asserting DSR\n", __func__);
+	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;
+
+	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("======== %s(): initial status=0x%08x\n",
+	       __func__, 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("======== %s(): asserting both CTS and RTS/RTR\n", __func__);
+	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("======== %s(): de-asserting CTS\n", __func__);
+	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);
+}
+
 int main(int argc, char **argv)
 {
 	test_rx();
@@ -355,5 +558,10 @@
 	test_tx_rx_pull_n(4);
 	test_tx_rx_pull_n(8);
 
+	/* 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
index 9ce3bc8..7922074 100644
--- a/tests/soft_uart/soft_uart_test.ok
+++ b/tests/soft_uart/soft_uart_test.ok
@@ -200,3 +200,48 @@
 01010101011111110101010101111111
 ======== test_tx_rx_pull_n(): feeding 32 bits into the receiver
 suart_rx_cb(flags=00): 55 55 
+======== 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)
+======== 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
+======== test_flow_control_dtr_dsr(): 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 
+======== test_flow_control_dtr_dsr(): 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 
+======== 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
+======== test_flow_control_rts_cts(): 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 
+======== test_flow_control_rts_cts(): 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