diff options
Diffstat (limited to 'src/host/trxcon/src/sched_prim.c')
-rw-r--r-- | src/host/trxcon/src/sched_prim.c | 410 |
1 files changed, 410 insertions, 0 deletions
diff --git a/src/host/trxcon/src/sched_prim.c b/src/host/trxcon/src/sched_prim.c new file mode 100644 index 00000000..67be75e5 --- /dev/null +++ b/src/host/trxcon/src/sched_prim.c @@ -0,0 +1,410 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: primitive management + * + * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@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 <errno.h> +#include <stdlib.h> +#include <string.h> +#include <talloc.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/prim.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/linuxlist.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> + +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> + +#define L1SCHED_PRIM_HEADROOM 64 +#define L1SCHED_PRIM_TAILROOM 512 + +osmo_static_assert(sizeof(struct l1sched_prim) <= L1SCHED_PRIM_HEADROOM, l1sched_prim_size); + +const struct value_string l1sched_prim_type_names[] = { + { L1SCHED_PRIM_T_DATA, "DATA" }, + { L1SCHED_PRIM_T_RACH, "RACH" }, + { L1SCHED_PRIM_T_SCH, "SCH" }, + { L1SCHED_PRIM_T_PCHAN_COMB, "PCHAN_COMB" }, + { 0, NULL }, +}; + +void l1sched_prim_init(struct msgb *msg, + enum l1sched_prim_type type, + enum osmo_prim_operation op) +{ + struct l1sched_prim *prim; + + msg->l2h = msg->data; + msg->l1h = msgb_push(msg, sizeof(*prim)); + + prim = l1sched_prim_from_msgb(msg); + osmo_prim_init(&prim->oph, 0, type, op, msg); +} + +struct msgb *l1sched_prim_alloc(enum l1sched_prim_type type, + enum osmo_prim_operation op) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(L1SCHED_PRIM_HEADROOM + L1SCHED_PRIM_TAILROOM, + L1SCHED_PRIM_HEADROOM, "l1sched_prim"); + if (msg == NULL) + return NULL; + + l1sched_prim_init(msg, type, op); + + return msg; +} + +/** + * Composes a new primitive from cached RR Measurement Report. + * + * @param lchan lchan to assign a primitive + * @return SACCH primitive to be transmitted + */ +static struct msgb *prim_compose_mr(struct l1sched_lchan_state *lchan) +{ + struct l1sched_prim *prim; + struct msgb *msg; + bool cached; + + /* Allocate a new primitive */ + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->data_req = (struct l1sched_prim_chdr) { + .chan_nr = l1sched_lchan_desc[lchan->type].chan_nr | lchan->ts->index, + .link_id = L1SCHED_CH_LID_SACCH, + }; + + /* Check if the MR cache is populated (verify LAPDm header) */ + cached = (lchan->sacch.mr_cache[2] != 0x00 + && lchan->sacch.mr_cache[3] != 0x00 + && lchan->sacch.mr_cache[4] != 0x00); + if (!cached) { + memcpy(&lchan->sacch.mr_cache[0], + &lchan->ts->sched->sacch_cache[0], + sizeof(lchan->sacch.mr_cache)); + } + + /* Compose a new Measurement Report primitive */ + memcpy(msgb_put(msg, GSM_MACBLOCK_LEN), + &lchan->sacch.mr_cache[0], + GSM_MACBLOCK_LEN); + + /* Inform about the cache usage count */ + if (++lchan->sacch.mr_cache_usage > 5) { + LOGP_LCHAND(lchan, LOGL_NOTICE, + "SACCH MR cache usage count=%u > 5 " + "=> ancient measurements, please fix!\n", + lchan->sacch.mr_cache_usage); + } + + LOGP_LCHAND(lchan, LOGL_NOTICE, "Using cached Measurement Report\n"); + + return msg; +} + +/** + * Dequeues a SACCH primitive from transmit queue, if present. + * Otherwise dequeues a cached Measurement Report (the last + * received one). Finally, if the cache is empty, a "dummy" + * measurement report is used. + * + * According to 3GPP TS 04.08, section 3.4.1, SACCH channel + * accompanies either a traffic or a signaling channel. It + * has the particularity that continuous transmission must + * occur in both directions, so on the Uplink direction + * measurement result messages are sent at each possible + * occasion when nothing else has to be sent. The LAPDm + * fill frames (0x01, 0x03, 0x01, 0x2b, ...) are not + * applicable on SACCH channels! + * + * Unfortunately, 3GPP TS 04.08 doesn't clearly state + * which "else messages" besides Measurement Reports + * can be send by the MS on SACCH channels. However, + * in sub-clause 3.4.1 it's stated that the interval + * between two successive measurement result messages + * shall not exceed one L2 frame. + * + * @param lchan lchan to assign a primitive + * @return SACCH primitive to be transmitted + */ +struct msgb *l1sched_lchan_prim_dequeue_sacch(struct l1sched_lchan_state *lchan) +{ + struct msgb *msg_nmr = NULL; + struct msgb *msg_mr = NULL; + struct msgb *msg; + bool mr_now; + + /* Shall we transmit MR now? */ + mr_now = !lchan->sacch.mr_tx_last; + +#define PRIM_MSGB_IS_MR(msg) \ + (l1sched_prim_data_from_msgb(msg)[5] == GSM48_PDISC_RR && \ + l1sched_prim_data_from_msgb(msg)[6] == GSM48_MT_RR_MEAS_REP) + + /* Iterate over all primitives in the queue */ + llist_for_each_entry(msg, &lchan->tx_prims, list) { + /* Look for a Measurement Report */ + if (!msg_mr && PRIM_MSGB_IS_MR(msg)) + msg_mr = msg; + + /* Look for anything else */ + if (!msg_nmr && !PRIM_MSGB_IS_MR(msg)) + msg_nmr = msg; + + /* Should we look further? */ + if (mr_now && msg_mr) + break; /* MR was found */ + else if (!mr_now && msg_nmr) + break; /* something else was found */ + } + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "SACCH MR selection: mr_tx_last=%d msg_mr=%p msg_nmr=%p\n", + lchan->sacch.mr_tx_last, msg_mr, msg_nmr); + + /* Prioritize non-MR prim if possible */ + if (mr_now && msg_mr) + msg = msg_mr; + else if (!mr_now && msg_nmr) + msg = msg_nmr; + else if (!mr_now && msg_mr) + msg = msg_mr; + else /* Nothing was found */ + msg = NULL; + + /* Have we found what we were looking for? */ + if (msg) /* Dequeue if so */ + llist_del(&msg->list); + else /* Otherwise compose a new MR */ + msg = prim_compose_mr(lchan); + + /* Update the cached report */ + if (msg == msg_mr) { + memcpy(lchan->sacch.mr_cache, msgb_l2(msg), GSM_MACBLOCK_LEN); + lchan->sacch.mr_cache_usage = 0; + + LOGP_LCHAND(lchan, LOGL_DEBUG, "SACCH MR cache has been updated\n"); + } + + /* Update the MR transmission state */ + lchan->sacch.mr_tx_last = PRIM_MSGB_IS_MR(msg); + + LOGP_LCHAND(lchan, LOGL_DEBUG, "SACCH decision: %s\n", + PRIM_MSGB_IS_MR(msg) ? "Measurement Report" : "data frame"); + + return msg; +} + +/** + * Dequeues either a FACCH, or a speech TCH primitive + * of a given channel type (Lm or Bm). + * + * @param lchan logical channel state + * @param facch FACCH (true) or speech (false) prim? + * @return either a FACCH, or a TCH primitive if found, + * otherwise NULL + */ +struct msgb *l1sched_lchan_prim_dequeue_tch(struct l1sched_lchan_state *lchan, bool facch) +{ + struct msgb *msg; + + /** + * There is no need to use the 'safe' list iteration here + * as an item removal is immediately followed by return. + */ + llist_for_each_entry(msg, &lchan->tx_prims, list) { + bool is_facch = msgb_l2len(msg) == GSM_MACBLOCK_LEN; + if (is_facch != facch) + continue; + + llist_del(&msg->list); + return msg; + } + + return NULL; +} + +/** + * Allocate a DATA.req with dummy LAPDm func=UI frame for the given logical channel. + * To be used when no suitable DATA.req is present in the Tx queue. + * + * @param lchan lchan to allocate a dummy primitive for + * @return an msgb with DATA.req primitive, or NULL + */ +struct msgb *l1sched_lchan_prim_dummy_lapdm(const struct l1sched_lchan_state *lchan) +{ + struct l1sched_prim *prim; + struct msgb *msg; + uint8_t *ptr; + + /* LAPDm func=UI is not applicable for SACCH */ + OSMO_ASSERT(!L1SCHED_CHAN_IS_SACCH(lchan->type)); + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->data_req = (struct l1sched_prim_chdr) { + .chan_nr = l1sched_lchan_desc[lchan->type].chan_nr | lchan->ts->index, + .link_id = l1sched_lchan_desc[lchan->type].link_id, + }; + + ptr = msgb_put(msg, GSM_MACBLOCK_LEN); + + /** + * TS 144.006, section 8.4.2.3 "Fill frames" + * A fill frame is a UI command frame for SAPI 0, P=0 + * and with an information field of 0 octet length. + */ + *(ptr++) = 0x01; + *(ptr++) = 0x03; + *(ptr++) = 0x01; + + /** + * TS 144.006, section 5.2 "Frame delimitation and fill bits" + * Except for the first octet containing fill bits which shall + * be set to the binary value "00101011", each fill bit should + * be set to a random value when sent by the network. + */ + *(ptr++) = 0x2b; + while (ptr < msg->tail) + *(ptr++) = (uint8_t)rand(); + + return msg; +} + +int l1sched_lchan_emit_data_ind(struct l1sched_lchan_state *lchan, + const uint8_t *data, size_t data_len, + int n_errors, int n_bits_total, + bool traffic) +{ + const struct l1sched_meas_set *meas = &lchan->meas_avg; + const struct l1sched_lchan_desc *lchan_desc; + struct l1sched_prim *prim; + struct msgb *msg; + + lchan_desc = &l1sched_lchan_desc[lchan->type]; + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_INDICATION); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->data_ind = (struct l1sched_prim_data_ind) { + .chdr = { + .frame_nr = meas->fn, + .chan_nr = lchan_desc->chan_nr | lchan->ts->index, + .link_id = lchan_desc->link_id, + .traffic = traffic, + }, + .toa256 = meas->toa256, + .rssi = meas->rssi, + .n_errors = n_errors, + .n_bits_total = n_bits_total, + }; + + if (data_len > 0) + memcpy(msgb_put(msg, data_len), data, data_len); + + return l1sched_prim_to_user(lchan->ts->sched, msg); +} + +int l1sched_lchan_emit_data_cnf(struct l1sched_lchan_state *lchan, + struct msgb *msg, uint32_t fn) +{ + struct l1sched_prim *prim; + + OSMO_ASSERT(msg != NULL); + + /* convert from DATA.req to DATA.cnf */ + prim = l1sched_prim_from_msgb(msg); + prim->oph.operation = PRIM_OP_CONFIRM; + + switch (prim->oph.primitive) { + case L1SCHED_PRIM_T_DATA: + prim->data_cnf.frame_nr = fn; + break; + case L1SCHED_PRIM_T_RACH: + prim->rach_cnf.chdr.frame_nr = fn; + break; + default: + /* shall not happen */ + OSMO_ASSERT(0); + } + + return l1sched_prim_to_user(lchan->ts->sched, msg); +} + +static int prim_enqeue(struct l1sched_state *sched, struct msgb *msg, + const struct l1sched_prim_chdr *chdr) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + struct l1sched_lchan_state *lchan; + + lchan = l1sched_find_lchan_by_chan_nr(sched, chdr->chan_nr, chdr->link_id); + if (OSMO_UNLIKELY(lchan == NULL || !lchan->active)) { + LOGP_SCHEDD(sched, LOGL_ERROR, + "No [active] lchan for primitive " L1SCHED_PRIM_STR_FMT " " + "(fn=%u, chan_nr=0x%02x, link_id=0x%02x, len=%u): %s\n", + L1SCHED_PRIM_STR_ARGS(prim), + chdr->frame_nr, chdr->chan_nr, chdr->link_id, + msgb_l2len(msg), msgb_hexdump_l2(msg)); + msgb_free(msg); + return -ENODEV; + } + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "Enqueue primitive " L1SCHED_PRIM_STR_FMT " " + "(fn=%u, chan_nr=0x%02x, link_id=0x%02x, len=%u): %s\n", + L1SCHED_PRIM_STR_ARGS(prim), + chdr->frame_nr, chdr->chan_nr, chdr->link_id, + msgb_l2len(msg), msgb_hexdump_l2(msg)); + + msgb_enqueue(&lchan->tx_prims, msg); + return 0; +} + +int l1sched_prim_from_user(struct l1sched_state *sched, struct msgb *msg) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + + LOGP_SCHEDD(sched, LOGL_DEBUG, + "%s(): Rx " L1SCHED_PRIM_STR_FMT "\n", + __func__, L1SCHED_PRIM_STR_ARGS(prim)); + + switch (OSMO_PRIM_HDR(&prim->oph)) { + case OSMO_PRIM(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST): + return prim_enqeue(sched, msg, &prim->data_req); + case OSMO_PRIM(L1SCHED_PRIM_T_RACH, PRIM_OP_REQUEST): + return prim_enqeue(sched, msg, &prim->rach_req.chdr); + default: + LOGP_SCHEDD(sched, LOGL_ERROR, + "%s(): Unhandled primitive " L1SCHED_PRIM_STR_FMT "\n", + __func__, L1SCHED_PRIM_STR_ARGS(prim)); + msgb_free(msg); + return -ENOTSUP; + } +} |