[gprs] NS: improved timer handling for RESET
diff --git a/openbsc/src/gprs_ns.c b/openbsc/src/gprs_ns.c
index 9c198ad..39ca810 100644
--- a/openbsc/src/gprs_ns.c
+++ b/openbsc/src/gprs_ns.c
@@ -109,6 +109,8 @@
 	return NULL;
 }
 
+static void gprs_ns_timer_cb(void *data);
+
 static struct gprs_nsvc *nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci)
 {
 	struct gprs_nsvc *nsvc;
@@ -118,6 +120,9 @@
 	/* before RESET procedure: BLOCKED and DEAD */
 	nsvc->state = NSE_S_BLOCKED;
 	nsvc->nsi = nsi;
+	nsvc->timer.cb = gprs_ns_timer_cb;
+	nsvc->timer.data = nsvc;
+
 	llist_add(&nsvc->list, &nsi->gprs_nsvcs);
 
 	return nsvc;
@@ -133,7 +138,7 @@
 	{ NS_CAUSE_BVCI_UNKNOWN,	"BVCI unknown" },
 	{ NS_CAUSE_SEM_INCORR_PDU,	"Semantically incorrect PDU" },
 	{ NS_CAUSE_PDU_INCOMP_PSTATE,	"PDU not compatible with protocol state" },
-	{ NS_CAUSE_PROTO_ERR_UNSPEC,	"Protocol error }, unspecified" },
+	{ NS_CAUSE_PROTO_ERR_UNSPEC,	"Protocol error, unspecified" },
 	{ NS_CAUSE_INVAL_ESSENT_IE,	"Invalid essential IE" },
 	{ NS_CAUSE_MISSING_ESSENT_IE,	"Missing essential IE" },
 	{ 0, NULL }
@@ -178,10 +183,47 @@
 	return gprs_ns_tx(nsvc, msg);
 }
 
-#define NS_TIMER_ALIVE	3, 0 	/* after 3 seconds without response, we retry */
-#define NS_TIMER_TEST	30, 0	/* every 10 seconds we check if the BTS is still alive */
+static int gprs_ns_tx_reset(struct gprs_nsvc *nsvc)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "GPRS/NS");
+	struct gprs_ns_hdr *nsh;
+	uint8_t cause = NS_CAUSE_OM_INTERVENTION;
+	uint16_t nsvci = htons(nsvc->nsvci);
+	uint16_t nsei = htons(nsvc->nsei);
+
+	if (!msg)
+		return -ENOMEM;
+
+	nsh = (struct gprs_ns_hdr *) msgb_put(msg, sizeof(*nsh));
+	nsh->pdu_type = NS_PDUT_RESET;
+
+	msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+	msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+	msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *) &nsei);
+
+	return gprs_ns_tx(nsvc, msg);
+
+}
+
 #define NS_ALIVE_RETRIES  10	/* after 3 failed retransmit we declare BTS as dead */
 
+static const uint8_t timer_mode_tout[_NSVC_TIMER_NR] = {
+	[NSVC_TIMER_TNS_RESET]	= 60,
+	[NSVC_TIMER_TNS_ALIVE]	= 3,
+	[NSVC_TIMER_TNS_TEST]	= 30,
+};
+
+static void nsvc_start_timer(struct gprs_nsvc *nsvc, enum nsvc_timer_mode mode)
+{
+	nsvc->alive_retries = 0;
+
+	if (bsc_timer_pending(&nsvc->timer))
+		bsc_del_timer(&nsvc->timer);
+
+	nsvc->timer_mode = mode;
+	bsc_schedule_timer(&nsvc->timer, timer_mode_tout[mode], 0);
+}
+
 static void gprs_ns_timer_cb(void *data)
 {
 	struct gprs_nsvc *nsvc = data;
@@ -193,20 +235,26 @@
 		if (nsvc->alive_retries > NS_ALIVE_RETRIES) {
 			/* mark as dead and blocked */
 			nsvc->state = NSE_S_BLOCKED;
-			DEBUGP(DGPRS, "Tns-alive more then %u retries, "
-				" blocking NS-VC\n", NS_ALIVE_RETRIES);
+			DEBUGP(DGPRS, "NSEI=%u Tns-alive expired more then "
+				"%u times, blocking NS-VC\n", nsvc->nsei,
+				NS_ALIVE_RETRIES);
 			/* FIXME: inform higher layers */
 			return;
 		}
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
 		break;
 	case NSVC_TIMER_TNS_TEST:
 		/* Tns-test case: send NS-ALIVE PDU */
 		gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE);
 		/* start Tns-alive timer */
