Change in osmo-pcu[master]: WIP: Introduce NACC support

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

pespin gerrit-no-reply at lists.osmocom.org
Wed Jan 20 14:01:13 UTC 2021


pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-pcu/+/22338 )


Change subject: WIP: Introduce NACC support
......................................................................

WIP: Introduce NACC support

Change-Id: Ife6e4a086d58d49676d12b6984f63079ec472e79
---
M configure.ac
M src/Makefile.am
M src/bts.cpp
M src/bts.h
M src/encoding.cpp
M src/encoding.h
M src/gprs_bssgp_pcu.c
M src/gprs_debug.cpp
M src/gprs_debug.h
M src/gprs_ms.c
M src/gprs_ms.h
M src/gprs_pcu.c
M src/gprs_pcu.h
M src/gprs_rlcmac_sched.cpp
A src/nacc_fsm.c
A src/nacc_fsm.h
M src/pcu_vty.c
M src/pdch.cpp
M src/pdch.h
M src/tbf.cpp
M src/tbf.h
M tests/Makefile.am
22 files changed, 826 insertions(+), 4 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/osmo-pcu refs/changes/38/22338/1

diff --git a/configure.ac b/configure.ac
index 2e99a15..11de328 100644
--- a/configure.ac
+++ b/configure.ac
@@ -84,6 +84,7 @@
 dnl checks for libraries
 PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.4.0)
 PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.4.0)
+PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.4.0)
 PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.4.0)
 PKG_CHECK_MODULES(LIBOSMOGB, libosmogb >= 1.4.0)
 
diff --git a/src/Makefile.am b/src/Makefile.am
index de924a6..2228aee 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -19,7 +19,7 @@
 #
 
 AUTOMAKE_OPTIONS = subdir-objects
-AM_CPPFLAGS = -I$(top_srcdir)/include $(STD_DEFINES_AND_INCLUDES) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGB_CFLAGS) $(LIBOSMOGSM_CFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir)/include $(STD_DEFINES_AND_INCLUDES) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGB_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOGSM_CFLAGS)
 
 if ENABLE_SYSMODSP
 AM_CPPFLAGS += -DENABLE_DIRECT_PHY
@@ -54,6 +54,7 @@
 	pcu_vty.c \
 	pcu_vty_functions.cpp \
 	mslot_class.c \
+	nacc_fsm.c \
 	tbf.cpp \
 	tbf_ul.cpp \
 	tbf_dl.cpp \
@@ -89,6 +90,7 @@
 	pcu_vty.h \
 	pcu_vty_functions.h \
 	mslot_class.h \
+	nacc_fsm.h \
 	tbf.h \
 	tbf_ul.h \
 	tbf_dl.h \
@@ -141,6 +143,7 @@
 	libgprs.la \
 	$(LIBOSMOGB_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOGSM_LIBS) \
 	$(COMMON_LA)
 endif
@@ -189,6 +192,7 @@
 	libgprs.la \
 	$(LIBOSMOGB_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOGSM_LIBS) \
 	$(COMMON_LA)
 
diff --git a/src/bts.cpp b/src/bts.cpp
index b5bb3a2..eb4bdd9 100644
--- a/src/bts.cpp
+++ b/src/bts.cpp
@@ -140,6 +140,9 @@
 	{ "pkt:ul_assignment",		"Packet UL Assignment "},
 	{ "pkt:access_reject",          "Packet Access Reject "},
 	{ "pkt:dl_assignment",		"Packet DL Assignment "},
+	{ "pkt:cell_chg_notification",	"Packet Cell Change Notification"},
+	{ "pkt:cell_chg_continue",	"Packet Cell Change Continue"},
+	{ "pkt:neigh_cell_data",	"Packet Neighbour Cell Data"},
 	{ "ul:control",			"UL control Block     "},
 	{ "ul:assignment_poll_timeout",	"UL Assign Timeout    "},
 	{ "ul:assignment_failed",	"UL Assign Failed     "},
diff --git a/src/bts.h b/src/bts.h
index 7f437e3..cf16e66 100644
--- a/src/bts.h
+++ b/src/bts.h
@@ -2,6 +2,7 @@
  *
  * Copyright (C) 2012 Ivan Klyuchnikov
  * Copyright (C) 2013 by Holger Hans Peter Freyther
+ * Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -125,6 +126,9 @@
 	CTR_PKT_UL_ASSIGNMENT,
 	CTR_PKT_ACCESS_REJ,
 	CTR_PKT_DL_ASSIGNMENT,
+	CTR_PKT_CELL_CHG_NOTIFICATION,
+	CTR_PKT_CELL_CHG_CONTINUE,
+	CTR_PKT_NEIGH_CELL_DATA,
 	CTR_RLC_RECV_CONTROL,
 	CTR_PUA_POLL_TIMEDOUT,
 	CTR_PUA_POLL_FAILED,
diff --git a/src/encoding.cpp b/src/encoding.cpp
index f605ca2..24e1d68 100644
--- a/src/encoding.cpp
+++ b/src/encoding.cpp
@@ -1731,3 +1731,56 @@
 	bitvec_write_field(dest, &wp, 5, 8);  //  WAIT_INDICATION value
 	bitvec_write_field(dest, &wp, 0, 1);  //  WAIT_INDICATION size in seconds
 }
+
+void write_packet_neighbour_cell_data(RlcMacDownlink_t *block,
+		bool tfi_is_dl, uint8_t tfi, uint8_t container_id,
+		uint8_t container_idx, PNCDContainer_t *container)
+{
+
+	block->PAYLOAD_TYPE = 0x1;  // RLC/MAC control block that does not include the optional octets of the RLC/MAC control header
+	block->RRBP         = 0;  // 0: N+13
+	block->SP           = 0; // RRBP field is valid
+	block->USF          = 0x0;  // Uplink state flag
+
+	block->u.Packet_Neighbour_Cell_Data.MESSAGE_TYPE = MT_PACKET_NEIGHBOUR_CELL_DATA;
+	block->u.Packet_Neighbour_Cell_Data.PAGE_MODE    = 0x0;  // Normal Paging
+
+	block->u.Packet_Neighbour_Cell_Data.Global_TFI.UnionType    = tfi_is_dl; // 0=UPLINK TFI, 1=DL TFI
+	if (tfi_is_dl) {
+		block->u.Packet_Neighbour_Cell_Data.Global_TFI.u.DOWNLINK_TFI = tfi;
+	} else {
+		block->u.Packet_Neighbour_Cell_Data.Global_TFI.u.UPLINK_TFI = tfi;
+	}
+	block->u.Packet_Neighbour_Cell_Data.CONTAINER_ID = container_id;
+	block->u.Packet_Neighbour_Cell_Data.spare = 0;
+	block->u.Packet_Neighbour_Cell_Data.CONTAINER_INDEX = container_idx;
+	block->u.Packet_Neighbour_Cell_Data.Container = *container;
+}
+
+void write_packet_cell_change_continue(RlcMacDownlink_t *block,
+		bool tfi_is_dl, uint8_t tfi, bool exist_id,
+		uint16_t arfcn, uint8_t bsic, uint8_t container_id)
+{
+
+	block->PAYLOAD_TYPE = 0x1;  // RLC/MAC control block that does not include the optional octets of the RLC/MAC control header
+	block->RRBP         = 0;  // 0: N+13
+	block->SP           = 0; // RRBP field is valid
+	block->USF          = 0x0;  // Uplink state flag
+
+	block->u.Packet_Cell_Change_Continue.MESSAGE_TYPE = MT_PACKET_CELL_CHANGE_CONTINUE;
+	block->u.Packet_Cell_Change_Continue.PAGE_MODE    = 0x0;  // Normal Paging
+
+	block->u.Packet_Cell_Change_Continue.Global_TFI.UnionType    = tfi_is_dl; // 0=UPLINK TFI, 1=DL TFI
+	if (tfi_is_dl) {
+		block->u.Packet_Cell_Change_Continue.Global_TFI.u.DOWNLINK_TFI = tfi;
+	} else {
+		block->u.Packet_Cell_Change_Continue.Global_TFI.u.UPLINK_TFI = tfi;
+	}
+
+	block->u.Packet_Cell_Change_Continue.Exist_ID = exist_id;
+	if (exist_id) {
+		block->u.Packet_Cell_Change_Continue.ARFCN = arfcn;
+		block->u.Packet_Cell_Change_Continue.BSIC = bsic;
+	}
+	block->u.Packet_Cell_Change_Continue.CONTAINER_ID = container_id;
+}
diff --git a/src/encoding.h b/src/encoding.h
index da63a61..4ebfa35 100644
--- a/src/encoding.h
+++ b/src/encoding.h
@@ -22,17 +22,22 @@
 
 #include <stdint.h>
 
