diff mbox

[3/3] Add basic SI2quater support

Message ID 1460729086-8961-3-git-send-email-msuraev@sysmocom.de
State Not Applicable
Headers show

Commit Message

Max April 15, 2016, 2:04 p.m. UTC
From: Max <msuraev@sysmocom.de>

* support for sending arbitrary static SI2quater.
* vty interface for neightbor EARFCNs specific to SI2quater.
* dynamic generation of SI2quater messages.
* unit test for SI2quater messages.

Fixes: OS#1630
---
 openbsc/include/openbsc/gsm_data_shared.h |   5 +
 openbsc/include/openbsc/rest_octets.h     |   5 +
 openbsc/src/libbsc/bsc_init.c             |   6 +-
 openbsc/src/libbsc/bsc_vty.c              |  71 +++++++++++
 openbsc/src/libbsc/rest_octets.c          | 200 ++++++++++++++++++++++++++++++
 openbsc/src/libbsc/system_information.c   |  23 ++++
 openbsc/src/libcommon/gsm_data.c          |   5 +
 openbsc/tests/gsm0408/Makefile.am         |   3 +-
 openbsc/tests/gsm0408/gsm0408_test.c      |  69 +++++++++++
 openbsc/tests/gsm0408/gsm0408_test.ok     |   8 ++
 10 files changed, 391 insertions(+), 4 deletions(-)

Comments

Holger Freyther April 16, 2016, 8:09 p.m. UTC | #1
> On 15 Apr 2016, at 10:04, msuraev@sysmocom.de wrote:
> 
> +	install_element(BTS_NODE, &cfg_bts_si2quater_neigh_add_cmd);
> +	install_element(BTS_NODE, &cfg_bts_si2quater_neigh_del_cmd);

<command id='si2quater neighbor-list del earfcn &lt;1900-2200&gt;'>
        <param name='&lt;1900-2200&gt;' doc='(null)' />

Documentation error (missing docs): 
<command id='si2quater neighbor-list del earfcn &lt;1900-2200&gt;'>
        <param name='&lt;1900-2200&gt;' doc='(null)' />

please fix.
diff mbox

Patch

diff --git a/openbsc/include/openbsc/gsm_data_shared.h b/openbsc/include/openbsc/gsm_data_shared.h
index 000207d..cbc0c77 100644
--- a/openbsc/include/openbsc/gsm_data_shared.h
+++ b/openbsc/include/openbsc/gsm_data_shared.h
@@ -105,6 +105,7 @@  struct gsm_abis_mo {
 #define A38_XOR_MAX_KEY_LEN	16
 #define A38_COMP128_KEY_LEN	16
 #define RSL_ENC_ALG_A5(x)	(x+1)
+#define MAX_EARFCN_LIST 512
 
 /* is the data link established? who established it? */
 #define LCHAN_SAPI_UNUSED	0
@@ -715,12 +716,16 @@  struct gsm_bts {
 		struct bitvec neigh_list;
 		struct bitvec cell_alloc;
 		struct bitvec si5_neigh_list;
+		struct bitvec si2quater_na_list;
+		struct osmo_earfcn_si2q si2quater_neigh_list;
 		struct {
 			/* bitmask large enough for all possible ARFCN's */
 			uint8_t neigh_list[1024/8];
 			uint8_t cell_alloc[1024/8];
 			/* If the user wants a different neighbor list in SI5 than in SI2 */
 			uint8_t si5_neigh_list[1024/8];
+			uint8_t meas_bw_list[MAX_EARFCN_LIST];
+			uint16_t earfcn_list[MAX_EARFCN_LIST];
 		} data;
 	} si_common;
 
diff --git a/openbsc/include/openbsc/rest_octets.h b/openbsc/include/openbsc/rest_octets.h
index 9560b14..fd5ec6a 100644
--- a/openbsc/include/openbsc/rest_octets.h
+++ b/openbsc/include/openbsc/rest_octets.h
@@ -1,10 +1,15 @@ 
 #ifndef _REST_OCTETS_H
 #define _REST_OCTETS_H
 
+#include <stdbool.h>
 #include <openbsc/gsm_04_08.h>
+#include <osmocom/gsm/sysinfo.h>
 
 /* generate SI1 rest octets */
 int rest_octets_si1(uint8_t *data, uint8_t *nch_pos, int is1800_net);
+int rest_octets_si2quater(uint8_t *data,
+			  const struct osmo_earfcn_si2q *e, bool uarfcn,
+			  bool earfcn);
 
 struct gsm48_si_selection_params {
 	uint16_t penalty_time:5,
diff --git a/openbsc/src/libbsc/bsc_init.c b/openbsc/src/libbsc/bsc_init.c
index fd8dd66..fea6562 100644
--- a/openbsc/src/libbsc/bsc_init.c
+++ b/openbsc/src/libbsc/bsc_init.c
@@ -192,9 +192,9 @@  int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx)
 
 	return 0;
 err_out:
-	LOGP(DRR, LOGL_ERROR, "Cannot generate SI%s for BTS %u, most likely "
-		"a problem with neighbor cell list generation\n",
-		get_value_string(osmo_sitype_strs, i), bts->nr);
+	LOGP(DRR, LOGL_ERROR, "Cannot generate SI%s for BTS %u: error <%s>,"
+	     "most likely a problem with neighbor cell list generation\n",
+	     get_value_string(osmo_sitype_strs, i), bts->nr, strerror(-rc));
 	return rc;
 }
 
