aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/osmocom/bsc/handover.h81
-rw-r--r--src/libbsc/bsc_vty.c2
-rw-r--r--src/libbsc/handover_decision.c27
-rw-r--r--src/libbsc/handover_logic.c78
4 files changed, 131 insertions, 57 deletions
diff --git a/include/osmocom/bsc/handover.h b/include/osmocom/bsc/handover.h
index bbb448c9d..0fbfaf671 100644
--- a/include/osmocom/bsc/handover.h
+++ b/include/osmocom/bsc/handover.h
@@ -1,12 +1,91 @@
#pragma once
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/timer.h>
+
struct gsm_lchan;
struct gsm_bts;
struct gsm_subscriber_connection;
-int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *new_bts,
+#define LOGPHOLCHANTOLCHAN(old_lchan, new_lchan, level, fmt, args...) \
+ LOGP(DHODEC, level, "(BTS %u trx %u arfcn %u ts %u lchan %u %s)->(BTS %u trx %u arfcn %u ts %u lchan %u %s) (subscr %s) " fmt, \
+ old_lchan->ts->trx->bts->nr, \
+ old_lchan->ts->trx->nr, \
+ old_lchan->ts->trx->arfcn, \
+ old_lchan->ts->nr, \
+ old_lchan->nr, \
+ gsm_pchan_name(old_lchan->ts->pchan), \
+ new_lchan->ts->trx->bts->nr, \
+ new_lchan->ts->trx->nr, \
+ new_lchan->ts->trx->arfcn, \
+ new_lchan->ts->nr, \
+ new_lchan->nr, \
+ gsm_pchan_name(new_lchan->ts->pchan), \
+ bsc_subscr_name(old_lchan->conn? old_lchan->conn->bsub : NULL), \
+ ## args)
+
+#define LOGPHO(struct_bsc_handover, level, fmt, args ...) \
+ LOGPHOLCHANTOLCHAN(struct_bsc_handover->old_lchan, struct_bsc_handover->new_lchan, level, fmt, ## args)
+
+enum hodec_id {
+ HODEC_NONE,
+ HODEC1 = 1,
+ HODEC2 = 2,
+};
+
+struct bsc_handover {
+ struct llist_head list;
+
+ enum hodec_id from_hodec_id;
+
+ struct gsm_lchan *old_lchan;
+ struct gsm_lchan *new_lchan;
+
+ struct osmo_timer_list T3103;
+
+ uint8_t ho_ref;
+
+ bool inter_cell;
+ bool async;
+};
+
+int bsc_handover_start(enum hodec_id from_hodec_id, struct gsm_lchan *old_lchan, struct gsm_bts *new_bts,
enum gsm_chan_t new_lchan_type);
void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan);
struct gsm_lchan *bsc_handover_pending(struct gsm_lchan *new_lchan);
int bsc_ho_count(struct gsm_bts *bts, bool inter_cell);
+
+/* Handover decision algorithms' actions to take on incoming handover-relevant events.
+ *
+ * All events that are interesting for handover decision are actually communicated by S_LCHAN_* signals,
+ * so theoretically, each handover algorithm could evaluate those. However, handover_logic.c cleans up
+ * handover operation state upon receiving some of these signals. To allow a handover decision algorithm
+ * to take advantage of e.g. the struct bsc_handover before it is discarded, the handover decision event
+ * handler needs to be invoked before handover_logic.c discards the state. For example, if the handover
+ * decision wants to place a penalty timer upon a handover failure, it still needs to know which target
+ * cell the handover failed for; handover_logic.c erases that knowledge on handover failure, since it
+ * needs to clean up the lchan's handover state.
+ *
+ * The most explicit and safest way to ensure the correct order of event handling is to invoke the
+ * handover decision algorithm's actions from handover_logic.c itself, before cleaning up. This struct
+ * provides the callback functions for this purpose.
+ *
+ * For consistency, also handle signals in this way that aren't actually in danger of interference from
+ * handover_logic.c (which also saves repeated lookup of handover state for lchans). Thus, handover
+ * decision algorithms should not register any signal handler at all.
+ */
+struct handover_decision_callbacks {
+ struct llist_head entry;
+
+ int hodec_id;
+
+ void (*on_measurement_report)(struct gsm_meas_rep *mr);
+ void (*on_ho_chan_activ_nack)(struct bsc_handover *ho);
+ void (*on_ho_failure)(struct bsc_handover *ho);
+};
+
+void handover_decision_callbacks_register(struct handover_decision_callbacks *hdc);
+struct handover_decision_callbacks *handover_decision_callbacks_get(int hodec_id);
diff --git a/src/libbsc/bsc_vty.c b/src/libbsc/bsc_vty.c
index 3ce310ad3..36c849d5e 100644
--- a/src/libbsc/bsc_vty.c
+++ b/src/libbsc/bsc_vty.c
@@ -1481,7 +1481,7 @@ static int trigger_ho_or_as(struct vty *vty, struct gsm_lchan *from_lchan, struc
} else
LOGP(DHO, LOGL_NOTICE, "%s (ARFCN %u) --> BTS %u Manually triggering Handover from VTY\n",
gsm_lchan_name(from_lchan), from_lchan->ts->trx->arfcn, to_bts->nr);
- rc = bsc_handover_start(from_lchan, to_bts, from_lchan->type);
+ rc = bsc_handover_start(HODEC_NONE, from_lchan, to_bts, from_lchan->type);
if (rc) {
vty_out(vty, "bsc_handover_start() returned %d=%s%s", rc,
strerror(-rc), VTY_NEWLINE);
diff --git a/src/libbsc/handover_decision.c b/src/libbsc/handover_decision.c
index e677b1fc2..887c2993f 100644
--- a/src/libbsc/handover_decision.c
+++ b/src/libbsc/handover_decision.c
@@ -70,7 +70,7 @@ static int handover_to_arfcn_bsic(struct gsm_lchan *lchan,
}
/* and actually try to handover to that cell */
- return bsc_handover_start(lchan, new_bts, lchan->type);
+ return bsc_handover_start(HODEC1, lchan, new_bts, lchan->type);
}
/* did we get a RXLEV for a given cell in the given report? */
@@ -257,7 +257,7 @@ static int attempt_handover(struct gsm_meas_rep *mr)
/* process an already parsed measurement report and decide if we want to
* attempt a handover */
-static void process_meas_rep(struct gsm_meas_rep *mr)
+static void on_measurement_report(struct gsm_meas_rep *mr)
{
struct gsm_bts *bts = mr->lchan->ts->trx->bts;
enum meas_rep_field dlev, dqual;
@@ -332,25 +332,12 @@ static void process_meas_rep(struct gsm_meas_rep *mr)
attempt_handover(mr);
}
-static int ho_dec_sig_cb(unsigned int subsys, unsigned int signal,
- void *handler_data, void *signal_data)
-{
- struct lchan_signal_data *lchan_data;
-
- if (subsys != SS_LCHAN)
- return 0;
-
- lchan_data = signal_data;
- switch (signal) {
- case S_LCHAN_MEAS_REP:
- process_meas_rep(lchan_data->mr);
- break;
- }
-
- return 0;
-}
+struct handover_decision_callbacks hodec1_callbacks = {
+ .hodec_id = HODEC1,
+ .on_measurement_report = on_measurement_report,
+};
void handover_decision_1_init(void)
{
- osmo_signal_register_handler(SS_LCHAN, ho_dec_sig_cb, NULL);
+ handover_decision_callbacks_register(&hodec1_callbacks);
}
diff --git a/src/libbsc/handover_logic.c b/src/libbsc/handover_logic.c
index 3dd7227db..483c76bea 100644
--- a/src/libbsc/handover_logic.c
+++ b/src/libbsc/handover_logic.c
@@ -39,42 +39,10 @@
#include <osmocom/bsc/bsc_subscriber.h>
#include <osmocom/bsc/gsm_04_08_utils.h>
#include <osmocom/bsc/handover.h>
-
-#define LOGPHOLCHANTOLCHAN(lchan, new_lchan, level, fmt, args...) \
- LOGP(DHODEC, level, "(BTS %u trx %u arfcn %u ts %u lchan %u %s)->(BTS %u trx %u arfcn %u ts %u lchan %u %s) (subscr %s) " fmt, \
- lchan->ts->trx->bts->nr, \
- lchan->ts->trx->nr, \
- lchan->ts->trx->arfcn, \
- lchan->ts->nr, \
- lchan->nr, \
- gsm_pchan_name(lchan->ts->pchan), \
- new_lchan->ts->trx->bts->nr, \
- new_lchan->ts->trx->nr, \
- new_lchan->ts->trx->arfcn, \
- new_lchan->ts->nr, \
- new_lchan->nr, \
- gsm_pchan_name(new_lchan->ts->pchan), \
- bsc_subscr_name(lchan->conn->bsub), \
- ## args)
-
-#define LOGPHO(struct_bsc_handover, level, fmt, args ...) \
- LOGPHOLCHANTOLCHAN(struct_bsc_handover->old_lchan, struct_bsc_handover->new_lchan, level, fmt, ## args)
-
-struct bsc_handover {
- struct llist_head list;
-
- struct gsm_lchan *old_lchan;
- struct gsm_lchan *new_lchan;
-
- struct osmo_timer_list T3103;
-
- uint8_t ho_ref;
-
- bool inter_cell;
- bool async;
-};
+#include <osmocom/bsc/handover_cfg.h>
static LLIST_HEAD(bsc_handovers);
+static LLIST_HEAD(handover_decision_callbacks);
static void handover_free(struct bsc_handover *ho)
{
@@ -110,7 +78,7 @@ static struct bsc_handover *bsc_ho_by_old_lchan(struct gsm_lchan *old_lchan)
/*! Hand over the specified logical channel to the specified new BTS and possibly change the lchan type.
* This is the main entry point for the actual handover algorithm, after the decision whether to initiate
* HO to a specific BTS. To not change the lchan type, pass old_lchan->type. */
-int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *new_bts,
+int bsc_handover_start(enum hodec_id from_hodec_id, struct gsm_lchan *old_lchan, struct gsm_bts *new_bts,
enum gsm_chan_t new_lchan_type)
{
struct gsm_network *network;
@@ -160,6 +128,7 @@ int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *new_bts,
lchan_free(new_lchan);
return -ENOMEM;
}
+ ho->from_hodec_id = from_hodec_id;
ho->old_lchan = old_lchan;
ho->new_lchan = new_lchan;
ho->ho_ref = ho_ref++;
@@ -282,6 +251,7 @@ static int ho_chan_activ_ack(struct gsm_lchan *new_lchan)
static int ho_chan_activ_nack(struct gsm_lchan *new_lchan)
{
struct bsc_handover *ho;
+ struct handover_decision_callbacks *hdc;
ho = bsc_ho_by_new_lchan(new_lchan);
if (!ho) {
@@ -289,6 +259,10 @@ static int ho_chan_activ_nack(struct gsm_lchan *new_lchan)
return -ENODEV;
}
+ hdc = handover_decision_callbacks_get(ho->from_hodec_id);
+ if (hdc && hdc->on_ho_chan_activ_nack)
+ hdc->on_ho_chan_activ_nack(ho);
+
new_lchan->conn->ho_lchan = NULL;
new_lchan->conn = NULL;
handover_free(ho);
@@ -341,6 +315,7 @@ static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan)
struct gsm_network *net = old_lchan->ts->trx->bts->network;
struct bsc_handover *ho;
struct gsm_lchan *new_lchan;
+ struct handover_decision_callbacks *hdc;
ho = bsc_ho_by_old_lchan(old_lchan);
if (!ho) {
@@ -348,6 +323,10 @@ static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan)
return -ENODEV;
}
+ hdc = handover_decision_callbacks_get(ho->from_hodec_id);
+ if (hdc && hdc->on_ho_failure)
+ hdc->on_ho_failure(ho);
+
rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED]);
new_lchan = ho->new_lchan;
@@ -383,6 +362,18 @@ static int ho_rsl_detect(struct gsm_lchan *new_lchan)
return 0;
}
+static int ho_meas_rep(struct gsm_meas_rep *mr)
+{
+ struct handover_decision_callbacks *hdc;
+ enum hodec_id hodec_id = ho_get_algorithm(mr->lchan->ts->trx->bts->ho);
+
+ hdc = handover_decision_callbacks_get(hodec_id);
+ if (!hdc || !hdc->on_measurement_report)
+ return 0;
+ hdc->on_measurement_report(mr);
+ return 0;
+}
+
static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
{
@@ -404,6 +395,8 @@ static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal,
return ho_gsm48_ho_compl(lchan);
case S_LCHAN_HANDOVER_FAIL:
return ho_gsm48_ho_fail(lchan);
+ case S_LCHAN_MEAS_REP:
+ return ho_meas_rep(lchan_data->mr);
}
break;
default:
@@ -445,3 +438,18 @@ int bsc_ho_count(struct gsm_bts *bts, bool inter_cell)
return count;
}
+
+void handover_decision_callbacks_register(struct handover_decision_callbacks *hdc)
+{
+ llist_add_tail(&hdc->entry, &handover_decision_callbacks);
+}
+
+struct handover_decision_callbacks *handover_decision_callbacks_get(int hodec_id)
+{
+ struct handover_decision_callbacks *hdc;
+ llist_for_each_entry(hdc, &handover_decision_callbacks, entry) {
+ if (hdc->hodec_id == hodec_id)
+ return hdc;
+ }
+ return NULL;
+}