389 lines
12 KiB
C
389 lines
12 KiB
C
/* bts_anr_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 <bts_anr_fsm.h>
|
|
#include <ms_anr_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))
|
|
|
|
/* Ask the MS to measure up to 5 neighbors at a time */
|
|
#define ANR_MAX_NEIGH_SUBSET 5
|
|
|
|
static const struct osmo_tdef_state_timeout bts_anr_fsm_timeouts[32] = {
|
|
[BTS_ANR_ST_DISABLED] = {},
|
|
[BTS_ANR_ST_ENABLED] = { .T = PCU_TDEF_ANR_SCHED_TBF },
|
|
};
|
|
|
|
/* Transition to a state, using the T timer defined in assignment_fsm_timeouts.
|
|
* The actual timeout value is in turn obtained from conn->T_defs.
|
|
* Assumes local variable fi exists. */
|
|
|
|
#define bts_anr_fsm_state_chg(fi, NEXT_STATE) \
|
|
osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, \
|
|
bts_anr_fsm_timeouts, \
|
|
((struct bts_anr_fsm_ctx*)(fi->priv))->bts->pcu->T_defs, \
|
|
-1)
|
|
|
|
const struct value_string bts_anr_fsm_event_names[] = {
|
|
{ BTS_ANR_EV_RX_ANR_REQ, "RX_ANR_REQ" },
|
|
{ BTS_ANR_EV_SCHED_MS_MEAS, "SCHED_MS_MEAS" },
|
|
{ BTS_ANR_EV_MS_MEAS_COMPL, "MS_MEAS_COMPL" },
|
|
{ BTS_ANR_EV_MS_MEAS_ABORTED, "MS_MEAS_ABORTED" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
static void copy_sort_arfcn_bsic(struct bts_anr_fsm_ctx *ctx, const struct gsm48_cell_desc *cell_list, unsigned int num_cells)
|
|
{
|
|
OSMO_ASSERT(num_cells <= ARRAY_SIZE(ctx->cell_list));
|
|
uint16_t last_min_arfcn = 0;
|
|
uint8_t last_min_bsic = 0;
|
|
ctx->num_cells = 0;
|
|
struct gprs_rlcmac_bts *bts = ctx->bts;
|
|
|
|
/* Copy over ARFCN+BSIC in an ARFCN then BSIC ascending ordered way */
|
|
while (ctx->num_cells < num_cells) {
|
|
bool found = false;
|
|
uint16_t curr_min_arfcn = 0xffff;
|
|
uint8_t curr_min_bsic = 0xff;
|
|
int i;
|
|
for (i = 0; i < num_cells; i++) {
|
|
uint16_t arfcn = (cell_list[i].arfcn_hi << 8) | cell_list[i].arfcn_lo;
|
|
uint8_t bsic = (cell_list[i].ncc << 3) | cell_list[i].bcc;
|
|
if ((arfcn > last_min_arfcn || (arfcn == last_min_arfcn && bsic > last_min_bsic)) &&
|
|
(arfcn < curr_min_arfcn || (arfcn == curr_min_arfcn && bsic < curr_min_bsic))) {
|
|
found = true;
|
|
curr_min_arfcn = arfcn;
|
|
curr_min_bsic = bsic;
|
|
}
|
|
}
|
|
if (!found)
|
|
break; /* we are done before copying all, probably due to duplicated arfcn in list */
|
|
|
|
/* Copy lower ARFCN+BSIC to dst */
|
|
if (curr_min_arfcn != bts->trx[0].arfcn || curr_min_bsic != bts->bsic) {
|
|
ctx->cell_list[ctx->num_cells] = (struct arfcn_bsic){
|
|
.arfcn = curr_min_arfcn,
|
|
.bsic = curr_min_bsic,
|
|
};
|
|
ctx->num_cells++;
|
|
LOGPFSML(ctx->fi, LOGL_DEBUG, "Added neigh cell to ANR list: ARFCN=%u BSIC=%u\n",
|
|
curr_min_arfcn, curr_min_bsic);
|
|
} else {
|
|
LOGPFSML(ctx->fi, LOGL_DEBUG, "Skip neigh cell to ANR list (itself): ARFCN=%u BSIC=%u\n",
|
|
curr_min_arfcn, curr_min_bsic);
|
|
}
|
|
last_min_arfcn = curr_min_arfcn;
|
|
last_min_bsic = curr_min_bsic;
|
|
}
|
|
}
|
|
|
|
static void rx_new_cell_list(struct bts_anr_fsm_ctx *ctx, const struct gsm_pcu_if_anr_req *anr_req)
|
|
{
|
|
unsigned int num_cells = anr_req->num_cells;
|
|
if (anr_req->num_cells > ARRAY_SIZE(anr_req->cell_list)) {
|
|
LOGPFSML(ctx->fi, LOGL_ERROR, "Too many cells received %u > %zu (max), trimming it\n",
|
|
anr_req->num_cells, ARRAY_SIZE(anr_req->cell_list));
|
|
num_cells = ARRAY_SIZE(anr_req->cell_list);
|
|
}
|
|
copy_sort_arfcn_bsic(ctx, (const struct gsm48_cell_desc *)anr_req->cell_list, num_cells);
|
|
}
|
|
|
|
static struct GprsMs *select_candidate_ms(struct gprs_rlcmac_bts *bts)
|
|
{
|
|
struct llist_head *tmp;
|
|
/* prio top to bottom: 0,1,2: */
|
|
struct GprsMs *ms_dl_tbf_assign = NULL;
|
|
|
|
/* We'll need a DL TBF. Take with higher priority an MS which already
|
|
* has one, otherwise one in process of acquiring one. In last place an
|
|
* MS which has no DL-TBF yet. */
|
|
llist_for_each(tmp, bts_ms_list(bts)) {
|
|
struct GprsMs *ms = llist_entry(tmp, typeof(*ms), list);
|
|
if (ms->anr) /* Don't pick MS already busy doing ANR */
|
|
continue;
|
|
if (!ms->dl_tbf)
|
|
continue;
|
|
switch (tbf_state((struct gprs_rlcmac_tbf*)ms->dl_tbf)) {
|
|
case TBF_ST_FLOW: /* Pick active DL-TBF as best option, early return: */
|
|
return ms;
|
|
case TBF_ST_ASSIGN:
|
|
ms_dl_tbf_assign = ms;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ms_dl_tbf_assign)
|
|
return ms_dl_tbf_assign;
|
|
|
|
llist_for_each(tmp, bts_ms_list(bts)) {
|
|
struct GprsMs *ms = llist_entry(tmp, typeof(*ms), list);
|
|
if (ms->anr) /* Don't pick MS already busy doing ANR */
|
|
continue;
|
|
if (!ms->dl_tbf) {
|
|
/* Trigger a Pkt Dl Assignment and do ANR procedure once it is active: */
|
|
struct gprs_rlcmac_dl_tbf *new_dl_tbf;
|
|
int rc;
|
|
rc = tbf_new_dl_assignment(ms->bts, ms, &new_dl_tbf);
|
|
if (rc < 0)
|
|
continue;
|
|
/* Fill the TBF with some LLC Dummy Command, since everyone expectes we send something to that DL TBF... */
|
|
uint16_t delay_csec = 0xffff;
|
|
/* The shortest dummy command (the spec requests at least 6 octets) */
|
|
const uint8_t llc_dummy_command[] = {
|
|
0x43, 0xc0, 0x01, 0x2b, 0x2b, 0x2b
|
|
};
|
|
dl_tbf_append_data(new_dl_tbf, delay_csec, &llc_dummy_command[0], ARRAY_SIZE(llc_dummy_command));
|
|
return ms;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Build up cell list subset for this MS to measure: */
|
|
static size_t take_next_cell_list_chunk(struct bts_anr_fsm_ctx *ctx, struct arfcn_bsic ms_cell_li[MAX_NEIGH_LIST_LEN])
|
|
{
|
|
unsigned int subset_len = ANR_MAX_NEIGH_SUBSET;
|
|
if (ctx->num_cells <= subset_len) {
|
|
memcpy(ms_cell_li, ctx->cell_list, ctx->num_cells * sizeof(ctx->cell_list[0]));
|
|
subset_len = ctx->num_cells;
|
|
} else if ((ctx->num_cells - ctx->next_cell) >= subset_len) {
|
|
memcpy(ms_cell_li, &ctx->cell_list[ctx->next_cell], subset_len * sizeof(ctx->cell_list[0]));
|
|
ctx->next_cell = (ctx->next_cell + subset_len) % ctx->num_cells;
|
|
} else {
|
|
unsigned int len = (ctx->num_cells - ctx->next_cell);
|
|
memcpy(ms_cell_li, &ctx->cell_list[ctx->next_cell], len * sizeof(ctx->cell_list[0]));
|
|
memcpy(&ms_cell_li[len], &ctx->cell_list[0], subset_len - len);
|
|
ctx->next_cell = subset_len - len;
|
|
}
|
|
return subset_len;
|
|
}
|
|
|
|
static void sched_ms_meas_report(struct bts_anr_fsm_ctx *ctx, const struct arfcn_bsic* cell_list,
|
|
unsigned int num_cells)
|
|
{
|
|
struct gprs_rlcmac_bts *bts = ctx->bts;
|
|
struct GprsMs *ms;
|
|
struct arfcn_bsic ms_cell_li[MAX_NEIGH_LIST_LEN];
|
|
/* HERE we'll:
|
|
* 1- Select a TBF candidate in the BTS
|
|
* 2- Pick a subset from ctx->cell_list (increasing index round buffer in array)
|
|
* 3- Send event to it to schedule the meas report [osmo_fsm_inst_dispatch(ms->meas_rep_fsm, MEAS_REP_EV_SCHEDULE, cell_sublist)]
|
|
* 4- Wait for event BTS_ANR_EV_MEAS_REP containing "Packet Measurement Report" as data
|
|
* 5- Filter out the list and submit it back over PCUIF */
|
|
|
|
/* First poor-man impl: pick first MS having a FLOW TBF: */
|
|
ms = select_candidate_ms(bts);
|
|
if (!ms) {
|
|
LOGPFSML(ctx->fi, LOGL_INFO, "Unable to find MS to start ANR measurements\n");
|
|
return;
|
|
}
|
|
LOGPMS(ms, DANR, LOGL_DEBUG, "Selected for ANR measurements\n");
|
|
|
|
/* Build up cell list subset for this MS to measure: */
|
|
if (!cell_list) {
|
|
num_cells = take_next_cell_list_chunk(ctx, &ms_cell_li[0]);
|
|
cell_list = &ms_cell_li[0];
|
|
}
|
|
|
|
if (ms_anr_start(ms, cell_list, num_cells) < 0)
|
|
LOGPFSML(ctx->fi, LOGL_ERROR, "Unable to start ANR measurements on MS\n");
|
|
}
|
|
|
|
static void handle_ms_meas_report(struct bts_anr_fsm_ctx *ctx, const struct ms_anr_ev_meas_compl* result)
|
|
{
|
|
struct gprs_rlcmac_bts *bts = ctx->bts;
|
|
pcu_tx_anr_cnf(bts, result->cell_list, result->meas_list, result->num_cells);
|
|
}
|
|
|
|
////////////////
|
|
// FSM states //
|
|
////////////////
|
|
|
|
static void st_disabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct bts_anr_fsm_ctx *ctx = (struct bts_anr_fsm_ctx *)fi->priv;
|
|
struct llist_head *tmp;
|
|
|
|
/* Abort ongoing scheduled ms_anr_fsm: */
|
|
llist_for_each(tmp, bts_ms_list(ctx->bts)) {
|
|
struct GprsMs *ms = llist_entry(tmp, typeof(*ms), list);
|
|
/* Remark: ms_anr_fsm_abort does NOT send BTS_ANR_EV_MS_MEAS_ABORTED back at us */
|
|
if (ms->anr)
|
|
ms_anr_fsm_abort(ms->anr);
|
|
}
|
|
}
|
|
|
|
static void st_disabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct bts_anr_fsm_ctx *ctx = (struct bts_anr_fsm_ctx *)fi->priv;
|
|
const struct gsm_pcu_if_anr_req *anr_req;
|
|
|
|
switch (event) {
|
|
case BTS_ANR_EV_RX_ANR_REQ:
|
|
anr_req = (const struct gsm_pcu_if_anr_req *)data;
|
|
rx_new_cell_list(ctx, anr_req);
|
|
if (ctx->num_cells > 0)
|
|
bts_anr_fsm_state_chg(fi, BTS_ANR_ST_ENABLED);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void st_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct bts_anr_fsm_ctx *ctx = (struct bts_anr_fsm_ctx *)fi->priv;
|
|
sched_ms_meas_report(ctx, NULL, 0);
|
|
}
|
|
|
|
static void st_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct bts_anr_fsm_ctx *ctx = (struct bts_anr_fsm_ctx *)fi->priv;
|
|
const struct gsm_pcu_if_anr_req *anr_req;
|
|
struct ms_anr_ev_abort *ev_abort_data;
|
|
|
|
switch (event) {
|
|
case BTS_ANR_EV_RX_ANR_REQ:
|
|
anr_req = (const struct gsm_pcu_if_anr_req *)data;
|
|
rx_new_cell_list(ctx, anr_req);
|
|
if (ctx->num_cells == 0)
|
|
bts_anr_fsm_state_chg(fi, BTS_ANR_ST_DISABLED);
|
|
break;
|
|
case BTS_ANR_EV_SCHED_MS_MEAS:
|
|
sched_ms_meas_report(ctx, NULL, 0);
|
|
break;
|
|
case BTS_ANR_EV_MS_MEAS_ABORTED:
|
|
ev_abort_data = (struct ms_anr_ev_abort*)data;
|
|
sched_ms_meas_report(ctx, ev_abort_data->cell_list, ev_abort_data->num_cells);
|
|
break;
|
|
case BTS_ANR_EV_MS_MEAS_COMPL:
|
|
handle_ms_meas_report(ctx, (const struct ms_anr_ev_meas_compl*)data);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
/*TODO: we need to track how many chunks are created, how many are in progress, how many are completed, etc. */
|
|
static int bts_anr_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
|
{
|
|
unsigned long timeout;
|
|
|
|
switch (fi->T) {
|
|
case PCU_TDEF_ANR_SCHED_TBF:
|
|
/* Re-schedule the timer */
|
|
timeout = osmo_tdef_get(((struct bts_anr_fsm_ctx*)(fi->priv))->bts->pcu->T_defs,
|
|
fi->T, OSMO_TDEF_S, -1);
|
|
osmo_timer_schedule(&fi->timer, timeout, 0);
|
|
/* Dispatch the schedule TBF MEAS event */
|
|
osmo_fsm_inst_dispatch(fi, BTS_ANR_EV_SCHED_MS_MEAS, NULL);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct osmo_fsm_state bts_anr_fsm_states[] = {
|
|
[BTS_ANR_ST_DISABLED] = {
|
|
.in_event_mask =
|
|
X(BTS_ANR_EV_RX_ANR_REQ),
|
|
.out_state_mask =
|
|
X(BTS_ANR_ST_ENABLED),
|
|
.name = "DISABLED",
|
|
.onenter = st_disabled_on_enter,
|
|
.action = st_disabled,
|
|
},
|
|
[BTS_ANR_ST_ENABLED] = {
|
|
.in_event_mask =
|
|
X(BTS_ANR_EV_RX_ANR_REQ) |
|
|
X(BTS_ANR_EV_SCHED_MS_MEAS) |
|
|
X(BTS_ANR_EV_MS_MEAS_COMPL) |
|
|
X(BTS_ANR_EV_MS_MEAS_ABORTED),
|
|
.out_state_mask =
|
|
X(BTS_ANR_ST_DISABLED),
|
|
.name = "ENABLED",
|
|
.onenter = st_enabled_on_enter,
|
|
.action = st_enabled,
|
|
},
|
|
};
|
|
|
|
static struct osmo_fsm bts_anr_fsm = {
|
|
.name = "BTS_ANR",
|
|
.states = bts_anr_fsm_states,
|
|
.num_states = ARRAY_SIZE(bts_anr_fsm_states),
|
|
.timer_cb = bts_anr_fsm_timer_cb,
|
|
.log_subsys = DANR,
|
|
.event_names = bts_anr_fsm_event_names,
|
|
};
|
|
|
|
static __attribute__((constructor)) void bts_anr_fsm_init(void)
|
|
{
|
|
OSMO_ASSERT(osmo_fsm_register(&bts_anr_fsm) == 0);
|
|
}
|
|
|
|
static int bts_anr_fsm_ctx_talloc_destructor(struct bts_anr_fsm_ctx *ctx)
|
|
{
|
|
if (ctx->fi) {
|
|
osmo_fsm_inst_free(ctx->fi);
|
|
ctx->fi = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct bts_anr_fsm_ctx *bts_anr_fsm_alloc(struct gprs_rlcmac_bts* bts)
|
|
{
|
|
struct bts_anr_fsm_ctx *ctx = talloc_zero(bts, struct bts_anr_fsm_ctx);
|
|
char buf[64];
|
|
|
|
talloc_set_destructor(ctx, bts_anr_fsm_ctx_talloc_destructor);
|
|
|
|
ctx->bts = bts;
|
|
|
|
snprintf(buf, sizeof(buf), "BTS-%u", bts->nr);
|
|
ctx->fi = osmo_fsm_inst_alloc(&bts_anr_fsm, ctx, ctx, LOGL_INFO, buf);
|
|
if (!ctx->fi)
|
|
goto free_ret;
|
|
|
|
return ctx;
|
|
free_ret:
|
|
talloc_free(ctx);
|
|
return NULL;
|
|
}
|