LAPDm: Add support for RTS based polling

The lower layer must set the 'POLLING_ONLY' flag and provide frame
number when polling a frame. If T200 is pending, it is started with a
timeout frame number in advance to given frame number.

The lower layer must call lapdm_t200_fn() after a frame has been
received or if a frame has not been received. Also it must be called
after a TCH frame has been received. LAPDm uses this to check the T200
timeout condition.

A new function is used to set the frame number based timeout values.

Related: OS#4074
Change-Id: I6ebe83f829d7751ea9de1d90eb478c7a628db64c
diff --git a/TODO-RELEASE b/TODO-RELEASE
index 1d56d45..226450b 100644
--- a/TODO-RELEASE
+++ b/TODO-RELEASE
@@ -10,3 +10,4 @@
 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
+gsm		ABI change		add T200 timer states to lapdm_datalink
diff --git a/include/osmocom/gsm/lapdm.h b/include/osmocom/gsm/lapdm.h
index 1a39fca..db66680 100644
--- a/include/osmocom/gsm/lapdm.h
+++ b/include/osmocom/gsm/lapdm.h
@@ -33,6 +33,9 @@
 	struct lapdm_msg_ctx mctx; /*!< context of established connection */
 
 	struct lapdm_entity *entity; /*!< LAPDm entity we are part of */
+
+	uint32_t t200_fn;	/*!< T200 timer in frames */
+	uint32_t t200_timeout;	/*!< T200 timeout frame number */
 };
 
 /*! LAPDm datalink SAPIs */
@@ -119,6 +122,11 @@
 void lapdm_entity_set_flags(struct lapdm_entity *le, unsigned int flags);
 void lapdm_channel_set_flags(struct lapdm_channel *lc, unsigned int flags);
 
+void lapdm_entity_set_t200_fn(struct lapdm_entity *le, const uint32_t *t200_fn);
+void lapdm_channel_set_t200_fn(struct lapdm_channel *lc, const uint32_t *t200_fn_dcch, const uint32_t *t200_fn_acch);
+
 int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp);
+int lapdm_phsap_dequeue_prim_fn(struct lapdm_entity *le, struct osmo_phsap_prim *pp, uint32_t fn);
+void lapdm_t200_fn(struct lapdm_entity *le, uint32_t fn);
 
 /*! @} */
diff --git a/src/gsm/lapdm.c b/src/gsm/lapdm.c
index 43f5662..d609317 100644
--- a/src/gsm/lapdm.c
+++ b/src/gsm/lapdm.c
@@ -27,6 +27,7 @@
 
 #include <stdio.h>
 #include <stdint.h>
+#include <inttypes.h>
 #include <string.h>
 #include <errno.h>
 
@@ -196,9 +197,9 @@
 		char name[256];
 		if (name_pfx) {
 			snprintf(name, sizeof(name), "%s[%s]", name_pfx, i == 0 ? "0" : "3");
-			lapdm_dl_init(&le->datalink[i], le, t200_ms[i], n200, name);
+			lapdm_dl_init(&le->datalink[i], le, (t200_ms) ? t200_ms[i] : 0, n200, name);
 		} else
-			lapdm_dl_init(&le->datalink[i], le, t200_ms[i], n200, NULL);
+			lapdm_dl_init(&le->datalink[i], le, (t200_ms) ? t200_ms[i] : 0, n200, NULL);
 	}
 
 	lapdm_entity_set_mode(le, mode);
@@ -358,6 +359,18 @@
 
 	/* if there is a pending message, queue it */
 	if (le->tx_pending || le->flags & LAPDM_ENT_F_POLLING_ONLY) {
+		struct msgb *old_msg;
+
+		/* In 'Polling only' mode there can be only one message. */
+		if (le->flags & LAPDM_ENT_F_POLLING_ONLY) {
+			/* Overwrite existing message by removing it first. */
+			if ((old_msg = msgb_dequeue(&dl->dl.tx_queue))) {
+				msgb_free(old_msg);
+				/* Reset V(S) to V(A), because there is no outstanding message now. */
+				dl->dl.v_send = dl->dl.v_ack;
+			}
+		}
+
 		*msgb_push(msg, 1) = pad;
 		*msgb_push(msg, 1) = link_id;
 		*msgb_push(msg, 1) = chan_nr;
@@ -377,21 +390,55 @@
 	return le->l1_prim_cb(&pp.oph, le->l1_ctx);
 }
 
