412 lines
12 KiB
C
412 lines
12 KiB
C
/*
|
|
* 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;
|
|
|
|
if (msg == NULL)
|
|
return -ENODEV;
|
|
|
|
/* 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;
|
|
}
|
|
}
|