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.orgpespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-pcu/+/22385 ) Change subject: WIP: Introduce NACC support ...................................................................... WIP: Introduce NACC support TODO: timeout timers to abort NACC? Change-Id: Id35f40d05f3e081f32fddbf1fa34cb338db452ca --- 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_rim.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 A src/neigh_cache.c A src/neigh_cache.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 24 files changed, 1,347 insertions(+), 5 deletions(-) git pull ssh://gerrit.osmocom.org:29418/osmo-pcu refs/changes/85/22385/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 f85a456..eefbea1 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 @@ -55,6 +55,8 @@ pcu_vty.c \ pcu_vty_functions.cpp \ mslot_class.c \ + nacc_fsm.c \ + neigh_cache.c \ tbf.cpp \ tbf_ul.cpp \ tbf_dl.cpp \ @@ -91,6 +93,8 @@ pcu_vty.h \ pcu_vty_functions.h \ mslot_class.h \ + nacc_fsm.h \ + neigh_cache.h \ tbf.h \ tbf_ul.h \ tbf_dl.h \ @@ -143,6 +147,7 @@ libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(COMMON_LA) endif @@ -191,6 +196,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..fb7de64 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 "}, @@ -1284,3 +1287,8 @@ { return bts_ms_store(bts)->get_ms(0, 0, imsi); } + +const struct llist_head* bts_ms_list(struct gprs_rlcmac_bts *bts) +{ + return bts_ms_store(bts)->ms_list(); +} diff --git a/src/bts.h b/src/bts.h index 7f437e3..ea15caf 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, @@ -337,6 +341,7 @@ void bts_set_max_mcs_dl(struct gprs_rlcmac_bts *bts, uint8_t mcs_dl); void bts_set_max_mcs_ul(struct gprs_rlcmac_bts *bts, uint8_t mcs_ul); bool bts_cs_dl_is_supported(const struct gprs_rlcmac_bts *bts, enum CodingScheme cs); +const struct llist_head* bts_ms_list(struct gprs_rlcmac_bts *bts); #ifdef __cplusplus } #endif 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_rim.c b/src/gprs_bssgp_rim.c index cf43a8f..3c273ec 100644 --- a/src/gprs_bssgp_rim.c +++ b/src/gprs_bssgp_rim.c @@ -26,6 +26,9 @@ #include "gprs_debug.h" #include "gprs_pcu.h" +#include "bts.h" +#include "gprs_ms.h" +#include "nacc_fsm.h" #define LOGPRIM(nsei, level, fmt, args...) \ LOGP(DRIM, level, "(NSEI=%u) " fmt, nsei, ## args) @@ -97,6 +100,51 @@ return false; } +static int handle_ran_info_response(struct osmo_bssgp_prim *bp, struct gprs_rlcmac_bts *bts) +{ + struct msgb *msg = bp->oph.msg; + struct bssgp_ran_information_pdu *pdu = &bp->u.rim_pdu; + struct bssgp_ran_inf_rim_cont *ran_info = &pdu->decoded.rim_cont; + struct bssgp_ran_inf_app_cont_nacc *nacc; + struct si_cache_value val; + struct si_cache_entry *entry; + struct llist_head *tmp; + int i; + + if (ran_info->app_err) { + LOGP(DRIM, LOGL_ERROR, + "RAN-INFO answered with an app error! cause=0x%x\n", + ran_info->u.app_err_cont_nacc.nacc_cause); + return bssgp_tx_status(BSSGP_CAUSE_UNKN_RIM_AI, NULL, msg); + } + + nacc = &ran_info->u.app_cont_nacc; + + LOGP(DRIM, LOGL_INFO, "Received RAN-INFO type=%sBCCH num_si=%d\n", + nacc->type_psi ? "B" : "", nacc->num_si); + + val.type_psi = nacc->type_psi; + val.si_len = 0; + for (i = 0; i < nacc->num_si; i++) { + size_t len = val.type_psi ? BSSGP_RIM_PSI_LEN : BSSGP_RIM_SI_LEN; + memcpy(&val.si_buf[val.si_len], nacc->si[i], len); + val.si_len += len; + } + entry = si_cache_add(bts->pcu->si_cache, &nacc->reprt_cell, &val); + + llist_for_each(tmp, bts_ms_list(bts)) { + struct GprsMs *ms = llist_entry(tmp, typeof(*ms), list); + if (!ms->nacc) + continue; + if (ms->nacc->fi->state != NACC_ST_WAIT_REQUEST_SI) + continue; + if (memcmp(&nacc->reprt_cell, &ms->nacc->cgi_ps, sizeof(nacc->reprt_cell)) != 0) + continue; + osmo_fsm_inst_dispatch(ms->nacc->fi, NACC_EV_SI_INFO_RECEIVED, entry); + } + return 0; +} + int handle_rim(struct osmo_bssgp_prim *bp) { struct msgb *msg = bp->oph.msg; @@ -151,8 +199,7 @@ LOGPRIM(nsei, LOGL_NOTICE, "Responding to RAN INFORMATION REQUEST not yet implemented!\n"); break; case BSSGP_IE_RI_RIM_CONTAINER: - LOGPRIM(nsei, LOGL_NOTICE, "Responding to RAN INFORMATION not yet implemented!\n"); - break; + return handle_ran_info_response(bp, bts); case BSSGP_IE_RI_APP_ERROR_RIM_CONT: case BSSGP_IE_RI_ACK_RIM_CONTAINER: case BSSGP_IE_RI_ERROR_RIM_COINTAINER: diff --git a/src/gprs_debug.cpp b/src/gprs_debug.cpp index 669ea27..0cbc488 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, + }, [DRIM] = { .name = "DRIM", .color = "\033[1;38m", diff --git a/src/gprs_debug.h b/src/gprs_debug.h index 8df405a..cebeabc 100644 --- a/src/gprs_debug.h +++ b/src/gprs_debug.h @@ -46,6 +46,7 @@ DTBFUL, DNS, DPCU, + DNACC, DRIM, 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 2f18dfd..9679914 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" @@ -40,12 +41,20 @@ { .T=0, .default_val=0, .unit=OSMO_TDEF_S, .desc=NULL, .val=0 } /* empty item at the end */ }; +static int gprs_pcu_talloc_destructor(struct gprs_pcu *pcu) +{ + neigh_cache_free(pcu->neigh_cache); + si_cache_free(pcu->si_cache); + return 0; +} + struct gprs_pcu *gprs_pcu_alloc(void *ctx) { struct gprs_pcu *pcu; pcu = (struct gprs_pcu *)talloc_zero(ctx, struct gprs_pcu); OSMO_ASSERT(pcu); + talloc_set_destructor(pcu, gprs_pcu_talloc_destructor); pcu->vty.fc_interval = 1; pcu->vty.max_cs_ul = MAX_GPRS_CS; @@ -97,12 +106,17 @@ 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); INIT_LLIST_HEAD(&pcu->bts_list); + pcu->neigh_cache = neigh_cache_alloc(pcu); + pcu->si_cache = si_cache_alloc(pcu); + return pcu; } diff --git a/src/gprs_pcu.h b/src/gprs_pcu.h index a1cd1ed..8e18f89 100644 --- a/src/gprs_pcu.h +++ b/src/gprs_pcu.h @@ -29,6 +29,8 @@ #include "gprs_bssgp_pcu.h" #include "coding_scheme.h" +#include "neigh_cache.h" + #define LLC_CODEL_DISABLE 0 #define LLC_CODEL_USE_DEFAULT (-1) @@ -102,6 +104,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; @@ -116,6 +121,9 @@ struct gprs_bssgp_pcu bssgp; struct osmo_tdef *T_defs; /* timers controlled by PCU */ + + struct neigh_cache *neigh_cache; /* ARFC+BSIC -> CGI PS cache */ + struct si_cache *si_cache; /* ARFC+BSIC -> CGI PS cache */ }; 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..1a60792 --- /dev/null +++ b/src/nacc_fsm.c @@ -0,0 +1,633 @@ +/* nacc_fsm.c + * + * Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de> + * Author: Pau Espin Pedrol <pespin 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 + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#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> +#include <neigh_cache.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 } +}; + +/* TS 44 060 11.2.9e Packet Neighbour Cell Data */ +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); + 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; + PNCDContainer_t container; + size_t max_len, len_to_write; + uint8_t *cont_buf; + uint8_t si_type = ctx->si_info.type_psi ? 0x01 : 0x0; + + memset(&container, 0, sizeof(container)); + if (ctx->container_idx == 0) { + container.UnionType = 1; /* with ID */ + container.u.PNCD_Container_With_ID.ARFCN = ctx->neigh_key.tgt_arfcn; + container.u.PNCD_Container_With_ID.BSIC = ctx->neigh_key.tgt_bsic; + cont_buf = &container.u.PNCD_Container_With_ID.CONTAINER[0]; + max_len = sizeof(container.u.PNCD_Container_With_ID.CONTAINER) - 1; + } else { + container.UnionType = 0; /* without ID */ + cont_buf = &container.u.PNCD_Container_Without_ID.CONTAINER[0]; + max_len = sizeof(container.u.PNCD_Container_Without_ID.CONTAINER) - 1; + } + + len_to_write = ctx->si_info.si_len - ctx->si_info_bytes_sent; + + if (len_to_write == 0) { + /* We sent all info on last message filing it exactly, we now send a zeroed one to finish */ + ctx->all_si_info_sent = true; + *cont_buf = (si_type << 5) | 0x00; + } else if (len_to_write >= max_len) { + /* We fill the rlcmac block, we'll need more messages */ + *cont_buf = (si_type << 5) | 0x1F; + memcpy(cont_buf + 1, &ctx->si_info.si_buf[ctx->si_info_bytes_sent], max_len); + ctx->si_info_bytes_sent += max_len; + } else { + /* Last block, we don't fill it exactly */ + ctx->all_si_info_sent = true; + *cont_buf = (si_type << 5) | (len_to_write & 0x1F); + memcpy(cont_buf + 1, &ctx->si_info.si_buf[ctx->si_info_bytes_sent], len_to_write); + ctx->si_info_bytes_sent += len_to_write; + } + + 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); + + write_packet_neighbour_cell_data(mac_control_block, + tfi_is_dl, tfi, container_id, + ctx->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); + + ctx->container_idx++; + + 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; + write_packet_cell_change_continue(mac_control_block, tfi_is_dl, tfi, true, + ctx->neigh_key.tgt_arfcn, ctx->neigh_key.tgt_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 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; +} + + +//////////////// +// FSM states // +//////////////// + +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; + struct gprs_rlcmac_bts *bts = ctx->ms->bts; + 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->neigh_key.local_lac = bts->cgi_ps.rai.lac.lac; + ctx->neigh_key.local_ci = bts->cgi_ps.cell_identity; + ctx->neigh_key.tgt_arfcn = notif->Target_Cell.u.Target_Cell_GSM_Notif.ARFCN; + ctx->neigh_key.tgt_bsic = notif->Target_Cell.u.Target_Cell_GSM_Notif.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 gprs_pcu *pcu = bts->pcu; + const struct osmo_cell_global_id_ps *cgi_ps; + struct ctrl_cmd *cmd; + int rc; + + /* First try to find the value in the cache */ + cgi_ps = neigh_cache_lookup_value(pcu->neigh_cache, &ctx->neigh_key); + if (cgi_ps) { + ctx->cgi_ps = *cgi_ps; + nacc_fsm_state_chg(fi, NACC_ST_WAIT_REQUEST_SI); + return; + } + + /* CGI-PS not in cache, resolve it using BSC Neighbor Resolution CTRL interface */ + cmd = ctrl_cmd_create(ctx, CTRL_TYPE_GET); + if (!cmd) { + LOGPFSML(fi, LOGL_ERROR, "CTRL msg creation failed\n"); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + return; + } + + cmd->id = talloc_asprintf(cmd, "1"); + cmd->variable = talloc_asprintf(cmd, "neighbor_resolve_cgi_ps_from_lac_ci.%d.%d.%d.%d", + ctx->neigh_key.local_lac, ctx->neigh_key.local_ci, + ctx->neigh_key.tgt_arfcn, ctx->neigh_key.tgt_bsic); + 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) +{ + switch (event) { + case NACC_EV_CELL_CHG_NOTIFICATION: + break; + case NACC_EV_RX_RESOLVE_RAC_CI: + /* Assumption: ctx->cgi_ps has been filled by caller of the event */ + nacc_fsm_state_chg(fi, NACC_ST_WAIT_REQUEST_SI); + break; + default: + OSMO_ASSERT(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 gprs_rlcmac_bts *bts = ctx->ms->bts; + struct gprs_pcu *pcu = bts->pcu; + struct bssgp_ran_information_pdu pdu; + const struct si_cache_value *si; + int rc; + + /* First check if we have SI info for the target cell in cache */ + si = si_cache_lookup_value(pcu->si_cache, &ctx->cgi_ps); + if (si) { + /* Copy info since cache can be deleted at any point */ + memcpy(&ctx->si_info, si, sizeof(ctx->si_info)); + /* 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); + return; + } + + /* SI info not in cache, resolve it using RIM procedure against SGSN */ + 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; + } +} + + +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; + struct si_cache_entry *entry; + + switch (event) { + case NACC_EV_SI_INFO_RECEIVED: + entry = (struct si_cache_entry *)data; + /* Copy info since cache can be deleted at any point */ + memcpy(&ctx->si_info, &entry->value, sizeof(ctx->si_info)); + /* 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); + if (!data_ctx->msg) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + return; + } + if (ctx->all_si_info_sent) /* DONE */ + 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; + /* after cleanup() 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_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 + neigh_cache_add(ctx->ms->bts->pcu->neigh_cache, &ctx->neigh_key, &ctx->cgi_ps); + + 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) { + osmo_wqueue_clear(&ctx->neigh_ctrl_conn->write_queue); + osmo_fd_unregister(&ctx->neigh_ctrl_conn->write_queue.bfd); + 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; + } + llist_add(&ctx->neigh_ctrl_conn->list_entry, &ctx->neigh_ctrl->ccon_list); + + 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..d660454 --- /dev/null +++ b/src/nacc_fsm.h @@ -0,0 +1,65 @@ +/* nacc_fsm.h + * + * Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de> + * Author: Pau Espin Pedrol <pespin 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 + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#pragma once + +#include <osmocom/core/fsm.h> +#include <osmocom/gsm/gsm23003.h> + +#include <neigh_cache.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, + NACC_EV_SI_INFO_RECEIVED, /* data: struct si_cache_entry* */ + NACC_EV_CREATE_RLCMAC_MSG, /* data: struct nacc_ev_create_rlcmac_msg_ctx* */ +}; + +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; + struct neigh_cache_entry_key neigh_key; /* target cell info from MS */ + struct osmo_cell_global_id_ps cgi_ps; /* target cell info resolved from req_{arfcn+bsic} */ + struct si_cache_value si_info; /* SI info resolved from SGSN, to be sent to MS */ + size_t si_info_bytes_sent; /* How many bytes out of si_info->si_len were already sent to MS */ + size_t container_idx; /* Next container_idx to assign when sending Packet Neighbor Data message */ + bool all_si_info_sent; /* Did we already encode all the SI info? */ +}; + +/* 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 during event processing */ +}; + +struct nacc_fsm_ctx *nacc_fsm_alloc(struct GprsMs* ms); diff --git a/src/neigh_cache.c b/src/neigh_cache.c new file mode 100644 index 0000000..cf77dc8 --- /dev/null +++ b/src/neigh_cache.c @@ -0,0 +1,254 @@ +/* si_cache.c + * + * Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de> + * Author: Pau Espin Pedrol <pespin 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 + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <string.h> +#include <talloc.h> + +#include <osmocom/core/utils.h> + +#include <neigh_cache.h> + +#define KEEP_TIME_DEFAULT_SEC 5 + +/*TODO: add a timer to the_pcu T_defs, pass value to struct neigh_cache instead of KEEP_TIME_DEFAULT_SEC */ + +static void neigh_cache_schedule_cleanup(struct neigh_cache *cache); +static void neigh_cache_cleanup_cb(void *data) { + struct timespec now, threshold; + struct neigh_cache *cache = (struct neigh_cache *)data; + struct neigh_cache_entry *it, *tmp; + + osmo_clock_gettime(CLOCK_MONOTONIC, &now); + + /* Instead of adding keep_time_intval to each, substract it from now once */ + timespecsub(&now, &cache->keep_time_intval, &threshold); + + llist_for_each_entry_safe(it, tmp, &cache->list, list) { + if (timespeccmp(&threshold, &it->update_ts, >)) + break; + llist_del(&it->list); + talloc_free(it); + } + + neigh_cache_schedule_cleanup(cache); +} + +static void neigh_cache_schedule_cleanup(struct neigh_cache *cache) { + struct neigh_cache_entry *it; + struct timespec now, threshold, result; + + /* First item is the one with oldest update_ts */ + it = llist_first_entry_or_null(&cache->list, struct neigh_cache_entry, list); + if (!it) + return; + + osmo_clock_gettime(CLOCK_MONOTONIC, &now); + + timespecadd(&it->update_ts, &cache->keep_time_intval, &threshold); + + if (timespeccmp(&now, &threshold, >=)) { + /* Too late, let's flush now synchonously */ + neigh_cache_cleanup_cb(cache); + } else { + timespecsub(&threshold, &now, &result); + osmo_timer_schedule(&cache->cleanup_timer, result.tv_sec, result.tv_nsec*1000); + } +} + +struct neigh_cache *neigh_cache_alloc(void *ctx) +{ + struct neigh_cache *cache = talloc_zero(ctx, struct neigh_cache); + INIT_LLIST_HEAD(&cache->list); + osmo_timer_setup(&cache->cleanup_timer, neigh_cache_cleanup_cb, cache); + cache->keep_time_intval = (struct timespec){ .tv_sec = KEEP_TIME_DEFAULT_SEC, .tv_nsec = 0}; + return cache; + +} +struct neigh_cache_entry *neigh_cache_add(struct neigh_cache *cache, + const struct neigh_cache_entry_key *key, + const struct osmo_cell_global_id_ps *value) +{ + struct neigh_cache_entry *it; + + /* First check if it already exists. If so, simply update timer+value */ + it = neigh_cache_lookup_entry(cache, key); + if (!it) { + it = talloc_zero(cache, struct neigh_cache_entry); + } else { + /* remove item, we'll add it to the end to have them sorted by last update */ + llist_del(&it->list); + } + + it->value = *value; + memcpy(&it->value, value, sizeof(it->value)); + OSMO_ASSERT(osmo_clock_gettime(CLOCK_MONOTONIC, &it->update_ts) == 0); + llist_add_tail(&it->list, &cache->list); + neigh_cache_schedule_cleanup(cache); + return it; +} + +struct neigh_cache_entry *neigh_cache_lookup_entry(struct neigh_cache *cache, + const struct neigh_cache_entry_key *key) +{ + struct neigh_cache_entry *tmp; + llist_for_each_entry(tmp, &cache->list, list) { + if (memcmp(&tmp->key, key, sizeof(*key)) == 0) + return tmp; + } + return NULL; +} + +const struct osmo_cell_global_id_ps *neigh_cache_lookup_value(struct neigh_cache *cache, + const struct neigh_cache_entry_key *key) +{ + struct neigh_cache_entry *it = neigh_cache_lookup_entry(cache, key); + if (it) + return &it->value; + return NULL; +} + +void neigh_cache_free(struct neigh_cache *cache) +{ + struct neigh_cache_entry *it, *tmp; + if (!cache) + return; + + llist_for_each_entry_safe(it, tmp, &cache->list, list) { + llist_del(&it->list); + talloc_free(it); + } + osmo_timer_del(&cache->cleanup_timer); + talloc_free(cache); +} + + +//////////////////// +// SI CACHE +/////////////////// + +/*TODO: add a timer to the_pcu T_defs, pass value to struct neigh_cache instead of KEEP_TIME_DEFAULT_SEC */ + +static void si_cache_schedule_cleanup(struct si_cache *cache); +static void si_cache_cleanup_cb(void *data) { + struct timespec now, threshold; + struct si_cache *cache = (struct si_cache *)data; + struct si_cache_entry *it, *tmp; + + osmo_clock_gettime(CLOCK_MONOTONIC, &now); + + /* Instead of adding keep_time_intval to each, substract it from now once */ + timespecsub(&now, &cache->keep_time_intval, &threshold); + + llist_for_each_entry_safe(it, tmp, &cache->list, list) { + if (timespeccmp(&threshold, &it->update_ts, >)) + break; + llist_del(&it->list); + talloc_free(it); + } + + si_cache_schedule_cleanup(cache); +} + +static void si_cache_schedule_cleanup(struct si_cache *cache) { + struct si_cache_entry *it; + struct timespec now, threshold, result; + + /* First item is the one with oldest update_ts */ + it = llist_first_entry_or_null(&cache->list, struct si_cache_entry, list); + if (!it) + return; + + osmo_clock_gettime(CLOCK_MONOTONIC, &now); + + timespecadd(&it->update_ts, &cache->keep_time_intval, &threshold); + + if (timespeccmp(&now, &threshold, >=)) { + /* Too late, let's flush now synchonously */ + si_cache_cleanup_cb(cache); + } else { + timespecsub(&threshold, &now, &result); + osmo_timer_schedule(&cache->cleanup_timer, result.tv_sec, result.tv_nsec*1000); + } +} + +struct si_cache *si_cache_alloc(void *ctx) +{ + struct si_cache *cache = talloc_zero(ctx, struct si_cache); + INIT_LLIST_HEAD(&cache->list); + osmo_timer_setup(&cache->cleanup_timer, si_cache_cleanup_cb, cache); + cache->keep_time_intval = (struct timespec){ .tv_sec = KEEP_TIME_DEFAULT_SEC, .tv_nsec = 0}; + return cache; +} +struct si_cache_entry *si_cache_add(struct si_cache *cache, + const struct osmo_cell_global_id_ps *key, + const struct si_cache_value *value) +{ + struct si_cache_entry *it; + + /* First check if it already exists. If so, simply update timer+value */ + it = si_cache_lookup_entry(cache, key); + if (!it) { + it = talloc_zero(cache, struct si_cache_entry); + } else { + /* remove item, we'll add it to the end to have them sorted by last update */ + llist_del(&it->list); + } + + it->value = *value; + memcpy(&it->value, value, sizeof(it->value)); + OSMO_ASSERT(osmo_clock_gettime(CLOCK_MONOTONIC, &it->update_ts) == 0); + llist_add_tail(&it->list, &cache->list); + si_cache_schedule_cleanup(cache); + return it; +} + +struct si_cache_entry *si_cache_lookup_entry(struct si_cache *cache, + const struct osmo_cell_global_id_ps *key) +{ + struct si_cache_entry *tmp; + llist_for_each_entry(tmp, &cache->list, list) { + if (memcmp(&tmp->key, key, sizeof(*key)) == 0) + return tmp; + } + return NULL; +} + +const struct si_cache_value *si_cache_lookup_value(struct si_cache *cache, + const struct osmo_cell_global_id_ps *key) +{ + struct si_cache_entry *it = si_cache_lookup_entry(cache, key); + if (it) + return &it->value; + return NULL; +} + +void si_cache_free(struct si_cache *cache) +{ + struct si_cache_entry *it, *tmp; + if (!cache) + return; + + llist_for_each_entry_safe(it, tmp, &cache->list, list) { + llist_del(&it->list); + talloc_free(it); + } + osmo_timer_del(&cache->cleanup_timer); + talloc_free(cache); +} diff --git a/src/neigh_cache.h b/src/neigh_cache.h new file mode 100644 index 0000000..68f5a5d --- /dev/null +++ b/src/neigh_cache.h @@ -0,0 +1,99 @@ +/* neigh_cache.h + * + * Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de> + * Author: Pau Espin Pedrol <pespin 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 + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#pragma once + +#include <stdint.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/timer.h> + +#include <osmocom/gsm/gsm23003.h> + +//////////////////// +// NEIGH CACHE +/////////////////// + +/* ARFC+BSIC -> CGI PS cache */ +struct neigh_cache { + struct llist_head list; /* list of neigh_cache_entry items */ + struct osmo_timer_list cleanup_timer; /* Timer removing too-old entries */ + struct timespec keep_time_intval; +}; + +struct neigh_cache_entry_key { + uint16_t local_lac; + uint16_t local_ci; + uint16_t tgt_arfcn; + uint8_t tgt_bsic; +}; + +struct neigh_cache_entry { + struct llist_head list; /* to be included in neigh_cache->list */ + //struct neigh_cache *cache; /* backpointer */ + struct timespec update_ts; + struct neigh_cache_entry_key key; + struct osmo_cell_global_id_ps value; +}; + +struct neigh_cache *neigh_cache_alloc(void *ctx); +struct neigh_cache_entry *neigh_cache_add(struct neigh_cache *cache, + const struct neigh_cache_entry_key *key, + const struct osmo_cell_global_id_ps *value); +struct neigh_cache_entry *neigh_cache_lookup_entry(struct neigh_cache *cache, + const struct neigh_cache_entry_key *key); +const struct osmo_cell_global_id_ps *neigh_cache_lookup_value(struct neigh_cache *cache, + const struct neigh_cache_entry_key *key); +void neigh_cache_free(struct neigh_cache *cache); + + +//////////////////// +// SI CACHE +/////////////////// + +/* CGI-PS-> SI cache */ +struct si_cache { + struct llist_head list; /* list of si_cache_entry items */ + struct osmo_timer_list cleanup_timer; /* Timer removing too-old entries */ + struct timespec keep_time_intval; +}; + +struct si_cache_value { + char si_buf[512]; + size_t si_len; + bool type_psi; +}; + +struct si_cache_entry { + struct llist_head list; /* to be included in si_cache->list */ + //struct si_cache *cache; /* backpointer */ + struct timespec update_ts; + struct osmo_cell_global_id_ps key; + struct si_cache_value value; +}; + +struct si_cache *si_cache_alloc(void *ctx); +struct si_cache_entry *si_cache_add(struct si_cache *cache, + const struct osmo_cell_global_id_ps *key, + const struct si_cache_value *value); +struct si_cache_entry *si_cache_lookup_entry(struct si_cache *cache, + const struct osmo_cell_global_id_ps *key); +const struct si_cache_value *si_cache_lookup_value(struct si_cache *cache, + const struct osmo_cell_global_id_ps *key); +void si_cache_free(struct si_cache *cache); 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/+/22385 To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: osmo-pcu Gerrit-Branch: master Gerrit-Change-Id: Id35f40d05f3e081f32fddbf1fa34cb338db452ca Gerrit-Change-Number: 22385 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/20210122/d15f183e/attachment.htm>