trxcon 0f64b7c12404c0ed45779b165270f40da4970b99

Change-Id: Ib267204c93586bfe305bd139dd7dc2c209520ffd
This commit is contained in:
Eric Wild 2022-10-27 16:54:09 +02:00
parent 0ded5a07ac
commit e29b3c8151
50 changed files with 7498 additions and 7822 deletions

27
trxcon/.gitignore vendored
View File

@ -1,27 +0,0 @@
# 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

View File

@ -1,58 +1,21 @@
#AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
include $(top_srcdir)/Makefile.common
SUBDIRS = \
include \
src \
$(NULL)
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
AM_CXXFLAGS = -Wall -O3 -g -ldl -lpthread
ACLOCAL_AMFLAGS = -I m4
BUILT_SOURCES = \
$(top_srcdir)/.version \
$(NULL)
EXTRA_DIST = \
.version \
$(NULL)
# 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)

45
trxcon/configure.ac Normal file
View File

@ -0,0 +1,45 @@
dnl Process this file with autoconf to produce a configure script
AC_INIT([trxcon], [0.0.0])
AM_INIT_AUTOMAKE
LT_INIT
CFLAGS="$CFLAGS -std=gnu11"
dnl kernel style compile messages
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl checks for programs
AC_PROG_MAKE_SET
AC_PROG_CC
AC_PROG_INSTALL
dnl checks for libraries
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore)
PKG_CHECK_MODULES(LIBOSMOCODING, libosmocoding)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm)
dnl checks for header files
AC_HEADER_STDC
AC_ARG_ENABLE(sanitize,
[AS_HELP_STRING(
[--enable-sanitize],
[Compile with address sanitizer enabled],
)], [sanitize=$enableval], [sanitize="no"])
if test x"$sanitize" = x"yes"
then
CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined"
CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
fi
dnl Checks for typedefs, structures and compiler characteristics
AC_CONFIG_MACRO_DIRS([m4])
AC_CONFIG_FILES([include/Makefile
include/osmocom/Makefile
include/osmocom/bb/Makefile
include/osmocom/bb/l1sched/Makefile
include/osmocom/bb/trxcon/Makefile
src/Makefile
Makefile])
AC_OUTPUT

View File

@ -0,0 +1,3 @@
SUBDIRS = \
osmocom \
$(NULL)

View File

@ -0,0 +1,3 @@
SUBDIRS = \
bb \
$(NULL)

View File

@ -0,0 +1,4 @@
SUBDIRS = \
l1sched \
trxcon \
$(NULL)

View File

@ -0,0 +1,4 @@
noinst_HEADERS = \
l1sched.h \
logging.h \
$(NULL)

View File

@ -0,0 +1,508 @@
#pragma once
#include <time.h>
#include <stdint.h>
#include <stdbool.h>
#include <arpa/inet.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/gsm/gsm0502.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/timer.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 L1SCHED_CH_LID_DEDIC 0x00
#define L1SCHED_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 L1SCHED_CH_LID_PTCCH 0x80
/* Is a channel related to PDCH (GPRS) */
#define L1SCHED_CH_FLAG_PDCH (1 << 0)
/* Should a channel be activated automatically */
#define L1SCHED_CH_FLAG_AUTO (1 << 1)
/* Is continuous burst transmission assumed */
#define L1SCHED_CH_FLAG_CBTX (1 << 2)
#define MAX_A5_KEY_LEN (128 / 8)
#define TRX_TS_COUNT 8
struct l1sched_lchan_state;
struct l1sched_meas_set;
struct l1sched_state;
struct l1sched_ts;
enum l1sched_clck_state {
L1SCHED_CLCK_ST_WAIT,
L1SCHED_CLCK_ST_OK,
};
enum l1sched_burst_type {
L1SCHED_BURST_GMSK,
L1SCHED_BURST_8PSK,
};
enum l1sched_ts_prim_type {
L1SCHED_PRIM_DATA,
L1SCHED_PRIM_RACH8,
L1SCHED_PRIM_RACH11,
};
/**
* These types define the different channels on a multiframe.
* Each channel has queues and can be activated individually.
*/
enum l1sched_lchan_type {
L1SCHED_IDLE = 0,
L1SCHED_FCCH,
L1SCHED_SCH,
L1SCHED_BCCH,
L1SCHED_RACH,
L1SCHED_CCCH,
L1SCHED_TCHF,
L1SCHED_TCHH_0,
L1SCHED_TCHH_1,
L1SCHED_SDCCH4_0,
L1SCHED_SDCCH4_1,
L1SCHED_SDCCH4_2,
L1SCHED_SDCCH4_3,
L1SCHED_SDCCH8_0,
L1SCHED_SDCCH8_1,
L1SCHED_SDCCH8_2,
L1SCHED_SDCCH8_3,
L1SCHED_SDCCH8_4,
L1SCHED_SDCCH8_5,
L1SCHED_SDCCH8_6,
L1SCHED_SDCCH8_7,
L1SCHED_SACCHTF,
L1SCHED_SACCHTH_0,
L1SCHED_SACCHTH_1,
L1SCHED_SACCH4_0,
L1SCHED_SACCH4_1,
L1SCHED_SACCH4_2,
L1SCHED_SACCH4_3,
L1SCHED_SACCH8_0,
L1SCHED_SACCH8_1,
L1SCHED_SACCH8_2,
L1SCHED_SACCH8_3,
L1SCHED_SACCH8_4,
L1SCHED_SACCH8_5,
L1SCHED_SACCH8_6,
L1SCHED_SACCH8_7,
L1SCHED_PDTCH,
L1SCHED_PTCCH,
L1SCHED_SDCCH4_CBCH,
L1SCHED_SDCCH8_CBCH,
_L1SCHED_CHAN_MAX
};
enum l1sched_data_type {
L1SCHED_DT_PACKET_DATA,
L1SCHED_DT_SIGNALING,
L1SCHED_DT_TRAFFIC,
L1SCHED_DT_OTHER, /* SCH and RACH */
};
enum l1sched_config_type {
/*! Channel combination for a timeslot */
L1SCHED_CFG_PCHAN_COMB,
};
/* Represents a (re)configuration request */
struct l1sched_config_req {
enum l1sched_config_type type;
union {
struct {
uint8_t tn;
enum gsm_phys_chan_config pchan;
} pchan_comb;
};
};
/* Represents a burst to be transmitted */
struct l1sched_burst_req {
uint32_t fn;
uint8_t tn;
uint8_t pwr;
/* Internally used by the scheduler */
uint8_t bid;
ubit_t burst[EDGE_BURST_LEN];
size_t burst_len;
};
typedef int l1sched_lchan_rx_func(struct l1sched_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
const struct l1sched_meas_set *meas);
typedef int l1sched_lchan_tx_func(struct l1sched_lchan_state *lchan,
struct l1sched_burst_req *br);
struct l1sched_lchan_desc {
/*! Human-readable name */
const char *name;
/*! Human-readable description */
const char *desc;
/*! Channel Number (like in RSL) */
uint8_t chan_nr;
/*! Link ID (like in RSL) */
uint8_t link_id;
/*! Sub-slot number (for SDCCH and TCH/H) */
uint8_t ss_nr;
/*! GSMTAP channel type (see GSMTAP_CHANNEL_*) */
uint8_t gsmtap_chan_type;
/*! How much memory do we need to store bursts */
size_t burst_buf_size;
/*! Channel specific flags */
uint8_t flags;
/*! Function to call when burst received from PHY */
l1sched_lchan_rx_func *rx_fn;
/*! Function to call when data received from L2 */
l1sched_lchan_tx_func *tx_fn;
};
struct l1sched_tdma_frame {
/*! Downlink channel (slot) type */
enum l1sched_lchan_type dl_chan;
/*! Downlink block ID */
uint8_t dl_bid;
/*! Uplink channel (slot) type */
enum l1sched_lchan_type ul_chan;
/*! Uplink block ID */
uint8_t ul_bid;
};
struct l1sched_tdma_multiframe {
/*! Channel combination */
enum gsm_phys_chan_config chan_config;
/*! Human-readable name */
const char *name;
/*! Repeats how many frames */
uint8_t period;
/*! Applies to which timeslots */
uint8_t slotmask;
/*! Contains which lchans */
uint64_t lchan_mask;
/*! Pointer to scheduling structure */
const struct l1sched_tdma_frame *frames;
};
struct l1sched_meas_set {
/*! TDMA frame number of the first burst this set belongs to */
uint32_t fn;
/*! ToA256 (Timing of Arrival, 1/256 of a symbol) */
int16_t toa256;
/*! RSSI (Received Signal Strength Indication) */
int8_t rssi;
};
/* Simple ring buffer (up to 8 unique measurements) */
struct l1sched_lchan_meas_hist {
struct l1sched_meas_set buf[8];
struct l1sched_meas_set *head;
};
/* States each channel on a multiframe */
struct l1sched_lchan_state {
/*! Channel type */
enum l1sched_lchan_type type;
/*! Channel status */
uint8_t active;
/*! Link to a list of channels */
struct llist_head list;
/*! Burst type: GMSK or 8PSK */
enum l1sched_burst_type burst_type;
/*! Mask of received bursts */
uint8_t rx_burst_mask;
/*! Mask of transmitted bursts */
uint8_t tx_burst_mask;
/*! Burst buffer for RX */
sbit_t *rx_bursts;
/*! Burst buffer for TX */
ubit_t *tx_bursts;
/*! A primitive being sent */
struct l1sched_ts_prim *prim;
/*! Mode for TCH channels (see GSM48_CMODE_*) */
uint8_t tch_mode;
/*! Training Sequence Code */
uint8_t tsc;
/*! FACCH/H on downlink */
bool dl_ongoing_facch;
/*! pending FACCH/H blocks on Uplink */
uint8_t ul_facch_blocks;
/*! Downlink measurements history */
struct l1sched_lchan_meas_hist meas_hist;
/*! AVG measurements of the last received block */
struct l1sched_meas_set meas_avg;
/*! TDMA loss detection state */
struct {
/*! Last processed TDMA frame number */
uint32_t last_proc;
/*! Number of processed TDMA frames */
unsigned long num_proc;
/*! Number of lost TDMA frames */
unsigned long num_lost;
} tdma;
/*! SACCH state */
struct {
/*! Cached measurement report (last received) */
uint8_t mr_cache[GSM_MACBLOCK_LEN];
/*! Cache usage counter */
uint8_t mr_cache_usage;
/*! Was a MR transmitted last time? */
bool mr_tx_last;
} sacch;
/* AMR specific */
struct {
/*! 4 possible codecs for AMR */
uint8_t codec[4];
/*! Number of possible codecs */
uint8_t codecs;
/*! Current uplink FT index */
uint8_t ul_ft;
/*! Current downlink FT index */
uint8_t dl_ft;
/*! Current uplink CMR index */
uint8_t ul_cmr;
/*! Current downlink CMR index */
uint8_t dl_cmr;
/*! If AMR loop is enabled */
uint8_t amr_loop;
/*! Number of bit error rates */
uint8_t ber_num;
/*! Sum of bit error rates */
float ber_sum;
/* last received dtx frame type */
uint8_t last_dtx;
} amr;
/*! 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 l1sched_ts *ts;
};
struct l1sched_ts {
/*! Timeslot index within a frame (0..7) */
uint8_t index;
/*! Pointer to multiframe layout */
const struct l1sched_tdma_multiframe *mf_layout;
/*! Channel states for logical channels */
struct llist_head lchans;
/*! Queue primitives for TX */
struct llist_head tx_prims;
/*! Backpointer to the scheduler */
struct l1sched_state *sched;
};
/* Represents one TX primitive in the queue of l1sched_ts */
struct l1sched_ts_prim {
/*! Link to queue of TS */
struct llist_head list;
/*! Type of primitive */
enum l1sched_ts_prim_type type;
/*! Logical channel type */
enum l1sched_lchan_type chan;
/*! Payload length */
size_t payload_len;
/*! Payload */
uint8_t payload[0];
};
/*! Represents a RACH (8-bit or 11-bit) primitive */
struct l1sched_ts_prim_rach {
/*! RA value */
uint16_t ra;
/*! Training Sequence (only for 11-bit RA) */
uint8_t synch_seq;
/*! Transmission offset (how many frames to skip) */
uint8_t offset;
};
/*! Scheduler configuration */
struct l1sched_cfg {
/*! Logging context (used as prefix for messages) */
const char *log_prefix;
/*! TDMA frame-number advance */
uint32_t fn_advance;
};
/*! One scheduler instance */
struct l1sched_state {
/*! Clock state */
enum l1sched_clck_state clck_state;
/*! Local clock source */
struct timespec clock;
/*! Count of processed frames */
uint32_t fn_counter_proc;
/*! Local frame counter advance */
uint32_t fn_counter_advance;
/*! Count of lost frames */
uint32_t fn_counter_lost;
/*! Frame callback timer */
struct osmo_timer_list clock_timer;
/*! Frame callback */
void (*clock_cb)(struct l1sched_state *sched);
/*! List of timeslots maintained by this scheduler */
struct l1sched_ts *ts[TRX_TS_COUNT];
/*! SACCH cache (common for all lchans) */
uint8_t sacch_cache[GSM_MACBLOCK_LEN];
/*! BSIC value learned from SCH bursts */
uint8_t bsic;
/*! Logging context (used as prefix for messages) */
const char *log_prefix;
/*! Some private data */
void *priv;
};
extern const struct l1sched_lchan_desc l1sched_lchan_desc[_L1SCHED_CHAN_MAX];
const struct l1sched_tdma_multiframe *l1sched_mframe_layout(
enum gsm_phys_chan_config config, int tn);
/* Scheduler management functions */
void l1sched_logging_init(int log_cat_common, int log_cat_data);
struct l1sched_state *l1sched_alloc(void *ctx, const struct l1sched_cfg *cfg, void *priv);
void l1sched_reset(struct l1sched_state *sched, bool reset_clock);
void l1sched_free(struct l1sched_state *sched);
/* Timeslot management functions */
struct l1sched_ts *l1sched_add_ts(struct l1sched_state *sched, int tn);
void l1sched_del_ts(struct l1sched_state *sched, int tn);
int l1sched_reset_ts(struct l1sched_state *sched, int tn);
int l1sched_configure_ts(struct l1sched_state *sched, int tn,
enum gsm_phys_chan_config config);
int l1sched_start_ciphering(struct l1sched_ts *ts, uint8_t algo,
const uint8_t *key, uint8_t key_len);
/* Logical channel management functions */
enum gsm_phys_chan_config l1sched_chan_nr2pchan_config(uint8_t chan_nr);
enum l1sched_lchan_type l1sched_chan_nr2lchan_type(uint8_t chan_nr,
uint8_t link_id);
void l1sched_deactivate_all_lchans(struct l1sched_ts *ts);
int l1sched_set_lchans(struct l1sched_ts *ts, uint8_t chan_nr,
int active, uint8_t tch_mode, uint8_t tsc);
int l1sched_activate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan);
int l1sched_deactivate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan);
struct l1sched_lchan_state *l1sched_find_lchan(struct l1sched_ts *ts,
enum l1sched_lchan_type chan);
/* Primitive management functions */
struct l1sched_ts_prim *l1sched_prim_push(struct l1sched_state *sched,
enum l1sched_ts_prim_type type,
uint8_t chan_nr, uint8_t link_id,
const uint8_t *pl, size_t pl_len);
#define L1SCHED_TCH_MODE_IS_SPEECH(mode) \
(mode == GSM48_CMODE_SPEECH_V1 \
|| mode == GSM48_CMODE_SPEECH_EFR \
|| mode == GSM48_CMODE_SPEECH_AMR)
#define L1SCHED_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 L1SCHED_CHAN_IS_TCH(chan) \
(chan == L1SCHED_TCHF || chan == L1SCHED_TCHH_0 || chan == L1SCHED_TCHH_1)
#define L1SCHED_CHAN_IS_SACCH(chan) \
(l1sched_lchan_desc[chan].link_id & L1SCHED_CH_LID_SACCH)
#define L1SCHED_PRIM_IS_RACH11(prim) \
(prim->type == L1SCHED_PRIM_RACH11)
#define L1SCHED_PRIM_IS_RACH8(prim) \
(prim->type == L1SCHED_PRIM_RACH8)
#define L1SCHED_PRIM_IS_RACH(prim) \
(L1SCHED_PRIM_IS_RACH8(prim) || L1SCHED_PRIM_IS_RACH11(prim))
#define L1SCHED_PRIM_IS_TCH(prim) \
(L1SCHED_CHAN_IS_TCH(prim->chan) && prim->payload_len != GSM_MACBLOCK_LEN)
#define L1SCHED_PRIM_IS_FACCH(prim) \
(L1SCHED_CHAN_IS_TCH(prim->chan) && prim->payload_len == GSM_MACBLOCK_LEN)
struct l1sched_ts_prim *l1sched_prim_dequeue(struct llist_head *queue,
uint32_t fn, struct l1sched_lchan_state *lchan);
int l1sched_prim_dummy(struct l1sched_lchan_state *lchan);
void l1sched_prim_drop(struct l1sched_lchan_state *lchan);
void l1sched_prim_flush_queue(struct llist_head *list);
int l1sched_handle_rx_burst(struct l1sched_state *sched, uint8_t tn,
uint32_t fn, sbit_t *bits, uint16_t nbits,
const struct l1sched_meas_set *meas);
/* Shared declarations for lchan handlers */
extern const uint8_t l1sched_nb_training_bits[8][26];
const char *l1sched_burst_mask2str(const uint8_t *mask, int bits);
size_t l1sched_bad_frame_ind(uint8_t *l2, struct l1sched_lchan_state *lchan);
/* Interleaved TCH/H block TDMA frame mapping */
bool l1sched_tchh_block_map_fn(enum l1sched_lchan_type chan,
uint32_t fn, bool ul, bool facch, bool start);
#define l1sched_tchh_traffic_start(chan, fn, ul) \
l1sched_tchh_block_map_fn(chan, fn, ul, 0, 1)
#define l1sched_tchh_traffic_end(chan, fn, ul) \
l1sched_tchh_block_map_fn(chan, fn, ul, 0, 0)
#define l1sched_tchh_facch_start(chan, fn, ul) \
l1sched_tchh_block_map_fn(chan, fn, ul, 1, 1)
#define l1sched_tchh_facch_end(chan, fn, ul) \
l1sched_tchh_block_map_fn(chan, fn, ul, 1, 0)
/* Measurement history */
void l1sched_lchan_meas_push(struct l1sched_lchan_state *lchan, const struct l1sched_meas_set *meas);
void l1sched_lchan_meas_avg(struct l1sched_lchan_state *lchan, unsigned int n);
int l1sched_clck_handle(struct l1sched_state *sched, uint32_t fn);
void l1sched_clck_reset(struct l1sched_state *sched);
/* External L1 API, must be implemented by the API user */
int l1sched_handle_config_req(struct l1sched_state *sched,
const struct l1sched_config_req *cr);
int l1sched_handle_burst_req(struct l1sched_state *sched,
const struct l1sched_burst_req *br);
/* External L2 API, must be implemented by the API user */
int l1sched_handle_data_ind(struct l1sched_lchan_state *lchan,
const uint8_t *data, size_t data_len,
int n_errors, int n_bits_total,
enum l1sched_data_type dt);
int l1sched_handle_data_cnf(struct l1sched_lchan_state *lchan,
uint32_t fn, enum l1sched_data_type dt);

View File

