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: Id35f40d05f3e081f32fddbf1fa34cb338db452cachanges/85/22385/12
parent
1e77ca88af
commit
c0a250d17d
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -46,6 +46,7 @@ enum {
|
|||
DTBFUL,
|
||||
DNS,
|
||||
DPCU,
|
||||
DNACC,
|
||||
DRIM,
|
||||
aDebug_LastEntry
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||