diff --git a/configure.ac b/configure.ac index 66b35953..60dcb875 100644 --- a/configure.ac +++ b/configure.ac @@ -84,6 +84,7 @@ fi 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 f85a4568..eefbea1d 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 @@ libgprs_la_SOURCES = \ 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 @@ noinst_HEADERS = \ 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 @@ osmo_pcu_remote_LDADD = \ libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(COMMON_LA) endif @@ -191,6 +196,7 @@ osmo_pcu_LDADD = \ libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(COMMON_LA) diff --git a/src/bts.cpp b/src/bts.cpp index b5bb3a26..fb7de649 100644 --- a/src/bts.cpp +++ b/src/bts.cpp @@ -140,6 +140,9 @@ static const struct rate_ctr_desc bts_ctr_description[] = { { "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 @@ struct GprsMs *bts_ms_by_imsi(struct gprs_rlcmac_bts *bts, const char *imsi) { 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 7f437e37..ea15caf4 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 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 da63a61b..4ebfa35f 100644 --- a/src/encoding.h +++ b/src/encoding.h @@ -22,17 +22,22 @@ #include +#ifdef __cplusplus extern "C" { +#endif #include #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 @@ public: 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 cf43a8f4..a191fffc 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,65 @@ static bool match_app_id(const struct bssgp_ran_information_pdu *pdu, enum bssgp return false; } +static int handle_ran_info_response_nacc(const struct bssgp_ran_inf_app_cont_nacc *nacc, struct gprs_rlcmac_bts *bts) +{ + struct si_cache_value val; + struct si_cache_entry *entry; + struct llist_head *tmp; + int i; + + LOGP(DRIM, LOGL_INFO, "Rx RAN-INFO cell=%s type=%sBCCH num_si=%d\n", + osmo_cgi_ps_name(&nacc->reprt_cell), + nacc->type_psi ? "P" : "", 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 (osmo_cgi_ps_cmp(&nacc->reprt_cell, &ms->nacc->cgi_ps) != 0) + continue; + osmo_fsm_inst_dispatch(ms->nacc->fi, NACC_EV_RX_SI, entry); + } + return 0; +} + +static int handle_ran_info_response(const struct bssgp_ran_information_pdu *pdu, struct gprs_rlcmac_bts *bts) +{ + const struct bssgp_ran_inf_rim_cont *ran_info = &pdu->decoded.rim_cont; + char ri_src_str[64]; + + if (ran_info->app_err) { + LOGP(DRIM, LOGL_ERROR, + "%s Rx RAN-INFO with an app error! cause: %s\n", + bssgp_rim_ri_name_buf(ri_src_str, sizeof(ri_src_str), &pdu->routing_info_src), + bssgp_nacc_cause_str(ran_info->u.app_err_cont_nacc.nacc_cause)); + return -1; + } + + switch (pdu->decoded.rim_cont.app_id) { + case BSSGP_RAN_INF_APP_ID_NACC: + handle_ran_info_response_nacc(&ran_info->u.app_cont_nacc, bts); + break; + default: + LOGP(DRIM, LOGL_ERROR, "%s Rx RAN-INFO with unknown/wrong application ID %s received\n", + bssgp_rim_ri_name_buf(ri_src_str, sizeof(ri_src_str), &pdu->routing_info_src), + bssgp_ran_inf_app_id_str(pdu->decoded.rim_cont.app_id)); + return -1; + } + return 0; +} + int handle_rim(struct osmo_bssgp_prim *bp) { struct msgb *msg = bp->oph.msg; @@ -151,8 +213,7 @@ int handle_rim(struct osmo_bssgp_prim *bp) 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(pdu, 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 669ea270..0cbc4880 100644 --- a/src/gprs_debug.cpp +++ b/src/gprs_debug.cpp @@ -119,6 +119,13 @@ static const struct log_info_cat default_categories[] = { .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 8df405af..cebeabce 100644 --- a/src/gprs_debug.h +++ b/src/gprs_debug.h @@ -46,6 +46,7 @@ enum { DTBFUL, DNS, DPCU, + DNACC, DRIM, aDebug_LastEntry }; diff --git a/src/gprs_ms.c b/src/gprs_ms.c index 3948abfa..8078b108 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 @@ -937,3 +938,36 @@ struct gprs_rlcmac_tbf *ms_tbf(const struct GprsMs *ms, enum gprs_rlcmac_tbf_dir 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_RX_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 39d65de2..c57bbf6a 100644 --- a/src/gprs_ms.h +++ b/src/gprs_ms.h @@ -40,6 +40,7 @@ extern "C" { #include #include "coding_scheme.h" +#include #include #include @@ -100,6 +101,7 @@ struct GprsMs { 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 @@ static inline struct gprs_rlcmac_dl_tbf *ms_dl_tbf(const struct GprsMs *ms) {ret 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 07861993..9a21cdb4 100644 --- a/src/gprs_pcu.c +++ b/src/gprs_pcu.c @@ -22,6 +22,7 @@ #include #include +#include #include "gprs_pcu.h" #include "bts.h" @@ -40,12 +41,20 @@ static struct osmo_tdef T_defs_pcu[] = { { .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 @@ struct gprs_pcu *gprs_pcu_alloc(void *ctx) 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 a1cd1ed1..8e18f89e 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 @@ struct gprs_pcu { 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_pcu { 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 6dea949e..58ed1ac4 100644 --- a/src/gprs_rlcmac_sched.cpp +++ b/src/gprs_rlcmac_sched.cpp @@ -38,6 +38,7 @@ struct tbf_sched_candidates { 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 @@ static uint32_t sched_poll(struct gprs_rlcmac_bts *bts, 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 @@ states? */ 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 @@ static struct msgb *sched_select_ctrl_msg( 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 @@ static struct msgb *sched_select_ctrl_msg( 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 00000000..0a20ae6d --- /dev/null +++ b/src/nacc_fsm.c @@ -0,0 +1,642 @@ +/* nacc_fsm.c + * + * Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH + * Author: Pau Espin Pedrol + * + * 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 + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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_RX_CELL_CHG_NOTIFICATION, "RX_CELL_CHG_NOTIFICATION" }, + { NACC_EV_RX_RAC_CI, "RX_RAC_CI" }, + { NACC_EV_RX_SI, "RX_SI" }, + { 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, + const struct gprs_rlcmac_tbf *tbf, + bool *all_si_info_sent) +{ + 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 */ + *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 */ + *all_si_info_sent = false; + *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 */ + *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; +} + +/* TS 44 060 11.2.2a Packet Cell Change Continue */ +static struct msgb *create_packet_cell_chg_continue(const struct nacc_fsm_ctx *ctx, + const 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(const 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 = { + .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 = RIM_PDU_TYPE_SING_REP, + }, + .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_RX_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); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + 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 */ + + LOGPFSML(fi, LOGL_DEBUG, "No CGI-PS found in cache, resolving " NEIGH_CACHE_ENTRY_KEY_FMT "...\n", + NEIGH_CACHE_ENTRY_KEY_ARGS(&ctx->neigh_key)); + + cmd = ctrl_cmd_create(ctx, CTRL_TYPE_GET); + if (!cmd) { + LOGPFSML(fi, LOGL_ERROR, "CTRL msg creation failed\n"); + goto err_term; + } + + 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); + goto err_term; + } + + talloc_free(cmd); + return; + +err_term: + talloc_free(cmd); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); +} + + +static void st_wait_resolve_rac_ci(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case NACC_EV_RX_CELL_CHG_NOTIFICATION: + break; + case NACC_EV_RX_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_RX_SI: + 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); + } +} + +/* st_tx_neighbour_data_on_enter: + * At this point, we already received all required SI information to send stored + * in struct nacc_fsm_ctx. 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; + bool all_si_info_sent; + + 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, &all_si_info_sent); + if (!data_ctx->msg) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + return; + } + if (all_si_info_sent) /* DONE */ + nacc_fsm_state_chg(fi, NACC_ST_TX_CELL_CHG_CONTINUE); + break; + default: + OSMO_ASSERT(0); + } +} + +/* st_cell_cgh_continue_on_enter: + * 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 and then we are done + */ + +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); + 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_RX_CELL_CHG_NOTIFICATION), + .out_state_mask = + X(NACC_ST_WAIT_RESOLVE_RAC_CI), + .name = "INITIAL", + .action = st_initial, + }, + [NACC_ST_WAIT_RESOLVE_RAC_CI] = { + .in_event_mask = + X(NACC_EV_RX_RAC_CI), + .out_state_mask = + X(NACC_ST_WAIT_REQUEST_SI), + .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_RX_CELL_CHG_NOTIFICATION) | + X(NACC_EV_RX_SI), + .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_RX_CELL_CHG_NOTIFICATION) | + X(NACC_EV_RX_SI) | + X(NACC_EV_CREATE_RLCMAC_MSG), + .out_state_mask = + X(NACC_ST_TX_CELL_CHG_CONTINUE), + .name = "TX_NEIGHBOUR_DATA", + .action = st_tx_neighbour_data, + }, + [NACC_ST_TX_CELL_CHG_CONTINUE] = { + .in_event_mask = + X(NACC_EV_RX_CELL_CHG_NOTIFICATION) | + X(NACC_EV_RX_SI) | + X(NACC_EV_CREATE_RLCMAC_MSG), + .out_state_mask = + X(NACC_ST_DONE), + .name = "TX_CELL_CHG_CONTINUE", + .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); + + /* Cache the cgi_ps so we can avoid requesting again same resolution for a while */ + neigh_cache_add(ctx->ms->bts->pcu->neigh_cache, &ctx->neigh_key, &ctx->cgi_ps); + + osmo_fsm_inst_dispatch(ctx->fi, NACC_EV_RX_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; + llist_add(&ctx->neigh_ctrl_conn->list_entry, &ctx->neigh_ctrl->ccon_list); + + 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 00000000..9107dafd --- /dev/null +++ b/src/nacc_fsm.h @@ -0,0 +1,64 @@ +/* nacc_fsm.h + * + * Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH + * Author: Pau Espin Pedrol + * + * 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 +#include + +#include + +struct GprsMs; +struct gprs_rlcmac_tbf; + +enum nacc_fsm_event { + NACC_EV_RX_CELL_CHG_NOTIFICATION, /* data: Packet_Cell_Change_Notification_t* */ + NACC_EV_RX_RAC_CI, /* no data passed, RAC_CI became available in neigh_cache */ + NACC_EV_RX_SI, /* 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 */ +}; + +/* 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 00000000..a4bdfbeb --- /dev/null +++ b/src/neigh_cache.c @@ -0,0 +1,290 @@ +/* si_cache.c + * + * Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH + * Author: Pau Espin Pedrol + * + * 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 +#include +#include + +#include + +#include +#include + +#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 inline bool neigh_cache_entry_key_eq(const struct neigh_cache_entry_key *a, + const struct neigh_cache_entry_key *b) +{ + return a->local_lac == b->local_lac && + a->local_ci == b->local_ci && + a->tgt_arfcn == b->tgt_arfcn && + a->tgt_bsic == b->tgt_bsic; +} + +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; + LOGP(DNACC, LOGL_DEBUG, + "neigh_cache: Removing entry " NEIGH_CACHE_ENTRY_KEY_FMT " => %s\n", + NEIGH_CACHE_ENTRY_KEY_ARGS(&it->key), osmo_cgi_ps_name(&it->value)); + 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 asynchonously so newly added isn't + * immediatelly freed before return. */ + result = (struct timespec){ .tv_sec = 0, .tv_nsec = 0 }; + } 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); + OSMO_ASSERT(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) { + LOGP(DNACC, LOGL_DEBUG, + "neigh_cache: Inserting new entry " NEIGH_CACHE_ENTRY_KEY_FMT " => %s\n", + NEIGH_CACHE_ENTRY_KEY_ARGS(key), osmo_cgi_ps_name(value)); + it = talloc_zero(cache, struct neigh_cache_entry); + OSMO_ASSERT(it); + memcpy(&it->key, key, sizeof(it->key)); + } else { + LOGP(DNACC, LOGL_DEBUG, + "neigh_cache: Updating entry " NEIGH_CACHE_ENTRY_KEY_FMT " => (%s -> %s)\n", + NEIGH_CACHE_ENTRY_KEY_ARGS(key), osmo_cgi_ps_name(&it->value), osmo_cgi_ps_name2(value)); + /* remove item, we'll add it to the end to have them sorted by last update */ + llist_del(&it->list); + } + + 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 (neigh_cache_entry_key_eq(&tmp->key, key)) + 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; + LOGP(DNACC, LOGL_DEBUG, "si_cache: Removing entry %s\n", + osmo_cgi_ps_name(&it->key)); + 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 asynchonously so newly added isn't + * immediatelly freed before return. */ + result = (struct timespec){ .tv_sec = 0, .tv_nsec = 0 }; + } 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); + OSMO_ASSERT(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) { + LOGP(DNACC, LOGL_DEBUG, "si_cache: Inserting new entry %s\n", + osmo_cgi_ps_name(key)); + it = talloc_zero(cache, struct si_cache_entry); + OSMO_ASSERT(it); + memcpy(&it->key, key, sizeof(it->key)); + } else { + LOGP(DNACC, LOGL_DEBUG, "si_cache: Updating entry %s\n", + osmo_cgi_ps_name(&it->key)); + /* remove item, we'll add it to the end to have them sorted by last update */ + llist_del(&it->list); + } + + 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 (osmo_cgi_ps_cmp(&tmp->key, 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 00000000..3fd56b7a --- /dev/null +++ b/src/neigh_cache.h @@ -0,0 +1,101 @@ +/* neigh_cache.h + * + * Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH + * Author: Pau Espin Pedrol + * + * 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 +#include + +#include +#include + +#include +#include + +//////////////////// +// 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; +}; +#define NEIGH_CACHE_ENTRY_KEY_FMT "%" PRIu16 "-%" PRIu16 "-%" PRIu16 "-%" PRIu8 +#define NEIGH_CACHE_ENTRY_KEY_ARGS(key) (key)->local_lac, (key)->local_ci, (key)->tgt_arfcn, (key)->tgt_bsic + +struct neigh_cache_entry { + struct llist_head list; /* to be included in neigh_cache->list */ + 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 { + uint8_t si_buf[BSSGP_RIM_PSI_LEN * 127]; /* 3GPP TS 48.018 11.3.63.2.1 */ + size_t si_len; + bool type_psi; +}; + +struct si_cache_entry { + struct llist_head list; /* to be included in si_cache->list */ + 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 4b502c9d..8f97f306 100644 --- a/src/pcu_vty.c +++ b/src/pcu_vty.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include "pcu_vty.h" @@ -1018,6 +1019,22 @@ DEFUN_USRATTR(cfg_pcu_gb_dialect, 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 @@ int pcu_vty_init(void) 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 5a329f3d..2028ba20 100644 --- a/src/pdch.cpp +++ b/src/pdch.cpp @@ -685,6 +685,33 @@ void gprs_rlcmac_pdch::rcv_measurement_report(Packet_Measurement_Report_t *repor 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 @@ int gprs_rlcmac_pdch::rcv_control_block(const uint8_t *data, uint8_t data_len, 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 8871986f..d5965315 100644 --- a/src/pdch.h +++ b/src/pdch.h @@ -139,6 +139,7 @@ private: 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 *tbf_list, uint8_t tfi, enum gprs_rlcmac_tbf_direction dir); diff --git a/src/tbf.cpp b/src/tbf.cpp index 0fec476c..54ae4fac 100644 --- a/src/tbf.cpp +++ b/src/tbf.cpp @@ -1198,3 +1198,8 @@ bool tbf_is_tfi_assigned(const struct gprs_rlcmac_tbf *tbf) { 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 983d38cc..a2c47477 100644 --- a/src/tbf.h +++ b/src/tbf.h @@ -204,6 +204,7 @@ uint8_t tbf_first_common_ts(const struct gprs_rlcmac_tbf *tbf); 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 c5996360..a7771b9f 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 @@ alloc_AllocTest_LDADD = \ $(top_builddir)/src/libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(COMMON_LA) @@ -23,6 +24,7 @@ alloc_MslotTest_LDADD = \ $(top_builddir)/src/libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(COMMON_LA) @@ -31,6 +33,7 @@ tbf_TbfTest_LDADD = \ $(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 @@ tbf_TbfTest_LDFLAGS = -Wl,--wrap=pcu_sock_send 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 @@ edge_EdgeTest_LDADD = \ $(top_builddir)/src/libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(COMMON_LA) @@ -56,6 +61,7 @@ emu_pcu_emu_LDADD = \ $(top_builddir)/src/libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(COMMON_LA) @@ -64,6 +70,7 @@ types_TypesTest_LDADD = \ $(top_builddir)/src/libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(COMMON_LA) @@ -72,6 +79,7 @@ ms_MsTest_LDADD = \ $(top_builddir)/src/libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(COMMON_LA) @@ -83,6 +91,7 @@ llc_LlcTest_LDADD = \ $(top_builddir)/src/libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(COMMON_LA) @@ -97,6 +106,7 @@ llist_LListTest_LDADD = \ 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 @@ fn_FnTest_LDADD = \ $(top_builddir)/src/libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(COMMON_LA) @@ -113,6 +124,7 @@ app_info_AppInfoTest_LDADD = \ $(top_builddir)/src/libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(COMMON_LA)