@ -0,0 +1,35 @@
#pragma once
extern int l1sched_log_cat_common;
extern int l1sched_log_cat_data;
/* Messages using l1sched_state as the context */
#define LOGP_SCHED_CAT(sched, cat, level, fmt, args...) \
LOGP(l1sched_log_cat_##cat, level, "%s" fmt, \
(sched)->log_prefix, ## args)
/* Common messages using l1sched_state as the context */
#define LOGP_SCHEDC(sched, level, fmt, args...) \
LOGP_SCHED_CAT(sched, common, level, fmt, ## args)
/* Data messages using l1sched_state as the context */
#define LOGP_SCHEDD(sched, level, fmt, args...) \
LOGP_SCHED_CAT(sched, common, level, fmt, ## args)
#define LOGP_LCHAN_NAME_FMT "TS%u-%s"
#define LOGP_LCHAN_NAME_ARGS(lchan) \
(lchan)->ts->index, l1sched_lchan_desc[(lchan)->type].name
/* Messages using l1sched_lchan_state as the context */
#define LOGP_LCHAN_CAT(lchan, cat, level, fmt, args...) \
LOGP_SCHED_CAT((lchan)->ts->sched, cat, level, LOGP_LCHAN_NAME_FMT " " fmt, \
LOGP_LCHAN_NAME_ARGS(lchan), ## args)
/* Common messages using l1sched_lchan_state as the context */
#define LOGP_LCHANC(lchan, level, fmt, args...) \
LOGP_LCHAN_CAT(lchan, common, level, fmt, ## args)
/* Data messages using l1sched_lchan_state as the context */
#define LOGP_LCHAND(lchan, level, fmt, args...) \
LOGP_LCHAN_CAT(lchan, data, level, fmt, ## args)

View File

@ -0,0 +1,9 @@
noinst_HEADERS = \
l1ctl_proto.h \
l1ctl_server.h \
l1ctl.h \
sched_utils.h \
trx_if.h \
logging.h \
trxcon.h \
$(NULL)

View File

@ -0,0 +1,27 @@
#pragma once
#include <stdint.h>
#include <osmocom/core/msgb.h>
#include <osmocom/bb/trxcon/l1ctl_server.h>
#include <osmocom/bb/trxcon/l1ctl_proto.h>
/* Event handlers */
int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg);
int l1ctl_tx_fbsb_conf(struct l1ctl_client *l1c, uint16_t band_arfcn, uint8_t bsic);
int l1ctl_tx_fbsb_fail(struct l1ctl_client *l1c, uint16_t band_arfcn);
int l1ctl_tx_ccch_mode_conf(struct l1ctl_client *l1c, uint8_t mode);
int l1ctl_tx_pm_conf(struct l1ctl_client *l1c, uint16_t band_arfcn,
int dbm, int last);
int l1ctl_tx_reset_conf(struct l1ctl_client *l1c, uint8_t type);
int l1ctl_tx_reset_ind(struct l1ctl_client *l1c, uint8_t type);
int l1ctl_tx_dt_ind(struct l1ctl_client *l1c,
const struct l1ctl_info_dl *dl_info,
const uint8_t *l2, size_t l2_len,
bool traffic);
int l1ctl_tx_dt_conf(struct l1ctl_client *l1c,
struct l1ctl_info_dl *data, bool traffic);
int l1ctl_tx_rach_conf(struct l1ctl_client *l1c,
uint16_t band_arfcn, uint32_t fn);

View File

@ -0,0 +1,67 @@
#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>
#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
struct l1ctl_client;
typedef int l1ctl_conn_data_func(struct l1ctl_client *, struct msgb *);
typedef void l1ctl_conn_state_func(struct l1ctl_client *);
struct l1ctl_server_cfg {
/* UNIX socket path to listen on */
const char *sock_path;
/* maximum number of connected clients */
unsigned int num_clients_max;
/* functions to be called on various events */
l1ctl_conn_data_func *conn_read_cb; /* mandatory */
l1ctl_conn_state_func *conn_accept_cb; /* optional */
l1ctl_conn_state_func *conn_close_cb; /* optional */
};
struct l1ctl_server {
/* list of connected clients */
struct llist_head clients;
/* number of connected clients */
unsigned int num_clients;
/* used for client ID generation */
unsigned int next_client_id;
/* socket on which we listen for connections */
struct osmo_fd ofd;
/* server configuration */
const struct l1ctl_server_cfg *cfg;
};
struct l1ctl_client {
/* list head in l1ctl_server.clients */
struct llist_head list;
/* struct l1ctl_server we belong to */
struct l1ctl_server *server;
/* client's write queue */
struct osmo_wqueue wq;
/* logging context (used as prefix for messages) */
const char *log_prefix;
/* unique client ID */
unsigned int id;
/* some private data */
void *priv;
};
struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg);
void l1ctl_server_free(struct l1ctl_server *server);
int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg);
void l1ctl_client_conn_close(struct l1ctl_client *client);

View File

@ -2,13 +2,11 @@
#include <osmocom/core/logging.h>
#define DEBUG_DEFAULT "DAPP:DL1C:DL1D:DTRX:DTRXD:DSCH:DSCHD"
enum {
DAPP,
DL1C,
DL1D,
DTRX,
DTRXC,
DTRXD,
DSCH,
DSCHD,

View File

@ -0,0 +1,62 @@
/* Auxiliary scheduler utilities.
*
* (C) 2017 by Harald Welte <laforge@gnumonks.org>
* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU 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/>.
*
*/
#pragma once
#include <stdint.h>
#include <errno.h>
#include <stdbool.h>
/*! determine whether an uplink AMR block is CMI according to 3GPP TS 45.009.
* \param[in] fn_begin frame number of the beginning of the block.
* \returns true in case of CMI; false otherwise. */
static inline bool ul_amr_fn_is_cmi(uint32_t fn_begin)
{
switch (fn_begin % 26) {
/*! See also: 3GPP TS 45.009, section 3.2.1.3 Transmitter/Receiver Synchronisation */
/* valid for AHS subslot 0 and AFS: */
case 0:
case 8:
case 17:
/* valid for AHS subslot 1: */
case 1:
case 9:
case 18:
return true;
break;
/* Complementary values for sanity check */
/* valid for AHS subslot 0 and AFS: */
case 4:
case 13:
case 21:
/* valid for AHS subslot 1: */
case 5:
case 14:
case 22:
return false;
break;
default:
OSMO_ASSERT(false);
return false;
break;
}
}

View File

@ -5,14 +5,14 @@
#include <osmocom/core/timer.h>
#include <osmocom/core/fsm.h>
#include "scheduler.h"
#include "sched_trx.h"
#include <osmocom/gsm/gsm_utils.h>
#define TRXC_BUF_SIZE 1024
#define TRXD_BUF_SIZE 512
/* Forward declaration to avoid mutual include */
struct l1ctl_link;
struct l1sched_burst_req;
struct trxcon_inst;
enum trx_fsm_states {
TRX_STATE_OFFLINE = 0,
@ -22,14 +22,15 @@ enum trx_fsm_states {
};
struct trx_instance {
#ifndef IPCIF
/* trxcon instance we belong to */
struct trxcon_inst *trxcon;
struct osmo_fd trx_ofd_ctrl;
struct osmo_fd trx_ofd_data;
#endif
struct osmo_timer_list trx_ctrl_timer;
struct llist_head trx_ctrl_list;
struct osmo_fsm_inst *fsm;
struct osmo_fsm_inst *fi;
/* HACK: we need proper state machines */
uint32_t prev_state;
@ -38,18 +39,6 @@ struct trx_instance {
/* 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 {
@ -60,7 +49,7 @@ struct trx_ctrl_msg {
int cmd_len;
};
struct trx_instance *trx_if_open(void *tall_ctx,
struct trx_instance *trx_if_open(struct trxcon_inst *trxcon,
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);
@ -68,19 +57,19 @@ 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_setslot(struct trx_instance *trx, uint8_t tn,
enum gsm_phys_chan_config pchan);
int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn, uint8_t maio,
const 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);
int trx_if_tx_burst(struct trx_instance *trx,
const struct l1sched_burst_req *br);

View File

@ -0,0 +1,169 @@
#pragma once
struct l1sched_state;
extern struct osmo_fsm trxcon_fsm_def;
enum trxcon_fsm_states {
TRXCON_ST_RESET,
TRXCON_ST_FULL_POWER_SCAN,
TRXCON_ST_FBSB_SEARCH,
TRXCON_ST_BCCH_CCCH,
TRXCON_ST_DEDICATED,
TRXCON_ST_PACKET_DATA,
};
enum trxcon_fsm_events {
TRXCON_EV_PHYIF_FAILURE,
TRXCON_EV_L2IF_FAILURE,
TRXCON_EV_RESET_FULL_REQ,
TRXCON_EV_RESET_SCHED_REQ,
TRXCON_EV_FULL_POWER_SCAN_REQ,
TRXCON_EV_FULL_POWER_SCAN_RES,
TRXCON_EV_FBSB_SEARCH_REQ,
TRXCON_EV_FBSB_SEARCH_RES,
TRXCON_EV_SET_CCCH_MODE_REQ,
TRXCON_EV_SET_TCH_MODE_REQ,
TRXCON_EV_SET_PHY_CONFIG_REQ,
TRXCON_EV_TX_ACCESS_BURST_REQ,
TRXCON_EV_UPDATE_SACCH_CACHE_REQ,
TRXCON_EV_DEDICATED_ESTABLISH_REQ,
TRXCON_EV_DEDICATED_RELEASE_REQ,
TRXCON_EV_TX_TRAFFIC_REQ,
TRXCON_EV_RX_TRAFFIC_IND,
TRXCON_EV_TX_DATA_REQ,
TRXCON_EV_RX_DATA_IND,
TRXCON_EV_CRYPTO_REQ,
};
/* param of TRXCON_EV_FULL_POWER_SCAN_REQ */
struct trxcon_param_full_power_scan_req {
uint16_t band_arfcn_start;
uint16_t band_arfcn_stop;
};
/* param of TRXCON_EV_FULL_POWER_SCAN_RES */
struct trxcon_param_full_power_scan_res {
bool last_result;
uint16_t band_arfcn;
int dbm;
};
/* param of TRXCON_EV_FBSB_SEARCH_REQ */
struct trxcon_param_fbsb_search_req {
uint16_t band_arfcn;
uint16_t timeout_ms;
uint8_t pchan_config;
};
/* param of TRXCON_EV_SET_{CCCH,TCH}_MODE_REQ */
struct trxcon_param_set_ccch_tch_mode_req {
uint8_t mode;
struct {
uint8_t start_codec;
uint8_t codecs_bitmask;
} amr;
bool applied;
};
/* param of TRXCON_EV_SET_PHY_CONFIG_REQ */
struct trxcon_param_set_phy_config_req {
enum {
TRXCON_PHY_CFGT_PCHAN_COMB,
TRXCON_PHY_CFGT_TX_PARAMS,
} type;
union {
struct {
uint8_t tn;
uint8_t pchan;
} pchan_comb;
struct {
uint8_t timing_advance;
uint8_t tx_power;
} tx_params;
};
};
/* param of TRXCON_EV_TX_{TRAFFIC,DATA}_REQ */
struct trxcon_param_tx_traffic_data_req {
uint8_t chan_nr;
uint8_t link_id;
size_t data_len;
const uint8_t *data;
};
/* param of TRXCON_EV_RX_{TRAFFIC,DATA}_IND */
struct trxcon_param_rx_traffic_data_ind {
uint8_t chan_nr;
uint8_t link_id;
uint32_t frame_nr;
int16_t toa256;
int8_t rssi;
int n_errors;
int n_bits_total;
size_t data_len;
const uint8_t *data;
};
/* param of TRXCON_EV_TX_ACCESS_BURST_REQ */
struct trxcon_param_tx_access_burst_req {
uint8_t chan_nr;
uint8_t link_id;
uint8_t offset;
uint8_t synch_seq;
uint16_t ra;
bool is_11bit;
};
/* param of TRXCON_EV_DEDICATED_ESTABLISH_REQ */
struct trxcon_param_dedicated_establish_req {
uint8_t chan_nr;
uint8_t tch_mode;
uint8_t tsc;
bool hopping;
union {
struct { /* hopping=false */
uint16_t band_arfcn;
} h0;
struct { /* hopping=true */
uint8_t hsn;
uint8_t maio;
uint8_t n;
uint16_t ma[64];
} h1;
};
};
/* param of TRXCON_EV_CRYPTO_REQ */
struct trxcon_param_crypto_req {
uint8_t chan_nr;
uint8_t a5_algo; /* 0 is A5/0 */
uint8_t key_len;
const uint8_t *key;
};
struct trxcon_inst {
struct osmo_fsm_inst *fi;
unsigned int id;
/* Logging context for sched and l1c */
const char *log_prefix;
/* The L1 scheduler */
struct l1sched_state *sched;
/* PHY interface (e.g. TRXC/TRXD) */
void *phyif;
/* L2 interface (e.g. L1CTL) */
void *l2if;
/* L1 parameters */
struct {
uint16_t band_arfcn;
uint8_t tx_power;
int8_t ta;
} l1p;
};
struct trxcon_inst *trxcon_inst_alloc(void *ctx, unsigned int id);
void trxcon_inst_free(struct trxcon_inst *trxcon);

View File

@ -1,917 +0,0 @@
/*
* 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, 2, 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);
}

View File

@ -1,26 +0,0 @@
#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);

View File

@ -1,316 +0,0 @@
/*
* 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);
}

View File

@ -1,48 +0,0 @@
#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);

View File

@ -1,387 +0,0 @@
/* 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__ */

View File

@ -1,232 +0,0 @@
/*
* 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;
}
}

View File

@ -1,303 +0,0 @@
/*
* 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;
}

View File

@ -1,501 +0,0 @@
/*
* 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;
}

View File

@ -1,217 +0,0 @@
/*
* 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

View File

@ -1,405 +0,0 @@
#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);

View File

@ -1,38 +0,0 @@
#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);

53
trxcon/src/Makefile.am Normal file
View File

@ -0,0 +1,53 @@
AM_CPPFLAGS = \
$(all_includes) \
-I$(top_srcdir)/include \
$(NULL)
AM_CFLAGS = \
-Wall \
$(LIBOSMOCORE_CFLAGS) \
$(LIBOSMOCODING_CFLAGS) \
$(LIBOSMOGSM_CFLAGS) \
$(NULL)
noinst_LTLIBRARIES = libl1sched.la
libl1sched_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)
libl1sched_la_LIBADD = \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOCODING_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(NULL)
bin_PROGRAMS = trxcon
trxcon_SOURCES = \
l1ctl_server.c \
l1ctl.c \
trx_if.c \
logging.c \
trxcon_fsm.c \
trxcon.c \
$(NULL)
trxcon_LDADD = \
libl1sched.la \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(NULL)

807
trxcon/src/l1ctl.c Normal file
View File

@ -0,0 +1,807 @@
/*
* OsmocomBB <-> SDR connection bridge
* GSM L1 control interface handlers
*
* (C) 2014 by Sylvain Munaut <tnt@246tNt.com>
* (C) 2016-2022 by Vadim Yanitskiy <axilirator@gmail.com>
* Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <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/fsm.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/gsm/gsm0502.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include <osmocom/bb/trxcon/logging.h>
#include <osmocom/bb/trxcon/l1ctl_server.h>
#include <osmocom/bb/trxcon/l1ctl_proto.h>
#include <osmocom/bb/trxcon/trxcon.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_client *l1c, uint16_t band_arfcn,
int dbm, int last)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct l1ctl_pm_conf *pmc;
struct msgb *msg;
msg = l1ctl_alloc_msg(L1CTL_PM_CONF);
if (!msg)
return -ENOMEM;
LOGPFSMSL(fi, 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_client_send(l1c, msg);
}
int l1ctl_tx_reset_ind(struct l1ctl_client *l1c, uint8_t type)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct msgb *msg;
struct l1ctl_reset *res;
msg = l1ctl_alloc_msg(L1CTL_RESET_IND);
if (!msg)
return -ENOMEM;
LOGPFSMSL(fi, DL1C, LOGL_DEBUG, "Send Reset Ind (%u)\n", type);
res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
res->type = type;
return l1ctl_client_send(l1c, msg);
}
int l1ctl_tx_reset_conf(struct l1ctl_client *l1c, uint8_t type)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct msgb *msg;
struct l1ctl_reset *res;
msg = l1ctl_alloc_msg(L1CTL_RESET_CONF);
if (!msg)
return -ENOMEM;
LOGPFSMSL(fi, DL1C, LOGL_DEBUG, "Send Reset Conf (%u)\n", type);
res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
res->type = type;
return l1ctl_client_send(l1c, msg);
}
static struct l1ctl_info_dl *put_dl_info_hdr(struct msgb *msg,
const 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));
conf->result = result;
conf->bsic = bsic;
return conf;
}
int l1ctl_tx_fbsb_fail(struct l1ctl_client *l1c, uint16_t band_arfcn)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct l1ctl_info_dl *dl;
struct msgb *msg;
msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
if (msg == NULL)
return -ENOMEM;
dl = put_dl_info_hdr(msg, NULL);
/* Fill in current ARFCN */
dl->band_arfcn = htons(band_arfcn);
fbsb_conf_make(msg, 255, 0);
LOGPFSMSL(fi, DL1C, LOGL_DEBUG, "Send FBSB Conf (timeout)\n");
return l1ctl_client_send(l1c, msg);
}
int l1ctl_tx_fbsb_conf(struct l1ctl_client *l1c, uint16_t band_arfcn, uint8_t bsic)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct l1ctl_fbsb_conf *conf;
struct l1ctl_info_dl *dl;
struct msgb *msg;
msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
if (msg == NULL)
return -ENOMEM;
dl = put_dl_info_hdr(msg, NULL);
/* Fill in current ARFCN */
dl->band_arfcn = htons(band_arfcn);
conf = fbsb_conf_make(msg, 0, bsic);
/* FIXME: set proper value */
conf->initial_freq_err = 0;
LOGPFSMSL(fi, DL1C, LOGL_DEBUG,
"Send FBSB Conf (result=%u, bsic=%u)\n",
conf->result, conf->bsic);
return l1ctl_client_send(l1c, msg);
}
int l1ctl_tx_ccch_mode_conf(struct l1ctl_client *l1c, 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_client_send(l1c, msg);
}
/**
* Handles both L1CTL_DATA_IND and L1CTL_TRAFFIC_IND.
*/
int l1ctl_tx_dt_ind(struct l1ctl_client *l1c,
const struct l1ctl_info_dl *dl_info,
const 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, dl_info);
/* 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_client_send(l1c, msg);
}
int l1ctl_tx_rach_conf(struct l1ctl_client *l1c,
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_client_send(l1c, msg);
}
/**
* Handles both L1CTL_DATA_CONF and L1CTL_TRAFFIC_CONF.
*/
int l1ctl_tx_dt_conf(struct l1ctl_client *l1c,
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_client_send(l1c, 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;
}
}
static int l1ctl_rx_fbsb_req(struct l1ctl_client *l1c, struct msgb *msg)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct l1ctl_fbsb_req *fbsb;
int rc = 0;
fbsb = (struct l1ctl_fbsb_req *) msg->l1h;
if (msgb_l1len(msg) < sizeof(*fbsb)) {
LOGPFSMSL(fi, DL1C, LOGL_ERROR,
"MSG too short FBSB Req: %u\n",
msgb_l1len(msg));
rc = -EINVAL;
goto exit;
}
struct trxcon_param_fbsb_search_req req = {
.pchan_config = l1ctl_ccch_mode2pchan_config(fbsb->ccch_mode),
.timeout_ms = ntohs(fbsb->timeout) * GSM_TDMA_FN_DURATION_uS / 1000,
.band_arfcn = ntohs(fbsb->band_arfcn),
};
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
"Received FBSB request (%s %d, timeout %u ms)\n",
arfcn2band_name(req.band_arfcn),
req.band_arfcn & ~ARFCN_FLAG_MASK,
req.timeout_ms);
osmo_fsm_inst_dispatch(fi, TRXCON_EV_FBSB_SEARCH_REQ, &req);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_pm_req(struct l1ctl_client *l1c, struct msgb *msg)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct l1ctl_pm_req *pmr;
int rc = 0;
pmr = (struct l1ctl_pm_req *) msg->l1h;
if (msgb_l1len(msg) < sizeof(*pmr)) {
LOGPFSMSL(fi, DL1C, LOGL_ERROR,
"MSG too short PM Req: %u\n",
msgb_l1len(msg));
rc = -EINVAL;
goto exit;
}
struct trxcon_param_full_power_scan_req req = {
.band_arfcn_start = ntohs(pmr->range.band_arfcn_from),
.band_arfcn_stop = ntohs(pmr->range.band_arfcn_to),
};
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
"Received power measurement request (%s: %d -> %d)\n",
arfcn2band_name(req.band_arfcn_start),
req.band_arfcn_start & ~ARFCN_FLAG_MASK,
req.band_arfcn_stop & ~ARFCN_FLAG_MASK);
osmo_fsm_inst_dispatch(fi, TRXCON_EV_FULL_POWER_SCAN_REQ, &req);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_reset_req(struct l1ctl_client *l1c, struct msgb *msg)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct l1ctl_reset *res;
int rc = 0;
res = (struct l1ctl_reset *) msg->l1h;
if (msgb_l1len(msg) < sizeof(*res)) {
LOGPFSMSL(fi, DL1C, LOGL_ERROR,
"MSG too short Reset Req: %u\n",
msgb_l1len(msg));
rc = -EINVAL;
goto exit;
}
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
"Received reset request (%u)\n", res->type);
switch (res->type) {
case L1CTL_RES_T_FULL:
osmo_fsm_inst_dispatch(fi, TRXCON_EV_RESET_FULL_REQ, NULL);
break;
case L1CTL_RES_T_SCHED:
osmo_fsm_inst_dispatch(fi, TRXCON_EV_RESET_SCHED_REQ, NULL);
break;
default:
LOGPFSMSL(fi, DL1C, LOGL_ERROR,
"Unknown L1CTL_RESET_REQ type\n");
goto exit;
}
/* Confirm */
rc = l1ctl_tx_reset_conf(l1c, res->type);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_echo_req(struct l1ctl_client *l1c, struct msgb *msg)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct l1ctl_hdr *l1h;
LOGPFSMSL(fi, DL1C, LOGL_NOTICE, "Recv Echo Req\n");
LOGPFSMSL(fi, 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_client_send(l1c, msg);
}
static int l1ctl_rx_ccch_mode_req(struct l1ctl_client *l1c, struct msgb *msg)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct l1ctl_ccch_mode_req *mode_req;
int rc;
mode_req = (struct l1ctl_ccch_mode_req *)msg->l1h;
if (msgb_l1len(msg) < sizeof(*mode_req)) {
LOGPFSMSL(fi, DL1C, LOGL_ERROR,
"MSG too short Reset Req: %u\n",
msgb_l1len(msg));
rc = -EINVAL;
goto exit;
}
LOGPFSMSL(fi, DL1C, LOGL_NOTICE, "Received CCCH mode request (%u)\n",
mode_req->ccch_mode); /* TODO: add value-string for ccch_mode */
struct trxcon_param_set_ccch_tch_mode_req req = {
/* Choose corresponding channel combination */
.mode = l1ctl_ccch_mode2pchan_config(mode_req->ccch_mode),
};
rc = osmo_fsm_inst_dispatch(fi, TRXCON_EV_SET_CCCH_MODE_REQ, &req);
if (rc == 0 && req.applied)
l1ctl_tx_ccch_mode_conf(l1c, mode_req->ccch_mode);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_rach_req(struct l1ctl_client *l1c, struct msgb *msg, bool is_11bit)
{
struct trxcon_param_tx_access_burst_req req;
struct osmo_fsm_inst *fi = l1c->priv;
struct l1ctl_info_ul *ul;
ul = (struct l1ctl_info_ul *) msg->l1h;
if (is_11bit) {
const struct l1ctl_ext_rach_req *rr = (void *)ul->payload;
req = (struct trxcon_param_tx_access_burst_req) {
.offset = ntohs(rr->offset),
.synch_seq = rr->synch_seq,
.ra = ntohs(rr->ra11),
.is_11bit = true,
};
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
"Received 11-bit RACH request "
"(offset=%u, synch_seq=%u, ra11=0x%02hx)\n",
req.offset, req.synch_seq, req.ra);
} else {
const struct l1ctl_rach_req *rr = (void *)ul->payload;
req = (struct trxcon_param_tx_access_burst_req) {
.offset = ntohs(rr->offset),
.ra = rr->ra,
};
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
"Received 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) {
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
"The UL info header is empty, assuming RACH is on TS0\n");
req.chan_nr = RSL_CHAN_RACH;
req.link_id = 0x00;
} else {
req.chan_nr = ul->chan_nr;
req.link_id = ul->link_id;
}
osmo_fsm_inst_dispatch(fi, TRXCON_EV_TX_ACCESS_BURST_REQ, &req);
msgb_free(msg);
return 0;
}
static int l1ctl_proc_est_req_h0(struct osmo_fsm_inst *fi,
struct trxcon_param_dedicated_establish_req *req,
const struct l1ctl_h0 *h)
{
req->h0.band_arfcn = ntohs(h->band_arfcn);
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
"L1CTL_DM_EST_REQ indicates single ARFCN %s %u\n",
arfcn2band_name(req->h0.band_arfcn),
req->h0.band_arfcn & ~ARFCN_FLAG_MASK);
return 0;
}
static int l1ctl_proc_est_req_h1(struct osmo_fsm_inst *fi,
struct trxcon_param_dedicated_establish_req *req,
const struct l1ctl_h1 *h)
{
unsigned int i;
LOGPFSMSL(fi, 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) {
LOGPFSMSL(fi, DL1C, LOGL_ERROR,
"No channels in mobile allocation?!?\n");
return -EINVAL;
} else if (h->n > ARRAY_SIZE(h->ma)) {
LOGPFSMSL(fi, 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++)
req->h1.ma[i] = ntohs(h->ma[i]);
req->h1.n = h->n;
req->h1.hsn = h->hsn;
req->h1.maio = h->maio;
return 0;
}
static int l1ctl_rx_dm_est_req(struct l1ctl_client *l1c, struct msgb *msg)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct l1ctl_dm_est_req *est_req;
struct l1ctl_info_ul *ul;
int rc;
ul = (struct l1ctl_info_ul *) msg->l1h;
est_req = (struct l1ctl_dm_est_req *) ul->payload;
struct trxcon_param_dedicated_establish_req req = {
.chan_nr = ul->chan_nr,
.tch_mode = est_req->tch_mode,
.tsc = est_req->tsc,
.hopping = est_req->h,
};
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
"Received L1CTL_DM_EST_REQ "
"(tn=%u, chan_nr=0x%02x, tsc=%u, tch_mode=0x%02x)\n",
req.chan_nr & 0x07, req.chan_nr, req.tsc, req.tch_mode);
/* Frequency hopping? */
if (est_req->h)
rc = l1ctl_proc_est_req_h1(fi, &req, &est_req->h1);
else /* Single ARFCN */
rc = l1ctl_proc_est_req_h0(fi, &req, &est_req->h0);
if (rc)
goto exit;
osmo_fsm_inst_dispatch(fi, TRXCON_EV_DEDICATED_ESTABLISH_REQ, &req);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_dm_rel_req(struct l1ctl_client *l1c, struct msgb *msg)
{
struct osmo_fsm_inst *fi = l1c->priv;
LOGPFSMSL(fi, DL1C, LOGL_NOTICE, "Received L1CTL_DM_REL_REQ\n");
osmo_fsm_inst_dispatch(fi, TRXCON_EV_DEDICATED_RELEASE_REQ, NULL);
msgb_free(msg);
return 0;
}
/**
* Handles both L1CTL_DATA_REQ and L1CTL_TRAFFIC_REQ.
*/
static int l1ctl_rx_dt_req(struct l1ctl_client *l1c,
struct msgb *msg, bool traffic)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct l1ctl_info_ul *ul;
/* Extract UL frame header */
ul = (struct l1ctl_info_ul *) msg->l1h;
msg->l2h = ul->payload;
struct trxcon_param_tx_traffic_data_req req = {
.chan_nr = ul->chan_nr,
.link_id = ul->link_id & 0x40,
.data_len = msgb_l2len(msg),
.data = ul->payload,
};
LOGPFSMSL(fi, DL1D, LOGL_DEBUG,
"Recv %s Req (chan_nr=0x%02x, link_id=0x%02x, len=%zu)\n",
traffic ? "TRAFFIC" : "DATA", req.chan_nr, req.link_id, req.data_len);
switch (fi->state) {
case TRXCON_ST_DEDICATED:
if (traffic)
osmo_fsm_inst_dispatch(fi, TRXCON_EV_TX_TRAFFIC_REQ, &req);
else
osmo_fsm_inst_dispatch(fi, TRXCON_EV_TX_DATA_REQ, &req);
break;
default:
if (!traffic && req.link_id == 0x40) /* only for SACCH */
osmo_fsm_inst_dispatch(fi, TRXCON_EV_UPDATE_SACCH_CACHE_REQ, &req);
/* TODO: log an error about uhnandled DATA.req / TRAFFIC.req */
}
msgb_free(msg);
return 0;
}
static int l1ctl_rx_param_req(struct l1ctl_client *l1c, struct msgb *msg)
{
struct osmo_fsm_inst *fi = l1c->priv;
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;
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
"Received L1CTL_PARAM_REQ (ta=%d, tx_power=%u)\n",
par_req->ta, par_req->tx_power);
struct trxcon_param_set_phy_config_req req = {
.type = TRXCON_PHY_CFGT_TX_PARAMS,
.tx_params = {
.timing_advance = par_req->ta,
.tx_power = par_req->tx_power,
}
};
osmo_fsm_inst_dispatch(fi, TRXCON_EV_SET_PHY_CONFIG_REQ, &req);
msgb_free(msg);
return 0;
}
static int l1ctl_rx_tch_mode_req(struct l1ctl_client *l1c, struct msgb *msg)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct l1ctl_tch_mode_req *mode_req;
int rc;
mode_req = (struct l1ctl_tch_mode_req *)msg->l1h;
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
"Received L1CTL_TCH_MODE_REQ (tch_mode=%u, audio_mode=%u)\n",
mode_req->tch_mode, mode_req->audio_mode);
/* TODO: do we need to care about audio_mode? */
struct trxcon_param_set_ccch_tch_mode_req req = {
.mode = mode_req->tch_mode,
};
if (mode_req->tch_mode == GSM48_CMODE_SPEECH_AMR) {
req.amr.start_codec = mode_req->amr.start_codec;
req.amr.codecs_bitmask = mode_req->amr.codecs_bitmask;
}
rc = osmo_fsm_inst_dispatch(fi, TRXCON_EV_SET_TCH_MODE_REQ, &req);
if (rc != 0 || !req.applied) {
talloc_free(msg);
return rc;
}
/* 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_client_send(l1c, msg);
}
static int l1ctl_rx_crypto_req(struct l1ctl_client *l1c, struct msgb *msg)
{
struct osmo_fsm_inst *fi = l1c->priv;
struct l1ctl_crypto_req *cr;
struct l1ctl_info_ul *ul;
ul = (struct l1ctl_info_ul *) msg->l1h;
cr = (struct l1ctl_crypto_req *) ul->payload;
struct trxcon_param_crypto_req req = {
.chan_nr = ul->chan_nr,
.a5_algo = cr->algo,
.key_len = cr->key_len,
.key = cr->key,
};
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
"L1CTL_CRYPTO_REQ (algo=A5/%u, key_len=%u)\n",
req.a5_algo, req.key_len);
osmo_fsm_inst_dispatch(fi, TRXCON_EV_CRYPTO_REQ, &req);
msgb_free(msg);
return 0;
}
int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg)
{
struct osmo_fsm_inst *fi = l1c->priv;
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(l1c, msg);
case L1CTL_PM_REQ:
return l1ctl_rx_pm_req(l1c, msg);
case L1CTL_RESET_REQ:
return l1ctl_rx_reset_req(l1c, msg);
case L1CTL_ECHO_REQ:
return l1ctl_rx_echo_req(l1c, msg);
case L1CTL_CCCH_MODE_REQ:
return l1ctl_rx_ccch_mode_req(l1c, msg);
case L1CTL_RACH_REQ:
return l1ctl_rx_rach_req(l1c, msg, false);
case L1CTL_EXT_RACH_REQ:
return l1ctl_rx_rach_req(l1c, msg, true);
case L1CTL_DM_EST_REQ:
return l1ctl_rx_dm_est_req(l1c, msg);
case L1CTL_DM_REL_REQ:
return l1ctl_rx_dm_rel_req(l1c, msg);
case L1CTL_DATA_REQ:
return l1ctl_rx_dt_req(l1c, msg, false);
case L1CTL_TRAFFIC_REQ:
return l1ctl_rx_dt_req(l1c, msg, true);
case L1CTL_PARAM_REQ:
return l1ctl_rx_param_req(l1c, msg);
case L1CTL_TCH_MODE_REQ:
return l1ctl_rx_tch_mode_req(l1c, msg);
case L1CTL_CRYPTO_REQ:
return l1ctl_rx_crypto_req(l1c, 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:
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
"Ignoring unsupported message (type=%u)\n",
l1h->msg_type);
msgb_free(msg);
return -ENOTSUP;
default:
LOGPFSMSL(fi, 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;
}
}

282
trxcon/src/l1ctl_server.c Normal file
View File

@ -0,0 +1,282 @@
/*
* OsmocomBB <-> SDR connection bridge
* UNIX socket server for L1CTL
*
* (C) 2013 by Sylvain Munaut <tnt@246tNt.com>
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
* (C) 2022 by by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/bb/trxcon/logging.h>
#include <osmocom/bb/trxcon/l1ctl_server.h>
#define LOGP_CLI(cli, cat, level, fmt, args...) \
LOGP(cat, level, "%s" fmt, (cli)->log_prefix, ## args)
static int l1ctl_client_read_cb(struct osmo_fd *ofd)
{
struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
struct msgb *msg;
uint16_t len;
int rc;
/* Attempt to read from socket */
rc = read(ofd->fd, &len, L1CTL_MSG_LEN_FIELD);
if (rc != L1CTL_MSG_LEN_FIELD) {
if (rc <= 0) {
LOGP_CLI(client, DL1D, LOGL_NOTICE,
"L1CTL connection error: read() failed (rc=%d): %s\n",
rc, strerror(errno));
} else {
LOGP_CLI(client, DL1D, LOGL_NOTICE,
"L1CTL connection error: short read\n");
rc = -EIO;
}
l1ctl_client_conn_close(client);
return rc;
}
/* Check message length */
len = ntohs(len);
if (len > L1CTL_LENGTH) {
LOGP_CLI(client, 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_CLI(client, DL1D, LOGL_ERROR, "Failed to allocate msg\n");
return -ENOMEM;
}
msg->l1h = msgb_put(msg, len);
rc = read(ofd->fd, msg->l1h, msgb_l1len(msg));
if (rc != len) {
LOGP_CLI(client, 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_CLI(client, DL1D, LOGL_DEBUG, "RX: '%s'\n", osmo_hexdump(msg->data, msg->len));
/* Call L1CTL handler */
client->server->cfg->conn_read_cb(client, msg);
return 0;
}
static int l1ctl_client_write_cb(struct osmo_fd *ofd, struct msgb *msg)
{
struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
int len;
if (ofd->fd <= 0)
return -EINVAL;
len = write(ofd->fd, msg->data, msg->len);
if (len != msg->len) {
LOGP_CLI(client, 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_server_conn_cb(struct osmo_fd *sfd, unsigned int flags)
{
struct l1ctl_server *server = (struct l1ctl_server *)sfd->data;
struct l1ctl_client *client;
int rc, client_fd;
client_fd = accept(sfd->fd, NULL, NULL);
if (client_fd < 0) {
LOGP(DL1C, LOGL_ERROR, "Failed to accept() a new connection: "
"%s\n", strerror(errno));
return client_fd;
}
if (server->cfg->num_clients_max > 0 /* 0 means unlimited */ &&
server->num_clients >= server->cfg->num_clients_max) {
LOGP(DL1C, LOGL_NOTICE, "L1CTL server cannot accept more "
"than %u connection(s)\n", server->cfg->num_clients_max);
close(client_fd);
return -ENOMEM;
}
client = talloc_zero(server, struct l1ctl_client);
if (client == NULL) {
LOGP(DL1C, LOGL_ERROR, "Failed to allocate an L1CTL client\n");
close(client_fd);
return -ENOMEM;
}
/* Init the client's write queue */
osmo_wqueue_init(&client->wq, 100);
INIT_LLIST_HEAD(&client->wq.bfd.list);
client->wq.write_cb = &l1ctl_client_write_cb;
client->wq.read_cb = &l1ctl_client_read_cb;
osmo_fd_setup(&client->wq.bfd, client_fd, OSMO_FD_READ, &osmo_wqueue_bfd_cb, client, 0);
/* Register the client's write queue */
rc = osmo_fd_register(&client->wq.bfd);
if (rc != 0) {
LOGP(DL1C, LOGL_ERROR, "Failed to register a new connection fd\n");
close(client->wq.bfd.fd);
talloc_free(client);
return rc;
}
llist_add_tail(&client->list, &server->clients);
client->id = server->next_client_id++;
client->server = server;
server->num_clients++;
LOGP(DL1C, LOGL_NOTICE, "L1CTL server got a new connection (id=%u)\n", client->id);
if (client->server->cfg->conn_accept_cb != NULL)
client->server->cfg->conn_accept_cb(client);
return 0;
}
int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg)
{
uint8_t *len;
/* Debug print */
LOGP_CLI(client, DL1D, LOGL_DEBUG, "TX: '%s'\n", osmo_hexdump(msg->data, msg->len));
if (msg->l1h != msg->data)
LOGP_CLI(client, 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(&client->wq, msg) != 0) {
LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to enqueue msg!\n");
msgb_free(msg);
return -EIO;
}
return 0;
}
void l1ctl_client_conn_close(struct l1ctl_client *client)
{
struct l1ctl_server *server = client->server;
LOGP_CLI(client, DL1C, LOGL_NOTICE, "Closing L1CTL connection\n");
if (server->cfg->conn_close_cb != NULL)
server->cfg->conn_close_cb(client);
/* Close connection socket */
osmo_fd_unregister(&client->wq.bfd);
close(client->wq.bfd.fd);
client->wq.bfd.fd = -1;
/* Clear pending messages */
osmo_wqueue_clear(&client->wq);
client->server->num_clients--;
llist_del(&client->list);
talloc_free(client);
/* If this was the last client, reset the client IDs generator to 0.
* This way avoid assigning huge unreadable client IDs like 26545. */
if (llist_empty(&server->clients))
server->next_client_id = 0;
}
struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg)
{
struct l1ctl_server *server;
int rc;
LOGP(DL1C, LOGL_NOTICE, "Init L1CTL server (sock_path=%s)\n", cfg->sock_path);
server = talloc(ctx, struct l1ctl_server);
OSMO_ASSERT(server != NULL);
*server = (struct l1ctl_server) {
.clients = LLIST_HEAD_INIT(server->clients),
.cfg = cfg,
};
/* conn_read_cb shall not be NULL */
OSMO_ASSERT(cfg->conn_read_cb != NULL);
/* Bind connection handler */
osmo_fd_setup(&server->ofd, -1, OSMO_FD_READ, &l1ctl_server_conn_cb, server, 0);
rc = osmo_sock_unix_init_ofd(&server->ofd, SOCK_STREAM, 0,
cfg->sock_path, OSMO_SOCK_F_BIND);
if (rc < 0) {
LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n",
strerror(errno));
talloc_free(server);
return NULL;
}
return server;
}
void l1ctl_server_free(struct l1ctl_server *server)
{
LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL server\n");
/* Close all client connections */
while (!llist_empty(&server->clients)) {
struct l1ctl_client *client = llist_entry(server->clients.next,
struct l1ctl_client,
list);
l1ctl_client_conn_close(client);
}
/* Unbind listening socket */
if (server->ofd.fd != -1) {
osmo_fd_unregister(&server->ofd);
close(server->ofd.fd);
server->ofd.fd = -1;
}
talloc_free(server);
}

View File

@ -15,17 +15,13 @@
* 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"
#include <osmocom/bb/trxcon/logging.h>
static struct log_info_cat trx_log_info_cat[] = {
[DAPP] = {
@ -46,8 +42,8 @@ static struct log_info_cat trx_log_info_cat[] = {
.color = "\033[1;31m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DTRX] = {
.name = "DTRX",
[DTRXC] = {
.name = "DTRXC",
.description = "Transceiver control interface",
.color = "\033[1;33m",
.enabled = 1, .loglevel = LOGL_NOTICE,
@ -62,7 +58,7 @@ static struct log_info_cat trx_log_info_cat[] = {
.name = "DSCH",
.description = "Scheduler management",
.color = "\033[1;36m",
.enabled = 0, .loglevel = LOGL_NOTICE,
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DSCHD] = {
.name = "DSCHD",

View File

@ -30,32 +30,29 @@
#include <inttypes.h>
#include <string.h>
#include <osmocom/core/logging.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"
#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/logging.h>
#define MAX_FN_SKEW 50
#define TRX_LOSS_FRAMES 400
static void sched_clck_tick(void *data)
static void l1sched_clck_tick(void *data)
{
struct trx_sched *sched = (struct trx_sched *) data;
struct l1sched_state *sched = (struct l1sched_state *) 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;
LOGP_SCHEDC(sched, LOGL_DEBUG, "No more clock from transceiver\n");
sched->clck_state = L1SCHED_CLCK_ST_WAIT;
return;
}
@ -69,10 +66,10 @@ static void sched_clck_tick(void *data)
/* 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);
LOGP_SCHEDC(sched, LOGL_NOTICE, "PC clock skew: "
"elapsed uS %" PRId64 "\n", elapsed_us);
sched->state = SCH_CLCK_STATE_WAIT;
sched->clck_state = L1SCHED_CLCK_ST_WAIT;
return;
}
@ -93,7 +90,7 @@ static void sched_clck_tick(void *data)
GSM_TDMA_FN_DURATION_uS - elapsed_us);
}
static void sched_clck_correct(struct trx_sched *sched,
static void l1sched_clck_correct(struct l1sched_state *sched,
struct timespec *tv_now, uint32_t fn)
{
sched->fn_counter_proc = fn;
@ -106,12 +103,12 @@ static void sched_clck_correct(struct trx_sched *sched,
sched->clock = *tv_now;
memset(&sched->clock_timer, 0, sizeof(sched->clock_timer));
sched->clock_timer.cb = sched_clck_tick;
sched->clock_timer.cb = l1sched_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)
int l1sched_clck_handle(struct l1sched_state *sched, uint32_t fn)
{
struct timespec tv_now, *tv_clock, elapsed;
int64_t elapsed_us, elapsed_fn;
@ -124,16 +121,16 @@ int sched_clck_handle(struct trx_sched *sched, uint32_t fn)
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);
if (sched->clck_state == L1SCHED_CLCK_ST_WAIT) {
l1sched_clck_correct(sched, &tv_now, fn);
LOGP(DSCH, LOGL_DEBUG, "Initial clock received: fn=%u\n", fn);
sched->state = SCH_CLCK_STATE_OK;
LOGP_SCHEDC(sched, LOGL_DEBUG, "Initial clock received: fn=%u\n", fn);
sched->clck_state = L1SCHED_CLCK_ST_OK;
return 0;
}
LOGP(DSCH, LOGL_NOTICE, "Clock indication: fn=%u\n", fn);
LOGP_SCHEDC(sched, LOGL_NOTICE, "Clock indication: fn=%u\n", fn);
osmo_timer_del(&sched->clock_timer);
@ -147,15 +144,15 @@ int sched_clck_handle(struct trx_sched *sched, uint32_t fn)
/* 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);
LOGP_SCHEDC(sched, LOGL_NOTICE, "GSM clock skew: old fn=%u, "
"new fn=%u\n", sched->fn_counter_proc, fn);
sched_clck_correct(sched, &tv_now, fn);
l1sched_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);
LOGP_SCHEDC(sched, 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) {
@ -192,10 +189,10 @@ int sched_clck_handle(struct trx_sched *sched, uint32_t fn)
return 0;
}
void sched_clck_reset(struct trx_sched *sched)
void l1sched_clck_reset(struct l1sched_state *sched)
{
/* Reset internal state */
sched->state = SCH_CLCK_STATE_WAIT;
sched->clck_state = L1SCHED_CLCK_ST_WAIT;
/* Stop clock timer */
osmo_timer_del(&sched->clock_timer);

View File

@ -0,0 +1,143 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: common routines for lchan handlers
*
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
* Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <errno.h>
#include <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/codec/codec.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/logging.h>
/* GSM 05.02 Chapter 5.2.3 Normal Burst (NB) */
const uint8_t l1sched_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 *l1sched_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;
}
/**
* 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 l1sched_bad_frame_ind(uint8_t *l2, struct l1sched_lchan_state *lchan)
{
int rc;
switch (lchan->tch_mode) {
case GSM48_CMODE_SPEECH_V1:
if (lchan->type == L1SCHED_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 */
rc = osmo_amr_rtp_enc(l2,
lchan->amr.codec[lchan->amr.dl_cmr],
lchan->amr.codec[lchan->amr.dl_ft],
AMR_BAD);
if (rc < 2) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Failed to encode AMR_BAD frame (rc=%d), "
"not sending BFI\n", rc);
return 0;
}
memset(l2 + 2, 0, rc - 2);
return rc;
case GSM48_CMODE_SIGN:
LOGP_LCHAND(lchan, LOGL_ERROR, "BFI is not allowed in signalling mode\n");
return 0;
default:
LOGP_LCHAND(lchan, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
return 0;
}
}

View File

@ -5,6 +5,7 @@
* (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>
* Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
@ -26,66 +27,66 @@
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include <osmocom/core/gsmtap.h>
#include "sched_trx.h"
#include <osmocom/bb/l1sched/l1sched.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 rx_data_fn(struct l1sched_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
const struct l1sched_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 tx_data_fn(struct l1sched_lchan_state *lchan,
struct l1sched_burst_req *br);
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 rx_sch_fn(struct l1sched_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
const struct l1sched_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 tx_rach_fn(struct l1sched_lchan_state *lchan,
struct l1sched_burst_req *br);
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 rx_tchf_fn(struct l1sched_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
const struct l1sched_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 tx_tchf_fn(struct l1sched_lchan_state *lchan,
struct l1sched_burst_req *br);
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 rx_tchh_fn(struct l1sched_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
const struct l1sched_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 tx_tchh_fn(struct l1sched_lchan_state *lchan,
struct l1sched_burst_req *br);
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 rx_pdtch_fn(struct l1sched_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
const struct l1sched_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);
int tx_pdtch_fn(struct l1sched_lchan_state *lchan,
struct l1sched_burst_req *br);
const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = {
[TRXC_IDLE] = {
const struct l1sched_lchan_desc l1sched_lchan_desc[_L1SCHED_CHAN_MAX] = {
[L1SCHED_IDLE] = {
.name = "IDLE",
.desc = "Idle channel",
/* The MS needs to perform neighbour measurements during
* IDLE slots, however this is not implemented (yet). */
},
[TRXC_FCCH] = {
[L1SCHED_FCCH] = {
.name = "FCCH", /* 3GPP TS 05.02, section 3.3.2.1 */
.desc = "Frequency correction channel",
/* Handled by transceiver, nothing to do. */
},
[TRXC_SCH] = {
[L1SCHED_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,
.flags = L1SCHED_CH_FLAG_AUTO,
.rx_fn = rx_sch_fn,
},
[TRXC_BCCH] = {
[L1SCHED_BCCH] = {
.name = "BCCH", /* 3GPP TS 05.02, section 3.3.2.3 */
.desc = "Broadcast control channel",
.gsmtap_chan_type = GSMTAP_CHANNEL_BCCH,
@ -95,20 +96,20 @@ const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = {
* 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,
.flags = L1SCHED_CH_FLAG_AUTO,
.rx_fn = rx_data_fn,
},
[TRXC_RACH] = {
[L1SCHED_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,
.flags = L1SCHED_CH_FLAG_AUTO,
.tx_fn = tx_rach_fn,
},
[TRXC_CCCH] = {
[L1SCHED_CCCH] = {
.name = "CCCH", /* 3GPP TS 05.02, section 3.3.3.1 */
.desc = "Common control channel",
.gsmtap_chan_type = GSMTAP_CHANNEL_CCCH,
@ -118,15 +119,15 @@ const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = {
* 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,
.flags = L1SCHED_CH_FLAG_AUTO,
.rx_fn = rx_data_fn,
},
[TRXC_TCHF] = {
[L1SCHED_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,
.link_id = L1SCHED_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):
@ -140,16 +141,16 @@ const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = {
* 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,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_tchf_fn,
.tx_fn = tx_tchf_fn,
},
[TRXC_TCHH_0] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 0,
/* Rx and Tx, multiple convolutional coding types (3GPP TS 05.03,
@ -169,402 +170,402 @@ const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = {
* 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,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_tchh_fn,
.tx_fn = tx_tchh_fn,
},
[TRXC_TCHH_1] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 1,
/* Same as for TRXC_TCHH_0, see above. */
/* Same as for L1SCHED_TCHH_0, see above. */
.burst_buf_size = 6 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_tchh_fn,
.tx_fn = tx_tchh_fn,
},
[TRXC_SDCCH4_0] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 0,
/* Same as for TRXC_BCCH (xCCH), see above. */
/* Same as for L1SCHED_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH4_1] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 1,
/* Same as for TRXC_BCCH (xCCH), see above. */
/* Same as for L1SCHED_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH4_2] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 2,
/* Same as for TRXC_BCCH (xCCH), see above. */
/* Same as for L1SCHED_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH4_3] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 3,
/* Same as for TRXC_BCCH (xCCH), see above. */
/* Same as for L1SCHED_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_0] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 0,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_1] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 1,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_2] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 2,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_3] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 3,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_4] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 4,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_5] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 5,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_6] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 6,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_7] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_DEDIC,
.ss_nr = 7,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCHTF] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
/* Same as for TRXC_BCCH (xCCH), see above. */
/* Same as for L1SCHED_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCHTH_0] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 0,
/* Same as for TRXC_BCCH (xCCH), see above. */
/* Same as for L1SCHED_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCHTH_1] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 1,
/* Same as for TRXC_BCCH (xCCH), see above. */
/* Same as for L1SCHED_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH4_0] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 0,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH4_1] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 1,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH4_2] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 2,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH4_3] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 3,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_0] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 0,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_1] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 1,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_2] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 2,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_3] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 3,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_4] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 4,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_5] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 5,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_6] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 6,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_7] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_SACCH,
.ss_nr = 7,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.flags = L1SCHED_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_PDTCH] = {
[L1SCHED_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,
@ -575,16 +576,16 @@ const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = {
* 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,
.flags = L1SCHED_CH_FLAG_PDCH,
.rx_fn = rx_pdtch_fn,
.tx_fn = tx_pdtch_fn,
},
[TRXC_PTCCH] = {
[L1SCHED_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,
.link_id = L1SCHED_CH_LID_PTCCH,
/* On the Uplink, mobile stations transmit random Access Bursts
* to allow estimation of the timing advance for one MS in packet
@ -592,30 +593,30 @@ const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = {
* 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,
.flags = L1SCHED_CH_FLAG_PDCH,
.rx_fn = rx_pdtch_fn,
.tx_fn = tx_rach_fn,
},
[TRXC_SDCCH4_CBCH] = {
[L1SCHED_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. */
/* Same as for L1SCHED_BCCH (xCCH), but Rx only. See above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_AUTO,
.flags = L1SCHED_CH_FLAG_AUTO,
.rx_fn = rx_data_fn,
},
[TRXC_SDCCH8_CBCH] = {
[L1SCHED_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. */
/* Same as for L1SCHED_BCCH (xCCH), but Rx only. See above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.rx_fn = rx_data_fn,
},

View File

@ -2,7 +2,8 @@
* 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-2022 by Vadim Yanitskiy <axilirator@gmail.com>
* Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
@ -16,10 +17,6 @@
* 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>
@ -33,30 +30,23 @@
#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"
#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/logging.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)
int rx_pdtch_fn(struct l1sched_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
const struct l1sched_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);
LOGP_LCHAND(lchan, LOGL_DEBUG, "Packet data received: fn=%u bid=%u\n", fn, bid);
/* Align to the first burst of a block */
if (*mask == 0x00 && bid != 0)
@ -66,7 +56,7 @@ int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
*mask |= (1 << bid);
/* Store the measurements */
sched_trx_meas_push(lchan, meas);
l1sched_lchan_meas_push(lchan, meas);
/* Copy burst to buffer of 4 bursts */
offset = buffer + bid * 116;
@ -78,16 +68,15 @@ int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
return 0;
/* Calculate AVG of the measurements */
sched_trx_meas_avg(lchan, 4);
l1sched_lchan_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);
LOGP_LCHAND(lchan, LOGL_ERROR,
"Received incomplete (%s) packet data at fn=%u (%u/%u)\n",
l1sched_burst_mask2str(mask, 4), lchan->meas_avg.fn,
lchan->meas_avg.fn % lchan->ts->mf_layout->period,
lchan->ts->mf_layout->period);
/* NOTE: do not abort here, give it a try. Maybe we're lucky ;) */
}
@ -98,40 +87,34 @@ int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
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);
LOGP_LCHAND(lchan, LOGL_ERROR,
"Received bad frame (rc=%d, ber=%d/%d) at fn=%u\n",
rc, n_errors, n_bits_total, lchan->meas_avg.fn);
}
/* 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);
l1sched_handle_data_ind(lchan, l2, l2_len, n_errors, n_bits_total, L1SCHED_DT_PACKET_DATA);
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)
int tx_pdtch_fn(struct l1sched_lchan_state *lchan,
struct l1sched_burst_req *br)
{
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 (br->bid > 0) {
/* If we have encoded bursts */
if (*mask)
goto send_burst;
@ -143,55 +126,43 @@ int tx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
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));
LOGP_LCHAND(lchan, 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);
l1sched_prim_drop(lchan);
return -EINVAL;
}
send_burst:
/* Determine which burst should be sent */
offset = buffer + bid * 116;
offset = buffer + br->bid * 116;
/* Update mask */
*mask |= (1 << bid);
*mask |= (1 << br->bid);
/* Choose proper TSC */
tsc = sched_nb_training_bits[trx->tsc];
tsc = l1sched_nb_training_bits[lchan->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 */
memset(br->burst, 0, 3); /* TB */
memcpy(br->burst + 3, offset, 58); /* Payload 1/2 */
memcpy(br->burst + 61, tsc, 26); /* TSC */
memcpy(br->burst + 87, offset + 58, 58); /* Payload 2/2 */
memset(br->burst + 145, 0, 3); /* TB */
br->burst_len = GSM_BURST_LEN;
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;
}
LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled at fn=%u burst=%u\n", br->fn, br->bid);
/* 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);
l1sched_handle_data_cnf(lchan, br->fn, L1SCHED_DT_PACKET_DATA);
/* Forget processed primitive */
sched_prim_drop(lchan);
l1sched_prim_drop(lchan);
/* Reset mask */
*mask = 0x00;

View File

@ -2,7 +2,8 @@
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2017-2019 by Vadim Yanitskiy <axilirator@gmail.com>
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
* Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
@ -16,10 +17,6 @@
* 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>
@ -33,12 +30,8 @@
#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"
#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/logging.h>
/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)" */
#define RACH_EXT_TAIL_BITS_LEN 8
@ -76,68 +69,63 @@ static struct value_string rach_synch_seq_names[] = {
};
/* 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)
int tx_rach_fn(struct l1sched_lchan_state *lchan,
struct l1sched_burst_req *br)
{
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;
const uint8_t bsic = lchan->ts->sched->bsic;
struct l1sched_ts_prim_rach *rach;
uint8_t *burst_ptr = br->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;
rach = (struct l1sched_ts_prim_rach *)lchan->prim->payload;
/* Delay sending according to offset value */
if (rach->offset-- > 0)
return 0;
if (L1SCHED_PRIM_IS_RACH11(lchan->prim)) {
/* Check requested synch. sequence */
if (synch_seq >= RACH_SYNCH_SEQ_NUM) {
LOGP(DSCHD, LOGL_ERROR, "Unknown RACH synch. sequence=0x%02x\n", synch_seq);
if (rach->synch_seq >= RACH_SYNCH_SEQ_NUM) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Unknown RACH synch. sequence=0x%02x\n",
rach->synch_seq);
/* Forget this primitive */
sched_prim_drop(lchan);
l1sched_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);
/* Encode 11-bit payload */
rc = gsm0503_rach_ext_encode(payload, rach->ra, bsic, true);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Could not encode extended RACH burst "
"(ra=%u bsic=%u)\n", ext_req->ra11, trx->bsic);
LOGP_LCHAND(lchan, LOGL_ERROR,
"Could not encode 11-bit RACH burst (ra=%u bsic=%u)\n",
rach->ra, bsic);
/* Forget this primitive */
sched_prim_drop(lchan);
l1sched_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;
} else if (L1SCHED_PRIM_IS_RACH8(lchan->prim)) {
rach->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);
/* Encode 8-bit payload */
rc = gsm0503_rach_ext_encode(payload, rach->ra, bsic, false);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Could not encode RACH burst "
"(ra=%u bsic=%u)\n", req->ra, trx->bsic);
LOGP_LCHAND(lchan, LOGL_ERROR,
"Could not encode RACH burst (ra=%u bsic=%u)\n",
rach->ra, bsic);
/* Forget this primitive */
sched_prim_drop(lchan);
l1sched_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);
LOGP_LCHAND(lchan, LOGL_ERROR,
"Primitive has unexpected type=0x%02x\n",
lchan->prim->type);
l1sched_prim_drop(lchan);
return -EINVAL;
}
@ -148,40 +136,25 @@ int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts,
/* 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';
*(burst_ptr++) = rach_synch_seq_bits[rach->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);
memset(burst_ptr, 0, br->burst + GSM_BURST_LEN - burst_ptr);
br->burst_len = GSM_BURST_LEN;
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;
}
LOGP_LCHAND(lchan, LOGL_NOTICE, "Scheduled %s-bit RACH (%s) at fn=%u\n",
L1SCHED_PRIM_IS_RACH11(lchan->prim) ? "11" : "8",
get_value_string(rach_synch_seq_names, rach->synch_seq), br->fn);
/* 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);
l1sched_handle_data_cnf(lchan, br->fn, L1SCHED_DT_OTHER);
/* Forget processed primitive */
sched_prim_drop(lchan);
l1sched_prim_drop(lchan);
return 0;
}

View File

@ -2,7 +2,8 @@
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
* Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
@ -16,10 +17,6 @@
* 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>
@ -35,18 +32,8 @@
#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);
}
#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/logging.h>
static void decode_sb(struct gsm_time *time, uint8_t *bsic, uint8_t *sb_info)
{
@ -74,9 +61,9 @@ static void decode_sb(struct gsm_time *time, uint8_t *bsic, uint8_t *sb_info)
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)
int rx_sch_fn(struct l1sched_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
const struct l1sched_meas_set *meas)
{
sbit_t payload[2 * 39];
struct gsm_time time;
@ -89,51 +76,31 @@ int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts,
memcpy(payload + 39, bits + 3 + 39 + 64, 39);
/* Attempt to decode */
rc = gsm0503_sch_decode_xray(sb_info, payload);
rc = gsm0503_sch_decode(sb_info, payload);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Received bad SCH burst at fn=%u\n", fn);
LOGP_LCHAND(lchan, 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);
LOGP_LCHAND(lchan, LOGL_DEBUG, "Received SCH: bsic=%u, fn=%u, sched_fn=%u\n",
bsic, time.fn, lchan->ts->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);
LOGP_LCHAND(lchan, 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;
/* Update BSIC value in the scheduler state */
lchan->ts->sched->bsic = bsic;
/* 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;
l1sched_handle_data_ind(lchan, (const uint8_t *)&time, sizeof(time),
0, 39 * 2, L1SCHED_DT_OTHER);
return 0;
}

View File

@ -0,0 +1,366 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
* Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <errno.h>
#include <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/coding/gsm0503_amr_dtx.h>
#include <osmocom/codec/codec.h>
#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/logging.h>
#include <osmocom/bb/trxcon/sched_utils.h>
/* 3GPP TS 45.009, table 3.2.1.3-{1,3}: AMR on Downlink TCH/F.
*
* +---+---+---+---+---+---+---+---+
* | a | b | c | d | e | f | g | h | Burst 'a' received first
* +---+---+---+---+---+---+---+---+
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Speech/FACCH frame (bursts 'a' .. 'h')
*
* TDMA frame number of burst 'h' is always used as the table index. */
static const uint8_t sched_tchf_dl_amr_cmi_map[26] = {
[11] = 1, /* TCH/F: a=4 / h=11 */
[20] = 1, /* TCH/F: a=13 / h=20 */
[3] = 1, /* TCH/F: a=21 / h=3 (21+7=28, 25 is idle -> 29. 29%26=3) */
};
int rx_tchf_fn(struct l1sched_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
const struct l1sched_meas_set *meas)
{
int n_errors = -1, n_bits_total = 0, rc;
sbit_t *buffer, *offset;
uint8_t l2[128], *mask;
size_t l2_len;
int amr = 0;
uint8_t ft;
bool amr_is_cmr;
/* Set up pointers */
mask = &lchan->rx_burst_mask;
buffer = lchan->rx_bursts;
LOGP_LCHAND(lchan, LOGL_DEBUG, "Traffic received: fn=%u bid=%u\n", fn, bid);
/* Align to the first burst of a block */
if (*mask == 0x00 && bid != 0)
return 0;
/* Update mask */
*mask |= (1 << bid);
/* Store the measurements */
l1sched_lchan_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 */
l1sched_lchan_meas_avg(lchan, 8);
/* Check for complete set of bursts */
if ((*mask & 0xff) != 0xff) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Received incomplete (%s) traffic frame at fn=%u (%u/%u)\n",
l1sched_burst_mask2str(mask, 8), lchan->meas_avg.fn,
lchan->meas_avg.fn % lchan->ts->mf_layout->period,
lchan->ts->mf_layout->period);
/* 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 */
/* the first FN 4,13,21 defines that CMI is included in frame,
* the first FN 0,8,17 defines that CMR/CMC is included in frame.
* NOTE: A frame ends 7 FN after start.
*/
amr_is_cmr = !sched_tchf_dl_amr_cmi_map[fn % 26];
/* we store tch_data + 2 header bytes, the amr variable set to
* 2 will allow us to skip the first 2 bytes in case we did
* receive an FACCH frame instead of a voice frame (we do not
* know this before we actually decode the frame) */
amr = 2;
rc = gsm0503_tch_afs_decode_dtx(l2 + amr, buffer,
amr_is_cmr, lchan->amr.codec, lchan->amr.codecs, &lchan->amr.dl_ft,
&lchan->amr.dl_cmr, &n_errors, &n_bits_total, &lchan->amr.last_dtx);
/* only good speech frames get rtp header */
if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
if (lchan->amr.last_dtx == AMR_OTHER) {
ft = lchan->amr.codec[lchan->amr.dl_ft];
} else {
/* SID frames will always get Frame Type Index 8 (AMR_SID) */
ft = AMR_SID;
}
rc = osmo_amr_rtp_enc(l2,
lchan->amr.codec[lchan->amr.dl_cmr],
ft, AMR_GOOD);
if (rc < 0)
LOGP_LCHAND(lchan, LOGL_ERROR,
"osmo_amr_rtp_enc() returned rc=%d\n", rc);
}
break;
default:
LOGP_LCHAND(lchan, 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_LCHAND(lchan, LOGL_ERROR,
"Received bad frame (rc=%d, ber=%d/%d) at fn=%u\n",
rc, n_errors, n_bits_total, lchan->meas_avg.fn);
/* Send BFI */
goto bfi;
} else if (rc == GSM_MACBLOCK_LEN) {
/* FACCH received, forward it to the higher layers */
l1sched_handle_data_ind(lchan, l2 + amr, GSM_MACBLOCK_LEN,
n_errors, n_bits_total,
L1SCHED_DT_SIGNALING);
/* 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 l1sched_handle_data_ind(lchan, l2, l2_len, n_errors, n_bits_total, L1SCHED_DT_TRAFFIC);
bfi:
/* Didn't try to decode, fake measurements */
if (n_errors < 0) {
lchan->meas_avg = (struct l1sched_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 l1sched_handle_data_ind(lchan, NULL, 0,
n_errors, n_bits_total,
L1SCHED_DT_TRAFFIC);
}
/* Bad frame indication */
l2_len = l1sched_bad_frame_ind(l2, lchan);
/* Send a BFI frame to the higher layers */
return l1sched_handle_data_ind(lchan, l2, l2_len,
n_errors, n_bits_total,
L1SCHED_DT_TRAFFIC);
}
int tx_tchf_fn(struct l1sched_lchan_state *lchan,
struct l1sched_burst_req *br)
{
ubit_t *buffer, *offset;
const uint8_t *tsc;
uint8_t *mask;
size_t l2_len;
int rc;
/* Set up pointers */
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 (br->bid > 0)
return 0;
/* Shift buffer by 4 bursts back for interleaving */
memcpy(buffer, buffer + 464, 464);
/* populate the buffer with bursts */
if (L1SCHED_PRIM_IS_FACCH(lchan->prim)) {
/* Encode payload */
rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, GSM_MACBLOCK_LEN, 1);
} else if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
int len;
uint8_t cmr_codec;
int ft, cmr, i;
enum osmo_amr_type ft_codec;
enum osmo_amr_quality bfi;
int8_t sti, cmi;
bool amr_fn_is_cmr;
/* the first FN 0,8,17 defines that CMI is included in frame,
* the first FN 4,13,21 defines that CMR is included in frame.
*/
amr_fn_is_cmr = !ul_amr_fn_is_cmi(br->fn);
len = osmo_amr_rtp_dec(lchan->prim->payload, lchan->prim->payload_len,
&cmr_codec, &cmi, &ft_codec,
&bfi, &sti);
if (len < 0) {
LOGP_LCHAND(lchan, LOGL_ERROR, "Cannot send invalid AMR payload (%zu): %s\n",
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, lchan->prim->payload_len));
goto free_bad_msg;
}
ft = -1;
cmr = -1;
for (i = 0; i < lchan->amr.codecs; i++) {
if (lchan->amr.codec[i] == ft_codec)
ft = i;
if (lchan->amr.codec[i] == cmr_codec)
cmr = i;
}
if (ft < 0) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Codec (FT = %d) of RTP frame not in list\n", ft_codec);
goto free_bad_msg;
}
if (amr_fn_is_cmr && lchan->amr.ul_ft != ft) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Codec (FT = %d) of RTP cannot be changed now, but in next frame\n",
ft_codec);
goto free_bad_msg;
}
lchan->amr.ul_ft = ft;
if (cmr < 0) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Codec (CMR = %d) of RTP frame not in list\n", cmr_codec);
} else {
lchan->amr.ul_cmr = cmr;
}
rc = gsm0503_tch_afs_encode(buffer, lchan->prim->payload + 2,
lchan->prim->payload_len - 2, amr_fn_is_cmr,
lchan->amr.codec, lchan->amr.codecs,
lchan->amr.ul_ft,
lchan->amr.ul_cmr);
} else {
/* Determine and check the payload length */
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;
default:
LOGP_LCHAND(lchan, LOGL_ERROR,
"Invalid TCH mode: %u, dropping frame...\n",
lchan->tch_mode);
/* Forget this primitive */
l1sched_prim_drop(lchan);
return -EINVAL;
}
if (lchan->prim->payload_len != l2_len) {
LOGP_LCHAND(lchan, 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);
l1sched_prim_drop(lchan);
return -EINVAL;
}
rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, l2_len, 1);
}
if (rc) {
LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
lchan->prim->payload_len));
free_bad_msg:
/* Forget this primitive */
l1sched_prim_drop(lchan);
return -EINVAL;
}
send_burst:
/* Determine which burst should be sent */
offset = buffer + br->bid * 116;
/* Update mask */
*mask |= (1 << br->bid);
/* Choose proper TSC */
tsc = l1sched_nb_training_bits[lchan->tsc];
/* Compose a new burst */
memset(br->burst, 0, 3); /* TB */
memcpy(br->burst + 3, offset, 58); /* Payload 1/2 */
memcpy(br->burst + 61, tsc, 26); /* TSC */
memcpy(br->burst + 87, offset + 58, 58); /* Payload 2/2 */
memset(br->burst + 145, 0, 3); /* TB */
br->burst_len = GSM_BURST_LEN;
LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled fn=%u burst=%u\n", br->fn, br->bid);
/* If we have sent the last (4/4) burst */
if (*mask == 0x0f) {
/* Confirm data / traffic sending */
enum l1sched_data_type dt = L1SCHED_PRIM_IS_TCH(lchan->prim) ?
L1SCHED_DT_TRAFFIC : L1SCHED_DT_SIGNALING;
l1sched_handle_data_cnf(lchan, br->fn, dt);
/* Forget processed primitive */
l1sched_prim_drop(lchan);
/* Reset mask */
*mask = 0x00;
}
return 0;
}

