Statefull reset and unblock BVCs and sending flow control messages

The flow control interval can be set via VTY.
diff --git a/src/gprs_bssgp_pcu.cpp b/src/gprs_bssgp_pcu.cpp
index 374baf6..020df61 100644
--- a/src/gprs_bssgp_pcu.cpp
+++ b/src/gprs_bssgp_pcu.cpp
@@ -25,8 +25,13 @@
 void *tall_bsc_ctx;
 struct bssgp_bvc_ctx *bctx = NULL;
 struct gprs_nsvc *nsvc = NULL;
+static int bvc_sig_reset = 0, bvc_reset = 0, bvc_unblocked = 0;
 extern uint16_t spoof_mcc, spoof_mnc;
 
+struct osmo_timer_list bvc_timer;
+
+static void bvc_timeout(void *_priv);
+
 int gprs_bssgp_pcu_rx_dl_ud(struct msgb *msg, struct tlv_parsed *tp)
 {
 	struct bssgp_ud_hdr *budh;
@@ -295,6 +300,11 @@
 			break;
 		case BSSGP_PDUT_BVC_RESET_ACK:
 			LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_BVC_RESET_ACK\n");
+			if (!bvc_sig_reset)
+				bvc_sig_reset = 1;
+			else
+				bvc_reset = 1;
+			bvc_timeout(NULL);
 			break;
 		case BSSGP_PDUT_PAGING_PS:
 			LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_PAGING_PS\n");
@@ -316,6 +326,8 @@
 			break;
 		case BSSGP_PDUT_BVC_UNBLOCK_ACK:
 			LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_BVC_UNBLOCK_ACK\n");
+			bvc_unblocked = 1;
+			bvc_timeout(NULL);
 			break;
 		case BSSGP_PDUT_SGSN_INVOKE_TRACE:
 			LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_SGSN_INVOKE_TRACE\n");
@@ -362,7 +374,9 @@
 	/* look-up or create the BTS context for this BVC */
 	bctx = btsctx_by_bvci_nsei(ns_bvci, msgb_nsei(msg));
 
-	if (!bctx && pdu_type != BSSGP_PDUT_BVC_RESET_ACK)
+	if (!bctx
+	 && pdu_type != BSSGP_PDUT_BVC_RESET_ACK
+	 && pdu_type != BSSGP_PDUT_BVC_UNBLOCK_ACK)
 	{
 		LOGP(DBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU "
 			"type %u for unknown BVCI\n", msgb_nsei(msg), ns_bvci,
@@ -438,14 +452,22 @@
 	case S_NS_UNBLOCK:
 		if (!nsvc_unblocked) {
 			nsvc_unblocked = 1;
-			LOGP(DPCU, LOGL_NOTICE, "NS-VC is unblocked.\n");
-			bssgp_tx_bvc_reset(bctx, bctx->bvci,
-				BSSGP_CAUSE_PROTO_ERR_UNSPEC);
+			LOGP(DPCU, LOGL_NOTICE, "NS-VC %d is unblocked.\n",
+				nsvc);
+			bvc_sig_reset = 0;
+			bvc_reset = 0;
+			bvc_unblocked = 0;
+			bvc_timeout(NULL);
 		}
 		break;
 	case S_NS_BLOCK:
 		if (nsvc_unblocked) {
 			nsvc_unblocked = 0;
+			if (osmo_timer_pending(&bvc_timer))
+				osmo_timer_del(&bvc_timer);
+			bvc_sig_reset = 0;
+			bvc_reset = 0;
+			bvc_unblocked = 0;
 			LOGP(DPCU, LOGL_NOTICE, "NS-VC is blocked.\n");
 		}
 		break;
@@ -454,6 +476,52 @@
 	return 0;
 }
 
+int gprs_bssgp_tx_fc_bvc(void)
+{
+	if (!bctx) {
+		LOGP(DBSSGP, LOGL_ERROR, "No bctx\n");
+		return -EIO;
+	}
+	/* FIXME: use real values */
+	return bssgp_tx_fc_bvc(bctx, 1, 6553500, 819100, 50000, 50000,
+		NULL, NULL);
+//	return bssgp_tx_fc_bvc(bctx, 1, 84000, 25000, 48000, 45000,
+//		NULL, NULL);
+}
+
+static void bvc_timeout(void *_priv)
+{
+	struct gprs_rlcmac_bts *bts = gprs_rlcmac_bts;
+
+	if (!bvc_sig_reset) {
+		LOGP(DBSSGP, LOGL_INFO, "Sending reset on BVCI 0\n");
+		bssgp_tx_bvc_reset(bctx, 0, BSSGP_CAUSE_OML_INTERV);
+		osmo_timer_schedule(&bvc_timer, 1, 0);
+		return;
+	}
+
+	if (!bvc_reset) {
+		LOGP(DBSSGP, LOGL_INFO, "Sending reset on BVCI %d\n",
+			bctx->bvci);
+		bssgp_tx_bvc_reset(bctx, bctx->bvci, BSSGP_CAUSE_OML_INTERV);
+		osmo_timer_schedule(&bvc_timer, 1, 0);
+		return;
+	}
+
+	if (!bvc_unblocked) {
+		LOGP(DBSSGP, LOGL_INFO, "Sending unblock on BVCI %d\n",
+			bctx->bvci);
+		bssgp_tx_bvc_unblock(bctx);
+		osmo_timer_schedule(&bvc_timer, 1, 0);
+		return;
+	}
+
+	LOGP(DBSSGP, LOGL_DEBUG, "Sending flow control info on BVCI %d\n",
+		bctx->bvci);
+	gprs_bssgp_tx_fc_bvc();
+	osmo_timer_schedule(&bvc_timer, bts->fc_interval, 0);
+}
+
 /* create BSSGP/NS layer instances */
 int gprs_bssgp_create(uint32_t sgsn_ip, uint16_t sgsn_port, uint16_t nsei,
 	uint16_t nsvci, uint16_t bvci, uint16_t mcc, uint16_t mnc, uint16_t lac,
@@ -501,6 +569,9 @@
 
 //	bssgp_tx_bvc_reset(bctx, bctx->bvci, BSSGP_CAUSE_PROTO_ERR_UNSPEC);
 
+	bvc_timer.cb = bvc_timeout;
+
+
 	return 0;
 }
 
@@ -509,6 +580,9 @@
 	if (!bssgp_nsi)
 		return;
 
+	if (osmo_timer_pending(&bvc_timer))
+		osmo_timer_del(&bvc_timer);
+
 	osmo_signal_unregister_handler(SS_L_NS, nsvc_signal_cb, NULL);
 
 	nsvc = NULL;
diff --git a/src/gprs_rlcmac.h b/src/gprs_rlcmac.h
index a5ac775..e7a68a4 100644
--- a/src/gprs_rlcmac.h
+++ b/src/gprs_rlcmac.h
@@ -62,6 +62,7 @@
 };
 
 struct gprs_rlcmac_bts {
+	uint8_t fc_interval;
 	uint8_t cs1;
 	uint8_t cs2;
 	uint8_t cs3;
diff --git a/src/pcu_main.cpp b/src/pcu_main.cpp
index 2d7e8ff..2392152 100644
--- a/src/pcu_main.cpp
+++ b/src/pcu_main.cpp
@@ -142,6 +142,7 @@
 	if (!gprs_rlcmac_bts)
 		return -ENOMEM;
 	gprs_rlcmac_bts->initial_cs = 1;
+	bts->fc_interval = 1;
 	bts->initial_cs = 1;
 	bts->cs1 = 1;
 	bts->t3142 = 20;
diff --git a/src/pcu_vty.c b/src/pcu_vty.c
index 39a1b72..8d5b47b 100644
--- a/src/pcu_vty.c
+++ b/src/pcu_vty.c
@@ -79,6 +79,8 @@
 	struct gprs_rlcmac_bts *bts = gprs_rlcmac_bts;
 
 	vty_out(vty, "pcu%s", VTY_NEWLINE);
+	vty_out(vty, " flow-control-interval %d%s", bts->fc_interval,
+		VTY_NEWLINE);
 	if (bts->force_cs)
 		vty_out(vty, " cs %d%s", bts->initial_cs, VTY_NEWLINE);
 	if (bts->force_llc_lifetime == 0xffff)
@@ -106,6 +108,19 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_pcu_fc_interval,
+      cfg_pcu_fc_interval_cmd,
+      "flow-control-interval <1..10>",
+      "Interval between sending subsequent Flow Control PDUs\n"
+      "Tiem in seconds\n")
+{
+	struct gprs_rlcmac_bts *bts = gprs_rlcmac_bts;
+
+	bts->fc_interval = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
 DEFUN(cfg_pcu_cs,
       cfg_pcu_cs_cmd,
       "cs <1-4>",
@@ -242,6 +257,7 @@
 	install_node(&pcu_node, config_write_pcu);
 	install_element(CONFIG_NODE, &cfg_pcu_cmd);
 	install_default(PCU_NODE);
+	install_element(PCU_NODE, &cfg_pcu_no_two_phase_cmd);
 	install_element(PCU_NODE, &cfg_pcu_cs_cmd);
 	install_element(PCU_NODE, &cfg_pcu_no_cs_cmd);
 	install_element(PCU_NODE, &cfg_pcu_queue_lifetime_cmd);
@@ -249,7 +265,7 @@
 	install_element(PCU_NODE, &cfg_pcu_no_queue_lifetime_cmd);
 	install_element(PCU_NODE, &cfg_pcu_alloc_cmd);
 	install_element(PCU_NODE, &cfg_pcu_two_phase_cmd);
-	install_element(PCU_NODE, &cfg_pcu_no_two_phase_cmd);
+	install_element(PCU_NODE, &cfg_pcu_fc_interval_cmd);
 	install_element(PCU_NODE, &ournode_end_cmd);
 
 	return 0;