osmo-bsc/src/osmo-bsc/chan_counts.c

311 lines
9.3 KiB
C

/* 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;
}
}