View File

@ -0,0 +1,587 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2018-2022 by Vadim Yanitskiy <axilirator@gmail.com>
* (C) 2018 by Harald Welte <laforge@gnumonks.org>
* Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <errno.h>
#include <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/coding/gsm0503_amr_dtx.h>
#include <osmocom/codec/codec.h>
#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/logging.h>
#include <osmocom/bb/trxcon/sched_utils.h>
/* 3GPP TS 45.009, table 3.2.1.3-{2,4}: AMR on Downlink TCH/H.
*
* +---+---+---+---+---+---+
* | a | b | c | d | e | f | Burst 'a' received first
* +---+---+---+---+---+---+
* ^^^^^^^^^^^^^^^^^^^^^^^ FACCH frame (bursts 'a' .. 'f')
* ^^^^^^^^^^^^^^^ Speech frame (bursts 'a' .. 'd')
*
* TDMA frame number of burst 'f' is always used as the table index. */
static const uint8_t sched_tchh_dl_amr_cmi_map[26] = {
[15] = 1, /* TCH/H(0): a=4 / d=10 / f=15 */
[23] = 1, /* TCH/H(0): a=13 / d=19 / f=23 */
[6] = 1, /* TCH/H(0): a=21 / d=2 / f=6 */
[16] = 1, /* TCH/H(1): a=5 / d=11 / f=16 */
[24] = 1, /* TCH/H(1): a=14 / d=20 / f=24 */
[7] = 1, /* TCH/H(1): a=22 / d=3 / f=7 */
};
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 },
};
/* FACCH/H channel mapping for Downlink (see 3GPP TS 45.002, table 1).
* This mapping is valid for both FACCH/H(0) and FACCH/H(1).
* TDMA frame number of burst 'f' is used as the table index. */
static const uint8_t sched_tchh_dl_facch_map[26] = {
[15] = 1, /* FACCH/H(0): B0(4,6,8,10,13,15) */
[16] = 1, /* FACCH/H(1): B0(5,7,9,11,14,16) */
[23] = 1, /* FACCH/H(0): B1(13,15,17,19,21,23) */
[24] = 1, /* FACCH/H(1): B1(14,16,18,20,22,24) */
[6] = 1, /* FACCH/H(0): B2(21,23,0,2,4,6) */
[7] = 1, /* FACCH/H(1): B2(22,24,1,3,5,7) */
};
/**
* 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 (L1SCHED_TCHH_0 or L1SCHED_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 l1sched_tchh_block_map_fn(enum l1sched_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 == L1SCHED_TCHH_0 || chan == L1SCHED_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 == L1SCHED_TCHH_0)
BLOCK_MAP_FN(tch_h0_ul_facch_block_map);
else
BLOCK_MAP_FN(tch_h1_ul_facch_block_map);
} else {
if (chan == L1SCHED_TCHH_0)
BLOCK_MAP_FN(tch_h0_dl_facch_block_map);
else
BLOCK_MAP_FN(tch_h1_dl_facch_block_map);
}
} else {
if (chan == L1SCHED_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 (L1SCHED_TCHH_0 or L1SCHED_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
*/
static uint32_t tchh_block_dl_first_fn(const struct l1sched_lchan_state *lchan,
uint32_t last_fn, bool facch)
{
enum l1sched_lchan_type chan = lchan->type;
uint8_t fn_mf, fn_diff;
int i = 0;
/* Just to be sure */
OSMO_ASSERT(chan == L1SCHED_TCHH_0 || chan == L1SCHED_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 == L1SCHED_TCHH_0)
BLOCK_FIRST_FN(tch_h0_dl_facch_block_map);
else
BLOCK_FIRST_FN(tch_h1_dl_facch_block_map);
} else {
if (chan == L1SCHED_TCHH_0)
BLOCK_FIRST_FN(tch_h0_traffic_block_map);
else
BLOCK_FIRST_FN(tch_h1_traffic_block_map);
}
LOGP_LCHAND(lchan, 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 l1sched_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
const struct l1sched_meas_set *meas)
{
int n_errors = -1, n_bits_total = 0, rc;
sbit_t *buffer, *offset;
uint8_t l2[128], *mask;
size_t l2_len;
int amr = 0;
uint8_t ft;
bool fn_is_cmi;
/* Set up pointers */
mask = &lchan->rx_burst_mask;
buffer = lchan->rx_bursts;
LOGP_LCHAND(lchan, LOGL_DEBUG, "Traffic received: fn=%u bid=%u\n", fn, 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 (!l1sched_tchh_facch_start(lchan->type, fn, 0))
return 0;
} else { /* or TCH/H traffic frame */
if (!l1sched_tchh_traffic_start(lchan->type, fn, 0))
return 0;
}
}
/* Update mask */
*mask |= (1 << bid);
/* Store the measurements */
l1sched_lchan_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,
!l1sched_tchh_facch_end(lchan->type, fn, 0),
&n_errors, &n_bits_total);
break;
case GSM48_CMODE_SPEECH_AMR: /* AMR */
/* the first FN FN 4,13,21 or 5,14,22 defines that CMI is
* included in frame, the first FN FN 0,8,17 or 1,9,18 defines
* that CMR/CMC is included in frame. */
fn_is_cmi = sched_tchh_dl_amr_cmi_map[fn % 26];
/* See comment in function rx_tchf_fn() */
amr = 2;
rc = gsm0503_tch_ahs_decode_dtx(l2 + amr, buffer,
!sched_tchh_dl_facch_map[fn % 26],
!fn_is_cmi, lchan->amr.codec, lchan->amr.codecs, &lchan->amr.dl_ft,
&lchan->amr.dl_cmr, &n_errors, &n_bits_total, &lchan->amr.last_dtx);
/* only good speech frames get rtp header */
if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
if (lchan->amr.last_dtx == AMR_OTHER) {
ft = lchan->amr.codec[lchan->amr.dl_ft];
} else {
/* SID frames will always get Frame Type Index 8 (AMR_SID) */
ft = AMR_SID;
}
rc = osmo_amr_rtp_enc(l2,
lchan->amr.codec[lchan->amr.dl_cmr],
ft, AMR_GOOD);
if (rc < 0)
LOGP_LCHAND(lchan, LOGL_ERROR,
"osmo_amr_rtp_enc() returned rc=%d\n", rc);
}
break;
default:
LOGP_LCHAND(lchan, 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) */
l1sched_lchan_meas_avg(lchan, 4);
LOGP_LCHAND(lchan, LOGL_ERROR,
"Received bad frame (rc=%d, ber=%d/%d) at fn=%u\n",
rc, n_errors, n_bits_total, lchan->meas_avg.fn);
/* 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) */
l1sched_lchan_meas_avg(lchan, 6);
/* FACCH/H received, forward to the higher layers */
l1sched_handle_data_ind(lchan, l2 + amr, GSM_MACBLOCK_LEN,
n_errors, n_bits_total,
L1SCHED_DT_SIGNALING);
/* 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) */
l1sched_lchan_meas_avg(lchan, 4);
}
/* Send a traffic frame to the higher layers */
return l1sched_handle_data_ind(lchan, l2, l2_len,
n_errors, n_bits_total,
L1SCHED_DT_TRAFFIC);
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 l1sched_meas_set) {
.fn = tchh_block_dl_first_fn(lchan, 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 l1sched_handle_data_ind(lchan, NULL, 0,
n_errors, n_bits_total,
L1SCHED_DT_SIGNALING);
}
/* Bad frame indication */
l2_len = l1sched_bad_frame_ind(l2, lchan);
/* Send a BFI frame to the higher layers */
return l1sched_handle_data_ind(lchan, l2, l2_len,
n_errors, n_bits_total,
L1SCHED_DT_TRAFFIC);
}
int tx_tchh_fn(struct l1sched_lchan_state *lchan,
struct l1sched_burst_req *br)
{
ubit_t *buffer, *offset;
const uint8_t *tsc;
uint8_t *mask;
size_t l2_len;
int rc;
/* Set up pointers */
mask = &lchan->tx_burst_mask;
buffer = lchan->tx_bursts;
if (br->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 (!l1sched_tchh_facch_start(lchan->type, br->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;
}
/* populate the buffer with bursts */
if (L1SCHED_PRIM_IS_FACCH(lchan->prim)) {
rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, lchan->prim->payload_len);
lchan->ul_facch_blocks = 6;
} else if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
int len;
uint8_t cmr_codec;
int ft, cmr, i;
enum osmo_amr_type ft_codec;
enum osmo_amr_quality bfi;
int8_t sti, cmi;
bool amr_fn_is_cmr;
/* the first FN 0,8,17 defines that CMI is included in frame,
* the first FN 4,13,21 defines that CMR is included in frame.
*/
amr_fn_is_cmr = !ul_amr_fn_is_cmi(br->fn);
len = osmo_amr_rtp_dec(lchan->prim->payload, lchan->prim->payload_len,
&cmr_codec, &cmi, &ft_codec,
&bfi, &sti);
if (len < 0) {
LOGP_LCHAND(lchan, LOGL_ERROR, "Cannot send invalid AMR payload (%zu): %s\n",
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, lchan->prim->payload_len));
goto free_bad_msg;
}
ft = -1;
cmr = -1;
for (i = 0; i < lchan->amr.codecs; i++) {
if (lchan->amr.codec[i] == ft_codec)
ft = i;
if (lchan->amr.codec[i] == cmr_codec)
cmr = i;
}
if (ft < 0) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Codec (FT = %d) of RTP frame not in list\n", ft_codec);
goto free_bad_msg;
}
if (amr_fn_is_cmr && lchan->amr.ul_ft != ft) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Codec (FT = %d) of RTP cannot be changed now, but in next frame\n",
ft_codec);
goto free_bad_msg;
}
lchan->amr.ul_ft = ft;
if (cmr < 0) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Codec (CMR = %d) of RTP frame not in list\n", cmr_codec);
} else {
lchan->amr.ul_cmr = cmr;
}
rc = gsm0503_tch_ahs_encode(buffer, lchan->prim->payload + 2,
lchan->prim->payload_len - 2, amr_fn_is_cmr,
lchan->amr.codec, lchan->amr.codecs,
lchan->amr.ul_ft,
lchan->amr.ul_cmr);
} else {
/* Determine and check the payload length */
switch (lchan->tch_mode) {
case GSM48_CMODE_SIGN:
case GSM48_CMODE_SPEECH_V1: /* HR */
l2_len = GSM_HR_BYTES + 1;
break;
default:
LOGP_LCHAND(lchan, LOGL_ERROR,
"Invalid TCH mode: %u, dropping frame...\n",
lchan->tch_mode);
/* Forget this primitive */
l1sched_prim_drop(lchan);
return -EINVAL;
}
if (lchan->prim->payload_len != l2_len) {
LOGP_LCHAND(lchan, 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 */
l1sched_prim_drop(lchan);
return -EINVAL;
}
rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, l2_len);
}
if (rc) {
LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
lchan->prim->payload_len));
free_bad_msg:
/* Forget this primitive */
l1sched_prim_drop(lchan);
return -EINVAL;
}
send_burst:
/* Determine which burst should be sent */
offset = buffer + br->bid * 116;
/* Update mask */
*mask |= (1 << br->bid);
/* Choose proper TSC */
tsc = l1sched_nb_training_bits[lchan->tsc];
/* Compose a new burst */
memset(br->burst, 0, 3); /* TB */
memcpy(br->burst + 3, offset, 58); /* Payload 1/2 */
memcpy(br->burst + 61, tsc, 26); /* TSC */
memcpy(br->burst + 87, offset + 58, 58); /* Payload 2/2 */
memset(br->burst + 145, 0, 3); /* TB */
br->burst_len = GSM_BURST_LEN;
LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled fn=%u burst=%u\n", br->fn, br->bid);
/* 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) {
enum l1sched_data_type dt = L1SCHED_PRIM_IS_TCH(lchan->prim) ?
L1SCHED_DT_TRAFFIC : L1SCHED_DT_SIGNALING;
l1sched_handle_data_cnf(lchan, br->fn, dt);
}
/* Forget processed primitive */
l1sched_prim_drop(lchan);
}
return 0;
}

