diff --git a/TODO-RELEASE b/TODO-RELEASE index 78fcea38f..551cc18bd 100644 --- a/TODO-RELEASE +++ b/TODO-RELEASE @@ -10,3 +10,4 @@ osmo-bsc VTY Timeslot phys_chan_config will now write back with new dynamic timeslot names: 'DYNAMIC/OSMOCOM' instead of 'TCH/F_TCH/H_SDCCH8_PDCH' and 'DYNAMIC/IPACCESS' instead of 'TCH/F_PDCH' osmo-bsc CTRL CTRL commands like 'bts.N.channel-load' will now respond with new dynamic timeslot names: 'DYNAMIC/OSMOCOM' instead of 'TCH/F_TCH/H_SDCCH8_PDCH' and 'DYNAMIC/IPACCESS' instead of 'TCH/F_PDCH' osmo-bsc CTRL,VTY osmo_fsm instance IDs now use new dynamic timeslot names 'DYNAMIC_OSMOCOM' and 'DYNAMIC_IPACCESS' +libosmogsm >1.8.0 circuit switched data stuff (gsm0808_enc/dec_channel_type etc.) diff --git a/include/osmocom/bsc/Makefile.am b/include/osmocom/bsc/Makefile.am index 53d45adbf..8baff9cb4 100644 --- a/include/osmocom/bsc/Makefile.am +++ b/include/osmocom/bsc/Makefile.am @@ -19,6 +19,7 @@ noinst_HEADERS = \ chan_alloc.h \ chan_counts.h \ codec_pref.h \ + data_rate_pref.h \ ctrl.h \ debug.h \ e1_config.h \ diff --git a/include/osmocom/bsc/codec_pref.h b/include/osmocom/bsc/codec_pref.h index adefe0473..5944a4a1d 100644 --- a/include/osmocom/bsc/codec_pref.h +++ b/include/osmocom/bsc/codec_pref.h @@ -17,6 +17,10 @@ enum rate_pref { RATE_PREF_FR, }; +int match_codec_pref_data(struct channel_mode_and_rate *ch_mode_rate, + const struct gsm0808_channel_type *ct, + const bool full_rate); + 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, diff --git a/include/osmocom/bsc/data_rate_pref.h b/include/osmocom/bsc/data_rate_pref.h new file mode 100644 index 000000000..da3f1fde2 --- /dev/null +++ b/include/osmocom/bsc/data_rate_pref.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +struct gsm0808_channel_type; +struct channel_mode_and_rate; + +int match_data_rate_pref(struct channel_mode_and_rate *ch_mode_rate, + const struct gsm0808_channel_type *ct, + const bool full_rate); diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am index 60a76faf0..53c091257 100644 --- a/src/osmo-bsc/Makefile.am +++ b/src/osmo-bsc/Makefile.am @@ -62,6 +62,7 @@ libbsc_la_SOURCES = \ chan_alloc.c \ chan_counts.c \ codec_pref.c \ + data_rate_pref.c \ e1_config.c \ gsm_04_08_rr.c \ gsm_data.c \ diff --git a/src/osmo-bsc/data_rate_pref.c b/src/osmo-bsc/data_rate_pref.c new file mode 100644 index 000000000..443fea72d --- /dev/null +++ b/src/osmo-bsc/data_rate_pref.c @@ -0,0 +1,152 @@ +/* + * (C) 2023 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Oliver Smith + * + * 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 . + * + */ + +#include +#include +#include +#include +#include + +static int gsm0808_data_rate_transp_to_gsm0858(enum gsm0808_data_rate_transp rate) +{ + switch (rate) { + case GSM0808_DATA_RATE_TRANSP_32k0: + return RSL_CMOD_CSD_T_32k0; + case GSM0808_DATA_RATE_TRANSP_28k8: + return RSL_CMOD_CSD_T_29k0; + case GSM0808_DATA_RATE_TRANSP_14k4: + return RSL_CMOD_CSD_T_14k4; + case GSM0808_DATA_RATE_TRANSP_9k6: + return RSL_CMOD_CSD_T_9k6; + case GSM0808_DATA_RATE_TRANSP_4k8: + return RSL_CMOD_CSD_T_4k8; + case GSM0808_DATA_RATE_TRANSP_2k4: + return RSL_CMOD_CSD_T_2k4; + case GSM0808_DATA_RATE_TRANSP_1k2: + return RSL_CMOD_CSD_T_1k2; + case GSM0808_DATA_RATE_TRANSP_600: + return RSL_CMOD_CSD_T_600; + case GSM0808_DATA_RATE_TRANSP_1200_75: + return RSL_CMOD_CSD_T_1200_75; + default: + LOGP(DMSC, LOGL_ERROR, "Unsupported transparent data rate 0x%x\n", rate); + return -1; + } +} + +static int gsm0808_data_rate_transp_to_gsm0408(enum gsm0808_data_rate_transp rate) +{ + switch (rate) { + case GSM0808_DATA_RATE_TRANSP_14k4: + return GSM48_CMODE_DATA_14k5; + case GSM0808_DATA_RATE_TRANSP_9k6: + return GSM48_CMODE_DATA_12k0; + case GSM0808_DATA_RATE_TRANSP_4k8: + return GSM48_CMODE_DATA_6k0; + case GSM0808_DATA_RATE_TRANSP_2k4: + case GSM0808_DATA_RATE_TRANSP_1k2: + case GSM0808_DATA_RATE_TRANSP_600: + case GSM0808_DATA_RATE_TRANSP_1200_75: + return GSM48_CMODE_DATA_3k6; + default: + LOGP(DMSC, LOGL_ERROR, "Unsupported transparent data rate 0x%x\n", rate); + return -1; + } +} + +static int gsm0808_data_rate_non_transp_to_gsm0408(enum gsm0808_data_rate_non_transp rate) +{ + LOGP(DMSC, LOGL_ERROR, "%s is not implemented\n", __func__); /* FIXME */ + return -1; +} + +static int gsm0808_data_rate_non_transp_to_gsm0858(enum gsm0808_data_rate_non_transp rate, bool full_rate) +{ + switch (rate) { + case GSM0808_DATA_RATE_NON_TRANSP_12000_6000: + if (full_rate) + return RSL_CMOD_CSD_NT_12k0; + return RSL_CMOD_CSD_NT_6k0; + case GSM0808_DATA_RATE_NON_TRANSP_14k5: + return RSL_CMOD_CSD_NT_14k5; + case GSM0808_DATA_RATE_NON_TRANSP_12k0: + return RSL_CMOD_CSD_NT_12k0; + case GSM0808_DATA_RATE_NON_TRANSP_6k0: + return RSL_CMOD_CSD_NT_6k0; + case GSM0808_DATA_RATE_NON_TRANSP_43k5: + return RSL_CMOD_CSD_NT_43k5; + case GSM0808_DATA_RATE_NON_TRANSP_29k0: + return RSL_CMOD_CSD_NT_28k8; + default: + LOGP(DMSC, LOGL_ERROR, "Unsupported non-transparent data rate 0x%x\n", rate); + return -1; + } +} + +static enum gsm48_chan_mode match_non_transp_data_rate(const struct gsm0808_channel_type *ct, bool full_rate) +{ + /* FIXME: Handle ct->data_rate_allowed too if it is set. Find the best + * match by comparing the preferred ct->data_rate + all allowed + * ct->data_rate_allowed against what's most suitable for the BTS. */ + + return gsm0808_data_rate_non_transp_to_gsm0858(ct->data_rate, full_rate); +} + +/*! Match the GSM 08.08 channel type received from the MSC to suitable data for + * the BTS, the GSM 04.08 channel mode, channel rate (FR/HR) and GSM 08.58 + * data rate. + * \param[out] ch_mode_rate resulting channel rate, channel mode and data rate + * \param[in] ct GSM 08.08 channel type received from MSC. + * \param[in] full_rate true means FR is preferred, false means HR + * \returns 0 on success, -1 in case no match was found */ +int match_data_rate_pref(struct channel_mode_and_rate *ch_mode_rate, + const struct gsm0808_channel_type *ct, + const bool full_rate) +{ + int rc; + *ch_mode_rate = (struct channel_mode_and_rate){}; + ch_mode_rate->chan_rate = full_rate ? CH_RATE_FULL : CH_RATE_HALF; + ch_mode_rate->data_transparent = ct->data_transparent; + + if (ct->data_transparent) { + rc = gsm0808_data_rate_transp_to_gsm0858(ct->data_rate); + if (rc == -1) + return -1; + ch_mode_rate->data_rate.t = rc; + + rc = gsm0808_data_rate_transp_to_gsm0408(ct->data_rate); + if (rc == -1) + return -1; + ch_mode_rate->chan_mode = rc; + } else { + rc = match_non_transp_data_rate(ct, full_rate); + if (rc == -1) + return -1; + ch_mode_rate->data_rate.nt = rc; + + rc = gsm0808_data_rate_non_transp_to_gsm0408(ct->data_rate); + if (rc == -1) + return -1; + ch_mode_rate->chan_mode = rc; + } + + return 0; +} diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c index 221e47779..59d5aa57c 100644 --- a/src/osmo-bsc/osmo_bsc_bssap.c +++ b/src/osmo-bsc/osmo_bsc_bssap.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -667,6 +668,67 @@ static int bssmap_handle_lcls_connect_ctrl(struct gsm_subscriber_connection *con return 0; } +/* Select a preferred and an alternative data 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 preferred 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 choice fails. */ +static int select_data_rates(struct assignment_request *req, struct gsm0808_channel_type *ct, + struct gsm_subscriber_connection *conn) +{ + int rc, i, nc = 0; + + switch (ct->ch_rate_type) { + case GSM0808_DATA_FULL_BM: + rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, true); + nc += (rc == 0) ? 1 : 0; + break; + case GSM0808_DATA_HALF_LM: + rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, false); + nc += (rc == 0) ? 1 : 0; + break; + case GSM0808_DATA_FULL_PREF_NO_CHANGE: + case GSM0808_DATA_FULL_PREF: + rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, true); + nc += (rc == 0) ? 1 : 0; + rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, false); + nc += (rc == 0) ? 1 : 0; + break; + case GSM0808_DATA_HALF_PREF_NO_CHANGE: + case GSM0808_DATA_HALF_PREF: + rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, false); + nc += (rc == 0) ? 1 : 0; + rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, true); + nc += (rc == 0) ? 1 : 0; + break; + default: + rc = -EINVAL; + break; + } + + if (!nc) { + LOGP(DMSC, LOGL_ERROR, "No supported data rate 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)); + return -EINVAL; + } + + for (i = 0; i < nc; i++) { + DEBUGP(DMSC, "Found matching data rate (pref=%d): %s %s for channel_type =" + " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n", + i, + req->ch_mode_rate_list[i].chan_rate == CH_RATE_FULL ? "full rate" : "half rate", + get_value_string(gsm48_chan_mode_names, req->ch_mode_rate_list[i].chan_mode), + ct->ch_indctr, ct->ch_rate_type, osmo_hexdump(ct->perm_spch, ct->perm_spch_len)); + } + + req->n_ch_mode_rate = nc; + + return 0; +} + /* Select a preferred and an alternative codec rate depending on the available * capabilities. This decision does not include the actual lchan availability yet, * this is also the reason why the result is a preferred and an alternate @@ -924,6 +986,38 @@ static int bssmap_handle_ass_req_tp_codec_list(struct gsm_subscriber_connection return 0; } +static int bssmap_handle_ass_req_ct_data(struct gsm_subscriber_connection *conn, struct tlv_parsed *tp, + struct gsm0808_channel_type *ct, struct assignment_request *req, + uint8_t *cause) +{ + bool aoip = gscon_is_aoip(conn); + int rc; + + *req = (struct assignment_request){ + .assign_for = ASSIGN_FOR_BSSMAP_REQ, + .aoip = aoip, + }; + + if (bssmap_handle_ass_req_tp_cic(tp, aoip, &req->msc_assigned_cic, cause) < 0) + return -1; + + if (bssmap_handle_ass_req_tp_rtp_addr(tp, aoip, req->msc_rtp_addr, sizeof(req->msc_rtp_addr), &req->msc_rtp_port, cause) < 0) + return -1; + + /* According to 3GPP TS 48.008 ยง 3.2.1.1 note 13, the codec list IE + * shall be included for aoip unless channel type is signalling. */ + if (bssmap_handle_ass_req_tp_codec_list(conn, tp, aoip, cause) < 0) + return -1; + + rc = select_data_rates(req, ct, conn); + if (rc < 0) { + *cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL; + return -1; + } + + return 0; +} + static int bssmap_handle_ass_req_ct_speech(struct gsm_subscriber_connection *conn, struct tlv_parsed *tp, struct gsm0808_channel_type *ct, struct assignment_request *req, uint8_t *cause) @@ -1023,12 +1117,12 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn, bssmap_handle_ass_req_lcls(conn, &tp); /* Currently we only support a limited subset of all - * possible channel types, such as multi-slot or CSD */ + * possible channel types, such as multi-slot */ switch (ct.ch_indctr) { case GSM0808_CHAN_DATA: - LOGP(DMSC, LOGL_ERROR, "Unsupported channel type, currently only speech is supported!\n"); - cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP; - goto reject; + if (bssmap_handle_ass_req_ct_data(conn, &tp, &ct, &req, &cause) < 0) + goto reject; + break; case GSM0808_CHAN_SPEECH: if (bssmap_handle_ass_req_ct_speech(conn, &tp, &ct, &req, &cause) < 0) goto reject;