LAPD: Add support for RTS based polling and T200

The T200 timer is started when the current frame is polled at
PH-READY-TO-SEND event.

A flag is used to enable this feature. The user of LAPD core must track
frame numbers to check the timeout condition. Then it must call the
external timeout function.

Related: OS#4074
Change-Id: Ib961b5a44911b99b0487641533301749c0286995
diff --git a/TODO-RELEASE b/TODO-RELEASE
index b67161d..1d56d45 100644
--- a/TODO-RELEASE
+++ b/TODO-RELEASE
@@ -9,3 +9,4 @@
 #library	what			description / commit summary line
 core      ADD       osmo_sock_multiaddr_{add,del}_local_addr()
 core      ADD       gsmtap_inst_fd2() core, DEPRECATE gsmtap_inst_fd()
+isdn		ABI change		add states and flags for external T200 handling
diff --git a/include/osmocom/isdn/lapd_core.h b/include/osmocom/isdn/lapd_core.h
index e4e8b46..776d4f4 100644
--- a/include/osmocom/isdn/lapd_core.h
+++ b/include/osmocom/isdn/lapd_core.h
@@ -84,6 +84,16 @@
 	LAPD_STATE_TIMER_RECOV,
 };
 
+/*! lapd_flags */
+#define LAPD_F_RTS		0x0001
+
+/*! LAPD T200 state in RTS mode */
+enum lapd_t200_rts {
+	LAPD_T200_RTS_OFF = 0,
+	LAPD_T200_RTS_PENDING,
+	LAPD_T200_RTS_RUNNING,
+};
+
 /*! LAPD message format (I / S / U) */
 enum lapd_format {
 	LAPD_FORM_UKN = 0,
@@ -133,6 +143,7 @@
 		struct lapd_cr_ent rem2loc;
 	} cr;
 	enum lapd_mode mode; /*!< current mode of link */
+	unsigned int lapd_flags; /*!< \ref lapd_flags to change processing */
 	int use_sabme; /*!< use SABME instead of SABM */
 	int reestablish; /*!< enable reestablish support */
 	int n200, n200_est_rel; /*!< number of retranmissions */
@@ -149,6 +160,7 @@
 	uint8_t peer_busy; /*!< receiver busy on remote side */
 	int t200_sec, t200_usec; /*!< retry timer (default 1 sec) */
 	int t203_sec, t203_usec; /*!< retry timer (default 10 secs) */
+	enum lapd_t200_rts t200_rts; /*!< state of T200 in RTS mode */
 	struct osmo_timer_list t200; /*!< T200 timer */
 	struct osmo_timer_list t203; /*!< T203 timer */
 	uint8_t retrans_ctr; /*!< re-transmission counter */
@@ -169,8 +181,11 @@
 void lapd_dl_set_name(struct lapd_datalink *dl, const char *name);
 void lapd_dl_exit(struct lapd_datalink *dl);
 void lapd_dl_reset(struct lapd_datalink *dl);
+int lapd_dl_set_flags(struct lapd_datalink *dl, unsigned int flags);
 int lapd_set_mode(struct lapd_datalink *dl, enum lapd_mode mode);
 int lapd_ph_data_ind(struct msgb *msg, struct lapd_msg_ctx *lctx);
+int lapd_ph_rts_ind(struct lapd_msg_ctx *lctx);
 int lapd_recv_dlsap(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx);
+int lapd_t200_timeout(struct lapd_datalink *dl);
 
 /*! @} */