View File

@ -0,0 +1,178 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
* Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <errno.h>
#include <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 <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/logging.h>
int rx_data_fn(struct l1sched_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
const struct l1sched_meas_set *meas)
{
uint8_t l2[GSM_MACBLOCK_LEN], *mask;
int n_errors, n_bits_total, rc;
sbit_t *buffer, *offset;
/* Set up pointers */
mask = &lchan->rx_burst_mask;
buffer = lchan->rx_bursts;
LOGP_LCHAND(lchan, LOGL_DEBUG, "Data received: fn=%u bid=%u\n", fn, bid);
/* Align to the first burst of a block */
if (*mask == 0x00 && bid != 0)
return 0;
/* Update mask */
*mask |= (1 << bid);
/* Store the measurements */
l1sched_lchan_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 */
l1sched_lchan_meas_avg(lchan, 4);
/* Check for complete set of bursts */
if ((*mask & 0xf) != 0xf) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Received incomplete (%s) data frame at fn=%u (%u/%u)\n",
l1sched_burst_mask2str(mask, 4), lchan->meas_avg.fn,
lchan->meas_avg.fn % lchan->ts->mf_layout->period,
lchan->ts->mf_layout->period);
/* 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(l2, buffer, &n_errors, &n_bits_total);
if (rc) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Received bad frame (rc=%d, ber=%d/%d) at fn=%u\n",
rc, n_errors, n_bits_total, lchan->meas_avg.fn);
}
/* Send a L2 frame to the higher layers */
return l1sched_handle_data_ind(lchan, l2, rc ? 0 : GSM_MACBLOCK_LEN,
n_errors, n_bits_total,
L1SCHED_DT_SIGNALING);
}
int tx_data_fn(struct l1sched_lchan_state *lchan,
struct l1sched_burst_req *br)
{
ubit_t *buffer, *offset;
const uint8_t *tsc;
uint8_t *mask;
int rc;
/* Set up pointers */
mask = &lchan->tx_burst_mask;
buffer = lchan->tx_bursts;
if (br->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_LCHAND(lchan, LOGL_ERROR,
"Primitive has odd length %zu (expected %u), so dropping...\n",
lchan->prim->payload_len, GSM_MACBLOCK_LEN);
l1sched_prim_drop(lchan);
return -EINVAL;
}
/* Encode payload */
rc = gsm0503_xcch_encode(buffer, lchan->prim->payload);
if (rc) {
LOGP_LCHAND(lchan, 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 */
l1sched_prim_drop(lchan);
return -EINVAL;
}
send_burst:
/* Determine which burst should be sent */
offset = buffer + br->bid * 116;
/* Update mask */
*mask |= (1 << br->bid);
/* Choose proper TSC */
tsc = l1sched_nb_training_bits[lchan->tsc];
/* Compose a new burst */
memset(br->burst, 0, 3); /* TB */
memcpy(br->burst + 3, offset, 58); /* Payload 1/2 */
memcpy(br->burst + 61, tsc, 26); /* TSC */
memcpy(br->burst + 87, offset + 58, 58); /* Payload 2/2 */
memset(br->burst + 145, 0, 3); /* TB */
br->burst_len = GSM_BURST_LEN;
LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled fn=%u burst=%u\n", br->fn, br->bid);
/* If we have sent the last (4/4) burst */
if ((*mask & 0x0f) == 0x0f) {
/* Confirm data sending */
l1sched_handle_data_cnf(lchan, br->fn, L1SCHED_DT_SIGNALING);
/* Forget processed primitive */
l1sched_prim_drop(lchan);
/* Reset mask */
*mask = 0x00;
}
return 0;
}

2102
trxcon/src/sched_mframe.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,8 @@
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: primitive management
*
* (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
* Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
@ -16,10 +17,6 @@
* 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>
@ -33,181 +30,130 @@
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include "scheduler.h"
#include "sched_trx.h"
#include "trx_if.h"
#include "logging.h"
#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/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 type prim payload type
* @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
* @return allocated primitive or NULL
*/
int sched_prim_init(void *ctx, struct trx_ts_prim **prim,
size_t pl_len, uint8_t chan_nr, uint8_t link_id)
static struct l1sched_ts_prim *prim_alloc(void *ctx, size_t pl_len,
enum l1sched_ts_prim_type type,
uint8_t chan_nr, uint8_t link_id)
{
enum trx_lchan_type lchan_type;
struct trx_ts_prim *new_prim;
uint8_t len;
enum l1sched_lchan_type lchan_type;
struct l1sched_ts_prim *prim;
/* Determine lchan type */
lchan_type = sched_trx_chan_nr2lchan_type(chan_nr, link_id);
lchan_type = l1sched_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;
/* TODO: use proper logging context */
LOGP(DLGLOBAL, LOGL_ERROR, "Couldn't determine lchan type "
"for chan_nr=%02x and link_id=%02x\n", chan_nr, link_id);
return NULL;
}
/* 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;
}
prim = talloc_zero_size(ctx, sizeof(*prim) + pl_len);
if (prim == NULL)
return NULL;
/* Init primitive header */
new_prim->payload_len = pl_len;
new_prim->chan = lchan_type;
prim->payload_len = pl_len;
prim->chan = lchan_type;
prim->type = type;
/* Set external pointer */
*prim = new_prim;
return 0;
return prim;
}
/**
* 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 sched scheduler instance
* @param chan_nr RSL channel description
* @return zero in case of success, otherwise a error number
* @param link_id RSL link description
* @param pl Payload data
* @param pl_len Payload length
* @return queued primitive or NULL
*/
int sched_prim_push(struct trx_instance *trx,
struct trx_ts_prim *prim, uint8_t chan_nr)
struct l1sched_ts_prim *l1sched_prim_push(struct l1sched_state *sched,
enum l1sched_ts_prim_type type,
uint8_t chan_nr, uint8_t link_id,
const uint8_t *pl, size_t pl_len)
{
struct trx_ts *ts;
struct l1sched_ts_prim *prim;
struct l1sched_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];
ts = sched->ts[tn];
if (ts == NULL || ts->mf_layout == NULL) {
LOGP(DSCH, LOGL_ERROR, "Timeslot %u isn't configured\n", tn);
return -EINVAL;
LOGP_SCHEDC(sched, LOGL_ERROR, "Timeslot %u isn't configured\n", tn);
return NULL;
}
/**
* Change talloc context of primitive
* from trx to the parent ts
*/
talloc_steal(ts, prim);
prim = prim_alloc(ts, pl_len, type, chan_nr, link_id);
if (prim == NULL)
return NULL;
memcpy(&prim->payload[0], pl, pl_len);
/* Add primitive to TS transmit queue */
llist_add_tail(&prim->list, &ts->tx_prims);
return 0;
return prim;
}
/**
* Composes a new primitive using either cached (if populated),
* or "dummy" Measurement Report message.
* Composes a new primitive from cached RR Measurement Report.
*
* @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)
static struct l1sched_ts_prim *prim_compose_mr(struct l1sched_lchan_state *lchan)
{
struct trx_ts_prim *prim;
uint8_t *mr_src_ptr;
struct l1sched_ts_prim *prim;
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);
prim = prim_alloc(lchan, GSM_MACBLOCK_LEN, L1SCHED_PRIM_DATA,
l1sched_lchan_desc[lchan->type].chan_nr,
L1SCHED_CH_LID_SACCH);
OSMO_ASSERT(prim != NULL);
/* 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;
if (!cached) {
memcpy(&lchan->sacch.mr_cache[0],
&lchan->ts->sched->sacch_cache[0],
sizeof(lchan->sacch.mr_cache));
}
/* Compose a new Measurement Report primitive */
memcpy(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;
}
memcpy(&prim->payload[0], &lchan->sacch.mr_cache[0], GSM_MACBLOCK_LEN);
/* 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);
if (++lchan->sacch.mr_cache_usage > 5) {
LOGP_LCHAND(lchan, LOGL_NOTICE,
"SACCH MR cache usage count=%u > 5 "
"=> ancient measurements, please fix!\n",
lchan->sacch.mr_cache_usage);
}
LOGP(DSCHD, LOGL_NOTICE, "Using a %s Measurement Report "
"on lchan=%s\n", (cached ? "cached" : "dummy"),
trx_lchan_desc[lchan->type].name);
LOGP_LCHAND(lchan, LOGL_NOTICE, "Using cached Measurement Report\n");
return prim;
}
@ -238,12 +184,12 @@ static struct trx_ts_prim *prim_compose_mr(struct trx_lchan_state *lchan)
* @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)
static struct l1sched_ts_prim *prim_dequeue_sacch(struct llist_head *queue,
struct l1sched_lchan_state *lchan)
{
struct trx_ts_prim *prim_nmr = NULL;
struct trx_ts_prim *prim_mr = NULL;
struct trx_ts_prim *prim;
struct l1sched_ts_prim *prim_nmr = NULL;
struct l1sched_ts_prim *prim_mr = NULL;
struct l1sched_ts_prim *prim;
bool mr_now;
/* Shall we transmit MR now? */
@ -274,11 +220,9 @@ static struct trx_ts_prim *prim_dequeue_sacch(struct llist_head *queue,
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);
LOGP_LCHAND(lchan, LOGL_DEBUG,
"SACCH MR selection: mr_tx_last=%d prim_mr=%p prim_nmr=%p\n",
lchan->sacch.mr_tx_last, prim_mr, prim_nmr);
/* Prioritize non-MR prim if possible */
if (mr_now && prim_mr)
@ -302,25 +246,23 @@ static struct trx_ts_prim *prim_dequeue_sacch(struct llist_head *queue,
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);
LOGP_LCHAND(lchan, LOGL_DEBUG, "SACCH MR cache has been updated\n");
}
/* 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");
LOGP_LCHAND(lchan, LOGL_DEBUG, "SACCH decision: %s\n",
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)
static struct l1sched_ts_prim *prim_dequeue_one(struct llist_head *queue,
enum l1sched_lchan_type lchan_type)
{
struct trx_ts_prim *prim;
struct l1sched_ts_prim *prim;
/**
* There is no need to use the 'safe' list iteration here
@ -341,22 +283,22 @@ static struct trx_ts_prim *prim_dequeue_one(struct llist_head *queue,
* 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(),
* check the prim's channel type using L1SCHED_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
* e.g. L1SCHED_TCHF, L1SCHED_TCHH_0, or L1SCHED_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)
static struct l1sched_ts_prim *prim_dequeue_tch(struct llist_head *queue,
enum l1sched_lchan_type lchan_type, bool facch)
{
struct trx_ts_prim *prim;
struct l1sched_ts_prim *prim;
/**
* There is no need to use the 'safe' list iteration here
@ -367,7 +309,7 @@ static struct trx_ts_prim *prim_dequeue_tch(struct llist_head *queue,
continue;
/* Either FACCH, or not FACCH */
if (PRIM_IS_FACCH(prim) != facch)
if (L1SCHED_PRIM_IS_FACCH(prim) != facch)
continue;
llist_del(&prim->list);
@ -386,14 +328,14 @@ static struct trx_ts_prim *prim_dequeue_tch(struct llist_head *queue,
* @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)
static struct l1sched_ts_prim *prim_dequeue_tch_f(struct llist_head *queue)
{
struct trx_ts_prim *facch;
struct trx_ts_prim *tch;
struct l1sched_ts_prim *facch;
struct l1sched_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);
facch = prim_dequeue_tch(queue, L1SCHED_TCHF, true);
tch = prim_dequeue_tch(queue, L1SCHED_TCHF, false);
/* Prioritize FACCH/F, if found */
if (facch) {
@ -432,15 +374,15 @@ static struct trx_ts_prim *prim_dequeue_tch_f(struct llist_head *queue)
* @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)
static struct l1sched_ts_prim *prim_dequeue_tch_h(struct llist_head *queue,
uint32_t fn, enum l1sched_lchan_type lchan_type)
{
struct trx_ts_prim *facch;
struct trx_ts_prim *tch;
struct l1sched_ts_prim *facch;
struct l1sched_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);
facch_now = l1sched_tchh_facch_start(lchan_type, fn, true);
if (!facch_now) /* Just dequeue a TCH/H prim */
goto no_facch;
@ -476,11 +418,11 @@ no_facch:
* @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)
struct l1sched_ts_prim *l1sched_prim_dequeue(struct llist_head *queue,
uint32_t fn, struct l1sched_lchan_state *lchan)
{
/* SACCH is unorthodox, see 3GPP TS 04.08, section 3.4.1 */
if (CHAN_IS_SACCH(lchan->type))
if (L1SCHED_CHAN_IS_SACCH(lchan->type))
return prim_dequeue_sacch(queue, lchan);
/* There is nothing to dequeue */
@ -489,12 +431,12 @@ struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue,
switch (lchan->type) {
/* TCH/F requires FACCH/F prioritization */
case TRXC_TCHF:
case L1SCHED_TCHF:
return prim_dequeue_tch_f(queue);
/* FACCH/H prioritization is a bit more complex */
case TRXC_TCHH_0:
case TRXC_TCHH_1:
case L1SCHED_TCHH_0:
case L1SCHED_TCHH_1:
return prim_dequeue_tch_h(queue, fn, lchan->type);
/* Other kinds of logical channels */
@ -508,7 +450,7 @@ struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue,
*
* @param lchan a logical channel to drop prim from
*/
void sched_prim_drop(struct trx_lchan_state *lchan)
void l1sched_prim_drop(struct l1sched_lchan_state *lchan)
{
/* Forget this primitive */
talloc_free(lchan->prim);
@ -523,11 +465,11 @@ void sched_prim_drop(struct trx_lchan_state *lchan)
* @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)
int l1sched_prim_dummy(struct l1sched_lchan_state *lchan)
{
enum trx_lchan_type chan = lchan->type;
enum l1sched_lchan_type chan = lchan->type;
uint8_t tch_mode = lchan->tch_mode;
struct trx_ts_prim *prim;
struct l1sched_ts_prim *prim;
uint8_t prim_buffer[40];
size_t prim_len = 0;
int i;
@ -545,7 +487,7 @@ int sched_prim_dummy(struct trx_lchan_state *lchan)
/* Make sure that there is no existing primitive */
OSMO_ASSERT(lchan->prim == NULL);
/* Not applicable for SACCH! */
OSMO_ASSERT(!CHAN_IS_SACCH(lchan->type));
OSMO_ASSERT(!L1SCHED_CHAN_IS_SACCH(lchan->type));
/**
* Determine what actually should be generated:
@ -553,10 +495,10 @@ int sched_prim_dummy(struct trx_lchan_state *lchan)
* TCH in other modes: silence frame;
* other channels: LAPDm fill frame.
*/
if (CHAN_IS_TCH(chan) && TCH_MODE_IS_SPEECH(tch_mode)) {
if (L1SCHED_CHAN_IS_TCH(chan) && L1SCHED_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)) {
prim_len = l1sched_bad_frame_ind(prim_buffer, lchan);
} else if (L1SCHED_CHAN_IS_TCH(chan) && L1SCHED_TCH_MODE_IS_DATA(tch_mode)) {
/* FIXME: should we do anything for CSD? */
return 0;
} else {
@ -581,7 +523,7 @@ int sched_prim_dummy(struct trx_lchan_state *lchan)
return 0;
/* Allocate a new primitive */
prim = talloc_zero_size(lchan, sizeof(struct trx_ts_prim) + prim_len);
prim = talloc_zero_size(lchan, sizeof(struct l1sched_ts_prim) + prim_len);
if (prim == NULL)
return -ENOMEM;
@ -595,8 +537,7 @@ int sched_prim_dummy(struct trx_lchan_state *lchan)
/* 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);
LOGP_LCHAND(lchan, LOGL_DEBUG, "Transmitting a dummy / silence frame\n");
return 0;
}
@ -606,9 +547,9 @@ int sched_prim_dummy(struct trx_lchan_state *lchan)
*
* @param list list of prims going to be flushed
*/
void sched_prim_flush_queue(struct llist_head *list)
void l1sched_prim_flush_queue(struct llist_head *list)
{
struct trx_ts_prim *prim, *prim_next;
struct l1sched_ts_prim *prim, *prim_next;
llist_for_each_entry_safe(prim, prim_next, list, list) {
llist_del(&prim->list);

View File

@ -2,7 +2,8 @@
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: GSM PHY routines
*
* (C) 2017-2019 by Vadim Yanitskiy <axilirator@gmail.com>
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
* Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
@ -16,10 +17,6 @@
* 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>
@ -35,28 +32,86 @@
#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"
#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/logging.h>
static void sched_frame_clck_cb(struct trx_sched *sched)
/* Logging categories to be used for common/data messages */
int l1sched_log_cat_common = DLGLOBAL;
int l1sched_log_cat_data = DLGLOBAL;
/* "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,
};
static int l1sched_cfg_pchan_comb_req(struct l1sched_state *sched,
uint8_t tn, enum gsm_phys_chan_config pchan)
{
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;
const struct l1sched_config_req cr = {
.type = L1SCHED_CFG_PCHAN_COMB,
.pchan_comb = {
.tn = tn,
.pchan = pchan,
},
};
return l1sched_handle_config_req(sched, &cr);
}
static void l1sched_a5_burst_enc(struct l1sched_lchan_state *lchan,
struct l1sched_burst_req *br);
static void sched_frame_clck_cb(struct l1sched_state *sched)
{
struct l1sched_burst_req br[TRX_TS_COUNT];
const struct l1sched_tdma_frame *frame;
struct l1sched_lchan_state *lchan;
l1sched_lchan_tx_func *handler;
enum l1sched_lchan_type chan;
uint8_t offset;
struct l1sched_ts *ts;
unsigned int tn;
/* Advance TDMA frame number in order to give the transceiver
* more time to handle the burst before the actual transmission. */
const uint32_t fn = GSM_TDMA_FN_SUM(sched->fn_counter_proc,
sched->fn_counter_advance);
/* Iterate over timeslot list */
for (i = 0; i < TRX_TS_COUNT; i++) {
for (tn = 0; tn < ARRAY_SIZE(br); tn++) {
/* Initialize the buffer for this timeslot */
br[tn] = (struct l1sched_burst_req) {
.fn = fn,
.tn = tn,
.burst_len = 0, /* NOPE.ind */
};
/* Timeslot is not allocated */
ts = trx->ts_list[i];
ts = sched->ts[tn];
if (ts == NULL)
continue;
@ -64,27 +119,21 @@ static void sched_frame_clck_cb(struct trx_sched *sched)
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;
br[tn].bid = frame->ul_bid;
chan = frame->ul_chan;
handler = trx_lchan_desc[chan].tx_fn;
handler = l1sched_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);
lchan = l1sched_find_lchan(ts, chan);
if (lchan == NULL)
continue;
@ -97,19 +146,19 @@ static void sched_frame_clck_cb(struct trx_sched *sched)
* attempt to obtain a new one from queue
*/
if (lchan->prim == NULL)
lchan->prim = sched_prim_dequeue(&ts->tx_prims, fn, lchan);
lchan->prim = l1sched_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) {
if (l1sched_lchan_desc[chan].flags & L1SCHED_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);
l1sched_prim_dummy(lchan);
}
/* If there is no primitive, do nothing */
@ -118,112 +167,122 @@ static void sched_frame_clck_cb(struct trx_sched *sched)
/* 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;
if (L1SCHED_PRIM_IS_RACH(lchan->prim) && lchan->prim->chan != L1SCHED_RACH)
handler = l1sched_lchan_desc[L1SCHED_RACH].tx_fn;
/* Poke lchan handler */
handler(trx, ts, lchan, fn, bid);
handler(lchan, &br[tn]);
/* Perform A5/X burst encryption if required */
if (lchan->a5.algo)
l1sched_a5_burst_enc(lchan, &br[tn]);
}
/* Send all bursts for this TDMA frame */
for (tn = 0; tn < ARRAY_SIZE(br); tn++)
l1sched_handle_burst_req(sched, &br[tn]);
}
int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance)
void l1sched_logging_init(int log_cat_common, int log_cat_data)
{
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;
l1sched_log_cat_common = log_cat_common;
l1sched_log_cat_data = log_cat_data;
}
int sched_trx_shutdown(struct trx_instance *trx)
struct l1sched_state *l1sched_alloc(void *ctx, const struct l1sched_cfg *cfg, void *priv)
{
int i;
struct l1sched_state *sched;
if (!trx)
return -EINVAL;
sched = talloc(ctx, struct l1sched_state);
if (!sched)
return NULL;
LOGP(DSCH, LOGL_NOTICE, "Shutdown scheduler\n");
*sched = (struct l1sched_state) {
/* .clock_timer is set up in l1sched_clck_correct() */
.clock_cb = &sched_frame_clck_cb,
.fn_counter_advance = cfg->fn_advance,
.priv = priv,
};
memcpy(&sched->sacch_cache[0], &meas_rep_dummy[0], sizeof(meas_rep_dummy));
if (cfg->log_prefix == NULL)
sched->log_prefix = talloc_asprintf(sched, "l1sched[0x%p]: ", sched);
else
sched->log_prefix = talloc_strdup(sched, cfg->log_prefix);
return sched;
}
void l1sched_free(struct l1sched_state *sched)
{
unsigned int tn;
if (sched == NULL)
return;
LOGP_SCHEDC(sched, LOGL_NOTICE, "Shutdown scheduler\n");
/* Free all potentially allocated timeslots */
for (i = 0; i < TRX_TS_COUNT; i++)
sched_trx_del_ts(trx, i);
for (tn = 0; tn < ARRAY_SIZE(sched->ts); tn++)
l1sched_del_ts(sched, tn);
return 0;
l1sched_clck_reset(sched);
talloc_free(sched);
}
int sched_trx_reset(struct trx_instance *trx, bool reset_clock)
void l1sched_reset(struct l1sched_state *sched, bool reset_clock)
{
int i;
unsigned int tn;
if (!trx)
return -EINVAL;
if (sched == NULL)
return;
LOGP(DSCH, LOGL_NOTICE, "Reset scheduler %s\n",
reset_clock ? "and clock counter" : "");
LOGP_SCHEDC(sched, 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);
for (tn = 0; tn < ARRAY_SIZE(sched->ts); tn++)
l1sched_del_ts(sched, tn);
/* Stop and reset clock counter if required */
if (reset_clock)
sched_clck_reset(&trx->sched);
l1sched_clck_reset(sched);
return 0;
memcpy(&sched->sacch_cache[0], &meas_rep_dummy[0], sizeof(meas_rep_dummy));
}
struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn)
struct l1sched_ts *l1sched_add_ts(struct l1sched_state *sched, 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);
if (sched->ts[tn] != NULL) {
LOGP_SCHEDC(sched, LOGL_ERROR, "Timeslot #%u already allocated\n", tn);
return NULL;
}
LOGP(DSCH, LOGL_NOTICE, "Add a new TDMA timeslot #%u\n", tn);
LOGP_SCHEDC(sched, LOGL_NOTICE, "Add a new TDMA timeslot #%u\n", tn);
/* Allocate a new one */
trx->ts_list[tn] = talloc_zero(trx, struct trx_ts);
sched->ts[tn] = talloc_zero(sched, struct l1sched_ts);
sched->ts[tn]->sched = sched;
sched->ts[tn]->index = tn;
/* Add backpointer */
trx->ts_list[tn]->trx = trx;
/* Assign TS index */
trx->ts_list[tn]->index = tn;
return trx->ts_list[tn];
return sched->ts[tn];
}
void sched_trx_del_ts(struct trx_instance *trx, int tn)
void l1sched_del_ts(struct l1sched_state *sched, int tn)
{
struct trx_lchan_state *lchan, *lchan_next;
struct trx_ts *ts;
struct l1sched_lchan_state *lchan, *lchan_next;
struct l1sched_ts *ts;
/* Find ts in list */
ts = trx->ts_list[tn];
ts = sched->ts[tn];
if (ts == NULL)
return;
LOGP(DSCH, LOGL_NOTICE, "Delete TDMA timeslot #%u\n", tn);
LOGP_SCHEDC(sched, LOGL_NOTICE, "Delete TDMA timeslot #%u\n", tn);
/* Deactivate all logical channels */
sched_trx_deactivate_all_lchans(ts);
l1sched_deactivate_all_lchans(ts);
/* Free channel states */
llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
@ -232,47 +291,48 @@ void sched_trx_del_ts(struct trx_instance *trx, int tn)
}
/* Flush queue primitives for TX */
sched_prim_flush_queue(&ts->tx_prims);
l1sched_prim_flush_queue(&ts->tx_prims);
/* Remove ts from list and free memory */
trx->ts_list[tn] = NULL;
sched->ts[tn] = NULL;
talloc_free(ts);
/* Notify transceiver about that */
trx_if_cmd_setslot(trx, tn, 0);
l1sched_cfg_pchan_comb_req(sched, tn, GSM_PCHAN_NONE);
}
#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)
int l1sched_configure_ts(struct l1sched_state *sched, int tn,
enum gsm_phys_chan_config config)
{
struct trx_lchan_state *lchan;
enum trx_lchan_type type;
struct trx_ts *ts;
struct l1sched_lchan_state *lchan;
enum l1sched_lchan_type type;
struct l1sched_ts *ts;
/* Try to find specified ts */
ts = trx->ts_list[tn];
ts = sched->ts[tn];
if (ts != NULL) {
/* Reconfiguration of existing one */
sched_trx_reset_ts(trx, tn);
l1sched_reset_ts(sched, tn);
} else {
/* Allocate a new one if doesn't exist */
ts = sched_trx_add_ts(trx, tn);
ts = l1sched_add_ts(sched, tn);
if (ts == NULL)
return -ENOMEM;
}
/* Choose proper multiframe layout */
ts->mf_layout = sched_mframe_layout(config, tn);
ts->mf_layout = l1sched_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);
LOGP_SCHEDC(sched, 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);
@ -280,12 +340,12 @@ int sched_trx_configure_ts(struct trx_instance *trx, int tn,
INIT_LLIST_HEAD(&ts->lchans);
/* Allocate channel states */
for (type = 0; type < _TRX_CHAN_MAX; type++) {
for (type = 0; type < _L1SCHED_CHAN_MAX; type++) {
if (!LAYOUT_HAS_LCHAN(ts->mf_layout, type))
continue;
/* Allocate a channel state */
lchan = talloc_zero(ts, struct trx_lchan_state);
lchan = talloc_zero(ts, struct l1sched_lchan_state);
if (!lchan)
return -ENOMEM;
@ -299,24 +359,23 @@ int sched_trx_configure_ts(struct trx_instance *trx, int tn,
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);
if (l1sched_lchan_desc[type].flags & L1SCHED_CH_FLAG_AUTO)
l1sched_activate_lchan(ts, type);
}
/* Notify transceiver about TS activation */
/* FIXME: set proper channel type */
trx_if_cmd_setslot(trx, tn, 1);
l1sched_cfg_pchan_comb_req(sched, tn, config);
return 0;
}
int sched_trx_reset_ts(struct trx_instance *trx, int tn)
int l1sched_reset_ts(struct l1sched_state *sched, int tn)
{
struct trx_lchan_state *lchan, *lchan_next;
struct trx_ts *ts;
struct l1sched_lchan_state *lchan, *lchan_next;
struct l1sched_ts *ts;
/* Try to find specified ts */
ts = trx->ts_list[tn];
ts = sched->ts[tn];
if (ts == NULL)
return -EINVAL;
@ -324,10 +383,10 @@ int sched_trx_reset_ts(struct trx_instance *trx, int tn)
ts->mf_layout = NULL;
/* Flush queue primitives for TX */
sched_prim_flush_queue(&ts->tx_prims);
l1sched_prim_flush_queue(&ts->tx_prims);
/* Deactivate all logical channels */
sched_trx_deactivate_all_lchans(ts);
l1sched_deactivate_all_lchans(ts);
/* Free channel states */
llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
@ -336,15 +395,15 @@ int sched_trx_reset_ts(struct trx_instance *trx, int tn)
}
/* Notify transceiver about that */
trx_if_cmd_setslot(trx, tn, 0);
l1sched_cfg_pchan_comb_req(sched, tn, GSM_PCHAN_NONE);
return 0;
}
int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo,
uint8_t *key, uint8_t key_len)
int l1sched_start_ciphering(struct l1sched_ts *ts, uint8_t algo,
const uint8_t *key, uint8_t key_len)
{
struct trx_lchan_state *lchan;
struct l1sched_lchan_state *lchan;
/* Prevent NULL-pointer deference */
if (!ts)
@ -372,10 +431,10 @@ int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo,
return 0;
}
struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts,
enum trx_lchan_type chan)
struct l1sched_lchan_state *l1sched_find_lchan(struct l1sched_ts *ts,
enum l1sched_lchan_type chan)
{
struct trx_lchan_state *lchan;
struct l1sched_lchan_state *lchan;
llist_for_each_entry(lchan, &ts->lchans, list)
if (lchan->type == chan)
@ -384,52 +443,49 @@ struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts,
return NULL;
}
int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode)
int l1sched_set_lchans(struct l1sched_ts *ts, uint8_t chan_nr,
int active, uint8_t tch_mode, uint8_t tsc)
{
const struct trx_lchan_desc *lchan_desc;
struct trx_lchan_state *lchan;
const struct l1sched_lchan_desc *lchan_desc;
struct l1sched_lchan_state *lchan;
int rc = 0;
/* Prevent NULL-pointer deference */
if (ts == NULL) {
LOGP(DSCH, LOGL_ERROR, "Timeslot isn't configured\n");
return -EINVAL;
}
OSMO_ASSERT(ts != NULL);
/* Iterate over all allocated lchans */
llist_for_each_entry(lchan, &ts->lchans, list) {
lchan_desc = &trx_lchan_desc[lchan->type];
lchan_desc = &l1sched_lchan_desc[lchan->type];
if (lchan_desc->chan_nr == (chan_nr & 0xf8)) {
if (active) {
rc |= sched_trx_activate_lchan(ts, lchan->type);
rc |= l1sched_activate_lchan(ts, lchan->type);
lchan->tch_mode = tch_mode;
lchan->tsc = tsc;
} else
rc |= sched_trx_deactivate_lchan(ts, lchan->type);
rc |= l1sched_deactivate_lchan(ts, lchan->type);
}
}
return rc;
}
int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
int l1sched_activate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan)
{
const struct trx_lchan_desc *lchan_desc = &trx_lchan_desc[chan];
struct trx_lchan_state *lchan;
const struct l1sched_lchan_desc *lchan_desc = &l1sched_lchan_desc[chan];
struct l1sched_lchan_state *lchan;
/* Try to find requested logical channel */
lchan = sched_trx_find_lchan(ts, chan);
lchan = l1sched_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);
LOGP_LCHANC(lchan, LOGL_ERROR, "is already activated\n");
return -EINVAL;
}
LOGP(DSCH, LOGL_NOTICE, "Activating lchan=%s "
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
LOGP_LCHANC(lchan, LOGL_NOTICE, "activating\n");
/* Conditionally allocate memory for bursts */
if (lchan_desc->rx_fn && lchan_desc->burst_buf_size > 0) {
@ -452,19 +508,19 @@ int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
return 0;
}
static void sched_trx_reset_lchan(struct trx_lchan_state *lchan)
static void l1sched_reset_lchan(struct l1sched_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);
if (l1sched_lchan_desc[lchan->type].rx_fn && lchan->active) {
LOGP_LCHANC(lchan, LOGL_DEBUG, "TDMA statistics: "
"%lu DL frames have been processed, "
"%lu lost (compensated), last fn=%u\n",
lchan->tdma.num_proc,
lchan->tdma.num_lost,
lchan->tdma.last_proc);
}
/* Reset internal state variables */
@ -479,10 +535,10 @@ static void sched_trx_reset_lchan(struct trx_lchan_state *lchan)
lchan->tx_bursts = NULL;
/* Forget the current prim */
sched_prim_drop(lchan);
l1sched_prim_drop(lchan);
/* Channel specific stuff */
if (CHAN_IS_TCH(lchan->type)) {
if (L1SCHED_CHAN_IS_TCH(lchan->type)) {
lchan->dl_ongoing_facch = 0;
lchan->ul_facch_blocks = 0;
@ -490,7 +546,7 @@ static void sched_trx_reset_lchan(struct trx_lchan_state *lchan)
/* Reset AMR state */
memset(&lchan->amr, 0x00, sizeof(lchan->amr));
} else if (CHAN_IS_SACCH(lchan->type)) {
} else if (L1SCHED_CHAN_IS_SACCH(lchan->type)) {
/* Reset SACCH state */
memset(&lchan->sacch, 0x00, sizeof(lchan->sacch));
}
@ -502,26 +558,24 @@ static void sched_trx_reset_lchan(struct trx_lchan_state *lchan)
memset(&lchan->tdma, 0x00, sizeof(lchan->tdma));
}
int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
int l1sched_deactivate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan)
{
struct trx_lchan_state *lchan;
struct l1sched_lchan_state *lchan;
/* Try to find requested logical channel */
lchan = sched_trx_find_lchan(ts, chan);
lchan = l1sched_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);
LOGP_LCHANC(lchan, LOGL_ERROR, "is already deactivated\n");
return -EINVAL;
}
LOGP(DSCH, LOGL_DEBUG, "Deactivating lchan=%s "
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
LOGP_LCHANC(lchan, LOGL_DEBUG, "deactivating\n");
/* Reset internal state, free memory */
sched_trx_reset_lchan(lchan);
l1sched_reset_lchan(lchan);
/* Update activation flag */
lchan->active = 0;
@ -529,12 +583,13 @@ int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
return 0;
}
void sched_trx_deactivate_all_lchans(struct trx_ts *ts)
void l1sched_deactivate_all_lchans(struct l1sched_ts *ts)
{
struct trx_lchan_state *lchan;
struct l1sched_lchan_state *lchan;
LOGP(DSCH, LOGL_DEBUG, "Deactivating all logical channels "
"on ts=%d\n", ts->index);
LOGP_SCHEDC(ts->sched, LOGL_DEBUG,
"Deactivating all logical channels on ts=%d\n",
ts->index);
llist_for_each_entry(lchan, &ts->lchans, list) {
/* Omit inactive channels */
@ -542,14 +597,14 @@ void sched_trx_deactivate_all_lchans(struct trx_ts *ts)
continue;
/* Reset internal state, free memory */
sched_trx_reset_lchan(lchan);
l1sched_reset_lchan(lchan);
/* Update activation flag */
lchan->active = 0;
}
}
enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr)
enum gsm_phys_chan_config l1sched_chan_nr2pchan_config(uint8_t chan_nr)
{
uint8_t cbits = chan_nr >> 3;
@ -571,21 +626,21 @@ enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr)
return GSM_PCHAN_NONE;
}
enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr,
enum l1sched_lchan_type l1sched_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)
for (i = 0; i < _L1SCHED_CHAN_MAX; i++)
if (l1sched_lchan_desc[i].chan_nr == (chan_nr & 0xf8))
if (l1sched_lchan_desc[i].link_id == link_id)
return i;
return TRXC_IDLE;
return L1SCHED_IDLE;
}
static void sched_trx_a5_burst_dec(struct trx_lchan_state *lchan,
static void l1sched_a5_burst_dec(struct l1sched_lchan_state *lchan,
uint32_t fn, sbit_t *burst)
{
ubit_t ks[114];
@ -603,28 +658,28 @@ static void sched_trx_a5_burst_dec(struct trx_lchan_state *lchan,
}
}
static void sched_trx_a5_burst_enc(struct trx_lchan_state *lchan,
uint32_t fn, ubit_t *burst)
static void l1sched_a5_burst_enc(struct l1sched_lchan_state *lchan,
struct l1sched_burst_req *br)
{
ubit_t ks[114];
int i;
/* Generate keystream for an UL burst */
osmo_a5(lchan->a5.algo, lchan->a5.key, fn, NULL, ks);
osmo_a5(lchan->a5.algo, lchan->a5.key, br->fn, NULL, ks);
/* Apply keystream over plaintext */
for (i = 0; i < 57; i++) {
burst[i + 3] ^= ks[i];
burst[i + 88] ^= ks[i + 57];
br->burst[i + 3] ^= ks[i];
br->burst[i + 88] ^= ks[i + 57];
}
}
static int subst_frame_loss(struct trx_lchan_state *lchan,
trx_lchan_rx_func *handler,
static int subst_frame_loss(struct l1sched_lchan_state *lchan,
l1sched_lchan_rx_func *handler,
uint32_t fn)
{
const struct trx_multiframe *mf;
const struct trx_frame *fp;
const struct l1sched_tdma_multiframe *mf;
const struct l1sched_tdma_frame *fp;
int elapsed, i;
/* Wait until at least one TDMA frame is processed */
@ -646,27 +701,28 @@ static int subst_frame_loss(struct trx_lchan_state *lchan,
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);
LOGP_LCHAND(lchan, LOGL_ERROR, "Rx burst with fn=%u older than the last "
"processed fn=%u (see OS#4658) => dropping\n",
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);
LOGP_LCHANC(lchan, 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);
LOGP_LCHANC(lchan, 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 = {
struct l1sched_meas_set fake_meas = {
.fn = lchan->tdma.last_proc,
.rssi = -120,
.toa256 = 0,
@ -678,12 +734,11 @@ static int subst_frame_loss(struct trx_lchan_state *lchan,
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);
LOGP_LCHANC(lchan, LOGL_NOTICE,
"Substituting lost TDMA frame fn=%u\n",
fake_meas.fn);
handler(lchan->ts->trx, lchan->ts, lchan,
fake_meas.fn, fp->dl_bid,
bits, &fake_meas);
handler(lchan, fake_meas.fn, fp->dl_bid, bits, &fake_meas);
/* Update TDMA frame statistics */
lchan->tdma.last_proc = fake_meas.fn;
@ -694,24 +749,24 @@ static int subst_frame_loss(struct trx_lchan_state *lchan,
return 0;
}
int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
int l1sched_handle_rx_burst(struct l1sched_state *sched, uint8_t tn,
uint32_t fn, sbit_t *bits, uint16_t nbits,
const struct trx_meas_set *meas)
const struct l1sched_meas_set *meas)
{
struct trx_lchan_state *lchan;
const struct trx_frame *frame;
struct trx_ts *ts;
struct l1sched_lchan_state *lchan;
const struct l1sched_tdma_frame *frame;
struct l1sched_ts *ts;
trx_lchan_rx_func *handler;
enum trx_lchan_type chan;
l1sched_lchan_rx_func *handler;
enum l1sched_lchan_type chan;
uint8_t offset, bid;
int rc;
/* Check whether required timeslot is allocated and configured */
ts = trx->ts_list[tn];
ts = sched->ts[tn];
if (ts == NULL || ts->mf_layout == NULL) {
LOGP(DSCHD, LOGL_DEBUG, "TDMA timeslot #%u isn't configured, "
"ignoring burst...\n", tn);
LOGP_SCHEDD(sched, LOGL_DEBUG,
"Timeslot #%u isn't configured, ignoring burst...\n", tn);
return -EINVAL;
}
@ -722,7 +777,7 @@ int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
/* Get required info from frame */
bid = frame->dl_bid;
chan = frame->dl_chan;
handler = trx_lchan_desc[chan].rx_fn;
handler = l1sched_lchan_desc[chan].rx_fn;
/* Omit bursts which have no handler, like IDLE bursts.
* TODO: handle noise indications during IDLE frames. */
@ -730,7 +785,7 @@ int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
return -ENODEV;
/* Find required channel state */
lchan = sched_trx_find_lchan(ts, chan);
lchan = l1sched_find_lchan(ts, chan);
if (lchan == NULL)
return -ENODEV;
@ -745,10 +800,10 @@ int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
/* Perform A5/X decryption if required */
if (lchan->a5.algo)
sched_trx_a5_burst_dec(lchan, fn, bits);
l1sched_a5_burst_dec(lchan, fn, bits);
/* Put burst to handler */
handler(trx, ts, lchan, fn, bid, bits, meas);
handler(lchan, fn, bid, bits, meas);
/* Update TDMA frame statistics */
lchan->tdma.last_proc = fn;
@ -758,43 +813,24 @@ int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
* 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");
LOGP_LCHAND(lchan, 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)
void l1sched_lchan_meas_push(struct l1sched_lchan_state *lchan, const struct l1sched_meas_set *meas)
{
struct trx_lchan_meas_hist *hist = &lchan->meas_hist;
struct l1sched_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)
@ -806,10 +842,10 @@ void sched_trx_meas_push(struct trx_lchan_state *lchan, const struct trx_meas_se
}
/* Calculate the AVG of n measurements from the history */
void sched_trx_meas_avg(struct trx_lchan_state *lchan, unsigned int n)
void l1sched_lchan_meas_avg(struct l1sched_lchan_state *lchan, unsigned int n)
{
struct trx_lchan_meas_hist *hist = &lchan->meas_hist;
struct trx_meas_set *meas = hist->head;
struct l1sched_lchan_meas_hist *hist = &lchan->meas_hist;
struct l1sched_meas_set *meas = hist->head;
int toa256_sum = 0;
int rssi_sum = 0;
int i;

View File

@ -30,8 +30,6 @@
#include <netinet/in.h>
#include <sys/eventfd.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
@ -41,15 +39,15 @@
#include <osmocom/gsm/gsm_utils.h>
#include "l1ctl.h"
#include "trxcon.h"
#include "trx_if.h"
#include "logging.h"
#include "scheduler.h"
#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/trxcon/trxcon.h>
#include <osmocom/bb/trxcon/trx_if.h>
#include <osmocom/bb/trxcon/logging.h>
#ifdef IPCIF
#include "../Transceiver52M/l1if.h"
#endif
#define S(x) (1 << (x))
static void trx_fsm_cleanup_cb(struct osmo_fsm_inst *fi,
enum osmo_fsm_term_cause cause);
static struct value_string trx_evt_names[] = {
{ 0, NULL } /* no events? */
@ -58,8 +56,8 @@ static struct value_string trx_evt_names[] = {
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)),
S(TRX_STATE_IDLE) |
S(TRX_STATE_RSP_WAIT)),
.name = "OFFLINE",
},
[TRX_STATE_IDLE] = {
@ -68,25 +66,26 @@ static struct osmo_fsm_state trx_fsm_states[] = {
},
[TRX_STATE_ACTIVE] = {
.out_state_mask = (
GEN_MASK(TRX_STATE_IDLE) |
GEN_MASK(TRX_STATE_RSP_WAIT)),
S(TRX_STATE_IDLE) |
S(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)),
S(TRX_STATE_IDLE) |
S(TRX_STATE_ACTIVE) |
S(TRX_STATE_OFFLINE)),
.name = "RSP_WAIT",
},
};
static struct osmo_fsm trx_fsm = {
.name = "trx_interface_fsm",
.name = "trx_interface",
.states = trx_fsm_states,
.num_states = ARRAY_SIZE(trx_fsm_states),
.log_subsys = DTRX,
.log_subsys = DTRXC,
.event_names = trx_evt_names,
.cleanup = &trx_fsm_cleanup_cb,
};
static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local,
@ -148,24 +147,14 @@ static void trx_ctrl_send(struct trx_instance *trx)
return;
tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
#ifdef IPCIF
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);
#else
/* Send command */
LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
LOGPFSML(trx->fi, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0);
#endif
/* 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);
if (trx->fi->state != TRX_STATE_RSP_WAIT) {
trx->prev_state = trx->fi->state;
osmo_fsm_inst_state_chg(trx->fi, TRX_STATE_RSP_WAIT, 0, 0);
}
/* Start expire timer */
@ -183,13 +172,13 @@ static void trx_ctrl_timer_cb(void *data)
if (llist_empty(&trx->trx_ctrl_list))
return;
LOGP(DTRX, LOGL_NOTICE, "No response from transceiver...\n");
LOGPFSML(trx->fi, 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);
LOGPFSML(trx->fi, LOGL_NOTICE, "Transceiver offline\n");
osmo_fsm_inst_state_chg(trx->fi, TRX_STATE_OFFLINE, 0, 0);
osmo_fsm_inst_term(trx->fi, OSMO_FSM_TERM_TIMEOUT, NULL);
return;
}
@ -228,7 +217,7 @@ static int trx_ctrl_cmd(struct trx_instance *trx, int critical,
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);
LOGPFSML(trx->fi, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd);
/* Send message, if no pending messages */
if (!pending)
@ -258,11 +247,6 @@ static int trx_ctrl_cmd(struct trx_instance *trx, int critical,
* 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", "");
@ -277,7 +261,7 @@ 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");
LOGPFSML(trx->fi, LOGL_ERROR, "Suppressing POWERON as we're already powered up\n");
return -EAGAIN;
}
return trx_ctrl_cmd(trx, 1, "POWERON", "");
@ -294,9 +278,24 @@ int trx_if_cmd_poweron(struct trx_instance *trx)
* RSP SETSLOT <status> <timeslot> <chantype>
*/
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type)
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn,
enum gsm_phys_chan_config pchan)
{
return trx_ctrl_cmd(trx, 1, "SETSLOT", "%u %u", tn, type);
/* Values correspond to 'enum ChannelCombination' in osmo-trx.git */
static const uint8_t chan_types[_GSM_PCHAN_MAX] = {
[GSM_PCHAN_UNKNOWN] = 0,
[GSM_PCHAN_NONE] = 0,
[GSM_PCHAN_CCCH] = 4,
[GSM_PCHAN_CCCH_SDCCH4] = 5,
[GSM_PCHAN_CCCH_SDCCH4_CBCH] = 5,
[GSM_PCHAN_TCH_F] = 1,
[GSM_PCHAN_TCH_H] = 3,
[GSM_PCHAN_SDCCH8_SACCH8C] = 7,
[GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 7,
[GSM_PCHAN_PDCH] = 13,
};
return trx_ctrl_cmd(trx, 1, "SETSLOT", "%u %u", tn, chan_types[pchan]);
}
/*
@ -318,7 +317,7 @@ int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn)
/* 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);
LOGPFSML(trx->fi, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
return -ENOTSUP;
}
@ -332,7 +331,7 @@ int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn)
/* 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);
LOGPFSML(trx->fi, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
return -ENOTSUP;
}
@ -363,7 +362,7 @@ int trx_if_cmd_measure(struct trx_instance *trx,
/* 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);
LOGPFSML(trx->fi, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn_start);
return -ENOTSUP;
}
@ -372,6 +371,7 @@ int trx_if_cmd_measure(struct trx_instance *trx,
static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp)
{
struct trxcon_inst *trxcon = trx->trxcon;
unsigned int freq10;
uint16_t band_arfcn;
int dbm;
@ -383,16 +383,20 @@ static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp)
/* 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: "
LOGPFSML(trx->fi, 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);
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);
struct trxcon_param_full_power_scan_res res = {
.last_result = band_arfcn == trx->pm_band_arfcn_stop,
.band_arfcn = band_arfcn,
.dbm = dbm,
};
osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_FULL_POWER_SCAN_RES, &res);
/* Schedule a next measurement */
if (band_arfcn != trx->pm_band_arfcn_stop)
@ -430,8 +434,8 @@ int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta)
* 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)
int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn, uint8_t maio,
const uint16_t *ma, size_t ma_len)
{
/* Reserve some room for CMD SETFH <HSN> <MAIO> */
char ma_buf[TRXC_BUF_SIZE - 24];
@ -442,7 +446,7 @@ int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
/* Make sure that Mobile Allocation has at least one ARFCN */
if (!ma_len || ma == NULL) {
LOGP(DTRX, LOGL_ERROR, "Mobile Allocation is empty?!?\n");
LOGPFSML(trx->fi, LOGL_ERROR, "Mobile Allocation is empty?!?\n");
return -EINVAL;
}
@ -452,7 +456,7 @@ int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
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 "
LOGPFSML(trx->fi, LOGL_ERROR, "Failed to convert ARFCN %u "
"to a pair of Rx/Tx frequencies\n",
ma[i] & ~ARFCN_FLAG_MASK);
return -EINVAL;
@ -461,7 +465,7 @@ int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
/* 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 "
LOGPFSML(trx->fi, LOGL_ERROR, "Not enough room to encode "
"Mobile Allocation (N=%zu)\n", ma_len);
return -ENOSPC;
}
@ -486,25 +490,15 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
char buf[TRXC_BUF_SIZE], *p;
ssize_t read_len;
#ifdef IPCIF
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);
#else
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);
LOGPFSML(trx->fi, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
return read_len;
}
buf[read_len] = '\0';
#endif
if (!!strncmp(buf, "RSP ", 4)) {
LOGP(DTRX, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf);
LOGPFSML(trx->fi, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf);
return 0;
}
@ -512,15 +506,14 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
p = strchr(buf + 4, ' ');
rsp_len = p ? p - buf - 4 : strlen(buf) - 4;
LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf);
LOGPFSML(trx->fi, 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);
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");
LOGPFSML(trx->fi, LOGL_NOTICE, "Response message without command\n");
return -EINVAL;
}
@ -529,7 +522,7 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
/* Check if response matches command */
if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) {
LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
LOGPFSML(trx->fi, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
"Response message '%s' does not match command "
"message '%s'\n", buf, tcm->cmd);
goto rsp_error;
@ -538,7 +531,7 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
/* Check for response code */
sscanf(p + 1, "%d", &resp);
if (resp) {
LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
LOGPFSML(trx->fi, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
"Transceiver rejected TRX command with "
"response: '%s'\n", buf);
@ -549,18 +542,18 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
/* 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);
osmo_fsm_inst_state_chg(trx->fi, 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);
osmo_fsm_inst_state_chg(trx->fi, 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);
osmo_fsm_inst_state_chg(trx->fi, TRX_STATE_IDLE, 0, 0);
else
osmo_fsm_inst_state_chg(trx->fsm, trx->prev_state, 0, 0);
osmo_fsm_inst_state_chg(trx->fi, trx->prev_state, 0, 0);
/* Remove command from list */
llist_del(&tcm->list);
@ -572,8 +565,7 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
return 0;
rsp_error:
/* Notify higher layers about the problem */
osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_RSP_ERROR, trx);
osmo_fsm_inst_term(trx->fi, OSMO_FSM_TERM_ERROR, NULL);
return -EIO;
}
@ -602,7 +594,8 @@ rsp_error:
static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
{
struct trx_instance *trx = ofd->data;
struct trx_meas_set meas;
struct trxcon_inst *trxcon = trx->trxcon;
struct l1sched_meas_set meas;
uint8_t buf[TRXD_BUF_SIZE];
sbit_t bits[148];
int8_t rssi, tn;
@ -610,89 +603,67 @@ static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
uint32_t fn;
ssize_t read_len;
#ifdef IPCIF
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);
#else
read_len = read(ofd->fd, buf, sizeof(buf));
if (read_len <= 0) {
LOGP(DTRXD, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
LOGPFSMSL(trx->fi, 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);
if (read_len < (8 + 148)) { /* TRXDv0 header + GMSK burst */
LOGPFSMSL(trx->fi, DTRXD, LOGL_ERROR,
"Got data message with invalid length '%zd'\n", read_len);
return -EINVAL;
}
#endif
tn = buf[0];
fn = osmo_load32be(buf + 1);
rssi = -(int8_t)buf[5];
toa256 = ((int16_t)(buf[6] << 8) | buf[7]);
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);
memcpy(bits, buf + 8, 148);
for (unsigned int i = 0; i < 148; i++) {
if (buf[8 + i] == 255)
bits[i] = -127;
else
bits[i] = 127 - buf[8 + i];
}
if (tn >= 8) {
LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn);
LOGPFSMSL(trx->fi, DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn);
return -EINVAL;
}
if (fn >= 2715648) {
LOGP(DTRXD, LOGL_ERROR, "Illegal FN %u\n", fn);
LOGPFSMSL(trx->fi, 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);
LOGPFSMSL(trx->fi, 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) {
meas = (struct l1sched_meas_set) {
.toa256 = toa256,
.rssi = rssi,
.fn = fn,
};
/* Poke scheduler */
sched_trx_handle_rx_burst(trx, tn, fn, bits, 148, &meas);
l1sched_handle_rx_burst(trxcon->sched, tn, fn, bits, 148, &meas);
/* Correct local clock counter */
if (fn % 51 == 0)
sched_clck_handle(&trx->sched, fn);
l1sched_clck_handle(trxcon->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)
int trx_if_tx_burst(struct trx_instance *trx,
const struct l1sched_burst_req *br)
{
#ifdef IPCIF
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);
#else
uint8_t buf[TRXD_BUF_SIZE];
size_t length;
/**
* We must be sure that we have clock,
@ -702,86 +673,89 @@ int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
* 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");
if (trx->fi->state != TRX_STATE_ACTIVE) {
LOGPFSMSL(trx->fi, 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);
LOGPFSMSL(trx->fi, DTRXD, LOGL_DEBUG,
"TX burst tn=%u fn=%u pwr=%u\n",
br->tn, br->fn, br->pwr);
buf[0] = tn;
osmo_store32be(fn, buf + 1);
buf[5] = pwr;
buf[0] = br->tn;
osmo_store32be(br->fn, buf + 1);
buf[5] = br->pwr;
length = 6;
/* Copy ubits {0,1} */
memcpy(buf + 6, bits, 148);
if (br->burst_len != 0) {
memcpy(buf + 6, br->burst, br->burst_len);
length += br->burst_len;
}
/* Send data to transceiver */
send(trx->trx_ofd_data.fd, buf, 154, 0);
send(trx->trx_ofd_data.fd, buf, length, 0);
#endif
return 0;
}
/* Init TRX interface (TRXC, TRXD sockets and FSM) */
struct trx_instance *trx_if_open(void *tall_ctx,
struct trx_instance *trx_if_open(struct trxcon_inst *trxcon,
const char *local_host, const char *remote_host,
uint16_t base_port)
{
const unsigned int offset = trxcon->id * 2;
struct trx_instance *trx;
struct osmo_fsm_inst *fi;
int rc;
LOGP(DTRX, LOGL_NOTICE, "Init transceiver interface "
"(%s:%u)\n", remote_host, base_port);
LOGPFSML(trxcon->fi, LOGL_NOTICE, "Init transceiver interface "
"(%s:%u/%u)\n", remote_host, base_port, trxcon->id);
/* Try to allocate memory */
trx = talloc_zero(tall_ctx, struct trx_instance);
if (!trx) {
LOGP(DTRX, LOGL_ERROR, "Failed to allocate memory\n");
/* Allocate a new dedicated state machine */
fi = osmo_fsm_inst_alloc_child(&trx_fsm, trxcon->fi, TRXCON_EV_PHYIF_FAILURE);
if (fi == NULL) {
LOGPFSML(trxcon->fi, LOGL_ERROR, "Failed to allocate an instance "
"of FSM '%s'\n", trx_fsm.name);
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);
trx = talloc_zero(fi, struct trx_instance);
if (!trx) {
LOGPFSML(trxcon->fi, LOGL_ERROR, "Failed to allocate memory\n");
osmo_fsm_inst_free(fi);
return NULL;
}
/* Initialize CTRL queue */
INIT_LLIST_HEAD(&trx->trx_ctrl_list);
#ifdef IPCIF
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());
#else
/* Open sockets */
rc = trx_udp_open(trx, &trx->trx_ofd_ctrl, local_host, base_port + 101, remote_host, base_port + 1,
rc = trx_udp_open(trx, &trx->trx_ofd_ctrl, /* TRXC */
local_host, base_port + 101 + offset,
remote_host, base_port + 1 + offset,
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,
rc = trx_udp_open(trx, &trx->trx_ofd_data, /* TRXD */
local_host, base_port + 102 + offset,
remote_host, base_port + 2 + offset,
trx_data_rx_cb);
if (rc < 0)
goto udp_error;
#endif
trx->trxcon = trxcon;
fi->priv = trx;
trx->fi = fi;
return trx;
udp_error:
LOGP(DTRX, LOGL_ERROR, "Couldn't establish UDP connection\n");
osmo_fsm_inst_free(trx->fsm);
talloc_free(trx);
LOGPFSML(trx->fi, LOGL_ERROR, "Couldn't establish UDP connection\n");
osmo_fsm_inst_free(trx->fi);
return NULL;
}
@ -791,7 +765,7 @@ 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);
osmo_fsm_inst_state_chg(trx->fi, TRX_STATE_IDLE, 0, 0);
/* Clear command queue */
while (!llist_empty(&trx->trx_ctrl_list)) {
@ -804,26 +778,39 @@ void trx_if_flush_ctrl(struct trx_instance *trx)
void trx_if_close(struct trx_instance *trx)
{
if (trx == NULL || trx->fi == NULL)
return;
osmo_fsm_inst_term(trx->fi, OSMO_FSM_TERM_REQUEST, NULL);
}
static void trx_fsm_cleanup_cb(struct osmo_fsm_inst *fi,
enum osmo_fsm_term_cause cause)
{
static const char cmd_poweroff[] = "CMD POWEROFF";
struct trx_instance *trx = fi->priv;
/* May be unallocated due to init error */
if (!trx)
return;
LOGP(DTRX, LOGL_NOTICE, "Shutdown transceiver interface\n");
LOGPFSML(fi, LOGL_NOTICE, "Shutdown transceiver interface\n");
/* Abort TRXC response timer (if pending) */
osmo_timer_del(&trx->trx_ctrl_timer);
/* Flush CTRL message list */
trx_if_flush_ctrl(trx);
/* Power off if the transceiver is up */
if (trx->powered_up && trx->trx_ofd_ctrl.fd >= 0)
send(trx->trx_ofd_ctrl.fd, &cmd_poweroff[0], sizeof(cmd_poweroff), 0);
/* Close sockets */
#ifdef IPCIF
close(get_c_fd()->fd);
close(get_d_fd()->fd);
#else
trx_udp_close(&trx->trx_ofd_ctrl);
trx_udp_close(&trx->trx_ofd_data);
#endif
/* Free memory */
osmo_fsm_inst_free(trx->fsm);
trx->fi->priv = NULL;
talloc_free(trx);
}

555
trxcon/src/trxcon.c Normal file
View File

@ -0,0 +1,555 @@
/*
* OsmocomBB <-> SDR connection bridge
*
* (C) 2016-2022 by Vadim Yanitskiy <axilirator@gmail.com>
* Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
#include <signal.h>
#include <errno.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 <osmocom/bb/trxcon/trxcon.h>
#include <osmocom/bb/trxcon/trx_if.h>
#include <osmocom/bb/trxcon/logging.h>
#include <osmocom/bb/trxcon/l1ctl.h>
#include <osmocom/bb/trxcon/l1ctl_server.h>
#include <osmocom/bb/trxcon/l1ctl_proto.h>
#include <osmocom/bb/l1sched/l1sched.h>
#define COPYRIGHT \
"Copyright (C) 2016-2022 by Vadim Yanitskiy <axilirator@gmail.com>\n" \
"Contributions by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>\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 */
unsigned int max_clients;
const char *bind_socket;
/* TRX specific */
const char *trx_bind_ip;
const char *trx_remote_ip;
uint16_t trx_base_port;
uint32_t trx_fn_advance;
/* GSMTAP specific */
struct gsmtap_inst *gsmtap;
const char *gsmtap_ip;
} app_data = {
.max_clients = 1, /* only one L1CTL client by default */
.bind_socket = "/tmp/osmocom_l2",
.trx_remote_ip = "127.0.0.1",
.trx_bind_ip = "0.0.0.0",
.trx_base_port = 6700,
.trx_fn_advance = 3,
};
static void *tall_trxcon_ctx = NULL;
static void trxcon_gsmtap_send(const struct l1sched_lchan_desc *lchan_desc,
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)
{
/* GSMTAP logging may be not enabled */
if (app_data.gsmtap == NULL)
return;
/* Omit frames with unknown channel type */
if (lchan_desc->gsmtap_chan_type == GSMTAP_CHANNEL_UNKNOWN)
return;
/* TODO: distinguish GSMTAP_CHANNEL_PCH and GSMTAP_CHANNEL_AGCH */
gsmtap_send(app_data.gsmtap, band_arfcn, tn, lchan_desc->gsmtap_chan_type,
lchan_desc->ss_nr, fn, signal_dbm, snr, data, data_len);
}
/* External L1 API for the scheduler */
int l1sched_handle_config_req(struct l1sched_state *sched,
const struct l1sched_config_req *cr)
{
struct trxcon_inst *trxcon = sched->priv;
switch (cr->type) {
case L1SCHED_CFG_PCHAN_COMB:
{
struct trxcon_param_set_phy_config_req req = {
.type = TRXCON_PHY_CFGT_PCHAN_COMB,
.pchan_comb = {
.tn = cr->pchan_comb.tn,
.pchan = cr->pchan_comb.pchan,
},
};
return osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_SET_PHY_CONFIG_REQ, &req);
}
default:
LOGPFSML(trxcon->fi, LOGL_ERROR,
"Unhandled config request (type 0x%02x)\n", cr->type);
return -ENODEV;
}
}
int l1sched_handle_burst_req(struct l1sched_state *sched,
const struct l1sched_burst_req *br)
{
struct trxcon_inst *trxcon = sched->priv;
return trx_if_tx_burst(trxcon->phyif, br);
}
/* External L2 API for the scheduler */
int l1sched_handle_data_ind(struct l1sched_lchan_state *lchan,
const uint8_t *data, size_t data_len,
int n_errors, int n_bits_total,
enum l1sched_data_type dt)
{
const struct l1sched_meas_set *meas = &lchan->meas_avg;
const struct l1sched_lchan_desc *lchan_desc;
struct l1sched_state *sched = lchan->ts->sched;
struct trxcon_inst *trxcon = sched->priv;
int rc;
lchan_desc = &l1sched_lchan_desc[lchan->type];
struct trxcon_param_rx_traffic_data_ind ind = {
.chan_nr = lchan_desc->chan_nr | lchan->ts->index,
.link_id = lchan_desc->link_id,
.frame_nr = meas->fn,
.toa256 = meas->toa256,
.rssi = meas->rssi,
.n_errors = n_errors,
.n_bits_total = n_bits_total,
.data_len = data_len,
.data = data,
};
switch (dt) {
case L1SCHED_DT_TRAFFIC:
case L1SCHED_DT_PACKET_DATA:
rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_RX_TRAFFIC_IND, &ind);
break;
case L1SCHED_DT_SIGNALING:
rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_RX_DATA_IND, &ind);
break;
case L1SCHED_DT_OTHER:
if (lchan->type == L1SCHED_SCH) {
if (trxcon->fi->state != TRXCON_ST_FBSB_SEARCH)
return 0;
rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_FBSB_SEARCH_RES, NULL);
break;
}
/* fall through */
default:
LOGPFSML(trxcon->fi, LOGL_ERROR,
"Unhandled L2 DATA.ind (type 0x%02x)\n", dt);
return -ENODEV;
}
if (data != NULL && data_len > 0) {
trxcon_gsmtap_send(lchan_desc, meas->fn, lchan->ts->index,
trxcon->l1p.band_arfcn, meas->rssi, 0,
data, data_len);
}
return rc;
}
int l1sched_handle_data_cnf(struct l1sched_lchan_state *lchan,
uint32_t fn, enum l1sched_data_type dt)
{
const struct l1sched_lchan_desc *lchan_desc;
struct l1sched_state *sched = lchan->ts->sched;
struct trxcon_inst *trxcon = sched->priv;
struct l1ctl_info_dl dl_hdr;
const uint8_t *data;
uint8_t ra_buf[2];
size_t data_len;
int rc;
lchan_desc = &l1sched_lchan_desc[lchan->type];
dl_hdr = (struct l1ctl_info_dl) {
.chan_nr = lchan_desc->chan_nr | lchan->ts->index,
.link_id = lchan_desc->link_id,
.frame_nr = htonl(fn),
.band_arfcn = htons(trxcon->l1p.band_arfcn),
};
switch (dt) {
case L1SCHED_DT_TRAFFIC:
case L1SCHED_DT_PACKET_DATA:
rc = l1ctl_tx_dt_conf(trxcon->l2if, &dl_hdr, true);
data_len = lchan->prim->payload_len;
data = lchan->prim->payload;
break;
case L1SCHED_DT_SIGNALING:
rc = l1ctl_tx_dt_conf(trxcon->l2if, &dl_hdr, false);
data_len = lchan->prim->payload_len;
data = lchan->prim->payload;
break;
case L1SCHED_DT_OTHER:
if (L1SCHED_PRIM_IS_RACH(lchan->prim)) {
const struct l1sched_ts_prim_rach *rach;
rach = (struct l1sched_ts_prim_rach *)lchan->prim->payload;
rc = l1ctl_tx_rach_conf(trxcon->l2if, trxcon->l1p.band_arfcn, fn);
if (lchan->prim->type == L1SCHED_PRIM_RACH11) {
ra_buf[0] = (uint8_t)(rach->ra >> 3);
ra_buf[1] = (uint8_t)(rach->ra & 0x07);
data = &ra_buf[0];
data_len = 2;
} else {
ra_buf[0] = (uint8_t)(rach->ra);
data = &ra_buf[0];
data_len = 1;
}
break;
}
/* fall through */
default:
LOGPFSML(trxcon->fi, LOGL_ERROR,
"Unhandled L2 DATA.cnf (type 0x%02x)\n", dt);
return -ENODEV;
}
trxcon_gsmtap_send(lchan_desc, fn, lchan->ts->index,
trxcon->l1p.band_arfcn | ARFCN_UPLINK,
0, 0, data, data_len);
return rc;
}
struct trxcon_inst *trxcon_inst_alloc(void *ctx, unsigned int id)
{
struct trxcon_inst *trxcon;
struct osmo_fsm_inst *fi;
fi = osmo_fsm_inst_alloc(&trxcon_fsm_def, ctx, NULL, LOGL_DEBUG, NULL);
OSMO_ASSERT(fi != NULL);
trxcon = talloc_zero(fi, struct trxcon_inst);
OSMO_ASSERT(trxcon != NULL);
fi->priv = trxcon;
trxcon->fi = fi;
osmo_fsm_inst_update_id_f(fi, "%u", id);
trxcon->id = id;
/* Logging context to be used by both l1ctl and l1sched modules */
trxcon->log_prefix = talloc_asprintf(trxcon, "%s: ", osmo_fsm_inst_name(fi));
/* Init transceiver interface */
trxcon->phyif = trx_if_open(trxcon,
app_data.trx_bind_ip,
app_data.trx_remote_ip,
app_data.trx_base_port);
if (trxcon->phyif == NULL) {
trxcon_inst_free(trxcon);
return NULL;
}
/* Init scheduler */
const struct l1sched_cfg sched_cfg = {
.fn_advance = app_data.trx_fn_advance,
.log_prefix = trxcon->log_prefix,
};
trxcon->sched = l1sched_alloc(trxcon, &sched_cfg, trxcon);
if (trxcon->sched == NULL) {
trxcon_inst_free(trxcon);
return NULL;
}
return trxcon;
}
void trxcon_inst_free(struct trxcon_inst *trxcon)
{
if (trxcon == NULL || trxcon->fi == NULL)
return;
osmo_fsm_inst_term(trxcon->fi, OSMO_FSM_TERM_REQUEST, NULL);
}
static void l1ctl_conn_accept_cb(struct l1ctl_client *l1c)
{
struct trxcon_inst *trxcon;
trxcon = trxcon_inst_alloc(l1c, l1c->id);
if (trxcon == NULL) {
l1ctl_client_conn_close(l1c);
return;
}
l1c->log_prefix = talloc_strdup(l1c, trxcon->log_prefix);
l1c->priv = trxcon->fi;
trxcon->l2if = l1c;
}
static void l1ctl_conn_close_cb(struct l1ctl_client *l1c)
{
struct osmo_fsm_inst *fi = l1c->priv;
if (fi == NULL)
return;
osmo_fsm_inst_dispatch(fi, TRXCON_EV_L2IF_FAILURE, NULL);
}
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 (e.g. DL1C:DSCH)\n");
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(" -C --max-clients Maximum number of L1CTL connections (default 1)\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'},
{"max-clients", 1, 0, 'C'},
{"daemonize", 0, 0, 'D'},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, "d:b:i:p:f:s:g:C: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 'C':
app_data.max_clients = atoi(optarg);
break;
case 'D':
app_data.daemonize = 1;
break;
default:
break;
}
}
}
static void signal_handler(int signum)
{
fprintf(stderr, "signal %u received\n", signum);
switch (signum) {
case SIGINT:
case SIGTERM:
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;
}
}
int main(int argc, char **argv)
{
struct l1ctl_server_cfg server_cfg;
struct l1ctl_server *server = NULL;
int rc = 0;
printf("%s", COPYRIGHT);
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(SIGTERM, &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);
l1sched_logging_init(DSCH, DSCHD);
/* 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);
log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END);
osmo_fsm_log_timeouts(true);
/* Optional GSMTAP */
if (app_data.gsmtap_ip != NULL) {
app_data.gsmtap = gsmtap_source_init(app_data.gsmtap_ip, GSMTAP_UDP_PORT, 1);
if (!app_data.gsmtap) {
LOGP(DAPP, LOGL_ERROR, "Failed to init GSMTAP\n");
goto exit;
}
/* Suppress ICMP "destination unreachable" errors */
gsmtap_source_add_sink(app_data.gsmtap);
}
/* Start the L1CTL server */
server_cfg = (struct l1ctl_server_cfg) {
.sock_path = app_data.bind_socket,
.num_clients_max = app_data.max_clients,
.conn_read_cb = &l1ctl_rx_cb,
.conn_accept_cb = &l1ctl_conn_accept_cb,
.conn_close_cb = &l1ctl_conn_close_cb,
};
server = l1ctl_server_alloc(tall_trxcon_ctx, &server_cfg);
if (server == NULL) {
rc = EXIT_FAILURE;
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));
while (!app_data.quit)
osmo_select_main(0);
exit:
if (server != NULL)
l1ctl_server_free(server);
/* 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;
}

611
trxcon/src/trxcon_fsm.c Normal file
View File

@ -0,0 +1,611 @@
/*
* OsmocomBB <-> SDR connection bridge
*
* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/bb/trxcon/trxcon.h>
#include <osmocom/bb/trxcon/trx_if.h>
#include <osmocom/bb/trxcon/logging.h>
#include <osmocom/bb/trxcon/l1ctl.h>
#include <osmocom/bb/trxcon/l1ctl_server.h>
#include <osmocom/bb/trxcon/l1ctl_proto.h>
#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/logging.h>
#define S(x) (1 << (x))
static void trxcon_allstate_action(struct osmo_fsm_inst *fi,
uint32_t event, void *data)
{
struct trxcon_inst *trxcon = fi->priv;
switch (event) {
case TRXCON_EV_PHYIF_FAILURE:
trxcon->phyif = NULL;
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
break;
case TRXCON_EV_L2IF_FAILURE:
trxcon->l2if = NULL;
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
break;
case TRXCON_EV_RESET_FULL_REQ:
if (fi->state != TRXCON_ST_RESET)
osmo_fsm_inst_state_chg(fi, TRXCON_ST_RESET, 0, 0);
l1sched_reset(trxcon->sched, true);
trx_if_cmd_poweroff(trxcon->phyif);
trx_if_cmd_echo(trxcon->phyif);
break;
case TRXCON_EV_RESET_SCHED_REQ:
l1sched_reset(trxcon->sched, false);
break;
case TRXCON_EV_SET_PHY_CONFIG_REQ:
{
const struct trxcon_param_set_phy_config_req *req = data;
switch (req->type) {
case TRXCON_PHY_CFGT_PCHAN_COMB:
trx_if_cmd_setslot(trxcon->phyif,
req->pchan_comb.tn,
req->pchan_comb.pchan);
break;
case TRXCON_PHY_CFGT_TX_PARAMS:
if (trxcon->l1p.ta != req->tx_params.timing_advance)
trx_if_cmd_setta(trxcon->phyif, req->tx_params.timing_advance);
trxcon->l1p.tx_power = req->tx_params.tx_power;
trxcon->l1p.ta = req->tx_params.timing_advance;
break;
}
break;
}
case TRXCON_EV_UPDATE_SACCH_CACHE_REQ:
{
const struct trxcon_param_tx_traffic_data_req *req = data;
if (req->link_id != L1SCHED_CH_LID_SACCH) {
LOGPFSML(fi, LOGL_ERROR, "Unexpected link_id=0x%02x\n", req->link_id);
break;
}
if (req->data_len != GSM_MACBLOCK_LEN) {
LOGPFSML(fi, LOGL_ERROR, "Unexpected data length=%zu\n", req->data_len);
break;
}
memcpy(&trxcon->sched->sacch_cache[0], req->data, req->data_len);
break;
}
default:
OSMO_ASSERT(0);
}
}
static int trxcon_timer_cb(struct osmo_fsm_inst *fi)
{
struct trxcon_inst *trxcon = fi->priv;
switch (fi->state) {
case TRXCON_ST_FBSB_SEARCH:
l1ctl_tx_fbsb_fail(trxcon->l2if, trxcon->l1p.band_arfcn);
osmo_fsm_inst_state_chg(fi, TRXCON_ST_RESET, 0, 0);
return 0;
default:
OSMO_ASSERT(0);
}
}
static void trxcon_st_reset_action(struct osmo_fsm_inst *fi,
uint32_t event, void *data)
{
struct trxcon_inst *trxcon = fi->priv;
switch (event) {
case TRXCON_EV_FBSB_SEARCH_REQ:
{
const struct trxcon_param_fbsb_search_req *req = data;
const struct trx_instance *trx = trxcon->phyif;
osmo_fsm_inst_state_chg_ms(fi, TRXCON_ST_FBSB_SEARCH, req->timeout_ms, 0);
l1sched_configure_ts(trxcon->sched, 0, req->pchan_config);
/* Only if current ARFCN differs */
if (trxcon->l1p.band_arfcn != req->band_arfcn) {
/* Update current ARFCN */
trxcon->l1p.band_arfcn = req->band_arfcn;
/* Tune transceiver to required ARFCN */
trx_if_cmd_rxtune(trxcon->phyif, req->band_arfcn);
trx_if_cmd_txtune(trxcon->phyif, req->band_arfcn);
}
/* Transceiver might have been powered on before, e.g.
* in case of sending L1CTL_FBSB_REQ due to signal loss. */
if (!trx->powered_up)
trx_if_cmd_poweron(trxcon->phyif);
break;
}
case TRXCON_EV_FULL_POWER_SCAN_REQ:
{
const struct trxcon_param_full_power_scan_req *req = data;
osmo_fsm_inst_state_chg(fi, TRXCON_ST_FULL_POWER_SCAN, 0, 0); /* TODO: timeout */
trx_if_cmd_measure(trxcon->phyif, req->band_arfcn_start, req->band_arfcn_stop);
break;
}
default:
OSMO_ASSERT(0);
}
}
static void trxcon_st_full_power_scan_action(struct osmo_fsm_inst *fi,
uint32_t event, void *data)
{
struct trxcon_inst *trxcon = fi->priv;
switch (event) {
case TRXCON_EV_FULL_POWER_SCAN_RES:
{
const struct trxcon_param_full_power_scan_res *res = data;
l1ctl_tx_pm_conf(trxcon->l2if, res->band_arfcn, res->dbm, res->last_result);
break;
}
case TRXCON_EV_FULL_POWER_SCAN_REQ:
{
const struct trxcon_param_full_power_scan_req *req = data;
trx_if_cmd_measure(trxcon->phyif, req->band_arfcn_start, req->band_arfcn_stop);
break;
}
default:
OSMO_ASSERT(0);
}
}
static void trxcon_st_fbsb_search_action(struct osmo_fsm_inst *fi,
uint32_t event, void *data)
{
struct trxcon_inst *trxcon = fi->priv;
switch (event) {
case TRXCON_EV_FBSB_SEARCH_RES:
osmo_fsm_inst_state_chg(fi, TRXCON_ST_BCCH_CCCH, 0, 0);
l1ctl_tx_fbsb_conf(trxcon->l2if,
trxcon->l1p.band_arfcn,
trxcon->sched->bsic);
break;
default:
OSMO_ASSERT(0);
}
}
static void handle_tx_access_burst_req(struct osmo_fsm_inst *fi,
const struct trxcon_param_tx_access_burst_req *req)
{
struct trxcon_inst *trxcon = fi->priv;
enum l1sched_ts_prim_type prim_type;
const struct l1sched_ts_prim *prim;
const struct l1sched_ts_prim_rach rach = {
.synch_seq = req->synch_seq,
.offset = req->offset,
.ra = req->ra,
};
prim_type = req->is_11bit ? L1SCHED_PRIM_RACH11 : L1SCHED_PRIM_RACH8;
prim = l1sched_prim_push(trxcon->sched, prim_type,
req->chan_nr, req->link_id,
(const uint8_t *)&rach, sizeof(rach));
if (prim == NULL)
LOGPFSML(fi, LOGL_ERROR, "Failed to enqueue a prim\n");
}
static void trxcon_st_bcch_ccch_action(struct osmo_fsm_inst *fi,
uint32_t event, void *data)
{
struct trxcon_inst *trxcon = fi->priv;
struct l1sched_ts *ts;
int rc;
switch (event) {
case TRXCON_EV_TX_ACCESS_BURST_REQ:
handle_tx_access_burst_req(fi, data);
break;
case TRXCON_EV_SET_CCCH_MODE_REQ:
{
struct trxcon_param_set_ccch_tch_mode_req *req = data;
enum gsm_phys_chan_config chan_config = req->mode;
/* Make sure that TS0 is allocated and configured */
ts = trxcon->sched->ts[0];
if (ts == NULL || ts->mf_layout == NULL) {
LOGPFSML(fi, LOGL_ERROR, "TS0 is not configured\n");
return;
}
/* Do nothing if the current mode matches required */
if (ts->mf_layout->chan_config != chan_config)
l1sched_configure_ts(trxcon->sched, 0, chan_config);
req->applied = true;
break;
}
case TRXCON_EV_DEDICATED_ESTABLISH_REQ:
{
const struct trxcon_param_dedicated_establish_req *req = data;
enum gsm_phys_chan_config config;
config = l1sched_chan_nr2pchan_config(req->chan_nr);
if (config == GSM_PCHAN_NONE) {
LOGPFSML(fi, LOGL_ERROR, "Failed to determine channel config\n");
return;
}
if (req->hopping) {
/* Apply the freq. hopping parameters */
rc = trx_if_cmd_setfh(trxcon->phyif,
req->h1.hsn, req->h1.maio,
&req->h1.ma[0], req->h1.n);
if (rc)
return;
/* Set current ARFCN to an invalid value */
trxcon->l1p.band_arfcn = 0xffff;
} else {
/* Tune transceiver to required ARFCN */
if (trx_if_cmd_rxtune(trxcon->phyif, req->h0.band_arfcn))
return;
if (trx_if_cmd_txtune(trxcon->phyif, req->h0.band_arfcn))
return;
/* Update current ARFCN */
trxcon->l1p.band_arfcn = req->h0.band_arfcn;
}
rc = l1sched_configure_ts(trxcon->sched, req->chan_nr & 0x07, config);
if (rc)
return;
ts = trxcon->sched->ts[req->chan_nr & 0x07];
OSMO_ASSERT(ts != NULL);
l1sched_deactivate_all_lchans(ts);
/* Activate only requested lchans */
rc = l1sched_set_lchans(ts, req->chan_nr, 1, req->tch_mode, req->tsc);
if (rc) {
LOGPFSML(fi, LOGL_ERROR, "Failed to activate requested lchans\n");
return;
}
if (config == GSM_PCHAN_PDCH)
osmo_fsm_inst_state_chg(fi, TRXCON_ST_PACKET_DATA, 0, 0);
else
osmo_fsm_inst_state_chg(fi, TRXCON_ST_DEDICATED, 0, 0);
break;
}
case TRXCON_EV_RX_DATA_IND:
{
const struct trxcon_param_rx_traffic_data_ind *ind = data;
const struct l1ctl_info_dl dl_hdr = {
.chan_nr = ind->chan_nr,
.link_id = ind->link_id,
.frame_nr = htonl(ind->frame_nr),
.band_arfcn = htons(trxcon->l1p.band_arfcn),
.fire_crc = ind->data_len > 0 ? 0 : 2,
.rx_level = dbm2rxlev(ind->rssi),
.num_biterr = ind->n_errors,
/* TODO: set proper .snr */
};
l1ctl_tx_dt_ind(trxcon->l2if, &dl_hdr, ind->data, ind->data_len, false);
break;
}
default:
OSMO_ASSERT(0);
}
}
static void trxcon_st_dedicated_action(struct osmo_fsm_inst *fi,
uint32_t event, void *data)
{
struct trxcon_inst *trxcon = fi->priv;
switch (event) {
case TRXCON_EV_TX_ACCESS_BURST_REQ:
handle_tx_access_burst_req(fi, data);
break;
case TRXCON_EV_DEDICATED_RELEASE_REQ:
l1sched_reset(trxcon->sched, false);
osmo_fsm_inst_state_chg(fi, TRXCON_ST_RESET, 0, 0);
break;
case TRXCON_EV_SET_TCH_MODE_REQ:
{
struct trxcon_param_set_ccch_tch_mode_req *req = data;
unsigned int tn;
/* Iterate over timeslot list */
for (tn = 0; tn < ARRAY_SIZE(trxcon->sched->ts); tn++) {
struct l1sched_ts *ts = trxcon->sched->ts[tn];
struct l1sched_lchan_state *lchan;
/* Timeslot is not allocated */
if (ts == NULL || 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;
lchan->tch_mode = req->mode;
if (req->mode == GSM48_CMODE_SPEECH_AMR) {
uint8_t bmask = req->amr.codecs_bitmask;
int n = 0;
int acum = 0;
int pos;
while ((pos = ffs(bmask)) != 0) {
acum += pos;
LOGPFSML(fi, LOGL_DEBUG,
LOGP_LCHAN_NAME_FMT " AMR codec[%u] = %u\n",
LOGP_LCHAN_NAME_ARGS(lchan), n, acum - 1);
lchan->amr.codec[n++] = acum - 1;
bmask >>= pos;
}
if (n == 0) {
LOGPFSML(fi, LOGL_ERROR,
LOGP_LCHAN_NAME_FMT " Empty AMR codec mode bitmask!\n",
LOGP_LCHAN_NAME_ARGS(lchan));
continue;
}
lchan->amr.codecs = n;
lchan->amr.dl_ft = req->amr.start_codec;
lchan->amr.dl_cmr = req->amr.start_codec;
lchan->amr.ul_ft = req->amr.start_codec;
lchan->amr.ul_cmr = req->amr.start_codec;
}
req->applied = true;
}
}
break;
}
case TRXCON_EV_CRYPTO_REQ:
{
const struct trxcon_param_crypto_req *req = data;
unsigned int tn = req->chan_nr & 0x07;
struct l1sched_ts *ts;
/* Make sure that required TS is allocated and configured */
ts = trxcon->sched->ts[tn];
if (ts == NULL || ts->mf_layout == NULL) {
LOGPFSML(fi, LOGL_ERROR, "TS%u is not configured\n", tn);
return;
}
if (l1sched_start_ciphering(ts, req->a5_algo, req->key, req->key_len) != 0) {
LOGPFSML(fi, LOGL_ERROR, "Failed to configure ciphering\n");
return;
}
break;
}
case TRXCON_EV_TX_TRAFFIC_REQ:
case TRXCON_EV_TX_DATA_REQ:
{
const struct trxcon_param_tx_traffic_data_req *req = data;
struct l1sched_ts_prim *prim;
prim = l1sched_prim_push(trxcon->sched, L1SCHED_PRIM_DATA,
req->chan_nr, req->link_id,
req->data, req->data_len);
if (prim == NULL) {
LOGPFSML(fi, LOGL_ERROR, "Failed to enqueue a prim\n");
return;
}
break;
}
case TRXCON_EV_RX_TRAFFIC_IND:
case TRXCON_EV_RX_DATA_IND:
{
const struct trxcon_param_rx_traffic_data_ind *ind = data;
const struct l1ctl_info_dl dl_hdr = {
.chan_nr = ind->chan_nr,
.link_id = ind->link_id,
.frame_nr = htonl(ind->frame_nr),
.band_arfcn = htons(trxcon->l1p.band_arfcn),
.fire_crc = ind->data_len > 0 ? 0 : 2,
.rx_level = dbm2rxlev(ind->rssi),
.num_biterr = ind->n_errors,
/* TODO: set proper .snr */
};
l1ctl_tx_dt_ind(trxcon->l2if, &dl_hdr,
ind->data, ind->data_len,
event == TRXCON_EV_RX_TRAFFIC_IND);
break;
}
default:
OSMO_ASSERT(0);
}
}
static void trxcon_st_packet_data_action(struct osmo_fsm_inst *fi,
uint32_t event, void *data)
{
struct trxcon_inst *trxcon = fi->priv;
switch (event) {
case TRXCON_EV_TX_ACCESS_BURST_REQ:
handle_tx_access_burst_req(fi, data);
break;
case TRXCON_EV_RX_TRAFFIC_IND:
LOGPFSML(fi, LOGL_NOTICE, "Rx PDTCH/D message\n");
break;
case TRXCON_EV_RX_DATA_IND:
LOGPFSML(fi, LOGL_NOTICE, "Rx PTCCH/D message\n");
break;
case TRXCON_EV_DEDICATED_RELEASE_REQ:
l1sched_reset(trxcon->sched, false);
osmo_fsm_inst_state_chg(fi, TRXCON_ST_RESET, 0, 0);
break;
default:
OSMO_ASSERT(0);
}
}
static void trxcon_fsm_pre_term_cb(struct osmo_fsm_inst *fi,
enum osmo_fsm_term_cause cause)
{
struct trxcon_inst *trxcon = fi->priv;
if (trxcon == NULL)
return;
/* Shutdown the scheduler */
if (trxcon->sched != NULL)
l1sched_free(trxcon->sched);
/* Close active connections */
if (trxcon->l2if != NULL) {
/* Avoid use-after-free: both *fi and *trxcon are children of
* the L2IF (L1CTL connection), so we need to re-parent *fi
* to NULL before calling l1ctl_client_conn_close(). */
talloc_steal(NULL, fi);
l1ctl_client_conn_close(trxcon->l2if);
}
if (trxcon->phyif != NULL)
trx_if_close(trxcon->phyif);
talloc_free(trxcon);
fi->priv = NULL;
}
static const struct osmo_fsm_state trxcon_fsm_states[] = {
[TRXCON_ST_RESET] = {
.name = "RESET",
.out_state_mask = S(TRXCON_ST_FBSB_SEARCH)
| S(TRXCON_ST_FULL_POWER_SCAN),
.in_event_mask = S(TRXCON_EV_FBSB_SEARCH_REQ)
| S(TRXCON_EV_FULL_POWER_SCAN_REQ),
.action = &trxcon_st_reset_action,
},
[TRXCON_ST_FULL_POWER_SCAN] = {
.name = "FULL_POWER_SCAN",
.out_state_mask = S(TRXCON_ST_RESET),
.in_event_mask = S(TRXCON_EV_FULL_POWER_SCAN_RES)
| S(TRXCON_EV_FULL_POWER_SCAN_REQ),
.action = &trxcon_st_full_power_scan_action,
},
[TRXCON_ST_FBSB_SEARCH] = {
.name = "FBSB_SEARCH",
.out_state_mask = S(TRXCON_ST_RESET)
| S(TRXCON_ST_BCCH_CCCH),
.in_event_mask = S(TRXCON_EV_FBSB_SEARCH_RES),
.action = &trxcon_st_fbsb_search_action,
},
[TRXCON_ST_BCCH_CCCH] = {
.name = "BCCH_CCCH",
.out_state_mask = S(TRXCON_ST_RESET)
| S(TRXCON_ST_FBSB_SEARCH)
| S(TRXCON_ST_DEDICATED)
| S(TRXCON_ST_PACKET_DATA),
.in_event_mask = S(TRXCON_EV_RX_DATA_IND)
| S(TRXCON_EV_SET_CCCH_MODE_REQ)
| S(TRXCON_EV_TX_ACCESS_BURST_REQ)
| S(TRXCON_EV_DEDICATED_ESTABLISH_REQ),
.action = &trxcon_st_bcch_ccch_action,
},
[TRXCON_ST_DEDICATED] = {
.name = "DEDICATED",
.out_state_mask = S(TRXCON_ST_RESET)
| S(TRXCON_ST_FBSB_SEARCH)
| S(TRXCON_ST_BCCH_CCCH),
.in_event_mask = S(TRXCON_EV_DEDICATED_RELEASE_REQ)
| S(TRXCON_EV_TX_ACCESS_BURST_REQ)
| S(TRXCON_EV_SET_TCH_MODE_REQ)
| S(TRXCON_EV_TX_TRAFFIC_REQ)
| S(TRXCON_EV_RX_TRAFFIC_IND)
| S(TRXCON_EV_TX_DATA_REQ)
| S(TRXCON_EV_RX_DATA_IND)
| S(TRXCON_EV_CRYPTO_REQ),
.action = &trxcon_st_dedicated_action,
},
[TRXCON_ST_PACKET_DATA] = {
.name = "PACKET_DATA",
.out_state_mask = S(TRXCON_ST_RESET)
| S(TRXCON_ST_FBSB_SEARCH)
| S(TRXCON_ST_BCCH_CCCH),
.in_event_mask = S(TRXCON_EV_DEDICATED_RELEASE_REQ)
| S(TRXCON_EV_TX_ACCESS_BURST_REQ)
| S(TRXCON_EV_RX_TRAFFIC_IND)
| S(TRXCON_EV_RX_DATA_IND),
.action = &trxcon_st_packet_data_action,
},
};
static const struct value_string trxcon_fsm_event_names[] = {
OSMO_VALUE_STRING(TRXCON_EV_PHYIF_FAILURE),
OSMO_VALUE_STRING(TRXCON_EV_L2IF_FAILURE),
OSMO_VALUE_STRING(TRXCON_EV_RESET_FULL_REQ),
OSMO_VALUE_STRING(TRXCON_EV_RESET_SCHED_REQ),
OSMO_VALUE_STRING(TRXCON_EV_FULL_POWER_SCAN_REQ),
OSMO_VALUE_STRING(TRXCON_EV_FULL_POWER_SCAN_RES),
OSMO_VALUE_STRING(TRXCON_EV_FBSB_SEARCH_REQ),
OSMO_VALUE_STRING(TRXCON_EV_FBSB_SEARCH_RES),
OSMO_VALUE_STRING(TRXCON_EV_SET_CCCH_MODE_REQ),
OSMO_VALUE_STRING(TRXCON_EV_SET_TCH_MODE_REQ),
OSMO_VALUE_STRING(TRXCON_EV_SET_PHY_CONFIG_REQ),
OSMO_VALUE_STRING(TRXCON_EV_TX_ACCESS_BURST_REQ),
OSMO_VALUE_STRING(TRXCON_EV_UPDATE_SACCH_CACHE_REQ),
OSMO_VALUE_STRING(TRXCON_EV_DEDICATED_ESTABLISH_REQ),
OSMO_VALUE_STRING(TRXCON_EV_DEDICATED_RELEASE_REQ),
OSMO_VALUE_STRING(TRXCON_EV_TX_TRAFFIC_REQ),
OSMO_VALUE_STRING(TRXCON_EV_RX_TRAFFIC_IND),
OSMO_VALUE_STRING(TRXCON_EV_TX_DATA_REQ),
OSMO_VALUE_STRING(TRXCON_EV_RX_DATA_IND),
OSMO_VALUE_STRING(TRXCON_EV_CRYPTO_REQ),
{ 0, NULL }
};
struct osmo_fsm trxcon_fsm_def = {
.name = "trxcon",
.states = trxcon_fsm_states,
.num_states = ARRAY_SIZE(trxcon_fsm_states),
.log_subsys = DAPP,
.event_names = trxcon_fsm_event_names,
.allstate_event_mask = S(TRXCON_EV_PHYIF_FAILURE)
| S(TRXCON_EV_L2IF_FAILURE)
| S(TRXCON_EV_RESET_FULL_REQ)
| S(TRXCON_EV_RESET_SCHED_REQ)
| S(TRXCON_EV_SET_PHY_CONFIG_REQ)
| S(TRXCON_EV_UPDATE_SACCH_CACHE_REQ),
.allstate_action = &trxcon_allstate_action,
.timer_cb = &trxcon_timer_cb,
.pre_term = &trxcon_fsm_pre_term_cb,
};
static __attribute__((constructor)) void on_dso_load(void)
{
OSMO_ASSERT(osmo_fsm_register(&trxcon_fsm_def) == 0);
}

View File

@ -1,816 +0,0 @@
/*
* 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);
}

View File

@ -1,408 +0,0 @@
/*
* 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.
*
*/
#define _GNU_SOURCE
#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 <pthread.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;
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(3, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
int prio = sched_get_priority_max(SCHED_RR) - 5;
struct sched_param param;
param.sched_priority = prio;
int rv = sched_setscheduler(0, SCHED_RR, &param);
if (rv < 0) {
LOGP(DAPP, LOGL_ERROR, "Failed to set sched!\n");
exit(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;
}

View File

@ -1,21 +0,0 @@
#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,
};