soft_uart: implement modem status lines and flow control

Change-Id: I26b93ce76f2f6b6fbf017f2684312007db3c6d48
Related: OS#4396
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