2017-07-04 13:55:12 +00:00
|
|
|
/*
|
|
|
|
* OsmocomBB <-> SDR connection bridge
|
|
|
|
* TDMA scheduler: GSM PHY routines
|
|
|
|
*
|
2022-07-06 11:06:07 +00:00
|
|
|
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
|
|
|
|
* Contributions by sysmocom - s.f.m.c. GmbH
|
2017-07-04 13:55:12 +00:00
|
|
|
*
|
|
|
|
* 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 <error.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <talloc.h>
|
2019-01-15 10:53:02 +00:00
|
|
|
#include <stdbool.h>
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2017-12-17 22:47:28 +00:00
|
|
|
#include <osmocom/gsm/a5.h>
|
2019-05-27 15:34:02 +00:00
|
|
|
#include <osmocom/gsm/protocol/gsm_08_58.h>
|
2017-07-04 13:55:12 +00:00
|
|
|
#include <osmocom/core/bits.h>
|
|
|
|
#include <osmocom/core/msgb.h>
|
|
|
|
#include <osmocom/core/logging.h>
|
|
|
|
#include <osmocom/core/linuxlist.h>
|
|
|
|
|
2022-07-04 15:25:27 +00:00
|
|
|
#include <osmocom/bb/trxcon/l1sched.h>
|
2022-07-01 10:45:12 +00:00
|
|
|
#include <osmocom/bb/trxcon/logging.h>
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
static int l1sched_cfg_pchan_comb_req(struct l1sched_state *sched,
|
2022-07-11 22:10:32 +00:00
|
|
|
uint8_t tn, enum gsm_phys_chan_config pchan)
|
2022-07-06 11:06:07 +00:00
|
|
|
{
|
|
|
|
const struct l1sched_config_req cr = {
|
|
|
|
.type = L1SCHED_CFG_PCHAN_COMB,
|
|
|
|
.pchan_comb = {
|
|
|
|
.tn = tn,
|
|
|
|
.pchan = pchan,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
return l1sched_handle_config_req(sched, &cr);
|
|
|
|
}
|
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
static void l1sched_a5_burst_enc(struct l1sched_lchan_state *lchan,
|
|
|
|
struct l1sched_burst_req *br);
|
2021-06-14 21:14:34 +00:00
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
static void sched_frame_clck_cb(struct l1sched_state *sched)
|
2017-07-04 13:55:12 +00:00
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_burst_req br[TRX_TS_COUNT];
|
|
|
|
const struct l1sched_tdma_frame *frame;
|
|
|
|
struct l1sched_lchan_state *lchan;
|
|
|
|
l1sched_lchan_tx_func *handler;
|
2022-07-02 12:00:53 +00:00
|
|
|
enum l1sched_lchan_type chan;
|
2021-06-14 21:14:34 +00:00
|
|
|
uint8_t offset;
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_ts *ts;
|
2022-07-15 21:50:35 +00:00
|
|
|
unsigned int tn;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2021-06-14 13:26:25 +00:00
|
|
|
/* Advance TDMA frame number in order to give the transceiver
|
|
|
|
* more time to handle the burst before the actual transmission. */
|
|
|
|
const uint32_t fn = GSM_TDMA_FN_SUM(sched->fn_counter_proc,
|
|
|
|
sched->fn_counter_advance);
|
|
|
|
|
2017-07-28 08:47:41 +00:00
|
|
|
/* Iterate over timeslot list */
|
2022-07-15 21:50:35 +00:00
|
|
|
for (tn = 0; tn < ARRAY_SIZE(br); tn++) {
|
2021-06-14 21:14:34 +00:00
|
|
|
/* Initialize the buffer for this timeslot */
|
2022-07-15 21:50:35 +00:00
|
|
|
br[tn] = (struct l1sched_burst_req) {
|
2021-06-14 21:14:34 +00:00
|
|
|
.fn = fn,
|
2022-07-15 21:50:35 +00:00
|
|
|
.tn = tn,
|
2021-06-14 21:14:34 +00:00
|
|
|
.burst_len = 0, /* NOPE.ind */
|
|
|
|
};
|
|
|
|
|
2017-07-28 08:47:41 +00:00
|
|
|
/* Timeslot is not allocated */
|
2022-07-15 21:50:35 +00:00
|
|
|
ts = sched->ts[tn];
|
2017-07-28 08:47:41 +00:00
|
|
|
if (ts == NULL)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Timeslot is not configured */
|
|
|
|
if (ts->mf_layout == NULL)
|
|
|
|
continue;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2017-07-12 11:48:18 +00:00
|
|
|
/* Get frame from multiframe */
|
|
|
|
offset = fn % ts->mf_layout->period;
|
|
|
|
frame = ts->mf_layout->frames + offset;
|
|
|
|
|
|
|
|
/* Get required info from frame */
|
2022-07-15 21:50:35 +00:00
|
|
|
br[tn].bid = frame->ul_bid;
|
2017-07-12 11:48:18 +00:00
|
|
|
chan = frame->ul_chan;
|
2022-07-01 18:42:26 +00:00
|
|
|
handler = l1sched_lchan_desc[chan].tx_fn;
|
2017-07-12 11:48:18 +00:00
|
|
|
|
|
|
|
/* Omit lchans without handler */
|
|
|
|
if (!handler)
|
|
|
|
continue;
|
|
|
|
|
2017-07-31 07:27:30 +00:00
|
|
|
/* Make sure that lchan was allocated and activated */
|
2022-07-01 18:42:26 +00:00
|
|
|
lchan = l1sched_find_lchan(ts, chan);
|
2017-07-31 07:27:30 +00:00
|
|
|
if (lchan == NULL)
|
|
|
|
continue;
|
|
|
|
|
2018-03-10 21:20:57 +00:00
|
|
|
/* Omit inactive lchans */
|
|
|
|
if (!lchan->active)
|
|
|
|
continue;
|
|
|
|
|
2017-12-17 20:47:23 +00:00
|
|
|
/**
|
|
|
|
* If we aren't processing any primitive yet,
|
|
|
|
* attempt to obtain a new one from queue
|
|
|
|
*/
|
|
|
|
if (lchan->prim == NULL)
|
2022-07-01 18:42:26 +00:00
|
|
|
lchan->prim = l1sched_prim_dequeue(&ts->tx_prims, fn, lchan);
|
2017-12-17 20:47:23 +00:00
|
|
|
|
2018-03-10 21:18:06 +00:00
|
|
|
/* TODO: report TX buffers health to the higher layers */
|
|
|
|
|
|
|
|
/* If CBTX (Continuous Burst Transmission) is assumed */
|
2022-07-01 18:42:26 +00:00
|
|
|
if (l1sched_lchan_desc[chan].flags & L1SCHED_CH_FLAG_CBTX) {
|
2018-03-10 21:18:06 +00:00
|
|
|
/**
|
|
|
|
* Probably, a TX buffer is empty. Nevertheless,
|
|
|
|
* we shall continuously transmit anything on
|
|
|
|
* CBTX channels.
|
|
|
|
*/
|
|
|
|
if (lchan->prim == NULL)
|
2022-07-01 18:42:26 +00:00
|
|
|
l1sched_prim_dummy(lchan);
|
2018-03-10 21:18:06 +00:00
|
|
|
}
|
|
|
|
|
2017-12-17 20:47:23 +00:00
|
|
|
/* If there is no primitive, do nothing */
|
|
|
|
if (lchan->prim == NULL)
|
|
|
|
continue;
|
2017-07-12 11:48:18 +00:00
|
|
|
|
2019-05-29 23:19:10 +00:00
|
|
|
/* Handover RACH needs to be handled regardless of the
|
|
|
|
* current channel type and the associated handler. */
|
2022-07-01 18:42:26 +00:00
|
|
|
if (L1SCHED_PRIM_IS_RACH(lchan->prim) && lchan->prim->chan != L1SCHED_RACH)
|
|
|
|
handler = l1sched_lchan_desc[L1SCHED_RACH].tx_fn;
|
2019-05-29 23:19:10 +00:00
|
|
|
|
2017-07-12 11:48:18 +00:00
|
|
|
/* Poke lchan handler */
|
2022-07-15 21:50:35 +00:00
|
|
|
handler(lchan, &br[tn]);
|
2021-06-14 21:14:34 +00:00
|
|
|
|
|
|
|
/* Perform A5/X burst encryption if required */
|
|
|
|
if (lchan->a5.algo)
|
2022-07-15 21:50:35 +00:00
|
|
|
l1sched_a5_burst_enc(lchan, &br[tn]);
|
2017-07-12 11:48:18 +00:00
|
|
|
}
|
2021-06-14 21:14:34 +00:00
|
|
|
|
|
|
|
/* Send all bursts for this TDMA frame */
|
2022-07-15 21:50:35 +00:00
|
|
|
for (tn = 0; tn < ARRAY_SIZE(br); tn++)
|
|
|
|
l1sched_handle_burst_req(sched, &br[tn]);
|
2017-07-04 13:55:12 +00:00
|
|
|
}
|
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
struct l1sched_state *l1sched_alloc(void *ctx, uint32_t fn_advance)
|
2017-07-04 13:55:12 +00:00
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_state *sched;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
|
|
|
LOGP(DSCH, LOGL_NOTICE, "Init scheduler\n");
|
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
sched = talloc(ctx, struct l1sched_state);
|
|
|
|
if (!sched)
|
|
|
|
return NULL;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
*sched = (struct l1sched_state) {
|
|
|
|
/* .clock_timer is set up in l1sched_clck_correct() */
|
|
|
|
.clock_cb = &sched_frame_clck_cb,
|
|
|
|
.fn_counter_advance = fn_advance,
|
|
|
|
};
|
2017-11-23 13:05:00 +00:00
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
return sched;
|
2017-07-04 13:55:12 +00:00
|
|
|
}
|
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
void l1sched_free(struct l1sched_state *sched)
|
2017-07-04 13:55:12 +00:00
|
|
|
{
|
2022-07-15 21:50:35 +00:00
|
|
|
unsigned int tn;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
if (sched == NULL)
|
|
|
|
return;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
|
|
|
LOGP(DSCH, LOGL_NOTICE, "Shutdown scheduler\n");
|
|
|
|
|
|
|
|
/* Free all potentially allocated timeslots */
|
2022-07-15 21:50:35 +00:00
|
|
|
for (tn = 0; tn < ARRAY_SIZE(sched->ts); tn++)
|
|
|
|
l1sched_del_ts(sched, tn);
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
l1sched_clck_reset(sched);
|
|
|
|
talloc_free(sched);
|
2017-07-04 13:55:12 +00:00
|
|
|
}
|
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
void l1sched_reset(struct l1sched_state *sched, bool reset_clock)
|
2017-07-04 13:55:12 +00:00
|
|
|
{
|
2022-07-15 21:50:35 +00:00
|
|
|
unsigned int tn;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
if (sched == NULL)
|
|
|
|
return;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2017-07-27 10:53:09 +00:00
|
|
|
LOGP(DSCH, LOGL_NOTICE, "Reset scheduler %s\n",
|
|
|
|
reset_clock ? "and clock counter" : "");
|
2017-07-04 13:55:12 +00:00
|
|
|
|
|
|
|
/* Free all potentially allocated timeslots */
|
2022-07-15 21:50:35 +00:00
|
|
|
for (tn = 0; tn < ARRAY_SIZE(sched->ts); tn++)
|
|
|
|
l1sched_del_ts(sched, tn);
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2017-07-27 10:53:09 +00:00
|
|
|
/* Stop and reset clock counter if required */
|
|
|
|
if (reset_clock)
|
2022-07-06 11:06:07 +00:00
|
|
|
l1sched_clck_reset(sched);
|
2017-07-04 13:55:12 +00:00
|
|
|
}
|
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
struct l1sched_ts *l1sched_add_ts(struct l1sched_state *sched, int tn)
|
2017-07-04 13:55:12 +00:00
|
|
|
{
|
2017-07-28 08:47:41 +00:00
|
|
|
/* Make sure that ts isn't allocated yet */
|
2022-07-15 21:33:03 +00:00
|
|
|
if (sched->ts[tn] != NULL) {
|
2017-07-28 08:47:41 +00:00
|
|
|
LOGP(DSCH, LOGL_ERROR, "Timeslot #%u already allocated\n", tn);
|
|
|
|
return NULL;
|
|
|
|
}
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2017-07-28 08:47:41 +00:00
|
|
|
LOGP(DSCH, LOGL_NOTICE, "Add a new TDMA timeslot #%u\n", tn);
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2022-07-15 21:33:03 +00:00
|
|
|
sched->ts[tn] = talloc_zero(sched, struct l1sched_ts);
|
|
|
|
sched->ts[tn]->sched = sched;
|
|
|
|
sched->ts[tn]->index = tn;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2022-07-15 21:33:03 +00:00
|
|
|
return sched->ts[tn];
|
2017-07-04 13:55:12 +00:00
|
|
|
}
|
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
void l1sched_del_ts(struct l1sched_state *sched, int tn)
|
2017-07-04 13:55:12 +00:00
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_lchan_state *lchan, *lchan_next;
|
|
|
|
struct l1sched_ts *ts;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
|
|
|
/* Find ts in list */
|
2022-07-15 21:33:03 +00:00
|
|
|
ts = sched->ts[tn];
|
2017-07-04 13:55:12 +00:00
|
|
|
if (ts == NULL)
|
|
|
|
return;
|
|
|
|
|
2017-07-28 08:47:41 +00:00
|
|
|
LOGP(DSCH, LOGL_NOTICE, "Delete TDMA timeslot #%u\n", tn);
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2018-01-04 02:17:51 +00:00
|
|
|
/* Deactivate all logical channels */
|
2022-07-01 18:42:26 +00:00
|
|
|
l1sched_deactivate_all_lchans(ts);
|
2018-01-04 02:17:51 +00:00
|
|
|
|
|
|
|
/* Free channel states */
|
2018-03-09 08:33:59 +00:00
|
|
|
llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
|
|
|
|
llist_del(&lchan->list);
|
2018-01-05 00:24:04 +00:00
|
|
|
talloc_free(lchan);
|
2018-03-09 08:33:59 +00:00
|
|
|
}
|
2018-01-04 02:17:51 +00:00
|
|
|
|
2017-07-04 13:55:12 +00:00
|
|
|
/* Flush queue primitives for TX */
|
2022-07-01 18:42:26 +00:00
|
|
|
l1sched_prim_flush_queue(&ts->tx_prims);
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2017-07-28 08:47:41 +00:00
|
|
|
/* Remove ts from list and free memory */
|
2022-07-15 21:33:03 +00:00
|
|
|
sched->ts[tn] = NULL;
|
2017-07-04 13:55:12 +00:00
|
|
|
talloc_free(ts);
|
2017-07-14 02:18:03 +00:00
|
|
|
|
|
|
|
/* Notify transceiver about that */
|
2022-07-11 22:10:32 +00:00
|
|
|
l1sched_cfg_pchan_comb_req(sched, tn, GSM_PCHAN_NONE);
|
2017-07-04 13:55:12 +00:00
|
|
|
}
|
|
|
|
|
2018-01-05 00:24:04 +00:00
|
|
|
#define LAYOUT_HAS_LCHAN(layout, lchan) \
|
|
|
|
(layout->lchan_mask & ((uint64_t) 0x01 << lchan))
|
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
int l1sched_configure_ts(struct l1sched_state *sched, int tn,
|
|
|
|
enum gsm_phys_chan_config config)
|
2017-07-04 13:55:12 +00:00
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_lchan_state *lchan;
|
2022-07-02 12:00:53 +00:00
|
|
|
enum l1sched_lchan_type type;
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_ts *ts;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
|
|
|
/* Try to find specified ts */
|
2022-07-15 21:33:03 +00:00
|
|
|
ts = sched->ts[tn];
|
2017-07-04 13:55:12 +00:00
|
|
|
if (ts != NULL) {
|
|
|
|
/* Reconfiguration of existing one */
|
2022-07-06 11:06:07 +00:00
|
|
|
l1sched_reset_ts(sched, tn);
|
2017-07-04 13:55:12 +00:00
|
|
|
} else {
|
|
|
|
/* Allocate a new one if doesn't exist */
|
2022-07-06 11:06:07 +00:00
|
|
|
ts = l1sched_add_ts(sched, tn);
|
2017-07-04 13:55:12 +00:00
|
|
|
if (ts == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Choose proper multiframe layout */
|
2022-07-01 18:42:26 +00:00
|
|
|
ts->mf_layout = l1sched_mframe_layout(config, tn);
|
2019-12-01 11:59:04 +00:00
|
|
|
if (!ts->mf_layout)
|
|
|
|
return -EINVAL;
|
2017-07-04 13:55:12 +00:00
|
|
|
if (ts->mf_layout->chan_config != config)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2017-07-26 14:28:01 +00:00
|
|
|
LOGP(DSCH, LOGL_NOTICE, "(Re)configure TDMA timeslot #%u as %s\n",
|
2017-07-28 08:47:41 +00:00
|
|
|
tn, ts->mf_layout->name);
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2018-01-05 00:24:04 +00:00
|
|
|
/* Init queue primitives for TX */
|
|
|
|
INIT_LLIST_HEAD(&ts->tx_prims);
|
|
|
|
/* Init logical channels list */
|
|
|
|
INIT_LLIST_HEAD(&ts->lchans);
|
2017-07-04 13:55:12 +00:00
|
|
|
|
|
|
|
/* Allocate channel states */
|
2022-07-02 12:00:53 +00:00
|
|
|
for (type = 0; type < _L1SCHED_CHAN_MAX; type++) {
|
2018-01-05 00:24:04 +00:00
|
|
|
if (!LAYOUT_HAS_LCHAN(ts->mf_layout, type))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Allocate a channel state */
|
2022-07-01 18:42:26 +00:00
|
|
|
lchan = talloc_zero(ts, struct l1sched_lchan_state);
|
2018-01-05 00:24:04 +00:00
|
|
|
if (!lchan)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2019-12-04 13:20:05 +00:00
|
|
|
/* set backpointer */
|
|
|
|
lchan->ts = ts;
|
|
|
|
|
2018-01-05 00:24:04 +00:00
|
|
|
/* Set channel type */
|
|
|
|
lchan->type = type;
|
|
|
|
|
|
|
|
/* Add to the list of channel states */
|
|
|
|
llist_add_tail(&lchan->list, &ts->lchans);
|
|
|
|
|
|
|
|
/* Enable channel automatically if required */
|
2022-07-01 18:42:26 +00:00
|
|
|
if (l1sched_lchan_desc[type].flags & L1SCHED_CH_FLAG_AUTO)
|
|
|
|
l1sched_activate_lchan(ts, type);
|
2017-07-04 13:55:12 +00:00
|
|
|
}
|
|
|
|
|
2017-07-14 02:18:03 +00:00
|
|
|
/* Notify transceiver about TS activation */
|
2022-07-11 22:10:32 +00:00
|
|
|
l1sched_cfg_pchan_comb_req(sched, tn, config);
|
2017-07-14 02:18:03 +00:00
|
|
|
|
2017-07-04 13:55:12 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
int l1sched_reset_ts(struct l1sched_state *sched, int tn)
|
2017-07-04 13:55:12 +00:00
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_lchan_state *lchan, *lchan_next;
|
|
|
|
struct l1sched_ts *ts;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
|
|
|
/* Try to find specified ts */
|
2022-07-15 21:33:03 +00:00
|
|
|
ts = sched->ts[tn];
|
2017-07-04 13:55:12 +00:00
|
|
|
if (ts == NULL)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* Undefine multiframe layout */
|
|
|
|
ts->mf_layout = NULL;
|
|
|
|
|
|
|
|
/* Flush queue primitives for TX */
|
2022-07-01 18:42:26 +00:00
|
|
|
l1sched_prim_flush_queue(&ts->tx_prims);
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2018-01-04 02:17:51 +00:00
|
|
|
/* Deactivate all logical channels */
|
2022-07-01 18:42:26 +00:00
|
|
|
l1sched_deactivate_all_lchans(ts);
|
2018-01-04 02:17:51 +00:00
|
|
|
|
2017-07-04 13:55:12 +00:00
|
|
|
/* Free channel states */
|
2018-01-05 00:24:04 +00:00
|
|
|
llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
|
|
|
|
llist_del(&lchan->list);
|
|
|
|
talloc_free(lchan);
|
|
|
|
}
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2017-07-14 02:18:03 +00:00
|
|
|
/* Notify transceiver about that */
|
2022-07-11 22:10:32 +00:00
|
|
|
l1sched_cfg_pchan_comb_req(sched, tn, GSM_PCHAN_NONE);
|
2017-07-14 02:18:03 +00:00
|
|
|
|
2017-07-04 13:55:12 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
int l1sched_start_ciphering(struct l1sched_ts *ts, uint8_t algo,
|
2017-12-17 22:47:28 +00:00
|
|
|
uint8_t *key, uint8_t key_len)
|
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_lchan_state *lchan;
|
2017-12-17 22:47:28 +00:00
|
|
|
|
|
|
|
/* Prevent NULL-pointer deference */
|
|
|
|
if (!ts)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* Make sure we can store this key */
|
|
|
|
if (key_len > MAX_A5_KEY_LEN)
|
|
|
|
return -ERANGE;
|
|
|
|
|
|
|
|
/* Iterate over all allocated logical channels */
|
2018-01-05 00:24:04 +00:00
|
|
|
llist_for_each_entry(lchan, &ts->lchans, list) {
|
|
|
|
/* Omit inactive channels */
|
|
|
|
if (!lchan->active)
|
|
|
|
continue;
|
|
|
|
|
2017-12-17 22:47:28 +00:00
|
|
|
/* Set key length and algorithm */
|
2018-01-05 00:24:04 +00:00
|
|
|
lchan->a5.key_len = key_len;
|
|
|
|
lchan->a5.algo = algo;
|
2017-12-17 22:47:28 +00:00
|
|
|
|
|
|
|
/* Copy requested key */
|
|
|
|
if (key_len)
|
2018-01-05 00:24:04 +00:00
|
|
|
memcpy(lchan->a5.key, key, key_len);
|
2017-12-17 22:47:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_lchan_state *l1sched_find_lchan(struct l1sched_ts *ts,
|
2022-07-02 12:00:53 +00:00
|
|
|
enum l1sched_lchan_type chan)
|
2017-07-04 13:55:12 +00:00
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_lchan_state *lchan;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
2018-01-05 00:24:04 +00:00
|
|
|
llist_for_each_entry(lchan, &ts->lchans, list)
|
|
|
|
if (lchan->type == chan)
|
|
|
|
return lchan;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
int l1sched_set_lchans(struct l1sched_ts *ts, uint8_t chan_nr,
|
|
|
|
int active, uint8_t tch_mode, uint8_t tsc)
|
2017-07-29 17:43:52 +00:00
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
const struct l1sched_lchan_desc *lchan_desc;
|
|
|
|
struct l1sched_lchan_state *lchan;
|
2018-01-05 00:24:04 +00:00
|
|
|
int rc = 0;
|
2017-07-29 17:43:52 +00:00
|
|
|
|
|
|
|
/* Prevent NULL-pointer deference */
|
2018-01-05 00:24:04 +00:00
|
|
|
if (ts == NULL) {
|
2017-07-29 17:43:52 +00:00
|
|
|
LOGP(DSCH, LOGL_ERROR, "Timeslot isn't configured\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Iterate over all allocated lchans */
|
2018-01-05 00:24:04 +00:00
|
|
|
llist_for_each_entry(lchan, &ts->lchans, list) {
|
2022-07-01 18:42:26 +00:00
|
|
|
lchan_desc = &l1sched_lchan_desc[lchan->type];
|
2017-07-29 17:43:52 +00:00
|
|
|
|
|
|
|
if (lchan_desc->chan_nr == (chan_nr & 0xf8)) {
|
2018-04-02 17:57:55 +00:00
|
|
|
if (active) {
|
2022-07-01 18:42:26 +00:00
|
|
|
rc |= l1sched_activate_lchan(ts, lchan->type);
|
2018-04-02 17:57:55 +00:00
|
|
|
lchan->tch_mode = tch_mode;
|
2022-07-06 11:06:07 +00:00
|
|
|
lchan->tsc = tsc;
|
2018-04-02 17:57:55 +00:00
|
|
|
} else
|
2022-07-01 18:42:26 +00:00
|
|
|
rc |= l1sched_deactivate_lchan(ts, lchan->type);
|
2017-07-29 17:43:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
int l1sched_activate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan)
|
2017-07-04 13:55:12 +00:00
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
const struct l1sched_lchan_desc *lchan_desc = &l1sched_lchan_desc[chan];
|
|
|
|
struct l1sched_lchan_state *lchan;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
|
|
|
/* Try to find requested logical channel */
|
2022-07-01 18:42:26 +00:00
|
|
|
lchan = l1sched_find_lchan(ts, chan);
|
2017-07-04 13:55:12 +00:00
|
|
|
if (lchan == NULL)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (lchan->active) {
|
|
|
|
LOGP(DSCH, LOGL_ERROR, "Logical channel %s already activated "
|
2022-07-01 18:42:26 +00:00
|
|
|
"on ts=%d\n", l1sched_lchan_desc[chan].name, ts->index);
|
2017-07-04 13:55:12 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2017-07-29 17:43:52 +00:00
|
|
|
LOGP(DSCH, LOGL_NOTICE, "Activating lchan=%s "
|
2022-07-01 18:42:26 +00:00
|
|
|
"on ts=%d\n", l1sched_lchan_desc[chan].name, ts->index);
|
2017-07-29 17:43:52 +00:00
|
|
|
|
2017-07-04 13:55:12 +00:00
|
|
|
/* Conditionally allocate memory for bursts */
|
|
|
|
if (lchan_desc->rx_fn && lchan_desc->burst_buf_size > 0) {
|
2018-01-05 00:24:04 +00:00
|
|
|
lchan->rx_bursts = talloc_zero_size(lchan,
|
2017-07-04 13:55:12 +00:00
|
|
|
lchan_desc->burst_buf_size);
|
|
|
|
if (lchan->rx_bursts == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lchan_desc->tx_fn && lchan_desc->burst_buf_size > 0) {
|
2018-01-05 00:24:04 +00:00
|
|
|
lchan->tx_bursts = talloc_zero_size(lchan,
|
2017-07-04 13:55:12 +00:00
|
|
|
lchan_desc->burst_buf_size);
|
|
|
|
if (lchan->tx_bursts == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Finally, update channel status */
|
|
|
|
lchan->active = 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
static void l1sched_reset_lchan(struct l1sched_lchan_state *lchan)
|
2018-01-04 00:26:34 +00:00
|
|
|
{
|
|
|
|
/* Prevent NULL-pointer deference */
|
|
|
|
OSMO_ASSERT(lchan != NULL);
|
|
|
|
|
2020-03-10 20:27:55 +00:00
|
|
|
/* Print some TDMA statistics for Downlink */
|
2022-07-01 18:42:26 +00:00
|
|
|
if (l1sched_lchan_desc[lchan->type].rx_fn && lchan->active) {
|
2020-03-10 20:27:55 +00:00
|
|
|
LOGP(DSCH, LOGL_DEBUG, "TDMA statistics for lchan=%s on ts=%u: "
|
|
|
|
"%lu DL frames have been processed, "
|
|
|
|
"%lu lost (compensated), last fn=%u\n",
|
2022-07-01 18:42:26 +00:00
|
|
|
l1sched_lchan_desc[lchan->type].name, lchan->ts->index,
|
2020-03-10 20:27:55 +00:00
|
|
|
lchan->tdma.num_proc, lchan->tdma.num_lost,
|
|
|
|
lchan->tdma.last_proc);
|
|
|
|
}
|
|
|
|
|
2018-01-04 00:26:34 +00:00
|
|
|
/* Reset internal state variables */
|
|
|
|
lchan->rx_burst_mask = 0x00;
|
|
|
|
lchan->tx_burst_mask = 0x00;
|
|
|
|
|
|
|
|
/* Free burst memory */
|
|
|
|
talloc_free(lchan->rx_bursts);
|
|
|
|
talloc_free(lchan->tx_bursts);
|
|
|
|
|
|
|
|
lchan->rx_bursts = NULL;
|
|
|
|
lchan->tx_bursts = NULL;
|
|
|
|
|
|
|
|
/* Forget the current prim */
|
2022-07-01 18:42:26 +00:00
|
|
|
l1sched_prim_drop(lchan);
|
2018-01-04 00:26:34 +00:00
|
|
|
|
trxcon/scheduler: fix Measurement Reporting on SACCH
According to 3GPP TS 04.08, section 3.4.1, SACCH logical 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.
This change introduces a separate handler for SACCH primitives,
which dequeues a SACCH primitive from transmit queue, if present.
Otherwise it dequeues a cached Measurement Report (the last
received one). Finally, if the cache is empty, a "dummy"
measurement report is used. When it's possible,
a non-MR primitive is prioritized.
Change-Id: If1b8dc74ced746d6270676fdde75fcda32f91a3d
Related: OS#2988
2018-03-22 18:40:03 +00:00
|
|
|
/* Channel specific stuff */
|
2022-07-01 18:42:26 +00:00
|
|
|
if (L1SCHED_CHAN_IS_TCH(lchan->type)) {
|
2018-01-04 00:26:34 +00:00
|
|
|
lchan->dl_ongoing_facch = 0;
|
2018-08-12 21:48:14 +00:00
|
|
|
lchan->ul_facch_blocks = 0;
|
2018-01-04 00:26:34 +00:00
|
|
|
|
2018-08-15 02:00:16 +00:00
|
|
|
lchan->tch_mode = GSM48_CMODE_SIGN;
|
2018-01-04 00:26:34 +00:00
|
|
|
|
|
|
|
/* Reset AMR state */
|
|
|
|
memset(&lchan->amr, 0x00, sizeof(lchan->amr));
|
2022-07-01 18:42:26 +00:00
|
|
|
} else if (L1SCHED_CHAN_IS_SACCH(lchan->type)) {
|
trxcon/scheduler: fix Measurement Reporting on SACCH
According to 3GPP TS 04.08, section 3.4.1, SACCH logical 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.
This change introduces a separate handler for SACCH primitives,
which dequeues a SACCH primitive from transmit queue, if present.
Otherwise it dequeues a cached Measurement Report (the last
received one). Finally, if the cache is empty, a "dummy"
measurement report is used. When it's possible,
a non-MR primitive is prioritized.
Change-Id: If1b8dc74ced746d6270676fdde75fcda32f91a3d
Related: OS#2988
2018-03-22 18:40:03 +00:00
|
|
|
/* Reset SACCH state */
|
|
|
|
memset(&lchan->sacch, 0x00, sizeof(lchan->sacch));
|
2018-01-04 00:26:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Reset ciphering state */
|
|
|
|
memset(&lchan->a5, 0x00, sizeof(lchan->a5));
|
2020-03-04 14:22:11 +00:00
|
|
|
|
|
|
|
/* Reset TDMA frame statistics */
|
|
|
|
memset(&lchan->tdma, 0x00, sizeof(lchan->tdma));
|
2018-01-04 00:26:34 +00:00
|
|
|
}
|
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
int l1sched_deactivate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan)
|
2017-07-04 13:55:12 +00:00
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_lchan_state *lchan;
|
2017-07-04 13:55:12 +00:00
|
|
|
|
|
|
|
/* Try to find requested logical channel */
|
2022-07-01 18:42:26 +00:00
|
|
|
lchan = l1sched_find_lchan(ts, chan);
|
2017-07-04 13:55:12 +00:00
|
|
|
if (lchan == NULL)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2017-07-08 12:39:14 +00:00
|
|
|
if (!lchan->active) {
|
2017-07-04 13:55:12 +00:00
|
|
|
LOGP(DSCH, LOGL_ERROR, "Logical channel %s already deactivated "
|
2022-07-01 18:42:26 +00:00
|
|
|
"on ts=%d\n", l1sched_lchan_desc[chan].name, ts->index);
|
2017-07-04 13:55:12 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2017-07-29 17:43:52 +00:00
|
|
|
LOGP(DSCH, LOGL_DEBUG, "Deactivating lchan=%s "
|
2022-07-01 18:42:26 +00:00
|
|
|
"on ts=%d\n", l1sched_lchan_desc[chan].name, ts->index);
|
2017-07-29 17:43:52 +00:00
|
|
|
|
2018-01-04 00:26:34 +00:00
|
|
|
/* Reset internal state, free memory */
|
2022-07-01 18:42:26 +00:00
|
|
|
l1sched_reset_lchan(lchan);
|
2017-12-17 20:47:23 +00:00
|
|
|
|
2018-01-04 00:26:34 +00:00
|
|
|
/* Update activation flag */
|
2017-07-04 13:55:12 +00:00
|
|
|
lchan->active = 0;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2017-07-04 14:12:25 +00:00
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
void l1sched_deactivate_all_lchans(struct l1sched_ts *ts)
|
2017-07-15 08:20:35 +00:00
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_lchan_state *lchan;
|
2017-07-15 08:20:35 +00:00
|
|
|
|
2017-07-29 17:43:52 +00:00
|
|
|
LOGP(DSCH, LOGL_DEBUG, "Deactivating all logical channels "
|
|
|
|
"on ts=%d\n", ts->index);
|
|
|
|
|
2018-01-05 00:24:04 +00:00
|
|
|
llist_for_each_entry(lchan, &ts->lchans, list) {
|
2018-01-04 00:26:34 +00:00
|
|
|
/* Omit inactive channels */
|
|
|
|
if (!lchan->active)
|
|
|
|
continue;
|
2017-07-15 08:20:35 +00:00
|
|
|
|
2018-01-04 00:26:34 +00:00
|
|
|
/* Reset internal state, free memory */
|
2022-07-01 18:42:26 +00:00
|
|
|
l1sched_reset_lchan(lchan);
|
2017-12-17 20:47:23 +00:00
|
|
|
|
2018-01-04 00:26:34 +00:00
|
|
|
/* Update activation flag */
|
2017-07-15 08:20:35 +00:00
|
|
|
lchan->active = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
enum gsm_phys_chan_config l1sched_chan_nr2pchan_config(uint8_t chan_nr)
|
2017-07-15 08:20:35 +00:00
|
|
|
{
|
|
|
|
uint8_t cbits = chan_nr >> 3;
|
|
|
|
|
2019-05-27 15:34:02 +00:00
|
|
|
if (cbits == ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs)
|
2017-07-15 08:20:35 +00:00
|
|
|
return GSM_PCHAN_TCH_F;
|
2019-05-27 15:34:02 +00:00
|
|
|
else if ((cbits & 0x1e) == ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0))
|
2017-07-15 08:20:35 +00:00
|
|
|
return GSM_PCHAN_TCH_H;
|
2019-05-27 15:34:02 +00:00
|
|
|
else if ((cbits & 0x1c) == ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0))
|
2017-07-15 08:20:35 +00:00
|
|
|
return GSM_PCHAN_CCCH_SDCCH4;
|
2019-05-27 15:34:02 +00:00
|
|
|
else if ((cbits & 0x18) == ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0))
|
2017-07-15 08:20:35 +00:00
|
|
|
return GSM_PCHAN_SDCCH8_SACCH8C;
|
2019-05-27 15:34:02 +00:00
|
|
|
else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH4)
|
2019-05-27 14:53:00 +00:00
|
|
|
return GSM_PCHAN_CCCH_SDCCH4_CBCH;
|
2019-05-27 15:34:02 +00:00
|
|
|
else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH8)
|
2018-10-01 21:44:06 +00:00
|
|
|
return GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
|
2019-05-28 00:10:48 +00:00
|
|
|
else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH)
|
|
|
|
return GSM_PCHAN_PDCH;
|
2017-07-15 08:20:35 +00:00
|
|
|
|
|
|
|
return GSM_PCHAN_NONE;
|
|
|
|
}
|
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
enum l1sched_lchan_type l1sched_chan_nr2lchan_type(uint8_t chan_nr,
|
2017-07-29 17:43:52 +00:00
|
|
|
uint8_t link_id)
|
2017-07-15 08:20:35 +00:00
|
|
|
{
|
2017-07-29 17:43:52 +00:00
|
|
|
int i;
|
2017-07-15 08:20:35 +00:00
|
|
|
|
2017-07-29 17:43:52 +00:00
|
|
|
/* Iterate over all known lchan types */
|
2022-07-02 12:00:53 +00:00
|
|
|
for (i = 0; i < _L1SCHED_CHAN_MAX; i++)
|
2022-07-01 18:42:26 +00:00
|
|
|
if (l1sched_lchan_desc[i].chan_nr == (chan_nr & 0xf8))
|
|
|
|
if (l1sched_lchan_desc[i].link_id == link_id)
|
2017-07-29 17:43:52 +00:00
|
|
|
return i;
|
2017-07-15 08:20:35 +00:00
|
|
|
|
2022-07-02 12:00:53 +00:00
|
|
|
return L1SCHED_IDLE;
|
2017-07-15 08:20:35 +00:00
|
|
|
}
|
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
static void l1sched_a5_burst_dec(struct l1sched_lchan_state *lchan,
|
2017-12-17 22:47:28 +00:00
|
|
|
uint32_t fn, sbit_t *burst)
|
|
|
|
{
|
|
|
|
ubit_t ks[114];
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* Generate keystream for a DL burst */
|
|
|
|
osmo_a5(lchan->a5.algo, lchan->a5.key, fn, ks, NULL);
|
|
|
|
|
|
|
|
/* Apply keystream over ciphertext */
|
|
|
|
for (i = 0; i < 57; i++) {
|
|
|
|
if (ks[i])
|
|
|
|
burst[i + 3] *= -1;
|
|
|
|
if (ks[i + 57])
|
|
|
|
burst[i + 88] *= -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
static void l1sched_a5_burst_enc(struct l1sched_lchan_state *lchan,
|
|
|
|
struct l1sched_burst_req *br)
|
2017-12-17 22:47:28 +00:00
|
|
|
{
|
|
|
|
ubit_t ks[114];
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* Generate keystream for an UL burst */
|
2021-06-14 21:14:34 +00:00
|
|
|
osmo_a5(lchan->a5.algo, lchan->a5.key, br->fn, NULL, ks);
|
2017-12-17 22:47:28 +00:00
|
|
|
|
|
|
|
/* Apply keystream over plaintext */
|
|
|
|
for (i = 0; i < 57; i++) {
|
2021-06-14 21:14:34 +00:00
|
|
|
br->burst[i + 3] ^= ks[i];
|
|
|
|
br->burst[i + 88] ^= ks[i + 57];
|
2017-12-17 22:47:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
static int subst_frame_loss(struct l1sched_lchan_state *lchan,
|
|
|
|
l1sched_lchan_rx_func *handler,
|
2020-03-04 14:22:11 +00:00
|
|
|
uint32_t fn)
|
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
const struct l1sched_tdma_multiframe *mf;
|
|
|
|
const struct l1sched_tdma_frame *fp;
|
2020-07-08 12:11:58 +00:00
|
|
|
int elapsed, i;
|
2020-03-04 14:22:11 +00:00
|
|
|
|
|
|
|
/* Wait until at least one TDMA frame is processed */
|
|
|
|
if (lchan->tdma.num_proc == 0)
|
|
|
|
return -EAGAIN;
|
|
|
|
|
|
|
|
/* Short alias for the current multiframe */
|
|
|
|
mf = lchan->ts->mf_layout;
|
|
|
|
|
2020-07-08 12:11:58 +00:00
|
|
|
/* Calculate how many frames elapsed since the last received one.
|
|
|
|
* The algorithm is based on GSM::FNDelta() from osmo-trx. */
|
|
|
|
elapsed = fn - lchan->tdma.last_proc;
|
|
|
|
if (elapsed >= GSM_TDMA_HYPERFRAME / 2)
|
|
|
|
elapsed -= GSM_TDMA_HYPERFRAME;
|
|
|
|
else if (elapsed < -GSM_TDMA_HYPERFRAME / 2)
|
|
|
|
elapsed += GSM_TDMA_HYPERFRAME;
|
|
|
|
|
|
|
|
/* Check TDMA frame order (wrong order is possible with fake_trx.py, see OS#4658) */
|
|
|
|
if (elapsed < 0) {
|
|
|
|
/* This burst has already been substituted by a dummy burst (all bits set to zero),
|
|
|
|
* so better drop it. Otherwise we risk to get undefined behavior in handler(). */
|
|
|
|
LOGP(DSCHD, LOGL_ERROR, "(%s) Rx burst with fn=%u older than the last "
|
|
|
|
"processed fn=%u (see OS#4658) => dropping\n",
|
2022-07-01 18:42:26 +00:00
|
|
|
l1sched_lchan_desc[lchan->type].name,
|
2020-07-08 12:11:58 +00:00
|
|
|
fn, lchan->tdma.last_proc);
|
|
|
|
return -EALREADY;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check how many frames we (potentially) need to compensate */
|
2020-03-04 14:22:11 +00:00
|
|
|
if (elapsed > mf->period) {
|
2020-07-08 12:11:58 +00:00
|
|
|
LOGP(DSCHD, LOGL_NOTICE, "Too many (>%u) contiguous TDMA frames elapsed (%d) "
|
2020-07-06 21:02:25 +00:00
|
|
|
"since the last processed fn=%u (current %u)\n",
|
|
|
|
mf->period, elapsed, lchan->tdma.last_proc, fn);
|
2020-07-06 20:51:32 +00:00
|
|
|
return -EIO;
|
2020-03-04 14:22:11 +00:00
|
|
|
} else if (elapsed == 0) {
|
|
|
|
LOGP(DSCHD, LOGL_ERROR, "No TDMA frames elapsed since the last processed "
|
|
|
|
"fn=%u, must be a bug?\n", lchan->tdma.last_proc);
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
2020-03-04 14:36:12 +00:00
|
|
|
static const sbit_t bits[148] = { 0 };
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_meas_set fake_meas = {
|
2020-03-04 14:22:11 +00:00
|
|
|
.fn = lchan->tdma.last_proc,
|
|
|
|
.rssi = -120,
|
|
|
|
.toa256 = 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Traverse from fp till the current frame */
|
|
|
|
for (i = 0; i < elapsed - 1; i++) {
|
2020-06-16 21:30:17 +00:00
|
|
|
fp = &mf->frames[GSM_TDMA_FN_INC(fake_meas.fn) % mf->period];
|
2020-03-04 14:22:11 +00:00
|
|
|
if (fp->dl_chan != lchan->type)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
LOGP(DSCHD, LOGL_NOTICE, "Substituting lost TDMA frame %u on %s\n",
|
2022-07-01 18:42:26 +00:00
|
|
|
fake_meas.fn, l1sched_lchan_desc[lchan->type].name);
|
2020-03-04 14:22:11 +00:00
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
handler(lchan, fake_meas.fn, fp->dl_bid, bits, &fake_meas);
|
2020-03-04 14:22:11 +00:00
|
|
|
|
|
|
|
/* Update TDMA frame statistics */
|
|
|
|
lchan->tdma.last_proc = fake_meas.fn;
|
|
|
|
lchan->tdma.num_proc++;
|
|
|
|
lchan->tdma.num_lost++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-07-06 11:06:07 +00:00
|
|
|
int l1sched_handle_rx_burst(struct l1sched_state *sched, uint8_t tn,
|
2020-03-04 14:22:11 +00:00
|
|
|
uint32_t fn, sbit_t *bits, uint16_t nbits,
|
2022-07-01 18:42:26 +00:00
|
|
|
const struct l1sched_meas_set *meas)
|
2017-07-04 14:12:25 +00:00
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_lchan_state *lchan;
|
|
|
|
const struct l1sched_tdma_frame *frame;
|
|
|
|
struct l1sched_ts *ts;
|
2017-07-04 14:12:25 +00:00
|
|
|
|
2022-07-01 18:42:26 +00:00
|
|
|
l1sched_lchan_rx_func *handler;
|
2022-07-02 12:00:53 +00:00
|
|
|
enum l1sched_lchan_type chan;
|
2017-07-04 14:12:25 +00:00
|
|
|
uint8_t offset, bid;
|
2020-07-08 12:11:58 +00:00
|
|
|
int rc;
|
2017-07-04 14:12:25 +00:00
|
|
|
|
2017-07-28 09:00:40 +00:00
|
|
|
/* Check whether required timeslot is allocated and configured */
|
2022-07-15 21:33:03 +00:00
|
|
|
ts = sched->ts[tn];
|
2017-07-28 09:00:40 +00:00
|
|
|
if (ts == NULL || ts->mf_layout == NULL) {
|
2017-08-19 06:38:24 +00:00
|
|
|
LOGP(DSCHD, LOGL_DEBUG, "TDMA timeslot #%u isn't configured, "
|
2017-07-28 08:47:41 +00:00
|
|
|
"ignoring burst...\n", tn);
|
2017-07-04 14:12:25 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2020-03-04 14:22:11 +00:00
|
|
|
/* Get frame from multiframe */
|
|
|
|
offset = fn % ts->mf_layout->period;
|
|
|
|
frame = ts->mf_layout->frames + offset;
|
2017-07-04 14:12:25 +00:00
|
|
|
|
2020-03-04 14:22:11 +00:00
|
|
|
/* Get required info from frame */
|
|
|
|
bid = frame->dl_bid;
|
|
|
|
chan = frame->dl_chan;
|
2022-07-01 18:42:26 +00:00
|
|
|
handler = l1sched_lchan_desc[chan].rx_fn;
|
2017-07-04 14:12:25 +00:00
|
|
|
|
2020-03-04 14:22:11 +00:00
|
|
|
/* Omit bursts which have no handler, like IDLE bursts.
|
|
|
|
* TODO: handle noise indications during IDLE frames. */
|
|
|
|
if (!handler)
|
|
|
|
return -ENODEV;
|
2017-07-04 14:12:25 +00:00
|
|
|
|
2020-03-04 14:22:11 +00:00
|
|
|
/* Find required channel state */
|
2022-07-01 18:42:26 +00:00
|
|
|
lchan = l1sched_find_lchan(ts, chan);
|
2020-03-04 14:22:11 +00:00
|
|
|
if (lchan == NULL)
|
|
|
|
return -ENODEV;
|
2017-07-04 14:12:25 +00:00
|
|
|
|
2020-03-04 14:22:11 +00:00
|
|
|
/* Ensure that channel is active */
|
|
|
|
if (!lchan->active)
|
|
|
|
return 0;
|
2017-07-04 14:12:25 +00:00
|
|
|
|
2020-03-04 14:22:11 +00:00
|
|
|
/* Compensate lost TDMA frames (if any) */
|
2020-07-08 12:11:58 +00:00
|
|
|
rc = subst_frame_loss(lchan, handler, fn);
|
|
|
|
if (rc == -EALREADY)
|
|
|
|
return rc;
|
2017-12-17 22:47:28 +00:00
|
|
|
|
2020-03-04 14:22:11 +00:00
|
|
|
/* Perform A5/X decryption if required */
|
|
|
|
if (lchan->a5.algo)
|
2022-07-01 18:42:26 +00:00
|
|
|
l1sched_a5_burst_dec(lchan, fn, bits);
|
2017-07-04 14:12:25 +00:00
|
|
|
|
2020-03-04 14:22:11 +00:00
|
|
|
/* Put burst to handler */
|
2022-07-06 11:06:07 +00:00
|
|
|
handler(lchan, fn, bid, bits, meas);
|
2017-07-04 14:12:25 +00:00
|
|
|
|
2020-03-04 14:22:11 +00:00
|
|
|
/* Update TDMA frame statistics */
|
|
|
|
lchan->tdma.last_proc = fn;
|
2020-03-10 20:00:22 +00:00
|
|
|
|
|
|
|
if (++lchan->tdma.num_proc == 0) {
|
|
|
|
/* Theoretically, we may have an integer overflow of num_proc counter.
|
|
|
|
* As a consequence, subst_frame_loss() will be unable to compensate
|
|
|
|
* one (potentionally lost) Downlink burst. On practice, it would
|
|
|
|
* happen once in 4615 * 10e-6 * (2 ^ 32 - 1) seconds or ~6 years. */
|
|
|
|
LOGP(DSCHD, LOGL_NOTICE, "Too many TDMA frames have been processed. "
|
|
|
|
"Are you running trxcon for more than 6 years?!?\n");
|
|
|
|
lchan->tdma.num_proc = 1;
|
|
|
|
}
|
2017-07-04 14:12:25 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2017-12-17 22:45:27 +00:00
|
|
|
|
2020-03-01 19:55:01 +00:00
|
|
|
#define MEAS_HIST_FIRST(hist) \
|
|
|
|
(&hist->buf[0])
|
|
|
|
#define MEAS_HIST_LAST(hist) \
|
|
|
|
(MEAS_HIST_FIRST(hist) + ARRAY_SIZE(hist->buf) - 1)
|
|
|
|
|
|
|
|
/* Add a new set of measurements to the history */
|
2022-07-01 18:42:26 +00:00
|
|
|
void l1sched_lchan_meas_push(struct l1sched_lchan_state *lchan, const struct l1sched_meas_set *meas)
|
2020-03-01 19:55:01 +00:00
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_lchan_meas_hist *hist = &lchan->meas_hist;
|
2020-03-01 19:55:01 +00:00
|
|
|
|
|
|
|
/* Find a new position where to store the measurements */
|
|
|
|
if (hist->head == MEAS_HIST_LAST(hist) || hist->head == NULL)
|
|
|
|
hist->head = MEAS_HIST_FIRST(hist);
|
|
|
|
else
|
|
|
|
hist->head++;
|
|
|
|
|
|
|
|
*hist->head = *meas;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Calculate the AVG of n measurements from the history */
|
2022-07-01 18:42:26 +00:00
|
|
|
void l1sched_lchan_meas_avg(struct l1sched_lchan_state *lchan, unsigned int n)
|
2020-03-01 19:55:01 +00:00
|
|
|
{
|
2022-07-01 18:42:26 +00:00
|
|
|
struct l1sched_lchan_meas_hist *hist = &lchan->meas_hist;
|
|
|
|
struct l1sched_meas_set *meas = hist->head;
|
2020-03-01 19:55:01 +00:00
|
|
|
int toa256_sum = 0;
|
|
|
|
int rssi_sum = 0;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
OSMO_ASSERT(n > 0 && n <= ARRAY_SIZE(hist->buf));
|
|
|
|
OSMO_ASSERT(meas != NULL);
|
|
|
|
|
|
|
|
/* Traverse backwards up to n entries, calculate the sum */
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
toa256_sum += meas->toa256;
|
|
|
|
rssi_sum += meas->rssi;
|
|
|
|
|
2020-03-02 12:07:35 +00:00
|
|
|
/* Do not go below the first burst */
|
|
|
|
if (i + 1 == n)
|
|
|
|
break;
|
|
|
|
|
2020-03-01 19:55:01 +00:00
|
|
|
if (meas == MEAS_HIST_FIRST(hist))
|
|
|
|
meas = MEAS_HIST_LAST(hist);
|
|
|
|
else
|
|
|
|
meas--;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Calculate the AVG */
|
|
|
|
lchan->meas_avg.toa256 = toa256_sum / n;
|
|
|
|
lchan->meas_avg.rssi = rssi_sum / n;
|
2020-03-02 12:07:35 +00:00
|
|
|
|
|
|
|
/* As a bonus, store TDMA frame number of the first burst */
|
|
|
|
lchan->meas_avg.fn = meas->fn;
|
2020-03-01 19:55:01 +00:00
|
|
|
}
|