+/* Get transmit frame from queue, if any. In polling mode, indicate RTS to LAPD and start T200, if pending. */
+static struct msgb *tx_dequeue_msgb(struct lapdm_datalink *dl, uint32_t fn)
+{
+	struct msgb *msg;
+
+	/* Call RTS function of LAPD, to poll next frame. */
+	if (dl->entity->flags & LAPDM_ENT_F_POLLING_ONLY) {
+		struct lapd_msg_ctx lctx;
+		int rc;
+
+		/* Poll next frame. */
+		lctx.dl = &dl->dl;
+		rc = lapd_ph_rts_ind(&lctx);
+
+		/* If T200 has been started, calculate timeout FN. */
+		if (rc == 1) {
+			/* Set T200 in advance. */
+			dl->t200_timeout = fn;
+			ADD_MODULO(dl->t200_timeout, dl->t200_fn, GSM_MAX_FN);
+
+			LOGDL(&dl->dl, LOGL_INFO,
+			      "T200 running from FN %"PRIu32" to FN %"PRIu32" (%"PRIu32" frames).\n",
+			      fn, dl->t200_timeout, dl->t200_fn);
+		}
+	}
+
+	/* If there is no frame from LAPD, send UI frame, if any. */
+	msg = msgb_dequeue(&dl->dl.tx_queue);
+	if (msg)
+		LOGDL(&dl->dl, LOGL_INFO, "Sending frame from TX queue. (FN %"PRIu32")\n", fn);
+	return msg;
+}
+
 /* Dequeue a Downlink message for DCCH (dedicated channel) */
-static struct msgb *tx_dequeue_dcch_msgb(struct lapdm_entity *le)
+static struct msgb *tx_dequeue_dcch_msgb(struct lapdm_entity *le, uint32_t fn)
 {
 	struct msgb *msg;
 
 	/* SAPI=0 always has higher priority than SAPI=3 */
-	msg = msgb_dequeue(&le->datalink[DL_SAPI0].dl.tx_queue);
-	if (msg == NULL) /* no SAPI=0 messages, dequeue SAPI=3 (if any) */
-		msg = msgb_dequeue(&le->datalink[DL_SAPI3].dl.tx_queue);
+	msg = tx_dequeue_msgb(&le->datalink[DL_SAPI0], fn);
+	if (msg == NULL) { /* no SAPI=0 messages, dequeue SAPI=3 (if any) */
+		msg = tx_dequeue_msgb(&le->datalink[DL_SAPI3], fn);
+	}
 
 	return msg;
 }
 
 /* Dequeue a Downlink message for ACCH (associated channel) */
-static struct msgb *tx_dequeue_acch_msgb(struct lapdm_entity *le)
+static struct msgb *tx_dequeue_acch_msgb(struct lapdm_entity *le, uint32_t fn)
 {
 	struct lapdm_datalink *dl;
 	int last = le->last_tx_dequeue;
@@ -403,7 +450,7 @@
 		/* next */
 		i = (i + 1) % n;
 		dl = &le->datalink[i];
-		if ((msg = msgb_dequeue(&dl->dl.tx_queue)))
+		if ((msg = tx_dequeue_msgb(dl, fn)))
 			break;
 	} while (i != last);
 
@@ -417,7 +464,7 @@
 
 /*! dequeue a msg that's pending transmission via L1 and wrap it into
  * a osmo_phsap_prim */
-int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp)
+int lapdm_phsap_dequeue_prim_fn(struct lapdm_entity *le, struct osmo_phsap_prim *pp, uint32_t fn)
 {
 	struct msgb *msg;
 	uint8_t pad;
@@ -425,9 +472,9 @@
 	/* Dequeue depending on channel type: DCCH or ACCH.
 	 * See 3GPP TS 44.005, section 4.2.2 "Priority". */
 	if (le == &le->lapdm_ch->lapdm_dcch)
-		msg = tx_dequeue_dcch_msgb(le);
+		msg = tx_dequeue_dcch_msgb(le, fn);
 	else
-		msg = tx_dequeue_acch_msgb(le);
+		msg = tx_dequeue_acch_msgb(le, fn);
 	if (!msg)
 		return -ENODEV;
 
@@ -449,6 +496,57 @@
 	return 0;
 }
 
