laforge has submitted this change. ( https://gerrit.osmocom.org/c/libosmocore/+/35172?usp=email )
Change subject: soft_uart: implement modem status lines and flow control ......................................................................
soft_uart: implement modem status lines and flow control
Change-Id: I26b93ce76f2f6b6fbf017f2684312007db3c6d48 Related: OS#4396 --- M include/osmocom/core/soft_uart.h M src/core/libosmocore.map M src/core/soft_uart.c M tests/soft_uart/soft_uart_test.c M tests/soft_uart/soft_uart_test.ok 5 files changed, 373 insertions(+), 14 deletions(-)
Approvals: jolly: Looks good to me, but someone else must approve Jenkins Builder: Verified laforge: Looks good to me, approved
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