+#ifdef __cplusplus
 extern "C" {
+#endif
 #include <osmocom/gsm/l1sap.h>
 #include "coding_scheme.h"
 #include "gsm_rlcmac.h"
+#ifdef __cplusplus
 }
+#endif
 
 struct gprs_rlcmac_tbf;
 struct bitvec;
 struct gprs_llc;
 struct gprs_rlc_data_block_info;
 
+#ifdef __cplusplus
 /**
  * I help with encoding data into CSN1 messages.
  * TODO: Nobody can remember a function signature like this. One should
@@ -108,3 +113,21 @@
 		const struct gprs_rlc_data_block_info *rdbi,
 		int *offset, int *num_chunks, uint8_t *data_block);
 };
+
+#endif /* ifdef __cplusplus */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void write_packet_neighbour_cell_data(RlcMacDownlink_t *block,
+		bool tfi_is_dl, uint8_t tfi, uint8_t container_id,
+		uint8_t container_idx, PNCDContainer_t *container);
+
+void write_packet_cell_change_continue(RlcMacDownlink_t *block,
+		bool tfi_is_dl, uint8_t tfi, bool exist_id,
+		uint16_t arfcn, uint8_t bsic, uint8_t container_id);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/gprs_bssgp_pcu.c b/src/gprs_bssgp_pcu.c
index 956fb66..9c05639 100644
--- a/src/gprs_bssgp_pcu.c
+++ b/src/gprs_bssgp_pcu.c
@@ -419,10 +419,22 @@
 	int rc = 0;
 	struct bssgp_bvc_ctx *bctx;
 
-	if (pdu_type == BSSGP_PDUT_STATUS)
+	switch (pdu_type) {
+	case BSSGP_PDUT_STATUS:
 		/* Pass the message to the generic BSSGP parser, which handles
 		 * STATUS and RESET messages in either direction. */
+	case BSSGP_PDUT_RAN_INFO:
+	case BSSGP_PDUT_RAN_INFO_REQ:
+	case BSSGP_PDUT_RAN_INFO_ACK:
+	case BSSGP_PDUT_RAN_INFO_ERROR:
+	case BSSGP_PDUT_RAN_INFO_APP_ERROR:
+		/* Also pass all RIM related messages to the generic BSSGP
+		 * parser so that it can deliver primitive to the RIM SAP
+		 * (SAP_BSSGP_RIM) */
 		return bssgp_rcvmsg(msg);
+	default:
+		break;
+	}
 
 	/* Identifiers from DOWN: NSEI, BVCI (both in msg->cb) */
 
@@ -542,6 +554,15 @@
 	}
 }
 
+static void handle_rim(struct osmo_bssgp_prim *bp)
+{
+	/* TODO: handle RIM messages here. for RAN-INFORMATION answer, we
+	   somehow need to match the RAN-INFORMATION-REQUEST that triggered it,
+	   or feed it to all active FSMs and see if that matches internally and
+	   can go forward. That's probably the best: fill into a cache and then
+	   signal all existing NACC FSMs. */
+}
+
 int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
 {
 	struct osmo_bssgp_prim *bp;
@@ -551,6 +572,8 @@
 	case SAP_BSSGP_NM:
 		if (oph->primitive == PRIM_NM_STATUS)
 			handle_nm_status(bp);
+	case SAP_BSSGP_RIM:
+		handle_rim(bp);
 		break;
 	default:
 		break;
diff --git a/src/gprs_debug.cpp b/src/gprs_debug.cpp
index a790e3f..1cefa9b 100644
--- a/src/gprs_debug.cpp
+++ b/src/gprs_debug.cpp
@@ -119,6 +119,13 @@
 		.loglevel = LOGL_NOTICE,
 		.enabled = 1,
 	},
+	[DNACC] = {
+		.name = "DNACC",
+		.color = "\033[1;37m",
+		.description = "Network Assisted Cell Change (NACC)",
+		.loglevel = LOGL_NOTICE,
+		.enabled = 1,
+	},
 };
 
 static int filter_fn(const struct log_context *ctx,
diff --git a/src/gprs_debug.h b/src/gprs_debug.h
index 84a0a07..23ea44d 100644
--- a/src/gprs_debug.h
+++ b/src/gprs_debug.h
@@ -46,6 +46,7 @@
 	DTBFUL,
 	DNS,
 	DPCU,
+	DNACC,
 	aDebug_LastEntry
 };
 
diff --git a/src/gprs_ms.c b/src/gprs_ms.c
index ea497a3..5d7afa1 100644
--- a/src/gprs_ms.c
+++ b/src/gprs_ms.c
@@ -26,6 +26,7 @@
 #include "gprs_debug.h"
 #include "gprs_codel.h"
 #include "pcu_utils.h"
+#include "nacc_fsm.h"
 
 #include <time.h>
 
@@ -902,3 +903,36 @@
 
 	return NULL;
 }
