implement nanoBTS frequency error test

This helps us to detect the frequency error of BS-11 if it is located
next to the nanoBTS 900.

If 'ipaccess-config -l' is called, it will produce a report like
<0020> ipaccess-config.c:85 TEST REPORT: test_no=0x42 test_res=0
<0020> ipaccess-config.c:108 ==> ARFCN  220, Frequency Error     22
<0020> ipaccess-config.c:108 ==> ARFCN    1, Frequency Error    -37
<0020> ipaccess-config.c:108 ==> ARFCN   10, Frequency Error      0
<0020> ipaccess-config.c:108 ==> ARFCN   20, Frequency Error     11
<0020> ipaccess-config.c:108 ==> ARFCN   53, Frequency Error      5
<0020> ipaccess-config.c:108 ==> ARFCN   63, Frequency Error     -4
<0020> ipaccess-config.c:108 ==> ARFCN   84, Frequency Error     11
<0020> ipaccess-config.c:108 ==> ARFCN  101, Frequency Error      0
<0020> ipaccess-config.c:108 ==> ARFCN  123, Frequency Error    -52

where in this case the ARFCN 123 is the BS-11 with a frequency error
larger than all the other (regular) BTS in the vicinity.
diff --git a/openbsc/include/openbsc/abis_nm.h b/openbsc/include/openbsc/abis_nm.h
index 3dc5531..d8cd9fa 100644
--- a/openbsc/include/openbsc/abis_nm.h
+++ b/openbsc/include/openbsc/abis_nm.h
@@ -681,6 +681,11 @@
 int abis_nm_conn_mdrop_link(struct gsm_bts *bts, u_int8_t e1_port0, u_int8_t ts0,
 			    u_int8_t e1_port1, u_int8_t ts1);
 
+int abis_nm_perform_test(struct gsm_bts *bts, u_int8_t obj_class,
+			 u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+			 u_int8_t test_nr, u_int8_t auton_report,
+			 u_int8_t *phys_config, u_int16_t phys_config_len);
+
 /* Siemens / BS-11 specific */
 int abis_nm_bs11_reset_resource(struct gsm_bts *bts);
 int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin);
diff --git a/openbsc/include/openbsc/signal.h b/openbsc/include/openbsc/signal.h
index 2ce812d..bdccd39 100644
--- a/openbsc/include/openbsc/signal.h
+++ b/openbsc/include/openbsc/signal.h
@@ -57,6 +57,7 @@
 	S_NM_FAIL_REP,		/* GSM 12.21 failure event report */
 	S_NM_NACK,		/* GSM 12.21 various NM_MT_*_NACK happened */
 	S_NM_IPACC_NACK,	/* GSM 12.21 nanoBTS extensions NM_MT_IPACC_*_*_NACK happened */
+	S_NM_TEST_REP,		/* GSM 12.21 Test Report */
 };
 
 /* SS_LCHAN signals */
diff --git a/openbsc/src/abis_nm.c b/openbsc/src/abis_nm.c
index 0eb8657..31f1df0 100644
--- a/openbsc/src/abis_nm.c
+++ b/openbsc/src/abis_nm.c
@@ -801,6 +801,10 @@
 		rx_fail_evt_rep(mb);
 		dispatch_signal(SS_NM, S_NM_FAIL_REP, mb);
 		break;
+	case NM_MT_TEST_REP:
+		DEBUGPC(DNM, "Test Report\n");
+		dispatch_signal(SS_NM, S_NM_TEST_REP, mb);
+		break;
 	default:
 		DEBUGPC(DNM, "reporting NM MT 0x%02x\n", mt);
 		break;
@@ -1790,6 +1794,33 @@
 	return abis_nm_sendmsg(bts, msg);
 }
 
+/* Chapter 8.7.1 */
+int abis_nm_perform_test(struct gsm_bts *bts, u_int8_t obj_class,
+			 u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+			 u_int8_t test_nr, u_int8_t auton_report,
+			 u_int8_t *phys_config, u_int16_t phys_config_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	int len = 4; /* 2 TV attributes */
+
+	DEBUGP(DNM, "PEFORM TEST\n");
+	
+	if (phys_config_len)
+		len += 3 + phys_config_len;
+	
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_PERF_TEST,
+			obj_class, bts_nr, trx_nr, ts_nr);
+	msgb_tv_put(msg, NM_ATT_TEST_NO, test_nr);
+	msgb_tv_put(msg, NM_ATT_AUTON_REPORT, auton_report);
+	if (phys_config_len)
+		msgb_tl16v_put(msg, NM_ATT_PHYS_CONF, phys_config_len,
+				phys_config);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
 int abis_nm_event_reports(struct gsm_bts *bts, int on)
 {
 	if (on == 0)
diff --git a/openbsc/src/ipaccess-config.c b/openbsc/src/ipaccess-config.c
index 5ccbaa7..a7fb9c4 100644
--- a/openbsc/src/ipaccess-config.c
+++ b/openbsc/src/ipaccess-config.c
@@ -38,9 +38,11 @@
 #include <openbsc/e1_input.h>
 #include <openbsc/abis_nm.h>
 #include <openbsc/signal.h>
+#include <openbsc/debug.h>
 
 static struct gsm_network *gsmnet;
 
+static int net_listen;
 static int restart;
 static char *prim_oml_ip;
 static char *unit_id;
@@ -66,12 +68,61 @@
 	return 0;
 }
 
