fixeria has submitted this change. ( https://gerrit.osmocom.org/c/osmocom-bb/+/35582?usp=email )
Change subject: mobile: integrate V.110 TA & soft-UART from libosmocore ...................................................................... mobile: integrate V.110 TA & soft-UART from libosmocore Change-Id: I7ac9c0e5010730fa4d8bc7a7a3c7ff85e11731c0 Depends: libosmocore.git I6d2f8e250df31c233a2741163113dc07515409ae Depends: libosmocore.git I5716bd6fd0201ee7a7a29e72f775972cd374082f Depends: libosmocore.git I2ca95963fd5852ddb89bdd35b86b31489127fe84 Related: OS#4396 --- M src/host/layer23/configure.ac M src/host/layer23/include/osmocom/bb/mobile/tch.h M src/host/layer23/src/mobile/Makefile.am M src/host/layer23/src/mobile/tch.c A src/host/layer23/src/mobile/tch_data.c 5 files changed, 511 insertions(+), 13 deletions(-) Approvals: Jenkins Builder: Verified pespin: Looks good to me, but someone else must approve fixeria: Looks good to me, approved diff --git a/src/host/layer23/configure.ac b/src/host/layer23/configure.ac index b4eb860..7fb8bf1 100644 --- a/src/host/layer23/configure.ac +++ b/src/host/layer23/configure.ac @@ -45,6 +45,7 @@ PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.5.0) PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.10.0) PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm) +PKG_CHECK_MODULES(LIBOSMOISDN, libosmoisdn) PKG_CHECK_MODULES(LIBOSMOCODEC, libosmocodec) PKG_CHECK_MODULES(LIBOSMOGPRSRLCMAC, libosmo-gprs-rlcmac) PKG_CHECK_MODULES(LIBOSMOGPRSLLC, libosmo-gprs-llc) diff --git a/src/host/layer23/include/osmocom/bb/mobile/tch.h b/src/host/layer23/include/osmocom/bb/mobile/tch.h index b99b834..8672860 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/tch.h +++ b/src/host/layer23/include/osmocom/bb/mobile/tch.h @@ -13,6 +13,10 @@ } voice; struct tch_data_state { enum tch_data_io_handler handler; + struct osmo_v110_ta *v110_ta; + struct osmo_soft_uart *suart; + unsigned int num_tx; + uint8_t e1e2e3[3]; } data; }; }; diff --git a/src/host/layer23/src/mobile/Makefile.am b/src/host/layer23/src/mobile/Makefile.am index 0639916..b4e45e2 100644 --- a/src/host/layer23/src/mobile/Makefile.am +++ b/src/host/layer23/src/mobile/Makefile.am @@ -8,6 +8,7 @@ $(LIBOSMOCORE_CFLAGS) \ $(LIBOSMOVTY_CFLAGS) \ $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOISDN_CFLAGS) \ $(LIBOSMOGPRSRLCMAC_CFLAGS) \ $(LIBOSMOGPRSLLC_CFLAGS) \ $(LIBOSMOGPRSSNDCP_CFLAGS) \ @@ -31,6 +32,7 @@ mncc_sock.c \ primitives.c \ tch.c \ + tch_data.c \ tch_voice.c \ transaction.c \ vty_interface.c \ @@ -45,6 +47,7 @@ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOVTY_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOISDN_LIBS) \ $(LIBOSMOGPRSRLCMAC_LIBS) \ $(LIBOSMOGPRSLLC_LIBS) \ $(LIBOSMOGPRSSNDCP_LIBS) \ diff --git a/src/host/layer23/src/mobile/tch.c b/src/host/layer23/src/mobile/tch.c index ad5a724..70c9163 100644 --- a/src/host/layer23/src/mobile/tch.c +++ b/src/host/layer23/src/mobile/tch.c @@ -37,16 +37,21 @@ int tch_voice_state_init(struct gsm_trans *trans, struct tch_voice_state *state); +int tch_data_state_init(struct gsm_trans *trans, + struct tch_data_state *state); + void tch_voice_state_free(struct tch_voice_state *state); +void tch_data_state_free(struct tch_data_state *state); int tch_voice_recv(struct osmocom_ms *ms, struct msgb *msg); +int tch_data_recv(struct osmocom_ms *ms, struct msgb *msg); int tch_voice_serve_ms(struct osmocom_ms *ms); /* Receive a Downlink traffic (voice/data) frame from the lower layers */ static int tch_recv_cb(struct osmocom_ms *ms, struct msgb *msg) { struct tch_state *state = ms->tch_state; - int rc = 0; + int rc; if (state == NULL) { msgb_free(msg); @@ -55,9 +60,8 @@ if (state->is_voice) rc = tch_voice_recv(ms, msg); - else /* TODO: tch_recv_data() */ - msgb_free(msg); - + else + rc = tch_data_recv(ms, msg); return rc; } @@ -126,6 +130,7 @@ state = talloc_zero(ms, struct tch_state); OSMO_ASSERT(state != NULL); + ms->tch_state = state; ch_mode = ms->rrlayer.cd_now.mode; switch (ch_mode) { @@ -141,24 +146,20 @@ case GSM48_CMODE_DATA_12k0: case GSM48_CMODE_DATA_6k0: case GSM48_CMODE_DATA_3k6: -#if 0 state->is_voice = false; state->data.handler = ms->settings.tch_data.io_handler; - /* TODO: tch_data_state_init() */ if (tch_data_state_init(trans, &state->data) != 0) goto exit_free; break; -#endif case GSM48_CMODE_SIGN: default: LOGP(DL1C, LOGL_ERROR, "Unhandled channel mode %s\n", get_value_string(gsm48_chan_mode_names, ch_mode)); exit_free: talloc_free(state); + ms->tch_state = NULL; return; } - - ms->tch_state = state; } static void tch_trans_free_cb(struct gsm_trans *trans) @@ -170,10 +171,8 @@ return; if (state->is_voice) tch_voice_state_free(&state->voice); -#if 0 - else /* TODO: tch_state_free_data() */ - tch_state_free_data(&state->data); -#endif + else + tch_data_state_free(&state->data); talloc_free(state); ms->tch_state = NULL; diff --git a/src/host/layer23/src/mobile/tch_data.c b/src/host/layer23/src/mobile/tch_data.c new file mode 100644 index 0000000..9f46641 --- /dev/null +++ b/src/host/layer23/src/mobile/tch_data.c @@ -0,0 +1,478 @@ +/* + * (C) 2023-2024 by sysmocom - s.f.m.c. GmbH <i...@sysmocom.de> + * Author: Vadim Yanitskiy <vyanits...@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + */ + +#include <stdint.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/soft_uart.h> + +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/gsm/gsm44021.h> + +#include <osmocom/isdn/v110.h> +#include <osmocom/isdn/v110_ta.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/transaction.h> +#include <osmocom/bb/mobile/tch.h> + +struct csd_v110_frame_desc { + uint16_t num_blocks; + uint16_t num_bits; +}; + +struct csd_v110_lchan_desc { + struct csd_v110_frame_desc fr; + struct csd_v110_frame_desc hr; +}; + +/* key is enum gsm48_chan_mode, so assuming a value in range 0..255 */ +const struct csd_v110_lchan_desc csd_v110_lchan_desc[256] = { +#if 0 + [GSM48_CMODE_DATA_14k5] = { + /* TCH/F14.4: 290 bits every 20 ms (14.5 kbit/s) */ + .fr = { .num_blocks = 1, .num_bits = 290 }, + }, +#endif + [GSM48_CMODE_DATA_12k0] = { + /* TCH/F9.6: 4 * 60 bits every 20 ms (12.0 kbit/s) */ + .fr = { .num_blocks = 4, .num_bits = 60 }, + }, + [GSM48_CMODE_DATA_6k0] = { + /* TCH/F4.8: 2 * 60 bits every 20 ms (6.0 kbit/s) */ + .fr = { .num_blocks = 2, .num_bits = 60 }, + /* TCH/H4.8: 4 * 60 bits every 40 ms (6.0 kbit/s) */ + .hr = { .num_blocks = 4, .num_bits = 60 }, + }, + [GSM48_CMODE_DATA_3k6] = { + /* TCH/F2.4: 2 * 36 bits every 20 ms (3.6 kbit/s) */ + .fr = { .num_blocks = 2, .num_bits = 36 }, + /* TCH/H2.4: 4 * 36 bits every 40 ms (3.6 kbit/s) */ + .hr = { .num_blocks = 4, .num_bits = 36 }, + }, +}; + +static void tch_soft_uart_rx_cb(void *priv, struct msgb *msg, unsigned int flags) +{ + LOGP(DL1C, LOGL_FATAL, "%s(): [flags=0x%08x] %s\n", + __func__, flags, msgb_hexdump(msg)); + msgb_free(msg); +} + +static void tch_soft_uart_tx_cb(void *priv, struct msgb *msg) +{ + const char *data = "TEST\r\n"; + size_t n_bytes; + + n_bytes = OSMO_MIN(msg->data_len, strlen(data)); + if (n_bytes > 0) + memcpy(msgb_put(msg, n_bytes), (void *)data, n_bytes); +} + +struct osmo_soft_uart *tch_soft_uart_alloc(struct osmocom_ms *ms, + const struct gsm_mncc_bearer_cap *bcap) +{ + struct osmo_soft_uart *suart; + + struct osmo_soft_uart_cfg cfg = { + .num_data_bits = bcap->data.nr_data_bits, + .num_stop_bits = bcap->data.nr_stop_bits, + /* .parity_mode is set below */ + .rx_buf_size = 1024, /* TODO: align with the current TCH mode */ + .rx_timeout_ms = 100, /* TODO: align with TCH framing interval */ + .priv = (void *)&ms->tch_state->data, + .rx_cb = &tch_soft_uart_rx_cb, + .tx_cb = &tch_soft_uart_tx_cb, + }; + + switch (bcap->data.parity) { + case GSM48_BCAP_PAR_ODD: + cfg.parity_mode = OSMO_SUART_PARITY_ODD; + break; + case GSM48_BCAP_PAR_EVEN: + cfg.parity_mode = OSMO_SUART_PARITY_EVEN; + break; + case GSM48_BCAP_PAR_ZERO: + cfg.parity_mode = OSMO_SUART_PARITY_SPACE; + break; + case GSM48_BCAP_PAR_ONE: + cfg.parity_mode = OSMO_SUART_PARITY_MARK; + break; + case GSM48_BCAP_PAR_NONE: + default: + cfg.parity_mode = OSMO_SUART_PARITY_NONE; + break; + } + + suart = osmo_soft_uart_alloc(ms, "csd_soft_uart", &cfg); + if (suart == NULL) + return NULL; + + osmo_soft_uart_set_rx(suart, true); + osmo_soft_uart_set_tx(suart, true); + + return suart; +} + +/*************************************************************************************/ + +static void tch_v110_ta_rx_cb(void *priv, const ubit_t *buf, size_t buf_size) +{ + /* TODO: send to the configured I/O handler */ +} + +static void tch_v110_ta_tx_cb(void *priv, ubit_t *buf, size_t buf_size) +{ + /* TODO: send to the configured I/O handler */ +} + +static void tch_v110_ta_async_rx_cb(void *priv, const ubit_t *buf, size_t buf_size) +{ + const struct tch_data_state *state = (struct tch_data_state *)priv; + + osmo_soft_uart_rx_ubits(state->suart, buf, buf_size); +} + +static void tch_v110_ta_async_tx_cb(void *priv, ubit_t *buf, size_t buf_size) +{ + const struct tch_data_state *state = (struct tch_data_state *)priv; + + osmo_soft_uart_tx_ubits(state->suart, buf, buf_size); +} + +static void tch_v110_ta_status_update_cb(void *priv, unsigned int status) +{ + LOGP(DL1C, LOGL_DEBUG, "%s(): [status=0x%08x]\n", __func__, status); + + /* TODO: update status lines of the soft-UART (if state.suart != NULL) */ +} + +struct osmo_v110_ta *tch_v110_ta_alloc(struct osmocom_ms *ms, + const struct gsm_mncc_bearer_cap *bcap) +{ + struct tch_data_state *state = &ms->tch_state->data; + + struct osmo_v110_ta_cfg cfg = { + /* .rate is set below */ + .priv = (void *)state, + .rx_cb = &tch_v110_ta_rx_cb, + .tx_cb = &tch_v110_ta_tx_cb, + .status_update_cb = &tch_v110_ta_status_update_cb, + }; + + if (bcap->data.async) { + OSMO_ASSERT(state->suart != NULL); + cfg.rx_cb = &tch_v110_ta_async_rx_cb; + cfg.tx_cb = &tch_v110_ta_async_tx_cb; + } + +#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_1200): + cfg.rate = OSMO_V110_SYNC_RA1_1200; + break; + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_2400): + cfg.rate = OSMO_V110_SYNC_RA1_2400; + break; + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_4800): + cfg.rate = OSMO_V110_SYNC_RA1_4800; + break; + case BCAP_RATE(GSM48_BCAP_IR_16k, GSM48_BCAP_UR_9600): + cfg.rate = OSMO_V110_SYNC_RA1_9600; + break; + /* TODO: according to 3GPP TS 44.021, section 4.1, the 300 bit/s user data + * signalling rate shall be adapted to a synchronous 600 bit/s stream. */ + case BCAP_RATE(GSM48_BCAP_IR_8k, GSM48_BCAP_UR_300): + default: + LOGP(DCC, 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 NULL; + } + +#undef BCAP_RATE + + osmo_v110_e1e2e3_set(state->e1e2e3, cfg.rate); + + return osmo_v110_ta_alloc(ms, "csd_v110_ta", &cfg); +} + +/*************************************************************************************/ + +static void swap_words(uint8_t *data, size_t data_len) +{ + /* swap bytes in words */ + while (data_len >= 2) { + uint8_t tmp = data[0]; + data[0] = data[1]; + data[1] = tmp; + data_len -= 2; + data += 2; + } +} + +static int tch_csd_rx_from_l1(struct osmocom_ms *ms, struct msgb *msg) +{ + const struct tch_data_state *state = &ms->tch_state->data; + const struct gsm48_rr_cd *cd = &ms->rrlayer.cd_now; + const struct csd_v110_frame_desc *desc; + ubit_t data[4 * 60]; + + if (msgb_l3len(msg) < 30) + return -EINVAL; + + if ((cd->chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_Bm_ACCHs) + desc = &csd_v110_lchan_desc[cd->mode].fr; + else /* RSL_CHAN_Lm_ACCHs */ + desc = &csd_v110_lchan_desc[cd->mode].hr; + if (OSMO_UNLIKELY(desc->num_blocks == 0)) + return -ENOTSUP; + + switch (ms->settings.tch_data.io_format) { + case TCH_DATA_IOF_OSMO: + break; + case TCH_DATA_IOF_TI: + /* the layer1 firmware emits frames with swapped words (LE ordering) */ + swap_words(msgb_l3(msg), msgb_l3len(msg)); + break; + } + + /* unpack packed bits (MSB goes first) */ + osmo_pbit2ubit_ext(data, 0, msgb_l3(msg), 0, sizeof(data), 1); + + for (unsigned int i = 0; i < desc->num_blocks; i++) { + struct osmo_v110_decoded_frame df; + + if (desc->num_bits == 60) + osmo_csd_12k_6k_decode_frame(&df, &data[i * 60], 60); + else /* desc->num_bits == 36 */ + osmo_csd_3k6_decode_frame(&df, &data[i * 36], 36); + + /* E1/E2/E3 is out-of-band knowledge in GSM/CSD */ + memcpy(df.e_bits, state->e1e2e3, sizeof(state->e1e2e3)); + + osmo_v110_ta_frame_in(state->v110_ta, &df); + } + + if (state->suart != NULL) + osmo_soft_uart_flush_rx(state->suart); + + return 0; +} + +static int tch_csd_tx_to_l1(struct osmocom_ms *ms) +{ + struct tch_data_state *state = &ms->tch_state->data; + const struct gsm48_rr_cd *cd = &ms->rrlayer.cd_now; + const struct csd_v110_frame_desc *desc; + ubit_t data[60 * 4]; + struct msgb *nmsg; + + if ((cd->chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_Bm_ACCHs) + desc = &csd_v110_lchan_desc[cd->mode].fr; + else /* RSL_CHAN_Lm_ACCHs */ + desc = &csd_v110_lchan_desc[cd->mode].hr; + if (OSMO_UNLIKELY(desc->num_blocks == 0)) + return -ENOTSUP; + + for (unsigned int i = 0; i < desc->num_blocks; i++) { + struct osmo_v110_decoded_frame df; + + if (osmo_v110_ta_frame_out(state->v110_ta, &df) != 0) + memset(&df, 0x01, sizeof(df)); + + /* If E1/E2/E3 bits indicate a meaningful user data rate (see Table 5/V.110), + * set E7 to binary 0 in every 4-th frame (as per 3GPP TS 44.021, subclause 10.2.1). + * ITU-T V.110 requires this only for 600 bps, but 3GPP TS 44.021 clearly states + * that "such a multiframe structure exists for all user data rates". */ + if ((df.e_bits[0] + df.e_bits[1] + df.e_bits[2]) == 2) + df.e_bits[6] = (state->num_tx != 0); + state->num_tx = (state->num_tx + 1) & 0x03; + + if (desc->num_bits == 60) + osmo_csd_12k_6k_encode_frame(&data[i * 60], 60, &df); + else /* desc->num_bits == 36 */ + osmo_csd_3k6_encode_frame(&data[i * 36], 36, &df); + } + + nmsg = msgb_alloc_headroom(33 + 64, 64, __func__); + OSMO_ASSERT(nmsg != NULL); + + nmsg->l2h = msgb_put(nmsg, 33); /* XXX: proper size */ + + /* pack unpacked bits (MSB goes first) */ + osmo_ubit2pbit_ext(msgb_l2(nmsg), 0, &data[0], 0, sizeof(data), 1); + + switch (ms->settings.tch_data.io_format) { + case TCH_DATA_IOF_OSMO: + break; + case TCH_DATA_IOF_TI: + /* the layer1 firmware expects frames with swapped words (LE ordering) */ + swap_words(msgb_l2(nmsg), msgb_l2len(nmsg)); + break; + } + + return gsm48_rr_tx_traffic(ms, nmsg); +} + +static int tch_data_check_bcap(const struct gsm_mncc_bearer_cap *bcap) +{ + if (bcap == NULL) { + LOGP(DL1C, LOGL_ERROR, + "%s(): CC transaction without BCap\n", + __func__); + return -ENODEV; + } + + if (bcap->mode != GSM48_BCAP_TMOD_CIRCUIT) { + LOGP(DCC, LOGL_ERROR, + "%s(): Transfer mode 0x%02x is not supported\n", + __func__, bcap->mode); + return -ENOTSUP; + } + if (bcap->coding != GSM48_BCAP_CODING_GSM_STD) { + LOGP(DCC, LOGL_ERROR, + "%s(): Coding standard 0x%02x is not supported\n", + __func__, bcap->coding); + return -ENOTSUP; + } + + switch (bcap->transfer) { + case GSM48_BCAP_ITCAP_UNR_DIG_INF: + case GSM48_BCAP_ITCAP_3k1_AUDIO: + case GSM48_BCAP_ITCAP_FAX_G3: + break; + default: + LOGP(DCC, LOGL_ERROR, + "%s(): Information transfer capability 0x%02x is not supported\n", + __func__, bcap->transfer); + return -ENOTSUP; + } + + if (bcap->data.rate_adaption != GSM48_BCAP_RA_V110_X30) { + LOGP(DCC, 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(DCC, LOGL_ERROR, + "%s(): Signalling access protocol (octet 5) 0x%02x is not supported\n", + __func__, bcap->data.sig_access); + return -ENOTSUP; + } + if (bcap->data.transp != GSM48_BCAP_TR_TRANSP) { + LOGP(DCC, LOGL_ERROR, + "%s(): only transparent calls are supported so far\n", + __func__); + return -ENOTSUP; + } + + return 0; +} + +/*************************************************************************************/ + +int tch_data_recv(struct osmocom_ms *ms, struct msgb *msg) +{ + struct tch_data_state *state = &ms->tch_state->data; + + switch (state->handler) { + case TCH_DATA_IOH_LOOPBACK: + /* Remove the DL info header */ + msgb_pull_to_l2(msg); + /* Send data frame back */ + return tch_send_msg(ms, msg); + case TCH_DATA_IOH_UNIX_SOCK: + tch_csd_rx_from_l1(ms, msg); + tch_csd_tx_to_l1(ms); + msgb_free(msg); + break; + case TCH_DATA_IOH_NONE: + /* Drop voice frame */ + msgb_free(msg); + break; + } + + return 0; +} + +int tch_data_state_init(struct gsm_trans *trans, + struct tch_data_state *state) +{ + struct osmocom_ms *ms = trans->ms; + const struct gsm_mncc_bearer_cap *bcap = trans->cc.bcap; + int rc; + + if ((rc = tch_data_check_bcap(bcap)) != 0) + return rc; + + switch (state->handler) { + case TCH_DATA_IOH_UNIX_SOCK: + /* TODO: open listening socket */ + break; + case TCH_DATA_IOH_LOOPBACK: + case TCH_DATA_IOH_NONE: + /* we don't need V.110 TA / soft-UART */ + return 0; + default: + break; + } + + if (bcap->data.async) { + state->suart = tch_soft_uart_alloc(ms, bcap); + if (state->suart == NULL) + goto exit_free; + } + + state->v110_ta = tch_v110_ta_alloc(ms, bcap); + if (state->v110_ta == NULL) + goto exit_free; + + return 0; + +exit_free: + if (state->suart != NULL) + osmo_soft_uart_free(state->suart); + if (state->v110_ta != NULL) + osmo_v110_ta_free(state->v110_ta); + return -1; +} + +void tch_data_state_free(struct tch_data_state *state) +{ + switch (state->handler) { + case TCH_DATA_IOH_UNIX_SOCK: + /* TODO: close listening socket */ + break; + default: + break; + } + + if (state->suart != NULL) + osmo_soft_uart_free(state->suart); + if (state->v110_ta != NULL) + osmo_v110_ta_free(state->v110_ta); +} -- To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/35582?usp=email To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: osmocom-bb Gerrit-Branch: master Gerrit-Change-Id: I7ac9c0e5010730fa4d8bc7a7a3c7ff85e11731c0 Gerrit-Change-Number: 35582 Gerrit-PatchSet: 6 Gerrit-Owner: fixeria <vyanits...@sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: fixeria <vyanits...@sysmocom.de> Gerrit-Reviewer: laforge <lafo...@osmocom.org> Gerrit-Reviewer: osmith <osm...@sysmocom.de> Gerrit-Reviewer: pespin <pes...@sysmocom.de> Gerrit-MessageType: merged