add linked trxcon copy
Change-Id: Ic746c26e527f4505188729d6e5df47435daac96f
This commit is contained in:
parent
966af04ff1
commit
901f689086
|
@ -28,6 +28,7 @@ AM_CXXFLAGS = -Wall -pthread
|
|||
|
||||
# Order must be preserved
|
||||
SUBDIRS = \
|
||||
trxcon \
|
||||
CommonLibs \
|
||||
GSM \
|
||||
Transceiver52M \
|
||||
|
|
|
@ -28,6 +28,7 @@ STD_DEFINES_AND_INCLUDES = \
|
|||
|
||||
COMMON_LA = $(top_builddir)/CommonLibs/libcommon.la
|
||||
GSM_LA = $(top_builddir)/GSM/libGSM.la
|
||||
TRXCON_LA = $(top_builddir)/trxcon/libtrxcon.la
|
||||
|
||||
if ARCH_ARM
|
||||
ARCH_LA = $(top_builddir)/Transceiver52M/arch/arm/libarch.la
|
||||
|
|
|
@ -312,6 +312,7 @@ AC_MSG_RESULT([LDFLAGS="$LDFLAGS"])
|
|||
dnl Output files
|
||||
AC_CONFIG_FILES([\
|
||||
Makefile \
|
||||
trxcon/Makefile \
|
||||
CommonLibs/Makefile \
|
||||
GSM/Makefile \
|
||||
Transceiver52M/Makefile \
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# autoreconf by-products
|
||||
*.in
|
||||
|
||||
aclocal.m4
|
||||
autom4te.cache/
|
||||
configure
|
||||
depcomp
|
||||
install-sh
|
||||
missing
|
||||
compile
|
||||
|
||||
# configure by-products
|
||||
.deps/
|
||||
Makefile
|
||||
|
||||
config.status
|
||||
version.h
|
||||
|
||||
# build by-products
|
||||
*.o
|
||||
*.a
|
||||
|
||||
trxcon
|
||||
|
||||
# various
|
||||
.version
|
||||
.tarball-version
|
|
@ -0,0 +1,58 @@
|
|||
#AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
|
||||
|
||||
include $(top_srcdir)/Makefile.common
|
||||
|
||||
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
|
||||
AM_CXXFLAGS = -Wall -O3 -g -ldl -lpthread
|
||||
|
||||
# versioning magic
|
||||
BUILT_SOURCES = $(top_srcdir)/.version
|
||||
$(top_srcdir)/.version:
|
||||
echo $(VERSION) > $@-t && mv $@-t $@
|
||||
dist-hook:
|
||||
echo $(VERSION) > $(distdir)/.tarball-version
|
||||
|
||||
AM_CPPFLAGS = \
|
||||
$(all_includes) \
|
||||
-I$(top_srcdir)/include \
|
||||
$(NULL)
|
||||
|
||||
AM_CFLAGS = \
|
||||
-Wall \
|
||||
$(LIBOSMOCORE_CFLAGS) \
|
||||
$(LIBOSMOCODING_CFLAGS) \
|
||||
$(LIBOSMOGSM_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
noinst_LTLIBRARIES = libtrxcon.la
|
||||
|
||||
libtrxcon_la_SOURCES = \
|
||||
l1ctl_link.c \
|
||||
l1ctl.c \
|
||||
trx_if.c \
|
||||
logging.c \
|
||||
trxcon.c \
|
||||
$(NULL)
|
||||
|
||||
# Scheduler
|
||||
libtrxcon_la_SOURCES += \
|
||||
sched_lchan_common.c \
|
||||
sched_lchan_pdtch.c \
|
||||
sched_lchan_desc.c \
|
||||
sched_lchan_xcch.c \
|
||||
sched_lchan_tchf.c \
|
||||
sched_lchan_tchh.c \
|
||||
sched_lchan_rach.c \
|
||||
sched_lchan_sch.c \
|
||||
sched_mframe.c \
|
||||
sched_clck.c \
|
||||
sched_prim.c \
|
||||
sched_trx.c \
|
||||
$(NULL)
|
||||
|
||||
libtrxcon_la_LIBADD = \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOCODING_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(NULL)
|
||||
|
|
@ -0,0 +1,918 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* GSM L1 control interface handlers
|
||||
*
|
||||
* (C) 2014 by Sylvain Munaut <tnt@246tNt.com>
|
||||
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
#include <osmocom/gsm/protocol/gsm_08_58.h>
|
||||
|
||||
#include "logging.h"
|
||||
#include "l1ctl_link.h"
|
||||
#include "l1ctl_proto.h"
|
||||
|
||||
#include "trx_if.h"
|
||||
#include "sched_trx.h"
|
||||
|
||||
static const char *arfcn2band_name(uint16_t arfcn)
|
||||
{
|
||||
enum gsm_band band;
|
||||
|
||||
if (gsm_arfcn2band_rc(arfcn, &band) < 0)
|
||||
return "(invalid)";
|
||||
|
||||
return gsm_band_name(band);
|
||||
}
|
||||
|
||||
static struct msgb *l1ctl_alloc_msg(uint8_t msg_type)
|
||||
{
|
||||
struct l1ctl_hdr *l1h;
|
||||
struct msgb *msg;
|
||||
|
||||
/**
|
||||
* Each L1CTL message gets its own length pushed in front
|
||||
* before sending. This is why we need this small headroom.
|
||||
*/
|
||||
msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_MSG_LEN_FIELD,
|
||||
L1CTL_MSG_LEN_FIELD, "l1ctl_tx_msg");
|
||||
if (!msg) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
msg->l1h = msgb_put(msg, sizeof(*l1h));
|
||||
l1h = (struct l1ctl_hdr *) msg->l1h;
|
||||
l1h->msg_type = msg_type;
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
int l1ctl_tx_pm_conf(struct l1ctl_link *l1l, uint16_t band_arfcn,
|
||||
int dbm, int last)
|
||||
{
|
||||
struct l1ctl_pm_conf *pmc;
|
||||
struct msgb *msg;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_PM_CONF);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
LOGP(DL1C, LOGL_DEBUG, "Send PM Conf (%s %d = %d dBm)\n",
|
||||
arfcn2band_name(band_arfcn),
|
||||
band_arfcn &~ ARFCN_FLAG_MASK, dbm);
|
||||
|
||||
pmc = (struct l1ctl_pm_conf *) msgb_put(msg, sizeof(*pmc));
|
||||
pmc->band_arfcn = htons(band_arfcn);
|
||||
pmc->pm[0] = dbm2rxlev(dbm);
|
||||
pmc->pm[1] = 0;
|
||||
|
||||
if (last) {
|
||||
struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->l1h;
|
||||
l1h->flags |= L1CTL_F_DONE;
|
||||
}
|
||||
|
||||
return l1ctl_link_send(l1l, msg);
|
||||
}
|
||||
|
||||
int l1ctl_tx_reset_ind(struct l1ctl_link *l1l, uint8_t type)
|
||||
{
|
||||
struct msgb *msg;
|
||||
struct l1ctl_reset *res;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_RESET_IND);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
LOGP(DL1C, LOGL_DEBUG, "Send Reset Ind (%u)\n", type);
|
||||
|
||||
res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
|
||||
res->type = type;
|
||||
|
||||
return l1ctl_link_send(l1l, msg);
|
||||
}
|
||||
|
||||
int l1ctl_tx_reset_conf(struct l1ctl_link *l1l, uint8_t type)
|
||||
{
|
||||
struct msgb *msg;
|
||||
struct l1ctl_reset *res;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_RESET_CONF);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
LOGP(DL1C, LOGL_DEBUG, "Send Reset Conf (%u)\n", type);
|
||||
res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
|
||||
res->type = type;
|
||||
|
||||
return l1ctl_link_send(l1l, msg);
|
||||
}
|
||||
|
||||
static struct l1ctl_info_dl *put_dl_info_hdr(struct msgb *msg, struct l1ctl_info_dl *dl_info)
|
||||
{
|
||||
size_t len = sizeof(struct l1ctl_info_dl);
|
||||
struct l1ctl_info_dl *dl = (struct l1ctl_info_dl *) msgb_put(msg, len);
|
||||
|
||||
if (dl_info) /* Copy DL info provided by handler */
|
||||
memcpy(dl, dl_info, len);
|
||||
else /* Init DL info header */
|
||||
memset(dl, 0x00, len);
|
||||
|
||||
return dl;
|
||||
}
|
||||
|
||||
/* Fill in FBSB payload: BSIC and sync result */
|
||||
static struct l1ctl_fbsb_conf *fbsb_conf_make(struct msgb *msg, uint8_t result, uint8_t bsic)
|
||||
{
|
||||
struct l1ctl_fbsb_conf *conf = (struct l1ctl_fbsb_conf *) msgb_put(msg, sizeof(*conf));
|
||||
|
||||
LOGP(DL1C, LOGL_DEBUG, "Send FBSB Conf (result=%u, bsic=%u)\n", result, bsic);
|
||||
|
||||
conf->result = result;
|
||||
conf->bsic = bsic;
|
||||
|
||||
return conf;
|
||||
}
|
||||
|
||||
int l1ctl_tx_fbsb_conf(struct l1ctl_link *l1l, uint8_t result,
|
||||
struct l1ctl_info_dl *dl_info, uint8_t bsic)
|
||||
{
|
||||
struct l1ctl_fbsb_conf *conf;
|
||||
struct msgb *msg;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
|
||||
if (msg == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
put_dl_info_hdr(msg, dl_info);
|
||||
talloc_free(dl_info);
|
||||
|
||||
conf = fbsb_conf_make(msg, result, bsic);
|
||||
|
||||
/* FIXME: set proper value */
|
||||
conf->initial_freq_err = 0;
|
||||
|
||||
/* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */
|
||||
l1l->fbsb_conf_sent = true;
|
||||
|
||||
/* Abort FBSB expire timer */
|
||||
if (osmo_timer_pending(&l1l->fbsb_timer))
|
||||
osmo_timer_del(&l1l->fbsb_timer);
|
||||
|
||||
return l1ctl_link_send(l1l, msg);
|
||||
}
|
||||
|
||||
int l1ctl_tx_ccch_mode_conf(struct l1ctl_link *l1l, uint8_t mode)
|
||||
{
|
||||
struct l1ctl_ccch_mode_conf *conf;
|
||||
struct msgb *msg;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_CCCH_MODE_CONF);
|
||||
if (msg == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
conf = (struct l1ctl_ccch_mode_conf *) msgb_put(msg, sizeof(*conf));
|
||||
conf->ccch_mode = mode;
|
||||
|
||||
return l1ctl_link_send(l1l, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles both L1CTL_DATA_IND and L1CTL_TRAFFIC_IND.
|
||||
*/
|
||||
int l1ctl_tx_dt_ind(struct l1ctl_link *l1l, struct l1ctl_info_dl *data,
|
||||
uint8_t *l2, size_t l2_len, bool traffic)
|
||||
{
|
||||
struct msgb *msg;
|
||||
uint8_t *msg_l2;
|
||||
|
||||
msg = l1ctl_alloc_msg(traffic ?
|
||||
L1CTL_TRAFFIC_IND : L1CTL_DATA_IND);
|
||||
if (msg == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
put_dl_info_hdr(msg, data);
|
||||
|
||||
/* Copy the L2 payload if preset */
|
||||
if (l2 && l2_len > 0) {
|
||||
msg_l2 = (uint8_t *) msgb_put(msg, l2_len);
|
||||
memcpy(msg_l2, l2, l2_len);
|
||||
}
|
||||
|
||||
/* Put message to upper layers */
|
||||
return l1ctl_link_send(l1l, msg);
|
||||
}
|
||||
|
||||
int l1ctl_tx_rach_conf(struct l1ctl_link *l1l,
|
||||
uint16_t band_arfcn, uint32_t fn)
|
||||
{
|
||||
struct l1ctl_info_dl *dl;
|
||||
struct msgb *msg;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_RACH_CONF);
|
||||
if (msg == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
dl = put_dl_info_hdr(msg, NULL);
|
||||
memset(dl, 0x00, sizeof(*dl));
|
||||
|
||||
dl->band_arfcn = htons(band_arfcn);
|
||||
dl->frame_nr = htonl(fn);
|
||||
|
||||
return l1ctl_link_send(l1l, msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles both L1CTL_DATA_CONF and L1CTL_TRAFFIC_CONF.
|
||||
*/
|
||||
int l1ctl_tx_dt_conf(struct l1ctl_link *l1l,
|
||||
struct l1ctl_info_dl *data, bool traffic)
|
||||
{
|
||||
struct msgb *msg;
|
||||
|
||||
msg = l1ctl_alloc_msg(traffic ?
|
||||
L1CTL_TRAFFIC_CONF : L1CTL_DATA_CONF);
|
||||
if (msg == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Copy DL frame header from source message */
|
||||
put_dl_info_hdr(msg, data);
|
||||
|
||||
return l1ctl_link_send(l1l, msg);
|
||||
}
|
||||
|
||||
static enum gsm_phys_chan_config l1ctl_ccch_mode2pchan_config(enum ccch_mode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
/* TODO: distinguish extended BCCH */
|
||||
case CCCH_MODE_NON_COMBINED:
|
||||
case CCCH_MODE_NONE:
|
||||
return GSM_PCHAN_CCCH;
|
||||
|
||||
case CCCH_MODE_COMBINED:
|
||||
return GSM_PCHAN_CCCH_SDCCH4;
|
||||
case CCCH_MODE_COMBINED_CBCH:
|
||||
return GSM_PCHAN_CCCH_SDCCH4_CBCH;
|
||||
|
||||
default:
|
||||
LOGP(DL1C, LOGL_NOTICE, "Undandled CCCH mode (%u), "
|
||||
"assuming non-combined configuration\n", mode);
|
||||
return GSM_PCHAN_CCCH;
|
||||
}
|
||||
}
|
||||
|
||||
/* FBSB expire timer */
|
||||
static void fbsb_timer_cb(void *data)
|
||||
{
|
||||
struct l1ctl_link *l1l = (struct l1ctl_link *) data;
|
||||
struct l1ctl_info_dl *dl;
|
||||
struct msgb *msg;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
|
||||
if (msg == NULL)
|
||||
return;
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "FBSB timer fired for ARFCN %u\n", l1l->trx->band_arfcn &~ ARFCN_FLAG_MASK);
|
||||
|
||||
dl = put_dl_info_hdr(msg, NULL);
|
||||
|
||||
/* Fill in current ARFCN */
|
||||
dl->band_arfcn = htons(l1l->trx->band_arfcn);
|
||||
|
||||
fbsb_conf_make(msg, 255, 0);
|
||||
|
||||
/* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */
|
||||
l1l->fbsb_conf_sent = true;
|
||||
|
||||
l1ctl_link_send(l1l, msg);
|
||||
}
|
||||
|
||||
static int l1ctl_rx_fbsb_req(struct l1ctl_link *l1l, struct msgb *msg)
|
||||
{
|
||||
enum gsm_phys_chan_config ch_config;
|
||||
struct l1ctl_fbsb_req *fbsb;
|
||||
uint16_t band_arfcn;
|
||||
uint16_t timeout;
|
||||
int rc = 0;
|
||||
|
||||
fbsb = (struct l1ctl_fbsb_req *) msg->l1h;
|
||||
if (msgb_l1len(msg) < sizeof(*fbsb)) {
|
||||
LOGP(DL1C, LOGL_ERROR, "MSG too short FBSB Req: %u\n",
|
||||
msgb_l1len(msg));
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ch_config = l1ctl_ccch_mode2pchan_config(fbsb->ccch_mode);
|
||||
band_arfcn = ntohs(fbsb->band_arfcn);
|
||||
timeout = ntohs(fbsb->timeout);
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Received FBSB request (%s %d)\n",
|
||||
arfcn2band_name(band_arfcn),
|
||||
band_arfcn &~ ARFCN_FLAG_MASK);
|
||||
|
||||
/* Reset scheduler and clock counter */
|
||||
sched_trx_reset(l1l->trx, true);
|
||||
|
||||
/* Configure a single timeslot */
|
||||
sched_trx_configure_ts(l1l->trx, 0, ch_config);
|
||||
|
||||
/* Ask SCH handler to send L1CTL_FBSB_CONF */
|
||||
l1l->fbsb_conf_sent = false;
|
||||
|
||||
/* Only if current ARFCN differs */
|
||||
// if (l1l->trx->band_arfcn != band_arfcn) {
|
||||
/* Update current ARFCN */
|
||||
l1l->trx->band_arfcn = band_arfcn;
|
||||
|
||||
/* Tune transceiver to required ARFCN */
|
||||
trx_if_cmd_rxtune(l1l->trx, band_arfcn);
|
||||
trx_if_cmd_txtune(l1l->trx, band_arfcn);
|
||||
// }
|
||||
|
||||
/* Transceiver might have been powered on before, e.g.
|
||||
* in case of sending L1CTL_FBSB_REQ due to signal loss. */
|
||||
if (!l1l->trx->powered_up)
|
||||
trx_if_cmd_poweron(l1l->trx);
|
||||
|
||||
trx_if_cmd_sync(l1l->trx);
|
||||
|
||||
/* Start FBSB expire timer */
|
||||
l1l->fbsb_timer.data = l1l;
|
||||
l1l->fbsb_timer.cb = fbsb_timer_cb;
|
||||
LOGP(DL1C, LOGL_INFO, "Starting FBSB timer %u ms\n", timeout * GSM_TDMA_FN_DURATION_uS / 1000);
|
||||
osmo_timer_schedule(&l1l->fbsb_timer, 35,
|
||||
timeout * GSM_TDMA_FN_DURATION_uS);
|
||||
|
||||
exit:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_pm_req(struct l1ctl_link *l1l, struct msgb *msg)
|
||||
{
|
||||
uint16_t band_arfcn_start, band_arfcn_stop;
|
||||
struct l1ctl_pm_req *pmr;
|
||||
int rc = 0;
|
||||
|
||||
pmr = (struct l1ctl_pm_req *) msg->l1h;
|
||||
if (msgb_l1len(msg) < sizeof(*pmr)) {
|
||||
LOGP(DL1C, LOGL_ERROR, "MSG too short PM Req: %u\n",
|
||||
msgb_l1len(msg));
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
band_arfcn_start = ntohs(pmr->range.band_arfcn_from);
|
||||
band_arfcn_stop = ntohs(pmr->range.band_arfcn_to);
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Received power measurement "
|
||||
"request (%s: %d -> %d)\n",
|
||||
arfcn2band_name(band_arfcn_start),
|
||||
band_arfcn_start &~ ARFCN_FLAG_MASK,
|
||||
band_arfcn_stop &~ ARFCN_FLAG_MASK);
|
||||
|
||||
/* Send measurement request to transceiver */
|
||||
rc = trx_if_cmd_measure(l1l->trx, band_arfcn_start, band_arfcn_stop);
|
||||
|
||||
exit:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_reset_req(struct l1ctl_link *l1l, struct msgb *msg)
|
||||
{
|
||||
struct l1ctl_reset *res;
|
||||
int rc = 0;
|
||||
|
||||
res = (struct l1ctl_reset *) msg->l1h;
|
||||
if (msgb_l1len(msg) < sizeof(*res)) {
|
||||
LOGP(DL1C, LOGL_ERROR, "MSG too short Reset Req: %u\n",
|
||||
msgb_l1len(msg));
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Received reset request (%u)\n",
|
||||
res->type);
|
||||
|
||||
switch (res->type) {
|
||||
case L1CTL_RES_T_FULL:
|
||||
/* TODO: implement trx_if_reset() */
|
||||
trx_if_cmd_poweroff(l1l->trx);
|
||||
trx_if_cmd_echo(l1l->trx);
|
||||
|
||||
/* Fall through */
|
||||
case L1CTL_RES_T_SCHED:
|
||||
sched_trx_reset(l1l->trx, true);
|
||||
break;
|
||||
default:
|
||||
LOGP(DL1C, LOGL_ERROR, "Unknown L1CTL_RESET_REQ type\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Confirm */
|
||||
rc = l1ctl_tx_reset_conf(l1l, res->type);
|
||||
|
||||
exit:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_echo_req(struct l1ctl_link *l1l, struct msgb *msg)
|
||||
{
|
||||
struct l1ctl_hdr *l1h;
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Recv Echo Req\n");
|
||||
LOGP(DL1C, LOGL_NOTICE, "Send Echo Conf\n");
|
||||
|
||||
/* Nothing to do, just send it back */
|
||||
l1h = (struct l1ctl_hdr *) msg->l1h;
|
||||
l1h->msg_type = L1CTL_ECHO_CONF;
|
||||
msg->data = msg->l1h;
|
||||
|
||||
return l1ctl_link_send(l1l, msg);
|
||||
}
|
||||
|
||||
static int l1ctl_rx_ccch_mode_req(struct l1ctl_link *l1l, struct msgb *msg)
|
||||
{
|
||||
enum gsm_phys_chan_config ch_config;
|
||||
struct l1ctl_ccch_mode_req *req;
|
||||
struct trx_ts *ts;
|
||||
int rc = 0;
|
||||
|
||||
req = (struct l1ctl_ccch_mode_req *) msg->l1h;
|
||||
if (msgb_l1len(msg) < sizeof(*req)) {
|
||||
LOGP(DL1C, LOGL_ERROR, "MSG too short Reset Req: %u\n",
|
||||
msgb_l1len(msg));
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Received CCCH mode request (%u)\n",
|
||||
req->ccch_mode); /* TODO: add value-string for ccch_mode */
|
||||
|
||||
/* Make sure that TS0 is allocated and configured */
|
||||
ts = l1l->trx->ts_list[0];
|
||||
if (ts == NULL || ts->mf_layout == NULL) {
|
||||
LOGP(DL1C, LOGL_ERROR, "TS0 is not configured");
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Choose corresponding channel combination */
|
||||
ch_config = l1ctl_ccch_mode2pchan_config(req->ccch_mode);
|
||||
|
||||
/* Do nothing if the current mode matches required */
|
||||
if (ts->mf_layout->chan_config != ch_config)
|
||||
rc = sched_trx_configure_ts(l1l->trx, 0, ch_config);
|
||||
|
||||
/* Confirm reconfiguration */
|
||||
if (!rc)
|
||||
rc = l1ctl_tx_ccch_mode_conf(l1l, req->ccch_mode);
|
||||
|
||||
exit:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_rach_req(struct l1ctl_link *l1l, struct msgb *msg, bool ext)
|
||||
{
|
||||
struct l1ctl_ext_rach_req *ext_req;
|
||||
struct l1ctl_rach_req *req;
|
||||
struct l1ctl_info_ul *ul;
|
||||
struct trx_ts_prim *prim;
|
||||
size_t len;
|
||||
int rc;
|
||||
|
||||
ul = (struct l1ctl_info_ul *) msg->l1h;
|
||||
|
||||
/* Is it extended (11-bit) RACH or not? */
|
||||
if (ext) {
|
||||
ext_req = (struct l1ctl_ext_rach_req *) ul->payload;
|
||||
ext_req->offset = ntohs(ext_req->offset);
|
||||
ext_req->ra11 = ntohs(ext_req->ra11);
|
||||
len = sizeof(*ext_req);
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Received extended (11-bit) RACH request "
|
||||
"(offset=%u, synch_seq=%u, ra11=0x%02hx)\n",
|
||||
ext_req->offset, ext_req->synch_seq, ext_req->ra11);
|
||||
} else {
|
||||
req = (struct l1ctl_rach_req *) ul->payload;
|
||||
req->offset = ntohs(req->offset);
|
||||
len = sizeof(*req);
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Received regular (8-bit) RACH request "
|
||||
"(offset=%u, ra=0x%02x)\n", req->offset, req->ra);
|
||||
}
|
||||
|
||||
/* The controlling L1CTL side always does include the UL info header,
|
||||
* but may leave it empty. We assume RACH is on TS0 in this case. */
|
||||
if (ul->chan_nr == 0x00) {
|
||||
LOGP(DL1C, LOGL_NOTICE, "The UL info header is empty, "
|
||||
"assuming RACH is on TS0\n");
|
||||
ul->chan_nr = RSL_CHAN_RACH;
|
||||
}
|
||||
|
||||
/* Init a new primitive */
|
||||
rc = sched_prim_init(l1l->trx, &prim, len, ul->chan_nr, ul->link_id);
|
||||
if (rc)
|
||||
goto exit;
|
||||
|
||||
/**
|
||||
* Push this primitive to the transmit queue.
|
||||
* Indicated timeslot needs to be configured.
|
||||
*/
|
||||
rc = sched_prim_push(l1l->trx, prim, ul->chan_nr);
|
||||
if (rc) {
|
||||
talloc_free(prim);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Fill in the payload */
|
||||
memcpy(prim->payload, ul->payload, len);
|
||||
|
||||
exit:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l1ctl_proc_est_req_h0(struct trx_instance *trx, struct l1ctl_h0 *h)
|
||||
{
|
||||
uint16_t band_arfcn;
|
||||
int rc = 0;
|
||||
|
||||
band_arfcn = ntohs(h->band_arfcn);
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "L1CTL_DM_EST_REQ indicates a single "
|
||||
"ARFCN=%u channel\n", band_arfcn &~ ARFCN_FLAG_MASK);
|
||||
|
||||
/* Do we need to retune? */
|
||||
if (trx->band_arfcn == band_arfcn)
|
||||
return 0;
|
||||
|
||||
/* Tune transceiver to required ARFCN */
|
||||
rc |= trx_if_cmd_rxtune(trx, band_arfcn);
|
||||
rc |= trx_if_cmd_txtune(trx, band_arfcn);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Update current ARFCN */
|
||||
trx->band_arfcn = band_arfcn;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l1ctl_proc_est_req_h1(struct trx_instance *trx, struct l1ctl_h1 *h)
|
||||
{
|
||||
uint16_t ma[64];
|
||||
int i, rc;
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "L1CTL_DM_EST_REQ indicates a Frequency "
|
||||
"Hopping (hsn=%u, maio=%u, chans=%u) channel\n",
|
||||
h->hsn, h->maio, h->n);
|
||||
|
||||
/* No channels?!? */
|
||||
if (!h->n) {
|
||||
LOGP(DL1C, LOGL_ERROR, "No channels in mobile allocation?!?\n");
|
||||
return -EINVAL;
|
||||
} else if (h->n > ARRAY_SIZE(ma)) {
|
||||
LOGP(DL1C, LOGL_ERROR, "More than 64 channels in mobile allocation?!?\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Convert from network to host byte order */
|
||||
for (i = 0; i < h->n; i++)
|
||||
ma[i] = ntohs(h->ma[i]);
|
||||
|
||||
/* Forward hopping parameters to TRX */
|
||||
rc = trx_if_cmd_setfh(trx, h->hsn, h->maio, ma, h->n);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/**
|
||||
* TODO: update the state of trx_instance somehow
|
||||
* in order to indicate that it is in hopping mode...
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_dm_est_req(struct l1ctl_link *l1l, struct msgb *msg)
|
||||
{
|
||||
enum gsm_phys_chan_config config;
|
||||
struct l1ctl_dm_est_req *est_req;
|
||||
struct l1ctl_info_ul *ul;
|
||||
struct trx_ts *ts;
|
||||
uint8_t chan_nr, tn;
|
||||
int rc;
|
||||
|
||||
ul = (struct l1ctl_info_ul *) msg->l1h;
|
||||
est_req = (struct l1ctl_dm_est_req *) ul->payload;
|
||||
|
||||
chan_nr = ul->chan_nr;
|
||||
tn = chan_nr & 0x07;
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_DM_EST_REQ "
|
||||
"(tn=%u, chan_nr=0x%02x, tsc=%u, tch_mode=0x%02x)\n",
|
||||
tn, chan_nr, est_req->tsc, est_req->tch_mode);
|
||||
|
||||
/* Determine channel config */
|
||||
config = sched_trx_chan_nr2pchan_config(chan_nr);
|
||||
if (config == GSM_PCHAN_NONE) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Couldn't determine channel config\n");
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Frequency hopping? */
|
||||
if (est_req->h)
|
||||
rc = l1ctl_proc_est_req_h1(l1l->trx, &est_req->h1);
|
||||
else /* Single ARFCN */
|
||||
rc = l1ctl_proc_est_req_h0(l1l->trx, &est_req->h0);
|
||||
if (rc)
|
||||
goto exit;
|
||||
|
||||
/* Update TSC (Training Sequence Code) */
|
||||
l1l->trx->tsc = est_req->tsc;
|
||||
|
||||
/* Configure requested TS */
|
||||
rc = sched_trx_configure_ts(l1l->trx, tn, config);
|
||||
ts = l1l->trx->ts_list[tn];
|
||||
if (rc) {
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Deactivate all lchans */
|
||||
sched_trx_deactivate_all_lchans(ts);
|
||||
|
||||
/* Activate only requested lchans */
|
||||
rc = sched_trx_set_lchans(ts, chan_nr, 1, est_req->tch_mode);
|
||||
if (rc) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Couldn't activate requested lchans\n");
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
exit:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_dm_rel_req(struct l1ctl_link *l1l, struct msgb *msg)
|
||||
{
|
||||
LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_DM_REL_REQ, "
|
||||
"switching back to CCCH\n");
|
||||
|
||||
/* Reset scheduler */
|
||||
sched_trx_reset(l1l->trx, false);
|
||||
|
||||
msgb_free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles both L1CTL_DATA_REQ and L1CTL_TRAFFIC_REQ.
|
||||
*/
|
||||
static int l1ctl_rx_dt_req(struct l1ctl_link *l1l,
|
||||
struct msgb *msg, bool traffic)
|
||||
{
|
||||
struct l1ctl_info_ul *ul;
|
||||
struct trx_ts_prim *prim;
|
||||
uint8_t chan_nr, link_id;
|
||||
size_t payload_len;
|
||||
int rc;
|
||||
|
||||
/* Extract UL frame header */
|
||||
ul = (struct l1ctl_info_ul *) msg->l1h;
|
||||
|
||||
/* Calculate the payload len */
|
||||
msg->l2h = ul->payload;
|
||||
payload_len = msgb_l2len(msg);
|
||||
|
||||
/* Obtain channel description */
|
||||
chan_nr = ul->chan_nr;
|
||||
link_id = ul->link_id & 0x40;
|
||||
|
||||
LOGP(DL1D, LOGL_DEBUG, "Recv %s Req (chan_nr=0x%02x, "
|
||||
"link_id=0x%02x, len=%zu)\n", traffic ? "TRAFFIC" : "DATA",
|
||||
chan_nr, link_id, payload_len);
|
||||
|
||||
/* Init a new primitive */
|
||||
rc = sched_prim_init(l1l->trx, &prim, payload_len,
|
||||
chan_nr, link_id);
|
||||
if (rc)
|
||||
goto exit;
|
||||
|
||||
/* Push this primitive to transmit queue */
|
||||
rc = sched_prim_push(l1l->trx, prim, chan_nr);
|
||||
if (rc) {
|
||||
talloc_free(prim);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Fill in the payload */
|
||||
memcpy(prim->payload, ul->payload, payload_len);
|
||||
|
||||
exit:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_param_req(struct l1ctl_link *l1l, struct msgb *msg)
|
||||
{
|
||||
struct l1ctl_par_req *par_req;
|
||||
struct l1ctl_info_ul *ul;
|
||||
|
||||
ul = (struct l1ctl_info_ul *) msg->l1h;
|
||||
par_req = (struct l1ctl_par_req *) ul->payload;
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_PARAM_REQ "
|
||||
"(ta=%d, tx_power=%u)\n", par_req->ta, par_req->tx_power);
|
||||
|
||||
/* Instruct TRX to use new TA value */
|
||||
if (l1l->trx->ta != par_req->ta) {
|
||||
trx_if_cmd_setta(l1l->trx, par_req->ta);
|
||||
l1l->trx->ta = par_req->ta;
|
||||
}
|
||||
|
||||
l1l->trx->tx_power = par_req->tx_power;
|
||||
|
||||
msgb_free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_tch_mode_req(struct l1ctl_link *l1l, struct msgb *msg)
|
||||
{
|
||||
struct l1ctl_tch_mode_req *req;
|
||||
struct trx_lchan_state *lchan;
|
||||
struct trx_ts *ts;
|
||||
int i;
|
||||
|
||||
req = (struct l1ctl_tch_mode_req *) msg->l1h;
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_TCH_MODE_REQ "
|
||||
"(tch_mode=%u, audio_mode=%u)\n", req->tch_mode, req->audio_mode);
|
||||
|
||||
/* Iterate over timeslot list */
|
||||
for (i = 0; i < TRX_TS_COUNT; i++) {
|
||||
/* Timeslot is not allocated */
|
||||
ts = l1l->trx->ts_list[i];
|
||||
if (ts == NULL)
|
||||
continue;
|
||||
|
||||
/* Timeslot is not configured */
|
||||
if (ts->mf_layout == NULL)
|
||||
continue;
|
||||
|
||||
/* Iterate over all allocated lchans */
|
||||
llist_for_each_entry(lchan, &ts->lchans, list) {
|
||||
/* Omit inactive channels */
|
||||
if (!lchan->active)
|
||||
continue;
|
||||
|
||||
/* Set TCH mode */
|
||||
lchan->tch_mode = req->tch_mode;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: do we need to care about audio_mode? */
|
||||
|
||||
/* Re-use the original message as confirmation */
|
||||
struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
|
||||
l1h->msg_type = L1CTL_TCH_MODE_CONF;
|
||||
|
||||
return l1ctl_link_send(l1l, msg);
|
||||
}
|
||||
|
||||
static int l1ctl_rx_crypto_req(struct l1ctl_link *l1l, struct msgb *msg)
|
||||
{
|
||||
struct l1ctl_crypto_req *req;
|
||||
struct l1ctl_info_ul *ul;
|
||||
struct trx_ts *ts;
|
||||
uint8_t tn;
|
||||
int rc = 0;
|
||||
|
||||
ul = (struct l1ctl_info_ul *) msg->l1h;
|
||||
req = (struct l1ctl_crypto_req *) ul->payload;
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "L1CTL_CRYPTO_REQ (algo=A5/%u, key_len=%u)\n",
|
||||
req->algo, req->key_len);
|
||||
|
||||
/* Determine TS index */
|
||||
tn = ul->chan_nr & 0x7;
|
||||
|
||||
/* Make sure that required TS is allocated and configured */
|
||||
ts = l1l->trx->ts_list[tn];
|
||||
if (ts == NULL || ts->mf_layout == NULL) {
|
||||
LOGP(DL1C, LOGL_ERROR, "TS %u is not configured\n", tn);
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Poke scheduler */
|
||||
rc = sched_trx_start_ciphering(ts, req->algo, req->key, req->key_len);
|
||||
if (rc) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Couldn't configure ciphering\n");
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
exit:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int l1ctl_rx_cb(struct l1ctl_link *l1l, struct msgb *msg)
|
||||
{
|
||||
struct l1ctl_hdr *l1h;
|
||||
|
||||
l1h = (struct l1ctl_hdr *) msg->l1h;
|
||||
msg->l1h = l1h->data;
|
||||
|
||||
switch (l1h->msg_type) {
|
||||
case L1CTL_FBSB_REQ:
|
||||
return l1ctl_rx_fbsb_req(l1l, msg);
|
||||
case L1CTL_PM_REQ:
|
||||
return l1ctl_rx_pm_req(l1l, msg);
|
||||
case L1CTL_RESET_REQ:
|
||||
return l1ctl_rx_reset_req(l1l, msg);
|
||||
case L1CTL_ECHO_REQ:
|
||||
return l1ctl_rx_echo_req(l1l, msg);
|
||||
case L1CTL_CCCH_MODE_REQ:
|
||||
return l1ctl_rx_ccch_mode_req(l1l, msg);
|
||||
case L1CTL_RACH_REQ:
|
||||
return l1ctl_rx_rach_req(l1l, msg, false);
|
||||
case L1CTL_EXT_RACH_REQ:
|
||||
return l1ctl_rx_rach_req(l1l, msg, true);
|
||||
case L1CTL_DM_EST_REQ:
|
||||
return l1ctl_rx_dm_est_req(l1l, msg);
|
||||
case L1CTL_DM_REL_REQ:
|
||||
return l1ctl_rx_dm_rel_req(l1l, msg);
|
||||
case L1CTL_DATA_REQ:
|
||||
return l1ctl_rx_dt_req(l1l, msg, false);
|
||||
case L1CTL_TRAFFIC_REQ:
|
||||
return l1ctl_rx_dt_req(l1l, msg, true);
|
||||
case L1CTL_PARAM_REQ:
|
||||
return l1ctl_rx_param_req(l1l, msg);
|
||||
case L1CTL_TCH_MODE_REQ:
|
||||
return l1ctl_rx_tch_mode_req(l1l, msg);
|
||||
case L1CTL_CRYPTO_REQ:
|
||||
return l1ctl_rx_crypto_req(l1l, msg);
|
||||
|
||||
/* Not (yet) handled messages */
|
||||
case L1CTL_NEIGH_PM_REQ:
|
||||
case L1CTL_DATA_TBF_REQ:
|
||||
case L1CTL_TBF_CFG_REQ:
|
||||
case L1CTL_DM_FREQ_REQ:
|
||||
case L1CTL_SIM_REQ:
|
||||
LOGP(DL1C, LOGL_NOTICE, "Ignoring unsupported message "
|
||||
"(type=%u)\n", l1h->msg_type);
|
||||
msgb_free(msg);
|
||||
return -ENOTSUP;
|
||||
default:
|
||||
LOGP(DL1C, LOGL_ERROR, "Unknown MSG type %u: %s\n", l1h->msg_type,
|
||||
osmo_hexdump(msgb_data(msg), msgb_length(msg)));
|
||||
msgb_free(msg);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
void l1ctl_shutdown_cb(struct l1ctl_link *l1l)
|
||||
{
|
||||
/* Abort FBSB expire timer */
|
||||
if (osmo_timer_pending(&l1l->fbsb_timer))
|
||||
osmo_timer_del(&l1l->fbsb_timer);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
|
||||
#include "l1ctl_link.h"
|
||||
#include "l1ctl_proto.h"
|
||||
|
||||
/* Event handlers */
|
||||
int l1ctl_rx_cb(struct l1ctl_link *l1l, struct msgb *msg);
|
||||
void l1ctl_shutdown_cb(struct l1ctl_link *l1l);
|
||||
|
||||
int l1ctl_tx_fbsb_conf(struct l1ctl_link *l1l, uint8_t result,
|
||||
struct l1ctl_info_dl *dl_info, uint8_t bsic);
|
||||
int l1ctl_tx_ccch_mode_conf(struct l1ctl_link *l1l, uint8_t mode);
|
||||
int l1ctl_tx_pm_conf(struct l1ctl_link *l1l, uint16_t band_arfcn,
|
||||
int dbm, int last);
|
||||
int l1ctl_tx_reset_conf(struct l1ctl_link *l1l, uint8_t type);
|
||||
int l1ctl_tx_reset_ind(struct l1ctl_link *l1l, uint8_t type);
|
||||
|
||||
int l1ctl_tx_dt_ind(struct l1ctl_link *l1l, struct l1ctl_info_dl *data,
|
||||
uint8_t *l2, size_t l2_len, bool traffic);
|
||||
int l1ctl_tx_dt_conf(struct l1ctl_link *l1l,
|
||||
struct l1ctl_info_dl *data, bool traffic);
|
||||
int l1ctl_tx_rach_conf(struct l1ctl_link *l1l,
|
||||
uint16_t band_arfcn, uint32_t fn);
|
|
@ -0,0 +1,316 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* GSM L1 control socket (/tmp/osmocom_l2) handlers
|
||||
*
|
||||
* (C) 2013 by Sylvain Munaut <tnt@246tNt.com>
|
||||
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/un.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/write_queue.h>
|
||||
|
||||
#include "trxcon.h"
|
||||
#include "logging.h"
|
||||
#include "l1ctl_link.h"
|
||||
#include "l1ctl.h"
|
||||
|
||||
static struct value_string l1ctl_evt_names[] = {
|
||||
{ 0, NULL } /* no events? */
|
||||
};
|
||||
|
||||
static struct osmo_fsm_state l1ctl_fsm_states[] = {
|
||||
[L1CTL_STATE_IDLE] = {
|
||||
.out_state_mask = GEN_MASK(L1CTL_STATE_CONNECTED),
|
||||
.name = "IDLE",
|
||||
},
|
||||
[L1CTL_STATE_CONNECTED] = {
|
||||
.out_state_mask = GEN_MASK(L1CTL_STATE_IDLE),
|
||||
.name = "CONNECTED",
|
||||
},
|
||||
};
|
||||
|
||||
static struct osmo_fsm l1ctl_fsm = {
|
||||
.name = "l1ctl_link_fsm",
|
||||
.states = l1ctl_fsm_states,
|
||||
.num_states = ARRAY_SIZE(l1ctl_fsm_states),
|
||||
.log_subsys = DL1C,
|
||||
.event_names = l1ctl_evt_names,
|
||||
};
|
||||
|
||||
static int l1ctl_link_read_cb(struct osmo_fd *bfd)
|
||||
{
|
||||
struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data;
|
||||
struct msgb *msg;
|
||||
uint16_t len;
|
||||
int rc;
|
||||
|
||||
/* Attempt to read from socket */
|
||||
rc = read(bfd->fd, &len, L1CTL_MSG_LEN_FIELD);
|
||||
if (rc < L1CTL_MSG_LEN_FIELD) {
|
||||
LOGP(DL1D, LOGL_NOTICE, "L1CTL has lost connection\n");
|
||||
if (rc >= 0)
|
||||
rc = -EIO;
|
||||
l1ctl_link_close_conn(l1l);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Check message length */
|
||||
len = ntohs(len);
|
||||
if (len > L1CTL_LENGTH) {
|
||||
LOGP(DL1D, LOGL_ERROR, "Length is too big: %u\n", len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Allocate a new msg */
|
||||
msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM,
|
||||
L1CTL_HEADROOM, "l1ctl_rx_msg");
|
||||
if (!msg) {
|
||||
LOGP(DL1D, LOGL_ERROR, "Failed to allocate msg\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
msg->l1h = msgb_put(msg, len);
|
||||
rc = read(bfd->fd, msg->l1h, msgb_l1len(msg));
|
||||
if (rc != len) {
|
||||
LOGP(DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: "
|
||||
"%s\n", len, rc, strerror(errno));
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Debug print */
|
||||
LOGP(DL1D, LOGL_DEBUG, "RX: '%s'\n",
|
||||
osmo_hexdump(msg->data, msg->len));
|
||||
|
||||
/* Call L1CTL handler */
|
||||
l1ctl_rx_cb(l1l, msg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l1ctl_link_write_cb(struct osmo_fd *bfd, struct msgb *msg)
|
||||
{
|
||||
int len;
|
||||
|
||||
if (bfd->fd <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
len = write(bfd->fd, msg->data, msg->len);
|
||||
if (len != msg->len) {
|
||||
LOGP(DL1D, LOGL_ERROR, "Failed to write data: "
|
||||
"written (%d) < msg_len (%d)\n", len, msg->len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Connection handler */
|
||||
static int l1ctl_link_accept(struct osmo_fd *bfd, unsigned int flags)
|
||||
{
|
||||
struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data;
|
||||
struct osmo_fd *conn_bfd = &l1l->wq.bfd;
|
||||
struct sockaddr_un un_addr;
|
||||
socklen_t len;
|
||||
int cfd;
|
||||
|
||||
len = sizeof(un_addr);
|
||||
cfd = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
|
||||
if (cfd < 0) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Failed to accept a new connection\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check if we already have an active connection */
|
||||
if (conn_bfd->fd != -1) {
|
||||
LOGP(DL1C, LOGL_NOTICE, "A new connection rejected: "
|
||||
"we already have another active\n");
|
||||
close(cfd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
osmo_wqueue_init(&l1l->wq, 100);
|
||||
INIT_LLIST_HEAD(&conn_bfd->list);
|
||||
|
||||
l1l->wq.write_cb = l1ctl_link_write_cb;
|
||||
l1l->wq.read_cb = l1ctl_link_read_cb;
|
||||
osmo_fd_setup(conn_bfd, cfd, OSMO_FD_READ, osmo_wqueue_bfd_cb, l1l, 0);
|
||||
|
||||
if (osmo_fd_register(conn_bfd) != 0) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Failed to register new connection fd\n");
|
||||
close(conn_bfd->fd);
|
||||
conn_bfd->fd = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_CONNECT, l1l);
|
||||
osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_CONNECTED, 0, 0);
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "L1CTL has a new connection\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l1ctl_link_send(struct l1ctl_link *l1l, struct msgb *msg)
|
||||
{
|
||||
uint8_t *len;
|
||||
|
||||
/* Debug print */
|
||||
LOGP(DL1D, LOGL_DEBUG, "TX: '%s'\n",
|
||||
osmo_hexdump(msg->data, msg->len));
|
||||
|
||||
if (msg->l1h != msg->data)
|
||||
LOGP(DL1D, LOGL_INFO, "Message L1 header != Message Data\n");
|
||||
|
||||
/* Prepend 16-bit length before sending */
|
||||
len = msgb_push(msg, L1CTL_MSG_LEN_FIELD);
|
||||
osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len);
|
||||
|
||||
if (osmo_wqueue_enqueue(&l1l->wq, msg) != 0) {
|
||||
LOGP(DL1D, LOGL_ERROR, "Failed to enqueue msg!\n");
|
||||
msgb_free(msg);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l1ctl_link_close_conn(struct l1ctl_link *l1l)
|
||||
{
|
||||
struct osmo_fd *conn_bfd = &l1l->wq.bfd;
|
||||
|
||||
if (conn_bfd->fd <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Close connection socket */
|
||||
osmo_fd_unregister(conn_bfd);
|
||||
close(conn_bfd->fd);
|
||||
conn_bfd->fd = -1;
|
||||
|
||||
/* Clear pending messages */
|
||||
osmo_wqueue_clear(&l1l->wq);
|
||||
|
||||
osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_DISCONNECT, l1l);
|
||||
osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_IDLE, 0, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct l1ctl_link *l1ctl_link_init(void *tall_ctx, const char *sock_path)
|
||||
{
|
||||
struct l1ctl_link *l1l;
|
||||
struct osmo_fd *bfd;
|
||||
int rc;
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Init L1CTL link (%s)\n", sock_path);
|
||||
|
||||
l1l = talloc_zero(tall_ctx, struct l1ctl_link);
|
||||
if (!l1l) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Allocate a new dedicated state machine */
|
||||
l1l->fsm = osmo_fsm_inst_alloc(&l1ctl_fsm, l1l,
|
||||
NULL, LOGL_DEBUG, "l1ctl_link");
|
||||
if (l1l->fsm == NULL) {
|
||||
LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance "
|
||||
"of FSM '%s'\n", l1ctl_fsm.name);
|
||||
talloc_free(l1l);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Create a socket and bind handlers */
|
||||
bfd = &l1l->listen_bfd;
|
||||
|
||||
/* Bind connection handler */
|
||||
osmo_fd_setup(bfd, -1, OSMO_FD_READ, l1ctl_link_accept, l1l, 0);
|
||||
|
||||
rc = osmo_sock_unix_init_ofd(bfd, SOCK_STREAM, 0, sock_path,
|
||||
OSMO_SOCK_F_BIND);
|
||||
if (rc < 0) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n",
|
||||
strerror(errno));
|
||||
osmo_fsm_inst_free(l1l->fsm);
|
||||
talloc_free(l1l);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Bind shutdown handler */
|
||||
l1l->shutdown_cb = l1ctl_shutdown_cb;
|
||||
|
||||
/**
|
||||
* To be able to accept first connection and
|
||||
* drop others, it should be set to -1
|
||||
*/
|
||||
l1l->wq.bfd.fd = -1;
|
||||
|
||||
return l1l;
|
||||
}
|
||||
|
||||
void l1ctl_link_shutdown(struct l1ctl_link *l1l)
|
||||
{
|
||||
struct osmo_fd *listen_bfd;
|
||||
|
||||
/* May be unallocated due to init error */
|
||||
if (!l1l)
|
||||
return;
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL link\n");
|
||||
|
||||
/* Call shutdown callback */
|
||||
if (l1l->shutdown_cb != NULL)
|
||||
l1l->shutdown_cb(l1l);
|
||||
|
||||
listen_bfd = &l1l->listen_bfd;
|
||||
|
||||
/* Check if we have an established connection */
|
||||
if (l1l->wq.bfd.fd != -1)
|
||||
l1ctl_link_close_conn(l1l);
|
||||
|
||||
/* Unbind listening socket */
|
||||
if (listen_bfd->fd != -1) {
|
||||
osmo_fd_unregister(listen_bfd);
|
||||
close(listen_bfd->fd);
|
||||
listen_bfd->fd = -1;
|
||||
}
|
||||
|
||||
osmo_fsm_inst_free(l1l->fsm);
|
||||
talloc_free(l1l);
|
||||
}
|
||||
|
||||
static __attribute__((constructor)) void on_dso_load(void)
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&l1ctl_fsm) == 0);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <osmocom/core/write_queue.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
|
||||
#define L1CTL_LENGTH 256
|
||||
#define L1CTL_HEADROOM 32
|
||||
|
||||
/**
|
||||
* Each L1CTL message gets its own length pushed
|
||||
* as two bytes in front before sending.
|
||||
*/
|
||||
#define L1CTL_MSG_LEN_FIELD 2
|
||||
|
||||
/* Forward declaration to avoid mutual include */
|
||||
struct trx_instance;
|
||||
|
||||
enum l1ctl_fsm_states {
|
||||
L1CTL_STATE_IDLE = 0,
|
||||
L1CTL_STATE_CONNECTED,
|
||||
};
|
||||
|
||||
struct l1ctl_link {
|
||||
struct osmo_fsm_inst *fsm;
|
||||
struct osmo_fd listen_bfd;
|
||||
struct osmo_wqueue wq;
|
||||
|
||||
/* Bind TRX instance */
|
||||
struct trx_instance *trx;
|
||||
|
||||
/* L1CTL handlers specific */
|
||||
struct osmo_timer_list fbsb_timer;
|
||||
bool fbsb_conf_sent;
|
||||
|
||||
/* Shutdown callback */
|
||||
void (*shutdown_cb)(struct l1ctl_link *l1l);
|
||||
};
|
||||
|
||||
struct l1ctl_link *l1ctl_link_init(void *tall_ctx, const char *sock_path);
|
||||
void l1ctl_link_shutdown(struct l1ctl_link *l1l);
|
||||
|
||||
int l1ctl_link_send(struct l1ctl_link *l1l, struct msgb *msg);
|
||||
int l1ctl_link_close_conn(struct l1ctl_link *l1l);
|
|
@ -0,0 +1,387 @@
|
|||
/* Messages to be sent between the different layers */
|
||||
|
||||
/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
|
||||
* (C) 2010 by Holger Hans Peter Freyther
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __L1CTL_PROTO_H__
|
||||
#define __L1CTL_PROTO_H__
|
||||
|
||||
enum {
|
||||
_L1CTL_NONE = 0,
|
||||
L1CTL_FBSB_REQ,
|
||||
L1CTL_FBSB_CONF,
|
||||
L1CTL_DATA_IND,
|
||||
L1CTL_RACH_REQ,
|
||||
L1CTL_DM_EST_REQ,
|
||||
L1CTL_DATA_REQ,
|
||||
L1CTL_RESET_IND,
|
||||
L1CTL_PM_REQ, /* power measurement */
|
||||
L1CTL_PM_CONF, /* power measurement */
|
||||
L1CTL_ECHO_REQ,
|
||||
L1CTL_ECHO_CONF,
|
||||
L1CTL_RACH_CONF,
|
||||
L1CTL_RESET_REQ,
|
||||
L1CTL_RESET_CONF,
|
||||
L1CTL_DATA_CONF,
|
||||
L1CTL_CCCH_MODE_REQ,
|
||||
L1CTL_CCCH_MODE_CONF,
|
||||
L1CTL_DM_REL_REQ,
|
||||
L1CTL_PARAM_REQ,
|
||||
L1CTL_DM_FREQ_REQ,
|
||||
L1CTL_CRYPTO_REQ,
|
||||
L1CTL_SIM_REQ,
|
||||
L1CTL_SIM_CONF,
|
||||
L1CTL_TCH_MODE_REQ,
|
||||
L1CTL_TCH_MODE_CONF,
|
||||
L1CTL_NEIGH_PM_REQ,
|
||||
L1CTL_NEIGH_PM_IND,
|
||||
L1CTL_TRAFFIC_REQ,
|
||||
L1CTL_TRAFFIC_CONF,
|
||||
L1CTL_TRAFFIC_IND,
|
||||
L1CTL_BURST_IND,
|
||||
|
||||
/* configure TBF for uplink/downlink */
|
||||
L1CTL_TBF_CFG_REQ,
|
||||
L1CTL_TBF_CFG_CONF,
|
||||
|
||||
L1CTL_DATA_TBF_REQ,
|
||||
L1CTL_DATA_TBF_CONF,
|
||||
|
||||
/* Extended (11-bit) RACH (see 3GPP TS 05.02, section 5.2.7) */
|
||||
L1CTL_EXT_RACH_REQ,
|
||||
};
|
||||
|
||||
enum ccch_mode {
|
||||
CCCH_MODE_NONE = 0,
|
||||
CCCH_MODE_NON_COMBINED,
|
||||
CCCH_MODE_COMBINED,
|
||||
CCCH_MODE_COMBINED_CBCH,
|
||||
};
|
||||
|
||||
enum neigh_mode {
|
||||
NEIGH_MODE_NONE = 0,
|
||||
NEIGH_MODE_PM,
|
||||
NEIGH_MODE_SB,
|
||||
};
|
||||
|
||||
enum l1ctl_coding_scheme {
|
||||
L1CTL_CS_NONE,
|
||||
L1CTL_CS1,
|
||||
L1CTL_CS2,
|
||||
L1CTL_CS3,
|
||||
L1CTL_CS4,
|
||||
L1CTL_MCS1,
|
||||
L1CTL_MCS2,
|
||||
L1CTL_MCS3,
|
||||
L1CTL_MCS4,
|
||||
L1CTL_MCS5,
|
||||
L1CTL_MCS6,
|
||||
L1CTL_MCS7,
|
||||
L1CTL_MCS8,
|
||||
L1CTL_MCS9,
|
||||
};
|
||||
|
||||
/*
|
||||
* NOTE: struct size. We do add manual padding out of the believe
|
||||
* that it will avoid some unaligned access.
|
||||
*/
|
||||
|
||||
/* there are no more messages in a sequence */
|
||||
#define L1CTL_F_DONE 0x01
|
||||
|
||||
struct l1ctl_hdr {
|
||||
uint8_t msg_type;
|
||||
uint8_t flags;
|
||||
uint8_t padding[2];
|
||||
uint8_t data[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*
|
||||
* downlink info ... down from the BTS..
|
||||
*/
|
||||
struct l1ctl_info_dl {
|
||||
/* GSM 08.58 channel number (9.3.1) */
|
||||
uint8_t chan_nr;
|
||||
/* GSM 08.58 link identifier (9.3.2) */
|
||||
uint8_t link_id;
|
||||
/* the ARFCN and the band. FIXME: what about MAIO? */
|
||||
uint16_t band_arfcn;
|
||||
|
||||
uint32_t frame_nr;
|
||||
|
||||
uint8_t rx_level; /* 0 .. 63 in typical GSM notation (dBm+110) */
|
||||
uint8_t snr; /* Signal/Noise Ration (dB) */
|
||||
uint8_t num_biterr;
|
||||
uint8_t fire_crc;
|
||||
|
||||
uint8_t payload[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
/* new CCCH was found. This is following the header */
|
||||
struct l1ctl_fbsb_conf {
|
||||
int16_t initial_freq_err;
|
||||
uint8_t result;
|
||||
uint8_t bsic;
|
||||
/* FIXME: contents of cell_info ? */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* CCCH mode was changed */
|
||||
struct l1ctl_ccch_mode_conf {
|
||||
uint8_t ccch_mode; /* enum ccch_mode */
|
||||
uint8_t padding[3];
|
||||
} __attribute__((packed));
|
||||
|
||||
/* 3GPP TS 44.014, section 5.1 (Calypso specific numbers) */
|
||||
enum l1ctl_tch_loop_mode {
|
||||
L1CTL_TCH_LOOP_OPEN = 0x00,
|
||||
L1CTL_TCH_LOOP_A = 0x01,
|
||||
L1CTL_TCH_LOOP_B = 0x02,
|
||||
L1CTL_TCH_LOOP_C = 0x03,
|
||||
L1CTL_TCH_LOOP_D = 0x04,
|
||||
L1CTL_TCH_LOOP_E = 0x05,
|
||||
L1CTL_TCH_LOOP_F = 0x06,
|
||||
L1CTL_TCH_LOOP_I = 0x07,
|
||||
};
|
||||
|
||||
/* TCH mode was changed */
|
||||
struct l1ctl_tch_mode_conf {
|
||||
uint8_t tch_mode; /* enum tch_mode */
|
||||
uint8_t audio_mode;
|
||||
uint8_t tch_loop_mode; /* enum l1ctl_tch_loop_mode */
|
||||
uint8_t padding[1];
|
||||
} __attribute__((packed));
|
||||
|
||||
/* data on the CCCH was found. This is following the header */
|
||||
struct l1ctl_data_ind {
|
||||
uint8_t data[23];
|
||||
} __attribute__((packed));
|
||||
|
||||
/* traffic from the network */
|
||||
struct l1ctl_traffic_ind {
|
||||
uint8_t data[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*
|
||||
* uplink info
|
||||
*/
|
||||
struct l1ctl_info_ul {
|
||||
/* GSM 08.58 channel number (9.3.1) */
|
||||
uint8_t chan_nr;
|
||||
/* GSM 08.58 link identifier (9.3.2) */
|
||||
uint8_t link_id;
|
||||
uint8_t padding[2];
|
||||
|
||||
uint8_t payload[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct l1ctl_info_ul_tbf {
|
||||
/* references l1ctl_tbf_cfg_req.tbf_nr */
|
||||
uint8_t tbf_nr;
|
||||
uint8_t coding_scheme;
|
||||
uint8_t padding[2];
|
||||
/* RLC/MAC block, size determines CS */
|
||||
uint8_t payload[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*
|
||||
* msg for FBSB_REQ
|
||||
* the l1_info_ul header is in front
|
||||
*/
|
||||
struct l1ctl_fbsb_req {
|
||||
uint16_t band_arfcn;
|
||||
uint16_t timeout; /* in TDMA frames */
|
||||
|
||||
uint16_t freq_err_thresh1;
|
||||
uint16_t freq_err_thresh2;
|
||||
|
||||
uint8_t num_freqerr_avg;
|
||||
uint8_t flags; /* L1CTL_FBSB_F_* */
|
||||
uint8_t sync_info_idx;
|
||||
uint8_t ccch_mode; /* enum ccch_mode */
|
||||
uint8_t rxlev_exp; /* expected signal level */
|
||||
} __attribute__((packed));
|
||||
|
||||
#define L1CTL_FBSB_F_FB0 (1 << 0)
|
||||
#define L1CTL_FBSB_F_FB1 (1 << 1)
|
||||
#define L1CTL_FBSB_F_SB (1 << 2)
|
||||
#define L1CTL_FBSB_F_FB01SB (L1CTL_FBSB_F_FB0|L1CTL_FBSB_F_FB1|L1CTL_FBSB_F_SB)
|
||||
|
||||
/*
|
||||
* msg for CCCH_MODE_REQ
|
||||
* the l1_info_ul header is in front
|
||||
*/
|
||||
struct l1ctl_ccch_mode_req {
|
||||
uint8_t ccch_mode; /* enum ccch_mode */
|
||||
uint8_t padding[3];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*
|
||||
* msg for TCH_MODE_REQ
|
||||
* the l1_info_ul header is in front
|
||||
*/
|
||||
struct l1ctl_tch_mode_req {
|
||||
uint8_t tch_mode; /* enum gsm48_chan_mode */
|
||||
#define AUDIO_TX_MICROPHONE (1<<0)
|
||||
#define AUDIO_TX_TRAFFIC_REQ (1<<1)
|
||||
#define AUDIO_RX_SPEAKER (1<<2)
|
||||
#define AUDIO_RX_TRAFFIC_IND (1<<3)
|
||||
uint8_t audio_mode;
|
||||
uint8_t tch_loop_mode; /* enum l1ctl_tch_loop_mode */
|
||||
uint8_t padding[1];
|
||||
} __attribute__((packed));
|
||||
|
||||
/* the l1_info_ul header is in front */
|
||||
struct l1ctl_rach_req {
|
||||
uint8_t ra;
|
||||
uint8_t combined;
|
||||
uint16_t offset;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
/* the l1_info_ul header is in front */
|
||||
struct l1ctl_ext_rach_req {
|
||||
uint16_t ra11;
|
||||
uint8_t synch_seq;
|
||||
uint8_t combined;
|
||||
uint16_t offset;
|
||||
} __attribute__((packed));
|
||||
|
||||
/* the l1_info_ul header is in front */
|
||||
struct l1ctl_par_req {
|
||||
int8_t ta;
|
||||
uint8_t tx_power;
|
||||
uint8_t padding[2];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct l1ctl_h0 {
|
||||
uint16_t band_arfcn;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct l1ctl_h1 {
|
||||
uint8_t hsn;
|
||||
uint8_t maio;
|
||||
uint8_t n;
|
||||
uint8_t _padding[1];
|
||||
uint16_t ma[64];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct l1ctl_dm_est_req {
|
||||
uint8_t tsc;
|
||||
uint8_t h;
|
||||
union {
|
||||
struct l1ctl_h0 h0;
|
||||
struct l1ctl_h1 h1;
|
||||
};
|
||||
uint8_t tch_mode;
|
||||
uint8_t audio_mode;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct l1ctl_dm_freq_req {
|
||||
uint16_t fn;
|
||||
uint8_t tsc;
|
||||
uint8_t h;
|
||||
union {
|
||||
struct l1ctl_h0 h0;
|
||||
struct l1ctl_h1 h1;
|
||||
};
|
||||
} __attribute__((packed));
|
||||
|
||||
struct l1ctl_crypto_req {
|
||||
uint8_t algo;
|
||||
uint8_t key_len;
|
||||
uint8_t key[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct l1ctl_pm_req {
|
||||
uint8_t type;
|
||||
uint8_t padding[3];
|
||||
|
||||
union {
|
||||
struct {
|
||||
uint16_t band_arfcn_from;
|
||||
uint16_t band_arfcn_to;
|
||||
} range;
|
||||
};
|
||||
} __attribute__((packed));
|
||||
|
||||
#define BI_FLG_DUMMY (1 << 4)
|
||||
#define BI_FLG_SACCH (1 << 5)
|
||||
|
||||
struct l1ctl_burst_ind {
|
||||
uint32_t frame_nr;
|
||||
uint16_t band_arfcn; /* ARFCN + band + ul indicator */
|
||||
uint8_t chan_nr; /* GSM 08.58 channel number (9.3.1) */
|
||||
uint8_t flags; /* BI_FLG_xxx + burst_id = 2LSBs */
|
||||
uint8_t rx_level; /* 0 .. 63 in typical GSM notation (dBm+110) */
|
||||
uint8_t snr; /* Reported SNR >> 8 (0-255) */
|
||||
uint8_t bits[15]; /* 114 bits + 2 steal bits. Filled MSB first */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* a single L1CTL_PM response */
|
||||
struct l1ctl_pm_conf {
|
||||
uint16_t band_arfcn;
|
||||
uint8_t pm[2];
|
||||
} __attribute__((packed));
|
||||
|
||||
enum l1ctl_reset_type {
|
||||
L1CTL_RES_T_BOOT, /* only _IND */
|
||||
L1CTL_RES_T_FULL,
|
||||
L1CTL_RES_T_SCHED,
|
||||
};
|
||||
|
||||
/* argument to L1CTL_RESET_REQ and L1CTL_RESET_IND */
|
||||
struct l1ctl_reset {
|
||||
uint8_t type;
|
||||
uint8_t pad[3];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct l1ctl_neigh_pm_req {
|
||||
uint8_t n;
|
||||
uint8_t padding[1];
|
||||
uint16_t band_arfcn[64];
|
||||
uint8_t tn[64];
|
||||
} __attribute__((packed));
|
||||
|
||||
/* neighbour cell measurement results */
|
||||
struct l1ctl_neigh_pm_ind {
|
||||
uint16_t band_arfcn;
|
||||
uint8_t pm[2];
|
||||
uint8_t tn;
|
||||
uint8_t padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
/* traffic data to network */
|
||||
struct l1ctl_traffic_req {
|
||||
uint8_t data[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct l1ctl_tbf_cfg_req {
|
||||
/* future support for multiple concurrent TBFs. 0 for now */
|
||||
uint8_t tbf_nr;
|
||||
/* is this about an UL TBF (1) or DL (0) */
|
||||
uint8_t is_uplink;
|
||||
uint8_t padding[2];
|
||||
|
||||
/* one USF for each TN, or 255 for invalid/unused */
|
||||
uint8_t usf[8];
|
||||
} __attribute__((packed));
|
||||
|
||||
#endif /* __L1CTL_PROTO_H__ */
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
*
|
||||
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <osmocom/core/application.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
static struct log_info_cat trx_log_info_cat[] = {
|
||||
[DAPP] = {
|
||||
.name = "DAPP",
|
||||
.description = "Application",
|
||||
.color = "\033[1;35m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DL1C] = {
|
||||
.name = "DL1C",
|
||||
.description = "Layer 1 control interface",
|
||||
.color = "\033[1;31m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DL1D] = {
|
||||
.name = "DL1D",
|
||||
.description = "Layer 1 data",
|
||||
.color = "\033[1;31m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DTRX] = {
|
||||
.name = "DTRX",
|
||||
.description = "Transceiver control interface",
|
||||
.color = "\033[1;33m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DTRXD] = {
|
||||
.name = "DTRXD",
|
||||
.description = "Transceiver data interface",
|
||||
.color = "\033[1;33m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DSCH] = {
|
||||
.name = "DSCH",
|
||||
.description = "Scheduler management",
|
||||
.color = "\033[1;36m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DSCHD] = {
|
||||
.name = "DSCHD",
|
||||
.description = "Scheduler data",
|
||||
.color = "\033[1;36m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct log_info trx_log_info = {
|
||||
.cat = trx_log_info_cat,
|
||||
.num_cat = ARRAY_SIZE(trx_log_info_cat),
|
||||
};
|
||||
|
||||
int trx_log_init(void *tall_ctx, const char *category_mask)
|
||||
{
|
||||
osmo_init_logging2(tall_ctx, &trx_log_info);
|
||||
|
||||
if (category_mask)
|
||||
log_parse_category_mask(osmo_stderr_target, category_mask);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
#define DEBUG_DEFAULT "DAPP:DL1C:DL1D:DTRX:DTRXD:DSCH:DSCHD"
|
||||
|
||||
enum {
|
||||
DAPP,
|
||||
DL1C,
|
||||
DL1D,
|
||||
DTRX,
|
||||
DTRXD,
|
||||
DSCH,
|
||||
DSCHD,
|
||||
};
|
||||
|
||||
int trx_log_init(void *tall_ctx, const char *category_mask);
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: clock synchronization
|
||||
*
|
||||
* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
|
||||
* (C) 2015 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 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.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/core/timer_compat.h>
|
||||
#include <osmocom/gsm/a5.h>
|
||||
|
||||
#include "scheduler.h"
|
||||
#include "logging.h"
|
||||
#include "trx_if.h"
|
||||
|
||||
#define MAX_FN_SKEW 50
|
||||
#define TRX_LOSS_FRAMES 400
|
||||
|
||||
static void sched_clck_tick(void *data)
|
||||
{
|
||||
struct trx_sched *sched = (struct trx_sched *) data;
|
||||
struct timespec tv_now, *tv_clock, elapsed;
|
||||
int64_t elapsed_us;
|
||||
const struct timespec frame_duration = { .tv_sec = 0, .tv_nsec = GSM_TDMA_FN_DURATION_nS };
|
||||
|
||||
/* Check if transceiver is still alive */
|
||||
if (sched->fn_counter_lost++ == TRX_LOSS_FRAMES) {
|
||||
LOGP(DSCH, LOGL_DEBUG, "No more clock from transceiver\n");
|
||||
sched->state = SCH_CLCK_STATE_WAIT;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get actual / previous frame time */
|
||||
osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now);
|
||||
tv_clock = &sched->clock;
|
||||
|
||||
timespecsub(&tv_now, tv_clock, &elapsed);
|
||||
elapsed_us = (elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000);
|
||||
|
||||
/* If someone played with clock, or if the process stalled */
|
||||
if (elapsed_us > GSM_TDMA_FN_DURATION_uS * MAX_FN_SKEW || elapsed_us < 0) {
|
||||
LOGP(DSCH, LOGL_NOTICE, "PC clock skew: "
|
||||
"elapsed uS %" PRId64 "\n", elapsed_us);
|
||||
|
||||
sched->state = SCH_CLCK_STATE_WAIT;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Schedule next FN clock */
|
||||
while (elapsed_us > GSM_TDMA_FN_DURATION_uS / 2) {
|
||||
timespecadd(tv_clock, &frame_duration, tv_clock);
|
||||
elapsed_us -= GSM_TDMA_FN_DURATION_uS;
|
||||
|
||||
GSM_TDMA_FN_INC(sched->fn_counter_proc);
|
||||
|
||||
/* Call frame callback */
|
||||
if (sched->clock_cb)
|
||||
sched->clock_cb(sched);
|
||||
}
|
||||
|
||||
osmo_timer_schedule(&sched->clock_timer, 0,
|
||||
GSM_TDMA_FN_DURATION_uS - elapsed_us);
|
||||
}
|
||||
|
||||
static void sched_clck_correct(struct trx_sched *sched,
|
||||
struct timespec *tv_now, uint32_t fn)
|
||||
{
|
||||
sched->fn_counter_proc = fn;
|
||||
|
||||
/* Call frame callback */
|
||||
if (sched->clock_cb)
|
||||
sched->clock_cb(sched);
|
||||
|
||||
/* Schedule first FN clock */
|
||||
sched->clock = *tv_now;
|
||||
memset(&sched->clock_timer, 0, sizeof(sched->clock_timer));
|
||||
|
||||
sched->clock_timer.cb = sched_clck_tick;
|
||||
sched->clock_timer.data = sched;
|
||||
osmo_timer_schedule(&sched->clock_timer, 0, GSM_TDMA_FN_DURATION_uS);
|
||||
}
|
||||
|
||||
int sched_clck_handle(struct trx_sched *sched, uint32_t fn)
|
||||
{
|
||||
struct timespec tv_now, *tv_clock, elapsed;
|
||||
int64_t elapsed_us, elapsed_fn;
|
||||
|
||||
/* Reset lost counter */
|
||||
sched->fn_counter_lost = 0;
|
||||
|
||||
/* Get actual / previous frame time */
|
||||
osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now);
|
||||
tv_clock = &sched->clock;
|
||||
|
||||
/* If this is the first CLCK IND */
|
||||
if (sched->state == SCH_CLCK_STATE_WAIT) {
|
||||
sched_clck_correct(sched, &tv_now, fn);
|
||||
|
||||
LOGP(DSCH, LOGL_DEBUG, "Initial clock received: fn=%u\n", fn);
|
||||
sched->state = SCH_CLCK_STATE_OK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOGP(DSCH, LOGL_NOTICE, "Clock indication: fn=%u\n", fn);
|
||||
|
||||
osmo_timer_del(&sched->clock_timer);
|
||||
|
||||
/* Calculate elapsed time / frames since last processed fn */
|
||||
timespecsub(&tv_now, tv_clock, &elapsed);
|
||||
elapsed_us = (elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000);
|
||||
elapsed_fn = GSM_TDMA_FN_SUB(fn, sched->fn_counter_proc);
|
||||
|
||||
if (elapsed_fn >= 135774)
|
||||
elapsed_fn -= GSM_TDMA_HYPERFRAME;
|
||||
|
||||
/* Check for max clock skew */
|
||||
if (elapsed_fn > MAX_FN_SKEW || elapsed_fn < -MAX_FN_SKEW) {
|
||||
LOGP(DSCH, LOGL_NOTICE, "GSM clock skew: old fn=%u, "
|
||||
"new fn=%u\n", sched->fn_counter_proc, fn);
|
||||
|
||||
sched_clck_correct(sched, &tv_now, fn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOGP(DSCH, LOGL_INFO, "GSM clock jitter: %" PRId64 "\n",
|
||||
elapsed_fn * GSM_TDMA_FN_DURATION_uS - elapsed_us);
|
||||
|
||||
/* Too many frames have been processed already */
|
||||
if (elapsed_fn < 0) {
|
||||
struct timespec duration;
|
||||
/**
|
||||
* Set clock to the time or last FN should
|
||||
* have been transmitted
|
||||
*/
|
||||
duration.tv_nsec = (0 - elapsed_fn) * GSM_TDMA_FN_DURATION_nS;
|
||||
duration.tv_sec = duration.tv_nsec / 1000000000;
|
||||
duration.tv_nsec = duration.tv_nsec % 1000000000;
|
||||
timespecadd(&tv_now, &duration, tv_clock);
|
||||
|
||||
/* Set time to the time our next FN has to be transmitted */
|
||||
osmo_timer_schedule(&sched->clock_timer, 0,
|
||||
GSM_TDMA_FN_DURATION_uS * (1 - elapsed_fn));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Transmit what we still need to transmit */
|
||||
while (fn != sched->fn_counter_proc) {
|
||||
GSM_TDMA_FN_INC(sched->fn_counter_proc);
|
||||
|
||||
/* Call frame callback */
|
||||
if (sched->clock_cb)
|
||||
sched->clock_cb(sched);
|
||||
}
|
||||
|
||||
/* Schedule next FN to be transmitted */
|
||||
*tv_clock = tv_now;
|
||||
osmo_timer_schedule(&sched->clock_timer, 0, GSM_TDMA_FN_DURATION_uS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sched_clck_reset(struct trx_sched *sched)
|
||||
{
|
||||
/* Reset internal state */
|
||||
sched->state = SCH_CLCK_STATE_WAIT;
|
||||
|
||||
/* Stop clock timer */
|
||||
osmo_timer_del(&sched->clock_timer);
|
||||
|
||||
/* Flush counters */
|
||||
sched->fn_counter_proc = 0;
|
||||
sched->fn_counter_lost = 0;
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: common routines for lchan handlers
|
||||
*
|
||||
* (C) 2017-2020 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <talloc.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/gsmtap_util.h>
|
||||
#include <osmocom/core/gsmtap.h>
|
||||
|
||||
#include <osmocom/codec/codec.h>
|
||||
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
#include <osmocom/gsm/protocol/gsm_08_58.h>
|
||||
|
||||
#include "l1ctl_proto.h"
|
||||
#include "scheduler.h"
|
||||
#include "sched_trx.h"
|
||||
#include "logging.h"
|
||||
#include "trxcon.h"
|
||||
#include "trx_if.h"
|
||||
#include "l1ctl.h"
|
||||
|
||||
/* GSM 05.02 Chapter 5.2.3 Normal Burst (NB) */
|
||||
const uint8_t sched_nb_training_bits[8][26] = {
|
||||
{
|
||||
0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0,
|
||||
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1,
|
||||
},
|
||||
{
|
||||
0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1,
|
||||
},
|
||||
{
|
||||
0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||
},
|
||||
{
|
||||
0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0,
|
||||
},
|
||||
{
|
||||
0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0,
|
||||
1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1,
|
||||
},
|
||||
{
|
||||
0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0,
|
||||
},
|
||||
{
|
||||
1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1,
|
||||
0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1,
|
||||
},
|
||||
{
|
||||
1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0,
|
||||
0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0,
|
||||
},
|
||||
};
|
||||
|
||||
/* Get a string representation of the burst buffer's completeness.
|
||||
* Examples: " ****.." (incomplete, 4/6 bursts)
|
||||
* " ****" (complete, all 4 bursts)
|
||||
* "**.***.." (incomplete, 5/8 bursts) */
|
||||
const char *burst_mask2str(const uint8_t *mask, int bits)
|
||||
{
|
||||
/* TODO: CSD is interleaved over 22 bursts, so the mask needs to be extended */
|
||||
static char buf[8 + 1];
|
||||
char *ptr = buf;
|
||||
|
||||
OSMO_ASSERT(bits <= 8 && bits > 0);
|
||||
|
||||
while (--bits >= 0)
|
||||
*(ptr++) = (*mask & (1 << bits)) ? '*' : '.';
|
||||
*ptr = '\0';
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
int sched_gsmtap_send(enum trx_lchan_type lchan_type, uint32_t fn, uint8_t tn,
|
||||
uint16_t band_arfcn, int8_t signal_dbm, uint8_t snr,
|
||||
const uint8_t *data, size_t data_len)
|
||||
{
|
||||
const struct trx_lchan_desc *lchan_desc = &trx_lchan_desc[lchan_type];
|
||||
|
||||
/* GSMTAP logging may not be enabled */
|
||||
if (gsmtap == NULL)
|
||||
return 0;
|
||||
|
||||
/* Omit frames with unknown channel type */
|
||||
if (lchan_desc->gsmtap_chan_type == GSMTAP_CHANNEL_UNKNOWN)
|
||||
return 0;
|
||||
|
||||
/* TODO: distinguish GSMTAP_CHANNEL_PCH and GSMTAP_CHANNEL_AGCH */
|
||||
return gsmtap_send(gsmtap, band_arfcn, tn, lchan_desc->gsmtap_chan_type,
|
||||
lchan_desc->ss_nr, fn, signal_dbm, snr, data, data_len);
|
||||
}
|
||||
|
||||
int sched_send_dt_ind(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint8_t *l2, size_t l2_len,
|
||||
int bit_error_count, bool dec_failed, bool traffic)
|
||||
{
|
||||
const struct trx_meas_set *meas = &lchan->meas_avg;
|
||||
const struct trx_lchan_desc *lchan_desc;
|
||||
struct l1ctl_info_dl dl_hdr;
|
||||
|
||||
/* Set up pointers */
|
||||
lchan_desc = &trx_lchan_desc[lchan->type];
|
||||
|
||||
/* Fill in known downlink info */
|
||||
dl_hdr.chan_nr = lchan_desc->chan_nr | ts->index;
|
||||
dl_hdr.link_id = lchan_desc->link_id;
|
||||
dl_hdr.band_arfcn = htons(trx->band_arfcn);
|
||||
dl_hdr.num_biterr = bit_error_count;
|
||||
|
||||
/* sched_trx_meas_avg() gives us TDMA frame number of the first burst */
|
||||
dl_hdr.frame_nr = htonl(meas->fn);
|
||||
|
||||
/* RX level: 0 .. 63 in typical GSM notation (dBm + 110) */
|
||||
dl_hdr.rx_level = dbm2rxlev(meas->rssi);
|
||||
|
||||
/* FIXME: set proper values */
|
||||
dl_hdr.snr = 0;
|
||||
|
||||
/* Mark frame as broken if so */
|
||||
dl_hdr.fire_crc = dec_failed ? 2 : 0;
|
||||
|
||||
/* Put a packet to higher layers */
|
||||
l1ctl_tx_dt_ind(trx->l1l, &dl_hdr, l2, l2_len, traffic);
|
||||
|
||||
/* Optional GSMTAP logging */
|
||||
if (l2_len > 0 && (!traffic || lchan_desc->chan_nr == RSL_CHAN_OSMO_PDCH)) {
|
||||
sched_gsmtap_send(lchan->type, meas->fn, ts->index,
|
||||
trx->band_arfcn, meas->rssi, 0, l2, l2_len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sched_send_dt_conf(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, bool traffic)
|
||||
{
|
||||
const struct trx_lchan_desc *lchan_desc;
|
||||
struct l1ctl_info_dl dl_hdr;
|
||||
|
||||
/* Set up pointers */
|
||||
lchan_desc = &trx_lchan_desc[lchan->type];
|
||||
|
||||
/* Zero-initialize DL header, because we don't set all fields */
|
||||
memset(&dl_hdr, 0x00, sizeof(struct l1ctl_info_dl));
|
||||
|
||||
/* Fill in known downlink info */
|
||||
dl_hdr.chan_nr = lchan_desc->chan_nr | ts->index;
|
||||
dl_hdr.link_id = lchan_desc->link_id;
|
||||
dl_hdr.band_arfcn = htons(trx->band_arfcn);
|
||||
dl_hdr.frame_nr = htonl(fn);
|
||||
|
||||
l1ctl_tx_dt_conf(trx->l1l, &dl_hdr, traffic);
|
||||
|
||||
/* Optional GSMTAP logging */
|
||||
if (!traffic || lchan_desc->chan_nr == RSL_CHAN_OSMO_PDCH) {
|
||||
sched_gsmtap_send(lchan->type, fn, ts->index,
|
||||
trx->band_arfcn | ARFCN_UPLINK,
|
||||
0, 0, lchan->prim->payload,
|
||||
lchan->prim->payload_len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes a bad frame indication message
|
||||
* according to the current tch_mode.
|
||||
*
|
||||
* @param l2 Caller-allocated byte array
|
||||
* @param lchan Logical channel to generate BFI for
|
||||
* @return How much bytes were written
|
||||
*/
|
||||
size_t sched_bad_frame_ind(uint8_t *l2, struct trx_lchan_state *lchan)
|
||||
{
|
||||
switch (lchan->tch_mode) {
|
||||
case GSM48_CMODE_SPEECH_V1:
|
||||
if (lchan->type == TRXC_TCHF) { /* Full Rate */
|
||||
memset(l2, 0x00, GSM_FR_BYTES);
|
||||
l2[0] = 0xd0;
|
||||
return GSM_FR_BYTES;
|
||||
} else { /* Half Rate */
|
||||
memset(l2 + 1, 0x00, GSM_HR_BYTES);
|
||||
l2[0] = 0x70; /* F = 0, FT = 111 */
|
||||
return GSM_HR_BYTES + 1;
|
||||
}
|
||||
case GSM48_CMODE_SPEECH_EFR: /* Enhanced Full Rate */
|
||||
memset(l2, 0x00, GSM_EFR_BYTES);
|
||||
l2[0] = 0xc0;
|
||||
return GSM_EFR_BYTES;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* Adaptive Multi Rate */
|
||||
/* FIXME: AMR is not implemented yet */
|
||||
return 0;
|
||||
case GSM48_CMODE_SIGN:
|
||||
LOGP(DSCH, LOGL_ERROR, "BFI is not allowed in signalling mode\n");
|
||||
return 0;
|
||||
default:
|
||||
LOGP(DSCH, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,622 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: logical channels, RX / TX handlers
|
||||
*
|
||||
* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
|
||||
* (C) 2015 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 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.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <osmocom/gsm/protocol/gsm_08_58.h>
|
||||
#include <osmocom/core/gsmtap.h>
|
||||
|
||||
#include "sched_trx.h"
|
||||
|
||||
/* Forward declaration of handlers */
|
||||
int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
|
||||
const sbit_t *bits, const struct trx_meas_set *meas);
|
||||
|
||||
int tx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
|
||||
|
||||
int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
|
||||
const sbit_t *bits, const struct trx_meas_set *meas);
|
||||
|
||||
int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
|
||||
|
||||
int rx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
|
||||
const sbit_t *bits, const struct trx_meas_set *meas);
|
||||
|
||||
int tx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
|
||||
|
||||
int rx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
|
||||
const sbit_t *bits, const struct trx_meas_set *meas);
|
||||
|
||||
int tx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
|
||||
|
||||
int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
|
||||
const sbit_t *bits, const struct trx_meas_set *meas);
|
||||
|
||||
int tx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
|
||||
|
||||
const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = {
|
||||
[TRXC_IDLE] = {
|
||||
.name = "IDLE",
|
||||
.desc = "Idle channel",
|
||||
/* The MS needs to perform neighbour measurements during
|
||||
* IDLE slots, however this is not implemented (yet). */
|
||||
},
|
||||
[TRXC_FCCH] = {
|
||||
.name = "FCCH", /* 3GPP TS 05.02, section 3.3.2.1 */
|
||||
.desc = "Frequency correction channel",
|
||||
/* Handled by transceiver, nothing to do. */
|
||||
},
|
||||
[TRXC_SCH] = {
|
||||
.name = "SCH", /* 3GPP TS 05.02, section 3.3.2.2 */
|
||||
.desc = "Synchronization channel",
|
||||
|
||||
/* 3GPP TS 05.03, section 4.7. Handled by transceiver,
|
||||
* however we still need to parse BSIC (BCC / NCC). */
|
||||
.flags = TRX_CH_FLAG_AUTO,
|
||||
.rx_fn = rx_sch_fn,
|
||||
},
|
||||
[TRXC_BCCH] = {
|
||||
.name = "BCCH", /* 3GPP TS 05.02, section 3.3.2.3 */
|
||||
.desc = "Broadcast control channel",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_BCCH,
|
||||
.chan_nr = RSL_CHAN_BCCH,
|
||||
|
||||
/* Rx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4),
|
||||
* regular interleaving (3GPP TS 05.02, clause 7, table 3):
|
||||
* a L2 frame is interleaved over 4 consecutive bursts. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_AUTO,
|
||||
.rx_fn = rx_data_fn,
|
||||
},
|
||||
[TRXC_RACH] = {
|
||||
.name = "RACH", /* 3GPP TS 05.02, section 3.3.3.1 */
|
||||
.desc = "Random access channel",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_RACH,
|
||||
.chan_nr = RSL_CHAN_RACH,
|
||||
|
||||
/* Tx only, RACH convolutional coding (3GPP TS 05.03, section 4.6). */
|
||||
.flags = TRX_CH_FLAG_AUTO,
|
||||
.tx_fn = tx_rach_fn,
|
||||
},
|
||||
[TRXC_CCCH] = {
|
||||
.name = "CCCH", /* 3GPP TS 05.02, section 3.3.3.1 */
|
||||
.desc = "Common control channel",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_CCCH,
|
||||
.chan_nr = RSL_CHAN_PCH_AGCH,
|
||||
|
||||
/* Rx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4),
|
||||
* regular interleaving (3GPP TS 05.02, clause 7, table 3):
|
||||
* a L2 frame is interleaved over 4 consecutive bursts. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_AUTO,
|
||||
.rx_fn = rx_data_fn,
|
||||
},
|
||||
[TRXC_TCHF] = {
|
||||
.name = "TCH/F", /* 3GPP TS 05.02, section 3.2 */
|
||||
.desc = "Full Rate traffic channel",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_F,
|
||||
.chan_nr = RSL_CHAN_Bm_ACCHs,
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
|
||||
/* Rx and Tx, multiple convolutional coding types (3GPP TS 05.03,
|
||||
* chapter 3), block diagonal interleaving (3GPP TS 05.02, clause 7):
|
||||
*
|
||||
* - a traffic frame is interleaved over 8 consecutive bursts
|
||||
* using the even numbered bits of the first 4 bursts
|
||||
* and odd numbered bits of the last 4 bursts;
|
||||
* - a FACCH/F frame 'steals' (replaces) one traffic frame,
|
||||
* interleaving is done in the same way.
|
||||
*
|
||||
* The MS shall continuously transmit bursts, even if there is nothing
|
||||
* to send, unless DTX (Discontinuous Transmission) is used. */
|
||||
.burst_buf_size = 8 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_tchf_fn,
|
||||
.tx_fn = tx_tchf_fn,
|
||||
},
|
||||
[TRXC_TCHH_0] = {
|
||||
.name = "TCH/H(0)", /* 3GPP TS 05.02, section 3.2 */
|
||||
.desc = "Half Rate traffic channel (sub-channel 0)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H,
|
||||
.chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 0,
|
||||
|
||||
/* Rx and Tx, multiple convolutional coding types (3GPP TS 05.03,
|
||||
* chapter 3), block diagonal interleaving (3GPP TS 05.02, clause 7):
|
||||
*
|
||||
* - a traffic frame is interleaved over 4 non-consecutive bursts
|
||||
* using the even numbered bits of the first 2 bursts,
|
||||
* and odd numbered bits of the last 2 bursts;
|
||||
* - a FACCH/H frame is interleaved over 6 non-consecutive bursts
|
||||
* using the even numbered bits of the first 2 bursts,
|
||||
* all bits of the middle two 2 bursts,
|
||||
* and odd numbered bits of the last 2 bursts;
|
||||
* - a FACCH/H frame 'steals' (replaces) two traffic frames,
|
||||
* interleaving is done over 4 consecutive bursts,
|
||||
* the same as given for a TCH/FS.
|
||||
*
|
||||
* The MS shall continuously transmit bursts, even if there is nothing
|
||||
* to send, unless DTX (Discontinuous Transmission) is used. */
|
||||
.burst_buf_size = 6 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_tchh_fn,
|
||||
.tx_fn = tx_tchh_fn,
|
||||
},
|
||||
[TRXC_TCHH_1] = {
|
||||
.name = "TCH/H(1)", /* 3GPP TS 05.02, section 3.2 */
|
||||
.desc = "Half Rate traffic channel (sub-channel 1)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H,
|
||||
.chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 1,
|
||||
|
||||
/* Same as for TRXC_TCHH_0, see above. */
|
||||
.burst_buf_size = 6 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_tchh_fn,
|
||||
.tx_fn = tx_tchh_fn,
|
||||
},
|
||||
[TRXC_SDCCH4_0] = {
|
||||
.name = "SDCCH/4(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 0)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 0,
|
||||
|
||||
/* Same as for TRXC_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SDCCH4_1] = {
|
||||
.name = "SDCCH/4(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 1)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 1,
|
||||
|
||||
/* Same as for TRXC_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SDCCH4_2] = {
|
||||
.name = "SDCCH/4(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 2)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 2,
|
||||
|
||||
/* Same as for TRXC_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SDCCH4_3] = {
|
||||
.name = "SDCCH/4(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 3)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 3,
|
||||
|
||||
/* Same as for TRXC_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SDCCH8_0] = {
|
||||
.name = "SDCCH/8(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 0)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 0,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SDCCH8_1] = {
|
||||
.name = "SDCCH/8(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 1)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 1,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SDCCH8_2] = {
|
||||
.name = "SDCCH/8(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 2)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 2,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SDCCH8_3] = {
|
||||
.name = "SDCCH/8(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 3)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 3,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SDCCH8_4] = {
|
||||
.name = "SDCCH/8(4)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 4)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 4,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SDCCH8_5] = {
|
||||
.name = "SDCCH/8(5)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 5)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 5,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SDCCH8_6] = {
|
||||
.name = "SDCCH/8(6)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 6)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 6,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SDCCH8_7] = {
|
||||
.name = "SDCCH/8(7)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 7)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3),
|
||||
.link_id = TRX_CH_LID_DEDIC,
|
||||
.ss_nr = 7,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCHTF] = {
|
||||
.name = "SACCH/TF", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow TCH/F associated control channel",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_F | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_Bm_ACCHs,
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
|
||||
/* Same as for TRXC_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCHTH_0] = {
|
||||
.name = "SACCH/TH(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow TCH/H associated control channel (sub-channel 0)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 0,
|
||||
|
||||
/* Same as for TRXC_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCHTH_1] = {
|
||||
.name = "SACCH/TH(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow TCH/H associated control channel (sub-channel 1)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 1,
|
||||
|
||||
/* Same as for TRXC_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCH4_0] = {
|
||||
.name = "SACCH/4(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/4 associated control channel (sub-channel 0)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 0,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCH4_1] = {
|
||||
.name = "SACCH/4(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/4 associated control channel (sub-channel 1)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 1,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCH4_2] = {
|
||||
.name = "SACCH/4(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/4 associated control channel (sub-channel 2)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 2,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCH4_3] = {
|
||||
.name = "SACCH/4(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/4 associated control channel (sub-channel 3)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 3,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCH8_0] = {
|
||||
.name = "SACCH/8(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 0)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 0,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCH8_1] = {
|
||||
.name = "SACCH/8(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 1)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 1,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCH8_2] = {
|
||||
.name = "SACCH/8(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 2)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 2,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCH8_3] = {
|
||||
.name = "SACCH/8(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 3)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 3,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCH8_4] = {
|
||||
.name = "SACCH/8(4)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 4)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 4,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCH8_5] = {
|
||||
.name = "SACCH/8(5)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 5)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 5,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCH8_6] = {
|
||||
.name = "SACCH/8(6)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 6)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 6,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_SACCH8_7] = {
|
||||
.name = "SACCH/8(7)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 7)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3),
|
||||
.link_id = TRX_CH_LID_SACCH,
|
||||
.ss_nr = 7,
|
||||
|
||||
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[TRXC_PDTCH] = {
|
||||
.name = "PDTCH", /* 3GPP TS 05.02, sections 3.2.4, 3.3.2.4 */
|
||||
.desc = "Packet data traffic & control channel",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_PDTCH,
|
||||
.chan_nr = RSL_CHAN_OSMO_PDCH,
|
||||
|
||||
/* Rx and Tx, multiple coding schemes: CS-1..4 and MCS-1..9 (3GPP TS
|
||||
* 05.03, chapter 5), regular interleaving as specified for xCCH.
|
||||
* NOTE: the burst buffer is three times bigger because the
|
||||
* payload of EDGE bursts is three times longer. */
|
||||
.burst_buf_size = 3 * 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_PDCH,
|
||||
.rx_fn = rx_pdtch_fn,
|
||||
.tx_fn = tx_pdtch_fn,
|
||||
},
|
||||
[TRXC_PTCCH] = {
|
||||
.name = "PTCCH", /* 3GPP TS 05.02, section 3.3.4.2 */
|
||||
.desc = "Packet Timing advance control channel",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_PTCCH,
|
||||
.chan_nr = RSL_CHAN_OSMO_PDCH,
|
||||
.link_id = TRX_CH_LID_PTCCH,
|
||||
|
||||
/* On the Uplink, mobile stations transmit random Access Bursts
|
||||
* to allow estimation of the timing advance for one MS in packet
|
||||
* transfer mode. On Downlink, the network sends timing advance
|
||||
* updates for several mobile stations. The coding scheme used
|
||||
* for PTCCH/D messages is the same as for PDTCH CS-1. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_PDCH,
|
||||
.rx_fn = rx_pdtch_fn,
|
||||
.tx_fn = tx_rach_fn,
|
||||
},
|
||||
[TRXC_SDCCH4_CBCH] = {
|
||||
.name = "SDCCH/4(CBCH)", /* 3GPP TS 05.02, section 3.3.5 */
|
||||
.desc = "Cell Broadcast channel on SDCCH/4",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_CBCH51,
|
||||
.chan_nr = RSL_CHAN_OSMO_CBCH4,
|
||||
.ss_nr = 2,
|
||||
|
||||
/* Same as for TRXC_BCCH (xCCH), but Rx only. See above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = TRX_CH_FLAG_AUTO,
|
||||
.rx_fn = rx_data_fn,
|
||||
},
|
||||
[TRXC_SDCCH8_CBCH] = {
|
||||
.name = "SDCCH/8(CBCH)", /* 3GPP TS 05.02, section 3.3.5 */
|
||||
.desc = "Cell Broadcast channel on SDCCH/8",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_CBCH52,
|
||||
.chan_nr = RSL_CHAN_OSMO_CBCH8,
|
||||
.ss_nr = 2,
|
||||
|
||||
/* Same as for TRXC_BCCH (xCCH), but Rx only. See above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.rx_fn = rx_data_fn,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: handlers for DL / UL bursts on logical channels
|
||||
*
|
||||
* (C) 2018-2020 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
|
||||
#include "l1ctl_proto.h"
|
||||
#include "scheduler.h"
|
||||
#include "sched_trx.h"
|
||||
#include "logging.h"
|
||||
#include "trx_if.h"
|
||||
#include "l1ctl.h"
|
||||
|
||||
int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
|
||||
const sbit_t *bits, const struct trx_meas_set *meas)
|
||||
{
|
||||
const struct trx_lchan_desc *lchan_desc;
|
||||
uint8_t l2[GPRS_L2_MAX_LEN], *mask;
|
||||
int n_errors, n_bits_total, rc;
|
||||
sbit_t *buffer, *offset;
|
||||
size_t l2_len;
|
||||
|
||||
/* Set up pointers */
|
||||
lchan_desc = &trx_lchan_desc[lchan->type];
|
||||
mask = &lchan->rx_burst_mask;
|
||||
buffer = lchan->rx_bursts;
|
||||
|
||||
LOGP(DSCHD, LOGL_DEBUG, "Packet data received on %s: "
|
||||
"fn=%u ts=%u bid=%u\n", lchan_desc->name, fn, ts->index, bid);
|
||||
|
||||
/* Align to the first burst of a block */
|
||||
if (*mask == 0x00 && bid != 0)
|
||||
return 0;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* Store the measurements */
|
||||
sched_trx_meas_push(lchan, meas);
|
||||
|
||||
/* Copy burst to buffer of 4 bursts */
|
||||
offset = buffer + bid * 116;
|
||||
memcpy(offset, bits + 3, 58);
|
||||
memcpy(offset + 58, bits + 87, 58);
|
||||
|
||||
/* Wait until complete set of bursts */
|
||||
if (bid != 3)
|
||||
return 0;
|
||||
|
||||
/* Calculate AVG of the measurements */
|
||||
sched_trx_meas_avg(lchan, 4);
|
||||
|
||||
/* Check for complete set of bursts */
|
||||
if ((*mask & 0xf) != 0xf) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Received incomplete (%s) data frame at "
|
||||
"fn=%u (%u/%u) for %s\n",
|
||||
burst_mask2str(mask, 4), lchan->meas_avg.fn,
|
||||
lchan->meas_avg.fn % ts->mf_layout->period,
|
||||
ts->mf_layout->period,
|
||||
lchan_desc->name);
|
||||
/* NOTE: do not abort here, give it a try. Maybe we're lucky ;) */
|
||||
}
|
||||
|
||||
/* Keep the mask updated */
|
||||
*mask = *mask << 4;
|
||||
|
||||
/* Attempt to decode */
|
||||
rc = gsm0503_pdtch_decode(l2, buffer,
|
||||
NULL, &n_errors, &n_bits_total);
|
||||
if (rc < 0) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Received bad packet data frame "
|
||||
"at fn=%u (%u/%u) for %s\n", lchan->meas_avg.fn,
|
||||
lchan->meas_avg.fn % ts->mf_layout->period,
|
||||
ts->mf_layout->period,
|
||||
lchan_desc->name);
|
||||
}
|
||||
|
||||
/* Determine L2 length */
|
||||
l2_len = rc > 0 ? rc : 0;
|
||||
|
||||
/* Send a L2 frame to the higher layers */
|
||||
sched_send_dt_ind(trx, ts, lchan,
|
||||
l2, l2_len, n_errors, rc < 0, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int tx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
|
||||
{
|
||||
const struct trx_lchan_desc *lchan_desc;
|
||||
ubit_t burst[GSM_BURST_LEN];
|
||||
ubit_t *buffer, *offset;
|
||||
const uint8_t *tsc;
|
||||
uint8_t *mask;
|
||||
int rc;
|
||||
|
||||
/* Set up pointers */
|
||||
lchan_desc = &trx_lchan_desc[lchan->type];
|
||||
mask = &lchan->tx_burst_mask;
|
||||
buffer = lchan->tx_bursts;
|
||||
|
||||
if (bid > 0) {
|
||||
/* If we have encoded bursts */
|
||||
if (*mask)
|
||||
goto send_burst;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Encode payload */
|
||||
rc = gsm0503_pdtch_encode(buffer, lchan->prim->payload,
|
||||
lchan->prim->payload_len);
|
||||
if (rc < 0) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
|
||||
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
|
||||
lchan->prim->payload_len));
|
||||
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
send_burst:
|
||||
/* Determine which burst should be sent */
|
||||
offset = buffer + bid * 116;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* Choose proper TSC */
|
||||
tsc = sched_nb_training_bits[trx->tsc];
|
||||
|
||||
/* Compose a new burst */
|
||||
memset(burst, 0, 3); /* TB */
|
||||
memcpy(burst + 3, offset, 58); /* Payload 1/2 */
|
||||
memcpy(burst + 61, tsc, 26); /* TSC */
|
||||
memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
|
||||
memset(burst + 145, 0, 3); /* TB */
|
||||
|
||||
LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
|
||||
lchan_desc->name, fn, ts->index, bid);
|
||||
|
||||
/* Forward burst to scheduler */
|
||||
rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
|
||||
if (rc) {
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
/* Reset mask */
|
||||
*mask = 0x00;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* If we have sent the last (4/4) burst */
|
||||
if ((*mask & 0x0f) == 0x0f) {
|
||||
/* Confirm data / traffic sending */
|
||||
sched_send_dt_conf(trx, ts, lchan, fn, true);
|
||||
|
||||
/* Forget processed primitive */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
/* Reset mask */
|
||||
*mask = 0x00;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: handlers for DL / UL bursts on logical channels
|
||||
*
|
||||
* (C) 2017-2019 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
|
||||
#include "l1ctl_proto.h"
|
||||
#include "scheduler.h"
|
||||
#include "sched_trx.h"
|
||||
#include "logging.h"
|
||||
#include "trx_if.h"
|
||||
#include "l1ctl.h"
|
||||
|
||||
/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)" */
|
||||
#define RACH_EXT_TAIL_BITS_LEN 8
|
||||
#define RACH_SYNCH_SEQ_LEN 41
|
||||
#define RACH_PAYLOAD_LEN 36
|
||||
|
||||
/* Extended tail bits (BN0..BN7) */
|
||||
static const ubit_t rach_ext_tail_bits[] = {
|
||||
0, 0, 1, 1, 1, 0, 1, 0,
|
||||
};
|
||||
|
||||
/* Synchronization (training) sequence types */
|
||||
enum rach_synch_seq_t {
|
||||
RACH_SYNCH_SEQ_UNKNOWN = -1,
|
||||
RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */
|
||||
RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */
|
||||
RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */
|
||||
RACH_SYNCH_SEQ_NUM
|
||||
};
|
||||
|
||||
/* Synchronization (training) sequence bits */
|
||||
static const char rach_synch_seq_bits[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = {
|
||||
[RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000",
|
||||
[RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101",
|
||||
[RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111",
|
||||
};
|
||||
|
||||
/* Synchronization (training) sequence names */
|
||||
static struct value_string rach_synch_seq_names[] = {
|
||||
{ RACH_SYNCH_SEQ_UNKNOWN, "UNKNOWN" },
|
||||
{ RACH_SYNCH_SEQ_TS0, "TS0: GSM, GMSK" },
|
||||
{ RACH_SYNCH_SEQ_TS1, "TS1: EGPRS, 8-PSK" },
|
||||
{ RACH_SYNCH_SEQ_TS2, "TS2: EGPRS, GMSK" },
|
||||
{ 0, NULL },
|
||||
};
|
||||
|
||||
/* Obtain a to-be-transmitted RACH burst */
|
||||
int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
|
||||
{
|
||||
struct l1ctl_ext_rach_req *ext_req = NULL;
|
||||
struct l1ctl_rach_req *req = NULL;
|
||||
enum rach_synch_seq_t synch_seq;
|
||||
uint8_t burst[GSM_BURST_LEN];
|
||||
uint8_t *burst_ptr = burst;
|
||||
uint8_t payload[36];
|
||||
int i, rc;
|
||||
|
||||
/* Is it extended (11-bit) RACH or not? */
|
||||
if (PRIM_IS_RACH11(lchan->prim)) {
|
||||
ext_req = (struct l1ctl_ext_rach_req *) lchan->prim->payload;
|
||||
synch_seq = ext_req->synch_seq;
|
||||
|
||||
/* Check requested synch. sequence */
|
||||
if (synch_seq >= RACH_SYNCH_SEQ_NUM) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Unknown RACH synch. sequence=0x%02x\n", synch_seq);
|
||||
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/* Delay sending according to offset value */
|
||||
if (ext_req->offset-- > 0)
|
||||
return 0;
|
||||
|
||||
/* Encode extended (11-bit) payload */
|
||||
rc = gsm0503_rach_ext_encode(payload, ext_req->ra11, trx->bsic, true);
|
||||
if (rc) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Could not encode extended RACH burst "
|
||||
"(ra=%u bsic=%u)\n", ext_req->ra11, trx->bsic);
|
||||
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
return rc;
|
||||
}
|
||||
} else if (PRIM_IS_RACH8(lchan->prim)) {
|
||||
req = (struct l1ctl_rach_req *) lchan->prim->payload;
|
||||
synch_seq = RACH_SYNCH_SEQ_TS0;
|
||||
|
||||
/* Delay sending according to offset value */
|
||||
if (req->offset-- > 0)
|
||||
return 0;
|
||||
|
||||
/* Encode regular (8-bit) payload */
|
||||
rc = gsm0503_rach_ext_encode(payload, req->ra, trx->bsic, false);
|
||||
if (rc) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Could not encode RACH burst "
|
||||
"(ra=%u bsic=%u)\n", req->ra, trx->bsic);
|
||||
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
return rc;
|
||||
}
|
||||
} else {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu (expected %zu or %zu), "
|
||||
"so dropping...\n", lchan->prim->payload_len,
|
||||
sizeof(*req), sizeof(*ext_req));
|
||||
sched_prim_drop(lchan);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
||||
/* BN0-7: extended tail bits */
|
||||
memcpy(burst_ptr, rach_ext_tail_bits, RACH_EXT_TAIL_BITS_LEN);
|
||||
burst_ptr += RACH_EXT_TAIL_BITS_LEN;
|
||||
|
||||
/* BN8-48: chosen synch. (training) sequence */
|
||||
for (i = 0; i < RACH_SYNCH_SEQ_LEN; i++)
|
||||
*(burst_ptr++) = rach_synch_seq_bits[synch_seq][i] == '1';
|
||||
|
||||
/* BN49-84: encrypted bits (the payload) */
|
||||
memcpy(burst_ptr, payload, RACH_PAYLOAD_LEN);
|
||||
burst_ptr += RACH_PAYLOAD_LEN;
|
||||
|
||||
/* BN85-156: tail bits & extended guard period */
|
||||
memset(burst_ptr, 0, burst + GSM_BURST_LEN - burst_ptr);
|
||||
|
||||
LOGP(DSCHD, LOGL_NOTICE, "Transmitting %s RACH (%s) on fn=%u, tn=%u, lchan=%s\n",
|
||||
PRIM_IS_RACH11(lchan->prim) ? "extended (11-bit)" : "regular (8-bit)",
|
||||
get_value_string(rach_synch_seq_names, synch_seq), fn,
|
||||
ts->index, trx_lchan_desc[lchan->type].name);
|
||||
|
||||
/* Forward burst to scheduler */
|
||||
rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
|
||||
if (rc) {
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Confirm RACH request */
|
||||
l1ctl_tx_rach_conf(trx->l1l, trx->band_arfcn, fn);
|
||||
|
||||
/* Optional GSMTAP logging */
|
||||
sched_gsmtap_send(lchan->type, fn, ts->index,
|
||||
trx->band_arfcn | ARFCN_UPLINK, 0, 0,
|
||||
PRIM_IS_RACH11(lchan->prim) ? (uint8_t *) &ext_req->ra11 : &req->ra,
|
||||
PRIM_IS_RACH11(lchan->prim) ? 2 : 1);
|
||||
|
||||
/* Forget processed primitive */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: handlers for DL / UL bursts on logical channels
|
||||
*
|
||||
* (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <talloc.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
|
||||
#include "l1ctl_proto.h"
|
||||
#include "scheduler.h"
|
||||
#include "sched_trx.h"
|
||||
#include "logging.h"
|
||||
#include "trx_if.h"
|
||||
#include "l1ctl.h"
|
||||
|
||||
__attribute__((xray_always_instrument)) __attribute__((noinline))
|
||||
static int gsm0503_sch_decode_xray(uint8_t *sb_info, const sbit_t *burst)
|
||||
{
|
||||
return gsm0503_sch_decode(sb_info, burst);
|
||||
}
|
||||
|
||||
static void decode_sb(struct gsm_time *time, uint8_t *bsic, uint8_t *sb_info)
|
||||
{
|
||||
uint8_t t3p;
|
||||
uint32_t sb;
|
||||
|
||||
sb = ((uint32_t)sb_info[3] << 24)
|
||||
| (sb_info[2] << 16)
|
||||
| (sb_info[1] << 8)
|
||||
| sb_info[0];
|
||||
|
||||
*bsic = (sb >> 2) & 0x3f;
|
||||
|
||||
/* TS 05.02 Chapter 3.3.2.2.1 SCH Frame Numbers */
|
||||
time->t1 = ((sb >> 23) & 0x01)
|
||||
| ((sb >> 7) & 0x1fe)
|
||||
| ((sb << 9) & 0x600);
|
||||
|
||||
time->t2 = (sb >> 18) & 0x1f;
|
||||
|
||||
t3p = ((sb >> 24) & 0x01) | ((sb >> 15) & 0x06);
|
||||
time->t3 = t3p * 10 + 1;
|
||||
|
||||
/* TS 05.02 Chapter 4.3.3 TDMA frame number */
|
||||
time->fn = gsm_gsmtime2fn(time);
|
||||
}
|
||||
|
||||
int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
|
||||
const sbit_t *bits, const struct trx_meas_set *meas)
|
||||
{
|
||||
sbit_t payload[2 * 39];
|
||||
struct gsm_time time;
|
||||
uint8_t sb_info[4];
|
||||
uint8_t bsic;
|
||||
int rc;
|
||||
|
||||
/* Obtain payload from burst */
|
||||
memcpy(payload, bits + 3, 39);
|
||||
memcpy(payload + 39, bits + 3 + 39 + 64, 39);
|
||||
|
||||
/* Attempt to decode */
|
||||
rc = gsm0503_sch_decode_xray(sb_info, payload);
|
||||
if (rc) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Received bad SCH burst at fn=%u\n", fn);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Decode BSIC and TDMA frame number */
|
||||
decode_sb(&time, &bsic, sb_info);
|
||||
|
||||
LOGP(DSCHD, LOGL_DEBUG, "Received SCH: bsic=%u, fn=%u, sched_fn=%u\n",
|
||||
bsic, time.fn, trx->sched.fn_counter_proc);
|
||||
|
||||
/* Check if decoded frame number matches */
|
||||
if (time.fn != fn) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Decoded fn=%u does not match "
|
||||
"fn=%u provided by scheduler\n", time.fn, fn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* We don't need to send L1CTL_FBSB_CONF */
|
||||
if (trx->l1l->fbsb_conf_sent)
|
||||
return 0;
|
||||
|
||||
/* Send L1CTL_FBSB_CONF to higher layers */
|
||||
struct l1ctl_info_dl *data;
|
||||
data = talloc_zero_size(ts, sizeof(struct l1ctl_info_dl));
|
||||
if (data == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Fill in some downlink info */
|
||||
data->chan_nr = trx_lchan_desc[lchan->type].chan_nr | ts->index;
|
||||
data->link_id = trx_lchan_desc[lchan->type].link_id;
|
||||
data->band_arfcn = htons(trx->band_arfcn);
|
||||
data->frame_nr = htonl(fn);
|
||||
data->rx_level = -(meas->rssi);
|
||||
|
||||
/* FIXME: set proper values */
|
||||
data->num_biterr = 0;
|
||||
data->fire_crc = 0;
|
||||
data->snr = 0;
|
||||
|
||||
l1ctl_tx_fbsb_conf(trx->l1l, 0, data, bsic);
|
||||
|
||||
/* Update BSIC value of trx_instance */
|
||||
trx->bsic = bsic;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,303 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: handlers for DL / UL bursts on logical channels
|
||||
*
|
||||
* (C) 2017-2020 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
#include <osmocom/codec/codec.h>
|
||||
|
||||
#include "l1ctl_proto.h"
|
||||
#include "scheduler.h"
|
||||
#include "sched_trx.h"
|
||||
#include "logging.h"
|
||||
#include "trx_if.h"
|
||||
#include "l1ctl.h"
|
||||
|
||||
int rx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
|
||||
const sbit_t *bits, const struct trx_meas_set *meas)
|
||||
{
|
||||
const struct trx_lchan_desc *lchan_desc;
|
||||
int n_errors = -1, n_bits_total, rc;
|
||||
sbit_t *buffer, *offset;
|
||||
uint8_t l2[128], *mask;
|
||||
size_t l2_len;
|
||||
|
||||
/* Set up pointers */
|
||||
lchan_desc = &trx_lchan_desc[lchan->type];
|
||||
mask = &lchan->rx_burst_mask;
|
||||
buffer = lchan->rx_bursts;
|
||||
|
||||
LOGP(DSCHD, LOGL_DEBUG, "Traffic received on %s: fn=%u ts=%u bid=%u\n",
|
||||
lchan_desc->name, fn, ts->index, bid);
|
||||
|
||||
/* Align to the first burst of a block */
|
||||
if (*mask == 0x00 && bid != 0)
|
||||
return 0;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* Store the measurements */
|
||||
sched_trx_meas_push(lchan, meas);
|
||||
|
||||
/* Copy burst to end of buffer of 8 bursts */
|
||||
offset = buffer + bid * 116 + 464;
|
||||
memcpy(offset, bits + 3, 58);
|
||||
memcpy(offset + 58, bits + 87, 58);
|
||||
|
||||
/* Wait until complete set of bursts */
|
||||
if (bid != 3)
|
||||
return 0;
|
||||
|
||||
/* Calculate AVG of the measurements */
|
||||
sched_trx_meas_avg(lchan, 8);
|
||||
|
||||
/* Check for complete set of bursts */
|
||||
if ((*mask & 0xff) != 0xff) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Received incomplete (%s) traffic frame at "
|
||||
"fn=%u (%u/%u) for %s\n",
|
||||
burst_mask2str(mask, 8), lchan->meas_avg.fn,
|
||||
lchan->meas_avg.fn % ts->mf_layout->period,
|
||||
ts->mf_layout->period,
|
||||
lchan_desc->name);
|
||||
/* NOTE: do not abort here, give it a try. Maybe we're lucky ;) */
|
||||
|
||||
}
|
||||
|
||||
/* Keep the mask updated */
|
||||
*mask = *mask << 4;
|
||||
|
||||
switch (lchan->tch_mode) {
|
||||
case GSM48_CMODE_SIGN:
|
||||
case GSM48_CMODE_SPEECH_V1: /* FR */
|
||||
rc = gsm0503_tch_fr_decode(l2, buffer,
|
||||
1, 0, &n_errors, &n_bits_total);
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_EFR: /* EFR */
|
||||
rc = gsm0503_tch_fr_decode(l2, buffer,
|
||||
1, 1, &n_errors, &n_bits_total);
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* AMR */
|
||||
/**
|
||||
* TODO: AMR requires a dedicated loop,
|
||||
* which will be implemented later...
|
||||
*/
|
||||
LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet\n");
|
||||
return -ENOTSUP;
|
||||
default:
|
||||
LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Shift buffer by 4 bursts for interleaving */
|
||||
memcpy(buffer, buffer + 464, 464);
|
||||
|
||||
/* Check decoding result */
|
||||
if (rc < 4) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Received bad TCH frame ending at "
|
||||
"fn=%u for %s\n", fn, lchan_desc->name);
|
||||
|
||||
/* Send BFI */
|
||||
goto bfi;
|
||||
} else if (rc == GSM_MACBLOCK_LEN) {
|
||||
/* FACCH received, forward it to the higher layers */
|
||||
sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
|
||||
n_errors, false, false);
|
||||
|
||||
/* Send BFI substituting a stolen TCH frame */
|
||||
n_errors = -1; /* ensure fake measurements */
|
||||
goto bfi;
|
||||
} else {
|
||||
/* A good TCH frame received */
|
||||
l2_len = rc;
|
||||
}
|
||||
|
||||
/* Send a traffic frame to the higher layers */
|
||||
return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
|
||||
n_errors, false, true);
|
||||
|
||||
bfi:
|
||||
/* Didn't try to decode, fake measurements */
|
||||
if (n_errors < 0) {
|
||||
lchan->meas_avg = (struct trx_meas_set) {
|
||||
.fn = lchan->meas_avg.fn,
|
||||
.toa256 = 0,
|
||||
.rssi = -110,
|
||||
};
|
||||
|
||||
/* No bursts => no errors */
|
||||
n_errors = 0;
|
||||
}
|
||||
|
||||
/* BFI is not applicable in signalling mode */
|
||||
if (lchan->tch_mode == GSM48_CMODE_SIGN)
|
||||
return sched_send_dt_ind(trx, ts, lchan, NULL, 0,
|
||||
n_errors, true, false);
|
||||
|
||||
/* Bad frame indication */
|
||||
l2_len = sched_bad_frame_ind(l2, lchan);
|
||||
|
||||
/* Send a BFI frame to the higher layers */
|
||||
return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
|
||||
n_errors, true, true);
|
||||
}
|
||||
|
||||
int tx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
|
||||
{
|
||||
const struct trx_lchan_desc *lchan_desc;
|
||||
ubit_t burst[GSM_BURST_LEN];
|
||||
ubit_t *buffer, *offset;
|
||||
const uint8_t *tsc;
|
||||
uint8_t *mask;
|
||||
size_t l2_len;
|
||||
int rc;
|
||||
|
||||
/* Set up pointers */
|
||||
lchan_desc = &trx_lchan_desc[lchan->type];
|
||||
mask = &lchan->tx_burst_mask;
|
||||
buffer = lchan->tx_bursts;
|
||||
|
||||
/* If we have encoded bursts */
|
||||
if (*mask)
|
||||
goto send_burst;
|
||||
|
||||
/* Wait until a first burst in period */
|
||||
if (bid > 0)
|
||||
return 0;
|
||||
|
||||
/* Check the current TCH mode */
|
||||
switch (lchan->tch_mode) {
|
||||
case GSM48_CMODE_SIGN:
|
||||
case GSM48_CMODE_SPEECH_V1: /* FR */
|
||||
l2_len = GSM_FR_BYTES;
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_EFR: /* EFR */
|
||||
l2_len = GSM_EFR_BYTES;
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* AMR */
|
||||
/**
|
||||
* TODO: AMR requires a dedicated loop,
|
||||
* which will be implemented later...
|
||||
*/
|
||||
LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet, "
|
||||
"dropping frame...\n");
|
||||
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
return -ENOTSUP;
|
||||
default:
|
||||
LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u, "
|
||||
"dropping frame...\n", lchan->tch_mode);
|
||||
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Determine and check the payload length */
|
||||
if (lchan->prim->payload_len == GSM_MACBLOCK_LEN) {
|
||||
l2_len = GSM_MACBLOCK_LEN; /* FACCH */
|
||||
} else if (lchan->prim->payload_len != l2_len) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu "
|
||||
"(expected %zu for TCH or %u for FACCH), so dropping...\n",
|
||||
lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
|
||||
|
||||
sched_prim_drop(lchan);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Shift buffer by 4 bursts back for interleaving */
|
||||
memcpy(buffer, buffer + 464, 464);
|
||||
|
||||
/* Encode payload */
|
||||
rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, l2_len, 1);
|
||||
if (rc) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
|
||||
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
|
||||
lchan->prim->payload_len));
|
||||
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
send_burst:
|
||||
/* Determine which burst should be sent */
|
||||
offset = buffer + bid * 116;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* Choose proper TSC */
|
||||
tsc = sched_nb_training_bits[trx->tsc];
|
||||
|
||||
/* Compose a new burst */
|
||||
memset(burst, 0, 3); /* TB */
|
||||
memcpy(burst + 3, offset, 58); /* Payload 1/2 */
|
||||
memcpy(burst + 61, tsc, 26); /* TSC */
|
||||
memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
|
||||
memset(burst + 145, 0, 3); /* TB */
|
||||
|
||||
LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
|
||||
lchan_desc->name, fn, ts->index, bid);
|
||||
|
||||
/* Forward burst to scheduler */
|
||||
rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
|
||||
if (rc) {
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
/* Reset mask */
|
||||
*mask = 0x00;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* If we have sent the last (4/4) burst */
|
||||
if (*mask == 0x0f) {
|
||||
/* Confirm data / traffic sending */
|
||||
sched_send_dt_conf(trx, ts, lchan, fn, PRIM_IS_TCH(lchan->prim));
|
||||
|
||||
/* Forget processed primitive */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
/* Reset mask */
|
||||
*mask = 0x00;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,501 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: handlers for DL / UL bursts on logical channels
|
||||
*
|
||||
* (C) 2018-2020 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
* (C) 2018 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
#include <osmocom/codec/codec.h>
|
||||
|
||||
#include "l1ctl_proto.h"
|
||||
#include "scheduler.h"
|
||||
#include "sched_trx.h"
|
||||
#include "logging.h"
|
||||
#include "trx_if.h"
|
||||
#include "l1ctl.h"
|
||||
|
||||
static const uint8_t tch_h0_traffic_block_map[3][4] = {
|
||||
/* B0(0,2,4,6), B1(4,6,8,10), B2(8,10,0,2) */
|
||||
{ 0, 2, 4, 6 },
|
||||
{ 4, 6, 8, 10 },
|
||||
{ 8, 10, 0, 2 },
|
||||
};
|
||||
|
||||
static const uint8_t tch_h1_traffic_block_map[3][4] = {
|
||||
/* B0(1,3,5,7), B1(5,7,9,11), B2(9,11,1,3) */
|
||||
{ 1, 3, 5, 7 },
|
||||
{ 5, 7, 9, 11 },
|
||||
{ 9, 11, 1, 3 },
|
||||
};
|
||||
|
||||
static const uint8_t tch_h0_dl_facch_block_map[3][6] = {
|
||||
/* B0(4,6,8,10,13,15), B1(13,15,17,19,21,23), B2(21,23,0,2,4,6) */
|
||||
{ 4, 6, 8, 10, 13, 15 },
|
||||
{ 13, 15, 17, 19, 21, 23 },
|
||||
{ 21, 23, 0, 2, 4, 6 },
|
||||
};
|
||||
|
||||
static const uint8_t tch_h0_ul_facch_block_map[3][6] = {
|
||||
/* B0(0,2,4,6,8,10), B1(8,10,13,15,17,19), B2(17,19,21,23,0,2) */
|
||||
{ 0, 2, 4, 6, 8, 10 },
|
||||
{ 8, 10, 13, 15, 17, 19 },
|
||||
{ 17, 19, 21, 23, 0, 2 },
|
||||
};
|
||||
|
||||
static const uint8_t tch_h1_dl_facch_block_map[3][6] = {
|
||||
/* B0(5,7,9,11,14,16), B1(14,16,18,20,22,24), B2(22,24,1,3,5,7) */
|
||||
{ 5, 7, 9, 11, 14, 16 },
|
||||
{ 14, 16, 18, 20, 22, 24 },
|
||||
{ 22, 24, 1, 3, 5, 7 },
|
||||
};
|
||||
|
||||
const uint8_t tch_h1_ul_facch_block_map[3][6] = {
|
||||
/* B0(1,3,5,7,9,11), B1(9,11,14,16,18,20), B2(18,20,22,24,1,3) */
|
||||
{ 1, 3, 5, 7, 9, 11 },
|
||||
{ 9, 11, 14, 16, 18, 20 },
|
||||
{ 18, 20, 22, 24, 1, 3 },
|
||||
};
|
||||
|
||||
/**
|
||||
* Can a TCH/H block transmission be initiated / finished
|
||||
* on a given frame number and a given channel type?
|
||||
*
|
||||
* See GSM 05.02, clause 7, table 1
|
||||
*
|
||||
* @param chan channel type (TRXC_TCHH_0 or TRXC_TCHH_1)
|
||||
* @param fn the current frame number
|
||||
* @param ul Uplink or Downlink?
|
||||
* @param facch FACCH/H or traffic?
|
||||
* @param start init or end of transmission?
|
||||
* @return true (yes) or false (no)
|
||||
*/
|
||||
bool sched_tchh_block_map_fn(enum trx_lchan_type chan,
|
||||
uint32_t fn, bool ul, bool facch, bool start)
|
||||
{
|
||||
uint8_t fn_mf;
|
||||
int i = 0;
|
||||
|
||||
/* Just to be sure */
|
||||
OSMO_ASSERT(chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1);
|
||||
|
||||
/* Calculate a modulo */
|
||||
fn_mf = facch ? (fn % 26) : (fn % 13);
|
||||
|
||||
#define MAP_GET_POS(map) \
|
||||
(start ? 0 : ARRAY_SIZE(map[i]) - 1)
|
||||
|
||||
#define BLOCK_MAP_FN(map) \
|
||||
do { \
|
||||
if (map[i][MAP_GET_POS(map)] == fn_mf) \
|
||||
return true; \
|
||||
} while (++i < ARRAY_SIZE(map))
|
||||
|
||||
/* Choose a proper block map */
|
||||
if (facch) {
|
||||
if (ul) {
|
||||
if (chan == TRXC_TCHH_0)
|
||||
BLOCK_MAP_FN(tch_h0_ul_facch_block_map);
|
||||
else
|
||||
BLOCK_MAP_FN(tch_h1_ul_facch_block_map);
|
||||
} else {
|
||||
if (chan == TRXC_TCHH_0)
|
||||
BLOCK_MAP_FN(tch_h0_dl_facch_block_map);
|
||||
else
|
||||
BLOCK_MAP_FN(tch_h1_dl_facch_block_map);
|
||||
}
|
||||
} else {
|
||||
if (chan == TRXC_TCHH_0)
|
||||
BLOCK_MAP_FN(tch_h0_traffic_block_map);
|
||||
else
|
||||
BLOCK_MAP_FN(tch_h1_traffic_block_map);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a frame number of the first burst
|
||||
* using given frame number of the last burst.
|
||||
*
|
||||
* See GSM 05.02, clause 7, table 1
|
||||
*
|
||||
* @param chan channel type (TRXC_TCHH_0 or TRXC_TCHH_1)
|
||||
* @param last_fn frame number of the last burst
|
||||
* @param facch FACCH/H or traffic?
|
||||
* @return either frame number of the first burst,
|
||||
* or fn=last_fn if calculation failed
|
||||
*/
|
||||
uint32_t sched_tchh_block_dl_first_fn(enum trx_lchan_type chan,
|
||||
uint32_t last_fn, bool facch)
|
||||
{
|
||||
uint8_t fn_mf, fn_diff;
|
||||
int i = 0;
|
||||
|
||||
/* Just to be sure */
|
||||
OSMO_ASSERT(chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1);
|
||||
|
||||
/* Calculate a modulo */
|
||||
fn_mf = facch ? (last_fn % 26) : (last_fn % 13);
|
||||
|
||||
#define BLOCK_FIRST_FN(map) \
|
||||
do { \
|
||||
if (map[i][ARRAY_SIZE(map[i]) - 1] == fn_mf) { \
|
||||
fn_diff = GSM_TDMA_FN_DIFF(fn_mf, map[i][0]); \
|
||||
return GSM_TDMA_FN_SUB(last_fn, fn_diff); \
|
||||
} \
|
||||
} while (++i < ARRAY_SIZE(map))
|
||||
|
||||
/* Choose a proper block map */
|
||||
if (facch) {
|
||||
if (chan == TRXC_TCHH_0)
|
||||
BLOCK_FIRST_FN(tch_h0_dl_facch_block_map);
|
||||
else
|
||||
BLOCK_FIRST_FN(tch_h1_dl_facch_block_map);
|
||||
} else {
|
||||
if (chan == TRXC_TCHH_0)
|
||||
BLOCK_FIRST_FN(tch_h0_traffic_block_map);
|
||||
else
|
||||
BLOCK_FIRST_FN(tch_h1_traffic_block_map);
|
||||
}
|
||||
|
||||
LOGP(DSCHD, LOGL_ERROR, "Failed to calculate TDMA "
|
||||
"frame number of the first burst of %s block, "
|
||||
"using the current fn=%u\n", facch ?
|
||||
"FACCH/H" : "TCH/H", last_fn);
|
||||
|
||||
/* Couldn't calculate the first fn, return the last */
|
||||
return last_fn;
|
||||
}
|
||||
|
||||
int rx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
|
||||
const sbit_t *bits, const struct trx_meas_set *meas)
|
||||
{
|
||||
const struct trx_lchan_desc *lchan_desc;
|
||||
int n_errors = -1, n_bits_total, rc;
|
||||
sbit_t *buffer, *offset;
|
||||
uint8_t l2[128], *mask;
|
||||
size_t l2_len;
|
||||
|
||||
/* Set up pointers */
|
||||
lchan_desc = &trx_lchan_desc[lchan->type];
|
||||
mask = &lchan->rx_burst_mask;
|
||||
buffer = lchan->rx_bursts;
|
||||
|
||||
LOGP(DSCHD, LOGL_DEBUG, "Traffic received on %s: fn=%u ts=%u bid=%u\n",
|
||||
lchan_desc->name, fn, ts->index, bid);
|
||||
|
||||
if (*mask == 0x00) {
|
||||
/* Align to the first burst */
|
||||
if (bid > 0)
|
||||
return 0;
|
||||
|
||||
/* Align reception of the first FACCH/H frame */
|
||||
if (lchan->tch_mode == GSM48_CMODE_SIGN) {
|
||||
if (!sched_tchh_facch_start(lchan->type, fn, 0))
|
||||
return 0;
|
||||
} else { /* or TCH/H traffic frame */
|
||||
if (!sched_tchh_traffic_start(lchan->type, fn, 0))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* Store the measurements */
|
||||
sched_trx_meas_push(lchan, meas);
|
||||
|
||||
/* Copy burst to the end of buffer of 6 bursts */
|
||||
offset = buffer + bid * 116 + 464;
|
||||
memcpy(offset, bits + 3, 58);
|
||||
memcpy(offset + 58, bits + 87, 58);
|
||||
|
||||
/* Wait until the second burst */
|
||||
if (bid != 1)
|
||||
return 0;
|
||||
|
||||
/* Wait for complete set of bursts */
|
||||
if (lchan->tch_mode == GSM48_CMODE_SIGN) {
|
||||
/* FACCH/H is interleaved over 6 bursts */
|
||||
if ((*mask & 0x3f) != 0x3f)
|
||||
goto bfi_shift;
|
||||
} else {
|
||||
/* Traffic is interleaved over 4 bursts */
|
||||
if ((*mask & 0x0f) != 0x0f)
|
||||
goto bfi_shift;
|
||||
}
|
||||
|
||||
/* Skip decoding attempt in case of FACCH/H */
|
||||
if (lchan->dl_ongoing_facch) {
|
||||
lchan->dl_ongoing_facch = false;
|
||||
goto bfi_shift; /* 2/2 BFI */
|
||||
}
|
||||
|
||||
switch (lchan->tch_mode) {
|
||||
case GSM48_CMODE_SIGN:
|
||||
case GSM48_CMODE_SPEECH_V1: /* HR */
|
||||
rc = gsm0503_tch_hr_decode(l2, buffer,
|
||||
!sched_tchh_facch_end(lchan->type, fn, 0),
|
||||
&n_errors, &n_bits_total);
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* AMR */
|
||||
/**
|
||||
* TODO: AMR requires a dedicated loop,
|
||||
* which will be implemented later...
|
||||
*/
|
||||
LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet\n");
|
||||
return -ENOTSUP;
|
||||
default:
|
||||
LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Shift buffer by 4 bursts for interleaving */
|
||||
memcpy(buffer, buffer + 232, 232);
|
||||
memcpy(buffer + 232, buffer + 464, 232);
|
||||
|
||||
/* Shift burst mask */
|
||||
*mask = *mask << 2;
|
||||
|
||||
/* Check decoding result */
|
||||
if (rc < 4) {
|
||||
/* Calculate AVG of the measurements (assuming 4 bursts) */
|
||||
sched_trx_meas_avg(lchan, 4);
|
||||
|
||||
LOGP(DSCHD, LOGL_ERROR, "Received bad TCH frame (%s) "
|
||||
"at fn=%u on %s (rc=%d)\n", burst_mask2str(mask, 6),
|
||||
lchan->meas_avg.fn, lchan_desc->name, rc);
|
||||
|
||||
/* Send BFI */
|
||||
goto bfi;
|
||||
} else if (rc == GSM_MACBLOCK_LEN) {
|
||||
/* Skip decoding of the next 2 stolen bursts */
|
||||
lchan->dl_ongoing_facch = true;
|
||||
|
||||
/* Calculate AVG of the measurements (FACCH/H takes 6 bursts) */
|
||||
sched_trx_meas_avg(lchan, 6);
|
||||
|
||||
/* FACCH/H received, forward to the higher layers */
|
||||
sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
|
||||
n_errors, false, false);
|
||||
|
||||
/* Send BFI substituting 1/2 stolen TCH frames */
|
||||
n_errors = -1; /* ensure fake measurements */
|
||||
goto bfi;
|
||||
} else {
|
||||
/* A good TCH frame received */
|
||||
l2_len = rc;
|
||||
|
||||
/* Calculate AVG of the measurements (traffic takes 4 bursts) */
|
||||
sched_trx_meas_avg(lchan, 4);
|
||||
}
|
||||
|
||||
/* Send a traffic frame to the higher layers */
|
||||
return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
|
||||
n_errors, false, true);
|
||||
|
||||
bfi_shift:
|
||||
/* Shift buffer */
|
||||
memcpy(buffer, buffer + 232, 232);
|
||||
memcpy(buffer + 232, buffer + 464, 232);
|
||||
|
||||
/* Shift burst mask */
|
||||
*mask = *mask << 2;
|
||||
|
||||
bfi:
|
||||
/* Didn't try to decode, fake measurements */
|
||||
if (n_errors < 0) {
|
||||
lchan->meas_avg = (struct trx_meas_set) {
|
||||
.fn = sched_tchh_block_dl_first_fn(lchan->type, fn, false),
|
||||
.toa256 = 0,
|
||||
.rssi = -110,
|
||||
};
|
||||
|
||||
/* No bursts => no errors */
|
||||
n_errors = 0;
|
||||
}
|
||||
|
||||
/* BFI is not applicable in signalling mode */
|
||||
if (lchan->tch_mode == GSM48_CMODE_SIGN)
|
||||
return sched_send_dt_ind(trx, ts, lchan, NULL, 0,
|
||||
n_errors, true, false);
|
||||
|
||||
/* Bad frame indication */
|
||||
l2_len = sched_bad_frame_ind(l2, lchan);
|
||||
|
||||
/* Send a BFI frame to the higher layers */
|
||||
return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
|
||||
n_errors, true, true);
|
||||
}
|
||||
|
||||
int tx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
|
||||
{
|
||||
const struct trx_lchan_desc *lchan_desc;
|
||||
ubit_t burst[GSM_BURST_LEN];
|
||||
ubit_t *buffer, *offset;
|
||||
const uint8_t *tsc;
|
||||
uint8_t *mask;
|
||||
size_t l2_len;
|
||||
int rc;
|
||||
|
||||
/* Set up pointers */
|
||||
lchan_desc = &trx_lchan_desc[lchan->type];
|
||||
mask = &lchan->tx_burst_mask;
|
||||
buffer = lchan->tx_bursts;
|
||||
|
||||
if (bid > 0) {
|
||||
/* Align to the first burst */
|
||||
if (*mask == 0x00)
|
||||
return 0;
|
||||
goto send_burst;
|
||||
}
|
||||
|
||||
if (*mask == 0x00) {
|
||||
/* Align transmission of the first FACCH/H frame */
|
||||
if (lchan->tch_mode == GSM48_CMODE_SIGN)
|
||||
if (!sched_tchh_facch_start(lchan->type, fn, 1))
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Shift buffer by 2 bursts back for interleaving */
|
||||
memcpy(buffer, buffer + 232, 232);
|
||||
|
||||
/* Also shift TX burst mask */
|
||||
*mask = *mask << 2;
|
||||
|
||||
/* If FACCH/H blocks are still pending */
|
||||
if (lchan->ul_facch_blocks > 2) {
|
||||
memcpy(buffer + 232, buffer + 464, 232);
|
||||
goto send_burst;
|
||||
}
|
||||
|
||||
/* Check the current TCH mode */
|
||||
switch (lchan->tch_mode) {
|
||||
case GSM48_CMODE_SIGN:
|
||||
case GSM48_CMODE_SPEECH_V1: /* HR */
|
||||
l2_len = GSM_HR_BYTES + 1;
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* AMR */
|
||||
/**
|
||||
* TODO: AMR requires a dedicated loop,
|
||||
* which will be implemented later...
|
||||
*/
|
||||
LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet, "
|
||||
"dropping frame...\n");
|
||||
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
return -ENOTSUP;
|
||||
default:
|
||||
LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u, "
|
||||
"dropping frame...\n", lchan->tch_mode);
|
||||
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Determine payload length */
|
||||
if (PRIM_IS_FACCH(lchan->prim)) {
|
||||
l2_len = GSM_MACBLOCK_LEN; /* FACCH */
|
||||
} else if (lchan->prim->payload_len != l2_len) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu "
|
||||
"(expected %zu for TCH or %u for FACCH), so dropping...\n",
|
||||
lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
|
||||
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Encode the payload */
|
||||
rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, l2_len);
|
||||
if (rc) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
|
||||
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
|
||||
lchan->prim->payload_len));
|
||||
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* A FACCH/H frame occupies 6 bursts */
|
||||
if (PRIM_IS_FACCH(lchan->prim))
|
||||
lchan->ul_facch_blocks = 6;
|
||||
|
||||
send_burst:
|
||||
/* Determine which burst should be sent */
|
||||
offset = buffer + bid * 116;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* Choose proper TSC */
|
||||
tsc = sched_nb_training_bits[trx->tsc];
|
||||
|
||||
/* Compose a new burst */
|
||||
memset(burst, 0, 3); /* TB */
|
||||
memcpy(burst + 3, offset, 58); /* Payload 1/2 */
|
||||
memcpy(burst + 61, tsc, 26); /* TSC */
|
||||
memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
|
||||
memset(burst + 145, 0, 3); /* TB */
|
||||
|
||||
LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
|
||||
lchan_desc->name, fn, ts->index, bid);
|
||||
|
||||
/* Forward burst to transceiver */
|
||||
sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
|
||||
|
||||
/* In case of a FACCH/H frame, one block less */
|
||||
if (lchan->ul_facch_blocks)
|
||||
lchan->ul_facch_blocks--;
|
||||
|
||||
if ((*mask & 0x0f) == 0x0f) {
|
||||
/**
|
||||
* If no more FACCH/H blocks pending,
|
||||
* confirm data / traffic sending
|
||||
*/
|
||||
if (!lchan->ul_facch_blocks)
|
||||
sched_send_dt_conf(trx, ts, lchan, fn,
|
||||
PRIM_IS_TCH(lchan->prim));
|
||||
|
||||
/* Forget processed primitive */
|
||||
sched_prim_drop(lchan);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: handlers for DL / UL bursts on logical channels
|
||||
*
|
||||
* (C) 2017-2020 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
|
||||
#include "l1ctl_proto.h"
|
||||
#include "scheduler.h"
|
||||
#include "sched_trx.h"
|
||||
#include "logging.h"
|
||||
#include "trx_if.h"
|
||||
#include "l1ctl.h"
|
||||
|
||||
__attribute__((xray_always_instrument)) __attribute__((noinline))
|
||||
static int gsm0503_xcch_decode_xray(uint8_t *l2_data, const sbit_t *bursts,
|
||||
int *n_errors, int *n_bits_total) {
|
||||
return gsm0503_xcch_decode(l2_data, bursts, n_errors, n_bits_total);
|
||||
}
|
||||
|
||||
__attribute__((xray_always_instrument)) __attribute__((noinline)) int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
|
||||
const sbit_t *bits, const struct trx_meas_set *meas)
|
||||
{
|
||||
const struct trx_lchan_desc *lchan_desc;
|
||||
uint8_t l2[GSM_MACBLOCK_LEN], *mask;
|
||||
int n_errors, n_bits_total, rc;
|
||||
sbit_t *buffer, *offset;
|
||||
|
||||
/* Set up pointers */
|
||||
lchan_desc = &trx_lchan_desc[lchan->type];
|
||||
mask = &lchan->rx_burst_mask;
|
||||
buffer = lchan->rx_bursts;
|
||||
|
||||
LOGP(DSCHD, LOGL_DEBUG, "Data received on %s: fn=%u ts=%u bid=%u\n",
|
||||
lchan_desc->name, fn, ts->index, bid);
|
||||
|
||||
/* Align to the first burst of a block */
|
||||
if (*mask == 0x00 && bid != 0)
|
||||
return 0;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* Store the measurements */
|
||||
sched_trx_meas_push(lchan, meas);
|
||||
|
||||
/* Copy burst to buffer of 4 bursts */
|
||||
offset = buffer + bid * 116;
|
||||
memcpy(offset, bits + 3, 58);
|
||||
memcpy(offset + 58, bits + 87, 58);
|
||||
|
||||
/* Wait until complete set of bursts */
|
||||
if (bid != 3)
|
||||
return 0;
|
||||
|
||||
/* Calculate AVG of the measurements */
|
||||
sched_trx_meas_avg(lchan, 4);
|
||||
|
||||
/* Check for complete set of bursts */
|
||||
if ((*mask & 0xf) != 0xf) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Received incomplete (%s) data frame at "
|
||||
"fn=%u (%u/%u) for %s\n",
|
||||
burst_mask2str(mask, 4), lchan->meas_avg.fn,
|
||||
lchan->meas_avg.fn % ts->mf_layout->period,
|
||||
ts->mf_layout->period,
|
||||
lchan_desc->name);
|
||||
/* NOTE: xCCH has an insane amount of redundancy for error
|
||||
* correction, so even just 2 valid bursts might be enough
|
||||
* to reconstruct some L2 frames. This is why we do not
|
||||
* abort here. */
|
||||
}
|
||||
|
||||
/* Keep the mask updated */
|
||||
*mask = *mask << 4;
|
||||
|
||||
/* Attempt to decode */
|
||||
rc = gsm0503_xcch_decode_xray(l2, buffer, &n_errors, &n_bits_total);
|
||||
if (rc) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Received bad data frame with %d errors at fn=%u "
|
||||
"(%u/%u) for %s\n", n_errors, lchan->meas_avg.fn,
|
||||
lchan->meas_avg.fn % ts->mf_layout->period,
|
||||
ts->mf_layout->period,
|
||||
lchan_desc->name);
|
||||
|
||||
/**
|
||||
* We should anyway send dummy frame for
|
||||
* proper measurement reporting...
|
||||
*/
|
||||
return sched_send_dt_ind(trx, ts, lchan, NULL, 0,
|
||||
n_errors, true, false);
|
||||
}
|
||||
|
||||
/* Send a L2 frame to the higher layers */
|
||||
return sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
|
||||
n_errors, false, false);
|
||||
}
|
||||
|
||||
int tx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
|
||||
{
|
||||
const struct trx_lchan_desc *lchan_desc;
|
||||
ubit_t burst[GSM_BURST_LEN];
|
||||
ubit_t *buffer, *offset;
|
||||
const uint8_t *tsc;
|
||||
uint8_t *mask;
|
||||
int rc;
|
||||
|
||||
/* Set up pointers */
|
||||
lchan_desc = &trx_lchan_desc[lchan->type];
|
||||
mask = &lchan->tx_burst_mask;
|
||||
buffer = lchan->tx_bursts;
|
||||
|
||||
if (bid > 0) {
|
||||
/* If we have encoded bursts */
|
||||
if (*mask)
|
||||
goto send_burst;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check the prim payload length */
|
||||
if (lchan->prim->payload_len != GSM_MACBLOCK_LEN) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu (expected %u), "
|
||||
"so dropping...\n", lchan->prim->payload_len, GSM_MACBLOCK_LEN);
|
||||
|
||||
sched_prim_drop(lchan);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Encode payload */
|
||||
rc = gsm0503_xcch_encode(buffer, lchan->prim->payload);
|
||||
if (rc) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
|
||||
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
|
||||
lchan->prim->payload_len));
|
||||
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
send_burst:
|
||||
/* Determine which burst should be sent */
|
||||
offset = buffer + bid * 116;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* Choose proper TSC */
|
||||
tsc = sched_nb_training_bits[trx->tsc];
|
||||
|
||||
/* Compose a new burst */
|
||||
memset(burst, 0, 3); /* TB */
|
||||
memcpy(burst + 3, offset, 58); /* Payload 1/2 */
|
||||
memcpy(burst + 61, tsc, 26); /* TSC */
|
||||
memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
|
||||
memset(burst + 145, 0, 3); /* TB */
|
||||
|
||||
LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
|
||||
lchan_desc->name, fn, ts->index, bid);
|
||||
|
||||
/* Forward burst to scheduler */
|
||||
rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
|
||||
if (rc) {
|
||||
/* Forget this primitive */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
/* Reset mask */
|
||||
*mask = 0x00;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* If we have sent the last (4/4) burst */
|
||||
if ((*mask & 0x0f) == 0x0f) {
|
||||
/* Confirm data sending */
|
||||
sched_send_dt_conf(trx, ts, lchan, fn, false);
|
||||
|
||||
/* Forget processed primitive */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
/* Reset mask */
|
||||
*mask = 0x00;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,617 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: primitive management
|
||||
*
|
||||
* (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <talloc.h>
|
||||
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
|
||||
#include "scheduler.h"
|
||||
#include "sched_trx.h"
|
||||
#include "trx_if.h"
|
||||
#include "logging.h"
|
||||
|
||||
/**
|
||||
* Initializes a new primitive by allocating memory
|
||||
* and filling some meta-information (e.g. lchan type).
|
||||
*
|
||||
* @param ctx parent talloc context
|
||||
* @param prim external prim pointer (will point to the allocated prim)
|
||||
* @param pl_len prim payload length
|
||||
* @param chan_nr RSL channel description (used to set a proper chan)
|
||||
* @param link_id RSL link description (used to set a proper chan)
|
||||
* @return zero in case of success, otherwise a error number
|
||||
*/
|
||||
int sched_prim_init(void *ctx, struct trx_ts_prim **prim,
|
||||
size_t pl_len, uint8_t chan_nr, uint8_t link_id)
|
||||
{
|
||||
enum trx_lchan_type lchan_type;
|
||||
struct trx_ts_prim *new_prim;
|
||||
uint8_t len;
|
||||
|
||||
/* Determine lchan type */
|
||||
lchan_type = sched_trx_chan_nr2lchan_type(chan_nr, link_id);
|
||||
if (!lchan_type) {
|
||||
LOGP(DSCH, LOGL_ERROR, "Couldn't determine lchan type "
|
||||
"for chan_nr=%02x and link_id=%02x\n", chan_nr, link_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* How much memory do we need? */
|
||||
len = sizeof(struct trx_ts_prim); /* Primitive header */
|
||||
len += pl_len; /* Requested payload size */
|
||||
|
||||
/* Allocate a new primitive */
|
||||
new_prim = talloc_zero_size(ctx, len);
|
||||
if (new_prim == NULL) {
|
||||
LOGP(DSCH, LOGL_ERROR, "Failed to allocate memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Init primitive header */
|
||||
new_prim->payload_len = pl_len;
|
||||
new_prim->chan = lchan_type;
|
||||
|
||||
/* Set external pointer */
|
||||
*prim = new_prim;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a primitive to the end of transmit queue of a particular
|
||||
* timeslot, whose index is parsed from chan_nr.
|
||||
*
|
||||
* @param trx TRX instance
|
||||
* @param prim to be enqueued primitive
|
||||
* @param chan_nr RSL channel description
|
||||
* @return zero in case of success, otherwise a error number
|
||||
*/
|
||||
int sched_prim_push(struct trx_instance *trx,
|
||||
struct trx_ts_prim *prim, uint8_t chan_nr)
|
||||
{
|
||||
struct trx_ts *ts;
|
||||
uint8_t tn;
|
||||
|
||||
/* Determine TS index */
|
||||
tn = chan_nr & 0x7;
|
||||
|
||||
/* Check whether required timeslot is allocated and configured */
|
||||
ts = trx->ts_list[tn];
|
||||
if (ts == NULL || ts->mf_layout == NULL) {
|
||||
LOGP(DSCH, LOGL_ERROR, "Timeslot %u isn't configured\n", tn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change talloc context of primitive
|
||||
* from trx to the parent ts
|
||||
*/
|
||||
talloc_steal(ts, prim);
|
||||
|
||||
/* Add primitive to TS transmit queue */
|
||||
llist_add_tail(&prim->list, &ts->tx_prims);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes a new primitive using either cached (if populated),
|
||||
* or "dummy" Measurement Report message.
|
||||
*
|
||||
* @param lchan lchan to assign a primitive
|
||||
* @return SACCH primitive to be transmitted
|
||||
*/
|
||||
static struct trx_ts_prim *prim_compose_mr(struct trx_lchan_state *lchan)
|
||||
{
|
||||
struct trx_ts_prim *prim;
|
||||
uint8_t *mr_src_ptr;
|
||||
bool cached;
|
||||
int rc;
|
||||
|
||||
/* "Dummy" Measurement Report */
|
||||
static const uint8_t meas_rep_dummy[] = {
|
||||
/* L1 SACCH pseudo-header */
|
||||
0x0f, 0x00,
|
||||
|
||||
/* LAPDm header */
|
||||
0x01, 0x03, 0x49,
|
||||
|
||||
/* RR Management messages, Measurement Report */
|
||||
0x06, 0x15,
|
||||
|
||||
/* Measurement results (see 3GPP TS 44.018, section 10.5.2.20):
|
||||
* 0... .... = BA-USED: 0
|
||||
* .0.. .... = DTX-USED: DTX was not used
|
||||
* ..11 0110 = RXLEV-FULL-SERVING-CELL: -57 <= x < -56 dBm (54)
|
||||
* 0... .... = 3G-BA-USED: 0
|
||||
* .1.. .... = MEAS-VALID: The measurement results are not valid
|
||||
* ..11 0110 = RXLEV-SUB-SERVING-CELL: -57 <= x < -56 dBm (54)
|
||||
* 0... .... = SI23_BA_USED: 0
|
||||
* .000 .... = RXQUAL-FULL-SERVING-CELL: BER < 0.2%, Mean value 0.14% (0)
|
||||
* .... 000. = RXQUAL-SUB-SERVING-CELL: BER < 0.2%, Mean value 0.14% (0)
|
||||
* .... ...1 11.. .... = NO-NCELL-M: Neighbour cell information not available */
|
||||
0x36, 0x76, 0x01, 0xc0,
|
||||
|
||||
/* 0** -- Padding with zeroes */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
/* Allocate a new primitive */
|
||||
rc = sched_prim_init(lchan, &prim, GSM_MACBLOCK_LEN,
|
||||
trx_lchan_desc[lchan->type].chan_nr, TRX_CH_LID_SACCH);
|
||||
OSMO_ASSERT(rc == 0);
|
||||
|
||||
/* 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) { /* Use the cached one */
|
||||
mr_src_ptr = lchan->sacch.mr_cache;
|
||||
lchan->sacch.mr_cache_usage++;
|
||||
} else { /* Use "dummy" one */
|
||||
mr_src_ptr = (uint8_t *) meas_rep_dummy;
|
||||
}
|
||||
|
||||
/* Compose a new Measurement Report primitive */
|
||||
memcpy(prim->payload, mr_src_ptr, GSM_MACBLOCK_LEN);
|
||||
|
||||
/**
|
||||
* Update the L1 SACCH pseudo-header (only for cached MRs)
|
||||
*
|
||||
* TODO: filling of the actual values into cached Measurement
|
||||
* Reports would break the distance spoofing feature. If it
|
||||
* were known whether the spoofing is enabled or not, we could
|
||||
* decide whether to update the cached L1 SACCH header here.
|
||||
*/
|
||||
if (!cached) {
|
||||
prim->payload[0] = lchan->ts->trx->tx_power;
|
||||
prim->payload[1] = lchan->ts->trx->ta;
|
||||
}
|
||||
|
||||
/* Inform about the cache usage count */
|
||||
if (cached && lchan->sacch.mr_cache_usage > 5) {
|
||||
LOGP(DSCHD, LOGL_NOTICE, "SACCH MR cache usage count=%u > 5 "
|
||||
"on lchan=%s => ancient measurements, please fix!\n",
|
||||
lchan->sacch.mr_cache_usage,
|
||||
trx_lchan_desc[lchan->type].name);
|
||||
}
|
||||
|
||||
LOGP(DSCHD, LOGL_NOTICE, "Using a %s Measurement Report "
|
||||
"on lchan=%s\n", (cached ? "cached" : "dummy"),
|
||||
trx_lchan_desc[lchan->type].name);
|
||||
|
||||
return prim;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 queue transmit queue to take a prim from
|
||||
* @param lchan lchan to assign a primitive
|
||||
* @return SACCH primitive to be transmitted
|
||||
*/
|
||||
static struct trx_ts_prim *prim_dequeue_sacch(struct llist_head *queue,
|
||||
struct trx_lchan_state *lchan)
|
||||
{
|
||||
struct trx_ts_prim *prim_nmr = NULL;
|
||||
struct trx_ts_prim *prim_mr = NULL;
|
||||
struct trx_ts_prim *prim;
|
||||
bool mr_now;
|
||||
|
||||
/* Shall we transmit MR now? */
|
||||
mr_now = !lchan->sacch.mr_tx_last;
|
||||
|
||||
#define PRIM_IS_MR(prim) \
|
||||
(prim->payload[5] == GSM48_PDISC_RR \
|
||||
&& prim->payload[6] == GSM48_MT_RR_MEAS_REP)
|
||||
|
||||
/* Iterate over all primitives in the queue */
|
||||
llist_for_each_entry(prim, queue, list) {
|
||||
/* We are looking for particular channel */
|
||||
if (prim->chan != lchan->type)
|
||||
continue;
|
||||
|
||||
/* Look for a Measurement Report */
|
||||
if (!prim_mr && PRIM_IS_MR(prim))
|
||||
prim_mr = prim;
|
||||
|
||||
/* Look for anything else */
|
||||
if (!prim_nmr && !PRIM_IS_MR(prim))
|
||||
prim_nmr = prim;
|
||||
|
||||
/* Should we look further? */
|
||||
if (mr_now && prim_mr)
|
||||
break; /* MR was found */
|
||||
else if (!mr_now && prim_nmr)
|
||||
break; /* something else was found */
|
||||
}
|
||||
|
||||
LOGP(DSCHD, LOGL_DEBUG, "SACCH MR selection on lchan=%s: "
|
||||
"mr_tx_last=%d prim_mr=%p prim_nmr=%p\n",
|
||||
trx_lchan_desc[lchan->type].name,
|
||||
lchan->sacch.mr_tx_last,
|
||||
prim_mr, prim_nmr);
|
||||
|
||||
/* Prioritize non-MR prim if possible */
|
||||
if (mr_now && prim_mr)
|
||||
prim = prim_mr;
|
||||
else if (!mr_now && prim_nmr)
|
||||
prim = prim_nmr;
|
||||
else if (!mr_now && prim_mr)
|
||||
prim = prim_mr;
|
||||
else /* Nothing was found */
|
||||
prim = NULL;
|
||||
|
||||
/* Have we found what we were looking for? */
|
||||
if (prim) /* Dequeue if so */
|
||||
llist_del(&prim->list);
|
||||
else /* Otherwise compose a new MR */
|
||||
prim = prim_compose_mr(lchan);
|
||||
|
||||
/* Update the cached report */
|
||||
if (prim == prim_mr) {
|
||||
memcpy(lchan->sacch.mr_cache,
|
||||
prim->payload, GSM_MACBLOCK_LEN);
|
||||
lchan->sacch.mr_cache_usage = 0;
|
||||
|
||||
LOGP(DSCHD, LOGL_DEBUG, "SACCH MR cache has been updated "
|
||||
"for lchan=%s\n", trx_lchan_desc[lchan->type].name);
|
||||
}
|
||||
|
||||
/* Update the MR transmission state */
|
||||
lchan->sacch.mr_tx_last = PRIM_IS_MR(prim);
|
||||
|
||||
LOGP(DSCHD, LOGL_DEBUG, "SACCH decision on lchan=%s: %s\n",
|
||||
trx_lchan_desc[lchan->type].name, PRIM_IS_MR(prim) ?
|
||||
"Measurement Report" : "data frame");
|
||||
|
||||
return prim;
|
||||
}
|
||||
|
||||
/* Dequeues a primitive of a given channel type */
|
||||
static struct trx_ts_prim *prim_dequeue_one(struct llist_head *queue,
|
||||
enum trx_lchan_type lchan_type)
|
||||
{
|
||||
struct trx_ts_prim *prim;
|
||||
|
||||
/**
|
||||
* There is no need to use the 'safe' list iteration here
|
||||
* as an item removal is immediately followed by return.
|
||||
*/
|
||||
llist_for_each_entry(prim, queue, list) {
|
||||
if (prim->chan == lchan_type) {
|
||||
llist_del(&prim->list);
|
||||
return prim;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dequeues either a FACCH, or a speech TCH primitive
|
||||
* of a given channel type (Lm or Bm).
|
||||
*
|
||||
* Note: we could avoid 'lchan_type' parameter and just
|
||||
* check the prim's channel type using CHAN_IS_TCH(),
|
||||
* but the current approach is a bit more flexible,
|
||||
* and allows one to have both sub-slots of TCH/H
|
||||
* enabled on same timeslot e.g. for testing...
|
||||
*
|
||||
* @param queue transmit queue to take a prim from
|
||||
* @param lchan_type required channel type of a primitive,
|
||||
* e.g. TRXC_TCHF, TRXC_TCHH_0, or TRXC_TCHH_1
|
||||
* @param facch FACCH (true) or speech (false) prim?
|
||||
* @return either a FACCH, or a TCH primitive if found,
|
||||
* otherwise NULL
|
||||
*/
|
||||
static struct trx_ts_prim *prim_dequeue_tch(struct llist_head *queue,
|
||||
enum trx_lchan_type lchan_type, bool facch)
|
||||
{
|
||||
struct trx_ts_prim *prim;
|
||||
|
||||
/**
|
||||
* There is no need to use the 'safe' list iteration here
|
||||
* as an item removal is immediately followed by return.
|
||||
*/
|
||||
llist_for_each_entry(prim, queue, list) {
|
||||
if (prim->chan != lchan_type)
|
||||
continue;
|
||||
|
||||
/* Either FACCH, or not FACCH */
|
||||
if (PRIM_IS_FACCH(prim) != facch)
|
||||
continue;
|
||||
|
||||
llist_del(&prim->list);
|
||||
return prim;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dequeues either a TCH/F, or a FACCH/F prim (preferred).
|
||||
* If a FACCH/F prim is found, one TCH/F prim is being
|
||||
* dropped (i.e. replaced).
|
||||
*
|
||||
* @param queue a transmit queue to take a prim from
|
||||
* @return either a FACCH/F, or a TCH/F primitive,
|
||||
* otherwise NULL
|
||||
*/
|
||||
static struct trx_ts_prim *prim_dequeue_tch_f(struct llist_head *queue)
|
||||
{
|
||||
struct trx_ts_prim *facch;
|
||||
struct trx_ts_prim *tch;
|
||||
|
||||
/* Attempt to find a pair of both FACCH/F and TCH/F frames */
|
||||
facch = prim_dequeue_tch(queue, TRXC_TCHF, true);
|
||||
tch = prim_dequeue_tch(queue, TRXC_TCHF, false);
|
||||
|
||||
/* Prioritize FACCH/F, if found */
|
||||
if (facch) {
|
||||
/* One TCH/F prim is replaced */
|
||||
if (tch)
|
||||
talloc_free(tch);
|
||||
return facch;
|
||||
} else if (tch) {
|
||||
/* Only TCH/F prim was found */
|
||||
return tch;
|
||||
} else {
|
||||
/* Nothing was found, e.g. when only SACCH frames are in queue */
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dequeues either a TCH/H, or a FACCH/H prim (preferred).
|
||||
* If a FACCH/H prim is found, two TCH/H prims are being
|
||||
* dropped (i.e. replaced).
|
||||
*
|
||||
* According to GSM 05.02, the following blocks can be used
|
||||
* to carry FACCH/H data (see clause 7, table 1 of 9):
|
||||
*
|
||||
* UL FACCH/H0:
|
||||
* B0(0,2,4,6,8,10), B1(8,10,13,15,17,19), B2(17,19,21,23,0,2)
|
||||
*
|
||||
* UL FACCH/H1:
|
||||
* B0(1,3,5,7,9,11), B1(9,11,14,16,18,20), B2(18,20,22,24,1,3)
|
||||
*
|
||||
* where the numbers within brackets are fn % 26.
|
||||
*
|
||||
* @param queue transmit queue to take a prim from
|
||||
* @param fn the current frame number
|
||||
* @param lchan_type required channel type of a primitive,
|
||||
* @return either a FACCH/H, or a TCH/H primitive,
|
||||
* otherwise NULL
|
||||
*/
|
||||
static struct trx_ts_prim *prim_dequeue_tch_h(struct llist_head *queue,
|
||||
uint32_t fn, enum trx_lchan_type lchan_type)
|
||||
{
|
||||
struct trx_ts_prim *facch;
|
||||
struct trx_ts_prim *tch;
|
||||
bool facch_now;
|
||||
|
||||
/* May we initiate an UL FACCH/H frame transmission now? */
|
||||
facch_now = sched_tchh_facch_start(lchan_type, fn, true);
|
||||
if (!facch_now) /* Just dequeue a TCH/H prim */
|
||||
goto no_facch;
|
||||
|
||||
/* If there are no FACCH/H prims in the queue */
|
||||
facch = prim_dequeue_tch(queue, lchan_type, true);
|
||||
if (!facch) /* Just dequeue a TCH/H prim */
|
||||
goto no_facch;
|
||||
|
||||
/* FACCH/H prim replaces two TCH/F prims */
|
||||
tch = prim_dequeue_tch(queue, lchan_type, false);
|
||||
if (tch) {
|
||||
/* At least one TCH/H prim is dropped */
|
||||
talloc_free(tch);
|
||||
|
||||
/* Attempt to find another */
|
||||
tch = prim_dequeue_tch(queue, lchan_type, false);
|
||||
if (tch) /* Drop the second TCH/H prim */
|
||||
talloc_free(tch);
|
||||
}
|
||||
|
||||
return facch;
|
||||
|
||||
no_facch:
|
||||
return prim_dequeue_tch(queue, lchan_type, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dequeues a single primitive of required type
|
||||
* from a specified transmit queue.
|
||||
*
|
||||
* @param queue a transmit queue to take a prim from
|
||||
* @param fn the current frame number (used for FACCH/H)
|
||||
* @param lchan logical channel state
|
||||
* @return a primitive or NULL if not found
|
||||
*/
|
||||
struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue,
|
||||
uint32_t fn, struct trx_lchan_state *lchan)
|
||||
{
|
||||
/* SACCH is unorthodox, see 3GPP TS 04.08, section 3.4.1 */
|
||||
if (CHAN_IS_SACCH(lchan->type))
|
||||
return prim_dequeue_sacch(queue, lchan);
|
||||
|
||||
/* There is nothing to dequeue */
|
||||
if (llist_empty(queue))
|
||||
return NULL;
|
||||
|
||||
switch (lchan->type) {
|
||||
/* TCH/F requires FACCH/F prioritization */
|
||||
case TRXC_TCHF:
|
||||
return prim_dequeue_tch_f(queue);
|
||||
|
||||
/* FACCH/H prioritization is a bit more complex */
|
||||
case TRXC_TCHH_0:
|
||||
case TRXC_TCHH_1:
|
||||
return prim_dequeue_tch_h(queue, fn, lchan->type);
|
||||
|
||||
/* Other kinds of logical channels */
|
||||
default:
|
||||
return prim_dequeue_one(queue, lchan->type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the current primitive of specified logical channel
|
||||
*
|
||||
* @param lchan a logical channel to drop prim from
|
||||
*/
|
||||
void sched_prim_drop(struct trx_lchan_state *lchan)
|
||||
{
|
||||
/* Forget this primitive */
|
||||
talloc_free(lchan->prim);
|
||||
lchan->prim = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a dummy primitive to a lchan depending on its type.
|
||||
* Could be used when there is nothing to transmit, but
|
||||
* CBTX (Continuous Burst Transmission) is assumed.
|
||||
*
|
||||
* @param lchan lchan to assign a primitive
|
||||
* @return zero in case of success, otherwise a error code
|
||||
*/
|
||||
int sched_prim_dummy(struct trx_lchan_state *lchan)
|
||||
{
|
||||
enum trx_lchan_type chan = lchan->type;
|
||||
uint8_t tch_mode = lchan->tch_mode;
|
||||
struct trx_ts_prim *prim;
|
||||
uint8_t prim_buffer[40];
|
||||
size_t prim_len = 0;
|
||||
int i;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static const uint8_t lapdm_fill_frame[] = {
|
||||
0x01, 0x03, 0x01, 0x2b,
|
||||
/* Pending part is to be randomized */
|
||||
};
|
||||
|
||||
/* Make sure that there is no existing primitive */
|
||||
OSMO_ASSERT(lchan->prim == NULL);
|
||||
/* Not applicable for SACCH! */
|
||||
OSMO_ASSERT(!CHAN_IS_SACCH(lchan->type));
|
||||
|
||||
/**
|
||||
* Determine what actually should be generated:
|
||||
* TCH in GSM48_CMODE_SIGN: LAPDm fill frame;
|
||||
* TCH in other modes: silence frame;
|
||||
* other channels: LAPDm fill frame.
|
||||
*/
|
||||
if (CHAN_IS_TCH(chan) && TCH_MODE_IS_SPEECH(tch_mode)) {
|
||||
/* Bad frame indication */
|
||||
prim_len = sched_bad_frame_ind(prim_buffer, lchan);
|
||||
} else if (CHAN_IS_TCH(chan) && TCH_MODE_IS_DATA(tch_mode)) {
|
||||
/* FIXME: should we do anything for CSD? */
|
||||
return 0;
|
||||
} else {
|
||||
/* Copy LAPDm fill frame's header */
|
||||
memcpy(prim_buffer, lapdm_fill_frame, sizeof(lapdm_fill_frame));
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
for (i = sizeof(lapdm_fill_frame); i < GSM_MACBLOCK_LEN; i++)
|
||||
prim_buffer[i] = (uint8_t) rand();
|
||||
|
||||
/* Define a prim length */
|
||||
prim_len = GSM_MACBLOCK_LEN;
|
||||
}
|
||||
|
||||
/* Nothing to allocate / assign */
|
||||
if (!prim_len)
|
||||
return 0;
|
||||
|
||||
/* Allocate a new primitive */
|
||||
prim = talloc_zero_size(lchan, sizeof(struct trx_ts_prim) + prim_len);
|
||||
if (prim == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Init primitive header */
|
||||
prim->payload_len = prim_len;
|
||||
prim->chan = lchan->type;
|
||||
|
||||
/* Fill in the payload */
|
||||
memcpy(prim->payload, prim_buffer, prim_len);
|
||||
|
||||
/* Assign the current prim */
|
||||
lchan->prim = prim;
|
||||
|
||||
LOGP(DSCHD, LOGL_DEBUG, "Transmitting a dummy / silence frame "
|
||||
"on lchan=%s\n", trx_lchan_desc[chan].name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes a queue of primitives
|
||||
*
|
||||
* @param list list of prims going to be flushed
|
||||
*/
|
||||
void sched_prim_flush_queue(struct llist_head *list)
|
||||
{
|
||||
struct trx_ts_prim *prim, *prim_next;
|
||||
|
||||
llist_for_each_entry_safe(prim, prim_next, list, list) {
|
||||
llist_del(&prim->list);
|
||||
talloc_free(prim);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,841 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: GSM PHY routines
|
||||
*
|
||||
* (C) 2017-2019 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <error.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <talloc.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <osmocom/gsm/a5.h>
|
||||
#include <osmocom/gsm/protocol/gsm_08_58.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
|
||||
#include "l1ctl_proto.h"
|
||||
#include "scheduler.h"
|
||||
#include "sched_trx.h"
|
||||
#include "trx_if.h"
|
||||
#include "logging.h"
|
||||
|
||||
static void sched_frame_clck_cb(struct trx_sched *sched)
|
||||
{
|
||||
struct trx_instance *trx = (struct trx_instance *) sched->data;
|
||||
const struct trx_frame *frame;
|
||||
struct trx_lchan_state *lchan;
|
||||
trx_lchan_tx_func *handler;
|
||||
enum trx_lchan_type chan;
|
||||
uint8_t offset, bid;
|
||||
struct trx_ts *ts;
|
||||
uint32_t fn;
|
||||
int i;
|
||||
|
||||
/* Iterate over timeslot list */
|
||||
for (i = 0; i < TRX_TS_COUNT; i++) {
|
||||
/* Timeslot is not allocated */
|
||||
ts = trx->ts_list[i];
|
||||
if (ts == NULL)
|
||||
continue;
|
||||
|
||||
/* Timeslot is not configured */
|
||||
if (ts->mf_layout == NULL)
|
||||
continue;
|
||||
|
||||
/**
|
||||
* Advance frame number, giving the transceiver more
|
||||
* time until a burst must be transmitted...
|
||||
*/
|
||||
fn = GSM_TDMA_FN_SUM(sched->fn_counter_proc, sched->fn_counter_advance);
|
||||
|
||||
/* Get frame from multiframe */
|
||||
offset = fn % ts->mf_layout->period;
|
||||
frame = ts->mf_layout->frames + offset;
|
||||
|
||||
/* Get required info from frame */
|
||||
bid = frame->ul_bid;
|
||||
chan = frame->ul_chan;
|
||||
handler = trx_lchan_desc[chan].tx_fn;
|
||||
|
||||
/* Omit lchans without handler */
|
||||
if (!handler)
|
||||
continue;
|
||||
|
||||
/* Make sure that lchan was allocated and activated */
|
||||
lchan = sched_trx_find_lchan(ts, chan);
|
||||
if (lchan == NULL)
|
||||
continue;
|
||||
|
||||
/* Omit inactive lchans */
|
||||
if (!lchan->active)
|
||||
continue;
|
||||
|
||||
/**
|
||||
* If we aren't processing any primitive yet,
|
||||
* attempt to obtain a new one from queue
|
||||
*/
|
||||
if (lchan->prim == NULL)
|
||||
lchan->prim = sched_prim_dequeue(&ts->tx_prims, fn, lchan);
|
||||
|
||||
/* TODO: report TX buffers health to the higher layers */
|
||||
|
||||
/* If CBTX (Continuous Burst Transmission) is assumed */
|
||||
if (trx_lchan_desc[chan].flags & TRX_CH_FLAG_CBTX) {
|
||||
/**
|
||||
* Probably, a TX buffer is empty. Nevertheless,
|
||||
* we shall continuously transmit anything on
|
||||
* CBTX channels.
|
||||
*/
|
||||
if (lchan->prim == NULL)
|
||||
sched_prim_dummy(lchan);
|
||||
}
|
||||
|
||||
/* If there is no primitive, do nothing */
|
||||
if (lchan->prim == NULL)
|
||||
continue;
|
||||
|
||||
/* Handover RACH needs to be handled regardless of the
|
||||
* current channel type and the associated handler. */
|
||||
if (PRIM_IS_RACH(lchan->prim) && lchan->prim->chan != TRXC_RACH)
|
||||
handler = trx_lchan_desc[TRXC_RACH].tx_fn;
|
||||
|
||||
/* Poke lchan handler */
|
||||
handler(trx, ts, lchan, fn, bid);
|
||||
}
|
||||
}
|
||||
|
||||
int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance)
|
||||
{
|
||||
struct trx_sched *sched;
|
||||
|
||||
if (!trx)
|
||||
return -EINVAL;
|
||||
|
||||
LOGP(DSCH, LOGL_NOTICE, "Init scheduler\n");
|
||||
|
||||
/* Obtain a scheduler instance from TRX */
|
||||
sched = &trx->sched;
|
||||
|
||||
/* Register frame clock callback */
|
||||
sched->clock_cb = sched_frame_clck_cb;
|
||||
|
||||
/* Set pointers */
|
||||
sched = &trx->sched;
|
||||
sched->data = trx;
|
||||
|
||||
/* Set frame counter advance */
|
||||
sched->fn_counter_advance = fn_advance;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sched_trx_shutdown(struct trx_instance *trx)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!trx)
|
||||
return -EINVAL;
|
||||
|
||||
LOGP(DSCH, LOGL_NOTICE, "Shutdown scheduler\n");
|
||||
|
||||
/* Free all potentially allocated timeslots */
|
||||
for (i = 0; i < TRX_TS_COUNT; i++)
|
||||
sched_trx_del_ts(trx, i);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sched_trx_reset(struct trx_instance *trx, bool reset_clock)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!trx)
|
||||
return -EINVAL;
|
||||
|
||||
LOGP(DSCH, LOGL_NOTICE, "Reset scheduler %s\n",
|
||||
reset_clock ? "and clock counter" : "");
|
||||
|
||||
/* Free all potentially allocated timeslots */
|
||||
for (i = 0; i < TRX_TS_COUNT; i++)
|
||||
sched_trx_del_ts(trx, i);
|
||||
|
||||
/* Stop and reset clock counter if required */
|
||||
if (reset_clock)
|
||||
sched_clck_reset(&trx->sched);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn)
|
||||
{
|
||||
/* Make sure that ts isn't allocated yet */
|
||||
if (trx->ts_list[tn] != NULL) {
|
||||
LOGP(DSCH, LOGL_ERROR, "Timeslot #%u already allocated\n", tn);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOGP(DSCH, LOGL_NOTICE, "Add a new TDMA timeslot #%u\n", tn);
|
||||
|
||||
/* Allocate a new one */
|
||||
trx->ts_list[tn] = talloc_zero(trx, struct trx_ts);
|
||||
|
||||
/* Add backpointer */
|
||||
trx->ts_list[tn]->trx = trx;
|
||||
|
||||
/* Assign TS index */
|
||||
trx->ts_list[tn]->index = tn;
|
||||
|
||||
return trx->ts_list[tn];
|
||||
}
|
||||
|
||||
void sched_trx_del_ts(struct trx_instance *trx, int tn)
|
||||
{
|
||||
struct trx_lchan_state *lchan, *lchan_next;
|
||||
struct trx_ts *ts;
|
||||
|
||||
/* Find ts in list */
|
||||
ts = trx->ts_list[tn];
|
||||
if (ts == NULL)
|
||||
return;
|
||||
|
||||
LOGP(DSCH, LOGL_NOTICE, "Delete TDMA timeslot #%u\n", tn);
|
||||
|
||||
/* Deactivate all logical channels */
|
||||
sched_trx_deactivate_all_lchans(ts);
|
||||
|
||||
/* Free channel states */
|
||||
llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
|
||||
llist_del(&lchan->list);
|
||||
talloc_free(lchan);
|
||||
}
|
||||
|
||||
/* Flush queue primitives for TX */
|
||||
sched_prim_flush_queue(&ts->tx_prims);
|
||||
|
||||
/* Remove ts from list and free memory */
|
||||
trx->ts_list[tn] = NULL;
|
||||
talloc_free(ts);
|
||||
|
||||
/* Notify transceiver about that */
|
||||
trx_if_cmd_setslot(trx, tn, 0);
|
||||
}
|
||||
|
||||
#define LAYOUT_HAS_LCHAN(layout, lchan) \
|
||||
(layout->lchan_mask & ((uint64_t) 0x01 << lchan))
|
||||
|
||||
int sched_trx_configure_ts(struct trx_instance *trx, int tn,
|
||||
enum gsm_phys_chan_config config)
|
||||
{
|
||||
struct trx_lchan_state *lchan;
|
||||
enum trx_lchan_type type;
|
||||
struct trx_ts *ts;
|
||||
|
||||
/* Try to find specified ts */
|
||||
ts = trx->ts_list[tn];
|
||||
if (ts != NULL) {
|
||||
/* Reconfiguration of existing one */
|
||||
sched_trx_reset_ts(trx, tn);
|
||||
} else {
|
||||
/* Allocate a new one if doesn't exist */
|
||||
ts = sched_trx_add_ts(trx, tn);
|
||||
if (ts == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Choose proper multiframe layout */
|
||||
ts->mf_layout = sched_mframe_layout(config, tn);
|
||||
if (!ts->mf_layout)
|
||||
return -EINVAL;
|
||||
if (ts->mf_layout->chan_config != config)
|
||||
return -EINVAL;
|
||||
|
||||
LOGP(DSCH, LOGL_NOTICE, "(Re)configure TDMA timeslot #%u as %s\n",
|
||||
tn, ts->mf_layout->name);
|
||||
|
||||
/* Init queue primitives for TX */
|
||||
INIT_LLIST_HEAD(&ts->tx_prims);
|
||||
/* Init logical channels list */
|
||||
INIT_LLIST_HEAD(&ts->lchans);
|
||||
|
||||
/* Allocate channel states */
|
||||
for (type = 0; type < _TRX_CHAN_MAX; type++) {
|
||||
if (!LAYOUT_HAS_LCHAN(ts->mf_layout, type))
|
||||
continue;
|
||||
|
||||
/* Allocate a channel state */
|
||||
lchan = talloc_zero(ts, struct trx_lchan_state);
|
||||
if (!lchan)
|
||||
return -ENOMEM;
|
||||
|
||||
/* set backpointer */
|
||||
lchan->ts = ts;
|
||||
|
||||
/* 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 */
|
||||
if (trx_lchan_desc[type].flags & TRX_CH_FLAG_AUTO)
|
||||
sched_trx_activate_lchan(ts, type);
|
||||
}
|
||||
|
||||
/* Notify transceiver about TS activation */
|
||||
/* FIXME: set proper channel type */
|
||||
trx_if_cmd_setslot(trx, tn, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sched_trx_reset_ts(struct trx_instance *trx, int tn)
|
||||
{
|
||||
struct trx_lchan_state *lchan, *lchan_next;
|
||||
struct trx_ts *ts;
|
||||
|
||||
/* Try to find specified ts */
|
||||
ts = trx->ts_list[tn];
|
||||
if (ts == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
/* Undefine multiframe layout */
|
||||
ts->mf_layout = NULL;
|
||||
|
||||
/* Flush queue primitives for TX */
|
||||
sched_prim_flush_queue(&ts->tx_prims);
|
||||
|
||||
/* Deactivate all logical channels */
|
||||
sched_trx_deactivate_all_lchans(ts);
|
||||
|
||||
/* Free channel states */
|
||||
llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
|
||||
llist_del(&lchan->list);
|
||||
talloc_free(lchan);
|
||||
}
|
||||
|
||||
/* Notify transceiver about that */
|
||||
trx_if_cmd_setslot(trx, tn, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo,
|
||||
uint8_t *key, uint8_t key_len)
|
||||
{
|
||||
struct trx_lchan_state *lchan;
|
||||
|
||||
/* 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 */
|
||||
llist_for_each_entry(lchan, &ts->lchans, list) {
|
||||
/* Omit inactive channels */
|
||||
if (!lchan->active)
|
||||
continue;
|
||||
|
||||
/* Set key length and algorithm */
|
||||
lchan->a5.key_len = key_len;
|
||||
lchan->a5.algo = algo;
|
||||
|
||||
/* Copy requested key */
|
||||
if (key_len)
|
||||
memcpy(lchan->a5.key, key, key_len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts,
|
||||
enum trx_lchan_type chan)
|
||||
{
|
||||
struct trx_lchan_state *lchan;
|
||||
|
||||
llist_for_each_entry(lchan, &ts->lchans, list)
|
||||
if (lchan->type == chan)
|
||||
return lchan;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode)
|
||||
{
|
||||
const struct trx_lchan_desc *lchan_desc;
|
||||
struct trx_lchan_state *lchan;
|
||||
int rc = 0;
|
||||
|
||||
/* Prevent NULL-pointer deference */
|
||||
if (ts == NULL) {
|
||||
LOGP(DSCH, LOGL_ERROR, "Timeslot isn't configured\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Iterate over all allocated lchans */
|
||||
llist_for_each_entry(lchan, &ts->lchans, list) {
|
||||
lchan_desc = &trx_lchan_desc[lchan->type];
|
||||
|
||||
if (lchan_desc->chan_nr == (chan_nr & 0xf8)) {
|
||||
if (active) {
|
||||
rc |= sched_trx_activate_lchan(ts, lchan->type);
|
||||
lchan->tch_mode = tch_mode;
|
||||
} else
|
||||
rc |= sched_trx_deactivate_lchan(ts, lchan->type);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
|
||||
{
|
||||
const struct trx_lchan_desc *lchan_desc = &trx_lchan_desc[chan];
|
||||
struct trx_lchan_state *lchan;
|
||||
|
||||
/* Try to find requested logical channel */
|
||||
lchan = sched_trx_find_lchan(ts, chan);
|
||||
if (lchan == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
if (lchan->active) {
|
||||
LOGP(DSCH, LOGL_ERROR, "Logical channel %s already activated "
|
||||
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOGP(DSCH, LOGL_NOTICE, "Activating lchan=%s "
|
||||
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
|
||||
|
||||
/* Conditionally allocate memory for bursts */
|
||||
if (lchan_desc->rx_fn && lchan_desc->burst_buf_size > 0) {
|
||||
lchan->rx_bursts = talloc_zero_size(lchan,
|
||||
lchan_desc->burst_buf_size);
|
||||
if (lchan->rx_bursts == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (lchan_desc->tx_fn && lchan_desc->burst_buf_size > 0) {
|
||||
lchan->tx_bursts = talloc_zero_size(lchan,
|
||||
lchan_desc->burst_buf_size);
|
||||
if (lchan->tx_bursts == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Finally, update channel status */
|
||||
lchan->active = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sched_trx_reset_lchan(struct trx_lchan_state *lchan)
|
||||
{
|
||||
/* Prevent NULL-pointer deference */
|
||||
OSMO_ASSERT(lchan != NULL);
|
||||
|
||||
/* Print some TDMA statistics for Downlink */
|
||||
if (trx_lchan_desc[lchan->type].rx_fn && lchan->active) {
|
||||
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",
|
||||
trx_lchan_desc[lchan->type].name, lchan->ts->index,
|
||||
lchan->tdma.num_proc, lchan->tdma.num_lost,
|
||||
lchan->tdma.last_proc);
|
||||
}
|
||||
|
||||
/* 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 */
|
||||
sched_prim_drop(lchan);
|
||||
|
||||
/* Channel specific stuff */
|
||||
if (CHAN_IS_TCH(lchan->type)) {
|
||||
lchan->dl_ongoing_facch = 0;
|
||||
lchan->ul_facch_blocks = 0;
|
||||
|
||||
lchan->tch_mode = GSM48_CMODE_SIGN;
|
||||
|
||||
/* Reset AMR state */
|
||||
memset(&lchan->amr, 0x00, sizeof(lchan->amr));
|
||||
} else if (CHAN_IS_SACCH(lchan->type)) {
|
||||
/* Reset SACCH state */
|
||||
memset(&lchan->sacch, 0x00, sizeof(lchan->sacch));
|
||||
}
|
||||
|
||||
/* Reset ciphering state */
|
||||
memset(&lchan->a5, 0x00, sizeof(lchan->a5));
|
||||
|
||||
/* Reset TDMA frame statistics */
|
||||
memset(&lchan->tdma, 0x00, sizeof(lchan->tdma));
|
||||
}
|
||||
|
||||
int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
|
||||
{
|
||||
struct trx_lchan_state *lchan;
|
||||
|
||||
/* Try to find requested logical channel */
|
||||
lchan = sched_trx_find_lchan(ts, chan);
|
||||
if (lchan == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
if (!lchan->active) {
|
||||
LOGP(DSCH, LOGL_ERROR, "Logical channel %s already deactivated "
|
||||
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOGP(DSCH, LOGL_DEBUG, "Deactivating lchan=%s "
|
||||
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
|
||||
|
||||
/* Reset internal state, free memory */
|
||||
sched_trx_reset_lchan(lchan);
|
||||
|
||||
/* Update activation flag */
|
||||
lchan->active = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sched_trx_deactivate_all_lchans(struct trx_ts *ts)
|
||||
{
|
||||
struct trx_lchan_state *lchan;
|
||||
|
||||
LOGP(DSCH, LOGL_DEBUG, "Deactivating all logical channels "
|
||||
"on ts=%d\n", ts->index);
|
||||
|
||||
llist_for_each_entry(lchan, &ts->lchans, list) {
|
||||
/* Omit inactive channels */
|
||||
if (!lchan->active)
|
||||
continue;
|
||||
|
||||
/* Reset internal state, free memory */
|
||||
sched_trx_reset_lchan(lchan);
|
||||
|
||||
/* Update activation flag */
|
||||
lchan->active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr)
|
||||
{
|
||||
uint8_t cbits = chan_nr >> 3;
|
||||
|
||||
if (cbits == ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs)
|
||||
return GSM_PCHAN_TCH_F;
|
||||
else if ((cbits & 0x1e) == ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0))
|
||||
return GSM_PCHAN_TCH_H;
|
||||
else if ((cbits & 0x1c) == ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0))
|
||||
return GSM_PCHAN_CCCH_SDCCH4;
|
||||
else if ((cbits & 0x18) == ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0))
|
||||
return GSM_PCHAN_SDCCH8_SACCH8C;
|
||||
else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH4)
|
||||
return GSM_PCHAN_CCCH_SDCCH4_CBCH;
|
||||
else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH8)
|
||||
return GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
|
||||
else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH)
|
||||
return GSM_PCHAN_PDCH;
|
||||
|
||||
return GSM_PCHAN_NONE;
|
||||
}
|
||||
|
||||
enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr,
|
||||
uint8_t link_id)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Iterate over all known lchan types */
|
||||
for (i = 0; i < _TRX_CHAN_MAX; i++)
|
||||
if (trx_lchan_desc[i].chan_nr == (chan_nr & 0xf8))
|
||||
if (trx_lchan_desc[i].link_id == link_id)
|
||||
return i;
|
||||
|
||||
return TRXC_IDLE;
|
||||
}
|
||||
|
||||
static void sched_trx_a5_burst_dec(struct trx_lchan_state *lchan,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
static void sched_trx_a5_burst_enc(struct trx_lchan_state *lchan,
|
||||
uint32_t fn, ubit_t *burst)
|
||||
{
|
||||
ubit_t ks[114];
|
||||
int i;
|
||||
|
||||
/* Generate keystream for an UL burst */
|
||||
osmo_a5(lchan->a5.algo, lchan->a5.key, fn, NULL, ks);
|
||||
|
||||
/* Apply keystream over plaintext */
|
||||
for (i = 0; i < 57; i++) {
|
||||
burst[i + 3] ^= ks[i];
|
||||
burst[i + 88] ^= ks[i + 57];
|
||||
}
|
||||
}
|
||||
|
||||
static int subst_frame_loss(struct trx_lchan_state *lchan,
|
||||
trx_lchan_rx_func *handler,
|
||||
uint32_t fn)
|
||||
{
|
||||
const struct trx_multiframe *mf;
|
||||
const struct trx_frame *fp;
|
||||
int elapsed, i;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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",
|
||||
trx_lchan_desc[lchan->type].name,
|
||||
fn, lchan->tdma.last_proc);
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
/* Check how many frames we (potentially) need to compensate */
|
||||
if (elapsed > mf->period) {
|
||||
LOGP(DSCHD, LOGL_NOTICE, "Too many (>%u) contiguous TDMA frames elapsed (%d) "
|
||||
"since the last processed fn=%u (current %u)\n",
|
||||
mf->period, elapsed, lchan->tdma.last_proc, fn);
|
||||
return -EIO;
|
||||
} 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;
|
||||
}
|
||||
|
||||
static const sbit_t bits[148] = { 0 };
|
||||
struct trx_meas_set fake_meas = {
|
||||
.fn = lchan->tdma.last_proc,
|
||||
.rssi = -120,
|
||||
.toa256 = 0,
|
||||
};
|
||||
|
||||
/* Traverse from fp till the current frame */
|
||||
for (i = 0; i < elapsed - 1; i++) {
|
||||
fp = &mf->frames[GSM_TDMA_FN_INC(fake_meas.fn) % mf->period];
|
||||
if (fp->dl_chan != lchan->type)
|
||||
continue;
|
||||
|
||||
LOGP(DSCHD, LOGL_NOTICE, "Substituting lost TDMA frame %u on %s\n",
|
||||
fake_meas.fn, trx_lchan_desc[lchan->type].name);
|
||||
|
||||
handler(lchan->ts->trx, lchan->ts, lchan,
|
||||
fake_meas.fn, fp->dl_bid,
|
||||
bits, &fake_meas);
|
||||
|
||||
/* Update TDMA frame statistics */
|
||||
lchan->tdma.last_proc = fake_meas.fn;
|
||||
lchan->tdma.num_proc++;
|
||||
lchan->tdma.num_lost++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
|
||||
uint32_t fn, sbit_t *bits, uint16_t nbits,
|
||||
const struct trx_meas_set *meas)
|
||||
{
|
||||
struct trx_lchan_state *lchan;
|
||||
const struct trx_frame *frame;
|
||||
struct trx_ts *ts;
|
||||
|
||||
trx_lchan_rx_func *handler;
|
||||
enum trx_lchan_type chan;
|
||||
uint8_t offset, bid;
|
||||
int rc;
|
||||
|
||||
/* Check whether required timeslot is allocated and configured */
|
||||
ts = trx->ts_list[tn];
|
||||
if (ts == NULL || ts->mf_layout == NULL) {
|
||||
LOGP(DSCHD, LOGL_DEBUG, "TDMA timeslot #%u isn't configured, "
|
||||
"ignoring burst...\n", tn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Get frame from multiframe */
|
||||
offset = fn % ts->mf_layout->period;
|
||||
frame = ts->mf_layout->frames + offset;
|
||||
|
||||
/* Get required info from frame */
|
||||
bid = frame->dl_bid;
|
||||
chan = frame->dl_chan;
|
||||
handler = trx_lchan_desc[chan].rx_fn;
|
||||
|
||||
/* Omit bursts which have no handler, like IDLE bursts.
|
||||
* TODO: handle noise indications during IDLE frames. */
|
||||
if (!handler)
|
||||
return -ENODEV;
|
||||
|
||||
/* Find required channel state */
|
||||
lchan = sched_trx_find_lchan(ts, chan);
|
||||
if (lchan == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
/* Ensure that channel is active */
|
||||
if (!lchan->active)
|
||||
return 0;
|
||||
|
||||
/* Compensate lost TDMA frames (if any) */
|
||||
rc = subst_frame_loss(lchan, handler, fn);
|
||||
if (rc == -EALREADY)
|
||||
return rc;
|
||||
|
||||
/* Perform A5/X decryption if required */
|
||||
if (lchan->a5.algo)
|
||||
sched_trx_a5_burst_dec(lchan, fn, bits);
|
||||
|
||||
/* Put burst to handler */
|
||||
handler(trx, ts, lchan, fn, bid, bits, meas);
|
||||
|
||||
/* Update TDMA frame statistics */
|
||||
lchan->tdma.last_proc = fn;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sched_trx_handle_tx_burst(struct trx_instance *trx,
|
||||
struct trx_ts *ts, struct trx_lchan_state *lchan,
|
||||
uint32_t fn, ubit_t *bits)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Perform A5/X burst encryption if required */
|
||||
if (lchan->a5.algo)
|
||||
sched_trx_a5_burst_enc(lchan, fn, bits);
|
||||
|
||||
/* Forward burst to transceiver */
|
||||
rc = trx_if_tx_burst(trx, ts->index, fn, trx->tx_power, bits);
|
||||
if (rc) {
|
||||
LOGP(DSCHD, LOGL_ERROR, "Could not send burst to transceiver\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#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 */
|
||||
void sched_trx_meas_push(struct trx_lchan_state *lchan, const struct trx_meas_set *meas)
|
||||
{
|
||||
struct trx_lchan_meas_hist *hist = &lchan->meas_hist;
|
||||
|
||||
/* 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 */
|
||||
void sched_trx_meas_avg(struct trx_lchan_state *lchan, unsigned int n)
|
||||
{
|
||||
struct trx_lchan_meas_hist *hist = &lchan->meas_hist;
|
||||
struct trx_meas_set *meas = hist->head;
|
||||
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;
|
||||
|
||||
/* Do not go below the first burst */
|
||||
if (i + 1 == n)
|
||||
break;
|
||||
|
||||
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;
|
||||
|
||||
/* As a bonus, store TDMA frame number of the first burst */
|
||||
lchan->meas_avg.fn = meas->fn;
|
||||
}
|
|
@ -0,0 +1,405 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
|
||||
#include "logging.h"
|
||||
#include "scheduler.h"
|
||||
|
||||
#define GSM_BURST_LEN 148
|
||||
#define GSM_BURST_PL_LEN 116
|
||||
|
||||
#define GPRS_BURST_LEN GSM_BURST_LEN
|
||||
#define EDGE_BURST_LEN 444
|
||||
|
||||
#define GPRS_L2_MAX_LEN 54
|
||||
#define EDGE_L2_MAX_LEN 155
|
||||
|
||||
#define TRX_CH_LID_DEDIC 0x00
|
||||
#define TRX_CH_LID_SACCH 0x40
|
||||
|
||||
/* Osmocom-specific extension for PTCCH (see 3GPP TS 45.002, section 3.3.4.2).
|
||||
* Shall be used to distinguish PTCCH and PDTCH channels on a PDCH time-slot. */
|
||||
#define TRX_CH_LID_PTCCH 0x80
|
||||
|
||||
/* Is a channel related to PDCH (GPRS) */
|
||||
#define TRX_CH_FLAG_PDCH (1 << 0)
|
||||
/* Should a channel be activated automatically */
|
||||
#define TRX_CH_FLAG_AUTO (1 << 1)
|
||||
/* Is continuous burst transmission assumed */
|
||||
#define TRX_CH_FLAG_CBTX (1 << 2)
|
||||
|
||||
#define MAX_A5_KEY_LEN (128 / 8)
|
||||
#define TRX_TS_COUNT 8
|
||||
|
||||
/* Forward declaration to avoid mutual include */
|
||||
struct trx_lchan_state;
|
||||
struct trx_meas_set;
|
||||
struct trx_instance;
|
||||
struct trx_ts;
|
||||
|
||||
enum trx_burst_type {
|
||||
TRX_BURST_GMSK,
|
||||
TRX_BURST_8PSK,
|
||||
};
|
||||
|
||||
/**
|
||||
* These types define the different channels on a multiframe.
|
||||
* Each channel has queues and can be activated individually.
|
||||
*/
|
||||
enum trx_lchan_type {
|
||||
TRXC_IDLE = 0,
|
||||
TRXC_FCCH,
|
||||
TRXC_SCH,
|
||||
TRXC_BCCH,
|
||||
TRXC_RACH,
|
||||
TRXC_CCCH,
|
||||
TRXC_TCHF,
|
||||
TRXC_TCHH_0,
|
||||
TRXC_TCHH_1,
|
||||
TRXC_SDCCH4_0,
|
||||
TRXC_SDCCH4_1,
|
||||
TRXC_SDCCH4_2,
|
||||
TRXC_SDCCH4_3,
|
||||
TRXC_SDCCH8_0,
|
||||
TRXC_SDCCH8_1,
|
||||
TRXC_SDCCH8_2,
|
||||
TRXC_SDCCH8_3,
|
||||
TRXC_SDCCH8_4,
|
||||
TRXC_SDCCH8_5,
|
||||
TRXC_SDCCH8_6,
|
||||
TRXC_SDCCH8_7,
|
||||
TRXC_SACCHTF,
|
||||
TRXC_SACCHTH_0,
|
||||
TRXC_SACCHTH_1,
|
||||
TRXC_SACCH4_0,
|
||||
TRXC_SACCH4_1,
|
||||
TRXC_SACCH4_2,
|
||||
TRXC_SACCH4_3,
|
||||
TRXC_SACCH8_0,
|
||||
TRXC_SACCH8_1,
|
||||
TRXC_SACCH8_2,
|
||||
TRXC_SACCH8_3,
|
||||
TRXC_SACCH8_4,
|
||||
TRXC_SACCH8_5,
|
||||
TRXC_SACCH8_6,
|
||||
TRXC_SACCH8_7,
|
||||
TRXC_PDTCH,
|
||||
TRXC_PTCCH,
|
||||
TRXC_SDCCH4_CBCH,
|
||||
TRXC_SDCCH8_CBCH,
|
||||
_TRX_CHAN_MAX
|
||||
};
|
||||
|
||||
typedef int trx_lchan_rx_func(struct trx_instance *trx,
|
||||
struct trx_ts *ts, struct trx_lchan_state *lchan,
|
||||
uint32_t fn, uint8_t bid, const sbit_t *bits,
|
||||
const struct trx_meas_set *meas);
|
||||
|
||||
typedef int trx_lchan_tx_func(struct trx_instance *trx,
|
||||
struct trx_ts *ts, struct trx_lchan_state *lchan,
|
||||
uint32_t fn, uint8_t bid);
|
||||
|
||||
struct trx_lchan_desc {
|
||||
/*! \brief Human-readable name */
|
||||
const char *name;
|
||||
/*! \brief Human-readable description */
|
||||
const char *desc;
|
||||
|
||||
/*! \brief Channel Number (like in RSL) */
|
||||
uint8_t chan_nr;
|
||||
/*! \brief Link ID (like in RSL) */
|
||||
uint8_t link_id;
|
||||
/*! \brief Sub-slot number (for SDCCH and TCH/H) */
|
||||
uint8_t ss_nr;
|
||||
/*! \brief GSMTAP channel type (see GSMTAP_CHANNEL_*) */
|
||||
uint8_t gsmtap_chan_type;
|
||||
|
||||
/*! \brief How much memory do we need to store bursts */
|
||||
size_t burst_buf_size;
|
||||
/*! \brief Channel specific flags */
|
||||
uint8_t flags;
|
||||
|
||||
/*! \brief Function to call when burst received from PHY */
|
||||
trx_lchan_rx_func *rx_fn;
|
||||
/*! \brief Function to call when data received from L2 */
|
||||
trx_lchan_tx_func *tx_fn;
|
||||
};
|
||||
|
||||
struct trx_frame {
|
||||
/*! \brief Downlink TRX channel type */
|
||||
enum trx_lchan_type dl_chan;
|
||||
/*! \brief Downlink block ID */
|
||||
uint8_t dl_bid;
|
||||
/*! \brief Uplink TRX channel type */
|
||||
enum trx_lchan_type ul_chan;
|
||||
/*! \brief Uplink block ID */
|
||||
uint8_t ul_bid;
|
||||
};
|
||||
|
||||
struct trx_multiframe {
|
||||
/*! \brief Channel combination */
|
||||
enum gsm_phys_chan_config chan_config;
|
||||
/*! \brief Human-readable name */
|
||||
const char *name;
|
||||
/*! \brief Repeats how many frames */
|
||||
uint8_t period;
|
||||
/*! \brief Applies to which timeslots */
|
||||
uint8_t slotmask;
|
||||
/*! \brief Contains which lchans */
|
||||
uint64_t lchan_mask;
|
||||
/*! \brief Pointer to scheduling structure */
|
||||
const struct trx_frame *frames;
|
||||
};
|
||||
|
||||
struct trx_meas_set {
|
||||
/*! \brief TDMA frame number of the first burst this set belongs to */
|
||||
uint32_t fn;
|
||||
/*! \brief ToA256 (Timing of Arrival, 1/256 of a symbol) */
|
||||
int16_t toa256;
|
||||
/*! \brief RSSI (Received Signal Strength Indication) */
|
||||
int8_t rssi;
|
||||
};
|
||||
|
||||
/* Simple ring buffer (up to 8 unique measurements) */
|
||||
struct trx_lchan_meas_hist {
|
||||
struct trx_meas_set buf[8];
|
||||
struct trx_meas_set *head;
|
||||
};
|
||||
|
||||
/* States each channel on a multiframe */
|
||||
struct trx_lchan_state {
|
||||
/*! \brief Channel type */
|
||||
enum trx_lchan_type type;
|
||||
/*! \brief Channel status */
|
||||
uint8_t active;
|
||||
/*! \brief Link to a list of channels */
|
||||
struct llist_head list;
|
||||
|
||||
/*! \brief Burst type: GMSK or 8PSK */
|
||||
enum trx_burst_type burst_type;
|
||||
/*! \brief Mask of received bursts */
|
||||
uint8_t rx_burst_mask;
|
||||
/*! \brief Mask of transmitted bursts */
|
||||
uint8_t tx_burst_mask;
|
||||
/*! \brief Burst buffer for RX */
|
||||
sbit_t *rx_bursts;
|
||||
/*! \brief Burst buffer for TX */
|
||||
ubit_t *tx_bursts;
|
||||
|
||||
/*! \brief A primitive being sent */
|
||||
struct trx_ts_prim *prim;
|
||||
|
||||
/*! \brief Mode for TCH channels (see GSM48_CMODE_*) */
|
||||
uint8_t tch_mode;
|
||||
|
||||
/*! \brief FACCH/H on downlink */
|
||||
bool dl_ongoing_facch;
|
||||
/*! \brief pending FACCH/H blocks on Uplink */
|
||||
uint8_t ul_facch_blocks;
|
||||
|
||||
/*! \brief Downlink measurements history */
|
||||
struct trx_lchan_meas_hist meas_hist;
|
||||
/*! \brief AVG measurements of the last received block */
|
||||
struct trx_meas_set meas_avg;
|
||||
|
||||
/*! \brief TDMA loss detection state */
|
||||
struct {
|
||||
/*! \brief Last processed TDMA frame number */
|
||||
uint32_t last_proc;
|
||||
/*! \brief Number of processed TDMA frames */
|
||||
unsigned long num_proc;
|
||||
/*! \brief Number of lost TDMA frames */
|
||||
unsigned long num_lost;
|
||||
} tdma;
|
||||
|
||||
/*! \brief SACCH state */
|
||||
struct {
|
||||
/*! \brief Cached measurement report (last received) */
|
||||
uint8_t mr_cache[GSM_MACBLOCK_LEN];
|
||||
/*! \brief Cache usage counter */
|
||||
uint8_t mr_cache_usage;
|
||||
/*! \brief Was a MR transmitted last time? */
|
||||
bool mr_tx_last;
|
||||
} sacch;
|
||||
|
||||
/* AMR specific */
|
||||
struct {
|
||||
/*! \brief 4 possible codecs for AMR */
|
||||
uint8_t codec[4];
|
||||
/*! \brief Number of possible codecs */
|
||||
uint8_t codecs;
|
||||
/*! \brief Current uplink FT index */
|
||||
uint8_t ul_ft;
|
||||
/*! \brief Current downlink FT index */
|
||||
uint8_t dl_ft;
|
||||
/*! \brief Current uplink CMR index */
|
||||
uint8_t ul_cmr;
|
||||
/*! \brief Current downlink CMR index */
|
||||
uint8_t dl_cmr;
|
||||
/*! \brief If AMR loop is enabled */
|
||||
uint8_t amr_loop;
|
||||
/*! \brief Number of bit error rates */
|
||||
uint8_t ber_num;
|
||||
/*! \brief Sum of bit error rates */
|
||||
float ber_sum;
|
||||
} amr;
|
||||
|
||||
/*! \brief A5/X encryption state */
|
||||
struct {
|
||||
uint8_t key[MAX_A5_KEY_LEN];
|
||||
uint8_t key_len;
|
||||
uint8_t algo;
|
||||
} a5;
|
||||
|
||||
/* TS that this lchan belongs to */
|
||||
struct trx_ts *ts;
|
||||
};
|
||||
|
||||
struct trx_ts {
|
||||
/*! \brief Timeslot index within a frame (0..7) */
|
||||
uint8_t index;
|
||||
|
||||
/*! \brief Pointer to multiframe layout */
|
||||
const struct trx_multiframe *mf_layout;
|
||||
/*! \brief Channel states for logical channels */
|
||||
struct llist_head lchans;
|
||||
/*! \brief Queue primitives for TX */
|
||||
struct llist_head tx_prims;
|
||||
/* backpointer to its TRX */
|
||||
struct trx_instance *trx;
|
||||
};
|
||||
|
||||
/* Represents one TX primitive in the queue of trx_ts */
|
||||
struct trx_ts_prim {
|
||||
/*! \brief Link to queue of TS */
|
||||
struct llist_head list;
|
||||
/*! \brief Logical channel type */
|
||||
enum trx_lchan_type chan;
|
||||
/*! \brief Payload length */
|
||||
size_t payload_len;
|
||||
/*! \brief Payload */
|
||||
uint8_t payload[0];
|
||||
};
|
||||
|
||||
extern const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX];
|
||||
const struct trx_multiframe *sched_mframe_layout(
|
||||
enum gsm_phys_chan_config config, int tn);
|
||||
|
||||
/* Scheduler management functions */
|
||||
int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance);
|
||||
int sched_trx_reset(struct trx_instance *trx, bool reset_clock);
|
||||
int sched_trx_shutdown(struct trx_instance *trx);
|
||||
|
||||
/* Timeslot management functions */
|
||||
struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn);
|
||||
void sched_trx_del_ts(struct trx_instance *trx, int tn);
|
||||
int sched_trx_reset_ts(struct trx_instance *trx, int tn);
|
||||
int sched_trx_configure_ts(struct trx_instance *trx, int tn,
|
||||
enum gsm_phys_chan_config config);
|
||||
int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo,
|
||||
uint8_t *key, uint8_t key_len);
|
||||
|
||||
/* Logical channel management functions */
|
||||
enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr);
|
||||
enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr,
|
||||
uint8_t link_id);
|
||||
|
||||
void sched_trx_deactivate_all_lchans(struct trx_ts *ts);
|
||||
int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode);
|
||||
int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan);
|
||||
int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan);
|
||||
struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts,
|
||||
enum trx_lchan_type chan);
|
||||
|
||||
/* Primitive management functions */
|
||||
int sched_prim_init(void *ctx, struct trx_ts_prim **prim,
|
||||
size_t pl_len, uint8_t chan_nr, uint8_t link_id);
|
||||
int sched_prim_push(struct trx_instance *trx,
|
||||
struct trx_ts_prim *prim, uint8_t chan_nr);
|
||||
|
||||
#define TCH_MODE_IS_SPEECH(mode) \
|
||||
(mode == GSM48_CMODE_SPEECH_V1 \
|
||||
|| mode == GSM48_CMODE_SPEECH_EFR \
|
||||
|| mode == GSM48_CMODE_SPEECH_AMR)
|
||||
|
||||
#define TCH_MODE_IS_DATA(mode) \
|
||||
(mode == GSM48_CMODE_DATA_14k5 \
|
||||
|| mode == GSM48_CMODE_DATA_12k0 \
|
||||
|| mode == GSM48_CMODE_DATA_6k0 \
|
||||
|| mode == GSM48_CMODE_DATA_3k6)
|
||||
|
||||
#define CHAN_IS_TCH(chan) \
|
||||
(chan == TRXC_TCHF || chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1)
|
||||
|
||||
#define CHAN_IS_SACCH(chan) \
|
||||
(trx_lchan_desc[chan].link_id & TRX_CH_LID_SACCH)
|
||||
|
||||
/* FIXME: we need a better way to identify / distinguish primitives */
|
||||
#define PRIM_IS_RACH11(prim) \
|
||||
(prim->payload_len == sizeof(struct l1ctl_ext_rach_req))
|
||||
|
||||
#define PRIM_IS_RACH8(prim) \
|
||||
(prim->payload_len == sizeof(struct l1ctl_rach_req))
|
||||
|
||||
#define PRIM_IS_RACH(prim) \
|
||||
(PRIM_IS_RACH8(prim) || PRIM_IS_RACH11(prim))
|
||||
|
||||
#define PRIM_IS_TCH(prim) \
|
||||
(CHAN_IS_TCH(prim->chan) && prim->payload_len != GSM_MACBLOCK_LEN)
|
||||
|
||||
#define PRIM_IS_FACCH(prim) \
|
||||
(CHAN_IS_TCH(prim->chan) && prim->payload_len == GSM_MACBLOCK_LEN)
|
||||
|
||||
struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue,
|
||||
uint32_t fn, struct trx_lchan_state *lchan);
|
||||
int sched_prim_dummy(struct trx_lchan_state *lchan);
|
||||
void sched_prim_drop(struct trx_lchan_state *lchan);
|
||||
void sched_prim_flush_queue(struct llist_head *list);
|
||||
|
||||
int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
|
||||
uint32_t fn, sbit_t *bits, uint16_t nbits,
|
||||
const struct trx_meas_set *meas);
|
||||
int sched_trx_handle_tx_burst(struct trx_instance *trx,
|
||||
struct trx_ts *ts, struct trx_lchan_state *lchan,
|
||||
uint32_t fn, ubit_t *bits);
|
||||
|
||||
/* Shared declarations for lchan handlers */
|
||||
extern const uint8_t sched_nb_training_bits[8][26];
|
||||
|
||||
const char *burst_mask2str(const uint8_t *mask, int bits);
|
||||
size_t sched_bad_frame_ind(uint8_t *l2, struct trx_lchan_state *lchan);
|
||||
int sched_send_dt_ind(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint8_t *l2, size_t l2_len,
|
||||
int bit_error_count, bool dec_failed, bool traffic);
|
||||
int sched_send_dt_conf(struct trx_instance *trx, struct trx_ts *ts,
|
||||
struct trx_lchan_state *lchan, uint32_t fn, bool traffic);
|
||||
int sched_gsmtap_send(enum trx_lchan_type lchan_type, uint32_t fn, uint8_t tn,
|
||||
uint16_t band_arfcn, int8_t signal_dbm, uint8_t snr,
|
||||
const uint8_t *data, size_t data_len);
|
||||
|
||||
/* Interleaved TCH/H block TDMA frame mapping */
|
||||
uint32_t sched_tchh_block_dl_first_fn(enum trx_lchan_type chan,
|
||||
uint32_t last_fn, bool facch);
|
||||
bool sched_tchh_block_map_fn(enum trx_lchan_type chan,
|
||||
uint32_t fn, bool ul, bool facch, bool start);
|
||||
|
||||
#define sched_tchh_traffic_start(chan, fn, ul) \
|
||||
sched_tchh_block_map_fn(chan, fn, ul, 0, 1)
|
||||
#define sched_tchh_traffic_end(chan, fn, ul) \
|
||||
sched_tchh_block_map_fn(chan, fn, ul, 0, 0)
|
||||
|
||||
#define sched_tchh_facch_start(chan, fn, ul) \
|
||||
sched_tchh_block_map_fn(chan, fn, ul, 1, 1)
|
||||
#define sched_tchh_facch_end(chan, fn, ul) \
|
||||
sched_tchh_block_map_fn(chan, fn, ul, 1, 0)
|
||||
|
||||
/* Measurement history */
|
||||
void sched_trx_meas_push(struct trx_lchan_state *lchan, const struct trx_meas_set *meas);
|
||||
void sched_trx_meas_avg(struct trx_lchan_state *lchan, unsigned int n);
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/gsm/gsm0502.h>
|
||||
|
||||
enum tdma_sched_clck_state {
|
||||
SCH_CLCK_STATE_WAIT,
|
||||
SCH_CLCK_STATE_OK,
|
||||
};
|
||||
|
||||
/* Forward structure declaration */
|
||||
struct trx_sched;
|
||||
|
||||
/*! \brief One scheduler instance */
|
||||
struct trx_sched {
|
||||
/*! \brief Clock state */
|
||||
enum tdma_sched_clck_state state;
|
||||
/*! \brief Local clock source */
|
||||
struct timespec clock;
|
||||
/*! \brief Count of processed frames */
|
||||
uint32_t fn_counter_proc;
|
||||
/*! \brief Local frame counter advance */
|
||||
uint32_t fn_counter_advance;
|
||||
/*! \brief Count of lost frames */
|
||||
uint32_t fn_counter_lost;
|
||||
/*! \brief Frame callback timer */
|
||||
struct osmo_timer_list clock_timer;
|
||||
/*! \brief Frame callback */
|
||||
void (*clock_cb)(struct trx_sched *sched);
|
||||
/*! \brief Private data (e.g. pointer to trx instance) */
|
||||
void *data;
|
||||
};
|
||||
|
||||
int sched_clck_handle(struct trx_sched *sched, uint32_t fn);
|
||||
void sched_clck_reset(struct trx_sched *sched);
|
|
@ -0,0 +1,737 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* Transceiver interface handlers
|
||||
*
|
||||
* Copyright (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
|
||||
#include "l1ctl.h"
|
||||
#include "trxcon.h"
|
||||
#include "trx_if.h"
|
||||
#include "logging.h"
|
||||
#include "scheduler.h"
|
||||
|
||||
#include "../Transceiver52M/l1if.h"
|
||||
|
||||
static struct value_string trx_evt_names[] = {
|
||||
{ 0, NULL } /* no events? */
|
||||
};
|
||||
|
||||
static struct osmo_fsm_state trx_fsm_states[] = {
|
||||
[TRX_STATE_OFFLINE] = {
|
||||
.out_state_mask = (
|
||||
GEN_MASK(TRX_STATE_IDLE) |
|
||||
GEN_MASK(TRX_STATE_RSP_WAIT)),
|
||||
.name = "OFFLINE",
|
||||
},
|
||||
[TRX_STATE_IDLE] = {
|
||||
.out_state_mask = UINT32_MAX,
|
||||
.name = "IDLE",
|
||||
},
|
||||
[TRX_STATE_ACTIVE] = {
|
||||
.out_state_mask = (
|
||||
GEN_MASK(TRX_STATE_IDLE) |
|
||||
GEN_MASK(TRX_STATE_RSP_WAIT)),
|
||||
.name = "ACTIVE",
|
||||
},
|
||||
[TRX_STATE_RSP_WAIT] = {
|
||||
.out_state_mask = (
|
||||
GEN_MASK(TRX_STATE_IDLE) |
|
||||
GEN_MASK(TRX_STATE_ACTIVE) |
|
||||
GEN_MASK(TRX_STATE_OFFLINE)),
|
||||
.name = "RSP_WAIT",
|
||||
},
|
||||
};
|
||||
|
||||
static struct osmo_fsm trx_fsm = {
|
||||
.name = "trx_interface_fsm",
|
||||
.states = trx_fsm_states,
|
||||
.num_states = ARRAY_SIZE(trx_fsm_states),
|
||||
.log_subsys = DTRX,
|
||||
.event_names = trx_evt_names,
|
||||
};
|
||||
|
||||
static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local,
|
||||
uint16_t port_local, const char *host_remote, uint16_t port_remote,
|
||||
int (*cb)(struct osmo_fd *fd, unsigned int what))
|
||||
{
|
||||
int rc;
|
||||
|
||||
ofd->data = priv;
|
||||
ofd->fd = -1;
|
||||
ofd->cb = cb;
|
||||
|
||||
/* Init UDP Connection */
|
||||
rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, host_local, port_local,
|
||||
host_remote, port_remote,
|
||||
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void trx_udp_close(struct osmo_fd *ofd)
|
||||
{
|
||||
if (ofd->fd > 0) {
|
||||
osmo_fd_unregister(ofd);
|
||||
close(ofd->fd);
|
||||
ofd->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Control (CTRL) interface handlers */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Commands on the Per-ARFCN Control Interface */
|
||||
/* */
|
||||
/* The per-ARFCN control interface uses a command-response protocol. */
|
||||
/* Commands are NULL-terminated ASCII strings, one per UDP socket. */
|
||||
/* Each command has a corresponding response. */
|
||||
/* Every command is of the form: */
|
||||
/* */
|
||||
/* CMD <cmdtype> [params] */
|
||||
/* */
|
||||
/* The <cmdtype> is the actual command. */
|
||||
/* Parameters are optional depending on the commands type. */
|
||||
/* Every response is of the form: */
|
||||
/* */
|
||||
/* RSP <cmdtype> <status> [result] */
|
||||
/* */
|
||||
/* The <status> is 0 for success and a non-zero error code for failure. */
|
||||
/* Successful responses may include results, depending on the command type. */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
static void trx_ctrl_timer_cb(void *data);
|
||||
|
||||
/* Send first CTRL message and start timer */
|
||||
static void trx_ctrl_send(struct trx_instance *trx)
|
||||
{
|
||||
struct trx_ctrl_msg *tcm;
|
||||
|
||||
if (llist_empty(&trx->trx_ctrl_list))
|
||||
return;
|
||||
tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
|
||||
|
||||
char* cmd = malloc(TRXC_BUF_SIZE);
|
||||
memcpy(cmd, tcm->cmd, TRXC_BUF_SIZE);
|
||||
|
||||
/* Send command */
|
||||
LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
|
||||
trxif_to_trx_c(cmd);
|
||||
// send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0);
|
||||
|
||||
/* Trigger state machine */
|
||||
if (trx->fsm->state != TRX_STATE_RSP_WAIT) {
|
||||
trx->prev_state = trx->fsm->state;
|
||||
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_RSP_WAIT, 0, 0);
|
||||
}
|
||||
|
||||
/* Start expire timer */
|
||||
trx->trx_ctrl_timer.data = trx;
|
||||
trx->trx_ctrl_timer.cb = trx_ctrl_timer_cb;
|
||||
osmo_timer_schedule(&trx->trx_ctrl_timer, 2, 0);
|
||||
}
|
||||
|
||||
static void trx_ctrl_timer_cb(void *data)
|
||||
{
|
||||
struct trx_instance *trx = (struct trx_instance *) data;
|
||||
struct trx_ctrl_msg *tcm;
|
||||
|
||||
/* Queue may be cleaned at this moment */
|
||||
if (llist_empty(&trx->trx_ctrl_list))
|
||||
return;
|
||||
|
||||
LOGP(DTRX, LOGL_NOTICE, "No response from transceiver...\n");
|
||||
|
||||
tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
|
||||
if (++tcm->retry_cnt > 3) {
|
||||
LOGP(DTRX, LOGL_NOTICE, "Transceiver offline\n");
|
||||
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_OFFLINE, 0, 0);
|
||||
osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_OFFLINE, trx);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Attempt to send a command again */
|
||||
trx_ctrl_send(trx);
|
||||
}
|
||||
|
||||
/* Add a new CTRL command to the trx_ctrl_list */
|
||||
static int trx_ctrl_cmd(struct trx_instance *trx, int critical,
|
||||
const char *cmd, const char *fmt, ...)
|
||||
{
|
||||
struct trx_ctrl_msg *tcm;
|
||||
int len, pending = 0;
|
||||
va_list ap;
|
||||
|
||||
/* TODO: make sure that transceiver online */
|
||||
|
||||
if (!llist_empty(&trx->trx_ctrl_list))
|
||||
pending = 1;
|
||||
|
||||
/* Allocate a message */
|
||||
tcm = talloc_zero(trx, struct trx_ctrl_msg);
|
||||
if (!tcm)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Fill in command arguments */
|
||||
if (fmt && fmt[0]) {
|
||||
len = snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s ", cmd);
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(tcm->cmd + len, sizeof(tcm->cmd) - len - 1, fmt, ap);
|
||||
va_end(ap);
|
||||
} else {
|
||||
snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s", cmd);
|
||||
}
|
||||
|
||||
tcm->cmd_len = strlen(cmd);
|
||||
tcm->critical = critical;
|
||||
llist_add_tail(&tcm->list, &trx->trx_ctrl_list);
|
||||
LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd);
|
||||
|
||||
/* Send message, if no pending messages */
|
||||
if (!pending)
|
||||
trx_ctrl_send(trx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Power Control
|
||||
*
|
||||
* ECHO is used to check transceiver availability.
|
||||
* CMD ECHO
|
||||
* RSP ECHO <status>
|
||||
*
|
||||
* POWEROFF shuts off transmitter power and stops the demodulator.
|
||||
* CMD POWEROFF
|
||||
* RSP POWEROFF <status>
|
||||
*
|
||||
* POWERON starts the transmitter and starts the demodulator.
|
||||
* Initial power level is very low.
|
||||
* This command fails if the transmitter and receiver are not yet tuned.
|
||||
* This command fails if the transmit or receive frequency creates a conflict
|
||||
* with another ARFCN that is already running.
|
||||
* If the transceiver is already on, it response with success to this command.
|
||||
* CMD POWERON
|
||||
* RSP POWERON <status>
|
||||
*/
|
||||
|
||||
int trx_if_cmd_sync(struct trx_instance *trx)
|
||||
{
|
||||
return trx_ctrl_cmd(trx, 1, "SYNC", "");
|
||||
}
|
||||
|
||||
int trx_if_cmd_echo(struct trx_instance *trx)
|
||||
{
|
||||
return trx_ctrl_cmd(trx, 1, "ECHO", "");
|
||||
}
|
||||
|
||||
int trx_if_cmd_poweroff(struct trx_instance *trx)
|
||||
{
|
||||
return trx_ctrl_cmd(trx, 1, "POWEROFF", "");
|
||||
}
|
||||
|
||||
int trx_if_cmd_poweron(struct trx_instance *trx)
|
||||
{
|
||||
if (trx->powered_up) {
|
||||
/* FIXME: this should be handled by the FSM, not here! */
|
||||
LOGP(DTRX, LOGL_ERROR, "Suppressing POWERON as we're already powered up\n");
|
||||
return -EAGAIN;
|
||||
}
|
||||
return trx_ctrl_cmd(trx, 1, "POWERON", "");
|
||||
}
|
||||
|
||||
/*
|
||||
* Timeslot Control
|
||||
*
|
||||
* SETSLOT sets the format of the uplink timeslots in the ARFCN.
|
||||
* The <timeslot> indicates the timeslot of interest.
|
||||
* The <chantype> indicates the type of channel that occupies the timeslot.
|
||||
* A chantype of zero indicates the timeslot is off.
|
||||
* CMD SETSLOT <timeslot> <chantype>
|
||||
* RSP SETSLOT <status> <timeslot> <chantype>
|
||||
*/
|
||||
|
||||
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type)
|
||||
{
|
||||
return trx_ctrl_cmd(trx, 1, "SETSLOT", "%u %u", tn, type);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tuning Control
|
||||
*
|
||||
* (RX/TX)TUNE tunes the receiver to a given frequency in kHz.
|
||||
* This command fails if the receiver is already running.
|
||||
* (To re-tune you stop the radio, re-tune, and restart.)
|
||||
* This command fails if the transmit or receive frequency
|
||||
* creates a conflict with another ARFCN that is already running.
|
||||
* CMD (RX/TX)TUNE <kHz>
|
||||
* RSP (RX/TX)TUNE <status> <kHz>
|
||||
*/
|
||||
|
||||
int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn)
|
||||
{
|
||||
uint16_t freq10;
|
||||
|
||||
/* RX is downlink on MS side */
|
||||
freq10 = gsm_arfcn2freq10(band_arfcn, 0);
|
||||
if (freq10 == 0xffff) {
|
||||
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return trx_ctrl_cmd(trx, 1, "RXTUNE", "%u", freq10 * 100);
|
||||
}
|
||||
|
||||
int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn)
|
||||
{
|
||||
uint16_t freq10;
|
||||
|
||||
/* TX is uplink on MS side */
|
||||
freq10 = gsm_arfcn2freq10(band_arfcn, 1);
|
||||
if (freq10 == 0xffff) {
|
||||
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return trx_ctrl_cmd(trx, 1, "TXTUNE", "%u", freq10 * 100);
|
||||
}
|
||||
|
||||
/*
|
||||
* Power measurement
|
||||
*
|
||||
* MEASURE instructs the transceiver to perform a power
|
||||
* measurement on specified frequency. After receiving this
|
||||
* request, transceiver should quickly re-tune to requested
|
||||
* frequency, measure power level and re-tune back to the
|
||||
* previous frequency.
|
||||
* CMD MEASURE <kHz>
|
||||
* RSP MEASURE <status> <kHz> <dB>
|
||||
*/
|
||||
|
||||
int trx_if_cmd_measure(struct trx_instance *trx,
|
||||
uint16_t band_arfcn_start, uint16_t band_arfcn_stop)
|
||||
{
|
||||
uint16_t freq10;
|
||||
|
||||
/* Update ARFCN range for measurement */
|
||||
trx->pm_band_arfcn_start = band_arfcn_start;
|
||||
trx->pm_band_arfcn_stop = band_arfcn_stop;
|
||||
|
||||
/* Calculate a frequency for current ARFCN (DL) */
|
||||
freq10 = gsm_arfcn2freq10(band_arfcn_start, 0);
|
||||
if (freq10 == 0xffff) {
|
||||
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn_start);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return trx_ctrl_cmd(trx, 1, "MEASURE", "%u", freq10 * 100);
|
||||
}
|
||||
|
||||
static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp)
|
||||
{
|
||||
unsigned int freq10;
|
||||
uint16_t band_arfcn;
|
||||
int dbm;
|
||||
|
||||
/* Parse freq. and power level */
|
||||
sscanf(resp, "%u %d", &freq10, &dbm);
|
||||
freq10 /= 100;
|
||||
|
||||
/* Check received ARFCN against expected */
|
||||
band_arfcn = gsm_freq102arfcn((uint16_t) freq10, 0);
|
||||
if (band_arfcn != trx->pm_band_arfcn_start) {
|
||||
LOGP(DTRX, LOGL_ERROR, "Power measurement error: "
|
||||
"response ARFCN=%u doesn't match expected ARFCN=%u\n",
|
||||
band_arfcn &~ ARFCN_FLAG_MASK,
|
||||
trx->pm_band_arfcn_start &~ ARFCN_FLAG_MASK);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send L1CTL_PM_CONF */
|
||||
l1ctl_tx_pm_conf(trx->l1l, band_arfcn, dbm,
|
||||
band_arfcn == trx->pm_band_arfcn_stop);
|
||||
|
||||
/* Schedule a next measurement */
|
||||
if (band_arfcn != trx->pm_band_arfcn_stop)
|
||||
trx_if_cmd_measure(trx, ++band_arfcn, trx->pm_band_arfcn_stop);
|
||||
}
|
||||
|
||||
/*
|
||||
* Timing Advance control
|
||||
*
|
||||
* SETTA instructs the transceiver to transmit bursts in
|
||||
* advance calculated from requested TA value. This value is
|
||||
* normally between 0 and 63, with each step representing
|
||||
* an advance of one bit period (about 3.69 microseconds).
|
||||
* Since OsmocomBB has a special feature, which allows one
|
||||
* to spoof the distance from BTS, the range is extended.
|
||||
* CMD SETTA <-128..127>
|
||||
* RSP SETTA <status> <TA>
|
||||
*/
|
||||
|
||||
int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta)
|
||||
{
|
||||
return trx_ctrl_cmd(trx, 0, "SETTA", "%d", ta);
|
||||
}
|
||||
|
||||
/*
|
||||
* Frequency Hopping parameters indication.
|
||||
*
|
||||
* SETFH instructs transceiver to enable frequency hopping mode
|
||||
* using the given HSN, MAIO, and Mobile Allocation parameters.
|
||||
*
|
||||
* CMD SETFH <HSN> <MAIO> <RXF1> <TXF1> [... <RXFN> <TXFN>]
|
||||
*
|
||||
* where <RXFN> and <TXFN> is a pair of Rx/Tx frequencies (in kHz)
|
||||
* corresponding to one ARFCN the Mobile Allocation. Note that the
|
||||
* channel list is expected to be sorted in ascending order.
|
||||
*/
|
||||
|
||||
int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
|
||||
uint8_t maio, uint16_t *ma, size_t ma_len)
|
||||
{
|
||||
/* Reserve some room for CMD SETFH <HSN> <MAIO> */
|
||||
char ma_buf[TRXC_BUF_SIZE - 24];
|
||||
size_t ma_buf_len = sizeof(ma_buf) - 1;
|
||||
uint16_t rx_freq, tx_freq;
|
||||
char *ptr;
|
||||
int i, rc;
|
||||
|
||||
/* Make sure that Mobile Allocation has at least one ARFCN */
|
||||
if (!ma_len || ma == NULL) {
|
||||
LOGP(DTRX, LOGL_ERROR, "Mobile Allocation is empty?!?\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Compose a sequence of Rx/Tx frequencies (mobile allocation) */
|
||||
for (i = 0, ptr = ma_buf; i < ma_len; i++) {
|
||||
/* Convert ARFCN to a pair of Rx/Tx frequencies (Hz * 10) */
|
||||
rx_freq = gsm_arfcn2freq10(ma[i], 0); /* Rx: Downlink */
|
||||
tx_freq = gsm_arfcn2freq10(ma[i], 1); /* Tx: Uplink */
|
||||
if (rx_freq == 0xffff || tx_freq == 0xffff) {
|
||||
LOGP(DTRX, LOGL_ERROR, "Failed to convert ARFCN %u "
|
||||
"to a pair of Rx/Tx frequencies\n",
|
||||
ma[i] & ~ARFCN_FLAG_MASK);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Append a pair of Rx/Tx frequencies (in kHz) to the buffer */
|
||||
rc = snprintf(ptr, ma_buf_len, "%u %u ", rx_freq * 100, tx_freq * 100);
|
||||
if (rc < 0 || rc > ma_buf_len) { /* Prevent buffer overflow */
|
||||
LOGP(DTRX, LOGL_ERROR, "Not enough room to encode "
|
||||
"Mobile Allocation (N=%zu)\n", ma_len);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
/* Move pointer */
|
||||
ma_buf_len -= rc;
|
||||
ptr += rc;
|
||||
}
|
||||
|
||||
/* Overwrite the last space */
|
||||
*(ptr - 1) = '\0';
|
||||
|
||||
return trx_ctrl_cmd(trx, 1, "SETFH", "%u %u %s", hsn, maio, ma_buf);
|
||||
}
|
||||
|
||||
/* Get response from CTRL socket */
|
||||
static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
|
||||
{
|
||||
struct trx_instance *trx = ofd->data;
|
||||
struct trx_ctrl_msg *tcm;
|
||||
int resp, rsp_len;
|
||||
char buf[TRXC_BUF_SIZE], *p;
|
||||
|
||||
char* response = trxif_from_trx_c();
|
||||
if (!response) {
|
||||
LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", response);
|
||||
goto rsp_error;
|
||||
}
|
||||
memcpy(buf, response, TRXC_BUF_SIZE);
|
||||
free(response);
|
||||
|
||||
if (!!strncmp(buf, "RSP ", 4)) {
|
||||
LOGP(DTRX, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Calculate the length of response item */
|
||||
p = strchr(buf + 4, ' ');
|
||||
rsp_len = p ? p - buf - 4 : strlen(buf) - 4;
|
||||
|
||||
LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf);
|
||||
|
||||
/* Abort expire timer */
|
||||
if (osmo_timer_pending(&trx->trx_ctrl_timer))
|
||||
osmo_timer_del(&trx->trx_ctrl_timer);
|
||||
|
||||
/* Get command for response message */
|
||||
if (llist_empty(&trx->trx_ctrl_list)) {
|
||||
LOGP(DTRX, LOGL_NOTICE, "Response message without command\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tcm = llist_entry(trx->trx_ctrl_list.next,
|
||||
struct trx_ctrl_msg, list);
|
||||
|
||||
/* Check if response matches command */
|
||||
if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) {
|
||||
LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
|
||||
"Response message '%s' does not match command "
|
||||
"message '%s'\n", buf, tcm->cmd);
|
||||
goto rsp_error;
|
||||
}
|
||||
|
||||
/* Check for response code */
|
||||
sscanf(p + 1, "%d", &resp);
|
||||
if (resp) {
|
||||
LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
|
||||
"Transceiver rejected TRX command with "
|
||||
"response: '%s'\n", buf);
|
||||
|
||||
if (tcm->critical)
|
||||
goto rsp_error;
|
||||
}
|
||||
|
||||
/* Trigger state machine */
|
||||
if (!strncmp(tcm->cmd + 4, "POWERON", 7)) {
|
||||
trx->powered_up = true;
|
||||
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_ACTIVE, 0, 0);
|
||||
}
|
||||
else if (!strncmp(tcm->cmd + 4, "POWEROFF", 8)) {
|
||||
trx->powered_up = false;
|
||||
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
|
||||
}
|
||||
else if (!strncmp(tcm->cmd + 4, "MEASURE", 7))
|
||||
trx_if_measure_rsp_cb(trx, buf + 14);
|
||||
else if (!strncmp(tcm->cmd + 4, "ECHO", 4))
|
||||
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
|
||||
else
|
||||
osmo_fsm_inst_state_chg(trx->fsm, trx->prev_state, 0, 0);
|
||||
|
||||
/* Remove command from list */
|
||||
llist_del(&tcm->list);
|
||||
talloc_free(tcm);
|
||||
|
||||
/* Send next message, if any */
|
||||
trx_ctrl_send(trx);
|
||||
|
||||
return 0;
|
||||
|
||||
rsp_error:
|
||||
/* Notify higher layers about the problem */
|
||||
osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_RSP_ERROR, trx);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Data interface handlers */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* DATA interface */
|
||||
/* */
|
||||
/* Messages on the data interface carry one radio burst per UDP message. */
|
||||
/* */
|
||||
/* Received Data Burst: */
|
||||
/* 1 byte timeslot index */
|
||||
/* 4 bytes GSM frame number, BE */
|
||||
/* 1 byte RSSI in -dBm */
|
||||
/* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE */
|
||||
/* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1" */
|
||||
/* 2 bytes are not used, but being sent by OsmoTRX */
|
||||
/* */
|
||||
/* Transmit Data Burst: */
|
||||
/* 1 byte timeslot index */
|
||||
/* 4 bytes GSM frame number, BE */
|
||||
/* 1 byte transmit level wrt ARFCN max, -dB (attenuation) */
|
||||
/* 148 bytes output symbol values, 0 & 1 */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
|
||||
{
|
||||
struct trx_instance *trx = ofd->data;
|
||||
struct trx_meas_set meas;
|
||||
uint8_t buf[TRXD_BUF_SIZE];
|
||||
sbit_t bits[148];
|
||||
int8_t rssi, tn;
|
||||
int16_t toa256;
|
||||
uint32_t fn;
|
||||
ssize_t read_len;
|
||||
|
||||
|
||||
struct trxd_from_trx* rcvd = trxif_from_trx_d();
|
||||
if (!rcvd) {
|
||||
LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", rcvd);
|
||||
return rcvd;
|
||||
}
|
||||
|
||||
tn = rcvd->ts;
|
||||
fn = rcvd->fn;
|
||||
rssi = -(int8_t) rcvd->rssi;
|
||||
toa256 = (int16_t) rcvd->toa;
|
||||
|
||||
/* Copy and convert bits {254..0} to sbits {-127..127} */
|
||||
//osmo_ubit2sbit(bits, rcvd->symbols, 148);
|
||||
memcpy(bits, rcvd->symbols, 148);
|
||||
|
||||
free(rcvd);
|
||||
|
||||
if (tn >= 8) {
|
||||
LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (fn >= 2715648) {
|
||||
LOGP(DTRXD, LOGL_ERROR, "Illegal FN %u\n", fn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOGP(DTRXD, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%d\n",
|
||||
tn, fn, rssi, toa256);
|
||||
|
||||
/* Group the measurements together */
|
||||
meas = (struct trx_meas_set) {
|
||||
.toa256 = toa256,
|
||||
.rssi = rssi,
|
||||
.fn = fn,
|
||||
};
|
||||
|
||||
/* Poke scheduler */
|
||||
sched_trx_handle_rx_burst(trx, tn, fn, bits, 148, &meas);
|
||||
|
||||
/* Correct local clock counter */
|
||||
if (fn % 51 == 0)
|
||||
sched_clck_handle(&trx->sched, fn);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
|
||||
uint8_t pwr, const ubit_t *bits)
|
||||
{
|
||||
struct trxd_to_trx* t = malloc(sizeof(struct trxd_to_trx));
|
||||
t->ts = tn;
|
||||
t->fn = fn;
|
||||
t->txlev = pwr;
|
||||
memcpy(t->symbols, bits, 148);
|
||||
trxif_to_trx_d(t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Init TRX interface (TRXC, TRXD sockets and FSM) */
|
||||
struct trx_instance *trx_if_open(void *tall_ctx,
|
||||
const char *local_host, const char *remote_host,
|
||||
uint16_t base_port)
|
||||
{
|
||||
struct trx_instance *trx;
|
||||
int rc;
|
||||
|
||||
LOGP(DTRX, LOGL_NOTICE, "Init transceiver interface "
|
||||
"(%s:%u)\n", remote_host, base_port);
|
||||
|
||||
/* Try to allocate memory */
|
||||
trx = talloc_zero(tall_ctx, struct trx_instance);
|
||||
if (!trx) {
|
||||
LOGP(DTRX, LOGL_ERROR, "Failed to allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Allocate a new dedicated state machine */
|
||||
trx->fsm = osmo_fsm_inst_alloc(&trx_fsm, trx,
|
||||
NULL, LOGL_DEBUG, "trx_interface");
|
||||
if (trx->fsm == NULL) {
|
||||
LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance "
|
||||
"of FSM '%s'\n", trx_fsm.name);
|
||||
talloc_free(trx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Initialize CTRL queue */
|
||||
INIT_LLIST_HEAD(&trx->trx_ctrl_list);
|
||||
|
||||
rc = eventfd(0, 0);
|
||||
osmo_fd_setup(get_c_fd(), rc, OSMO_FD_READ, trx_ctrl_read_cb, trx, 0);
|
||||
osmo_fd_register(get_c_fd());
|
||||
|
||||
rc = eventfd(0, 0);
|
||||
osmo_fd_setup(get_d_fd(), rc, OSMO_FD_READ, trx_data_rx_cb, trx, 0);
|
||||
osmo_fd_register(get_d_fd());
|
||||
|
||||
return trx;
|
||||
}
|
||||
|
||||
/* Flush pending control messages */
|
||||
void trx_if_flush_ctrl(struct trx_instance *trx)
|
||||
{
|
||||
struct trx_ctrl_msg *tcm;
|
||||
|
||||
/* Reset state machine */
|
||||
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
|
||||
|
||||
/* Clear command queue */
|
||||
while (!llist_empty(&trx->trx_ctrl_list)) {
|
||||
tcm = llist_entry(trx->trx_ctrl_list.next,
|
||||
struct trx_ctrl_msg, list);
|
||||
llist_del(&tcm->list);
|
||||
talloc_free(tcm);
|
||||
}
|
||||
}
|
||||
|
||||
void trx_if_close(struct trx_instance *trx)
|
||||
{
|
||||
/* May be unallocated due to init error */
|
||||
if (!trx)
|
||||
return;
|
||||
|
||||
LOGP(DTRX, LOGL_NOTICE, "Shutdown transceiver interface\n");
|
||||
|
||||
/* Flush CTRL message list */
|
||||
trx_if_flush_ctrl(trx);
|
||||
|
||||
/* Close sockets */
|
||||
close(get_c_fd()->fd);
|
||||
close(get_d_fd()->fd);
|
||||
|
||||
/* Free memory */
|
||||
osmo_fsm_inst_free(trx->fsm);
|
||||
talloc_free(trx);
|
||||
}
|
||||
|
||||
static __attribute__((constructor)) void on_dso_load(void)
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&trx_fsm) == 0);
|
||||
}
|
|
@ -0,0 +1,816 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* Transceiver interface handlers
|
||||
*
|
||||
* Copyright (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
|
||||
#include "l1ctl.h"
|
||||
#include "trxcon.h"
|
||||
#include "trx_if.h"
|
||||
#include "logging.h"
|
||||
#include "scheduler.h"
|
||||
|
||||
#include "../Transceiver52M/l1if.h"
|
||||
|
||||
static struct value_string trx_evt_names[] = {
|
||||
{ 0, NULL } /* no events? */
|
||||
};
|
||||
|
||||
static struct osmo_fsm_state trx_fsm_states[] = {
|
||||
[TRX_STATE_OFFLINE] = {
|
||||
.out_state_mask = (
|
||||
GEN_MASK(TRX_STATE_IDLE) |
|
||||
GEN_MASK(TRX_STATE_RSP_WAIT)),
|
||||
.name = "OFFLINE",
|
||||
},
|
||||
[TRX_STATE_IDLE] = {
|
||||
.out_state_mask = UINT32_MAX,
|
||||
.name = "IDLE",
|
||||
},
|
||||
[TRX_STATE_ACTIVE] = {
|
||||
.out_state_mask = (
|
||||
GEN_MASK(TRX_STATE_IDLE) |
|
||||
GEN_MASK(TRX_STATE_RSP_WAIT)),
|
||||
.name = "ACTIVE",
|
||||
},
|
||||
[TRX_STATE_RSP_WAIT] = {
|
||||
.out_state_mask = (
|
||||
GEN_MASK(TRX_STATE_IDLE) |
|
||||
GEN_MASK(TRX_STATE_ACTIVE) |
|
||||
GEN_MASK(TRX_STATE_OFFLINE)),
|
||||
.name = "RSP_WAIT",
|
||||
},
|
||||
};
|
||||
|
||||
static struct osmo_fsm trx_fsm = {
|
||||
.name = "trx_interface_fsm",
|
||||
.states = trx_fsm_states,
|
||||
.num_states = ARRAY_SIZE(trx_fsm_states),
|
||||
.log_subsys = DTRX,
|
||||
.event_names = trx_evt_names,
|
||||
};
|
||||
|
||||
static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local,
|
||||
uint16_t port_local, const char *host_remote, uint16_t port_remote,
|
||||
int (*cb)(struct osmo_fd *fd, unsigned int what))
|
||||
{
|
||||
int rc;
|
||||
|
||||
ofd->data = priv;
|
||||
ofd->fd = -1;
|
||||
ofd->cb = cb;
|
||||
|
||||
/* Init UDP Connection */
|
||||
rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, host_local, port_local,
|
||||
host_remote, port_remote,
|
||||
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void trx_udp_close(struct osmo_fd *ofd)
|
||||
{
|
||||
if (ofd->fd > 0) {
|
||||
osmo_fd_unregister(ofd);
|
||||
close(ofd->fd);
|
||||
ofd->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Control (CTRL) interface handlers */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Commands on the Per-ARFCN Control Interface */
|
||||
/* */
|
||||
/* The per-ARFCN control interface uses a command-response protocol. */
|
||||
/* Commands are NULL-terminated ASCII strings, one per UDP socket. */
|
||||
/* Each command has a corresponding response. */
|
||||
/* Every command is of the form: */
|
||||
/* */
|
||||
/* CMD <cmdtype> [params] */
|
||||
/* */
|
||||
/* The <cmdtype> is the actual command. */
|
||||
/* Parameters are optional depending on the commands type. */
|
||||
/* Every response is of the form: */
|
||||
/* */
|
||||
/* RSP <cmdtype> <status> [result] */
|
||||
/* */
|
||||
/* The <status> is 0 for success and a non-zero error code for failure. */
|
||||
/* Successful responses may include results, depending on the command type. */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
static void trx_ctrl_timer_cb(void *data);
|
||||
|
||||
/* Send first CTRL message and start timer */
|
||||
static void trx_ctrl_send(struct trx_instance *trx)
|
||||
{
|
||||
struct trx_ctrl_msg *tcm;
|
||||
|
||||
if (llist_empty(&trx->trx_ctrl_list))
|
||||
return;
|
||||
tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
|
||||
|
||||
char* cmd = malloc(TRXC_BUF_SIZE);
|
||||
memcpy(cmd, tcm->cmd, TRXC_BUF_SIZE);
|
||||
|
||||
/* Send command */
|
||||
LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
|
||||
trxif_to_trx_c(cmd);
|
||||
// send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0);
|
||||
|
||||
/* Trigger state machine */
|
||||
if (trx->fsm->state != TRX_STATE_RSP_WAIT) {
|
||||
trx->prev_state = trx->fsm->state;
|
||||
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_RSP_WAIT, 0, 0);
|
||||
}
|
||||
|
||||
/* Start expire timer */
|
||||
trx->trx_ctrl_timer.data = trx;
|
||||
trx->trx_ctrl_timer.cb = trx_ctrl_timer_cb;
|
||||
osmo_timer_schedule(&trx->trx_ctrl_timer, 2, 0);
|
||||
}
|
||||
|
||||
static void trx_ctrl_timer_cb(void *data)
|
||||
{
|
||||
struct trx_instance *trx = (struct trx_instance *) data;
|
||||
struct trx_ctrl_msg *tcm;
|
||||
|
||||
/* Queue may be cleaned at this moment */
|
||||
if (llist_empty(&trx->trx_ctrl_list))
|
||||
return;
|
||||
|
||||
LOGP(DTRX, LOGL_NOTICE, "No response from transceiver...\n");
|
||||
|
||||
tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
|
||||
if (++tcm->retry_cnt > 3) {
|
||||
LOGP(DTRX, LOGL_NOTICE, "Transceiver offline\n");
|
||||
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_OFFLINE, 0, 0);
|
||||
osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_OFFLINE, trx);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Attempt to send a command again */
|
||||
trx_ctrl_send(trx);
|
||||
}
|
||||
|
||||
/* Add a new CTRL command to the trx_ctrl_list */
|
||||
static int trx_ctrl_cmd(struct trx_instance *trx, int critical,
|
||||
const char *cmd, const char *fmt, ...)
|
||||
{
|
||||
struct trx_ctrl_msg *tcm;
|
||||
int len, pending = 0;
|
||||
va_list ap;
|
||||
|
||||
/* TODO: make sure that transceiver online */
|
||||
|
||||
if (!llist_empty(&trx->trx_ctrl_list))
|
||||
pending = 1;
|
||||
|
||||
/* Allocate a message */
|
||||
tcm = talloc_zero(trx, struct trx_ctrl_msg);
|
||||
if (!tcm)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Fill in command arguments */
|
||||
if (fmt && fmt[0]) {
|
||||
len = snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s ", cmd);
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(tcm->cmd + len, sizeof(tcm->cmd) - len - 1, fmt, ap);
|
||||
va_end(ap);
|
||||
} else {
|
||||
snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s", cmd);
|
||||
}
|
||||
|
||||
tcm->cmd_len = strlen(cmd);
|
||||
tcm->critical = critical;
|
||||
llist_add_tail(&tcm->list, &trx->trx_ctrl_list);
|
||||
LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd);
|
||||
|
||||
/* Send message, if no pending messages */
|
||||
if (!pending)
|
||||
trx_ctrl_send(trx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Power Control
|
||||
*
|
||||
* ECHO is used to check transceiver availability.
|
||||
* CMD ECHO
|
||||
* RSP ECHO <status>
|
||||
*
|
||||
* POWEROFF shuts off transmitter power and stops the demodulator.
|
||||
* CMD POWEROFF
|
||||
* RSP POWEROFF <status>
|
||||
*
|
||||
* POWERON starts the transmitter and starts the demodulator.
|
||||
* Initial power level is very low.
|
||||
* This command fails if the transmitter and receiver are not yet tuned.
|
||||
* This command fails if the transmit or receive frequency creates a conflict
|
||||
* with another ARFCN that is already running.
|
||||
* If the transceiver is already on, it response with success to this command.
|
||||
* CMD POWERON
|
||||
* RSP POWERON <status>
|
||||
*/
|
||||
|
||||
int trx_if_cmd_sync(struct trx_instance *trx)
|
||||
{
|
||||
return trx_ctrl_cmd(trx, 1, "SYNC", "");
|
||||
}
|
||||
|
||||
int trx_if_cmd_echo(struct trx_instance *trx)
|
||||
{
|
||||
return trx_ctrl_cmd(trx, 1, "ECHO", "");
|
||||
}
|
||||
|
||||
int trx_if_cmd_poweroff(struct trx_instance *trx)
|
||||
{
|
||||
return trx_ctrl_cmd(trx, 1, "POWEROFF", "");
|
||||
}
|
||||
|
||||
int trx_if_cmd_poweron(struct trx_instance *trx)
|
||||
{
|
||||
if (trx->powered_up) {
|
||||
/* FIXME: this should be handled by the FSM, not here! */
|
||||
LOGP(DTRX, LOGL_ERROR, "Suppressing POWERON as we're already powered up\n");
|
||||
return -EAGAIN;
|
||||
}
|
||||
return trx_ctrl_cmd(trx, 1, "POWERON", "");
|
||||
}
|
||||
|
||||
/*
|
||||
* Timeslot Control
|
||||
*
|
||||
* SETSLOT sets the format of the uplink timeslots in the ARFCN.
|
||||
* The <timeslot> indicates the timeslot of interest.
|
||||
* The <chantype> indicates the type of channel that occupies the timeslot.
|
||||
* A chantype of zero indicates the timeslot is off.
|
||||
* CMD SETSLOT <timeslot> <chantype>
|
||||
* RSP SETSLOT <status> <timeslot> <chantype>
|
||||
*/
|
||||
|
||||
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type)
|
||||
{
|
||||
return trx_ctrl_cmd(trx, 1, "SETSLOT", "%u %u", tn, type);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tuning Control
|
||||
*
|
||||
* (RX/TX)TUNE tunes the receiver to a given frequency in kHz.
|
||||
* This command fails if the receiver is already running.
|
||||
* (To re-tune you stop the radio, re-tune, and restart.)
|
||||
* This command fails if the transmit or receive frequency
|
||||
* creates a conflict with another ARFCN that is already running.
|
||||
* CMD (RX/TX)TUNE <kHz>
|
||||
* RSP (RX/TX)TUNE <status> <kHz>
|
||||
*/
|
||||
|
||||
int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn)
|
||||
{
|
||||
uint16_t freq10;
|
||||
|
||||
/* RX is downlink on MS side */
|
||||
freq10 = gsm_arfcn2freq10(band_arfcn, 0);
|
||||
if (freq10 == 0xffff) {
|
||||
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return trx_ctrl_cmd(trx, 1, "RXTUNE", "%u", freq10 * 100);
|
||||
}
|
||||
|
||||
int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn)
|
||||
{
|
||||
uint16_t freq10;
|
||||
|
||||
/* TX is uplink on MS side */
|
||||
freq10 = gsm_arfcn2freq10(band_arfcn, 1);
|
||||
if (freq10 == 0xffff) {
|
||||
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return trx_ctrl_cmd(trx, 1, "TXTUNE", "%u", freq10 * 100);
|
||||
}
|
||||
|
||||
/*
|
||||
* Power measurement
|
||||
*
|
||||
* MEASURE instructs the transceiver to perform a power
|
||||
* measurement on specified frequency. After receiving this
|
||||
* request, transceiver should quickly re-tune to requested
|
||||
* frequency, measure power level and re-tune back to the
|
||||
* previous frequency.
|
||||
* CMD MEASURE <kHz>
|
||||
* RSP MEASURE <status> <kHz> <dB>
|
||||
*/
|
||||
|
||||
int trx_if_cmd_measure(struct trx_instance *trx,
|
||||
uint16_t band_arfcn_start, uint16_t band_arfcn_stop)
|
||||
{
|
||||
uint16_t freq10;
|
||||
|
||||
/* Update ARFCN range for measurement */
|
||||
trx->pm_band_arfcn_start = band_arfcn_start;
|
||||
trx->pm_band_arfcn_stop = band_arfcn_stop;
|
||||
|
||||
/* Calculate a frequency for current ARFCN (DL) */
|
||||
freq10 = gsm_arfcn2freq10(band_arfcn_start, 0);
|
||||
if (freq10 == 0xffff) {
|
||||
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn_start);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return trx_ctrl_cmd(trx, 1, "MEASURE", "%u", freq10 * 100);
|
||||
}
|
||||
|
||||
static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp)
|
||||
{
|
||||
unsigned int freq10;
|
||||
uint16_t band_arfcn;
|
||||
int dbm;
|
||||
|
||||
/* Parse freq. and power level */
|
||||
sscanf(resp, "%u %d", &freq10, &dbm);
|
||||
freq10 /= 100;
|
||||
|
||||
/* Check received ARFCN against expected */
|
||||
band_arfcn = gsm_freq102arfcn((uint16_t) freq10, 0);
|
||||
if (band_arfcn != trx->pm_band_arfcn_start) {
|
||||
LOGP(DTRX, LOGL_ERROR, "Power measurement error: "
|
||||
"response ARFCN=%u doesn't match expected ARFCN=%u\n",
|
||||
band_arfcn &~ ARFCN_FLAG_MASK,
|
||||
trx->pm_band_arfcn_start &~ ARFCN_FLAG_MASK);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send L1CTL_PM_CONF */
|
||||
l1ctl_tx_pm_conf(trx->l1l, band_arfcn, dbm,
|
||||
band_arfcn == trx->pm_band_arfcn_stop);
|
||||
|
||||
/* Schedule a next measurement */
|
||||
if (band_arfcn != trx->pm_band_arfcn_stop)
|
||||
trx_if_cmd_measure(trx, ++band_arfcn, trx->pm_band_arfcn_stop);
|
||||
}
|
||||
|
||||
/*
|
||||
* Timing Advance control
|
||||
*
|
||||
* SETTA instructs the transceiver to transmit bursts in
|
||||
* advance calculated from requested TA value. This value is
|
||||
* normally between 0 and 63, with each step representing
|
||||
* an advance of one bit period (about 3.69 microseconds).
|
||||
* Since OsmocomBB has a special feature, which allows one
|
||||
* to spoof the distance from BTS, the range is extended.
|
||||
* CMD SETTA <-128..127>
|
||||
* RSP SETTA <status> <TA>
|
||||
*/
|
||||
|
||||
int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta)
|
||||
{
|
||||
return trx_ctrl_cmd(trx, 0, "SETTA", "%d", ta);
|
||||
}
|
||||
|
||||
/*
|
||||
* Frequency Hopping parameters indication.
|
||||
*
|
||||
* SETFH instructs transceiver to enable frequency hopping mode
|
||||
* using the given HSN, MAIO, and Mobile Allocation parameters.
|
||||
*
|
||||
* CMD SETFH <HSN> <MAIO> <RXF1> <TXF1> [... <RXFN> <TXFN>]
|
||||
*
|
||||
* where <RXFN> and <TXFN> is a pair of Rx/Tx frequencies (in kHz)
|
||||
* corresponding to one ARFCN the Mobile Allocation. Note that the
|
||||
* channel list is expected to be sorted in ascending order.
|
||||
*/
|
||||
|
||||
int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
|
||||
uint8_t maio, uint16_t *ma, size_t ma_len)
|
||||
{
|
||||
/* Reserve some room for CMD SETFH <HSN> <MAIO> */
|
||||
char ma_buf[TRXC_BUF_SIZE - 24];
|
||||
size_t ma_buf_len = sizeof(ma_buf) - 1;
|
||||
uint16_t rx_freq, tx_freq;
|
||||
char *ptr;
|
||||
int i, rc;
|
||||
|
||||
/* Make sure that Mobile Allocation has at least one ARFCN */
|
||||
if (!ma_len || ma == NULL) {
|
||||
LOGP(DTRX, LOGL_ERROR, "Mobile Allocation is empty?!?\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Compose a sequence of Rx/Tx frequencies (mobile allocation) */
|
||||
for (i = 0, ptr = ma_buf; i < ma_len; i++) {
|
||||
/* Convert ARFCN to a pair of Rx/Tx frequencies (Hz * 10) */
|
||||
rx_freq = gsm_arfcn2freq10(ma[i], 0); /* Rx: Downlink */
|
||||
tx_freq = gsm_arfcn2freq10(ma[i], 1); /* Tx: Uplink */
|
||||
if (rx_freq == 0xffff || tx_freq == 0xffff) {
|
||||
LOGP(DTRX, LOGL_ERROR, "Failed to convert ARFCN %u "
|
||||
"to a pair of Rx/Tx frequencies\n",
|
||||
ma[i] & ~ARFCN_FLAG_MASK);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Append a pair of Rx/Tx frequencies (in kHz) to the buffer */
|
||||
rc = snprintf(ptr, ma_buf_len, "%u %u ", rx_freq * 100, tx_freq * 100);
|
||||
if (rc < 0 || rc > ma_buf_len) { /* Prevent buffer overflow */
|
||||
LOGP(DTRX, LOGL_ERROR, "Not enough room to encode "
|
||||
"Mobile Allocation (N=%zu)\n", ma_len);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
/* Move pointer */
|
||||
ma_buf_len -= rc;
|
||||
ptr += rc;
|
||||
}
|
||||
|
||||
/* Overwrite the last space */
|
||||
*(ptr - 1) = '\0';
|
||||
|
||||
return trx_ctrl_cmd(trx, 1, "SETFH", "%u %u %s", hsn, maio, ma_buf);
|
||||
}
|
||||
|
||||
/* Get response from CTRL socket */
|
||||
static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
|
||||
{
|
||||
struct trx_instance *trx = ofd->data;
|
||||
struct trx_ctrl_msg *tcm;
|
||||
int resp, rsp_len;
|
||||
char buf[TRXC_BUF_SIZE], *p;
|
||||
ssize_t read_len;
|
||||
|
||||
// read_len = read(ofd->fd, buf, sizeof(buf) - 1);
|
||||
// if (read_len <= 0) {
|
||||
// LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
|
||||
// return read_len;
|
||||
// }
|
||||
// buf[read_len] = '\0';
|
||||
|
||||
LOGP(DTRX, LOGL_NOTICE, "C wat: %d\n", what);
|
||||
char* response = trxif_from_trx_c();
|
||||
if (!response) {
|
||||
LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", response);
|
||||
return response;
|
||||
}
|
||||
memcpy(buf, response, TRXC_BUF_SIZE);
|
||||
free(response);
|
||||
|
||||
if (!!strncmp(buf, "RSP ", 4)) {
|
||||
LOGP(DTRX, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Calculate the length of response item */
|
||||
p = strchr(buf + 4, ' ');
|
||||
rsp_len = p ? p - buf - 4 : strlen(buf) - 4;
|
||||
|
||||
LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf);
|
||||
|
||||
/* Abort expire timer */
|
||||
if (osmo_timer_pending(&trx->trx_ctrl_timer))
|
||||
osmo_timer_del(&trx->trx_ctrl_timer);
|
||||
|
||||
/* Get command for response message */
|
||||
if (llist_empty(&trx->trx_ctrl_list)) {
|
||||
LOGP(DTRX, LOGL_NOTICE, "Response message without command\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tcm = llist_entry(trx->trx_ctrl_list.next,
|
||||
struct trx_ctrl_msg, list);
|
||||
|
||||
/* Check if response matches command */
|
||||
if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) {
|
||||
LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
|
||||
"Response message '%s' does not match command "
|
||||
"message '%s'\n", buf, tcm->cmd);
|
||||
goto rsp_error;
|
||||
}
|
||||
|
||||
/* Check for response code */
|
||||
sscanf(p + 1, "%d", &resp);
|
||||
if (resp) {
|
||||
LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
|
||||
"Transceiver rejected TRX command with "
|
||||
"response: '%s'\n", buf);
|
||||
|
||||
if (tcm->critical)
|
||||
goto rsp_error;
|
||||
}
|
||||
|
||||
/* Trigger state machine */
|
||||
if (!strncmp(tcm->cmd + 4, "POWERON", 7)) {
|
||||
trx->powered_up = true;
|
||||
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_ACTIVE, 0, 0);
|
||||
}
|
||||
else if (!strncmp(tcm->cmd + 4, "POWEROFF", 8)) {
|
||||
trx->powered_up = false;
|
||||
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
|
||||
}
|
||||
else if (!strncmp(tcm->cmd + 4, "MEASURE", 7))
|
||||
trx_if_measure_rsp_cb(trx, buf + 14);
|
||||
else if (!strncmp(tcm->cmd + 4, "ECHO", 4))
|
||||
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
|
||||
else
|
||||
osmo_fsm_inst_state_chg(trx->fsm, trx->prev_state, 0, 0);
|
||||
|
||||
/* Remove command from list */
|
||||
llist_del(&tcm->list);
|
||||
talloc_free(tcm);
|
||||
|
||||
/* Send next message, if any */
|
||||
trx_ctrl_send(trx);
|
||||
|
||||
return 0;
|
||||
|
||||
rsp_error:
|
||||
/* Notify higher layers about the problem */
|
||||
osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_RSP_ERROR, trx);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Data interface handlers */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* DATA interface */
|
||||
/* */
|
||||
/* Messages on the data interface carry one radio burst per UDP message. */
|
||||
/* */
|
||||
/* Received Data Burst: */
|
||||
/* 1 byte timeslot index */
|
||||
/* 4 bytes GSM frame number, BE */
|
||||
/* 1 byte RSSI in -dBm */
|
||||
/* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE */
|
||||
/* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1" */
|
||||
/* 2 bytes are not used, but being sent by OsmoTRX */
|
||||
/* */
|
||||
/* Transmit Data Burst: */
|
||||
/* 1 byte timeslot index */
|
||||
/* 4 bytes GSM frame number, BE */
|
||||
/* 1 byte transmit level wrt ARFCN max, -dB (attenuation) */
|
||||
/* 148 bytes output symbol values, 0 & 1 */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
|
||||
{
|
||||
struct trx_instance *trx = ofd->data;
|
||||
struct trx_meas_set meas;
|
||||
uint8_t buf[TRXD_BUF_SIZE];
|
||||
sbit_t bits[148];
|
||||
int8_t rssi, tn;
|
||||
int16_t toa256;
|
||||
uint32_t fn;
|
||||
ssize_t read_len;
|
||||
|
||||
|
||||
|
||||
// read_len = read(ofd->fd, buf, sizeof(buf));
|
||||
// if (read_len <= 0) {
|
||||
// LOGP(DTRXD, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
|
||||
// return read_len;
|
||||
// }
|
||||
|
||||
// if (read_len != 158) {
|
||||
// LOGP(DTRXD, LOGL_ERROR, "Got data message with invalid "
|
||||
// "length '%zd'\n", read_len);
|
||||
// return -EINVAL;
|
||||
// }
|
||||
|
||||
// tn = buf[0];
|
||||
// fn = osmo_load32be(buf + 1);
|
||||
// rssi = -(int8_t) buf[5];
|
||||
// toa256 = ((int16_t) (buf[6] << 8) | buf[7]);
|
||||
|
||||
// /* Copy and convert bits {254..0} to sbits {-127..127} */
|
||||
// osmo_ubit2sbit(bits, buf + 8, 148);
|
||||
|
||||
struct trxd_from_trx* rcvd = trxif_from_trx_d();
|
||||
if (!rcvd) {
|
||||
LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", rcvd);
|
||||
return rcvd;
|
||||
}
|
||||
|
||||
tn = rcvd->ts;
|
||||
fn = rcvd->fn;
|
||||
rssi = -(int8_t) rcvd->rssi;
|
||||
toa256 = (int16_t) rcvd->toa;
|
||||
|
||||
/* Copy and convert bits {254..0} to sbits {-127..127} */
|
||||
//osmo_ubit2sbit(bits, rcvd->symbols, 148);
|
||||
memcpy(bits, rcvd->symbols, 148);
|
||||
|
||||
free(rcvd);
|
||||
|
||||
if (tn >= 8) {
|
||||
LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (fn >= 2715648) {
|
||||
LOGP(DTRXD, LOGL_ERROR, "Illegal FN %u\n", fn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOGP(DTRXD, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%d\n",
|
||||
tn, fn, rssi, toa256);
|
||||
|
||||
/* Group the measurements together */
|
||||
meas = (struct trx_meas_set) {
|
||||
.toa256 = toa256,
|
||||
.rssi = rssi,
|
||||
.fn = fn,
|
||||
};
|
||||
|
||||
/* Poke scheduler */
|
||||
sched_trx_handle_rx_burst(trx, tn, fn, bits, 148, &meas);
|
||||
|
||||
/* Correct local clock counter */
|
||||
if (fn % 51 == 0)
|
||||
sched_clck_handle(&trx->sched, fn);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
|
||||
uint8_t pwr, const ubit_t *bits)
|
||||
{
|
||||
// uint8_t buf[TRXD_BUF_SIZE];
|
||||
|
||||
// /**
|
||||
// * We must be sure that we have clock,
|
||||
// * and we have sent all control data
|
||||
// *
|
||||
// * TODO: introduce proper state machines for both
|
||||
// * transceiver and its TRXC interface.
|
||||
// */
|
||||
//#if 0
|
||||
// if (trx->fsm->state != TRX_STATE_ACTIVE) {
|
||||
// LOGP(DTRXD, LOGL_ERROR, "Ignoring TX data, "
|
||||
// "transceiver isn't ready\n");
|
||||
// return -EAGAIN;
|
||||
// }
|
||||
//#endif
|
||||
|
||||
// LOGP(DTRXD, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr);
|
||||
|
||||
// buf[0] = tn;
|
||||
// osmo_store32be(fn, buf + 1);
|
||||
// buf[5] = pwr;
|
||||
|
||||
// /* Copy ubits {0,1} */
|
||||
// memcpy(buf + 6, bits, 148);
|
||||
|
||||
// /* Send data to transceiver */
|
||||
// send(trx->trx_ofd_data.fd, buf, 154, 0);
|
||||
|
||||
struct trxd_to_trx* t = malloc(sizeof(struct trxd_to_trx));
|
||||
t->ts = tn;
|
||||
t->fn = fn;
|
||||
t->txlev = pwr;
|
||||
memcpy(t->symbols, bits, 148);
|
||||
trxif_to_trx_d(t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Init TRX interface (TRXC, TRXD sockets and FSM) */
|
||||
struct trx_instance *trx_if_open(void *tall_ctx,
|
||||
const char *local_host, const char *remote_host,
|
||||
uint16_t base_port)
|
||||
{
|
||||
struct trx_instance *trx;
|
||||
int rc;
|
||||
|
||||
LOGP(DTRX, LOGL_NOTICE, "Init transceiver interface "
|
||||
"(%s:%u)\n", remote_host, base_port);
|
||||
|
||||
/* Try to allocate memory */
|
||||
trx = talloc_zero(tall_ctx, struct trx_instance);
|
||||
if (!trx) {
|
||||
LOGP(DTRX, LOGL_ERROR, "Failed to allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Allocate a new dedicated state machine */
|
||||
trx->fsm = osmo_fsm_inst_alloc(&trx_fsm, trx,
|
||||
NULL, LOGL_DEBUG, "trx_interface");
|
||||
if (trx->fsm == NULL) {
|
||||
LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance "
|
||||
"of FSM '%s'\n", trx_fsm.name);
|
||||
talloc_free(trx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Initialize CTRL queue */
|
||||
INIT_LLIST_HEAD(&trx->trx_ctrl_list);
|
||||
|
||||
rc = eventfd(0, 0);
|
||||
osmo_fd_setup(get_c_fd(), rc, OSMO_FD_READ, trx_ctrl_read_cb, trx, 0);
|
||||
osmo_fd_register(get_c_fd());
|
||||
|
||||
rc = eventfd(0, 0);
|
||||
osmo_fd_setup(get_d_fd(), rc, OSMO_FD_READ, trx_data_rx_cb, trx, 0);
|
||||
osmo_fd_register(get_d_fd());
|
||||
|
||||
// /* Open sockets */
|
||||
// rc = trx_udp_open(trx, &trx->trx_ofd_ctrl, local_host,
|
||||
// base_port + 101, remote_host, base_port + 1, trx_ctrl_read_cb);
|
||||
// if (rc < 0)
|
||||
// goto udp_error;
|
||||
|
||||
// rc = trx_udp_open(trx, &trx->trx_ofd_data, local_host,
|
||||
// base_port + 102, remote_host, base_port + 2, trx_data_rx_cb);
|
||||
// if (rc < 0)
|
||||
// goto udp_error;
|
||||
|
||||
return trx;
|
||||
|
||||
//udp_error:
|
||||
// LOGP(DTRX, LOGL_ERROR, "Couldn't establish UDP connection\n");
|
||||
// osmo_fsm_inst_free(trx->fsm);
|
||||
// talloc_free(trx);
|
||||
// return NULL;
|
||||
}
|
||||
|
||||
/* Flush pending control messages */
|
||||
void trx_if_flush_ctrl(struct trx_instance *trx)
|
||||
{
|
||||
struct trx_ctrl_msg *tcm;
|
||||
|
||||
/* Reset state machine */
|
||||
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
|
||||
|
||||
/* Clear command queue */
|
||||
while (!llist_empty(&trx->trx_ctrl_list)) {
|
||||
tcm = llist_entry(trx->trx_ctrl_list.next,
|
||||
struct trx_ctrl_msg, list);
|
||||
llist_del(&tcm->list);
|
||||
talloc_free(tcm);
|
||||
}
|
||||
}
|
||||
|
||||
void trx_if_close(struct trx_instance *trx)
|
||||
{
|
||||
/* May be unallocated due to init error */
|
||||
if (!trx)
|
||||
return;
|
||||
|
||||
LOGP(DTRX, LOGL_NOTICE, "Shutdown transceiver interface\n");
|
||||
|
||||
/* Flush CTRL message list */
|
||||
trx_if_flush_ctrl(trx);
|
||||
|
||||
/* Close sockets */
|
||||
close(get_c_fd()->fd);
|
||||
close(get_d_fd()->fd);
|
||||
|
||||
// trx_udp_close(&trx->trx_ofd_ctrl);
|
||||
// trx_udp_close(&trx->trx_ofd_data);
|
||||
|
||||
/* Free memory */
|
||||
osmo_fsm_inst_free(trx->fsm);
|
||||
talloc_free(trx);
|
||||
}
|
||||
|
||||
static __attribute__((constructor)) void on_dso_load(void)
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&trx_fsm) == 0);
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
#pragma once
|
||||
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
|
||||
#include "scheduler.h"
|
||||
#include "sched_trx.h"
|
||||
|
||||
#define TRXC_BUF_SIZE 1024
|
||||
#define TRXD_BUF_SIZE 512
|
||||
|
||||
/* Forward declaration to avoid mutual include */
|
||||
struct l1ctl_link;
|
||||
|
||||
enum trx_fsm_states {
|
||||
TRX_STATE_OFFLINE = 0,
|
||||
TRX_STATE_IDLE,
|
||||
TRX_STATE_ACTIVE,
|
||||
TRX_STATE_RSP_WAIT,
|
||||
};
|
||||
|
||||
struct trx_instance {
|
||||
// struct osmo_fd trx_ofd_ctrl;
|
||||
// struct osmo_fd trx_ofd_data;
|
||||
|
||||
struct osmo_timer_list trx_ctrl_timer;
|
||||
struct llist_head trx_ctrl_list;
|
||||
struct osmo_fsm_inst *fsm;
|
||||
|
||||
/* HACK: we need proper state machines */
|
||||
uint32_t prev_state;
|
||||
bool powered_up;
|
||||
|
||||
/* GSM L1 specific */
|
||||
uint16_t pm_band_arfcn_start;
|
||||
uint16_t pm_band_arfcn_stop;
|
||||
uint16_t band_arfcn;
|
||||
uint8_t tx_power;
|
||||
uint8_t bsic;
|
||||
uint8_t tsc;
|
||||
int8_t ta;
|
||||
|
||||
/* Scheduler stuff */
|
||||
struct trx_sched sched;
|
||||
struct trx_ts *ts_list[TRX_TS_COUNT];
|
||||
|
||||
/* Bind L1CTL link */
|
||||
struct l1ctl_link *l1l;
|
||||
};
|
||||
|
||||
struct trx_ctrl_msg {
|
||||
struct llist_head list;
|
||||
char cmd[TRXC_BUF_SIZE];
|
||||
int retry_cnt;
|
||||
int critical;
|
||||
int cmd_len;
|
||||
};
|
||||
|
||||
struct trx_instance *trx_if_open(void *tall_ctx,
|
||||
const char *local_host, const char *remote_host, uint16_t port);
|
||||
void trx_if_flush_ctrl(struct trx_instance *trx);
|
||||
void trx_if_close(struct trx_instance *trx);
|
||||
|
||||
int trx_if_cmd_poweron(struct trx_instance *trx);
|
||||
int trx_if_cmd_poweroff(struct trx_instance *trx);
|
||||
int trx_if_cmd_echo(struct trx_instance *trx);
|
||||
int trx_if_cmd_sync(struct trx_instance *trx);
|
||||
|
||||
int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta);
|
||||
|
||||
int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn);
|
||||
int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn);
|
||||
|
||||
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type);
|
||||
int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
|
||||
uint8_t maio, uint16_t *ma, size_t ma_len);
|
||||
|
||||
int trx_if_cmd_measure(struct trx_instance *trx,
|
||||
uint16_t band_arfcn_start, uint16_t band_arfcn_stop);
|
||||
|
||||
int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
|
||||
uint8_t pwr, const ubit_t *bits);
|
|
@ -0,0 +1,391 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
*
|
||||
* (C) 2016-2020 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <getopt.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/signal.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/application.h>
|
||||
#include <osmocom/core/gsmtap_util.h>
|
||||
#include <osmocom/core/gsmtap.h>
|
||||
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
|
||||
#include "trxcon.h"
|
||||
#include "trx_if.h"
|
||||
#include "logging.h"
|
||||
#include "l1ctl.h"
|
||||
#include "l1ctl_link.h"
|
||||
#include "l1ctl_proto.h"
|
||||
#include "scheduler.h"
|
||||
#include "sched_trx.h"
|
||||
|
||||
#define COPYRIGHT \
|
||||
"Copyright (C) 2016-2020 by Vadim Yanitskiy <axilirator@gmail.com>\n" \
|
||||
"License GPLv2+: GNU GPL version 2 or later " \
|
||||
"<http://gnu.org/licenses/gpl.html>\n" \
|
||||
"This is free software: you are free to change and redistribute it.\n" \
|
||||
"There is NO WARRANTY, to the extent permitted by law.\n\n"
|
||||
|
||||
static struct {
|
||||
const char *debug_mask;
|
||||
int daemonize;
|
||||
int quit;
|
||||
|
||||
/* L1CTL specific */
|
||||
struct l1ctl_link *l1l;
|
||||
const char *bind_socket;
|
||||
|
||||
/* TRX specific */
|
||||
struct trx_instance *trx;
|
||||
const char *trx_bind_ip;
|
||||
const char *trx_remote_ip;
|
||||
uint16_t trx_base_port;
|
||||
uint32_t trx_fn_advance;
|
||||
const char *gsmtap_ip;
|
||||
} app_data;
|
||||
|
||||
static void *tall_trxcon_ctx = NULL;
|
||||
struct gsmtap_inst *gsmtap = NULL;
|
||||
struct osmo_fsm_inst *trxcon_fsm;
|
||||
|
||||
static void trxcon_fsm_idle_action(struct osmo_fsm_inst *fi,
|
||||
uint32_t event, void *data)
|
||||
{
|
||||
if (event == L1CTL_EVENT_CONNECT)
|
||||
osmo_fsm_inst_state_chg(trxcon_fsm, TRXCON_STATE_MANAGED, 0, 0);
|
||||
}
|
||||
|
||||
static void trxcon_fsm_managed_action(struct osmo_fsm_inst *fi,
|
||||
uint32_t event, void *data)
|
||||
{
|
||||
switch (event) {
|
||||
case L1CTL_EVENT_DISCONNECT:
|
||||
osmo_fsm_inst_state_chg(trxcon_fsm, TRXCON_STATE_IDLE, 0, 0);
|
||||
|
||||
if (app_data.trx->fsm->state != TRX_STATE_OFFLINE) {
|
||||
/* Reset scheduler and clock counter */
|
||||
sched_trx_reset(app_data.trx, true);
|
||||
|
||||
/* TODO: implement trx_if_reset() */
|
||||
trx_if_cmd_poweroff(app_data.trx);
|
||||
trx_if_cmd_echo(app_data.trx);
|
||||
}
|
||||
break;
|
||||
case TRX_EVENT_RSP_ERROR:
|
||||
case TRX_EVENT_OFFLINE:
|
||||
/* TODO: notify L2 & L3 about that */
|
||||
break;
|
||||
default:
|
||||
LOGPFSML(fi, LOGL_ERROR, "Unhandled event %u\n", event);
|
||||
}
|
||||
}
|
||||
|
||||
static struct osmo_fsm_state trxcon_fsm_states[] = {
|
||||
[TRXCON_STATE_IDLE] = {
|
||||
.in_event_mask = GEN_MASK(L1CTL_EVENT_CONNECT),
|
||||
.out_state_mask = GEN_MASK(TRXCON_STATE_MANAGED),
|
||||
.name = "IDLE",
|
||||
.action = trxcon_fsm_idle_action,
|
||||
},
|
||||
[TRXCON_STATE_MANAGED] = {
|
||||
.in_event_mask = (
|
||||
GEN_MASK(L1CTL_EVENT_DISCONNECT) |
|
||||
GEN_MASK(TRX_EVENT_RSP_ERROR) |
|
||||
GEN_MASK(TRX_EVENT_OFFLINE)),
|
||||
.out_state_mask = GEN_MASK(TRXCON_STATE_IDLE),
|
||||
.name = "MANAGED",
|
||||
.action = trxcon_fsm_managed_action,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct value_string app_evt_names[] = {
|
||||
OSMO_VALUE_STRING(L1CTL_EVENT_CONNECT),
|
||||
OSMO_VALUE_STRING(L1CTL_EVENT_DISCONNECT),
|
||||
OSMO_VALUE_STRING(TRX_EVENT_OFFLINE),
|
||||
OSMO_VALUE_STRING(TRX_EVENT_RSP_ERROR),
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static struct osmo_fsm trxcon_fsm_def = {
|
||||
.name = "trxcon_app_fsm",
|
||||
.states = trxcon_fsm_states,
|
||||
.num_states = ARRAY_SIZE(trxcon_fsm_states),
|
||||
.log_subsys = DAPP,
|
||||
.event_names = app_evt_names,
|
||||
};
|
||||
|
||||
static void print_usage(const char *app)
|
||||
{
|
||||
printf("Usage: %s\n", app);
|
||||
}
|
||||
|
||||
static void print_help(void)
|
||||
{
|
||||
printf(" Some help...\n");
|
||||
printf(" -h --help this text\n");
|
||||
printf(" -d --debug Change debug flags. Default: %s\n", DEBUG_DEFAULT);
|
||||
printf(" -b --trx-bind TRX bind IP address (default 0.0.0.0)\n");
|
||||
printf(" -i --trx-remote TRX remote IP address (default 127.0.0.1)\n");
|
||||
printf(" -p --trx-port Base port of TRX instance (default 6700)\n");
|
||||
printf(" -f --trx-advance Uplink burst scheduling advance (default 3)\n");
|
||||
printf(" -s --socket Listening socket for layer23 (default /tmp/osmocom_l2)\n");
|
||||
printf(" -g --gsmtap-ip The destination IP used for GSMTAP (disabled by default)\n");
|
||||
printf(" -D --daemonize Run as daemon\n");
|
||||
}
|
||||
|
||||
static void handle_options(int argc, char **argv)
|
||||
{
|
||||
while (1) {
|
||||
int option_index = 0, c;
|
||||
static struct option long_options[] = {
|
||||
{"help", 0, 0, 'h'},
|
||||
{"debug", 1, 0, 'd'},
|
||||
{"socket", 1, 0, 's'},
|
||||
{"trx-bind", 1, 0, 'b'},
|
||||
/* NOTE: 'trx-ip' is now an alias for 'trx-remote'
|
||||
* due to backward compatibility reasons! */
|
||||
{"trx-ip", 1, 0, 'i'},
|
||||
{"trx-remote", 1, 0, 'i'},
|
||||
{"trx-port", 1, 0, 'p'},
|
||||
{"trx-advance", 1, 0, 'f'},
|
||||
{"gsmtap-ip", 1, 0, 'g'},
|
||||
{"daemonize", 0, 0, 'D'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "d:b:i:p:f:s:g:Dh",
|
||||
long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
case 'h':
|
||||
print_usage(argv[0]);
|
||||
print_help();
|
||||
exit(0);
|
||||
break;
|
||||
case 'd':
|
||||
app_data.debug_mask = optarg;
|
||||
break;
|
||||
case 'b':
|
||||
app_data.trx_bind_ip = optarg;
|
||||
break;
|
||||
case 'i':
|
||||
app_data.trx_remote_ip = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
app_data.trx_base_port = atoi(optarg);
|
||||
break;
|
||||
case 'f':
|
||||
app_data.trx_fn_advance = atoi(optarg);
|
||||
break;
|
||||
case 's':
|
||||
app_data.bind_socket = optarg;
|
||||
break;
|
||||
case 'g':
|
||||
app_data.gsmtap_ip = optarg;
|
||||
break;
|
||||
case 'D':
|
||||
app_data.daemonize = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void init_defaults(void)
|
||||
{
|
||||
app_data.bind_socket = "/tmp/osmocom_l2";
|
||||
app_data.trx_remote_ip = "127.0.0.1";
|
||||
app_data.trx_bind_ip = "0.0.0.0";
|
||||
app_data.trx_base_port = 6700;
|
||||
app_data.trx_fn_advance = 3;
|
||||
|
||||
app_data.debug_mask = NULL;
|
||||
app_data.gsmtap_ip = NULL;
|
||||
app_data.daemonize = 0;
|
||||
app_data.quit = 0;
|
||||
}
|
||||
|
||||
static void signal_handler(int signum)
|
||||
{
|
||||
fprintf(stderr, "signal %u received\n", signum);
|
||||
|
||||
switch (signum) {
|
||||
case SIGINT:
|
||||
app_data.quit++;
|
||||
break;
|
||||
case SIGABRT:
|
||||
/* in case of abort, we want to obtain a talloc report and
|
||||
* then run default SIGABRT handler, who will generate coredump
|
||||
* and abort the process. abort() should do this for us after we
|
||||
* return, but program wouldn't exit if an external SIGABRT is
|
||||
* received.
|
||||
*/
|
||||
talloc_report_full(tall_trxcon_ctx, stderr);
|
||||
signal(SIGABRT, SIG_DFL);
|
||||
raise(SIGABRT);
|
||||
break;
|
||||
case SIGUSR1:
|
||||
case SIGUSR2:
|
||||
talloc_report_full(tall_trxcon_ctx, stderr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extern void init_external_transceiver(int argc, char **argv);
|
||||
extern void stop_trx();
|
||||
extern volatile bool gshutdown;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
printf("%s", COPYRIGHT);
|
||||
init_defaults();
|
||||
handle_options(argc, argv);
|
||||
|
||||
/* Track the use of talloc NULL memory contexts */
|
||||
talloc_enable_null_tracking();
|
||||
|
||||
/* Init talloc memory management system */
|
||||
tall_trxcon_ctx = talloc_init("trxcon context");
|
||||
msgb_talloc_ctx_init(tall_trxcon_ctx, 0);
|
||||
|
||||
/* Setup signal handlers */
|
||||
// signal(SIGINT, &signal_handler);
|
||||
// signal(SIGABRT, &signal_handler);
|
||||
// signal(SIGUSR1, &signal_handler);
|
||||
// signal(SIGUSR2, &signal_handler);
|
||||
osmo_init_ignore_signals();
|
||||
|
||||
/* Init logging system */
|
||||
trx_log_init(tall_trxcon_ctx, app_data.debug_mask);
|
||||
|
||||
/* Configure pretty logging */
|
||||
log_set_print_extended_timestamp(osmo_stderr_target, 1);
|
||||
log_set_print_category_hex(osmo_stderr_target, 0);
|
||||
log_set_print_category(osmo_stderr_target, 1);
|
||||
log_set_print_level(osmo_stderr_target, 1);
|
||||
|
||||
/* Optional GSMTAP */
|
||||
if (app_data.gsmtap_ip != NULL) {
|
||||
gsmtap = gsmtap_source_init(app_data.gsmtap_ip, GSMTAP_UDP_PORT, 1);
|
||||
if (!gsmtap) {
|
||||
LOGP(DAPP, LOGL_ERROR, "Failed to init GSMTAP\n");
|
||||
goto exit;
|
||||
}
|
||||
/* Suppress ICMP "destination unreachable" errors */
|
||||
gsmtap_source_add_sink(gsmtap);
|
||||
}
|
||||
|
||||
/* Allocate the application state machine */
|
||||
OSMO_ASSERT(osmo_fsm_register(&trxcon_fsm_def) == 0);
|
||||
trxcon_fsm = osmo_fsm_inst_alloc(&trxcon_fsm_def, tall_trxcon_ctx,
|
||||
NULL, LOGL_DEBUG, "main");
|
||||
|
||||
/* Init L1CTL server */
|
||||
app_data.l1l = l1ctl_link_init(tall_trxcon_ctx,
|
||||
app_data.bind_socket);
|
||||
if (app_data.l1l == NULL)
|
||||
goto exit;
|
||||
|
||||
/* Init transceiver interface */
|
||||
app_data.trx = trx_if_open(tall_trxcon_ctx,
|
||||
app_data.trx_bind_ip, app_data.trx_remote_ip,
|
||||
app_data.trx_base_port);
|
||||
if (!app_data.trx)
|
||||
goto exit;
|
||||
|
||||
/* Bind L1CTL with TRX and vice versa */
|
||||
app_data.l1l->trx = app_data.trx;
|
||||
app_data.trx->l1l = app_data.l1l;
|
||||
|
||||
/* Init scheduler */
|
||||
rc = sched_trx_init(app_data.trx, app_data.trx_fn_advance);
|
||||
if (rc)
|
||||
goto exit;
|
||||
|
||||
LOGP(DAPP, LOGL_NOTICE, "Init complete\n");
|
||||
|
||||
if (app_data.daemonize) {
|
||||
rc = osmo_daemonize();
|
||||
if (rc < 0) {
|
||||
perror("Error during daemonize");
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize pseudo-random generator */
|
||||
srand(time(NULL));
|
||||
|
||||
init_external_transceiver(argc, argv);
|
||||
|
||||
while (!app_data.quit)
|
||||
osmo_select_main(0);
|
||||
|
||||
gshutdown = true;
|
||||
stop_trx();
|
||||
|
||||
|
||||
exit:
|
||||
/* Close active connections */
|
||||
l1ctl_link_shutdown(app_data.l1l);
|
||||
sched_trx_shutdown(app_data.trx);
|
||||
trx_if_close(app_data.trx);
|
||||
|
||||
/* Shutdown main state machine */
|
||||
osmo_fsm_inst_free(trxcon_fsm);
|
||||
|
||||
/* Deinitialize logging */
|
||||
log_fini();
|
||||
|
||||
/**
|
||||
* Print report for the root talloc context in order
|
||||
* to be able to find and fix potential memory leaks.
|
||||
*/
|
||||
talloc_report_full(tall_trxcon_ctx, stderr);
|
||||
talloc_free(tall_trxcon_ctx);
|
||||
|
||||
/* Make both Valgrind and ASAN happy */
|
||||
talloc_report_full(NULL, stderr);
|
||||
talloc_disable_null_tracking();
|
||||
|
||||
return rc;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#define GEN_MASK(state) (0x01 << state)
|
||||
|
||||
extern struct osmo_fsm_inst *trxcon_fsm;
|
||||
extern struct gsmtap_inst *gsmtap;
|
||||
|
||||
enum trxcon_fsm_states {
|
||||
TRXCON_STATE_IDLE = 0,
|
||||
TRXCON_STATE_MANAGED,
|
||||
};
|
||||
|
||||
enum trxcon_fsm_events {
|
||||
/* L1CTL specific events */
|
||||
L1CTL_EVENT_CONNECT,
|
||||
L1CTL_EVENT_DISCONNECT,
|
||||
|
||||
/* TRX specific events */
|
||||
TRX_EVENT_RSP_ERROR,
|
||||
TRX_EVENT_OFFLINE,
|
||||
};
|
Loading…
Reference in New Issue