OM2000: Track the Operational Info and MO state
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
index ae448c4..4d427be 100644
--- a/openbsc/include/openbsc/gsm_data.h
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -595,11 +595,19 @@
 		} bs11;
 		struct {
 			struct {
+				struct gsm_nm_state nm_state;
 				struct llist_head conn_groups;
 			} is;
 			struct {
+				struct gsm_nm_state nm_state;
 				struct llist_head conn_groups;
 			} con;
+			struct {
+				struct gsm_nm_state nm_state;
+			} dp;
+			struct {
+				struct gsm_nm_state nm_state;
+			} tf;
 		} rbs2000;
 		struct {
 			unsigned long serno;
diff --git a/openbsc/include/openbsc/signal.h b/openbsc/include/openbsc/signal.h
index a2257db..572c975 100644
--- a/openbsc/include/openbsc/signal.h
+++ b/openbsc/include/openbsc/signal.h
@@ -172,12 +172,18 @@
 	u_int8_t msg_type;	
 };
 
+struct abis_om2k_mo;
+
 struct nm_statechg_signal_data {
 	u_int8_t obj_class;
 	void *obj;
 	struct gsm_nm_state *old_state;
 	struct gsm_nm_state *new_state;
+
+	/* This pointer is vaold for TS 12.21 MO */
 	struct abis_om_obj_inst *obj_inst;
+	/* This pointer is vaold for RBS2000 MO */
+	struct abis_om2k_mo *om2k_mo;
 };
 
 struct nm_nack_signal_data {
diff --git a/openbsc/src/libbsc/abis_nm.c b/openbsc/src/libbsc/abis_nm.c
index 0e7fc8d..788dd4d 100644
--- a/openbsc/src/libbsc/abis_nm.c
+++ b/openbsc/src/libbsc/abis_nm.c
@@ -685,6 +685,8 @@
 	struct gsm_nm_state *nm_state, new_state;
 	struct nm_statechg_signal_data nsd;
 
+	memset(&nsd, 0, sizeof(nsd));
+
 	nsd.obj = objclass2obj(bts, obj_class, obj_inst);
 	if (!nsd.obj)
 		return -EINVAL;
diff --git a/openbsc/src/libbsc/abis_om2000.c b/openbsc/src/libbsc/abis_om2000.c
index e98ac1c..00fc60a 100644
--- a/openbsc/src/libbsc/abis_om2000.c
+++ b/openbsc/src/libbsc/abis_om2000.c
@@ -652,6 +652,149 @@
 	return mo_buf;
 }
 
