aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc/chan_counts.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bsc/chan_counts.c')
-rw-r--r--src/osmo-bsc/chan_counts.c310
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;
+ }
+}