diff options
Diffstat (limited to 'src/osmo-bsc/chan_counts.c')
-rw-r--r-- | src/osmo-bsc/chan_counts.c | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/src/osmo-bsc/chan_counts.c b/src/osmo-bsc/chan_counts.c new file mode 100644 index 000000000..bf863688d --- /dev/null +++ b/src/osmo-bsc/chan_counts.c @@ -0,0 +1,310 @@ +/* count total, allocated and free channels of all types. + * + * (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/gsm/gsm_utils.h> + +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/bts_trx.h> +#include <osmocom/bsc/lchan_fsm.h> +#include <osmocom/bsc/chan_counts.h> +#include <osmocom/bsc/bsc_stats.h> +#include <osmocom/bsc/signal.h> + +static const unsigned int lchans_per_pchan[_GSM_PCHAN_MAX][_GSM_LCHAN_MAX] = { + [GSM_PCHAN_NONE] = {0}, + [GSM_PCHAN_CCCH] = { [GSM_LCHAN_CCCH] = 1, }, + [GSM_PCHAN_PDCH] = { [GSM_LCHAN_PDTCH] = 1, }, + [GSM_PCHAN_CCCH_SDCCH4] = { + [GSM_LCHAN_CCCH] = 1, + [GSM_LCHAN_SDCCH] = 3, + }, + [GSM_PCHAN_TCH_F] = { [GSM_LCHAN_TCH_F] = 1, }, + [GSM_PCHAN_TCH_H] = { [GSM_LCHAN_TCH_H] = 2, }, + [GSM_PCHAN_SDCCH8_SACCH8C] = { [GSM_LCHAN_SDCCH] = 8, }, + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = { + [GSM_LCHAN_CCCH] = 1, + [GSM_LCHAN_SDCCH] = 3, + [GSM_LCHAN_CBCH] = 1, + }, + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = { + [GSM_LCHAN_SDCCH] = 8, + [GSM_LCHAN_CBCH] = 1, + }, + [GSM_PCHAN_OSMO_DYN] = { + [GSM_LCHAN_TCH_F] = 1, + [GSM_LCHAN_TCH_H] = 2, + [GSM_LCHAN_SDCCH] = 8, + [GSM_LCHAN_PDTCH] = 1, + }, + [GSM_PCHAN_TCH_F_PDCH] = { + [GSM_LCHAN_TCH_F] = 1, + [GSM_LCHAN_PDTCH] = 1, + }, +}; + +static inline void chan_counts_per_pchan_add(struct chan_counts *dst, + enum chan_counts_dim1 dim1, enum chan_counts_dim2 dim2, + enum gsm_phys_chan_config pchan) +{ + int i; + for (i = 0; i < _GSM_LCHAN_MAX; i++) + dst->val[dim1][dim2][i] += lchans_per_pchan[pchan][i]; +} + +static const char *chan_counts_dim1_name[_CHAN_COUNTS1_NUM] = { + [CHAN_COUNTS1_ALL] = "all", + [CHAN_COUNTS1_STATIC] = "static", + [CHAN_COUNTS1_DYNAMIC] = "dynamic", +}; + +static const char *chan_counts_dim2_name[_CHAN_COUNTS2_NUM] = { + [CHAN_COUNTS2_MAX_TOTAL] = "max", + [CHAN_COUNTS2_CURRENT_TOTAL] = "current", + [CHAN_COUNTS2_ALLOCATED] = "alloc", + [CHAN_COUNTS2_FREE] = "free", +}; + +int chan_counts_to_str_buf(char *buf, size_t buflen, const struct chan_counts *c) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + int i1, i2, i3; + OSMO_STRBUF_PRINTF(sb, "{"); + for (i1 = 0; i1 < _CHAN_COUNTS1_NUM; i1++) { + for (i2 = 0; i2 < _CHAN_COUNTS2_NUM; i2++) { + bool p12 = false; + + for (i3 = 0; i3 < _GSM_LCHAN_MAX; i3++) { + + int v = c->val[i1][i2][i3]; + if (v) { + if (!p12) { + p12 = true; + OSMO_STRBUF_PRINTF(sb, " %s.%s{", chan_counts_dim1_name[i1], + chan_counts_dim2_name[i2]); + } + OSMO_STRBUF_PRINTF(sb, " %s=%d", gsm_chan_t_name(i3), v); + } + } + + if (p12) + OSMO_STRBUF_PRINTF(sb, " }"); + } + } + OSMO_STRBUF_PRINTF(sb, " }"); + return sb.chars_needed; +} + +char *chan_counts_to_str_c(void *ctx, const struct chan_counts *c) +{ + OSMO_NAME_C_IMPL(ctx, 64, "ERROR", chan_counts_to_str_buf, c) +} + +void chan_counts_for_ts(struct chan_counts *ts_counts, const struct gsm_bts_trx_ts *ts) +{ + const struct gsm_lchan *lchan; + bool ts_is_dynamic; + + chan_counts_zero(ts_counts); + + if (!ts_is_usable(ts)) + return; + + /* Count the full potential nr of lchans for dynamic TS */ + chan_counts_per_pchan_add(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_MAX_TOTAL, ts->pchan_on_init); + + switch (ts->pchan_on_init) { + case GSM_PCHAN_TCH_F_PDCH: + case GSM_PCHAN_OSMO_DYN: + ts_is_dynamic = true; + break; + default: + ts_is_dynamic = false; + break; + } + + if (ts_is_dynamic && ts->pchan_is == GSM_PCHAN_PDCH) { + /* Dynamic timeslots in PDCH mode can become TCH or SDCCH immediately, + * so set CURRENT_TOTAL = MAX_TOTAL. */ + chan_counts_dim3_add(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_CURRENT_TOTAL, + ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_MAX_TOTAL); + } else { + /* Static TS, or dyn TS that are currently fixed on a specific pchan: count lchans for the + * current pchan mode. */ + chan_counts_per_pchan_add(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_CURRENT_TOTAL, ts->pchan_is); + } + + /* Count currently allocated lchans */ + ts_for_n_lchans(lchan, ts, ts->max_primary_lchans) { + if (!lchan_state_is(lchan, LCHAN_ST_UNUSED)) + ts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_ALLOCATED][lchan->type]++; + } + + chan_counts_dim3_add(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_FREE, + ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_CURRENT_TOTAL); + chan_counts_dim3_sub(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_FREE, + ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_ALLOCATED); + + if (ts_is_dynamic) + chan_counts_dim2_add(ts_counts, CHAN_COUNTS1_DYNAMIC, ts_counts, CHAN_COUNTS1_ALL); + else + chan_counts_dim2_add(ts_counts, CHAN_COUNTS1_STATIC, ts_counts, CHAN_COUNTS1_ALL); +} + +static void chan_counts_diff(struct chan_counts *diff, const struct chan_counts *left, const struct chan_counts *right) +{ + chan_counts_zero(diff); + chan_counts_add(diff, right); + chan_counts_sub(diff, left); +} + +static void _chan_counts_ts_update(struct gsm_bts_trx_ts *ts, const struct chan_counts *ts_new_counts) +{ + struct chan_counts diff; + + chan_counts_diff(&diff, &ts->chan_counts, ts_new_counts); + if (chan_counts_is_zero(&diff)) + return; + + ts->chan_counts = *ts_new_counts; + chan_counts_add(&ts->trx->chan_counts, &diff); + chan_counts_add(&ts->trx->bts->chan_counts, &diff); + chan_counts_add(&bsc_gsmnet->chan_counts, &diff); + + all_allocated_update_bts(ts->trx->bts); + all_allocated_update_bsc(); + + LOGP(DLGLOBAL, LOGL_DEBUG, "change in channel counts: ts %u-%u-%u: %s\n", + ts->trx->bts->nr, ts->trx->nr, ts->nr, chan_counts_to_str_c(OTC_SELECT, &diff)); + LOGP(DLGLOBAL, LOGL_DEBUG, "bsc channel counts: %s\n", + chan_counts_to_str_c(OTC_SELECT, &bsc_gsmnet->chan_counts)); +} + +/* Re-count this TS, and update ts->chan_counts. If the new ts->chan_counts differ, propagate the difference to + * trx->chan_counts, bts->chan_counts and gsm_network->chan_counts. */ +void chan_counts_ts_update(struct gsm_bts_trx_ts *ts) +{ + struct chan_counts ts_new_counts; + chan_counts_for_ts(&ts_new_counts, ts); + _chan_counts_ts_update(ts, &ts_new_counts); +} + +void chan_counts_ts_clear(struct gsm_bts_trx_ts *ts) +{ + struct chan_counts ts_new_counts = {0}; + _chan_counts_ts_update(ts, &ts_new_counts); +} + +void chan_counts_trx_update(struct gsm_bts_trx *trx) +{ + int i; + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + chan_counts_ts_update(ts); + } +} + +static int chan_counts_sig_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) +{ + struct nm_running_chg_signal_data *nsd; + struct gsm_bts_trx *trx; + if (signal != S_NM_RUNNING_CHG) + return 0; + nsd = signal_data; + switch (nsd->obj_class) { + case NM_OC_RADIO_CARRIER: + trx = (struct gsm_bts_trx *)nsd->obj; + break; + case NM_OC_BASEB_TRANSC: + trx = gsm_bts_bb_trx_get_trx((struct gsm_bts_bb_trx *)nsd->obj); + break; + default: + return 0; + } + chan_counts_trx_update(trx); + return 0; +} + +void chan_counts_sig_init(void) +{ + osmo_signal_register_handler(SS_NM, chan_counts_sig_cb, NULL); +} + +void chan_counts_bsc_verify(void) +{ + struct gsm_bts *bts; + struct chan_counts bsc_counts = {0}; + struct chan_counts diff; + + llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) { + struct gsm_bts_trx *trx; + struct chan_counts bts_counts = {0}; + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct chan_counts trx_counts = {0}; + int i; + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct chan_counts ts_counts; + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + chan_counts_for_ts(&ts_counts, ts); + + chan_counts_diff(&diff, &ts->chan_counts, &ts_counts); + if (!chan_counts_is_zero(&diff)) { + LOGP(DLGLOBAL, LOGL_ERROR, + "internal error in channel counts, on bts-trx-ts %u-%u-%u, fixing." + " diff: %s\n", + bts->nr, trx->nr, ts->nr, + chan_counts_to_str_c(OTC_SELECT, &diff)); + ts->chan_counts = ts_counts; + } + + chan_counts_add(&trx_counts, &ts_counts); + } + + chan_counts_diff(&diff, &trx->chan_counts, &trx_counts); + if (!chan_counts_is_zero(&diff)) { + LOGP(DLGLOBAL, LOGL_ERROR, "internal error in channel counts, on bts-trx %u-%u, fixing." + " diff: %s\n", + bts->nr, trx->nr, chan_counts_to_str_c(OTC_SELECT, &diff)); + trx->chan_counts = trx_counts; + } + + chan_counts_add(&bts_counts, &trx_counts); + } + + chan_counts_diff(&diff, &bts->chan_counts, &bts_counts); + if (!chan_counts_is_zero(&diff)) { + LOGP(DLGLOBAL, LOGL_ERROR, "internal error in channel counts, on bts %u, fixing. diff: %s\n", + bts->nr, chan_counts_to_str_c(OTC_SELECT, &diff)); + bts->chan_counts = bts_counts; + } + + chan_counts_add(&bsc_counts, &bts_counts); + } + + chan_counts_diff(&diff, &bsc_gsmnet->chan_counts, &bsc_counts); + if (!chan_counts_is_zero(&diff)) { + LOGP(DLGLOBAL, LOGL_ERROR, "internal error in overall channel counts, fixing. diff: %s\n", + chan_counts_to_str_c(OTC_SELECT, &diff)); + bsc_gsmnet->chan_counts = bsc_counts; + } +} |