tbf: Poll MS on idle DL TBFs

If an MS wants to open a new UL TBF, it can either use (P)RACH or
request one in a Ack/Nack message for a DL TBF (PACCH). When a TBF
becomes idle (LCC queue is empty but the TBF is kept open), there
aren't any Ack/Nack requests that can be used by the MS to ask for an
UL TBF, therefore it has to use the RACH. This leads to many RACH
requests even for a single HTTP transaction, so it takes some time to
retrieve even a simple web page.

This commit modifies the scheduler to regularly send Ack/Nack
requests on idle DL TBFs. It does so by extending the priority based
scheduling algorithm to have 5 priority levels (highest priority
first):

  - Control block is pending
  - High age (100%) threshold reached (-> request Ack/Nack)
  - Data is waiting or there are pending Nacks
  - Low age (200ms) threshold reached (-> request Ack/Nack)
  - Pending Nacks that have been resent already
  - None of the above (-> send DL dummy control block)

The 'age' refers to the time since since the last control block has
been sent on the TBF. This high age threshold is set to
dl-tbf-idle-time or to 50% of T3190 (whichever is smaller), aiming
for at least a poll (and TBF shutdown) after the TBF has expired and
to safely prevent expiry of T3190. So if dl-tbf-idle-time > 200ms,
there will be a poll every 200ms and a final poll after
dl-tbf-idle-time. On high load, the interval between polls can get
higher, but the 'high age' poll should be in place.

This commit implements the scheduling with respect to GSM 44.060,
9.3.1a ("Delayed release of downlink TBF").

Ticket: #556
Sponsored-by: On-Waves ehf
diff --git a/src/gprs_rlcmac_sched.cpp b/src/gprs_rlcmac_sched.cpp
index c8977e4..dedea26 100644
--- a/src/gprs_rlcmac_sched.cpp
+++ b/src/gprs_rlcmac_sched.cpp
@@ -23,6 +23,8 @@
 #include <bts.h>
 #include <tbf.h>
 
+#include "pcu_utils.h"
+
 static uint32_t sched_poll(struct gprs_rlcmac_bts *bts,
 		    uint8_t trx, uint8_t ts, uint32_t fn, uint8_t block_nr,
 		    struct gprs_rlcmac_tbf **poll_tbf,
@@ -168,8 +170,21 @@
 {
 	struct msgb *msg = NULL;
 	struct gprs_rlcmac_dl_tbf *tbf, *prio_tbf = NULL;
-	int prio, max_prio = -1;
+	enum {
+		DL_PRIO_NONE,
+		DL_PRIO_SENT_DATA, /* the data has been sent and not (yet) nacked */
+		DL_PRIO_LOW_AGE,   /* the age has reached the first threshold */
+		DL_PRIO_NEW_DATA,  /* the data has not been sent yet or nacked */
+		DL_PRIO_HIGH_AGE,  /* the age has reached the second threshold */
+		DL_PRIO_CONTROL,   /* a control block needs to be sent */
+	} prio, max_prio = DL_PRIO_NONE;
+
 	uint8_t i, tfi, prio_tfi;
+	int age;
+	const int age_thresh1 = msecs_to_frames(200);
+	const int high_prio_msecs =
+		OSMO_MIN(BTS::TIMER_T3190_MSEC/2, bts->dl_tbf_idle_msec);
+	const int age_thresh2 = msecs_to_frames(high_prio_msecs);
 
 	/* select downlink resource */
 	for (i = 0, tfi = pdch->next_dl_tfi; i < 32;
@@ -190,13 +205,24 @@
 		if (tbf->m_wait_confirm)
 			continue;
 
+		age = tbf->frames_since_last_poll(fn);
+
 		/* compute priority */
-		if (tbf->state_is(GPRS_RLCMAC_FINISHED) &&
-			tbf->m_window.resend_needed() < 0)
-			/* would re-retransmit blocks */
-			prio = 1;
+		if (tbf->is_control_ts(ts) && tbf->need_control_ts())
+			prio = DL_PRIO_CONTROL;
+		else if (tbf->is_control_ts(ts) &&
+			age > age_thresh2 && age_thresh2 > 0)
+			prio = DL_PRIO_HIGH_AGE;
+		else if ((tbf->state_is(GPRS_RLCMAC_FLOW) && tbf->have_data()) ||
+			tbf->m_window.resend_needed() >= 0)
+			prio = DL_PRIO_NEW_DATA;
+		else if (tbf->is_control_ts(ts) &&
+			age > age_thresh1 && tbf->keep_open(fn))
+			prio = DL_PRIO_LOW_AGE;
+		else if (!tbf->m_window.window_empty())
+			prio = DL_PRIO_SENT_DATA;
 		else
-			prio = 2;
+			continue;
 
 		/* get the TBF with the highest priority */
 		if (prio > max_prio) {
@@ -208,7 +234,8 @@
 
 	if (prio_tbf) {
 		LOGP(DRLCMACSCHED, LOGL_DEBUG, "Scheduling data message at "
-			"RTS for DL TFI=%d (TRX=%d, TS=%d)\n", prio_tfi, trx, ts);
+			"RTS for DL TFI=%d (TRX=%d, TS=%d) prio=%d\n",
+			prio_tfi, trx, ts, max_prio);
 		/* next TBF to handle resource is the next one */
 		pdch->next_dl_tfi = (prio_tfi + 1) & 31;
 		/* generate DL data block */