+/* resolve the gsm_nm_state data structure for a given MO */
+static struct gsm_nm_state *
+mo2nm_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	struct gsm_bts_trx *trx;
+	struct gsm_nm_state *nm_state = NULL;
+
+	switch (mo->class) {
+	case OM2K_MO_CLS_TRXC:
+		trx = gsm_bts_trx_num(bts, mo->assoc_so);
+		if (!trx)
+			return NULL;
+		nm_state = &trx->nm_state;
+		break;
+	case OM2K_MO_CLS_TS:
+		trx = gsm_bts_trx_num(bts, mo->assoc_so);
+		if (!trx)
+			return NULL;
+		if (mo->inst >= ARRAY_SIZE(trx->ts))
+			return NULL;
+		nm_state = &trx->ts[mo->inst].nm_state;
+		break;
+	case OM2K_MO_CLS_TF:
+		nm_state = &bts->rbs2000.tf.nm_state;
+		break;
+	case OM2K_MO_CLS_IS:
+		nm_state = &bts->rbs2000.is.nm_state;
+		break;
+	case OM2K_MO_CLS_CON:
+		nm_state = &bts->rbs2000.con.nm_state;
+		break;
+	case OM2K_MO_CLS_DP:
+		nm_state = &bts->rbs2000.con.nm_state;
+		break;
+	case OM2K_MO_CLS_CF:
+		nm_state = &bts->nm_state;
+		break;
+	case OM2K_MO_CLS_TX:
+		trx = gsm_bts_trx_num(bts, mo->assoc_so);
+		if (!trx)
+			return NULL;
+		break;
+	case OM2K_MO_CLS_RX:
+		trx = gsm_bts_trx_num(bts, mo->assoc_so);
+		if (!trx)
+			return NULL;
+		break;
+	}
+
+	return nm_state;
+}
+
+static void *mo2obj(struct gsm_bts *bts, struct abis_om2k_mo *mo)
+{
+	struct gsm_bts_trx *trx;
+
+	switch (mo->class) {
+	case OM2K_MO_CLS_TX:
+	case OM2K_MO_CLS_RX:
+	case OM2K_MO_CLS_TRXC:
+		return gsm_bts_trx_num(bts, mo->assoc_so);
+	case OM2K_MO_CLS_TS:
+		trx = gsm_bts_trx_num(bts, mo->assoc_so);
+		if (!trx)
+			return NULL;
+		if (mo->inst >= ARRAY_SIZE(trx->ts))
+			return NULL;
+		return &trx->ts[mo->inst];
+	case OM2K_MO_CLS_TF:
+	case OM2K_MO_CLS_IS:
+	case OM2K_MO_CLS_CON:
+	case OM2K_MO_CLS_DP:
+	case OM2K_MO_CLS_CF:
+		return bts;
+	}
+
+	return NULL;
+}
+
+static void update_mo_state(struct gsm_bts *bts, struct abis_om2k_mo *mo,
+			    uint8_t mo_state)
+{
+	struct gsm_nm_state *nm_state = mo2nm_state(bts, mo);
+	struct gsm_nm_state new_state;
+	struct nm_statechg_signal_data nsd;
+
+	if (!nm_state)
+		return;
+
+	new_state = *nm_state;
+	/* NOTICE: 12.21 Availability state values != OM2000 */
+	new_state.availability = mo_state;
+
+	memset(&nsd, 0, sizeof(nsd));
+
+	nsd.obj = mo2obj(bts, mo);
+	nsd.old_state = nm_state;
+	nsd.new_state = &new_state;
+	nsd.om2k_mo = mo;
+
+	dispatch_signal(SS_NM, S_NM_STATECHG_ADM, &nsd);
+
+	nm_state->availability = new_state.availability;
+}
+
+static void update_op_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+			    uint8_t op_state)
+{
+	struct gsm_nm_state *nm_state = mo2nm_state(bts, mo);
+	struct gsm_nm_state new_state;
+
+	if (!nm_state)
+		return;
+
+	new_state = *nm_state;
+	switch (op_state) {
+	case 1:
+		new_state.operational = NM_OPSTATE_ENABLED;
+		break;
+	case 0:
+		new_state.operational = NM_OPSTATE_DISABLED;
+		break;
+	default:
+		new_state.operational = NM_OPSTATE_NULL;
+		break;
+	}
+
+	nm_state->operational = new_state.operational;
+}
+
+static void signal_op_state(struct gsm_bts *bts, struct abis_om2k_mo *mo)
+{
+	struct gsm_nm_state *nm_state = mo2nm_state(bts, mo);
+	struct nm_statechg_signal_data nsd;
+
+	nsd.obj = mo2obj(bts, mo);
+	nsd.old_state = nm_state;
+	nsd.new_state = nm_state;
+	nsd.om2k_mo = mo;
+
+	dispatch_signal(SS_NM, S_NM_STATECHG_OPER, &nsd);
+}
+
 static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg)
 {
 	struct abis_om2k_hdr *o2h;
@@ -805,6 +948,9 @@
 	DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo),
 		get_value_string(om2k_msgcode_vals, OM2K_MSGT_OP_INFO));
 
+	/* we update the state here... and send the signal at ACK */
+	update_op_state(bts, mo, operational);
+
 	return abis_om2k_sendmsg(bts, msg);
 }
 
@@ -1160,7 +1306,11 @@
 {
 	struct abis_om2k_hdr *o2h = msgb_l2(msg);
 
-	/* FIXME: update Operational state in our structures */
+	/* This Acknowledgement does not contain the actual operational state,
+	 * so we signal whatever state we saved when we sent the Op Info
+	 * request */
+
+	signal_op_state(msg->trx->bts, &o2h->mo);
 
 	return 0;
 }
@@ -1269,6 +1419,8 @@
 		get_value_string(om2k_msgcode_vals, msg_type),
 		get_value_string(om2k_mostate_vals, mo_state));
 
+	update_mo_state(bts, &o2h->mo, mo_state);
+
 	return 0;
 }