From 3a4c4e8f0e24d081f9f3e44bc34d0401f51611a7 Mon Sep 17 00:00:00 2001 From: Vadim Yanitskiy Date: Wed, 11 Oct 2023 05:14:19 +0700 Subject: mobile: add support for Circuit Switched Data calls TODO: add VTY command(s) for changing the Bearer Capability Change-Id: I1995fa0a7a68d9b980852b664d472d4633777ac6 Related: OS#4396 --- .../layer23/include/osmocom/bb/common/settings.h | 10 ++ .../layer23/include/osmocom/bb/common/support.h | 8 ++ .../layer23/include/osmocom/bb/mobile/mncc_ms.h | 2 +- src/host/layer23/src/common/settings.c | 8 ++ src/host/layer23/src/common/support.c | 16 +++ src/host/layer23/src/mobile/gsm48_rr.c | 56 ++++++++++ src/host/layer23/src/mobile/mnccms.c | 116 +++++++++++++++++++-- src/host/layer23/src/mobile/vty_interface.c | 39 ++++++- 8 files changed, 241 insertions(+), 14 deletions(-) diff --git a/src/host/layer23/include/osmocom/bb/common/settings.h b/src/host/layer23/include/osmocom/bb/common/settings.h index 884578b0..8f03d531 100644 --- a/src/host/layer23/include/osmocom/bb/common/settings.h +++ b/src/host/layer23/include/osmocom/bb/common/settings.h @@ -1,6 +1,8 @@ #ifndef _settings_h #define _settings_h +#include + #include #include #include @@ -161,6 +163,14 @@ struct gsm_settings { uint8_t ch_cap; /* channel capability */ int8_t min_rxlev_dbm; /* min dBm to access */ + /* CSD modes */ + bool csd_tch_f144; + bool csd_tch_f96; + bool csd_tch_f48; + bool csd_tch_h48; + bool csd_tch_f24; + bool csd_tch_h24; + /* support for ASCI */ bool vgcs; /* support of VGCS */ bool vbs; /* support of VBS */ diff --git a/src/host/layer23/include/osmocom/bb/common/support.h b/src/host/layer23/include/osmocom/bb/common/support.h index 2fae57ec..b0c71f5a 100644 --- a/src/host/layer23/include/osmocom/bb/common/support.h +++ b/src/host/layer23/include/osmocom/bb/common/support.h @@ -91,6 +91,14 @@ struct gsm_support { uint8_t half_v1; uint8_t half_v3; + /* CSD modes */ + uint8_t csd_tch_f144; + uint8_t csd_tch_f96; + uint8_t csd_tch_f48; + uint8_t csd_tch_h48; + uint8_t csd_tch_f24; + uint8_t csd_tch_h24; + /* EDGE / UMTS / CDMA */ uint8_t edge_ms_sup; uint8_t edge_psk_sup; diff --git a/src/host/layer23/include/osmocom/bb/mobile/mncc_ms.h b/src/host/layer23/include/osmocom/bb/mobile/mncc_ms.h index 705c681e..00b8ec44 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/mncc_ms.h +++ b/src/host/layer23/include/osmocom/bb/mobile/mncc_ms.h @@ -1,6 +1,6 @@ #pragma once -int mncc_call(struct osmocom_ms *ms, const char *number); +int mncc_call(struct osmocom_ms *ms, const char *number, bool data); int mncc_hangup(struct osmocom_ms *ms); int mncc_answer(struct osmocom_ms *ms); int mncc_hold(struct osmocom_ms *ms); diff --git a/src/host/layer23/src/common/settings.c b/src/host/layer23/src/common/settings.c index 395d2394..10ae1d9e 100644 --- a/src/host/layer23/src/common/settings.c +++ b/src/host/layer23/src/common/settings.c @@ -100,6 +100,14 @@ int gsm_settings_init(struct osmocom_ms *ms) set->ch_cap = sup->ch_cap; set->min_rxlev_dbm = sup->min_rxlev_dbm; set->dsc_max = sup->dsc_max; + + set->csd_tch_f144 = sup->csd_tch_f144; + set->csd_tch_f96 = sup->csd_tch_f96; + set->csd_tch_f48 = sup->csd_tch_f48; + set->csd_tch_h48 = sup->csd_tch_h48; + set->csd_tch_f24 = sup->csd_tch_f24; + set->csd_tch_h24 = sup->csd_tch_h24; + set->vgcs = sup->vgcs; set->vbs = sup->vbs; diff --git a/src/host/layer23/src/common/support.c b/src/host/layer23/src/common/support.c index 2f93016c..ed514651 100644 --- a/src/host/layer23/src/common/support.c +++ b/src/host/layer23/src/common/support.c @@ -97,6 +97,14 @@ void gsm_support_init(struct osmocom_ms *ms) sup->full_v3 = 0; sup->half_v1 = 1; sup->half_v3 = 0; + + /* CSD modes */ + sup->csd_tch_f144 = 1; + sup->csd_tch_f96 = 1; + sup->csd_tch_f48 = 1; + sup->csd_tch_h48 = 1; + sup->csd_tch_f24 = 1; + sup->csd_tch_h24 = 1; } /* (3.2.1) maximum channels to scan within each band */ @@ -173,6 +181,14 @@ void gsm_support_dump(struct osmocom_ms *ms, print(priv, " Full-Rate V3 : %s\n", SUP_SET(full_v3)); print(priv, " Half-Rate V1 : %s\n", SUP_SET(half_v1)); print(priv, " Half-Rate V3 : %s\n", SUP_SET(half_v3)); + + print(priv, " CSD TCH/F14.4: %s\n", SUP_SET(csd_tch_f144)); + print(priv, " CSD TCH/F9.6 : %s\n", SUP_SET(csd_tch_f96)); + print(priv, " CSD TCH/F4.8 : %s\n", SUP_SET(csd_tch_f48)); + print(priv, " CSD TCH/H4.8 : %s\n", SUP_SET(csd_tch_h48)); + print(priv, " CSD TCH/F2.4 : %s\n", SUP_SET(csd_tch_f24)); + print(priv, " CSD TCH/H2.4 : %s\n", SUP_SET(csd_tch_h24)); + print(priv, " Min RXLEV : %d\n", set->min_rxlev_dbm); } diff --git a/src/host/layer23/src/mobile/gsm48_rr.c b/src/host/layer23/src/mobile/gsm48_rr.c index 3200412f..38f3309e 100644 --- a/src/host/layer23/src/mobile/gsm48_rr.c +++ b/src/host/layer23/src/mobile/gsm48_rr.c @@ -374,6 +374,62 @@ static uint8_t gsm48_rr_check_mode(struct osmocom_ms *ms, uint8_t chan_nr, LOGP(DRR, LOGL_INFO, "Mode: half-rate speech V3\n"); } break; + case GSM48_CMODE_DATA_14k5: + if (ch_type != RSL_CHAN_Bm_ACCHs) { + LOGP(DRR, LOGL_ERROR, + "TCH/F is expected for mode %s\n", + gsm48_chan_mode_name(mode)); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } else if (!set->csd_tch_f144) { + LOGP(DRR, LOGL_ERROR, + "Not supporting TCH/F14.4 data (%s)\n", + gsm48_chan_mode_name(mode)); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + LOGP(DRR, LOGL_INFO, "Mode: TCH/F14.4 data (%s)\n", + gsm48_chan_mode_name(mode)); + break; + case GSM48_CMODE_DATA_12k0: + if (ch_type != RSL_CHAN_Bm_ACCHs) { + LOGP(DRR, LOGL_ERROR, + "TCH/F is expected for mode %s\n", + gsm48_chan_mode_name(mode)); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } else if (!set->csd_tch_f96) { + LOGP(DRR, LOGL_ERROR, + "Not supporting TCH/F9.6 data (%s)\n", + gsm48_chan_mode_name(mode)); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + LOGP(DRR, LOGL_INFO, "Mode: TCH/F9.6 data (%s)\n", + gsm48_chan_mode_name(mode)); + break; + case GSM48_CMODE_DATA_6k0: + if ((ch_type == RSL_CHAN_Bm_ACCHs && !set->csd_tch_f48) + || (ch_type == RSL_CHAN_Lm_ACCHs && !set->csd_tch_h48)) { + LOGP(DRR, LOGL_ERROR, + "Not supporting TCH/%c4.8 data (%s)\n", + ch_type == RSL_CHAN_Bm_ACCHs ? 'F' : 'H', + gsm48_chan_mode_name(mode)); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + LOGP(DRR, LOGL_INFO, "Mode: TCH/%c4.8 data (%s)\n", + ch_type == RSL_CHAN_Bm_ACCHs ? 'F' : 'H', + gsm48_chan_mode_name(mode)); + break; + case GSM48_CMODE_DATA_3k6: + if ((ch_type == RSL_CHAN_Bm_ACCHs && !set->csd_tch_f24) + || (ch_type == RSL_CHAN_Lm_ACCHs && !set->csd_tch_h24)) { + LOGP(DRR, LOGL_ERROR, + "Not supporting TCH/%c2.4 data (%s)\n", + ch_type == RSL_CHAN_Bm_ACCHs ? 'F' : 'H', + gsm48_chan_mode_name(mode)); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + LOGP(DRR, LOGL_INFO, "Mode: TCH/%c2.4 data (%s)\n", + ch_type == RSL_CHAN_Bm_ACCHs ? 'F' : 'H', + gsm48_chan_mode_name(mode)); + break; default: LOGP(DRR, LOGL_ERROR, "Mode 0x%02x not supported!\n", mode); return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; diff --git a/src/host/layer23/src/mobile/mnccms.c b/src/host/layer23/src/mobile/mnccms.c index d23fbb70..fb09778c 100644 --- a/src/host/layer23/src/mobile/mnccms.c +++ b/src/host/layer23/src/mobile/mnccms.c @@ -134,9 +134,9 @@ static int8_t mncc_get_bearer(const struct gsm_settings *set, uint8_t speech_ver return speech_ver; } -static void mncc_set_bearer(struct gsm_mncc *mncc, - const struct gsm_settings *set, - int8_t speech_ver) +static void mncc_set_bcap_speech(struct gsm_mncc *mncc, + const struct gsm_settings *set, + int speech_ver) { int i = 0; @@ -192,6 +192,44 @@ static void mncc_set_bearer(struct gsm_mncc *mncc, mncc->bearer_cap.mode = GSM48_BCAP_TMOD_CIRCUIT; } +static void mncc_set_bcap_data(struct gsm_mncc *mncc, + const struct gsm_settings *set) +{ + struct gsm_mncc_bearer_cap *bcap = &mncc->bearer_cap; + + mncc->fields |= MNCC_F_BEARER_CAP; + + *bcap = (struct gsm_mncc_bearer_cap) { + .transfer = GSM_MNCC_BCAP_UNR_DIG, + .mode = GSM48_BCAP_TMOD_CIRCUIT, + .coding = GSM48_BCAP_CODING_GSM_STD, + /* .radio is set below */ + .data = { + /* TODO: make these fields configurable via *set */ + .rate_adaption = GSM48_BCAP_RA_V110_X30, + .sig_access = GSM48_BCAP_SA_I440_I450, + .async = 1, + .transp = GSM48_BCAP_TR_TRANSP, + .nr_data_bits = 8, + .parity = GSM48_BCAP_PAR_NONE, + .nr_stop_bits = 1, + .user_rate = GSM48_BCAP_UR_9600, + .interm_rate = GSM48_BCAP_IR_16k, + }, + }; + + if (set->ch_cap == GSM_CAP_SDCCH_TCHF_TCHH) { + if (set->half_prefer) + bcap->radio = GSM48_BCAP_RRQ_DUAL_HR; + else + bcap->radio = GSM48_BCAP_RRQ_DUAL_FR; + LOGP(DMNCC, LOGL_INFO, " support TCH/H also\n"); + } else { + bcap->radio = GSM48_BCAP_RRQ_FR_ONLY; + LOGP(DMNCC, LOGL_INFO, " support TCH/F only\n"); + } +} + /* Check the given Bearer Capability, select first supported speech codec version. * The choice between half-rate and full-rate is made based on current settings. * Return a selected codec or -1 if no speech codec was selected. */ @@ -249,6 +287,59 @@ static int mncc_handle_bcap_speech(const struct gsm_mncc_bearer_cap *bcap, return speech_ver; } +/* Check the given Bearer Capability for a data call (CSD). + * Return 0 if the bearer is accepted, otherwise return -1. */ +static int mncc_handle_bcap_data(const struct gsm_mncc_bearer_cap *bcap, + const struct gsm_settings *set) +{ + if (bcap->data.rate_adaption != GSM48_BCAP_RA_V110_X30) { + LOGP(DMNCC, LOGL_ERROR, + "%s(): Rate adaption (octet 5) 0x%02x is not supported\n", + __func__, bcap->data.rate_adaption); + return -ENOTSUP; + } + if (bcap->data.sig_access != GSM48_BCAP_SA_I440_I450) { + LOGP(DMNCC, LOGL_ERROR, + "%s(): Signalling access protocol (octet 5) 0x%02x is not supported\n", + __func__, bcap->data.sig_access); + return -ENOTSUP; + } + +#define BCAP_RATE(interm_rate, user_rate) \ + ((interm_rate << 8) | (user_rate << 0)) + + switch (BCAP_RATE(bcap->data.interm_rate, bcap->data.user_rate)) { + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_300): + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_1200): + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_2400): + if (bcap->data.transp != GSM48_BCAP_TR_TRANSP) { + LOGP(DMNCC, LOGL_ERROR, + "%s(): wrong user-rate 0x%02x for a non-transparent call\n", + __func__, bcap->data.user_rate); + return -EINVAL; + } + /* fall-through */ + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_4800): + case BCAP_RATE(GSM48_BCAP_IR_16k, GSM48_BCAP_UR_9600): + if (bcap->data.transp != GSM48_BCAP_TR_TRANSP) { + LOGP(DMNCC, LOGL_ERROR, + "%s(): only transparent calls are supported so far\n", + __func__); + return -ENOTSUP; + } + break; + default: + LOGP(DMNCC, LOGL_ERROR, + "%s(): User rate 0x%02x (octets 6a) is not supported (IR=0x%02x)\n", + __func__, bcap->data.user_rate, bcap->data.interm_rate); + return -ENOTSUP; + } + +#undef BCAP_RATE + + return 0; +} + static int mncc_handle_bcap(struct gsm_mncc *mncc_out, /* CC Call Confirmed */ const struct gsm_mncc *mncc_in, /* CC Setup */ const struct gsm_settings *set) @@ -262,7 +353,7 @@ static int mncc_handle_bcap(struct gsm_mncc *mncc_out, /* CC Call Confirmed */ /* if the Bearer Capability 1 IE is not present */ if (~mncc_in->fields & MNCC_F_BEARER_CAP) { /* ... include our own Bearer Capability, assuming a speech call */ - mncc_set_bearer(mncc_out, set, -1); + mncc_set_bcap_speech(mncc_out, set, -1); return 0; } @@ -289,12 +380,15 @@ static int mncc_handle_bcap(struct gsm_mncc *mncc_out, /* CC Call Confirmed */ * or if given codec is unimplemented */ if (speech_ver < 0) - mncc_set_bearer(mncc_out, set, -1); + mncc_set_bcap_speech(mncc_out, set, -1); else if (bcap->speech_ver[1] >= 0 || speech_ver != 0) - mncc_set_bearer(mncc_out, set, speech_ver); + mncc_set_bcap_speech(mncc_out, set, speech_ver); break; } case GSM48_BCAP_ITCAP_UNR_DIG_INF: + if (mncc_handle_bcap_data(bcap, set) != 0) + return -ENOTSUP; + break; default: LOGP(DMNCC, LOGL_ERROR, "%s(): Information transfer capability 0x%02x is not supported\n", @@ -596,7 +690,7 @@ int mncc_recv_internal(struct osmocom_ms *ms, int msg_type, void *arg) return 0; } -int mncc_call(struct osmocom_ms *ms, const char *number) +int mncc_call(struct osmocom_ms *ms, const char *number, bool data) { struct gsm_call *call; struct gsm_mncc setup; @@ -627,7 +721,8 @@ int mncc_call(struct osmocom_ms *ms, const char *number) /* emergency */ setup.emergency = 1; } else { - LOGP(DMNCC, LOGL_INFO, "Make call to %s\n", number); + LOGP(DMNCC, LOGL_INFO, "Make %s call to %s\n", + data ? "data" : "voice", number); /* called number */ setup.fields |= MNCC_F_CALLED; if (number[0] == '+') { @@ -640,7 +735,10 @@ int mncc_call(struct osmocom_ms *ms, const char *number) OSMO_STRLCPY_ARRAY(setup.called.number, number); /* bearer capability (mandatory) */ - mncc_set_bearer(&setup, &ms->settings, -1); + if (data) + mncc_set_bcap_data(&setup, &ms->settings); + else + mncc_set_bcap_speech(&setup, &ms->settings, -1); /* CLIR */ if (ms->settings.clir) diff --git a/src/host/layer23/src/mobile/vty_interface.c b/src/host/layer23/src/mobile/vty_interface.c index 17660712..d128f2df 100644 --- a/src/host/layer23/src/mobile/vty_interface.c +++ b/src/host/layer23/src/mobile/vty_interface.c @@ -494,10 +494,12 @@ DEFUN(network_select, network_select_cmd, "Name of MS (see \"show ms\")\n" DEFUN(call_num, call_num_cmd, - CALL_CMD " NUMBER", + CALL_CMD " NUMBER [(voice|data)]", CALL_CMD_DESC "Phone number to call " - "(Use digits '0123456789*#abc', and '+' to dial international)\n") + "(Use digits '0123456789*#abc', and '+' to dial international)\n" + "Initiate a regular voice call (default)\n" + "Initiate a CSD (Circuit Switched Data) call\n") { struct osmocom_ms *ms; struct gsm_settings *set; @@ -527,7 +529,11 @@ DEFUN(call_num, call_num_cmd, if (vty_check_number(vty, number)) return CMD_WARNING; - mncc_call(ms, number); + + if (argc > 2 && !strcmp(argv[2], "data")) /* data call: explicit selection */ + mncc_call(ms, number, true); + else /* voice call: explicit selection or implicit default */ + mncc_call(ms, number, false); return CMD_SUCCESS; } @@ -557,7 +563,7 @@ DEFUN(call, call_cmd, number = argv[1]; if (!strcmp(number, "emergency")) - mncc_call(ms, number); + mncc_call(ms, number, false); else if (!strcmp(number, "answer")) mncc_answer(ms); else if (!strcmp(number, "hangup")) @@ -1311,6 +1317,12 @@ static void config_write_ms(struct vty *vty, struct osmocom_ms *ms) SUP_WRITE(full_v3, "full-speech-v3"); SUP_WRITE(half_v1, "half-speech-v1"); SUP_WRITE(half_v3, "half-speech-v3"); + SUP_WRITE(csd_tch_f144, "full-data-14400"); + SUP_WRITE(csd_tch_f96, "full-data-9600"); + SUP_WRITE(csd_tch_f48, "full-data-4800"); + SUP_WRITE(csd_tch_h48, "half-data-4800"); + SUP_WRITE(csd_tch_f24, "full-data-2400"); + SUP_WRITE(csd_tch_h24, "half-data-2400"); if (!l23_vty_hide_default || sup->min_rxlev_dbm != set->min_rxlev_dbm) vty_out(vty, " min-rxlev %d%s", set->min_rxlev_dbm, VTY_NEWLINE); @@ -2248,6 +2260,13 @@ SUP_EN_DI(full_v3, "full-speech-v3", "Full rate speech V3 (AMR)", 0); SUP_EN_DI(half_v1, "half-speech-v1", "Half rate speech V1", 0); SUP_EN_DI(half_v3, "half-speech-v3", "Half rate speech V3 (AMR)", 0); +SUP_EN_DI(csd_tch_f144, "full-data-14400", "CSD TCH/F14.4", 0); +SUP_EN_DI(csd_tch_f96, "full-data-9600", "CSD TCH/F9.6", 0); +SUP_EN_DI(csd_tch_f48, "full-data-4800", "CSD TCH/F4.8", 0); +SUP_EN_DI(csd_tch_h48, "half-data-4800", "CSD TCH/H4.8", 0); +SUP_EN_DI(csd_tch_f24, "full-data-2400", "CSD TCH/F2.4", 0); +SUP_EN_DI(csd_tch_h24, "half-data-2400", "CSD TCH/H2.4", 0); + DEFUN(cfg_ms_sup_min_rxlev, cfg_ms_sup_min_rxlev_cmd, "min-rxlev <-110--47>", "Set the minimum receive level to select a cell\n" "Minimum receive level from -110 dBm to -47 dBm") @@ -2638,6 +2657,18 @@ int ms_vty_init(void) install_element(SUPPORT_NODE, &cfg_ms_sup_di_half_v1_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_en_half_v3_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_di_half_v3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_csd_tch_f144_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_csd_tch_f144_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_csd_tch_f96_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_csd_tch_f96_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_csd_tch_f48_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_csd_tch_f48_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_csd_tch_h48_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_csd_tch_h48_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_csd_tch_f24_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_csd_tch_f24_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_en_csd_tch_h24_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_di_csd_tch_h24_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_min_rxlev_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_dsc_max_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_skip_max_per_band_cmd); -- cgit v1.2.3