fixeria has uploaded this change for review. ( 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, 341 insertions(+), 12 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/libosmocore refs/changes/72/35172/1
diff --git a/include/osmocom/core/soft_uart.h b/include/osmocom/core/soft_uart.h index 7b7c394..a2435a8 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>
@@ -40,12 +43,31 @@ OSMO_SUART_F_BREAK = (1 << 2), };
-#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_M_NONE, + /*! DTR/DSR flow control: Tx if DSR is active and drop DTR if cannot Rx anymore. */ + OSMO_SUART_FLOW_CTRL_M_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_M_RTS_CTS, +};
/* configuration for a soft-uart */ struct osmo_soft_uart_cfg { @@ -75,8 +97,11 @@ * number of requested bits and the effective UART configuration). */ void (*tx_cb)(void *priv, struct msgb *tx_data);
- /*! modem status line change call-back. gets bitmask of osmo_soft_uart_status */ + /*! modem status line change call-back. gets bitmask 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; @@ -97,5 +122,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 1d9984b..e3e3b0a 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_M_NONE, };
/************************************************************************* @@ -193,13 +195,33 @@ * \param[in] suart soft-UART instance to feed bits into. * \param[in] ubits pointer to the unpacked bits. * \param[in] n_ubits number of unpacked bits to be fed. - * \returns 0 on success; negative on error. */ + * \returns 0 on success; negative on error. + * -EAGAIN indicates that the receiver is either disabled + * or suspended by the flow control (if enabled). */ int osmo_soft_uart_rx_ubits(struct osmo_soft_uart *suart, const ubit_t *ubits, size_t n_ubits) { if (!suart->rx.running) return -EAGAIN; + + switch (suart->cfg.flow_ctrl_mode) { + case OSMO_SUART_FLOW_CTRL_M_DTR_DSR: + /* cannot Rx anymore if DTR is de-asserted */ + if (~suart->status & OSMO_SUART_STATUS_F_DTR) + return -EAGAIN; + break; + case OSMO_SUART_FLOW_CTRL_M_RTS_CTS: + /* cannot Rx anymore if RTS/RTR is de-asserted */ + if (~suart->status & OSMO_SUART_STATUS_F_RTS_RTR) + return -EAGAIN; + break; + case OSMO_SUART_FLOW_CTRL_M_NONE: + default: + break; + } + for (size_t i = 0; i < n_ubits; i++) osmo_uart_rx_bit(suart, ubits[i]); + return 0; }
@@ -276,7 +298,9 @@ * \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 0 on success; negative on error. */ + * \returns 0 on success; negative on error. + * -EAGAIN indicates that the transmitter is either disabled + * or suspended by the flow control (if enabled). */ int osmo_soft_uart_tx_ubits(struct osmo_soft_uart *suart, ubit_t *ubits, size_t n_ubits) { const struct osmo_soft_uart_cfg *cfg = &suart->cfg; @@ -289,6 +313,22 @@ if (!suart->tx.running) return -EAGAIN;
+ switch (suart->cfg.flow_ctrl_mode) { + case OSMO_SUART_FLOW_CTRL_M_DTR_DSR: + /* cannot Tx if DSR is de-asserted */ + if (~suart->status & OSMO_SUART_STATUS_F_DSR) + return -EAGAIN; + break; + case OSMO_SUART_FLOW_CTRL_M_RTS_CTS: + /* cannot Tx if CTS is de-asserted */ + if (~suart->status & OSMO_SUART_STATUS_F_CTS) + return -EAGAIN; + break; + case OSMO_SUART_FLOW_CTRL_M_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) @@ -319,16 +359,55 @@ return 0; }
-/*! 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) +{ + const struct osmo_soft_uart_cfg *cfg = &suart->cfg; + unsigned int status = suart->status; + + if (active) /* assert the given line */ + status |= line; + else /* de-assert the given line */ + status &= ~line; + + if (cfg->status_change_cb != NULL) { + if (suart->status != status) + cfg->status_change_cb(cfg->priv, status); + } + + suart->status = status; +} +
/************************************************************************* * Management / Initialization diff --git a/tests/soft_uart/soft_uart_test.c b/tests/soft_uart/soft_uart_test.c index f1b0b25..3631db7 100644 --- a/tests/soft_uart/soft_uart_test.c +++ b/tests/soft_uart/soft_uart_test.c @@ -16,6 +16,7 @@ * */
+#include <errno.h> #include <stdio.h> #include <stdint.h>
@@ -47,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, @@ -54,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, @@ -343,6 +350,161 @@ 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[32]; + int rc; + + g_tx_cb_cfg.data = (void *)"\x42\x42\x42"; + g_tx_cb_cfg.data_len = 3; + + cfg = suart_test_default_cfg; + cfg.flow_ctrl_mode = OSMO_SUART_FLOW_CTRL_M_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); + + /* a) 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)); + + printf("expecting osmo_soft_uart_tx_ubits() to yeild nothing\n"); + rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], sizeof(tx_buf)); + OSMO_ASSERT(rc == -EAGAIN); + + printf("expecting osmo_soft_uart_rx_ubits() to consume nothing\n"); + rc = osmo_soft_uart_rx_ubits(suart, &tx_buf[0], sizeof(tx_buf)); + OSMO_ASSERT(rc == -EAGAIN); + + /* b) DSR is asserted, expect only the transmitter to work */ + printf("======== %s(): asserting DSR\n", __func__); + osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_DSR, true); + + printf("expecting osmo_soft_uart_tx_ubits() to yeild some data\n"); + rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], sizeof(tx_buf)); + OSMO_ASSERT(rc == 0); + printf("%s\n", osmo_ubit_dump(&tx_buf[0], sizeof(tx_buf))); + + printf("expecting osmo_soft_uart_rx_ubits() to consume nothing\n"); + rc = osmo_soft_uart_rx_ubits(suart, &tx_buf[0], sizeof(tx_buf)); + OSMO_ASSERT(rc == -EAGAIN); + + /* c) both DTR and DSR are asserted, expect both Rx and Tx to work */ + printf("======== %s(): asserting DTR\n", __func__); + osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_DTR, true); + + printf("expecting osmo_soft_uart_tx_ubits() to yeild some data\n"); + rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], sizeof(tx_buf)); + OSMO_ASSERT(rc == 0); + printf("%s\n", osmo_ubit_dump(&tx_buf[0], sizeof(tx_buf))); + + printf("expecting osmo_soft_uart_rx_ubits() to consume some data\n"); + 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[32]; + int rc; + + g_tx_cb_cfg.data = (void *)"\x42\x42\x42"; + g_tx_cb_cfg.data_len = 3; + + cfg = suart_test_default_cfg; + cfg.flow_ctrl_mode = OSMO_SUART_FLOW_CTRL_M_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); + + /* a) 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)); + + printf("expecting osmo_soft_uart_tx_ubits() to yeild nothing\n"); + rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], sizeof(tx_buf)); + OSMO_ASSERT(rc == -EAGAIN); + + printf("expecting osmo_soft_uart_rx_ubits() to consume nothing\n"); + rc = osmo_soft_uart_rx_ubits(suart, &tx_buf[0], sizeof(tx_buf)); + OSMO_ASSERT(rc == -EAGAIN); + + /* b) CTS is asserted, expect only the transmitter to work */ + printf("======== %s(): asserting CTS\n", __func__); + osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_CTS, true); + + printf("expecting osmo_soft_uart_tx_ubits() to yeild some data\n"); + rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], sizeof(tx_buf)); + OSMO_ASSERT(rc == 0); + printf("%s\n", osmo_ubit_dump(&tx_buf[0], sizeof(tx_buf))); + + printf("expecting osmo_soft_uart_rx_ubits() to consume nothing\n"); + rc = osmo_soft_uart_rx_ubits(suart, &tx_buf[0], sizeof(tx_buf)); + OSMO_ASSERT(rc == -EAGAIN); + + /* c) both RTS/RTR and CTS are asserted, expect both Rx and Tx to work */ + printf("======== %s(): asserting RTS/RTR\n", __func__); + osmo_soft_uart_set_status_line(suart, OSMO_SUART_STATUS_F_RTS_RTR, true); + + printf("expecting osmo_soft_uart_tx_ubits() to yeild some data\n"); + rc = osmo_soft_uart_tx_ubits(suart, &tx_buf[0], sizeof(tx_buf)); + OSMO_ASSERT(rc == 0); + printf("%s\n", osmo_ubit_dump(&tx_buf[0], sizeof(tx_buf))); + + printf("expecting osmo_soft_uart_rx_ubits() to consume some data\n"); + 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(); @@ -354,5 +516,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..37c342f 100644 --- a/tests/soft_uart/soft_uart_test.ok +++ b/tests/soft_uart/soft_uart_test.ok @@ -200,3 +200,44 @@ 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 yeild nothing +expecting osmo_soft_uart_rx_ubits() to consume nothing +======== test_flow_control_dtr_dsr(): asserting DSR +suart_status_change_cb(status=0x00000004) +expecting osmo_soft_uart_tx_ubits() to yeild some data +suart_tx_cb(len=3/3): 42 42 42 +00100001010010000101001000010111 +expecting osmo_soft_uart_rx_ubits() to consume nothing +======== test_flow_control_dtr_dsr(): asserting DTR +suart_status_change_cb(status=0x00000005) +expecting osmo_soft_uart_tx_ubits() to yeild some data +suart_tx_cb(len=3/3): 42 42 42 +00100001010010000101001000010111 +expecting osmo_soft_uart_rx_ubits() to consume some data +suart_rx_cb(flags=00): 42 42 42 +======== test_flow_control_rts_cts(): initial status=0x00000000 +expecting osmo_soft_uart_tx_ubits() to yeild nothing +expecting osmo_soft_uart_rx_ubits() to consume nothing +======== test_flow_control_rts_cts(): asserting CTS +suart_status_change_cb(status=0x00000020) +expecting osmo_soft_uart_tx_ubits() to yeild some data +suart_tx_cb(len=3/3): 42 42 42 +00100001010010000101001000010111 +expecting osmo_soft_uart_rx_ubits() to consume nothing +======== test_flow_control_rts_cts(): asserting RTS/RTR +suart_status_change_cb(status=0x00000030) +expecting osmo_soft_uart_tx_ubits() to yeild some data +suart_tx_cb(len=3/3): 42 42 42 +00100001010010000101001000010111 +expecting osmo_soft_uart_rx_ubits() to consume some data +suart_rx_cb(flags=00): 42 42 42