aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc/lchan_select.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bsc/lchan_select.c')
-rw-r--r--src/osmo-bsc/lchan_select.c436
1 files changed, 315 insertions, 121 deletions
diff --git a/src/osmo-bsc/lchan_select.c b/src/osmo-bsc/lchan_select.c
index 6d3caaced..2388e2449 100644
--- a/src/osmo-bsc/lchan_select.c
+++ b/src/osmo-bsc/lchan_select.c
@@ -2,7 +2,7 @@
*
* (C) 2008 by Harald Welte <laforge@gnumonks.org>
* (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
- * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * (C) 2018-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
@@ -21,6 +21,8 @@
*
*/
+#include <stdlib.h>
+
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/gsm_data.h>
@@ -30,148 +32,332 @@
#include <osmocom/bsc/lchan_select.h>
#include <osmocom/bsc/bts.h>
-static struct gsm_lchan *
-_lc_find_trx(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan,
- enum gsm_phys_chan_config as_pchan)
+struct lchan_select_ts_list {
+ struct gsm_bts_trx_ts **list;
+ unsigned int num;
+};
+
+const struct value_string lchan_select_reason_names[] = {
+ OSMO_VALUE_STRING(SELECT_FOR_MS_CHAN_REQ),
+ OSMO_VALUE_STRING(SELECT_FOR_ASSIGNMENT),
+ OSMO_VALUE_STRING(SELECT_FOR_HANDOVER),
+ {0, NULL}
+};
+
+static struct gsm_lchan *pick_better_lchan(struct gsm_lchan *a, struct gsm_lchan *b)
+{
+ if (!a)
+ return b;
+ if (!b)
+ return a;
+ /* comparing negative dBm values: smaller value means less interference. */
+ if (b->interf_dbm < a->interf_dbm)
+ return b;
+ return a;
+}
+
+static struct gsm_lchan *_lc_find(struct lchan_select_ts_list *ts_list,
+ enum gsm_phys_chan_config pchan,
+ enum gsm_phys_chan_config as_pchan,
+ bool allow_pchan_switch, bool log)
{
struct gsm_lchan *lchan;
- struct gsm_bts_trx_ts *ts;
- int j, start, stop, dir;
+ struct gsm_lchan *found_lchan = NULL;
-#define LOGPLCHANALLOC(fmt, args...) \
- LOGP(DRLL, LOGL_DEBUG, "looking for lchan %s%s%s: " fmt, \
+#define LOGPLCHANALLOC(fmt, args...) do { \
+ if (log) \
+ LOGP(DRLL, LOGL_DEBUG, "looking for lchan %s%s%s%s: " fmt, \
gsm_pchan_name(pchan), \
pchan == as_pchan ? "" : " as ", \
- pchan == as_pchan ? "" : gsm_pchan_name(as_pchan), ## args)
+ pchan == as_pchan ? "" : gsm_pchan_name(as_pchan), \
+ ((pchan != as_pchan) && !allow_pchan_switch) ? " without pchan switch" : "", \
+ ## args); \
+ } while (0)
- if (!trx_is_usable(trx)) {
- LOGPLCHANALLOC("%s trx not usable\n", gsm_trx_name(trx));
- return NULL;
- }
+ for (unsigned int tn = 0; tn < ts_list->num; tn++) {
+ struct gsm_bts_trx_ts *ts = ts_list->list[tn];
+ int lchans_as_pchan;
- if (trx->bts->chan_alloc_reverse) {
- /* check TS 7..0 */
- start = 7;
- stop = -1;
- dir = -1;
- } else {
- /* check TS 0..7 */
- start = 0;
- stop = 8;
- dir = 1;
- }
-
- for (j = start; j != stop; j += dir) {
- ts = &trx->ts[j];
- if (!ts_is_usable(ts))
- continue;
/* The caller first selects what kind of TS to search in, e.g. looking for exact
- * GSM_PCHAN_TCH_F, or maybe among dynamic GSM_PCHAN_TCH_F_TCH_H_PDCH... */
+ * GSM_PCHAN_TCH_F, or maybe among dynamic GSM_PCHAN_OSMO_DYN... */
if (ts->pchan_on_init != pchan) {
LOGPLCHANALLOC("%s is != %s\n", gsm_ts_and_pchan_name(ts),
gsm_pchan_name(pchan));
continue;
}
/* Next, is this timeslot in or can it be switched to the pchan we want to use it for? */
- if (!ts_usable_as_pchan(ts, as_pchan)) {
- LOGPLCHANALLOC("%s is not usable as %s\n", gsm_ts_and_pchan_name(ts),
- gsm_pchan_name(as_pchan));
+ if (!ts_usable_as_pchan(ts, as_pchan, allow_pchan_switch)) {
+ LOGPLCHANALLOC("%s is not usable as %s%s\n", gsm_ts_and_pchan_name(ts),
+ gsm_pchan_name(as_pchan),
+ allow_pchan_switch ? "" : " without pchan switch");
continue;
}
/* TS is (going to be) in desired pchan mode. Go ahead and check for an available lchan. */
- ts_as_pchan_for_each_lchan(lchan, ts, as_pchan) {
- if (lchan->fi->state == LCHAN_ST_UNUSED) {
- LOGPLCHANALLOC("%s ss=%d is available%s\n",
+ lchans_as_pchan = pchan_subslots(as_pchan);
+ ts_for_n_lchans(lchan, ts, lchans_as_pchan) {
+ struct gsm_lchan *was = found_lchan;
+
+ if (lchan->fi->state != LCHAN_ST_UNUSED) {
+ LOGPLCHANALLOC("%s ss=%d in type=%s,state=%s not suitable\n",
gsm_ts_and_pchan_name(ts), lchan->nr,
- ts->pchan_is != as_pchan ? " after dyn PCHAN change" : "");
- return lchan;
+ gsm_chan_t_name(lchan->type),
+ osmo_fsm_inst_state_name(lchan->fi));
+ continue;
}
- LOGPLCHANALLOC("%s ss=%d in type=%s,state=%s not suitable\n",
- gsm_ts_and_pchan_name(ts), lchan->nr,
- gsm_lchant_name(lchan->type),
- osmo_fsm_inst_state_name(lchan->fi));
+
+ found_lchan = pick_better_lchan(found_lchan, lchan);
+ if (found_lchan != was)
+ LOGPLCHANALLOC("%s ss=%d interf=%u=%ddBm is %s%s\n",
+ gsm_ts_and_pchan_name(ts), lchan->nr,
+ lchan->interf_band, lchan->interf_dbm,
+ was == NULL ? "available" : "better",
+ ts->pchan_is != as_pchan ? ", after dyn PCHAN change" : "");
+ else
+ LOGPLCHANALLOC("%s ss=%d interf=%u=%ddBm is also available but not better\n",
+ gsm_ts_and_pchan_name(ts), lchan->nr,
+ lchan->interf_band, lchan->interf_dbm);
+
+ /* When picking an lchan with least interference, continue to loop across all lchans. When
+ * ignoring interference levels, return the first match. */
+ if (found_lchan && !ts->trx->bts->chan_alloc_avoid_interf)
+ return found_lchan;
}
}
- return NULL;
+ if (found_lchan)
+ LOGPLCHANALLOC("%s ss=%d interf=%ddBm%s is the best pick\n",
+ gsm_ts_and_pchan_name(found_lchan->ts), found_lchan->nr,
+ found_lchan->interf_dbm,
+ found_lchan->ts->pchan_is != as_pchan ? ", after dyn PCHAN change," : "");
+ else
+ LOGPLCHANALLOC("Nothing found\n");
+ return found_lchan;
#undef LOGPLCHANALLOC
}
-static struct gsm_lchan *
-_lc_dyn_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan,
- enum gsm_phys_chan_config dyn_as_pchan)
+static struct gsm_lchan *lc_dyn_find(struct lchan_select_ts_list *ts_list,
+ enum gsm_phys_chan_config pchan,
+ enum gsm_phys_chan_config dyn_as_pchan,
+ bool log)
{
- struct gsm_bts_trx *trx;
- struct gsm_lchan *lc;
+ struct gsm_lchan *lchan;
- if (bts->chan_alloc_reverse) {
- llist_for_each_entry_reverse(trx, &bts->trx_list, list) {
- lc = _lc_find_trx(trx, pchan, dyn_as_pchan);
- if (lc)
- return lc;
- }
- } else {
- llist_for_each_entry(trx, &bts->trx_list, list) {
- lc = _lc_find_trx(trx, pchan, dyn_as_pchan);
- if (lc)
- return lc;
- }
- }
+ /* First find an lchan that needs no change in its timeslot pchan mode.
+ * In particular, this ensures that handover to a dynamic timeslot in TCH/H favors timeslots that are currently
+ * using only one of two TCH/H, so that we don't switch more dynamic timeslots to TCH/H than necessary.
+ * For non-dynamic timeslots, it is not necessary to do a second pass with allow_pchan_switch ==
+ * true, because they never switch anyway. */
+ if ((lchan = _lc_find(ts_list, pchan, dyn_as_pchan, false, log)))
+ return lchan;
+ if ((lchan = _lc_find(ts_list, pchan, dyn_as_pchan, true, log)))
+ return lchan;
return NULL;
}
-static struct gsm_lchan *
-_lc_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan)
+static struct gsm_lchan *lc_find(struct lchan_select_ts_list *ts_list,
+ enum gsm_phys_chan_config pchan,
+ bool log)
{
- return _lc_dyn_find_bts(bts, pchan, pchan);
+ return _lc_find(ts_list, pchan, pchan, false, log);
}
-struct gsm_lchan *lchan_select_by_chan_mode(struct gsm_bts *bts,
- enum gsm48_chan_mode chan_mode, enum channel_rate chan_rate)
+enum gsm_chan_t chan_mode_to_chan_type(enum gsm48_chan_mode chan_mode, enum channel_rate chan_rate)
{
- enum gsm_chan_t type;
-
- switch (chan_mode) {
+ switch (gsm48_chan_mode_to_non_vamos(chan_mode)) {
case GSM48_CMODE_SIGN:
switch (chan_rate) {
- case CH_RATE_SDCCH: type = GSM_LCHAN_SDCCH; break;
- case CH_RATE_HALF: type = GSM_LCHAN_TCH_H; break;
- case CH_RATE_FULL: type = GSM_LCHAN_TCH_F; break;
- default: return NULL;
+ case CH_RATE_SDCCH:
+ return GSM_LCHAN_SDCCH;
+ case CH_RATE_HALF:
+ return GSM_LCHAN_TCH_H;
+ case CH_RATE_FULL:
+ return GSM_LCHAN_TCH_F;
+ default:
+ return GSM_LCHAN_NONE;
}
- break;
case GSM48_CMODE_SPEECH_EFR:
/* EFR works over FR channels only */
if (chan_rate != CH_RATE_FULL)
- return NULL;
+ return GSM_LCHAN_NONE;
/* fall through */
case GSM48_CMODE_SPEECH_V1:
case GSM48_CMODE_SPEECH_AMR:
switch (chan_rate) {
- case CH_RATE_HALF: type = GSM_LCHAN_TCH_H; break;
- case CH_RATE_FULL: type = GSM_LCHAN_TCH_F; break;
- default: return NULL;
+ case CH_RATE_HALF:
+ return GSM_LCHAN_TCH_H;
+ case CH_RATE_FULL:
+ return GSM_LCHAN_TCH_F;
+ default:
+ return GSM_LCHAN_NONE;
}
- break;
default:
- return NULL;
+ return GSM_LCHAN_NONE;
}
+}
+
+static int qsort_func(const void *_a, const void *_b)
+{
+ const struct gsm_bts_trx *trx_a = *(const struct gsm_bts_trx **)_a;
+ const struct gsm_bts_trx *trx_b = *(const struct gsm_bts_trx **)_b;
- return lchan_select_by_type(bts, type);
+ int pwr_a = trx_a->nominal_power - trx_a->max_power_red;
+ int pwr_b = trx_b->nominal_power - trx_b->max_power_red;
+
+ /* Sort in descending order */
+ return pwr_b - pwr_a;
}
-struct gsm_lchan *lchan_avail_by_type(struct gsm_bts *bts, enum gsm_chan_t type)
+static void populate_ts_list(struct lchan_select_ts_list *ts_list,
+ struct gsm_bts *bts,
+ bool chan_alloc_reverse,
+ bool sort_by_trx_power,
+ bool log)
+{
+ struct gsm_bts_trx **trx_list;
+ struct gsm_bts_trx *trx;
+ unsigned int num = 0;
+
+ /* Allocate an array with pointers to all TRX instances of a BTS */
+ trx_list = talloc_array_ptrtype(bts, trx_list, bts->num_trx);
+ OSMO_ASSERT(trx_list != NULL);
+
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ trx_list[trx->nr] = trx;
+
+ /* Sort by TRX power in descending order (if needed) */
+ if (sort_by_trx_power)
+ qsort(&trx_list[0], bts->num_trx, sizeof(trx), &qsort_func);
+
+ for (unsigned int trxn = 0; trxn < bts->num_trx; trxn++) {
+ trx = trx_list[trxn];
+ for (unsigned int tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+ if (ts_is_usable(ts))
+ ts_list->list[num++] = ts;
+ else if (log)
+ LOGP(DRLL, LOGL_DEBUG, "%s is not usable\n", gsm_ts_name(ts));
+ }
+ }
+
+ talloc_free(trx_list);
+ ts_list->num = num;
+
+ /* Reverse the timeslot list if required */
+ if (chan_alloc_reverse) {
+ for (unsigned int tn = 0; tn < num / 2; tn++) {
+ struct gsm_bts_trx_ts *temp = ts_list->list[tn];
+ ts_list->list[tn] = ts_list->list[num - tn - 1];
+ ts_list->list[num - tn - 1] = temp;
+ }
+ }
+}
+
+static bool chan_alloc_ass_dynamic_reverse(struct gsm_bts *bts,
+ void *ctx, bool log)
+{
+ const struct load_counter *ll = &bts->c0->lchan_load;
+ const struct gsm_lchan *old_lchan = ctx;
+ unsigned int lchan_load;
+ int avg_ul_rxlev;
+
+ OSMO_ASSERT(old_lchan != NULL);
+ OSMO_ASSERT(old_lchan->ts->trx->bts == bts);
+
+#define LOG_COND(fmt, args...) do { \
+ if (log) \
+ LOG_LCHAN(old_lchan, LOGL_DEBUG, fmt, ## args); \
+ } while (0)
+
+ /* Condition a) Channel load on the C0 (BCCH carrier) */
+ lchan_load = ll->total ? ll->used * 100 / ll->total : 0;
+ if (lchan_load < bts->chan_alloc_dyn_params.c0_chan_load_thresh) {
+ LOG_COND("C0 Channel Load %u%% < thresh %u%% => using ascending order\n",
+ lchan_load, bts->chan_alloc_dyn_params.c0_chan_load_thresh);
+ return false;
+ }
+
+ /* Condition b) average Uplink RxLev */
+ avg_ul_rxlev = get_meas_rep_avg(old_lchan, TDMA_MEAS_FIELD_RXLEV,
+ TDMA_MEAS_DIR_UL, TDMA_MEAS_SET_AUTO,
+ bts->chan_alloc_dyn_params.ul_rxlev_avg_num);
+ if (avg_ul_rxlev < 0) {
+ LOG_COND("Unknown AVG UL RxLev => using ascending order\n");
+ return false;
+ }
+ if (avg_ul_rxlev < bts->chan_alloc_dyn_params.ul_rxlev_thresh) {
+ LOG_COND("AVG UL RxLev %u < thresh %u => using ascending order\n",
+ avg_ul_rxlev, bts->chan_alloc_dyn_params.ul_rxlev_thresh);
+ return false;
+ }
+
+ LOG_COND("C0 Channel Load %u%% >= thresh %u%% and "
+ "AVG UL RxLev %u >= thresh %u => using descending order\n",
+ lchan_load, bts->chan_alloc_dyn_params.c0_chan_load_thresh,
+ avg_ul_rxlev, bts->chan_alloc_dyn_params.ul_rxlev_thresh);
+
+#undef LOG_COND
+
+ return true;
+}
+
+struct gsm_lchan *lchan_select_by_chan_mode(struct gsm_bts *bts,
+ enum gsm48_chan_mode chan_mode,
+ enum channel_rate chan_rate,
+ enum lchan_select_reason reason,
+ void *ctx)
+{
+ enum gsm_chan_t type = chan_mode_to_chan_type(chan_mode, chan_rate);
+ if (type == GSM_LCHAN_NONE)
+ return NULL;
+ return lchan_select_by_type(bts, type, reason, ctx);
+}
+
+struct gsm_lchan *lchan_avail_by_type(struct gsm_bts *bts,
+ enum gsm_chan_t type,
+ enum lchan_select_reason reason,
+ void *ctx, bool log)
{
struct gsm_lchan *lchan = NULL;
enum gsm_phys_chan_config first, first_cbch, second, second_cbch;
+ struct lchan_select_ts_list ts_list;
+ bool sort_by_trx_power = false;
+ bool chan_alloc_reverse = false;
- LOG_BTS(bts, DRLL, LOGL_DEBUG, "lchan_avail_by_type(%s)\n", gsm_lchant_name(type));
+ if (log) {
+ LOG_BTS(bts, DRLL, LOGL_DEBUG, "lchan_avail_by_type(type=%s, reason=%s)\n",
+ gsm_chan_t_name(type), lchan_select_reason_name(reason));
+ }
+
+ switch (reason) {
+ case SELECT_FOR_MS_CHAN_REQ:
+ chan_alloc_reverse = bts->chan_alloc_chan_req_reverse;
+ break;
+ case SELECT_FOR_ASSIGNMENT:
+ if (bts->chan_alloc_assignment_dynamic) {
+ chan_alloc_reverse = chan_alloc_ass_dynamic_reverse(bts, ctx, log);
+ sort_by_trx_power = bts->chan_alloc_dyn_params.sort_by_trx_power;
+ } else {
+ chan_alloc_reverse = bts->chan_alloc_assignment_reverse;
+ }
+ break;
+ case SELECT_FOR_HANDOVER:
+ chan_alloc_reverse = bts->chan_alloc_handover_reverse;
+ break;
+ }
+
+ /* Allocate an array with pointers to all timeslots of a BTS */
+ ts_list.list = talloc_array_ptrtype(bts, ts_list.list, bts->num_trx * 8);
+ if (OSMO_UNLIKELY(ts_list.list == NULL))
+ return NULL;
+
+ /* Populate this array with the actual pointers */
+ populate_ts_list(&ts_list, bts, chan_alloc_reverse, sort_by_trx_power, log);
switch (type) {
case GSM_LCHAN_SDCCH:
- if (bts->chan_alloc_reverse) {
+ if (chan_alloc_reverse) {
first = GSM_PCHAN_SDCCH8_SACCH8C;
first_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
second = GSM_PCHAN_CCCH_SDCCH4;
@@ -183,71 +369,79 @@ struct gsm_lchan *lchan_avail_by_type(struct gsm_bts *bts, enum gsm_chan_t type)
second_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
}
- lchan = _lc_find_bts(bts, first);
+ lchan = lc_find(&ts_list, first, log);
if (lchan == NULL)
- lchan = _lc_find_bts(bts, first_cbch);
+ lchan = lc_find(&ts_list, first_cbch, log);
if (lchan == NULL)
- lchan = _lc_find_bts(bts, second);
+ lchan = lc_find(&ts_list, second, log);
if (lchan == NULL)
- lchan = _lc_find_bts(bts, second_cbch);
+ lchan = lc_find(&ts_list, second_cbch, log);
+ /* No dedicated SDCCH available -- try fully dynamic
+ * TCH/F_TCH/H_SDCCH8_PDCH if BTS supports it: */
+ if (lchan == NULL && osmo_bts_has_feature(&bts->features, BTS_FEAT_DYN_TS_SDCCH8))
+ lchan = lc_dyn_find(&ts_list, GSM_PCHAN_OSMO_DYN,
+ GSM_PCHAN_SDCCH8_SACCH8C, log);
break;
case GSM_LCHAN_TCH_F:
- lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+ lchan = lc_find(&ts_list, GSM_PCHAN_TCH_F, log);
/* If we don't have TCH/F available, try dynamic TCH/F_PDCH */
- if (!lchan) {
- lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH,
- GSM_PCHAN_TCH_F);
- /* TCH/F_PDCH used as TCH/F -- here, type is already
- * set to GSM_LCHAN_TCH_F, but for clarity's sake... */
- if (lchan)
- type = GSM_LCHAN_TCH_F;
- }
+ if (!lchan)
+ lchan = lc_dyn_find(&ts_list, GSM_PCHAN_TCH_F_PDCH,
+ GSM_PCHAN_TCH_F, log);
/* Try fully dynamic TCH/F_TCH/H_PDCH as TCH/F... */
- if (!lchan && bts->network->dyn_ts_allow_tch_f) {
- lchan = _lc_dyn_find_bts(bts,
- GSM_PCHAN_TCH_F_TCH_H_PDCH,
- GSM_PCHAN_TCH_F);
- if (lchan)
- type = GSM_LCHAN_TCH_F;
- }
+ if (!lchan && bts->network->dyn_ts_allow_tch_f)
+ lchan = lc_dyn_find(&ts_list, GSM_PCHAN_OSMO_DYN,
+ GSM_PCHAN_TCH_F, log);
break;
case GSM_LCHAN_TCH_H:
- lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H);
+ lchan = lc_find(&ts_list, GSM_PCHAN_TCH_H, log);
/* No dedicated TCH/x available -- try fully dynamic
* TCH/F_TCH/H_PDCH */
- if (!lchan) {
- lchan = _lc_dyn_find_bts(bts,
- GSM_PCHAN_TCH_F_TCH_H_PDCH,
- GSM_PCHAN_TCH_H);
- if (lchan)
- type = GSM_LCHAN_TCH_H;
- }
+ if (!lchan)
+ lchan = lc_dyn_find(&ts_list, GSM_PCHAN_OSMO_DYN,
+ GSM_PCHAN_TCH_H, log);
break;
default:
LOG_BTS(bts, DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type);
}
+ talloc_free(ts_list.list);
+
return lchan;
}
/* Return a matching lchan from a specific BTS that is currently available. The next logical step is
* lchan_activate() on it, which would possibly cause dynamic timeslot pchan switching, taken care of by
* the lchan and timeslot FSMs. */
-struct gsm_lchan *lchan_select_by_type(struct gsm_bts *bts, enum gsm_chan_t type)
+struct gsm_lchan *lchan_select_by_type(struct gsm_bts *bts,
+ enum gsm_chan_t type,
+ enum lchan_select_reason reason,
+ void *ctx)
{
struct gsm_lchan *lchan = NULL;
- lchan = lchan_avail_by_type(bts, type);
+ LOG_BTS(bts, DRLL, LOGL_DEBUG, "lchan_select_by_type(type=%s, reason=%s)\n",
+ gsm_chan_t_name(type), lchan_select_reason_name(reason));
- LOG_BTS(bts, DRLL, LOGL_DEBUG, "lchan_select_by_type(%s)\n", gsm_lchant_name(type));
+ lchan = lchan_avail_by_type(bts, type, reason, ctx, true);
- if (lchan) {
- lchan->type = type;
- LOG_LCHAN(lchan, LOGL_INFO, "Selected\n");
- } else
- LOG_BTS(bts, DRLL, LOGL_NOTICE, "Failed to select %s channel\n",
- gsm_lchant_name(type));
+ if (!lchan) {
+ LOG_BTS(bts, DRLL, LOGL_NOTICE, "Failed to select %s channel (%s)\n",
+ gsm_chan_t_name(type), lchan_select_reason_name(reason));
+ return NULL;
+ }
+ lchan_select_set_type(lchan, type);
return lchan;
}
+
+/* Set available lchan to given type. Usually used on lchan obtained with
+ * lchan_avail_by_type. The next logical step is lchan_activate() on it, which
+ * would possibly cause dynamic timeslot pchan switching, taken care of by the
+ * lchan and timeslot FSMs. */
+void lchan_select_set_type(struct gsm_lchan *lchan, enum gsm_chan_t type)
+{
+ lchan->type = type;
+ LOG_LCHAN(lchan, LOGL_INFO, "Selected\n");
+}