diff options
Diffstat (limited to 'src/bts_anr_fsm.c')
-rw-r--r-- | src/bts_anr_fsm.c | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/src/bts_anr_fsm.c b/src/bts_anr_fsm.c new file mode 100644 index 00000000..c8ef8af9 --- /dev/null +++ b/src/bts_anr_fsm.c @@ -0,0 +1,388 @@ +/* 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; +} |