+static void lapdm_t200_fn_dl(struct lapdm_datalink *dl, uint32_t fn)
+{
+	uint32_t diff;
+
+	OSMO_ASSERT((dl->dl.lapd_flags & LAPD_F_RTS));
+
+	/* If T200 is running, check if it has fired. */
+	if (dl->dl.t200_rts != LAPD_T200_RTS_RUNNING)
+		return;
+
+	/* Calculate how many frames fn is behind t200_timeout.
+	 * If it is negative (>= GSM_MAX_FN / 2), we have not reached t200_timeout yet.
+	 * If it is 0 or positive, we reached it or we are a bit too late, which is not a problem.
+	 */
+	diff = fn;
+	ADD_MODULO(diff, GSM_MAX_FN - dl->t200_timeout, GSM_MAX_FN);
+	if (diff >= GSM_MAX_FN / 2)
+		return;
+
+	LOGDL(&dl->dl, LOGL_INFO, "T200 timeout at FN %"PRIu32", detected at FN %"PRIu32".\n", dl->t200_timeout, fn);
+
+	lapd_t200_timeout(&dl->dl);
+}
+
+/*! Get receive frame number from L1. It is used to check the T200 timeout.
+ * This function is used if LAPD is in RTS mode only. (Applies if the LAPDM_ENT_F_POLLING_ONLY flag is set.)
+ * This function must be called for every valid or invalid data frame received.
+ * The frame number fn must be the frame number of the first burst of a data frame.
+ * This function must be called after the frame is delivered to layer 2.
+ * In case of TCH, this this function must be called for every speech frame received, meaning that there was no valid
+ * data frame. */
+void lapdm_t200_fn(struct lapdm_entity *le, uint32_t fn)
+{
+	unsigned int i;
+
+	if (!(le->flags & LAPDM_ENT_F_POLLING_ONLY)) {
+		LOGP(DLLAPD, LOGL_ERROR, "Function call not allowed on timer based T200.\n");
+		return;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(le->datalink); i++)
+		lapdm_t200_fn_dl(&le->datalink[i], fn);
+}
+
+/*! dequeue a msg that's pending transmission via L1 and wrap it into
+ * a osmo_phsap_prim */
+int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp)
+{
+	return lapdm_phsap_dequeue_prim_fn(le, pp, 0);
+}
+
 /* get next frame from the tx queue. because the ms has multiple datalinks,
  * each datalink's queue is read round-robin.
  */
@@ -853,6 +951,7 @@
 			return -EINVAL;
 		}
 		/* send to LAPD */
+		LOGDL(lctx.dl, LOGL_DEBUG, "Frame received at FN %"PRIu32".\n", fn);
 		rc = lapd_ph_data_ind(msg, &lctx);
 		break;
 	case LAPDm_FMT_Bter:
@@ -1494,7 +1593,20 @@
 /*! Set the flags of a LAPDm entity */
 void lapdm_entity_set_flags(struct lapdm_entity *le, unsigned int flags)
 {
+	unsigned int dl_flags = 0;
+	struct lapdm_datalink *dl;
+	int i;
+
 	le->flags = flags;
+
+	/* Set flags at LAPD. */
+	if (le->flags & LAPDM_ENT_F_POLLING_ONLY)
+		dl_flags |= LAPD_F_RTS;
+
+	for (i = 0; i < ARRAY_SIZE(le->datalink); i++) {
+		dl = &le->datalink[i];
+		lapd_dl_set_flags(&dl->dl, dl_flags);
+	}
 }
 
 /*! Set the flags of all LAPDm entities in a LAPDm channel */
@@ -1504,4 +1616,25 @@
 	lapdm_entity_set_flags(&lc->lapdm_acch, flags);
 }
 
+/*! Set the T200 FN timer of a LAPDm entity
+ *  \param[in] \ref lapdm_entity
+ *  \param[in] t200_fn Array of T200 timeout in frame numbers for all SAPIs (0, 3) */
+void lapdm_entity_set_t200_fn(struct lapdm_entity *le, const uint32_t *t200_fn)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(le->datalink); i++)
+		le->datalink[i].t200_fn = t200_fn[i];
+}
+
+/*! Set the T200 FN timer of all LAPDm entities in a LAPDm channel
+ *  \param[in] \ref lapdm_channel
+ *  \param[in] t200_fn_dcch Array of T200 timeout in frame numbers for all SAPIs (0, 3) on SDCCH/FACCH
+ *  \param[in] t200_fn_acch Array of T200 timeout in frame numbers for all SAPIs (0, 3) on SACCH */
+void lapdm_channel_set_t200_fn(struct lapdm_channel *lc, const uint32_t *t200_fn_dcch, const uint32_t *t200_fn_acch)
+{
+	lapdm_entity_set_t200_fn(&lc->lapdm_dcch, t200_fn_dcch);
+	lapdm_entity_set_t200_fn(&lc->lapdm_acch, t200_fn_acch);
+}
+
 /*! @} */
diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map
index 056d320..6bb3b4b 100644
--- a/src/gsm/libosmogsm.map
+++ b/src/gsm/libosmogsm.map
@@ -587,7 +587,11 @@
 lapdm_entity_reset;
 lapdm_entity_set_flags;
 lapdm_entity_set_mode;
+lapdm_entity_set_t200_fn;
+lapdm_channel_set_t200_fn;
 lapdm_phsap_dequeue_prim;
+lapdm_phsap_dequeue_prim_fn;
+lapdm_t200_fn;
 lapdm_phsap_up;
 lapdm_rslms_recvmsg;