From 49428b2f3d888751a6288b49fb8d084f18212b36 Mon Sep 17 00:00:00 2001 From: Philipp Maier Date: Tue, 22 Jan 2019 11:29:06 +0100 Subject: assignment_fsm: fix channel allocator preferences When the MSC allocates a channel through the ASSIGNMENT REQUEST, it may ask for a TCH/H and a TCH/F at the same time and tell which of the two types it prefers. The process of channel allocation currently selects, based on the BTS, MSC and MS capabilites exactly one apropriate codec/rate (e.g. TCH/H) and then trys to allocate it. If that allocation fails, there is no way to try the second choice and the assignment fails. For example: The MSC asks for TCH/F and TCH/H, prefering TCH/F, then the channel allocator will try TCH/F and if it fails (all TCH/F are currently in use), then TCH/H is never tried. Since the BSC currently only trys the first best codec/rate that is supported it also ignores the preference. Lets fix those problems by including the preference information and both possible codec/rate settings into the channel allocation decision. Change-Id: I5239e05c1cfbcb8af28f43373a58fa6c2d216c51 Related: OS#3503 --- include/osmocom/bsc/codec_pref.h | 13 ++- include/osmocom/bsc/gsm_data.h | 22 +++- src/osmo-bsc/assignment_fsm.c | 224 ++++++++++++++++++++++++++++++------- src/osmo-bsc/codec_pref.c | 42 +++---- src/osmo-bsc/handover_fsm.c | 18 ++- src/osmo-bsc/osmo_bsc_bssap.c | 129 ++++++++++++++++----- tests/codec_pref/codec_pref_test.c | 8 +- 7 files changed, 348 insertions(+), 108 deletions(-) diff --git a/include/osmocom/bsc/codec_pref.h b/include/osmocom/bsc/codec_pref.h index 51340c118..adefe0473 100644 --- a/include/osmocom/bsc/codec_pref.h +++ b/include/osmocom/bsc/codec_pref.h @@ -9,14 +9,19 @@ struct gsm_audio_support; struct bts_codec_conf; struct bsc_msc_data; struct gsm_bts; +struct channel_mode_and_rate; -int match_codec_pref(enum gsm48_chan_mode *chan_mode, - bool *full_rate, - uint16_t *s15_s0, +enum rate_pref { + RATE_PREF_NONE, + RATE_PREF_HR, + RATE_PREF_FR, +}; + +int match_codec_pref(struct channel_mode_and_rate *ch_mode_rate, const struct gsm0808_channel_type *ct, const struct gsm0808_speech_codec_list *scl, const struct bsc_msc_data *msc, - const struct gsm_bts *bts); + const struct gsm_bts *bts, enum rate_pref rate_pref); void gen_bss_supported_codec_list(struct gsm0808_speech_codec_list *scl, const struct bsc_msc_data *msc, diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h index a17f67e68..931e36356 100644 --- a/include/osmocom/bsc/gsm_data.h +++ b/include/osmocom/bsc/gsm_data.h @@ -99,6 +99,12 @@ enum subscr_sccp_state { SUBSCR_SCCP_ST_CONNECTED }; +struct channel_mode_and_rate { + enum gsm48_chan_mode chan_mode; + bool full_rate; + uint16_t s15_s0; +}; + struct assignment_request { bool aoip; @@ -107,9 +113,12 @@ struct assignment_request { char msc_rtp_addr[INET_ADDRSTRLEN]; uint16_t msc_rtp_port; - enum gsm48_chan_mode chan_mode; - bool full_rate; - uint16_t s15_s0; + /* Prefered rate/codec setting (mandatory) */ + struct channel_mode_and_rate ch_mode_rate_pref; + + /* Alternate rate/codec setting (optional) */ + bool ch_mode_rate_alt_present; + struct channel_mode_and_rate ch_mode_rate_alt; }; struct assignment_fsm_data { @@ -282,6 +291,13 @@ struct gsm_subscriber_connection { /* pointer to "other" connection, if Call Leg Relocation was successful */ struct gsm_subscriber_connection *other; } lcls; + + /* Depending on the preferences that where submitted together with + * the assignment and the current channel load, the BSC has to select + * one of the offered codec/rates. The final selection by the BSC is + * stored here and is used when sending the assignment complete or + * when performing a handover procedure. */ + struct channel_mode_and_rate ch_mode_rate; }; diff --git a/src/osmo-bsc/assignment_fsm.c b/src/osmo-bsc/assignment_fsm.c index a24f7f904..e7dc945af 100644 --- a/src/osmo-bsc/assignment_fsm.c +++ b/src/osmo-bsc/assignment_fsm.c @@ -170,7 +170,7 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn) if (gscon_is_aoip(conn)) { /* Extrapolate speech codec from speech mode */ gsm0808_speech_codec_from_chan_type(&sc, perm_spch); - sc.cfg = conn->assignment.req.s15_s0; + sc.cfg = conn->ch_mode_rate.s15_s0; sc_ptr = ≻ } } @@ -251,9 +251,11 @@ static void assignment_fsm_update_id(struct gsm_subscriber_connection *conn) new_lchan->nr); } -static bool lchan_type_compat_with_mode(enum gsm_chan_t type, - enum gsm48_chan_mode chan_mode, int full_rate) +static bool lchan_type_compat_with_mode(enum gsm_chan_t type, const struct channel_mode_and_rate *ch_mode_rate) { + enum gsm48_chan_mode chan_mode = ch_mode_rate->chan_mode; + bool full_rate = ch_mode_rate->full_rate; + switch (chan_mode) { case GSM48_CMODE_SIGN: switch (type) { @@ -296,60 +298,129 @@ void assignment_fsm_init() OSMO_ASSERT(osmo_fsm_register(&assignment_fsm) == 0); } -void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts *bts, - struct assignment_request *req) +/* Check if the incoming assignment requests requires a voice stream or not, + * we will look at the preferred and the alternate channel mode and also make + * sure that both are consistent. */ +static int check_requires_voice_stream(struct gsm_subscriber_connection *conn) { - struct osmo_fsm_inst *fi; - struct lchan_activate_info info; - - OSMO_ASSERT(conn); - OSMO_ASSERT(conn->fi); - OSMO_ASSERT(!conn->assignment.fi); - OSMO_ASSERT(!conn->assignment.new_lchan); - - assignment_count(BSC_CTR_ASSIGNMENT_ATTEMPTED); - - fi = osmo_fsm_inst_alloc_child(&assignment_fsm, conn->fi, GSCON_EV_ASSIGNMENT_END); - OSMO_ASSERT(fi); - conn->assignment.fi = fi; - fi->priv = conn; - - conn->assignment.req = *req; + bool result_requires_voice_alt = false; + bool result_requires_voice_pref = false; + struct assignment_request *req = &conn->assignment.req; + struct osmo_fsm_inst *fi = conn->fi; - switch (req->chan_mode) { + /* When the assignment request indicates that there is an alternate + * rate available (e.g. "Full or Half rate channel, Half rate + * preferred..."), then both must be either voice or either signalling, + * a mismatch is not permitted */ + /* Check the prefered setting */ + switch (req->ch_mode_rate_pref.chan_mode) { case GSM48_CMODE_SPEECH_V1: case GSM48_CMODE_SPEECH_EFR: case GSM48_CMODE_SPEECH_AMR: - conn->assignment.requires_voice_stream = true; - /* Select an lchan below. */ + result_requires_voice_pref = true; break; - case GSM48_CMODE_SIGN: - conn->assignment.requires_voice_stream = false; - /* Select an lchan below. */ + result_requires_voice_pref = false; break; + default: + assignment_fail(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP, + "Prefered channel mode not supported: %s", + gsm48_chan_mode_name(req->ch_mode_rate_pref.chan_mode)); + return -EINVAL; + } + conn->assignment.requires_voice_stream = result_requires_voice_pref; + /* If there is an alternate setting, check that one as well */ + if (!req->ch_mode_rate_alt_present) + return 0; + switch (req->ch_mode_rate_alt.chan_mode) { + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + result_requires_voice_alt = true; + break; + case GSM48_CMODE_SIGN: + result_requires_voice_alt = false; + break; default: assignment_fail(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP, - "Channel mode not supported: %s", - gsm48_chan_mode_name(req->chan_mode)); - return; + "Alternate channel mode not supported: %s", + gsm48_chan_mode_name(req->ch_mode_rate_alt.chan_mode)); + return -EINVAL; + } + + /* Make sure both settings match */ + if (result_requires_voice_pref != result_requires_voice_alt) { + assignment_fail(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP, + "Inconsistent channel modes: %s != %s", + gsm48_chan_mode_name(req->ch_mode_rate_pref.chan_mode), + gsm48_chan_mode_name(req->ch_mode_rate_alt.chan_mode)); + return -EINVAL; } - if (conn->lchan - && lchan_type_compat_with_mode(conn->lchan->type, req->chan_mode, req->full_rate)) { + return 0; +} - if (conn->lchan->tch_mode == req->chan_mode) { +/* Check if the conn is already associated with an lchan. If yes, we will check + * if that lchan is compatible with the preferred rate/codec. If the lchan + * turns out to be incompatible we try with the alternate rate/codec. */ +static int check_for_existing_lchan(struct gsm_subscriber_connection *conn) +{ + struct assignment_request *req = &conn->assignment.req; + + if (!conn->lchan) + return 0; + + /* Check if the currently existing lchan is compatible with the + * preferred rate/codec. */ + if (lchan_type_compat_with_mode(conn->lchan->type, &req->ch_mode_rate_pref)) { + if (conn->lchan->tch_mode == req->ch_mode_rate_pref.chan_mode) { /* current lchan suffices and already is in the right mode. We're done. */ LOG_ASSIGNMENT(conn, LOGL_DEBUG, "Current lchan is compatible with requested chan_mode," " sending BSSMAP Assignment Complete directly." " requested chan_mode=%s; current lchan is %s\n", - gsm48_chan_mode_name(req->chan_mode), + gsm48_chan_mode_name(req->ch_mode_rate_pref.chan_mode), gsm_lchan_name(conn->lchan)); + + conn->ch_mode_rate = req->ch_mode_rate_pref; + send_assignment_complete(conn); - return; + return 1; + } + + if (!req->ch_mode_rate_alt_present) { + /* FIXME: send Channel Mode Modify to put the current lchan in the right mode, and kick + * off its RTP stream setup code path. See gsm48_lchan_modify() and + * gsm48_rx_rr_modif_ack(), and see lchan_fsm.h LCHAN_EV_CHAN_MODE_MODIF_* */ + LOG_ASSIGNMENT(conn, LOGL_ERROR, + "NOT IMPLEMENTED:" + " Current lchan would be compatible, we should send Channel Mode Modify\n"); + return 0; + } + } + + /* Exit early when no alternate choice for a rate/codec exists. */ + if (!req->ch_mode_rate_alt_present) + return 0; + + /* In cases where the prefered rate/codec does is not compatible, + * try the alternate rate/codec. */ + if (lchan_type_compat_with_mode(conn->lchan->type, &req->ch_mode_rate_alt)) { + if (conn->lchan->tch_mode == req->ch_mode_rate_alt.chan_mode) { + /* current lchan suffices and already is in the right mode. We're done. */ + LOG_ASSIGNMENT(conn, LOGL_DEBUG, + "Current lchan is compatible with requested chan_mode," + " sending BSSMAP Assignment Complete directly." + " requested chan_mode=%s; current lchan is %s\n", + gsm48_chan_mode_name(req->ch_mode_rate_alt.chan_mode), + gsm_lchan_name(conn->lchan)); + + conn->ch_mode_rate = req->ch_mode_rate_alt; + + send_assignment_complete(conn); + return 1; } /* FIXME: send Channel Mode Modify to put the current lchan in the right mode, and kick @@ -360,29 +431,100 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts " Current lchan would be compatible, we should send Channel Mode Modify\n"); } - conn->assignment.new_lchan = lchan_select_by_chan_mode(bts, req->chan_mode, req->full_rate); + return 0; +} + +/* Check if a new lchan finally exists, if not log the problem and inform the + * MSC by sending an BSSMAP assignment failure */ +static int check_new_lchan(struct gsm_subscriber_connection *conn, struct osmo_fsm_inst *fi) +{ + struct assignment_request *req = &conn->assignment.req; - if (!conn->assignment.new_lchan) { + if (!conn->assignment.new_lchan && req->ch_mode_rate_alt_present) { + assignment_count_result(BSC_CTR_ASSIGNMENT_NO_CHANNEL); + assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, + "BSSMAP Assignment Command:" + " No lchan available for: chan_mode_pref=%s, full_rate_pref=%i / chan_mode_alt=%s, full_rate_alt=%i\n", + get_value_string(gsm48_chan_mode_names, req->ch_mode_rate_pref.chan_mode), + req->ch_mode_rate_pref.full_rate, get_value_string(gsm48_chan_mode_names, + req->ch_mode_rate_alt.chan_mode), + req->ch_mode_rate_alt.full_rate); + return -1; + } else if (!conn->assignment.new_lchan) { assignment_count_result(BSC_CTR_ASSIGNMENT_NO_CHANNEL); assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, "BSSMAP Assignment Command:" " No lchan available for: chan_mode=%s, full_rate=%i\n", - get_value_string(gsm48_chan_mode_names, req->chan_mode), req->full_rate); + get_value_string(gsm48_chan_mode_names, req->ch_mode_rate_pref.chan_mode), + req->ch_mode_rate_pref.full_rate); + return -1; + } + + return 0; +} + +void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts *bts, + struct assignment_request *req) +{ + struct osmo_fsm_inst *fi; + struct lchan_activate_info info; + + OSMO_ASSERT(conn); + OSMO_ASSERT(conn->fi); + OSMO_ASSERT(!conn->assignment.fi); + OSMO_ASSERT(!conn->assignment.new_lchan); + + assignment_count(BSC_CTR_ASSIGNMENT_ATTEMPTED); + + fi = osmo_fsm_inst_alloc_child(&assignment_fsm, conn->fi, GSCON_EV_ASSIGNMENT_END); + OSMO_ASSERT(fi); + conn->assignment.fi = fi; + fi->priv = conn; + + /* Create a copy of the request data and use that copy from now on. */ + conn->assignment.req = *req; + req = &conn->assignment.req; + + /* Check if we need a voice stream. If yes, set the approriate struct + * members in conn */ + if (check_requires_voice_stream(conn) < 0) return; + + /* There may be an already existing lchan, if yes, try to work with + * the existing lchan */ + if (check_for_existing_lchan(conn)) + return; + + /* Try to allocate a new lchan with the preferred codec/rate choice */ + conn->assignment.new_lchan = + lchan_select_by_chan_mode(bts, req->ch_mode_rate_pref.chan_mode, req->ch_mode_rate_pref.full_rate); + conn->ch_mode_rate = req->ch_mode_rate_pref; + + /* In case the lchan allocation fails, we try with the alternat + * codec/rate choice (if possible) */ + if (!conn->assignment.new_lchan && req->ch_mode_rate_alt_present) { + conn->assignment.new_lchan = + lchan_select_by_chan_mode(bts, req->ch_mode_rate_alt.chan_mode, req->ch_mode_rate_alt.full_rate); + conn->ch_mode_rate = req->ch_mode_rate_alt; } + /* Check whether the lchan allocation was successful or not and tear + * down the assignment in case of failure. */ + if (check_new_lchan(conn, fi) < 0) + return; + assignment_fsm_update_id(conn); LOG_ASSIGNMENT(conn, LOGL_INFO, "Starting Assignment: chan_mode=%s, full_rate=%d," " aoip=%s MSC-rtp=%s:%u\n", - gsm48_chan_mode_name(req->chan_mode), req->full_rate, + gsm48_chan_mode_name(conn->ch_mode_rate.chan_mode), conn->ch_mode_rate.full_rate, req->aoip ? "yes" : "no", req->msc_rtp_addr, req->msc_rtp_port); assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE); info = (struct lchan_activate_info){ .activ_for = FOR_ASSIGNMENT, .for_conn = conn, - .chan_mode = req->chan_mode, - .s15_s0 = req->s15_s0, + .chan_mode = conn->ch_mode_rate.chan_mode, + .s15_s0 = conn->ch_mode_rate.s15_s0, .requires_voice_stream = conn->assignment.requires_voice_stream, .msc_assigned_cic = req->msc_assigned_cic, .re_use_mgw_endpoint_from_lchan = conn->lchan, diff --git a/src/osmo-bsc/codec_pref.c b/src/osmo-bsc/codec_pref.c index c99c38335..a94d6a834 100644 --- a/src/osmo-bsc/codec_pref.c +++ b/src/osmo-bsc/codec_pref.c @@ -266,21 +266,18 @@ static uint16_t gen_bss_supported_amr_s15_s0(const struct bsc_msc_data *msc, con /*! Match the codec preferences from local config with a received codec preferences IEs received from the * MSC and the BTS' codec configuration. - * \param[out] chan_mode GSM 04.08 channel mode. - * \param[out] full_rate true if full-rate. - * \param[out] s15_s0 codec configuration bits S15-S0 (AMR) + * \param[out] ch_mode_rate resulting codec and rate information * \param[in] ct GSM 08.08 channel type received from MSC. * \param[in] scl GSM 08.08 speech codec list received from MSC (optional). * \param[in] msc associated msc (current codec settings). * \param[in] bts associated bts (current codec settings). + * \param[in] pref selected rate preference (full, half or none). * \returns 0 on success, -1 in case no match was found */ -int match_codec_pref(enum gsm48_chan_mode *chan_mode, - bool *full_rate, - uint16_t *s15_s0, +int match_codec_pref(struct channel_mode_and_rate *ch_mode_rate, const struct gsm0808_channel_type *ct, const struct gsm0808_speech_codec_list *scl, const struct bsc_msc_data *msc, - const struct gsm_bts *bts) + const struct gsm_bts *bts, enum rate_pref rate_pref) { unsigned int i; uint8_t perm_spch; @@ -297,6 +294,18 @@ int match_codec_pref(enum gsm48_chan_mode *chan_mode, /* Pick a permitted speech value from the global codec configuration list */ perm_spch = audio_support_to_gsm88(msc->audio_support[i]); + /* Determine if the result is a half or full rate codec */ + rc = full_rate_from_perm_spch(&ch_mode_rate->full_rate, perm_spch); + if (rc < 0) + return -EINVAL; + + /* If we have a preference for FR or HR in our request, we + * discard the potential match */ + if (rate_pref == RATE_PREF_HR && ch_mode_rate->full_rate) + continue; + if (rate_pref == RATE_PREF_FR && !ch_mode_rate->full_rate) + continue; + /* Check this permitted speech value against the BTS specific parameters. * if the BTS does not support the codec, try the next one */ if (!test_codec_support_bts(bts, perm_spch)) @@ -312,19 +321,14 @@ int match_codec_pref(enum gsm48_chan_mode *chan_mode, /* Exit without result, in case no match can be deteched */ if (!match) { - *full_rate = false; - *chan_mode = GSM48_CMODE_SIGN; - *s15_s0 = 0; + ch_mode_rate->full_rate = false; + ch_mode_rate->chan_mode = GSM48_CMODE_SIGN; + ch_mode_rate->s15_s0 = 0; return -1; } - /* Determine if the result is a half or full rate codec */ - rc = full_rate_from_perm_spch(full_rate, perm_spch); - if (rc < 0) - return -EINVAL; - /* Lookup a channel mode for the selected codec */ - *chan_mode = gsm88_to_chan_mode(perm_spch); + ch_mode_rate->chan_mode = gsm88_to_chan_mode(perm_spch); /* Special handling for AMR */ if (perm_spch == GSM0808_PERM_HR3 || perm_spch == GSM0808_PERM_FR3) { @@ -337,9 +341,9 @@ int match_codec_pref(enum gsm48_chan_mode *chan_mode, * result */ amr_s15_s0_supported = gen_bss_supported_amr_s15_s0(msc, bts, (perm_spch == GSM0808_PERM_HR3)); if (sc_match) - *s15_s0 = sc_match->cfg & amr_s15_s0_supported; + ch_mode_rate->s15_s0 = sc_match->cfg & amr_s15_s0_supported; else - *s15_s0 = amr_s15_s0_supported; + ch_mode_rate->s15_s0 = amr_s15_s0_supported; /* NOTE: The function test_codec_pref() will populate the * sc_match pointer from the searched speech codec list. For @@ -349,7 +353,7 @@ int match_codec_pref(enum gsm48_chan_mode *chan_mode, * However s15_s0 is always populated with a meaningful value, * regardless if AoIP is in use or not. */ } else - *s15_s0 = 0; + ch_mode_rate->s15_s0 = 0; return 0; } diff --git a/src/osmo-bsc/handover_fsm.c b/src/osmo-bsc/handover_fsm.c index 35f2e5553..dc89ca45e 100644 --- a/src/osmo-bsc/handover_fsm.c +++ b/src/osmo-bsc/handover_fsm.c @@ -521,10 +521,8 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn, struct bsc_msc_data *msc = conn->sccp.msc; struct handover_in_req *req = &ho->inter_bsc_in; int match_idx; - enum gsm48_chan_mode mode; - bool full_rate = false; - uint16_t s15_s0; struct osmo_fsm_inst *fi; + struct channel_mode_and_rate ch_mode_rate = {}; handover_fsm_alloc(conn); @@ -562,7 +560,7 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn, bts->nr, req->cell_id_target_name); /* Figure out channel type */ - if (match_codec_pref(&mode, &full_rate, &s15_s0, &req->ct, &req->scl, msc, bts)) { + if (match_codec_pref(&ch_mode_rate, &req->ct, &req->scl, msc, bts, RATE_PREF_NONE)) { LOG_HO(conn, LOGL_DEBUG, "BTS %u has no matching channel codec (%s, speech codec list len = %u)\n", bts->nr, gsm0808_channel_type_name(&req->ct), req->scl.len); @@ -570,10 +568,10 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn, } LOG_HO(conn, LOGL_DEBUG, "BTS %u: Found matching audio type: %s %s (for %s)\n", - bts->nr, gsm48_chan_mode_name(mode), full_rate? "full-rate" : "half-rate", + bts->nr, gsm48_chan_mode_name(ch_mode_rate.chan_mode), ch_mode_rate.full_rate? "full-rate" : "half-rate", gsm0808_channel_type_name(&req->ct)); - lchan = lchan_select_by_chan_mode(bts, mode, full_rate); + lchan = lchan_select_by_chan_mode(bts, ch_mode_rate.chan_mode, ch_mode_rate.full_rate); if (!lchan) { LOG_HO(conn, LOGL_DEBUG, "BTS %u has no matching free channels\n", bts->nr); continue; @@ -605,9 +603,9 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn, info = (struct lchan_activate_info){ .activ_for = FOR_HANDOVER, .for_conn = conn, - .chan_mode = mode, - .s15_s0 = s15_s0, - .requires_voice_stream = chan_mode_is_tch(mode), + .chan_mode = ch_mode_rate.chan_mode, + .s15_s0 = ch_mode_rate.s15_s0, + .requires_voice_stream = chan_mode_is_tch(ch_mode_rate.chan_mode), .msc_assigned_cic = req->msc_assigned_cic, }; @@ -715,7 +713,7 @@ static void send_handover_performed(struct gsm_subscriber_connection *conn) if (gscon_is_aoip(conn)) { /* Extrapolate speech codec from speech mode */ gsm0808_speech_codec_from_chan_type(&sc, ho_perf_params.speech_version_chosen); - sc.cfg = conn->assignment.req.s15_s0; + sc.cfg = conn->ch_mode_rate.s15_s0; memcpy(&ho_perf_params.speech_codec_chosen, &sc, sizeof(sc)); ho_perf_params.speech_codec_chosen_present = true; } diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c index f2ccf2cf1..7b3cd25fc 100644 --- a/src/osmo-bsc/osmo_bsc_bssap.c +++ b/src/osmo-bsc/osmo_bsc_bssap.c @@ -599,6 +599,97 @@ static int bssmap_handle_lcls_connect_ctrl(struct gsm_subscriber_connection *con return 0; } +/* Select a prefered and an alternative codec rate depending on the available + * capabilities. This decision does not include the actual channel load yet, + * this is also the reason why the result is a prefered and an alternate + * setting. The final decision is made in assignment_fsm.c when the actual + * lchan is requested. The preferred lchan will be requested first. If we + * find an alternate setting here, this one will be tried secondly if our + * primary coice fails. */ +static int select_codec(struct assignment_request *req, struct gsm0808_channel_type *ct, + struct gsm_subscriber_connection *conn) +{ + int rc; + struct bsc_msc_data *msc; + + msc = conn->sccp.msc; + req->ch_mode_rate_alt_present = false; + + switch (ct->ch_rate_type & GSM0808_SPEECH_PERM) { + case GSM0808_DATA_FULL_BM: + rc = match_codec_pref(&req->ch_mode_rate_pref, ct, &conn->codec_list, msc, conn_get_bts(conn), + RATE_PREF_FR); + break; + case GSM0808_DATA_HALF_LM: + rc = match_codec_pref(&req->ch_mode_rate_pref, ct, &conn->codec_list, msc, conn_get_bts(conn), + RATE_PREF_HR); + break; + case GSM0808_DATA_FULL_RPREF: + rc = match_codec_pref(&req->ch_mode_rate_pref, ct, &conn->codec_list, msc, conn_get_bts(conn), + RATE_PREF_FR); + if (rc < 0) { + rc = match_codec_pref(&req->ch_mode_rate_pref, ct, &conn->codec_list, msc, conn_get_bts(conn), + RATE_PREF_NONE); + break; + } + rc = match_codec_pref(&req->ch_mode_rate_alt, ct, &conn->codec_list, msc, conn_get_bts(conn), + RATE_PREF_HR); + if (rc == 0) + req->ch_mode_rate_alt_present = true; + rc = 0; + break; + case GSM0808_DATA_HALF_PREF: + rc = match_codec_pref(&req->ch_mode_rate_pref, ct, &conn->codec_list, msc, conn_get_bts(conn), + RATE_PREF_HR); + + if (rc < 0) { + rc = match_codec_pref(&req->ch_mode_rate_pref, ct, &conn->codec_list, msc, conn_get_bts(conn), + RATE_PREF_NONE); + break; + } + rc = match_codec_pref(&req->ch_mode_rate_alt, ct, &conn->codec_list, msc, conn_get_bts(conn), + RATE_PREF_FR); + if (rc == 0) + req->ch_mode_rate_alt_present = true; + rc = 0; + break; + default: + rc = -EINVAL; + break; + } + + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "No supported audio type found for channel_type =" + " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[%s] }\n", + ct->ch_indctr, ct->ch_rate_type, osmo_hexdump(ct->perm_spch, ct->perm_spch_len)); + /* TODO: actually output codec names, e.g. implement + * gsm0808_permitted_speech_names[] and iterate perm_spch. */ + return -EINVAL; + } + + if (req->ch_mode_rate_alt_present) { + DEBUGP(DMSC, "Found matching audio type (preferred): %s %s for channel_type =" + " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n", + req->ch_mode_rate_pref.full_rate ? "full rate" : "half rate", + get_value_string(gsm48_chan_mode_names, req->ch_mode_rate_pref.chan_mode), + ct->ch_indctr, ct->ch_rate_type, osmo_hexdump(ct->perm_spch, ct->perm_spch_len)); + DEBUGP(DMSC, "Found matching audio type (alternative): %s %s for channel_type =" + " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n", + req->ch_mode_rate_alt.full_rate ? "full rate" : "half rate", + get_value_string(gsm48_chan_mode_names, req->ch_mode_rate_alt.chan_mode), + ct->ch_indctr, ct->ch_rate_type, osmo_hexdump(ct->perm_spch, ct->perm_spch_len)); + } else { + DEBUGP(DMSC, "Found matching audio type: %s %s for channel_type =" + " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n", + req->ch_mode_rate_pref.full_rate ? "full rate" : "half rate", + get_value_string(gsm48_chan_mode_names, req->ch_mode_rate_pref.chan_mode), + ct->ch_indctr, ct->ch_rate_type, osmo_hexdump(ct->perm_spch, ct->perm_spch_len)); + + } + + return 0; +} + /* * Handle the assignment request message. * @@ -608,18 +699,15 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn, struct msgb *msg, unsigned int length) { struct msgb *resp; - struct bsc_msc_data *msc; struct tlv_parsed tp; uint16_t cic = 0; - enum gsm48_chan_mode chan_mode = GSM48_CMODE_SIGN; - bool full_rate = false; - uint16_t s15_s0 = 0; bool aoip = false; struct sockaddr_storage rtp_addr; struct gsm0808_channel_type ct; uint8_t cause; int rc; struct assignment_request req = {}; + struct channel_mode_and_rate ch_mode_rate_pref = {}; if (!conn) { LOGP(DMSC, LOGL_ERROR, @@ -627,7 +715,6 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn, return -1; } - msc = conn->sccp.msc; aoip = gscon_is_aoip(conn); tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0); @@ -715,33 +802,19 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn, goto reject; } + req = (struct assignment_request){ + .aoip = aoip, + .msc_assigned_cic = cic, + }; + /* Match codec information from the assignment command against the * local preferences of the BSC and BTS */ - rc = match_codec_pref(&chan_mode, &full_rate, &s15_s0, &ct, &conn->codec_list, - msc, conn_get_bts(conn)); + rc = select_codec(&req, &ct, conn); if (rc < 0) { - LOGP(DMSC, LOGL_ERROR, "No supported audio type found for channel_type =" - " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n", - ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len)); - /* TODO: actually output codec names, e.g. implement - * gsm0808_permitted_speech_names[] and iterate perm_spch. */ cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL; goto reject; } - DEBUGP(DMSC, "Found matching audio type: %s %s for channel_type =" - " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n", - full_rate? "full rate" : "half rate", - get_value_string(gsm48_chan_mode_names, chan_mode), - ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len)); - - req = (struct assignment_request){ - .aoip = aoip, - .msc_assigned_cic = cic, - .chan_mode = chan_mode, - .full_rate = full_rate, - .s15_s0 = s15_s0 - }; if (aoip) { unsigned int rc = osmo_sockaddr_to_str_and_uint(req.msc_rtp_addr, sizeof(req.msc_rtp_addr), @@ -755,9 +828,13 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn, } break; case GSM0808_CHAN_SIGN: + ch_mode_rate_pref = (struct channel_mode_and_rate) { + .chan_mode = GSM48_CMODE_SIGN, + }; + req = (struct assignment_request){ .aoip = aoip, - .chan_mode = chan_mode, + .ch_mode_rate_pref = ch_mode_rate_pref, }; break; default: diff --git a/tests/codec_pref/codec_pref_test.c b/tests/codec_pref/codec_pref_test.c index 534b99ef7..bb5468a4c 100644 --- a/tests/codec_pref/codec_pref_test.c +++ b/tests/codec_pref/codec_pref_test.c @@ -380,9 +380,7 @@ static int test_match_codec_pref(const struct gsm0808_channel_type *ct, const st { int rc; unsigned int i; - bool full_rate; - enum gsm48_chan_mode chan_mode; - uint16_t s15_s0; + struct channel_mode_and_rate ch_mode_rate = { }; printf("Determining channel mode and rate:\n"); @@ -407,9 +405,9 @@ static int test_match_codec_pref(const struct gsm0808_channel_type *ct, const st printf(" codec->efr=%u\n", bts->codec.efr); printf(" codec->amr=%u\n", bts->codec.amr); - rc = match_codec_pref(&chan_mode, &full_rate, &s15_s0, ct, scl, msc, bts); + rc = match_codec_pref(&ch_mode_rate, ct, scl, msc, bts, RATE_PREF_NONE); printf(" * result: rc=%i, full_rate=%i, s15_s0=%04x, chan_mode=%s\n", - rc, full_rate, s15_s0, gsm48_chan_mode_name(chan_mode)); + rc, ch_mode_rate.full_rate, ch_mode_rate.s15_s0, gsm48_chan_mode_name(ch_mode_rate.chan_mode)); printf("\n"); -- cgit v1.2.3