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 + +#include +#include + 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 #include #include - -#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 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; +}