diff --git a/src/isdn/lapd_core.c b/src/isdn/lapd_core.c
index 57a0225..34748a4 100644
--- a/src/isdn/lapd_core.c
+++ b/src/isdn/lapd_core.c
@@ -204,11 +204,35 @@
 
 static void lapd_start_t200(struct lapd_datalink *dl)
 {
-	if (osmo_timer_pending(&dl->t200))
-		return;
-	LOGDL(dl, LOGL_INFO, "start T200 (timeout=%d.%06ds)\n",
-	      dl->t200_sec, dl->t200_usec);
-	osmo_timer_schedule(&dl->t200, dl->t200_sec, dl->t200_usec);
+	if ((dl->lapd_flags & LAPD_F_RTS)) {
+		if (dl->t200_rts != LAPD_T200_RTS_OFF)
+			return;
+		LOGDL(dl, LOGL_INFO, "Start T200. (pending until triggered by RTS)\n");
+		dl->t200_rts = LAPD_T200_RTS_PENDING;
+	} else {
+		if (osmo_timer_pending(&dl->t200))
+			return;
+		LOGDL(dl, LOGL_INFO, "Start T200 (timeout=%d.%06ds).\n", dl->t200_sec, dl->t200_usec);
+		osmo_timer_schedule(&dl->t200, dl->t200_sec, dl->t200_usec);
+	}
+}
+
+/*! Handle timeout condition of T200 in RTS mode.
+ * The caller (LAPDm code) implements the T200 timer and must detect timeout condition.
+ * The function gets called by LAPDm code when it detects a timeout of T200.
+ *  \param[in] dl caller-allocated datalink structure */
+int lapd_t200_timeout(struct lapd_datalink *dl)
+{
+	OSMO_ASSERT((dl->lapd_flags & LAPD_F_RTS));
+
+	if (dl->t200_rts != LAPD_T200_RTS_RUNNING)
+		return -EINVAL;
+
+	dl->t200_rts = LAPD_T200_RTS_OFF;
+
+	lapd_t200_cb(dl);
+
+	return 0;
 }
 
 static void lapd_start_t203(struct lapd_datalink *dl)
@@ -221,10 +245,24 @@
 
 static void lapd_stop_t200(struct lapd_datalink *dl)
 {
-	if (!osmo_timer_pending(&dl->t200))
-		return;
+	if ((dl->lapd_flags & LAPD_F_RTS)) {
+		if (dl->t200_rts == LAPD_T200_RTS_OFF)
+			return;
+		dl->t200_rts = LAPD_T200_RTS_OFF;
+	} else {
+		if (!osmo_timer_pending(&dl->t200))
+			return;
+		osmo_timer_del(&dl->t200);
+	}
 	LOGDL(dl, LOGL_INFO, "stop T200\n");
-	osmo_timer_del(&dl->t200);
+}
+
+static bool lapd_is_t200_started(struct lapd_datalink *dl)
+{
+	if ((dl->lapd_flags & LAPD_F_RTS))
+		return (dl->t200_rts != LAPD_T200_RTS_OFF);
+	else
+		return osmo_timer_pending(&dl->t200);
 }
 
 static void lapd_stop_t203(struct lapd_datalink *dl)
@@ -359,6 +397,21 @@
 	lapd_dl_newstate(dl, LAPD_STATE_IDLE);
 }
 
+/*! Set lapd_flags to change behaviour
+ *  \param[in] dl \ref lapd_datalink instance
+ *  \param[in] flags \ref lapd_flags */
+int lapd_dl_set_flags(struct lapd_datalink *dl, unsigned int flags)
+{
+	if (lapd_is_t200_started(dl) && (flags & LAPD_F_RTS) != (dl->lapd_flags & LAPD_F_RTS)) {
+		LOGDL(dl, LOGL_ERROR, "Changing RTS flag not allowed while T200 is running.\n");
+		return -EINVAL;
+	}
+
+	dl->lapd_flags = flags;
+
+	return 0;
+}
+
 /* reset and de-allocate history buffer */
 void lapd_dl_exit(struct lapd_datalink *dl)
 {
@@ -806,11 +859,8 @@
 	/* Stop T203, if running */
 	lapd_stop_t203(dl);
 	/* Start T203, if T200 is not running in MF EST state, if enabled */
-	if (!osmo_timer_pending(&dl->t200)
-	 && (dl->t203_sec || dl->t203_usec)
-	 && (dl->state == LAPD_STATE_MF_EST)) {
+	if (!lapd_is_t200_started(dl) && (dl->t203_sec || dl->t203_usec) && (dl->state == LAPD_STATE_MF_EST))
 		lapd_start_t203(dl);
-	}
 }
 
 /* L1 -> L2 */