diff --git a/openbsc/src/libbsc/bsc_vty.c b/openbsc/src/libbsc/bsc_vty.c
index 29f2501..9634508 100644
--- a/openbsc/src/libbsc/bsc_vty.c
+++ b/openbsc/src/libbsc/bsc_vty.c
@@ -692,6 +692,21 @@  static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
 		}
 	}
 
+	for (i = 0; i < MAX_EARFCN_LIST; i++) {
+		if (bts->si_common.si2quater_neigh_list.arfcn[i] !=
+		    OSMO_EARFCN_INVALID) {
+			vty_out(vty, "  si2quater neighbor-list add earfcn %u threshold %u",
+				bts->si_common.si2quater_neigh_list.arfcn[i],
+				bts->si_common.si2quater_neigh_list.thresh_hi);
+			if (bts->si_common.si2quater_neigh_list.meas_bw[i] !=
+			    OSMO_EARFCN_MEAS_INVALID)
+				vty_out(vty, " %u",
+					bts->si_common.si2quater_neigh_list.meas_bw[i]);
+
+			vty_out(vty, "%s", VTY_NEWLINE);
+		}
+	}
+
 	vty_out(vty, "  codec-support fr");
 	if (bts->codec.hr)
 		vty_out(vty, " hr");
@@ -2743,6 +2758,60 @@  DEFUN(cfg_bts_neigh, cfg_bts_neigh_cmd,
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_bts_si2quater_neigh_add, cfg_bts_si2quater_neigh_add_cmd,
+      "si2quater neighbor-list add earfcn <1900-2200> threshold <0-1000> "
+      "[<0-255>]", "SI2quater Neighbor List\n"
+      "SI2quater Neighbor List\n" "Add to manual SI2quater neighbor list\n"
+      "EARFCN of neighbor\n" "EARFCN of neighbor\n" "threshold high bits\n"
+      "threshold high bits\n" "measurement bandwidth\n")
+{
+	struct gsm_bts *bts = vty->index;
+	struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+	uint16_t arfcn = atoi(argv[0]);
+	uint8_t meas = OSMO_EARFCN_MEAS_INVALID, thresh = atoi(argv[1]);
+	int r;
+
+	if (3 == argc)
+		meas = atoi(argv[2]);
+
+	r = osmo_earfcn_add(e, arfcn, meas);
+
+	if (r < 0) {
+		vty_out(vty, "Unable to add arfcn %u: %s%s", arfcn, strerror(r),
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (e->thresh_hi && thresh != e->thresh_hi)
+		vty_out(vty, "Warning: multiple thresholds are not supported, "
+			"overriding previous threshold %u%s",
+			e->thresh_hi, VTY_NEWLINE);
+
+	e->thresh_hi = thresh;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_si2quater_neigh_del, cfg_bts_si2quater_neigh_del_cmd,
+	"si2quater neighbor-list del earfcn <1900-2200>",
+	"SI2quater Neighbor List\n"
+	"SI2quater Neighbor List\n"
+	"Delete from SI2quater manual neighbor list\n"
+	"EARFCN of neighbor\n")
+{
+	struct gsm_bts *bts = vty->index;
+	struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+	uint16_t arfcn = atoi(argv[1]);
+	int r = osmo_earfcn_del(e, arfcn);
+	if (r < 0) {
+		vty_out(vty, "Unable to delete arfcn %u: %s%s", arfcn,
+			strerror(r), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
 DEFUN(cfg_bts_si5_neigh, cfg_bts_si5_neigh_cmd,
 	"si5 neighbor-list (add|del) arfcn <0-1023>",
 	"SI5 Neighbor List\n"
@@ -3873,6 +3942,8 @@  int bsc_vty_init(const struct log_info *cat)
 	install_element(BTS_NODE, &cfg_bts_neigh_mode_cmd);
 	install_element(BTS_NODE, &cfg_bts_neigh_cmd);
 	install_element(BTS_NODE, &cfg_bts_si5_neigh_cmd);
+	install_element(BTS_NODE, &cfg_bts_si2quater_neigh_add_cmd);
+	install_element(BTS_NODE, &cfg_bts_si2quater_neigh_del_cmd);
 	install_element(BTS_NODE, &cfg_bts_excl_rf_lock_cmd);
 	install_element(BTS_NODE, &cfg_bts_no_excl_rf_lock_cmd);
 	install_element(BTS_NODE, &cfg_bts_force_comb_si_cmd);
diff --git a/openbsc/src/libbsc/rest_octets.c b/openbsc/src/libbsc/rest_octets.c
index fa35f21..b0f8728 100644
--- a/openbsc/src/libbsc/rest_octets.c
+++ b/openbsc/src/libbsc/rest_octets.c
@@ -24,10 +24,15 @@ 
 #include <string.h>
 #include <stdlib.h>
 #include <errno.h>
+#include <stdbool.h>
 
+#include <openbsc/debug.h>
 #include <openbsc/gsm_data.h>
 #include <osmocom/core/bitvec.h>
 #include <openbsc/rest_octets.h>
+#include <openbsc/arfcn_range_encode.h>
+
+#define SI2Q_MAX_LEN 160
 
 /* generate SI1 rest octets */
 int rest_octets_si1(uint8_t *data, uint8_t *nch_pos, int is1800_net)
@@ -53,6 +58,201 @@  int rest_octets_si1(uint8_t *data, uint8_t *nch_pos, int is1800_net)
 	return bv.data_len;
 }
 
+/* Append Repeated E-UTRAN Neighbour Cell to bitvec:
+ * see 3GPP TS 44.018 Table 10.5.2.33b.1
+ */
+static inline void append_eutran_neib_cell(struct bitvec *bv,
+					    const struct osmo_earfcn_si2q *e)
+{
+	unsigned i;
+	for (i = 0; i < e->length; i++) {
+		if (e->arfcn[i] != OSMO_EARFCN_INVALID) {
+			bitvec_set_bit(bv, 1); /* EARFCN: */
+			bitvec_set_uint(bv, e->arfcn[i], 16);
+
+			if (OSMO_EARFCN_MEAS_INVALID == e->meas_bw[i])
+				bitvec_set_bit(bv, 0);
+			else {
+				/* Measurement Bandwidth: 9.1.54 */
+				bitvec_set_bit(bv, 1);
+				bitvec_set_uint(bv, e->meas_bw[i], 3);
+			}
+		}
+	}
+
+	/* stop bit - end of EARFCN + Measurement Bandwidth sequence */
+	bitvec_set_bit(bv, 0);
+
+	if (e->prio_valid) {
+		/* E-UTRAN_PRIORITY: 3GPP TS 45.008*/
+		bitvec_set_bit(bv, 1);
+		bitvec_set_uint(bv, e->prio, 3);
+	} else
+		bitvec_set_bit(bv, 0);
+
+	/* THRESH_E-UTRAN_high */
+	bitvec_set_uint(bv, e->thresh_hi, 5);
+
+	if (e->thresh_lo_valid) {
+		/* THRESH_E-UTRAN_low: */
+		bitvec_set_bit(bv, 1);
+		bitvec_set_uint(bv, e->thresh_lo, 5);
+	} else
+		bitvec_set_bit(bv, 0);
+
+	if (e->qrxlm_valid) {
+		/* E-UTRAN_QRXLEVMIN: */
+		bitvec_set_bit(bv, 1);
+		bitvec_set_uint(bv, e->qrxlm, 5);
+	} else
+		bitvec_set_bit(bv, 0);
+}
+
+static inline int append_earfcn_size(const struct osmo_earfcn_si2q *e)
+{
+	if (!e)
+		return -EFAULT;
+	/* account for all the constant bits */
+	return 25 + osmo_earfcn_bit_size(e);
+}
+
+static inline void append_earfcn(struct bitvec *bv,
+				const struct osmo_earfcn_si2q *e)
+{
+	/* Additions in Rel-5: */
+	bitvec_set_bit(bv, H);
+	/* No 3G Additional Measurement Param. Descr. */
+	bitvec_set_bit(bv, 0);
+	/* No 3G ADDITIONAL MEASUREMENT Param. Descr. 2 */
+	bitvec_set_bit(bv, 0);
+	/* Additions in Rel-6: */
+	bitvec_set_bit(bv, H);
+	/* 3G_CCN_ACTIVE */
+	bitvec_set_bit(bv, 0);
+	/* Additions in Rel-7: */
+	bitvec_set_bit(bv, H);
+	/* No 700_REPORTING_OFFSET */
+	bitvec_set_bit(bv, 0);
+	/* No 810_REPORTING_OFFSET */
+	bitvec_set_bit(bv, 0);
+	/* Additions in Rel-8: */
+	bitvec_set_bit(bv, H);
+
+	/* Priority and E-UTRAN Parameters Description */
+	bitvec_set_bit(bv, 1);
+
+	/* No Serving Cell Priority Parameters Descr. */
+	bitvec_set_bit(bv, 0);
+	/* No 3G Priority Parameters Description */
+	bitvec_set_bit(bv, 0);
+	/* E-UTRAN Parameters Description */
+	bitvec_set_bit(bv, 1);
+
+	/* E-UTRAN_CCN_ACTIVE */
+	bitvec_set_bit(bv, 0);
+	/* E-UTRAN_Start: 9.1.54 */
+	bitvec_set_bit(bv, 1);
+	/* E-UTRAN_Stop: 9.1.54 */
+	bitvec_set_bit(bv, 1);
+
+	/* No E-UTRAN Measurement Parameters Descr. */
+	bitvec_set_bit(bv, 0);
+	/* No GPRS E-UTRAN Measurement Param. Descr. */
+	bitvec_set_bit(bv, 0);
+
+	/* Note: each of next 3 "repeated" structures might be repeated any
+	   (0, 1, 2...) times - we only support 1 and 0 */
+
+	/* Repeated E-UTRAN Neighbour Cells */
+	bitvec_set_bit(bv, 1);
+
+	/* Note: we don't support different EARFCN arrays each with different
+	   priority, threshold etc. */
+	append_eutran_neib_cell(bv, e);
+
+	/* stop bit - end of Repeated E-UTRAN Neighbour Cells sequence: */
+	bitvec_set_bit(bv, 0);
+
+	/* Note: following 2 repeated structs are not supported ATM */
+	/* stop bit - end of Repeated E-UTRAN Not Allowed Cells sequence: */
+	bitvec_set_bit(bv, 0);
+	/* stop bit - end of Repeated E-UTRAN PCID to TA mapping sequence: */
+	bitvec_set_bit(bv, 0);
+
+	/* Priority and E-UTRAN Parameters Description ends here */
+	/* No 3G CSG Description */
+	bitvec_set_bit(bv, 0);
+	/* No E-UTRAN CSG Description */
+	bitvec_set_bit(bv, 0);
+	/* No Additions in Rel-9: */
+	bitvec_set_bit(bv, L);
+}
+
+/* generate SI2quater rest octets: 3GPP TS 44.018 ยง 10.5.2.33b */
+int rest_octets_si2quater(uint8_t *data, const struct osmo_earfcn_si2q *e,
+			  bool uarfcn, bool earfcn)
+{
+	int rc;
+	struct bitvec bv;
+	bv.data = data;
+	bv.data_len = 20;
+	bitvec_zero(&bv);
+
+	/* BA_IND */
+	bitvec_set_bit(&bv, 1);
+	/* 3G_BA_IND */
+	bitvec_set_bit(&bv, 1);
+	/* MP_CHANGE_MARK */
+	bitvec_set_bit(&bv, 0);
+
+	/* we do not support multiple si2quater messages at the moment: */
+	/* SI2quater_INDEX */
+	bitvec_set_uint(&bv, 0, 4);
+	/* SI2quater_COUNT */
+	bitvec_set_uint(&bv, 0, 4);
+
+	/* No Measurement_Parameters Description */
+	bitvec_set_bit(&bv, 0);
+	/* No GPRS_Real Time Difference Description */
+	bitvec_set_bit(&bv, 0);
+	/* No GPRS_BSIC Description */
+	bitvec_set_bit(&bv, 0);
+	/* No GPRS_REPORT PRIORITY Description */
+	bitvec_set_bit(&bv, 0);
+	/* No GPRS_MEASUREMENT_Parameters Description */
+	bitvec_set_bit(&bv, 0);
+	/* No NC Measurement Parameters */
+	bitvec_set_bit(&bv, 0);
+	/* No extension (length) */
+	bitvec_set_bit(&bv, 0);
+
+	if (uarfcn) {
+
+	} else { /* No 3G Neighbour Cell Description */
+		bitvec_set_bit(&bv, 0);
+	}
+
+	/* No 3G Measurement Parameters Description */
+	bitvec_set_bit(&bv, 0);
+	/* No GPRS_3G_MEASUREMENT Parameters Descr. */
+	bitvec_set_bit(&bv, 0);
+
+	if (earfcn) {
+		rc = append_earfcn_size(e);
+		if (rc < 0)
+			return rc;
+		if (rc  + bv.cur_bit > SI2Q_MAX_LEN)
+			return -ENOMEM;
+		append_earfcn(&bv, e);
+	} else {
+		/* No Additions in Rel-5: */
+		bitvec_set_bit(&bv, L);
+	}
+
+	bitvec_spare_padding(&bv, (bv.data_len * 8) - 1);
+	return bv.data_len;
+}
+
 /* Append selection parameters to bitvec */
 static void append_selection_params(struct bitvec *bv,
 				    const struct gsm48_si_selection_params *sp)
diff --git a/openbsc/src/libbsc/system_information.c b/openbsc/src/libbsc/system_information.c
index 5490c83..43a492a 100644
--- a/openbsc/src/libbsc/system_information.c
+++ b/openbsc/src/libbsc/system_information.c
@@ -494,6 +494,28 @@  static int generate_si2ter(uint8_t *output, struct gsm_bts *bts)
 	return sizeof(*si2t);
 }
 
+static int generate_si2quater(uint8_t *output, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_2quater *si2q =
+		(struct gsm48_system_information_type_2quater *) output;
+
+	memset(si2q, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si2q->header.l2_plen = GSM48_LEN2PLEN(22);
+	si2q->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si2q->header.skip_indicator = 0;
+	si2q->header.system_information = GSM48_MT_RR_SYSINFO_2quater;
+
+	rc = rest_octets_si2quater(si2q->rest_octets,
+				   &bts->si_common.si2quater_neigh_list, false,
+				   true);
+	if (rc < 0)
+		return rc;
+
+	return sizeof(*si2q) + rc;
+}
+
 static struct gsm48_si_ro_info si_info = {
 	.selection_params = {
 		.present = 0,
@@ -831,6 +853,7 @@  static const gen_si_fn_t gen_si_fn[_MAX_SYSINFO_TYPE] = {
 	[SYSINFO_TYPE_2] = &generate_si2,
 	[SYSINFO_TYPE_2bis] = &generate_si2bis,
 	[SYSINFO_TYPE_2ter] = &generate_si2ter,
+	[SYSINFO_TYPE_2quater] = &generate_si2quater,
 	[SYSINFO_TYPE_3] = &generate_si3,
 	[SYSINFO_TYPE_4] = &generate_si4,
 	[SYSINFO_TYPE_5] = &generate_si5,
diff --git a/openbsc/src/libcommon/gsm_data.c b/openbsc/src/libcommon/gsm_data.c
index 16035ed..242c014 100644
--- a/openbsc/src/libcommon/gsm_data.c
+++ b/openbsc/src/libcommon/gsm_data.c
@@ -315,6 +315,11 @@  struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net, enum gsm_bts_typ
 	bts->neigh_list_manual_mode = 0;
 	bts->si_common.cell_sel_par.cell_resel_hyst = 2; /* 4 dB */
 	bts->si_common.cell_sel_par.rxlev_acc_min = 0;
+	bts->si_common.si2quater_neigh_list.arfcn = bts->si_common.data.earfcn_list;
+	bts->si_common.si2quater_neigh_list.meas_bw = bts->si_common.data.meas_bw_list;
+	bts->si_common.si2quater_neigh_list.length = MAX_EARFCN_LIST;
+	bts->si_common.si2quater_neigh_list.thresh_hi = 0;
+	osmo_earfcn_init(&bts->si_common.si2quater_neigh_list);
 	bts->si_common.neigh_list.data = bts->si_common.data.neigh_list;
 	bts->si_common.neigh_list.data_len =
 				sizeof(bts->si_common.data.neigh_list);
diff --git a/openbsc/tests/gsm0408/Makefile.am b/openbsc/tests/gsm0408/Makefile.am
index 1c29ece..ee04102 100644
--- a/openbsc/tests/gsm0408/Makefile.am
+++ b/openbsc/tests/gsm0408/Makefile.am
@@ -7,6 +7,7 @@  EXTRA_DIST = gsm0408_test.ok
 gsm0408_test_SOURCES = gsm0408_test.c
 gsm0408_test_LDADD =	$(top_builddir)/src/libbsc/libbsc.a \
 			$(top_builddir)/src/libmsc/libmsc.a \
+			$(top_builddir)/src/libtrau/libtrau.a \
 			$(top_builddir)/src/libbsc/libbsc.a \
 			$(top_builddir)/src/libcommon/libcommon.a \
-			$(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) -ldbi
+			$(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOABIS_LIBS) -ldbi
diff --git a/openbsc/tests/gsm0408/gsm0408_test.c b/openbsc/tests/gsm0408/gsm0408_test.c
index 781ef61..d6abce6 100644
--- a/openbsc/tests/gsm0408/gsm0408_test.c
+++ b/openbsc/tests/gsm0408/gsm0408_test.c
@@ -29,7 +29,11 @@ 
 #include <openbsc/gsm_subscriber.h>
 #include <openbsc/debug.h>
 #include <openbsc/arfcn_range_encode.h>
+#include <openbsc/system_information.h>
+#include <openbsc/abis_rsl.h>
+
 #include <osmocom/core/application.h>
+#include <osmocom/gsm/sysinfo.h>
 
 #define COMPARE(result, op, value) \
     if (!((result) op (value))) {\
@@ -79,6 +83,70 @@  static void test_location_area_identifier(void)
     COMPARE(lai48.lac, ==, htons(0x000f));
 }
 
+static inline void add_arfcn_b(struct osmo_earfcn_si2q *e, uint16_t earfcn,
+			       uint8_t bw)
+{
+	int r = osmo_earfcn_add(e, earfcn, bw);
+	if (r)
+		printf("failed to add EARFCN %u: %s\n", earfcn, strerror(r));
+	else
+		printf("added EARFCN %u - ", earfcn);
+}
+
+static inline void gen(struct gsm_bts *bts)
+{
+	int r = gsm_generate_si(bts, SYSINFO_TYPE_2quater);
+	if (r > 0)
+		printf("generated SI2quater: [%d] %s\n", r,
+		       osmo_hexdump(bts->si_buf[SYSINFO_TYPE_2quater], r));
+	else
+		printf("failed to generate SI2quater: %s\n", strerror(-r));
+}
+
+static inline void test_si2q(void)
+{
+	struct gsm_bts *bts;
+	struct gsm_network *network = gsm_network_init(1, 1, NULL);
+	printf("Testing SYSINFO_TYPE_2quater generation:\n");
+
+	if (!network)
+		exit(1);
+	bts = gsm_bts_alloc(network);
+
+	bts->si_common.si2quater_neigh_list.arfcn =
+		bts->si_common.data.earfcn_list;
+	bts->si_common.si2quater_neigh_list.meas_bw =
+		bts->si_common.data.meas_bw_list;
+	bts->si_common.si2quater_neigh_list.length = MAX_EARFCN_LIST;
+	bts->si_common.si2quater_neigh_list.thresh_hi = 5;
+
+	osmo_earfcn_init(&bts->si_common.si2quater_neigh_list);
+
+	add_arfcn_b(&bts->si_common.si2quater_neigh_list, 1917, 1);
+	gen(bts);
+
+	add_arfcn_b(&bts->si_common.si2quater_neigh_list, 1932,
+		    OSMO_EARFCN_MEAS_INVALID);
+	gen(bts);
+
+	add_arfcn_b(&bts->si_common.si2quater_neigh_list, 1937, 2);
+	gen(bts);
+
+	add_arfcn_b(&bts->si_common.si2quater_neigh_list, 1945,
+		    OSMO_EARFCN_MEAS_INVALID);
+	gen(bts);
+
+	add_arfcn_b(&bts->si_common.si2quater_neigh_list, 1965,
+		    OSMO_EARFCN_MEAS_INVALID);
+	gen(bts);
+
+	add_arfcn_b(&bts->si_common.si2quater_neigh_list, 1967, 4);
+	gen(bts);
+
+	add_arfcn_b(&bts->si_common.si2quater_neigh_list, 1982, 3);
+	gen(bts);
+}
+
 static void test_mi_functionality(void)
 {
 	const char *imsi_odd  = "987654321098763";
@@ -486,6 +554,7 @@  int main(int argc, char **argv)
 	test_range_encoding();
 	test_gsm411_rp_ref_wrap();
 
+	test_si2q();
 	printf("Done.\n");
 	return EXIT_SUCCESS;
 }
diff --git a/openbsc/tests/gsm0408/gsm0408_test.ok b/openbsc/tests/gsm0408/gsm0408_test.ok
index 058563a..59319bf 100644
--- a/openbsc/tests/gsm0408/gsm0408_test.ok
+++ b/openbsc/tests/gsm0408/gsm0408_test.ok
@@ -62,4 +62,12 @@  testing RP-Reference wrap
 Allocated reference: 255
 Allocated reference: 0
 Allocated reference: 1
+Testing SYSINFO_TYPE_2quater generation:
+added EARFCN 1917 - generated SI2quater: [23] 59 06 07 c0 00 04 86 59 83 be c8 50 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+added EARFCN 1932 - generated SI2quater: [23] 59 06 07 c0 00 04 86 59 83 be cc 1e 30 14 03 2b 2b 2b 2b 2b 2b 2b 2b 
+added EARFCN 1937 - generated SI2quater: [23] 59 06 07 c0 00 04 86 59 83 be cc 1e 31 07 91 a0 a0 2b 2b 2b 2b 2b 2b 
+added EARFCN 1945 - generated SI2quater: [23] 59 06 07 c0 00 04 86 59 83 be cc 1e 31 07 91 a8 3c c8 28 0b 2b 2b 2b 
+added EARFCN 1965 - generated SI2quater: [23] 59 06 07 c0 00 04 86 59 83 be cc 1e 31 07 91 a8 3c ca 0f 5a 0a 03 2b 
+added EARFCN 1967 - failed to generate SI2quater: Cannot allocate memory
+added EARFCN 1982 - failed to generate SI2quater: Cannot allocate memory
 Done.