Introduce NACC support

A new nacc_fsm is introduced per MS object, with its partner priv
structure struct nacc_fsm_ctx, which exists and is available in the MS
object only during the duration of the NACC procedure.

The NACC context is created on an MS whenever a Pkt Cell Change
Notification is received on Uplink RLCMAC, which asks for neighbor
information of a given ARFCN+BSIC.

First, the target ARFCN+BSIC needs to be translated into a CGI-PS
(RAC+CI) address. That's done by asking the BSC through the Neighbour
Resolution Service available in osmo-bsc using the CTRL interface.

Once the CGI-PS of the target cell is known, PCU starts a RIM RAN-INFO
request against the SGSN (which will route the request as needed), and
wait for a response containing the SI bits from the target cell.

After the SI are received, the scheduler is instructed to eventually
poll a TBF for the MS originating the CCN, so that we can send the SI
encapsulated into multiple Packet Neighbor Cell Data messages on the
downlink.

One all the SI bits are sent, the scheduler is instructed to send a
Packet Cell Change Continue message.

Once the message above has been sent, the FSM autodestroys itself.

Caches are also introduced in this patch which allows for re-using
recently known translations ARFCN+BSIC -> CGI-PS and CGI-PS -> SI_INFO
respectively.

Change-Id: Id35f40d05f3e081f32fddbf1fa34cb338db452ca
This commit is contained in:
Pau Espin 2021-01-21 18:46:13 +01:00
parent 1e77ca88af
commit c0a250d17d
24 changed files with 1407 additions and 5 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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();
}

View File

@ -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@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 @@ enum {
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_cs_ul(struct gprs_rlcmac_bts *bts, uint8_t cs_ul);
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

View File

@ -1731,3 +1731,56 @@ void Encoding::write_packet_access_reject(
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;
}

View File

@ -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 @@ 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

View File

@ -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:

View File

@ -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",

View File

@ -46,6 +46,7 @@ enum {
DTBFUL,
DNS,
DPCU,
DNACC,
DRIM,
aDebug_LastEntry
};

View File

@ -26,6 +26,7 @@
#include "gprs_debug.h"
#include "gprs_codel.h"
#include "pcu_utils.h"
#include "nacc_fsm.h"
#include <time.h>
@ -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;
}

View File

@ -40,6 +40,7 @@ extern "C" {
#include <osmocom/gsm/gsm48.h>
#include "coding_scheme.h"
#include <gsm_rlcmac.h>
#include <stdint.h>
#include <stddef.h>
@ -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);

View File

@ -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 @@ 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;
}

View File

@ -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 */
};

View File

@ -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) {

642
src/nacc_fsm.c Normal file
View File

@ -0,0 +1,642 @@
/* nacc_fsm.c
*
* Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@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_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;
}

64
src/nacc_fsm.h Normal file
View File

@ -0,0 +1,64 @@
/* nacc_fsm.h
*
* Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@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_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);

290
src/neigh_cache.c Normal file
View File

@ -0,0 +1,290 @@
/* si_cache.c
*
* Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@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 <inttypes.h>
#include <osmocom/core/utils.h>
#include <neigh_cache.h>
#include <gprs_debug.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 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);
}

101
src/neigh_cache.h Normal file
View File

@ -0,0 +1,101 @@
/* neigh_cache.h
*
* Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@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 <inttypes.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/timer.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/gprs/gprs_bssgp_rim.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;
};
#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);

View File

@ -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 @@ 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);

View File

@ -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,

View File

@ -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<gprs_rlcmac_tbf> *tbf_list, uint8_t tfi,
enum gprs_rlcmac_tbf_direction dir);

View File

@ -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();
}

View File

@ -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
}

View File

@ -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)