-		nsvc->timer_mode = NSVC_TIMER_TNS_ALIVE;
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
+		break;
+	case NSVC_TIMER_TNS_RESET:
+		/* Chapter 7.3: Re-send the RESET */
+		gprs_ns_tx_reset(nsvc);
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_RESET);
 		break;
 	}
-	bsc_schedule_timer(&nsvc->timer, NS_TIMER_ALIVE);
 }
 
 /* Section 9.2.6 */
@@ -249,6 +297,17 @@
 		return -EINVAL;
 	}
 
+	if (!(nsvc->state & NSE_S_ALIVE)) {
+		LOGP(DGPRS, LOGL_ERROR, "NSEI=%u is not alive, cannot send\n",
+			nsvc->nsei);
+		return -EBUSY;
+	}
+	if (nsvc->state & NSE_S_BLOCKED) {
+		LOGP(DGPRS, LOGL_ERROR, "NSEI=%u is blocked, cannot send\n",
+			nsvc->nsei);
+		return -EBUSY;
+	}
+
 	nsh = (struct gprs_ns_hdr *) msgb_push(msg, sizeof(*nsh) + 3);
 	if (!nsh) {
 		LOGP(DGPRS, LOGL_ERROR, "Not enough headroom for NS header\n");
@@ -333,9 +392,7 @@
 
 	/* mark the NS-VC as blocked and alive */
 	/* start the test procedure */
-	nsvc->timer.cb = gprs_ns_timer_cb;
-	nsvc->timer.data = nsvc;
-	bsc_schedule_timer(&nsvc->timer, NS_TIMER_ALIVE);
+	nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
 
 	return gprs_ns_tx_reset_ack(nsvc);
 }
@@ -375,8 +432,7 @@
 		/* stop Tns-alive */
 		bsc_del_timer(&nsvc->timer);
 		/* start Tns-test */
-		nsvc->timer_mode = NSVC_TIMER_TNS_TEST;
-		bsc_schedule_timer(&nsvc->timer, NS_TIMER_TEST);
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_TEST);
 		break;
 	case NS_PDUT_UNITDATA:
 		/* actual user data */
@@ -392,6 +448,10 @@
 		DEBUGP(DGPRS, "NSEI=%u Rx NS RESET ACK\n", nsvc->nsei);
 		/* mark remote NS-VC as blocked + active */
 		nsvc->remote_state = NSE_S_BLOCKED | NSE_S_ALIVE;
+		if (nsvc->remote_end_is_sgsn) {
+			/* stop RESET timer */
+			bsc_del_timer(&nsvc->timer);
+		}
 		break;
 	case NS_PDUT_UNBLOCK:
 		/* Section 7.2: unblocking procedure */
@@ -561,9 +621,12 @@
 	nsvc->remote_end_is_sgsn = 1;
 
 	/* Initiate a RESET procedure */
-	if (gprs_ns_tx_simple(nsvc, NS_PDUT_RESET) < 0)
-		return NULL;
-	/* FIXME: should we run a timer and re-transmit the reset request? */
+	if (gprs_ns_tx_reset(nsvc) < 0) {
+		LOGP(DGPRS, LOGL_ERROR, "NSEI=%u, error resetting NS-VC\n",
+			nsei);
+	}
+	/* run a timer and re-transmit the reset request? */
+	nsvc_start_timer(nsvc, NSVC_TIMER_TNS_RESET);
 
 	return nsvc;
 }