@@ -1732,6 +1782,31 @@
 	return rc;
 }
 
+/*! Enqueue next LAPD frame and run pending T200. (Must be called when frame is ready to send.)
+ * The caller (LAPDm code) calls this function before it sends the next frame.
+ * If there is no frame in the TX queue, LAPD will enqueue next I-frame, if possible.
+ * If the T200 is pending, it is changed to running state.
+ *  \param[in] lctx LAPD context
+ *  \param[out] rc set to 1, if timer T200 state changed to running, set to 0, if not. */
+int lapd_ph_rts_ind(struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+
+	/* If there is no pending frame, try to enqueue next I frame. */
+	if (llist_empty(&dl->tx_queue) && (dl->state == LAPD_STATE_MF_EST || dl->state == LAPD_STATE_TIMER_RECOV)) {
+		/* Send an I frame, if there are pending outgoing messages. */
+		lapd_send_i(dl, __LINE__, true);
+	}
+
+	/* Run T200 at RTS, if pending. Tell caller that is has been started. (rc = 1) */
+	if (dl->t200_rts == LAPD_T200_RTS_PENDING) {
+		dl->t200_rts = LAPD_T200_RTS_RUNNING;
+		return 1;
+	}
+
+	return 0;
+}
+
 /* L3 -> L2 */
 
 /* send unit data */
@@ -1861,6 +1936,12 @@
 	if (!rts)
 		LOGDL(dl, LOGL_INFO, "%s() called from line %d\n", __func__, line);
 
+	if ((dl->lapd_flags & LAPD_F_RTS) && !llist_empty(&dl->tx_queue)) {
+		if (!rts)
+			LOGDL(dl, LOGL_INFO, "There is a frame in the TX queue, not checking for sending I frame.\n");
+		return rc;
+	}
+
 	next_frame:
 
 	if (dl->peer_busy) {
@@ -1978,7 +2059,7 @@
 	/* If timer T200 is not running at the time right before transmitting a
 	 * frame, when the PH-READY-TO-SEND primitive is received from the
 	 * physical layer., it shall be set. */
-	if (!osmo_timer_pending(&dl->t200)) {
+	if (!lapd_is_t200_started(dl)) {
 		/* stop Timer T203, if running */
 		lapd_stop_t203(dl);
 		/* start Timer T200 */
@@ -1987,7 +2068,11 @@
 
 	dl->send_ph_data_req(&nctx, msg);
 
-	rc = 0; /* we sent something */
+	/* When using RTS, we send only one frame. */
+	if ((dl->lapd_flags & LAPD_F_RTS))
+		return 0;
+
+	rc = 0; /* We sent an I frame, so sending RR frame is not required. */
 	goto next_frame;
 }
 
diff --git a/src/isdn/libosmoisdn.map b/src/isdn/libosmoisdn.map
index 3269771..c29240b 100644
--- a/src/isdn/libosmoisdn.map
+++ b/src/isdn/libosmoisdn.map
@@ -7,11 +7,14 @@
 lapd_dl_init2;
 lapd_dl_set_name;
 lapd_dl_reset;
+lapd_dl_set_flags;
 lapd_msgb_alloc;
 lapd_ph_data_ind;
+lapd_ph_rts_ind;
 lapd_recv_dlsap;
 lapd_set_mode;
 lapd_state_names;
+lapd_t200_timeout;
 
 osmo_i460_demux_in;
 osmo_i460_mux_enqueue;