+
+int ms_nacc_start(struct GprsMs *ms, Packet_Cell_Change_Notification_t *notif)
+{
+	if (!ms->nacc)
+		ms->nacc = nacc_fsm_alloc(ms);
+	if (!ms->nacc)
+		return -EINVAL;
+	return osmo_fsm_inst_dispatch(ms->nacc->fi, NACC_EV_CELL_CHG_NOTIFICATION, notif);
+}
+
+bool ms_nacc_rts(const struct GprsMs *ms)
+{
+	if (!ms->nacc)
+		return false;
+	if (ms->nacc->fi->state == NACC_ST_TX_NEIGHBOUR_DATA ||
+	    ms->nacc->fi->state == NACC_ST_TX_CELL_CHG_CONTINUE)
+		return true;
+	return false;
+}
+
+struct msgb *ms_nacc_create_rlcmac_msg(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf)
+{
+	int rc;
+	struct nacc_ev_create_rlcmac_msg_ctx data_ctx;
+
+	data_ctx.tbf = tbf;
+	data_ctx.msg = NULL;
+
+	rc = osmo_fsm_inst_dispatch(ms->nacc->fi, NACC_EV_CREATE_RLCMAC_MSG, &data_ctx);
+	if (rc != 0 || !data_ctx.msg)
+		return NULL;
+	return data_ctx.msg;
+}
diff --git a/src/gprs_ms.h b/src/gprs_ms.h
index 12809f1..6587231 100644
--- a/src/gprs_ms.h
+++ b/src/gprs_ms.h
@@ -40,6 +40,7 @@
 #include <osmocom/gsm/gsm48.h>
 
 #include "coding_scheme.h"
+#include <gsm_rlcmac.h>
 
 #include <stdint.h>
 #include <stddef.h>
@@ -100,6 +101,7 @@
 	enum mcs_kind mode;
 
 	struct rate_ctr_group *ctrs;
+	struct nacc_fsm_ctx *nacc;
 };
 
 struct GprsMs *ms_alloc(struct gprs_rlcmac_bts *bts, uint32_t tlli);
@@ -140,6 +142,10 @@
 
 void ms_set_callback(struct GprsMs *ms, struct gpr_ms_callback *cb);
 
+int ms_nacc_start(struct GprsMs *ms, Packet_Cell_Change_Notification_t *notif);
+bool ms_nacc_rts(const struct GprsMs *ms);
+struct msgb *ms_nacc_create_rlcmac_msg(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf);
+
 static inline bool ms_is_idle(const struct GprsMs *ms)
 {
 	return !ms->ul_tbf && !ms->dl_tbf && !ms->ref && llist_empty(&ms->old_tbfs);
diff --git a/src/gprs_pcu.c b/src/gprs_pcu.c
index d291e71..19fbf7d 100644
--- a/src/gprs_pcu.c
+++ b/src/gprs_pcu.c
@@ -22,6 +22,7 @@
 
 #include <osmocom/core/utils.h>
 #include <osmocom/core/linuxlist.h>
+#include <osmocom/ctrl/ports.h>
 
 #include "gprs_pcu.h"
 #include "bts.h"
@@ -97,6 +98,8 @@
 	pcu->vty.ws_pdch = 0;
 	pcu->vty.llc_codel_interval_msec = LLC_CODEL_USE_DEFAULT;
 	pcu->vty.llc_idle_ack_csec = 10;
+	pcu->vty.neigh_ctrl_addr = talloc_strdup(pcu, "127.0.0.1");
+	pcu->vty.neigh_ctrl_port = OSMO_CTRL_PORT_BSC_NEIGH;
 
 	pcu->T_defs = T_defs_pcu;
 	osmo_tdefs_reset(pcu->T_defs);
diff --git a/src/gprs_pcu.h b/src/gprs_pcu.h
index 058d102..f9f9893 100644
--- a/src/gprs_pcu.h
+++ b/src/gprs_pcu.h
@@ -102,6 +102,9 @@
 		uint32_t llc_discard_csec;
 		uint32_t llc_idle_ack_csec;
 		uint32_t llc_codel_interval_msec; /* 0=disabled, -1=use default interval */
+		/* Remote BSS resolution sevice (CTRL iface) */
+		char *neigh_ctrl_addr;
+		uint16_t neigh_ctrl_port;
 	} vty;
 
 	struct gsmtap_inst *gsmtap;
diff --git a/src/gprs_rlcmac_sched.cpp b/src/gprs_rlcmac_sched.cpp
index 5640158..66e9c6b 100644
--- a/src/gprs_rlcmac_sched.cpp
+++ b/src/gprs_rlcmac_sched.cpp
@@ -38,6 +38,7 @@
 	struct gprs_rlcmac_tbf *poll;
 	struct gprs_rlcmac_tbf *ul_ass;
 	struct gprs_rlcmac_tbf *dl_ass;
+	struct gprs_rlcmac_tbf *nacc;
 	struct gprs_rlcmac_ul_tbf *ul_ack;
 };
 
@@ -71,6 +72,9 @@
 		if (ul_tbf->ul_ass_state_is(GPRS_RLCMAC_UL_ASS_SEND_ASS)
 		    || ul_tbf->ul_ass_state_is(GPRS_RLCMAC_UL_ASS_SEND_ASS_REJ))
 			tbf_cand->ul_ass = ul_tbf;
+		/* NACC ready to send */
+		if (ms_nacc_rts(ul_tbf->ms()))
+			tbf_cand->nacc = ul_tbf;
 /* FIXME: Is this supposed to be fair? The last TBF for each wins? Maybe use llist_add_tail and skip once we have all
 states? */
 	}
@@ -88,6 +92,9 @@
 		if (dl_tbf->ul_ass_state_is(GPRS_RLCMAC_UL_ASS_SEND_ASS)
 		    || dl_tbf->ul_ass_state_is(GPRS_RLCMAC_UL_ASS_SEND_ASS_REJ))
 			tbf_cand->ul_ass = dl_tbf;
+		/* NACC ready to send */
+		if (ms_nacc_rts(dl_tbf->ms()))
+			tbf_cand->nacc = dl_tbf;
 	}
 
 	return poll_fn;
@@ -166,7 +173,8 @@
 	struct gprs_rlcmac_tbf *tbf = NULL;
 	struct gprs_rlcmac_tbf *next_list[] = { tbfs->ul_ass,
 						tbfs->dl_ass,
-						tbfs->ul_ack };
+						tbfs->ul_ack,
+						tbfs->nacc };
 
 	/* Send Packet Application Information first (ETWS primary notifications) */
 	msg = sched_app_info(tbfs->dl_ass);
@@ -194,6 +202,9 @@
 				msg = tbfs->dl_ass->create_dl_ass(fn, ts);
 			else if (tbf == tbfs->ul_ack)
 				msg = tbfs->ul_ack->create_ul_ack(fn, ts);
