/* count total, allocated and free channels of all types. * * (C) 2021 by sysmocom - s.f.m.c. GmbH * All Rights Reserved * * Author: Neels Hofmeyr * * 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 . * */ #include #include #include #include #include #include #include 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; } }