+struct ipacc_ferr_elem {
+	int16_t freq_err;
+	u_int8_t freq_qual;
+	u_int8_t arfcn;
+} __attribute__((packed));
+
+static int test_rep(void *_msg)
+{
+	struct msgb *msg = _msg;
+	struct abis_om_fom_hdr *foh = msgb_l3(msg);
+	u_int16_t test_rep_len, ferr_list_len;
+	struct ipacc_ferr_elem *ife;
+	int i;
+
+	DEBUGP(DNM, "TEST REPORT: ");
+
+	if (foh->data[0] != NM_ATT_TEST_NO ||
+	    foh->data[2] != NM_ATT_TEST_REPORT)
+		return -EINVAL;
+
+	DEBUGPC(DNM, "test_no=0x%02x ", foh->data[1]);
+	/* data[2] == NM_ATT_TEST_REPORT */
+	/* data[3..4]: test_rep_len */
+	test_rep_len = ntohs(*(u_int16_t *) &foh->data[3]);
+	/* data[5]: ip.access test result */
+	DEBUGPC(DNM, "test_res=%u\n", foh->data[5]);
+
+	/* data[6]: ip.access nested IE. 3 == freq_err_list */
+	switch (foh->data[6]) {
+	case 3:
+		/* data[7..8]: length of ferr_list */
+		ferr_list_len = ntohs(*(u_int16_t *) &foh->data[7]);
+
+		/* data[9...]: frequency error list elements */
+		for (i = 0; i < ferr_list_len; i+= sizeof(*ife)) {
+			ife = (struct ipacc_ferr_elem *) (foh->data + 9 + i);
+			DEBUGP(DNM, "==> ARFCN %4u, Frequency Error %6hd\n",
+			ife->arfcn, ntohs(ife->freq_err));
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
 static int nm_sig_cb(unsigned int subsys, unsigned int signal,
 		     void *handler_data, void *signal_data)
 {
 	switch (signal) {
 	case S_NM_IPACC_NACK:
 		return ipacc_msg_nack((int)signal_data);
+	case S_NM_TEST_REP:
+		return test_rep(signal_data);
 	default:
 		break;
 	}
@@ -141,6 +192,9 @@
 		printf("restarting BTS\n");
 		abis_nm_ipaccess_restart(bts);
 	}
+
+	if (net_listen) {
+	}
 }
 
 void input_event(int event, enum e1inp_sign_type type, struct gsm_bts_trx *trx)
@@ -170,6 +224,16 @@
 int nm_state_event(enum nm_evt evt, u_int8_t obj_class, void *obj,
 		   struct gsm_nm_state *old_state, struct gsm_nm_state *new_state)
 {
+	if (evt == EVT_STATECHG_OPER &&
+	    obj_class == NM_OC_RADIO_CARRIER &&
+	    new_state->availability == 3 &&
+	    net_listen) {
+		struct gsm_bts_trx *trx = obj;
+		u_int8_t phys_config[] = { 0x02, 0x0a, 0x00, 0x01, 0x02 };
+		abis_nm_perform_test(trx->bts, 2, 0, 0, 0xff,
+				     NM_IPACC_TESTNO_FREQ_SYNC, 1,
+				     phys_config, sizeof(phys_config));
+	}
 	return 0;
 }
 
@@ -183,7 +247,8 @@
 	printf("  -u --unit-id UNIT_ID\n");
 	printf("  -o --oml-ip ip\n");
 	printf("  -r --restart\n");
-	printf("  -n flags/mask Set NVRAM attributes.\n");
+	printf("  -n flags/mask\tSet NVRAM attributes.\n");
+	printf("  -l --listen\tPerform Frequency Error test\n");
 	printf("  -h --help this text\n");
 }
 
@@ -205,9 +270,10 @@
 			{ "oml-ip", 1, 0, 'o' },
 			{ "restart", 0, 0, 'r' },
 			{ "help", 0, 0, 'h' },
+			{ "listen", 0, 0, 'l' },
 		};
 
-		c = getopt_long(argc, argv, "u:o:rn:h", long_options,
+		c = getopt_long(argc, argv, "u:o:rn:lh", long_options,
 				&option_index);
 
 		if (c == -1)
@@ -232,6 +298,9 @@
 			ul = strtoul(slash+1, NULL, 16);
 			nv_mask = ul & 0xffff;
 			break;
+		case 'l':
+			net_listen = 1;
+			break;
 		case 'h':
 			print_usage();
 			print_help();