+			else if (tbf == tbfs->nacc) {
+				msg = ms_nacc_create_rlcmac_msg(tbf->ms(), tbf);
+			}
 			/* else: if tbf/ms is pending to send tx_neigbhourData or tx_CellchangeContinue, send it */
 
 			if (!msg) {
diff --git a/src/nacc_fsm.c b/src/nacc_fsm.c
new file mode 100644
index 0000000..1ae417d
--- /dev/null
+++ b/src/nacc_fsm.c
@@ -0,0 +1,538 @@
+#include <unistd.h>
+
+#include <talloc.h>
+
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_if.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gprs/gprs_bssgp_rim.h>
+
+#include <nacc_fsm.h>
+#include <gprs_rlcmac.h>
+#include <gprs_debug.h>
+#include <gprs_ms.h>
+#include <encoding.h>
+#include <bts.h>
+
+#define X(s) (1 << (s))
+
+#define nacc_fsm_state_chg(fi, NEXT_STATE) \
+	osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+const struct value_string nacc_fsm_event_names[] = {
+	{ NACC_EV_CELL_CHG_NOTIFICATION, "CELL_CHG_NOTIFICATION" },
+	{ NACC_EV_RX_RESOLVE_RAC_CI, "RX_RESOLVE_RAC_CI" },
+	{ NACC_EV_SI_INFO_RECEIVED, "SI_INFO_RECEIVED" },
+	{ NACC_EV_CREATE_RLCMAC_MSG, "CREATE_RLCMAC_MSG" },
+	{ 0, NULL }
+};
+
+static struct msgb *create_packet_neighbour_cell_data(struct nacc_fsm_ctx *ctx, struct gprs_rlcmac_tbf *tbf)
+{
+	struct msgb *msg;
+	int rc;
+	RlcMacDownlink_t *mac_control_block;
+	struct GprsMs *ms = tbf_ms(tbf);
+
+	msg = msgb_alloc(GSM_MACBLOCK_LEN, "neighbour_cell_data");
+	if (!msg)
+		return NULL;
+
+	/* Initialize a bit vector that uses allocated msgb as the data buffer. */
+	struct bitvec bv = {
+		.data = msgb_put(msg, GSM_MACBLOCK_LEN),
+		.data_len = GSM_MACBLOCK_LEN,
+	};
+	bitvec_unhex(&bv, DUMMY_VEC);
+
+	mac_control_block = (RlcMacDownlink_t *)talloc_zero(ctx->ms, RlcMacDownlink_t);
+
+	OSMO_ASSERT(tbf_is_tfi_assigned(tbf));
+	uint8_t tfi_is_dl = tbf_direction(tbf) == GPRS_RLCMAC_DL_TBF;
+	uint8_t tfi = tbf_tfi(tbf);
+	uint8_t container_id = 0; /* FIXME: don't hardcode */
+	uint8_t container_idx = 0; /* FIXME: don't hardcode */
+	PNCDContainer_t container;
+	memset(&container, 0, sizeof(container));
+	container.UnionType = 1; /* with ID */
+	container.u.PNCD_Container_With_ID.ARFCN = ctx->req_arfcn;
+	container.u.PNCD_Container_With_ID.BSIC = ctx->req_bsic;
+	write_packet_neighbour_cell_data(mac_control_block,
+					 tfi_is_dl, tfi, container_id,
+					 container_idx, &container);
+	LOGP(DNACC, LOGL_DEBUG, "+++++++++++++++++++++++++ TX : Packet Neighbour Cell Data +++++++++++++++++++++++++\n");
+	rc = encode_gsm_rlcmac_downlink(&bv, mac_control_block);
+	if (rc < 0) {
+		LOGP(DTBF, LOGL_ERROR, "Encoding of Packet Neighbour Cell Data failed (%d)\n", rc);
+		goto free_ret;
+	}
+	LOGP(DNACC, LOGL_DEBUG, "------------------------- TX : Packet Neighbour Cell Data -------------------------\n");
+	rate_ctr_inc(&bts_rate_counters(ms->bts)->ctr[CTR_PKT_NEIGH_CELL_DATA]);
+	talloc_free(mac_control_block);
+	return msg;
+
+free_ret:
+	talloc_free(mac_control_block);
+	msgb_free(msg);
+	return NULL;
+}
+
+static struct msgb *create_packet_cell_chg_continue(struct nacc_fsm_ctx *ctx, struct gprs_rlcmac_tbf *tbf)
+{
+	struct msgb *msg;
+	int rc;
+	RlcMacDownlink_t *mac_control_block;
+	struct GprsMs *ms = tbf_ms(tbf);
+
+	msg = msgb_alloc(GSM_MACBLOCK_LEN, "pkt_cell_chg_continue");
+	if (!msg)
+		return NULL;
+
+	/* Initialize a bit vector that uses allocated msgb as the data buffer. */
+	struct bitvec bv = {
+		.data = msgb_put(msg, GSM_MACBLOCK_LEN),
+		.data_len = GSM_MACBLOCK_LEN,
+	};
+	bitvec_unhex(&bv, DUMMY_VEC);
+
+	mac_control_block = (RlcMacDownlink_t *)talloc_zero(ctx->ms, RlcMacDownlink_t);
+
+	OSMO_ASSERT(tbf_is_tfi_assigned(tbf));
+	uint8_t tfi_is_dl = tbf_direction(tbf) == GPRS_RLCMAC_DL_TBF;
+	uint8_t tfi = tbf_tfi(tbf);
+	uint8_t container_id = 0; /* FIXME: don't hardcode */
+	write_packet_cell_change_continue(mac_control_block, tfi_is_dl, tfi, true,
+			ctx->req_arfcn, ctx->req_bsic, container_id);
+	LOGP(DNACC, LOGL_DEBUG, "+++++++++++++++++++++++++ TX : Packet Cell Change Continue +++++++++++++++++++++++++\n");
+	rc = encode_gsm_rlcmac_downlink(&bv, mac_control_block);
+	if (rc < 0) {
+		LOGP(DTBF, LOGL_ERROR, "Encoding of Packet Cell Change Continue failed (%d)\n", rc);
+		goto free_ret;
+	}
+	LOGP(DNACC, LOGL_DEBUG, "------------------------- TX : Packet Cell Change Continue -------------------------\n");
+	rate_ctr_inc(&bts_rate_counters(ms->bts)->ctr[CTR_PKT_CELL_CHG_CONTINUE]);
+	talloc_free(mac_control_block);
+	return msg;
+
+free_ret:
+	talloc_free(mac_control_block);
+	msgb_free(msg);
+	return NULL;
+}
+
+static void st_initial(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct nacc_fsm_ctx *ctx = (struct nacc_fsm_ctx *)fi->priv;
+	Packet_Cell_Change_Notification_t *notif;
+
+	switch (event) {
+	case NACC_EV_CELL_CHG_NOTIFICATION:
+		notif = (Packet_Cell_Change_Notification_t *)data;
+		switch (notif->Target_Cell.UnionType) {
+		case 0: /* GSM */
+			ctx->req_arfcn = notif->Target_Cell.u.Target_Cell_GSM_Notif.ARFCN;
+			ctx->req_bsic = notif->Target_Cell.u.Target_Cell_GSM_Notif.BSIC;
+			/* TODO: look up in resolution table whether we already have RAC+CI for ARFCN+BSIC */
+			nacc_fsm_state_chg(fi, NACC_ST_WAIT_RESOLVE_RAC_CI);
+			break;
+		default:
+			LOGPFSML(fi, LOGL_NOTICE, "TargetCell type=0x%x not supported\n",
+				 notif->Target_Cell.UnionType);
+			return;
+		}
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+
+static void st_wait_resolve_rac_ci_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct nacc_fsm_ctx *ctx = (struct nacc_fsm_ctx *)fi->priv;
+	struct gprs_rlcmac_bts *bts = ctx->ms->bts;
+	struct ctrl_cmd *cmd;
+	int rc;
+
+	cmd = ctrl_cmd_create(ctx, CTRL_TYPE_GET);
+	if (!cmd) {
+		LOGPFSML(fi, LOGL_ERROR, "CTRL msg creation failed\n");
+		return;
+	}
+
+	cmd->id = talloc_asprintf(cmd, "1");
+	cmd->variable = talloc_asprintf(cmd, "neighbor_resolve_cgi_ps_from_lac_ci.%d.%d.%d.%d",
+					bts->cgi_ps.rai.lac.lac, bts->cgi_ps.cell_identity,
+					ctx->req_arfcn, ctx->req_bsic);
+	//rep->value = 0;
+	rc = ctrl_cmd_send(&ctx->neigh_ctrl_conn->write_queue, cmd);
+	if (rc)
+		LOGPFSML(fi, LOGL_ERROR, "CTRL msg sent failed: %d\n", rc);
+	talloc_free(cmd);
+}
+
+
+static void st_wait_resolve_rac_ci(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	//struct nacc_fsm_ctx *ctx = (struct nacc_fsm_ctx *)fi->priv;
+
+	switch (event) {
+	case NACC_EV_CELL_CHG_NOTIFICATION:
+		break;
+	case NACC_EV_RX_RESOLVE_RAC_CI:
+		nacc_fsm_state_chg(fi, NACC_ST_WAIT_REQUEST_SI);
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+
+static int fill_rim_ran_info_req(struct nacc_fsm_ctx *ctx, struct bssgp_ran_information_pdu *pdu)
+{
+	struct gprs_rlcmac_bts *bts = ctx->ms->bts;
+
+	*pdu = (struct bssgp_ran_information_pdu){
+		.routing_info_dest = {
+			.discr = BSSGP_RIM_ROUTING_INFO_GERAN,
+			.geran = {
+				.raid = {
+					.mcc = ctx->cgi_ps.rai.lac.plmn.mcc,
+					.mnc = ctx->cgi_ps.rai.lac.plmn.mnc,
+					.mnc_3_digits = ctx->cgi_ps.rai.lac.plmn.mnc_3_digits,
+					.lac = ctx->cgi_ps.rai.lac.lac,
+					.rac = ctx->cgi_ps.rai.rac,
+				},
+				.cid = ctx->cgi_ps.cell_identity,
+			},
+		},
+		.routing_info_src = {
+			.discr = BSSGP_RIM_ROUTING_INFO_GERAN,
+			.geran = {
+				.raid = { /* TODO: fill properly */
+					.mcc = bts->cgi_ps.rai.lac.plmn.mcc,
+					.mnc = bts->cgi_ps.rai.lac.plmn.mnc,
+					.mnc_3_digits = bts->cgi_ps.rai.lac.plmn.mnc_3_digits,
+					.lac = bts->cgi_ps.rai.lac.lac,
+					.rac = bts->cgi_ps.rai.rac,
+				},
+				.cid = bts->cgi_ps.cell_identity,
+			},
+		},
+		.rim_cont_iei = BSSGP_IE_RI_REQ_RIM_CONTAINER,
+		.decoded_present = true,
+		.decoded = {
+			.req_rim_cont = {
+				.app_id = BSSGP_RAN_INF_APP_ID_NACC,
+				.seq_num = 1,
+				.pdu_ind = {
+					.ack_requested = 0,
+					.pdu_type_ext = 1,
+				},
+				.prot_ver = 1,
+				.son_trans_app_id = NULL,
+				.son_trans_app_id_len = 0,
+				.u = {
+					.app_cont_nacc = {
+						.reprt_cell = ctx->cgi_ps,
+					},
+				},
+			},
+		},
+	};
+
+	return 0;
+}
+
+/* At this point, we expect correct tgt cell info to be already in ctx->cgi_ps */
+static void st_wait_request_si_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct nacc_fsm_ctx *ctx = (struct nacc_fsm_ctx *)fi->priv;
+	struct bssgp_ran_information_pdu pdu;
+	int rc;
+
+	if (fill_rim_ran_info_req(ctx, &pdu) < 0) {
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+		return;
+	}
+
+	rc = bssgp_tx_rim(&pdu, gprs_ns2_nse_nsei(ctx->ms->bts->nse));
+	if (rc < 0) {
+		LOGPFSML(fi, LOGL_ERROR, "Failed transmitting RIM PDU: %d\n", rc);
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+		return;
+	}
+
+	osmo_fsm_inst_dispatch(fi, NACC_EV_SI_INFO_RECEIVED, NULL);
+}
+
+
+static void st_wait_request_si(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	//struct nacc_fsm_ctx *ctx = (struct nacc_fsm_ctx *)fi->priv;
+
+	switch (event) {
+	case NACC_EV_SI_INFO_RECEIVED:
+		/* TODO: In here we'll parse RIM response */
+
+		/* Tell the PCU scheduler we are ready to go, from here one we
+		 * are polled/driven by the scheduler */
+		nacc_fsm_state_chg(fi, NACC_ST_TX_NEIGHBOUR_DATA);
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+
+
+static void st_tx_neighbour_data_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	/* At this point, we already received all required RIM messages or we
+	   have them cached. We now wait for scheduler to ask us to construct
+	   RLCMAC DL CTRL messages to move FSM states forward */
+}
+
+static void st_tx_neighbour_data(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct nacc_fsm_ctx *ctx = (struct nacc_fsm_ctx *)fi->priv;
+	struct nacc_ev_create_rlcmac_msg_ctx *data_ctx;
+
+	switch (event) {
+	case NACC_EV_CREATE_RLCMAC_MSG:
+		data_ctx = (struct nacc_ev_create_rlcmac_msg_ctx *)data;
+		data_ctx->msg = create_packet_neighbour_cell_data(ctx, data_ctx->tbf);
+		/* TODO: logic if no more Neighbour cell data messages need to be send, then: */
+		nacc_fsm_state_chg(fi, NACC_ST_TX_CELL_CHG_CONTINUE);
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+
+static void st_cell_cgh_continue_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	/* At this point, we already sent all Pkt Cell Neighbour Change rlcmac
+	   blocks, and we only need to wait to be scheduled again to send PKT
+	   CELL CHANGE NOTIFICATION */
+}
+
+static void st_cell_cgh_continue(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct nacc_fsm_ctx *ctx = (struct nacc_fsm_ctx *)fi->priv;
+	struct nacc_ev_create_rlcmac_msg_ctx *data_ctx;
+
+	switch (event) {
+	case NACC_EV_CREATE_RLCMAC_MSG:
+		data_ctx = (struct nacc_ev_create_rlcmac_msg_ctx *)data;
+		data_ctx->msg = create_packet_cell_chg_continue(ctx, data_ctx->tbf);
+		/* TODO: logic if no more Neighbour cell data messages need to be send, then: */
+		nacc_fsm_state_chg(fi, NACC_ST_DONE);
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+
+
+static void st_done_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+static void nacc_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+	struct nacc_fsm_ctx *ctx = (struct nacc_fsm_ctx *)fi->priv;
+	/* afte rcleanup() finishes, FSM termination calls osmo_fsm_inst_free,
+	   so we need to avoid double-freeing it during ctx talloc free
+	   destructor */
+	//talloc_reparent(ctx, ctx->ms, ctx->fi);
+	ctx->fi = NULL;
+
+	/* remove references from owning MS and free entire ctx */
+	ctx->ms->nacc = NULL;
+	talloc_free(ctx);
+}
+
+static struct osmo_fsm_state nacc_fsm_states[] = {
+	[NACC_ST_INITIAL] = {
+		.in_event_mask =
+			X(NACC_EV_CELL_CHG_NOTIFICATION),
+		.out_state_mask =
+			X(NACC_ST_WAIT_RESOLVE_RAC_CI) |
+			X(NACC_ST_WAIT_REQUEST_SI) |
+			X(NACC_ST_TX_NEIGHBOUR_DATA),
+		.name = "INITIAL",
+		//.onenter = st_initial_on_enter,
+		.action = st_initial,
+	},
+	[NACC_ST_WAIT_RESOLVE_RAC_CI] = {
+		.in_event_mask =
+			X(NACC_EV_RX_RESOLVE_RAC_CI),
+		.out_state_mask =
+			X(NACC_ST_WAIT_REQUEST_SI) |
+			X(NACC_ST_TX_CELL_CHG_CONTINUE),
+		.name = "WAIT_RESOLVE_RAC_CI",
+		.onenter = st_wait_resolve_rac_ci_on_enter,
+		.action = st_wait_resolve_rac_ci,
+	},
+	[NACC_ST_WAIT_REQUEST_SI] = {
+		.in_event_mask =
+			X(NACC_EV_CELL_CHG_NOTIFICATION) |
+			X(NACC_EV_SI_INFO_RECEIVED),
+		.out_state_mask =
+			X(NACC_ST_TX_NEIGHBOUR_DATA),
+		.name = "WAIT_REQUEST_SI",
+		.onenter = st_wait_request_si_on_enter,
+		.action = st_wait_request_si,
+	},
+	[NACC_ST_TX_NEIGHBOUR_DATA] = {
+		.in_event_mask =
+			X(NACC_EV_CELL_CHG_NOTIFICATION) |
+			X(NACC_EV_SI_INFO_RECEIVED) |
+			X(NACC_EV_CREATE_RLCMAC_MSG),
+		.out_state_mask =
+			X(NACC_ST_TX_CELL_CHG_CONTINUE),
+		.name = "TX_NEIGHBOUR_DATA",
+		.onenter = st_tx_neighbour_data_on_enter,
+		.action = st_tx_neighbour_data,
+	},
+	[NACC_ST_TX_CELL_CHG_CONTINUE] = {
+		.in_event_mask =
+			X(NACC_EV_CELL_CHG_NOTIFICATION) |
+			X(NACC_EV_SI_INFO_RECEIVED) |
+			X(NACC_EV_CREATE_RLCMAC_MSG),
+		.out_state_mask =
+			X(NACC_ST_DONE),
+		.name = "TX_CELL_CHG_CONTINUE",
+		.onenter = st_cell_cgh_continue_on_enter,
+		.action = st_cell_cgh_continue,
+	},
+	[NACC_ST_DONE] = {
+		.in_event_mask = 0,
+		.out_state_mask = 0,
+		.name = "DONE",
+		.onenter = st_done_on_enter,
+	},
+};
+
+static struct osmo_fsm nacc_fsm = {
+	.name = "NACC",
+	.states = nacc_fsm_states,
+	.num_states = ARRAY_SIZE(nacc_fsm_states),
+	.cleanup = nacc_fsm_cleanup,
+	.log_subsys = DNACC,
+	.event_names = nacc_fsm_event_names,
+};
+
+static __attribute__((constructor)) void nacc_fsm_init(void)
+{
+	OSMO_ASSERT(osmo_fsm_register(&nacc_fsm) == 0);
+}
+
+void nacc_fsm_ctrl_reply_cb(struct ctrl_handle *ctrl, struct ctrl_cmd *cmd, void *data)
+{
+	struct nacc_fsm_ctx *ctx = (struct nacc_fsm_ctx *)data;
+	char *tmp = NULL, *tok, *saveptr;
+
+	LOGPFSML(ctx->fi, LOGL_NOTICE, "Received CTRL message: type=%d %s: %s\n",
+		 cmd->type, cmd->variable, osmo_escape_str(cmd->reply, -1));
+
+	if (cmd->type != CTRL_TYPE_GET_REPLY || !cmd->reply) {
+		osmo_fsm_inst_term(ctx->fi, OSMO_FSM_TERM_ERROR, NULL);
+		return;
+	}
+
+	/* TODO: Potentially validate cmd->variable contains same params as we
+	   sent, and that cmd->id matches the original set. We may want to keep
+	   the original cmd around by setting cmd->defer=1 when sending it. */
+
+	tmp = talloc_strdup(cmd, cmd->reply);
+	if (!tmp)
+		goto free_ret;
+
+	if (!(tok = strtok_r(tmp, "-", &saveptr)))
+		goto free_ret;
+	ctx->cgi_ps.rai.lac.plmn.mcc = atoi(tok);
+
+	if (!(tok = strtok_r(NULL, "-", &saveptr)))
+		goto free_ret;
+	ctx->cgi_ps.rai.lac.plmn.mnc = atoi(tok);
+
+	if (!(tok = strtok_r(NULL, "-", &saveptr)))
+		goto free_ret;
+	ctx->cgi_ps.rai.lac.lac = atoi(tok);
+
+	if (!(tok = strtok_r(NULL, "-", &saveptr)))
+		goto free_ret;
+	ctx->cgi_ps.rai.rac = atoi(tok);
+
+	if (!(tok = strtok_r(NULL, "\0", &saveptr)))
+		goto free_ret;
+	ctx->cgi_ps.cell_identity = atoi(tok);
+
+	// TODO: cache the cgi_ps so we can avoid requesting again same resolution
+
+	osmo_fsm_inst_dispatch(ctx->fi, NACC_EV_RX_RESOLVE_RAC_CI, NULL);
+	return;
+
+free_ret:
+	talloc_free(tmp);
+	osmo_fsm_inst_term(ctx->fi, OSMO_FSM_TERM_ERROR, NULL);
+	return;
+}
+
+static int nacc_fsm_ctx_talloc_destructor(struct nacc_fsm_ctx *ctx)
+{
+	if (ctx->fi) {
+		osmo_fsm_inst_free(ctx->fi);
+		ctx->fi = NULL;
+	}
+
+	if (ctx->neigh_ctrl_conn) {
+		if (ctx->neigh_ctrl_conn->write_queue.bfd.fd != -1) {
+			close(ctx->neigh_ctrl_conn->write_queue.bfd.fd);
+			ctx->neigh_ctrl_conn->write_queue.bfd.fd = -1;
+		}
+	}
+
+	return 0;
+}
+
+struct nacc_fsm_ctx *nacc_fsm_alloc(struct GprsMs* ms)
+{
+	struct gprs_rlcmac_bts *bts = ms->bts;
+	struct gprs_pcu *pcu = bts->pcu;
+	struct nacc_fsm_ctx *ctx = talloc_zero(ms, struct nacc_fsm_ctx);
+	char buf[64];
+	int rc;
+
+	talloc_set_destructor(ctx, nacc_fsm_ctx_talloc_destructor);
+
+	ctx->ms = ms;
+
+	snprintf(buf, sizeof(buf), "TLLI-0x%08x", ms_tlli(ms));
+	ctx->fi = osmo_fsm_inst_alloc(&nacc_fsm, ctx, ctx, LOGL_INFO, buf);
+	if (!ctx->fi)
+		goto free_ret;
+
+	ctx->neigh_ctrl = ctrl_handle_alloc(ctx, ctx, NULL);
+	ctx->neigh_ctrl->reply_cb = nacc_fsm_ctrl_reply_cb;
+	ctx->neigh_ctrl_conn = osmo_ctrl_conn_alloc(ctx, ctx->neigh_ctrl);
+	if (!ctx->neigh_ctrl_conn)
+		goto free_ret;
+
+	rc = osmo_sock_init2_ofd(&ctx->neigh_ctrl_conn->write_queue.bfd, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP,
+				 NULL, 0, pcu->vty.neigh_ctrl_addr, pcu->vty.neigh_ctrl_port,
+				 OSMO_SOCK_F_CONNECT);
+	if (rc < 0) {
+		LOGP(DNACC, LOGL_ERROR, "Can't connect to CTRL @ %s:%u\n",
+		     pcu->vty.neigh_ctrl_addr, pcu->vty.neigh_ctrl_port);
+		goto free_ret;
+	}
+
+	return ctx;
+free_ret:
+	talloc_free(ctx);
+	return NULL;
+}
diff --git a/src/nacc_fsm.h b/src/nacc_fsm.h
new file mode 100644
index 0000000..21c22ce
--- /dev/null
+++ b/src/nacc_fsm.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/gsm/gsm23003.h>
+
+struct GprsMs;
+struct gprs_rlcmac_tbf;
+
+enum nacc_fsm_event {
+	NACC_EV_CELL_CHG_NOTIFICATION, /* data: Packet_Cell_Change_Notification_t* */
+	NACC_EV_RX_RESOLVE_RAC_CI, /* data: ctrl msg resp */
+	NACC_EV_SI_INFO_RECEIVED,
+	NACC_EV_CREATE_RLCMAC_MSG,
+};
+
+enum nacc_fsm_states {
+	NACC_ST_INITIAL,
+	NACC_ST_WAIT_RESOLVE_RAC_CI,
+	NACC_ST_WAIT_REQUEST_SI,
+	NACC_ST_TX_NEIGHBOUR_DATA,
+	NACC_ST_TX_CELL_CHG_CONTINUE,
+	NACC_ST_DONE,
+};
+
+struct nacc_fsm_ctx {
+	struct osmo_fsm_inst *fi;
+	struct GprsMs* ms; /* back pointer */
+	struct ctrl_handle *neigh_ctrl;
+	struct ctrl_connection *neigh_ctrl_conn;
+	uint16_t req_arfcn;
+	uint8_t req_bsic;
+	struct osmo_cell_global_id_ps cgi_ps; /* target SGSN, resolved from req_{arfcn+bsic} */
+};
+
+/* passed as data in NACC_EV_CREATE_RLCMAC_MSG */
+struct nacc_ev_create_rlcmac_msg_ctx {
+	struct gprs_rlcmac_tbf *tbf; /* target tbf to create messages for */
+	struct msgb *msg; /* to be filled by FSM duringe event processing */
+};
+
+struct nacc_fsm_ctx *nacc_fsm_alloc(struct GprsMs* ms);
diff --git a/src/pcu_vty.c b/src/pcu_vty.c
index 288f241..b7021c8 100644
--- a/src/pcu_vty.c
+++ b/src/pcu_vty.c
@@ -14,6 +14,7 @@
 #include <osmocom/vty/misc.h>
 #include <osmocom/core/linuxlist.h>
 #include <osmocom/core/rate_ctr.h>
+#include <osmocom/ctrl/ports.h>
 #include <osmocom/pcu/pcuif_proto.h>
 #include <osmocom/gprs/gprs_ns2.h>
 #include "pcu_vty.h"
@@ -1018,6 +1019,22 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_neighbor_resolution, cfg_neighbor_resolution_cmd,
+       "neighbor resolution " VTY_IPV46_CMD " [<0-65535>]",
+       "Manage local and remote-BSS neighbor cells\n"
+       "Connect to Neighbor Resolution Service (CTRL interface) to given ip and port\n"
+       "IPv4 address to connect to\n" "IPv6 address to connect to\n"
+       "Port to connect to (default 4248)\n")
+{
+	osmo_talloc_replace_string(the_pcu, &the_pcu->vty.neigh_ctrl_addr, argv[0]);
+	if (argc > 1)
+		the_pcu->vty.neigh_ctrl_port = atoi(argv[1]);
+	else
+		the_pcu->vty.neigh_ctrl_port = OSMO_CTRL_PORT_BSC_NEIGH;
+	return CMD_SUCCESS;
+}
+
+
 DEFUN(show_bts_timer, show_bts_timer_cmd,
       "show bts-timer " OSMO_TDEF_VTY_ARG_T_OPTIONAL,
       SHOW_STR "Show BTS controlled timers\n"
@@ -1220,6 +1237,7 @@
 	install_element(PCU_NODE, &cfg_pcu_no_gsmtap_categ_cmd);
 	install_element(PCU_NODE, &cfg_pcu_sock_cmd);
 	install_element(PCU_NODE, &cfg_pcu_gb_dialect_cmd);
+	install_element(PCU_NODE, &cfg_neighbor_resolution_cmd);
 	install_element(PCU_NODE, &cfg_pcu_timer_cmd);
 
 	install_element_ve(&show_bts_stats_cmd);
diff --git a/src/pdch.cpp b/src/pdch.cpp
index 5a329f3..2028ba2 100644
--- a/src/pdch.cpp
+++ b/src/pdch.cpp
@@ -685,6 +685,33 @@
 	gprs_rlcmac_meas_rep(ms, report);
 }
 
+void gprs_rlcmac_pdch::rcv_cell_change_notification(Packet_Cell_Change_Notification_t *notif,
+						    uint32_t fn, struct pcu_l1_meas *meas)
+{
+	GprsMs *ms;
+
+	bts_do_rate_ctr_inc(bts(), CTR_PKT_CELL_CHG_NOTIFICATION);
+
+	if (notif->Global_TFI.UnionType == 0) {
+		struct gprs_rlcmac_ul_tbf *ul_tbf = ul_tbf_by_tfi(notif->Global_TFI.u.UPLINK_TFI);
+		if (!ul_tbf) {
+			LOGP(DRLCMAC, LOGL_NOTICE, "UL TBF TFI=0x%2x not found\n", notif->Global_TFI.u.UPLINK_TFI);
+			return;
+		}
+		ms = ul_tbf->ms();
+	} else if (notif->Global_TFI.UnionType == 1) {
+		struct gprs_rlcmac_dl_tbf *dl_tbf = dl_tbf_by_tfi(notif->Global_TFI.u.DOWNLINK_TFI);
+		if (!dl_tbf) {
+			LOGP(DRLCMAC, LOGL_NOTICE, "DL TBF TFI=0x%2x not found\n", notif->Global_TFI.u.DOWNLINK_TFI);
+			return;
+		}
+		ms = dl_tbf->ms();
+	} else { OSMO_ASSERT(0); }
+
+	ms_update_l1_meas(ms, meas);
+	ms_nacc_start(ms, notif);
+}
+
 /* Received Uplink RLC control block. */
 int gprs_rlcmac_pdch::rcv_control_block(const uint8_t *data, uint8_t data_len,
 					uint32_t fn, struct pcu_l1_meas *meas, enum CodingScheme cs)
@@ -734,6 +761,9 @@
 	case MT_PACKET_UPLINK_DUMMY_CONTROL_BLOCK:
 		/* ignoring it. change the SI to not force sending these? */
 		break;
+	case MT_PACKET_CELL_CHANGE_NOTIFICATION:
+		rcv_cell_change_notification(&ul_control_block->u.Packet_Cell_Change_Notification, fn, meas);
+		break;
 	default:
 		bts_do_rate_ctr_inc(bts(), CTR_DECODE_ERRORS);
 		LOGP(DRLCMAC, LOGL_NOTICE,
diff --git a/src/pdch.h b/src/pdch.h
index 8871986..d596531 100644
--- a/src/pdch.h
+++ b/src/pdch.h
@@ -139,6 +139,7 @@
 	void rcv_control_egprs_dl_ack_nack(EGPRS_PD_AckNack_t *, uint32_t fn, struct pcu_l1_meas *meas);
 	void rcv_resource_request(Packet_Resource_Request_t *t, uint32_t fn, struct pcu_l1_meas *meas);
 	void rcv_measurement_report(Packet_Measurement_Report_t *t, uint32_t fn);
+	void rcv_cell_change_notification(Packet_Cell_Change_Notification_t *, uint32_t fn, struct pcu_l1_meas *meas);
 	gprs_rlcmac_tbf *tbf_from_list_by_tfi(
 		LListHead<gprs_rlcmac_tbf> *tbf_list, uint8_t tfi,
 		enum gprs_rlcmac_tbf_direction dir);
diff --git a/src/tbf.cpp b/src/tbf.cpp
index 37af21f..5b2fe3d 100644
--- a/src/tbf.cpp
+++ b/src/tbf.cpp
@@ -1197,3 +1197,8 @@
 {
 	return tbf->is_tfi_assigned();
 }
+
+uint8_t tbf_tfi(const struct gprs_rlcmac_tbf *tbf)
+{
+	return tbf->tfi();
+}
diff --git a/src/tbf.h b/src/tbf.h
index d616076..815d254 100644
--- a/src/tbf.h
+++ b/src/tbf.h
@@ -204,6 +204,7 @@
 uint8_t tbf_dl_slots(const struct gprs_rlcmac_tbf *tbf);
 uint8_t tbf_ul_slots(const struct gprs_rlcmac_tbf *tbf);
 bool tbf_is_tfi_assigned(const struct gprs_rlcmac_tbf *tbf);
+uint8_t tbf_tfi(const struct gprs_rlcmac_tbf *tbf);
 int tbf_assign_control_ts(struct gprs_rlcmac_tbf *tbf);
 #ifdef __cplusplus
 }
diff --git a/tests/Makefile.am b/tests/Makefile.am
index c599636..a7771b9 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,4 +1,4 @@
-AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGB_CFLAGS) $(LIBOSMOGSM_CFLAGS) -I$(top_srcdir)/src/ -I$(top_srcdir)/include/
+AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOGB_CFLAGS) $(LIBOSMOGSM_CFLAGS) -I$(top_srcdir)/src/ -I$(top_srcdir)/include/
 AM_LDFLAGS = -lrt -no-install
 
 check_PROGRAMS = rlcmac/RLCMACTest alloc/AllocTest alloc/MslotTest tbf/TbfTest types/TypesTest ms/MsTest llist/LListTest llc/LlcTest codel/codel_test edge/EdgeTest bitcomp/BitcompTest fn/FnTest app_info/AppInfoTest
@@ -15,6 +15,7 @@
 	$(top_builddir)/src/libgprs.la \
 	$(LIBOSMOGB_LIBS) \
 	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
 	$(COMMON_LA)
 
@@ -23,6 +24,7 @@
 	$(top_builddir)/src/libgprs.la \
 	$(LIBOSMOGB_LIBS) \
 	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
 	$(COMMON_LA)
 
@@ -31,6 +33,7 @@
 	$(top_builddir)/src/libgprs.la \
 	$(LIBOSMOGB_LIBS) \
 	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
 	$(COMMON_LA)
 tbf_TbfTest_LDFLAGS = -Wl,--wrap=pcu_sock_send
@@ -38,6 +41,7 @@
 bitcomp_BitcompTest_SOURCES = bitcomp/BitcompTest.cpp ../src/egprs_rlc_compression.cpp
 bitcomp_BitcompTest_LDADD = \
 	$(top_builddir)/src/libgprs.la \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
 	$(COMMON_LA)
 
@@ -46,6 +50,7 @@
 	$(top_builddir)/src/libgprs.la \
 	$(LIBOSMOGB_LIBS) \
 	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
 	$(COMMON_LA)
 
@@ -56,6 +61,7 @@
 	$(top_builddir)/src/libgprs.la \
 	$(LIBOSMOGB_LIBS) \
 	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
 	$(COMMON_LA)
 
@@ -64,6 +70,7 @@
 	$(top_builddir)/src/libgprs.la \
 	$(LIBOSMOGB_LIBS) \
 	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
 	$(COMMON_LA)
 
@@ -72,6 +79,7 @@
 	$(top_builddir)/src/libgprs.la \
 	$(LIBOSMOGB_LIBS) \
 	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
 	$(COMMON_LA)
 
@@ -83,6 +91,7 @@
 	$(top_builddir)/src/libgprs.la \
 	$(LIBOSMOGB_LIBS) \
 	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
 	$(COMMON_LA)
 
@@ -97,6 +106,7 @@
 codel_codel_test_SOURCES = codel/codel_test.c
 codel_codel_test_LDADD = \
 	$(top_builddir)/src/libgprs.la \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
 	$(COMMON_LA)
 
@@ -105,6 +115,7 @@
 	$(top_builddir)/src/libgprs.la \
 	$(LIBOSMOGB_LIBS) \
 	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
 	$(COMMON_LA)
 
@@ -113,6 +124,7 @@
 	$(top_builddir)/src/libgprs.la \
 	$(LIBOSMOGB_LIBS) \
 	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
 	$(LIBOSMOCORE_LIBS) \
 	$(COMMON_LA)
 

-- 
To view, visit https://gerrit.osmocom.org/c/osmo-pcu/+/22338
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-pcu
Gerrit-Branch: master
Gerrit-Change-Id: Ife6e4a086d58d49676d12b6984f63079ec472e79
Gerrit-Change-Number: 22338
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pespin at sysmocom.de>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20210120/8618b4d2/attachment.htm>


More information about the gerrit-log mailing list