/* bts_anr_fsm.c * * Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH * Author: Pau Espin Pedrol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }