diff options
Diffstat (limited to 'src/host/trxcon')
63 files changed, 9677 insertions, 7861 deletions
diff --git a/src/host/trxcon/.gitignore b/src/host/trxcon/.gitignore index fe90e43c..b5c3a99d 100644 --- a/src/host/trxcon/.gitignore +++ b/src/host/trxcon/.gitignore @@ -9,6 +9,11 @@ install-sh missing compile +# libtool by-products +ltmain.sh +libtool +m4/*.m4 + # configure by-products .deps/ Makefile @@ -18,9 +23,11 @@ version.h # build by-products *.o +*.lo *.a +*.la -trxcon +/src/trxcon # various .version diff --git a/src/host/trxcon/Makefile.am b/src/host/trxcon/Makefile.am index b51db02f..5b1002c8 100644 --- a/src/host/trxcon/Makefile.am +++ b/src/host/trxcon/Makefile.am @@ -1,52 +1,21 @@ AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 -# 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) \ +SUBDIRS = \ + include \ + src \ $(NULL) -bin_PROGRAMS = trxcon +ACLOCAL_AMFLAGS = -I m4 -trxcon_SOURCES = \ - l1ctl_link.c \ - l1ctl.c \ - trx_if.c \ - logging.c \ - trxcon.c \ +BUILT_SOURCES = \ + $(top_srcdir)/.version \ $(NULL) - -# Scheduler -trxcon_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 \ +EXTRA_DIST = \ + .version \ $(NULL) -trxcon_LDADD = \ - $(LIBOSMOCORE_LIBS) \ - $(LIBOSMOCODING_LIBS) \ - $(LIBOSMOGSM_LIBS) \ - $(NULL) +# versioning magic +$(top_srcdir)/.version: + echo $(VERSION) > $@-t && mv $@-t $@ +dist-hook: + echo $(VERSION) > $(distdir)/.tarball-version diff --git a/src/host/trxcon/configure.ac b/src/host/trxcon/configure.ac index 1f24260d..6508689b 100644 --- a/src/host/trxcon/configure.ac +++ b/src/host/trxcon/configure.ac @@ -2,6 +2,8 @@ dnl Process this file with autoconf to produce a configure script AC_INIT([trxcon], [0.0.0]) AM_INIT_AUTOMAKE +CFLAGS="$CFLAGS -std=gnu11" + dnl kernel style compile messages m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) @@ -18,6 +20,9 @@ PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm) dnl checks for header files AC_HEADER_STDC +dnl init libtool +LT_INIT + AC_ARG_ENABLE(sanitize, [AS_HELP_STRING( [--enable-sanitize], @@ -29,7 +34,36 @@ then CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined" fi +AC_ARG_ENABLE(werror, + [AS_HELP_STRING( + [--enable-werror], + [Turn all compiler warnings into errors, with exceptions: + a) deprecation (allow upstream to mark deprecation without breaking builds); + b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds) + ] + )], + [werror=$enableval], [werror="no"]) +if test x"$werror" = x"yes" +then + WERROR_FLAGS="-Werror" + WERROR_FLAGS+=" -Werror=implicit-int -Werror=int-conversion -Werror=old-style-definition" + WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations" + WERROR_FLAGS+=" -Wno-error=cpp" # "#warning" + CFLAGS="$CFLAGS $WERROR_FLAGS" + CPPFLAGS="$CPPFLAGS $WERROR_FLAGS" +fi + +AC_MSG_RESULT([CFLAGS="$CFLAGS"]) +AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"]) + dnl Checks for typedefs, structures and compiler characteristics -AC_OUTPUT( - Makefile) +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 diff --git a/src/host/trxcon/include/Makefile.am b/src/host/trxcon/include/Makefile.am new file mode 100644 index 00000000..9d963a02 --- /dev/null +++ b/src/host/trxcon/include/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + osmocom \ + $(NULL) diff --git a/src/host/trxcon/include/osmocom/Makefile.am b/src/host/trxcon/include/osmocom/Makefile.am new file mode 100644 index 00000000..83c6385c --- /dev/null +++ b/src/host/trxcon/include/osmocom/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + bb \ + $(NULL) diff --git a/src/host/trxcon/include/osmocom/bb/Makefile.am b/src/host/trxcon/include/osmocom/bb/Makefile.am new file mode 100644 index 00000000..4a575ffb --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/Makefile.am @@ -0,0 +1,9 @@ +SUBDIRS = \ + l1sched \ + trxcon \ + $(NULL) + +noinst_HEADERS = \ + l1ctl_proto.h \ + l1gprs.h \ + $(NULL) diff --git a/src/host/trxcon/include/osmocom/bb/l1ctl_proto.h b/src/host/trxcon/include/osmocom/bb/l1ctl_proto.h new file mode 120000 index 00000000..ee19b80e --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/l1ctl_proto.h @@ -0,0 +1 @@ +../../../../../../include/l1ctl_proto.h
\ No newline at end of file diff --git a/src/host/trxcon/include/osmocom/bb/l1gprs.h b/src/host/trxcon/include/osmocom/bb/l1gprs.h new file mode 120000 index 00000000..3bf85176 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/l1gprs.h @@ -0,0 +1 @@ +../../../../../../include/l1gprs.h
\ No newline at end of file diff --git a/src/host/trxcon/include/osmocom/bb/l1sched/Makefile.am b/src/host/trxcon/include/osmocom/bb/l1sched/Makefile.am new file mode 100644 index 00000000..39c32ba0 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/l1sched/Makefile.am @@ -0,0 +1,5 @@ +noinst_HEADERS = \ + l1sched.h \ + logging.h \ + prim.h \ + $(NULL) diff --git a/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h b/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h new file mode 100644 index 00000000..6c5a31e8 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h @@ -0,0 +1,417 @@ +#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> + +#include <osmocom/bb/l1sched/prim.h> + +#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) + +#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_burst_type { + L1SCHED_BURST_GMSK, + L1SCHED_BURST_8PSK, +}; + +/** + * 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 +}; + +/* 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[GSM_NBITS_NB_8PSK_BURST]; + size_t burst_len; +}; + +/* Represents a received burst */ +struct l1sched_burst_ind { + uint32_t fn; + uint8_t tn; + + /*! ToA256 (Timing of Arrival, 1/256 of a symbol) */ + int16_t toa256; + /*! RSSI (Received Signal Strength Indication) */ + int8_t rssi; + + /* Internally used by the scheduler */ + uint8_t bid; + + sbit_t burst[GSM_NBITS_NB_8PSK_BURST]; + size_t burst_len; +}; + +/* Probed lchan is active */ +#define L1SCHED_PROBE_F_ACTIVE (1 << 0) + +/* RTR (Ready-to-Receive) probe */ +struct l1sched_probe { + uint32_t flags; /* see L1SCHED_PROBE_F_* above */ + uint32_t fn; + uint8_t tn; +}; + +typedef int l1sched_lchan_rx_func(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi); + +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; + + /*! 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 24 unique measurements) */ +struct l1sched_lchan_meas_hist { + struct l1sched_meas_set buf[24]; + 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 */ + uint32_t rx_burst_mask; + /*! Mask of transmitted bursts */ + uint32_t tx_burst_mask; + /*! Burst buffer for RX */ + sbit_t *rx_bursts; + /*! Burst buffer for TX */ + ubit_t *tx_bursts; + + /*! Queue of Tx primitives */ + struct llist_head tx_prims; + /*! Tx primitive being sent */ + struct msgb *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; + /*! Backpointer to the scheduler */ + struct l1sched_state *sched; +}; + +/*! Scheduler configuration */ +struct l1sched_cfg { + /*! Logging context (used as prefix for messages) */ + const char *log_prefix; +}; + +/*! One scheduler instance */ +struct l1sched_state { + /*! 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, uint8_t tn); + +/* Scheduler management functions */ +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); + +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_lchan_set_amr_cfg(struct l1sched_lchan_state *lchan, + uint8_t codecs_bitmask, uint8_t start_codec); +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_by_type(struct l1sched_ts *ts, + enum l1sched_lchan_type type); +struct l1sched_lchan_state *l1sched_find_lchan_by_chan_nr(struct l1sched_state *sched, + uint8_t chan_nr, uint8_t link_id); + +#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) + +int l1sched_handle_rx_burst(struct l1sched_state *sched, + struct l1sched_burst_ind *bi); +int l1sched_handle_rx_probe(struct l1sched_state *sched, + struct l1sched_probe *probe); +int l1sched_handle_burst_req(struct l1sched_state *sched, + const struct l1sched_burst_req *br); + +/* Shared declarations for lchan handlers */ +extern const uint8_t l1sched_nb_training_bits[8][26]; + +const char *l1sched_burst_mask2str(const uint32_t *mask, int bits); + +/* 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_burst_ind *bi); +void l1sched_lchan_meas_avg(struct l1sched_lchan_state *lchan, unsigned int n); + +/* Clock and Downlink scheduling trigger */ +int l1sched_clck_handle(struct l1sched_state *sched, uint32_t fn); +void l1sched_clck_reset(struct l1sched_state *sched); + +void l1sched_pull_burst(struct l1sched_state *sched, struct l1sched_burst_req *br); +void l1sched_pull_send_frame(struct l1sched_state *sched); diff --git a/src/host/trxcon/include/osmocom/bb/l1sched/logging.h b/src/host/trxcon/include/osmocom/bb/l1sched/logging.h new file mode 100644 index 00000000..21be9955 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/l1sched/logging.h @@ -0,0 +1,37 @@ +#pragma once + +extern int l1sched_log_cat_common; +extern int l1sched_log_cat_data; + +void l1sched_logging_init(int log_cat_common, int 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, data, 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) diff --git a/src/host/trxcon/include/osmocom/bb/l1sched/prim.h b/src/host/trxcon/include/osmocom/bb/l1sched/prim.h new file mode 100644 index 00000000..a9187c2a --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/l1sched/prim.h @@ -0,0 +1,132 @@ +#pragma once + +#include <stdint.h> +#include <stdbool.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/prim.h> +#include <osmocom/core/utils.h> + +#define l1sched_prim_from_msgb(msg) \ + ((struct l1sched_prim *)(msg)->l1h) + +#define l1sched_prim_data_from_msgb(msg) \ + ((uint8_t *)msgb_l2(msg)) + +#define l1sched_prim_type_from_msgb(msg) \ + l1sched_prim_from_msgb(msg)->oph.primitive + +#define L1SCHED_PRIM_STR_FMT "%s.%s" +#define L1SCHED_PRIM_STR_ARGS(prim) \ + l1sched_prim_type_name((prim)->oph.primitive), \ + osmo_prim_operation_name((prim)->oph.operation) + +enum l1sched_prim_type { + L1SCHED_PRIM_T_DATA, /* Req | Ind | Cnf */ + L1SCHED_PRIM_T_RACH, /* Req | Cnf */ + L1SCHED_PRIM_T_SCH, /* Ind */ + L1SCHED_PRIM_T_PCHAN_COMB, /* Ind */ +}; + +extern const struct value_string l1sched_prim_type_names[]; +static inline const char *l1sched_prim_type_name(enum l1sched_prim_type val) +{ + return get_value_string(l1sched_prim_type_names, val); +} + +/*! Common header for L1SCHED_PRIM_T_{DATA,RACH} */ +struct l1sched_prim_chdr { + /*! TDMA Frame Number */ + uint32_t frame_nr; + /*! RSL Channel Number */ + uint8_t chan_nr; + /*! RSL Link Identifier */ + uint8_t link_id; + /*! Traffic or signalling */ + bool traffic; +}; + +/*! Payload of L1SCHED_PRIM_T_DATA | Ind */ +struct l1sched_prim_data_ind { + /*! Common sub-header */ + struct l1sched_prim_chdr chdr; + int16_t toa256; + int8_t rssi; + int n_errors; + int n_bits_total; +}; + +/*! Payload of L1SCHED_PRIM_T_RACH | {Req,Cnf} */ +struct l1sched_prim_rach { + /*! Common sub-header */ + struct l1sched_prim_chdr chdr; + /*! Training Sequence (only for 11-bit RA) */ + uint8_t synch_seq; + /*! Transmission offset (how many frames to skip) */ + uint8_t offset; + /*! RA value is 11 bit */ + bool is_11bit; + /*! RA value */ + uint16_t ra; +}; + +struct l1sched_prim { + /*! Primitive header */ + struct osmo_prim_hdr oph; + /*! Type specific header */ + union { + /*! L1SCHED_PRIM_T_DATA | Req */ + struct l1sched_prim_chdr data_req; + /*! L1SCHED_PRIM_T_DATA | Cnf */ + struct l1sched_prim_chdr data_cnf; + /*! L1SCHED_PRIM_T_DATA | Ind */ + struct l1sched_prim_data_ind data_ind; + + /*! L1SCHED_PRIM_T_RACH | Req */ + struct l1sched_prim_rach rach_req; + /*! L1SCHED_PRIM_T_RACH | Cnf */ + struct l1sched_prim_rach rach_cnf; + + /*! L1SCHED_PRIM_T_SCH | Ind */ + struct { + /*! TDMA frame number */ + uint32_t frame_nr; + /*! BSIC */ + uint8_t bsic; + } sch_ind; + + /*! L1SCHED_PRIM_T_PCHAN_COMB | Ind */ + struct { + /*! Timeslot number */ + uint8_t tn; + /*! Channel combination for a timeslot */ + enum gsm_phys_chan_config pchan; + } pchan_comb_ind; + }; +}; + + +struct l1sched_state; +struct l1sched_lchan_state; + +void l1sched_prim_init(struct msgb *msg, + enum l1sched_prim_type type, + enum osmo_prim_operation op); + +struct msgb *l1sched_prim_alloc(enum l1sched_prim_type type, + enum osmo_prim_operation op); + +bool l1sched_lchan_amr_prim_is_valid(struct l1sched_lchan_state *lchan, + struct msgb *msg, bool is_cmr); +struct msgb *l1sched_lchan_prim_dequeue_sacch(struct l1sched_lchan_state *lchan); +struct msgb *l1sched_lchan_prim_dequeue_tch(struct l1sched_lchan_state *lchan, bool facch); +struct msgb *l1sched_lchan_prim_dummy_lapdm(const struct l1sched_lchan_state *lchan); + +int l1sched_lchan_emit_data_ind(struct l1sched_lchan_state *lchan, + const uint8_t *data, size_t data_len, + int n_errors, int n_bits_total, bool traffic); +int l1sched_lchan_emit_data_cnf(struct l1sched_lchan_state *lchan, + struct msgb *msg, uint32_t fn); + +int l1sched_prim_from_user(struct l1sched_state *sched, struct msgb *msg); +int l1sched_prim_to_user(struct l1sched_state *sched, struct msgb *msg); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/Makefile.am b/src/host/trxcon/include/osmocom/bb/trxcon/Makefile.am new file mode 100644 index 00000000..ad1e89a0 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/Makefile.am @@ -0,0 +1,9 @@ +noinst_HEADERS = \ + l1ctl_server.h \ + l1ctl.h \ + phyif.h \ + trx_if.h \ + logging.h \ + trxcon.h \ + trxcon_fsm.h \ + $(NULL) diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl.h b/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl.h new file mode 100644 index 00000000..7e2fa6a5 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl.h @@ -0,0 +1,22 @@ +#pragma once + +#include <stdint.h> + +struct msgb; +struct trxcon_param_rx_data_ind; +struct trxcon_param_tx_data_cnf; +struct trxcon_param_tx_access_burst_cnf; + +int l1ctl_tx_fbsb_conf(struct trxcon_inst *trxcon, uint16_t band_arfcn, uint8_t bsic); +int l1ctl_tx_fbsb_fail(struct trxcon_inst *trxcon, uint16_t band_arfcn); +int l1ctl_tx_ccch_mode_conf(struct trxcon_inst *trxcon, uint8_t mode); +int l1ctl_tx_pm_conf(struct trxcon_inst *trxcon, uint16_t band_arfcn, int dbm, int last); +int l1ctl_tx_reset_conf(struct trxcon_inst *trxcon, uint8_t type); +int l1ctl_tx_reset_ind(struct trxcon_inst *trxcon, uint8_t type); + +int l1ctl_tx_dt_ind(struct trxcon_inst *trxcon, + const struct trxcon_param_rx_data_ind *ind); +int l1ctl_tx_dt_conf(struct trxcon_inst *trxcon, + const struct trxcon_param_tx_data_cnf *cnf); +int l1ctl_tx_rach_conf(struct trxcon_inst *trxcon, + const struct trxcon_param_tx_access_burst_cnf *cnf); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl_server.h b/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl_server.h new file mode 100644 index 00000000..83c61f02 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl_server.h @@ -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 512 +#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); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/logging.h b/src/host/trxcon/include/osmocom/bb/trxcon/logging.h new file mode 100644 index 00000000..ce149926 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/logging.h @@ -0,0 +1,16 @@ +#pragma once + +#include <osmocom/core/logging.h> + +enum { + DAPP, + DL1C, + DL1D, + DTRXC, + DTRXD, + DSCH, + DSCHD, + DGPRS, +}; + +int trxcon_logging_init(void *tall_ctx, const char *category_mask); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/phyif.h b/src/host/trxcon/include/osmocom/bb/trxcon/phyif.h new file mode 100644 index 00000000..2ad7a678 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/phyif.h @@ -0,0 +1,120 @@ +#pragma once + +#include <stdint.h> + +#include <osmocom/core/bits.h> + +/* PHYIF command type */ +enum trxcon_phyif_cmd_type { + TRXCON_PHYIF_CMDT_RESET, + TRXCON_PHYIF_CMDT_POWERON, + TRXCON_PHYIF_CMDT_POWEROFF, + TRXCON_PHYIF_CMDT_MEASURE, + TRXCON_PHYIF_CMDT_SETFREQ_H0, + TRXCON_PHYIF_CMDT_SETFREQ_H1, + TRXCON_PHYIF_CMDT_SETSLOT, + TRXCON_PHYIF_CMDT_SETTA, +}; + +/* param of TRXCON_PHYIF_CMDT_SETFREQ_H0 */ +struct trxcon_phyif_cmdp_setfreq_h0 { + uint16_t band_arfcn; +}; + +/* param of TRXCON_PHYIF_CMDT_SETFREQ_H1 */ +struct trxcon_phyif_cmdp_setfreq_h1 { + uint8_t hsn; + uint8_t maio; + const uint16_t *ma; + unsigned int ma_len; +}; + +/* param of TRXCON_PHYIF_CMDT_SETSLOT */ +struct trxcon_phyif_cmdp_setslot { + uint8_t tn; + uint8_t pchan; /* enum gsm_phys_chan_config */ +}; + +/* param of TRXCON_PHYIF_CMDT_SETTA */ +struct trxcon_phyif_cmdp_setta { + int8_t ta; /* intentionally signed */ +}; + +/* param of TRXCON_PHYIF_CMDT_MEASURE (command) */ +struct trxcon_phyif_cmdp_measure { + uint16_t band_arfcn; +}; + +/* param of TRXCON_PHYIF_CMDT_MEASURE (response) */ +struct trxcon_phyif_rspp_measure { + uint16_t band_arfcn; + int dbm; +}; + +struct trxcon_phyif_cmd { + enum trxcon_phyif_cmd_type type; + union { + struct trxcon_phyif_cmdp_setfreq_h0 setfreq_h0; + struct trxcon_phyif_cmdp_setfreq_h1 setfreq_h1; + struct trxcon_phyif_cmdp_setslot setslot; + struct trxcon_phyif_cmdp_setta setta; + struct trxcon_phyif_cmdp_measure measure; + } param; +}; + +struct trxcon_phyif_rsp { + enum trxcon_phyif_cmd_type type; + union { + struct trxcon_phyif_rspp_measure measure; + } param; +}; + +/* RTS.ind - Ready-to-Send indication */ +struct trxcon_phyif_rts_ind { + uint32_t fn; + uint8_t tn; +}; + +/* RTR.ind - Ready-to-Receive indicaton */ +struct trxcon_phyif_rtr_ind { + uint32_t fn; + uint8_t tn; +}; + +/* The probed lchan is active */ +#define TRXCON_PHYIF_RTR_F_ACTIVE (1 << 0) + +/* RTR.rsp - Ready-to-Receive response */ +struct trxcon_phyif_rtr_rsp { + uint32_t flags; /* see TRXCON_PHYIF_RTR_F_* above */ +}; + +/* BURST.req - a burst to be transmitted */ +struct trxcon_phyif_burst_req { + uint32_t fn; + uint8_t tn; + uint8_t pwr; + const ubit_t *burst; + unsigned int burst_len; +}; + +/* BURST.ind - a received burst */ +struct trxcon_phyif_burst_ind { + uint32_t fn; + uint8_t tn; + int16_t toa256; + int8_t rssi; + const sbit_t *burst; + unsigned int burst_len; +}; + +int trxcon_phyif_handle_burst_req(void *phyif, const struct trxcon_phyif_burst_req *br); +int trxcon_phyif_handle_burst_ind(void *priv, const struct trxcon_phyif_burst_ind *bi); + +int trxcon_phyif_handle_rts_ind(void *priv, const struct trxcon_phyif_rts_ind *rts); +int trxcon_phyif_handle_rtr_ind(void *priv, const struct trxcon_phyif_rtr_ind *ind, + struct trxcon_phyif_rtr_rsp *rsp); + +int trxcon_phyif_handle_cmd(void *phyif, const struct trxcon_phyif_cmd *cmd); +int trxcon_phyif_handle_rsp(void *priv, const struct trxcon_phyif_rsp *rsp); +void trxcon_phyif_close(void *phyif); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/trx_if.h b/src/host/trxcon/include/osmocom/bb/trxcon/trx_if.h new file mode 100644 index 00000000..e564fd8e --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/trx_if.h @@ -0,0 +1,61 @@ +#pragma once + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/fsm.h> + +#include <osmocom/bb/trxcon/phyif.h> + +#define TRXC_BUF_SIZE 1024 +#define TRXD_BUF_SIZE 512 + +enum trx_fsm_states { + TRX_STATE_OFFLINE = 0, + TRX_STATE_IDLE, + TRX_STATE_ACTIVE, + TRX_STATE_RSP_WAIT, +}; + +struct trx_instance { + struct osmo_fd trx_ofd_ctrl; + struct osmo_fd trx_ofd_data; + + struct osmo_timer_list trx_ctrl_timer; + struct llist_head trx_ctrl_list; + struct osmo_fsm_inst *fi; + uint32_t fn_advance; + + /* HACK: we need proper state machines */ + uint32_t prev_state; + bool powered_up; + + /* Some private data */ + void *priv; +}; + +struct trx_ctrl_msg { + struct llist_head list; + char cmd[TRXC_BUF_SIZE]; + int retry_cnt; + int critical; + int cmd_len; +}; + +struct trx_if_params { + const char *local_host; + const char *remote_host; + uint16_t base_port; + uint32_t fn_advance; + uint8_t instance; + + struct osmo_fsm_inst *parent_fi; + uint32_t parent_term_event; + void *priv; +}; + +struct trx_instance *trx_if_open(const struct trx_if_params *params); +void trx_if_close(struct trx_instance *trx); + +int trx_if_handle_phyif_burst_req(struct trx_instance *trx, const struct trxcon_phyif_burst_req *br); +int trx_if_handle_phyif_cmd(struct trx_instance *trx, const struct trxcon_phyif_cmd *cmd); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h new file mode 100644 index 00000000..ff54e785 --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h @@ -0,0 +1,64 @@ +#pragma once + +#include <stdint.h> + +struct osmo_fsm_inst; +struct l1sched_state; +struct l1gprs_state; +struct msgb; + +struct trxcon_inst { + struct osmo_fsm_inst *fi; + unsigned int id; + + /* Logging context for sched and l1c */ + const char *log_prefix; + + /* GSMTAP instance (optional) */ + struct gsmtap_inst *gsmtap; + + /* The L1 scheduler */ + struct l1sched_state *sched; + /* GPRS state (MAC layer) */ + struct l1gprs_state *gprs; + + /* PHY interface (e.g. TRXC/TRXD) */ + void *phyif; + /* L2 interface (e.g. L1CTL) */ + void *l2if; + + /* State specific data of trxcon_fsm */ + void *fi_data; + + /* L1 parameters */ + struct { + uint16_t band_arfcn; + uint8_t tx_power; + uint8_t tsc; /* only valid for DCCH/PDCH */ + int8_t ta; + } l1p; + + /* PHY specific quirks */ + struct { + /* FBSB timeout extension (in TDMA FNs) */ + unsigned int fbsb_extend_fns; + } phy_quirks; +}; + +enum trxcon_log_cat { + TRXCON_LOGC_FSM, /* trxcon_fsm */ + TRXCON_LOGC_L1C, /* L1CTL control */ + TRXCON_LOGC_L1D, /* L1CTL data */ + TRXCON_LOGC_SCHC, /* l1sched control */ + TRXCON_LOGC_SCHD, /* l1sched data */ + TRXCON_LOGC_GPRS, /* l1gprs logging */ +}; + +void trxcon_set_log_cfg(const int *logc, unsigned int logc_num); + +struct trxcon_inst *trxcon_inst_alloc(void *ctx, unsigned int id); +void trxcon_inst_free(struct trxcon_inst *trxcon); + +int trxcon_l1ctl_receive(struct trxcon_inst *trxcon, struct msgb *msg); +int trxcon_l1ctl_send(struct trxcon_inst *trxcon, struct msgb *msg); +void trxcon_l1ctl_close(struct trxcon_inst *trxcon); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/trxcon_fsm.h b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon_fsm.h new file mode 100644 index 00000000..9eba4fde --- /dev/null +++ b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon_fsm.h @@ -0,0 +1,169 @@ +#pragma once + +#include <stdint.h> +#include <stdbool.h> + +#include <osmocom/core/fsm.h> + +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_TX_ACCESS_BURST_CNF, + TRXCON_EV_UPDATE_SACCH_CACHE_REQ, + TRXCON_EV_DCH_EST_REQ, + TRXCON_EV_DCH_REL_REQ, + TRXCON_EV_TX_DATA_REQ, + TRXCON_EV_TX_DATA_CNF, + TRXCON_EV_RX_DATA_IND, + TRXCON_EV_CRYPTO_REQ, + TRXCON_EV_GPRS_UL_TBF_CFG_REQ, /* param: L1CTL msgb */ + TRXCON_EV_GPRS_DL_TBF_CFG_REQ, /* param: L1CTL msgb */ + TRXCON_EV_GPRS_UL_BLOCK_REQ, /* param: L1CTL msgb */ +}; + +/* 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 { + 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_fns; /* in TDMA Fn periods */ + 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_DATA_REQ */ +struct trxcon_param_tx_data_req { + bool traffic; + uint8_t chan_nr; + uint8_t link_id; + size_t data_len; + const uint8_t *data; +}; + +/* param of TRXCON_EV_TX_DATA_CNF */ +struct trxcon_param_tx_data_cnf { + bool traffic; + uint8_t chan_nr; + uint8_t link_id; + uint16_t band_arfcn; + uint32_t frame_nr; + size_t data_len; + const uint8_t *data; +}; + +/* param of TRXCON_EV_RX_DATA_IND */ +struct trxcon_param_rx_data_ind { + bool traffic; + uint8_t chan_nr; + uint8_t link_id; + uint16_t band_arfcn; + 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_TX_ACCESS_BURST_CNF */ +struct trxcon_param_tx_access_burst_cnf { + uint16_t band_arfcn; + uint32_t frame_nr; +}; + +/* param of TRXCON_EV_DCH_EST_REQ */ +struct trxcon_param_dch_est_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; +}; diff --git a/src/host/trxcon/l1ctl.c b/src/host/trxcon/l1ctl.c deleted file mode 100644 index e722624c..00000000 --- a/src/host/trxcon/l1ctl.c +++ /dev/null @@ -1,905 +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); - - /* 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 * FRAME_DURATION_uS / 1000); - osmo_timer_schedule(&l1l->fbsb_timer, 0, - timeout * FRAME_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) -{ - int 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; - } - - /* Forward hopping parameters to TRX */ - rc = trx_if_cmd_setfh(trx, h->hsn, h->maio, h->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? */ - - msgb_free(msg); - return 0; -} - -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); -} diff --git a/src/host/trxcon/l1ctl.h b/src/host/trxcon/l1ctl.h deleted file mode 100644 index 48bbe097..00000000 --- a/src/host/trxcon/l1ctl.h +++ /dev/null @@ -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); diff --git a/src/host/trxcon/l1ctl_link.c b/src/host/trxcon/l1ctl_link.c deleted file mode 100644 index b7ea262a..00000000 --- a/src/host/trxcon/l1ctl_link.c +++ /dev/null @@ -1,319 +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; - conn_bfd->when = BSC_FD_READ; - conn_bfd->data = l1l; - conn_bfd->fd = cfd; - - 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) -{ - uint16_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 = (uint16_t *) msgb_push(msg, L1CTL_MSG_LEN_FIELD); - *len = htons(msg->len - L1CTL_MSG_LEN_FIELD); - - 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; - 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; - - /* Bind connection handler */ - bfd->cb = l1ctl_link_accept; - bfd->when = BSC_FD_READ; - bfd->data = l1l; - - /** - * 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); -} diff --git a/src/host/trxcon/l1ctl_link.h b/src/host/trxcon/l1ctl_link.h deleted file mode 100644 index a333e407..00000000 --- a/src/host/trxcon/l1ctl_link.h +++ /dev/null @@ -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); diff --git a/src/host/trxcon/l1ctl_proto.h b/src/host/trxcon/l1ctl_proto.h deleted file mode 120000 index 75862bae..00000000 --- a/src/host/trxcon/l1ctl_proto.h +++ /dev/null @@ -1 +0,0 @@ -../../../include/l1ctl_proto.h
\ No newline at end of file diff --git a/src/host/trxcon/logging.h b/src/host/trxcon/logging.h deleted file mode 100644 index 152c3467..00000000 --- a/src/host/trxcon/logging.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include <osmocom/core/logging.h> - -#define DEBUG_DEFAULT "DAPP:DL1C:DL1D:DTRX:DTRXD:DSCH:DSCHD" - -enum { - DAPP, - DL1C, - DL1D, - DTRX, - DTRXD, - DSCH, - DSCHD, -}; - -int trx_log_init(void *tall_ctx, const char *category_mask); diff --git a/src/host/trxcon/m4/.gitkeep b/src/host/trxcon/m4/.gitkeep new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/host/trxcon/m4/.gitkeep diff --git a/src/host/trxcon/sched_clck.c b/src/host/trxcon/sched_clck.c deleted file mode 100644 index 66477b24..00000000 --- a/src/host/trxcon/sched_clck.c +++ /dev/null @@ -1,206 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: clock synchronization - * - * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> - * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> - * (C) 2015 by Harald Welte <laforge@gnumonks.org> - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - */ - -#include <stdio.h> -#include <errno.h> -#include <stdlib.h> -#include <stdint.h> -#include <inttypes.h> -#include <string.h> - -#include <osmocom/core/talloc.h> -#include <osmocom/core/msgb.h> -#include <osmocom/core/bits.h> -#include <osmocom/core/fsm.h> -#include <osmocom/core/timer.h> -#include <osmocom/core/timer_compat.h> -#include <osmocom/gsm/a5.h> - -#include "scheduler.h" -#include "logging.h" -#include "trx_if.h" - -#define MAX_FN_SKEW 50 -#define TRX_LOSS_FRAMES 400 - -static void sched_clck_tick(void *data) -{ - struct trx_sched *sched = (struct trx_sched *) data; - struct timespec tv_now, *tv_clock, elapsed; - int64_t elapsed_us; - const struct timespec frame_duration = { .tv_sec = 0, .tv_nsec = FRAME_DURATION_uS * 1000 }; - - /* Check if transceiver is still alive */ - if (sched->fn_counter_lost++ == TRX_LOSS_FRAMES) { - LOGP(DSCH, LOGL_DEBUG, "No more clock from transceiver\n"); - sched->state = SCH_CLCK_STATE_WAIT; - - return; - } - - /* Get actual / previous frame time */ - osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now); - tv_clock = &sched->clock; - - timespecsub(&tv_now, tv_clock, &elapsed); - elapsed_us = (elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000); - - /* If someone played with clock, or if the process stalled */ - if (elapsed_us > FRAME_DURATION_uS * MAX_FN_SKEW || elapsed_us < 0) { - LOGP(DSCH, LOGL_NOTICE, "PC clock skew: " - "elapsed uS %" PRId64 "\n", elapsed_us); - - sched->state = SCH_CLCK_STATE_WAIT; - - return; - } - - /* Schedule next FN clock */ - while (elapsed_us > FRAME_DURATION_uS / 2) { - timespecadd(tv_clock, &frame_duration, tv_clock); - elapsed_us -= FRAME_DURATION_uS; - - sched->fn_counter_proc = TDMA_FN_INC(sched->fn_counter_proc); - - /* Call frame callback */ - if (sched->clock_cb) - sched->clock_cb(sched); - } - - osmo_timer_schedule(&sched->clock_timer, 0, - FRAME_DURATION_uS - elapsed_us); -} - -static void sched_clck_correct(struct trx_sched *sched, - struct timespec *tv_now, uint32_t fn) -{ - sched->fn_counter_proc = fn; - - /* Call frame callback */ - if (sched->clock_cb) - sched->clock_cb(sched); - - /* Schedule first FN clock */ - sched->clock = *tv_now; - memset(&sched->clock_timer, 0, sizeof(sched->clock_timer)); - - sched->clock_timer.cb = sched_clck_tick; - sched->clock_timer.data = sched; - osmo_timer_schedule(&sched->clock_timer, 0, FRAME_DURATION_uS); -} - -int sched_clck_handle(struct trx_sched *sched, uint32_t fn) -{ - struct timespec tv_now, *tv_clock, elapsed; - int64_t elapsed_us, elapsed_fn; - - /* Reset lost counter */ - sched->fn_counter_lost = 0; - - /* Get actual / previous frame time */ - osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now); - tv_clock = &sched->clock; - - /* If this is the first CLCK IND */ - if (sched->state == SCH_CLCK_STATE_WAIT) { - sched_clck_correct(sched, &tv_now, fn); - - LOGP(DSCH, LOGL_DEBUG, "Initial clock received: fn=%u\n", fn); - sched->state = SCH_CLCK_STATE_OK; - - return 0; - } - - LOGP(DSCH, LOGL_NOTICE, "Clock indication: fn=%u\n", fn); - - osmo_timer_del(&sched->clock_timer); - - /* Calculate elapsed time / frames since last processed fn */ - timespecsub(&tv_now, tv_clock, &elapsed); - elapsed_us = (elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000); - elapsed_fn = TDMA_FN_SUB(fn, sched->fn_counter_proc); - - if (elapsed_fn >= 135774) - elapsed_fn -= GSM_HYPERFRAME; - - /* Check for max clock skew */ - if (elapsed_fn > MAX_FN_SKEW || elapsed_fn < -MAX_FN_SKEW) { - LOGP(DSCH, LOGL_NOTICE, "GSM clock skew: old fn=%u, " - "new fn=%u\n", sched->fn_counter_proc, fn); - - sched_clck_correct(sched, &tv_now, fn); - return 0; - } - - LOGP(DSCH, LOGL_INFO, "GSM clock jitter: %" PRId64 "\n", - elapsed_fn * FRAME_DURATION_uS - elapsed_us); - - /* Too many frames have been processed already */ - if (elapsed_fn < 0) { - struct timespec duration; - /** - * Set clock to the time or last FN should - * have been transmitted - */ - duration.tv_nsec = (0 - elapsed_fn) * FRAME_DURATION_uS * 1000; - duration.tv_sec = duration.tv_nsec / 1000000000; - duration.tv_nsec = duration.tv_nsec % 1000000000; - timespecadd(&tv_now, &duration, tv_clock); - - /* Set time to the time our next FN has to be transmitted */ - osmo_timer_schedule(&sched->clock_timer, 0, - FRAME_DURATION_uS * (1 - elapsed_fn)); - - return 0; - } - - /* Transmit what we still need to transmit */ - while (fn != sched->fn_counter_proc) { - sched->fn_counter_proc = TDMA_FN_INC(sched->fn_counter_proc); - - /* Call frame callback */ - if (sched->clock_cb) - sched->clock_cb(sched); - } - - /* Schedule next FN to be transmitted */ - *tv_clock = tv_now; - osmo_timer_schedule(&sched->clock_timer, 0, FRAME_DURATION_uS); - - return 0; -} - -void sched_clck_reset(struct trx_sched *sched) -{ - /* Reset internal state */ - sched->state = SCH_CLCK_STATE_WAIT; - - /* Stop clock timer */ - osmo_timer_del(&sched->clock_timer); - - /* Flush counters */ - sched->fn_counter_proc = 0; - sched->fn_counter_lost = 0; -} diff --git a/src/host/trxcon/sched_lchan_common.c b/src/host/trxcon/sched_lchan_common.c deleted file mode 100644 index 615d81c9..00000000 --- a/src/host/trxcon/sched_lchan_common.c +++ /dev/null @@ -1,181 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: common routines for lchan handlers - * - * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com> - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#include <errno.h> -#include <string.h> -#include <talloc.h> -#include <stdint.h> -#include <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 "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" -#include "logging.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, - }, -}; - -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_lchan_desc *lchan_desc; - struct l1ctl_info_dl dl_hdr; - int dbm_avg; - - /* 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.frame_nr = htonl(lchan->rx_first_fn); - dl_hdr.num_biterr = bit_error_count; - - /* Convert average RSSI to RX level */ - if (lchan->meas.num) { - /* RX level: 0 .. 63 in typical GSM notation (dBm + 110) */ - dbm_avg = lchan->meas.rssi_sum / lchan->meas.num; - dl_hdr.rx_level = dbm2rxlev(dbm_avg); - } else { - /* No measurements, assuming the worst */ - dl_hdr.rx_level = 0; - } - - /* 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); - - 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); - - 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; - } -} diff --git a/src/host/trxcon/sched_lchan_pdtch.c b/src/host/trxcon/sched_lchan_pdtch.c deleted file mode 100644 index 733e5741..00000000 --- a/src/host/trxcon/sched_lchan_pdtch.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: handlers for DL / UL bursts on logical channels - * - * (C) 2018 by Vadim Yanitskiy <axilirator@gmail.com> - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#include <errno.h> -#include <string.h> -#include <stdint.h> - -#include <osmocom/core/logging.h> -#include <osmocom/core/bits.h> - -#include <osmocom/gsm/gsm_utils.h> -#include <osmocom/gsm/protocol/gsm_04_08.h> -#include <osmocom/coding/gsm0503_coding.h> - -#include "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" -#include "logging.h" -#include "trx_if.h" -#include "l1ctl.h" - -int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, - sbit_t *bits, int8_t rssi, int16_t toa256) -{ - 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; - uint32_t *first_fn; - size_t l2_len; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - first_fn = &lchan->rx_first_fn; - 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); - - /* Reset internal state */ - if (bid == 0) { - /* Clean up old measurements */ - memset(&lchan->meas, 0x00, sizeof(lchan->meas)); - - *first_fn = fn; - *mask = 0x0; - } - - /* Update mask */ - *mask |= (1 << bid); - - /* Update measurements */ - lchan->meas.toa256_sum += toa256; - lchan->meas.rssi_sum += rssi; - lchan->meas.num++; - - /* 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; - - /* Check for complete set of bursts */ - if ((*mask & 0xf) != 0xf) { - LOGP(DSCHD, LOGL_ERROR, "Received incomplete data frame at " - "fn=%u (%u/%u) for %s\n", *first_fn, - (*first_fn) % ts->mf_layout->period, - ts->mf_layout->period, - lchan_desc->name); - - return -1; - } - - /* Attempt to decode */ - rc = gsm0503_pdtch_decode(l2, buffer, - NULL, &n_errors, &n_bits_total); - if (rc < 0) { - LOGP(DSCHD, LOGL_ERROR, "Received bad packet data frame " - "at fn=%u (%u/%u) for %s\n", *first_fn, - (*first_fn) % ts->mf_layout->period, - ts->mf_layout->period, - lchan_desc->name); - } - - /* Determine L2 length */ - l2_len = rc > 0 ? rc : 0; - - /* Send a L2 frame to the higher layers */ - sched_send_dt_ind(trx, ts, lchan, - l2, l2_len, n_errors, rc < 0, true); - - return 0; -} - - -int tx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid) -{ - const struct trx_lchan_desc *lchan_desc; - ubit_t burst[GSM_BURST_LEN]; - ubit_t *buffer, *offset; - const uint8_t *tsc; - uint8_t *mask; - int rc; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - mask = &lchan->tx_burst_mask; - buffer = lchan->tx_bursts; - - if (bid > 0) { - /* If we have encoded bursts */ - if (*mask) - goto send_burst; - else - return 0; - } - - /* Encode payload */ - rc = gsm0503_pdtch_encode(buffer, lchan->prim->payload, - lchan->prim->payload_len); - if (rc < 0) { - LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n", - lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, - lchan->prim->payload_len)); - - /* Forget this primitive */ - sched_prim_drop(lchan); - - return -EINVAL; - } - -send_burst: - /* Determine which burst should be sent */ - offset = buffer + bid * 116; - - /* Update mask */ - *mask |= (1 << bid); - - /* Choose proper TSC */ - tsc = sched_nb_training_bits[trx->tsc]; - - /* Compose a new burst */ - memset(burst, 0, 3); /* TB */ - memcpy(burst + 3, offset, 58); /* Payload 1/2 */ - memcpy(burst + 61, tsc, 26); /* TSC */ - memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */ - memset(burst + 145, 0, 3); /* TB */ - - LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n", - lchan_desc->name, fn, ts->index, bid); - - /* Forward burst to scheduler */ - rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst); - if (rc) { - /* Forget this primitive */ - sched_prim_drop(lchan); - - /* Reset mask */ - *mask = 0x00; - - return rc; - } - - /* If we have sent the last (4/4) burst */ - if ((*mask & 0x0f) == 0x0f) { - /* Confirm data / traffic sending */ - sched_send_dt_conf(trx, ts, lchan, fn, true); - - /* Forget processed primitive */ - sched_prim_drop(lchan); - - /* Reset mask */ - *mask = 0x00; - } - - return 0; -} diff --git a/src/host/trxcon/sched_lchan_rach.c b/src/host/trxcon/sched_lchan_rach.c deleted file mode 100644 index 5d1f3ab9..00000000 --- a/src/host/trxcon/sched_lchan_rach.c +++ /dev/null @@ -1,181 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: handlers for DL / UL bursts on logical channels - * - * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com> - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#include <errno.h> -#include <string.h> -#include <stdint.h> -#include <stdbool.h> - -#include <osmocom/core/logging.h> -#include <osmocom/core/bits.h> - -#include <osmocom/gsm/gsm_utils.h> -#include <osmocom/coding/gsm0503_coding.h> - -#include "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" -#include "logging.h" -#include "trx_if.h" -#include "l1ctl.h" - -/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)" */ -#define RACH_EXT_TAIL_BITS_LEN 8 -#define RACH_SYNCH_SEQ_LEN 41 -#define RACH_PAYLOAD_LEN 36 - -/* Extended tail bits (BN0..BN7) */ -static const ubit_t rach_ext_tail_bits[] = { - 0, 0, 1, 1, 1, 0, 1, 0, -}; - -/* Synchronization (training) sequence types */ -enum rach_synch_seq_t { - RACH_SYNCH_SEQ_UNKNOWN = -1, - RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */ - RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */ - RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */ - RACH_SYNCH_SEQ_NUM -}; - -/* Synchronization (training) sequence bits */ -static const char rach_synch_seq_bits[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = { - [RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000", - [RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101", - [RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111", -}; - -/* Synchronization (training) sequence names */ -static struct value_string rach_synch_seq_names[] = { - { RACH_SYNCH_SEQ_UNKNOWN, "UNKNOWN" }, - { RACH_SYNCH_SEQ_TS0, "TS0: GSM, GMSK" }, - { RACH_SYNCH_SEQ_TS1, "TS1: EGPRS, 8-PSK" }, - { RACH_SYNCH_SEQ_TS2, "TS2: EGPRS, GMSK" }, - { 0, NULL }, -}; - -/* Obtain a to-be-transmitted RACH burst */ -int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid) -{ - struct l1ctl_ext_rach_req *ext_req = NULL; - struct l1ctl_rach_req *req = NULL; - enum rach_synch_seq_t synch_seq; - uint8_t burst[GSM_BURST_LEN]; - uint8_t *burst_ptr = burst; - uint8_t payload[36]; - int i, rc; - - /* Is it extended (11-bit) RACH or not? */ - if (PRIM_IS_RACH11(lchan->prim)) { - ext_req = (struct l1ctl_ext_rach_req *) lchan->prim->payload; - synch_seq = ext_req->synch_seq; - - /* Check requested synch. sequence */ - if (synch_seq >= RACH_SYNCH_SEQ_NUM) { - LOGP(DSCHD, LOGL_ERROR, "Unknown RACH synch. sequence=0x%02x\n", synch_seq); - - /* Forget this primitive */ - sched_prim_drop(lchan); - return -ENOTSUP; - } - - /* Delay sending according to offset value */ - if (ext_req->offset-- > 0) - return 0; - - /* Encode extended (11-bit) payload */ - rc = gsm0503_rach_ext_encode(payload, ext_req->ra11, trx->bsic, true); - if (rc) { - LOGP(DSCHD, LOGL_ERROR, "Could not encode extended RACH burst " - "(ra=%u bsic=%u)\n", ext_req->ra11, trx->bsic); - - /* Forget this primitive */ - sched_prim_drop(lchan); - return rc; - } - } else if (PRIM_IS_RACH8(lchan->prim)) { - req = (struct l1ctl_rach_req *) lchan->prim->payload; - synch_seq = RACH_SYNCH_SEQ_TS0; - - /* Delay sending according to offset value */ - if (req->offset-- > 0) - return 0; - - /* Encode regular (8-bit) payload */ - rc = gsm0503_rach_ext_encode(payload, req->ra, trx->bsic, false); - if (rc) { - LOGP(DSCHD, LOGL_ERROR, "Could not encode RACH burst " - "(ra=%u bsic=%u)\n", req->ra, trx->bsic); - - /* Forget this primitive */ - sched_prim_drop(lchan); - return rc; - } - } else { - LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu (expected %zu or %zu), " - "so dropping...\n", lchan->prim->payload_len, - sizeof(*req), sizeof(*ext_req)); - sched_prim_drop(lchan); - return -EINVAL; - } - - - /* BN0-7: extended tail bits */ - memcpy(burst_ptr, rach_ext_tail_bits, RACH_EXT_TAIL_BITS_LEN); - burst_ptr += RACH_EXT_TAIL_BITS_LEN; - - /* BN8-48: chosen synch. (training) sequence */ - for (i = 0; i < RACH_SYNCH_SEQ_LEN; i++) - *(burst_ptr++) = rach_synch_seq_bits[synch_seq][i] == '1'; - - /* BN49-84: encrypted bits (the payload) */ - memcpy(burst_ptr, payload, RACH_PAYLOAD_LEN); - burst_ptr += RACH_PAYLOAD_LEN; - - /* BN85-156: tail bits & extended guard period */ - memset(burst_ptr, 0, burst + GSM_BURST_LEN - burst_ptr); - - LOGP(DSCHD, LOGL_NOTICE, "Transmitting %s RACH (%s) on fn=%u, tn=%u, lchan=%s\n", - PRIM_IS_RACH11(lchan->prim) ? "extended (11-bit)" : "regular (8-bit)", - get_value_string(rach_synch_seq_names, synch_seq), fn, - ts->index, trx_lchan_desc[lchan->type].name); - - /* Forward burst to scheduler */ - rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst); - if (rc) { - /* Forget this primitive */ - sched_prim_drop(lchan); - - return rc; - } - - /* Confirm RACH request */ - l1ctl_tx_rach_conf(trx->l1l, trx->band_arfcn, fn); - - /* Forget processed primitive */ - sched_prim_drop(lchan); - - return 0; -} diff --git a/src/host/trxcon/sched_lchan_tchf.c b/src/host/trxcon/sched_lchan_tchf.c deleted file mode 100644 index f2ecdcc6..00000000 --- a/src/host/trxcon/sched_lchan_tchf.c +++ /dev/null @@ -1,297 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: handlers for DL / UL bursts on logical channels - * - * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com> - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#include <errno.h> -#include <string.h> -#include <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, - sbit_t *bits, int8_t rssi, int16_t toa256) -{ - const struct trx_lchan_desc *lchan_desc; - int n_errors = -1, n_bits_total, rc; - sbit_t *buffer, *offset; - uint8_t l2[128], *mask; - uint32_t *first_fn; - size_t l2_len; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - first_fn = &lchan->rx_first_fn; - 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); - - /* Reset internal state */ - if (bid == 0) { - /* Clean up old measurements */ - memset(&lchan->meas, 0x00, sizeof(lchan->meas)); - - *first_fn = fn; - *mask = 0x00; - } - - /* Update mask */ - *mask |= (1 << bid); - - /* Update mask and RSSI */ - lchan->meas.rssi_sum += rssi; - lchan->meas.toa256_sum += toa256; - lchan->meas.num++; - - /* 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; - - /* Check for complete set of bursts */ - if ((*mask & 0xf) != 0xf) { - LOGP(DSCHD, LOGL_ERROR, "Received incomplete traffic frame at " - "fn=%u (%u/%u) for %s\n", *first_fn, - (*first_fn) % ts->mf_layout->period, - ts->mf_layout->period, - lchan_desc->name); - - /* Send BFI */ - goto bfi; - } - - 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 instead of stolen TCH frame */ - 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 */ - if (n_errors < 0) - n_errors = 116 * 4; - - /* 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; -} diff --git a/src/host/trxcon/sched_lchan_tchh.c b/src/host/trxcon/sched_lchan_tchh.c deleted file mode 100644 index 0201ee35..00000000 --- a/src/host/trxcon/sched_lchan_tchh.c +++ /dev/null @@ -1,502 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: handlers for DL / UL bursts on logical channels - * - * (C) 2018 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 = TDMA_FN_DIFF(fn_mf, map[i][0]); \ - return 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, - sbit_t *bits, int8_t rssi, int16_t toa256) -{ - 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); - - /** - * FIXME: properly update measurements - * - * Since TCH/H channel is using block-diagonal interleaving, - * a single burst may carry 57 bits of one encoded frame, - * and 57 bits of another. This should be taken into account. - */ - lchan->meas.rssi_sum += rssi; - lchan->meas.toa256_sum += toa256; - lchan->meas.num++; - - /* 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) { - LOGP(DSCHD, LOGL_ERROR, "Received bad TCH frame ending at " - "fn=%u on %s (rc=%d)\n", 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 TDMA frame number of the first burst */ - lchan->rx_first_fn = sched_tchh_block_dl_first_fn(lchan->type, - fn, true); /* FACCH/H */ - - /* FACCH/H received, forward to the higher layers */ - sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN, - n_errors, false, false); - - /* 1/2 BFI */ - goto bfi; - } else { - /* A good TCH frame received */ - l2_len = rc; - } - - /* Calculate TDMA frame number of the first burst */ - lchan->rx_first_fn = sched_tchh_block_dl_first_fn(lchan->type, - fn, false); /* TCH/H */ - - /* 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 */ - if (n_errors < 0) - n_errors = 116 * 2; - - /* Calculate TDMA frame number of the first burst */ - lchan->rx_first_fn = sched_tchh_block_dl_first_fn(lchan->type, - fn, false); /* TCH/H */ - - /* 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; -} diff --git a/src/host/trxcon/sched_lchan_xcch.c b/src/host/trxcon/sched_lchan_xcch.c deleted file mode 100644 index 196f949c..00000000 --- a/src/host/trxcon/sched_lchan_xcch.c +++ /dev/null @@ -1,213 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: handlers for DL / UL bursts on logical channels - * - * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com> - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#include <errno.h> -#include <string.h> -#include <stdint.h> - -#include <osmocom/core/logging.h> -#include <osmocom/core/bits.h> - -#include <osmocom/gsm/gsm_utils.h> -#include <osmocom/gsm/protocol/gsm_04_08.h> -#include <osmocom/coding/gsm0503_coding.h> - -#include "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" -#include "logging.h" -#include "trx_if.h" -#include "l1ctl.h" - -int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts, - struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, - sbit_t *bits, int8_t rssi, int16_t toa256) -{ - 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; - uint32_t *first_fn; - - /* Set up pointers */ - lchan_desc = &trx_lchan_desc[lchan->type]; - first_fn = &lchan->rx_first_fn; - 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); - - /* Reset internal state */ - if (bid == 0) { - /* Clean up old measurements */ - memset(&lchan->meas, 0x00, sizeof(lchan->meas)); - - *first_fn = fn; - *mask = 0x0; - } - - /* Update mask */ - *mask |= (1 << bid); - - /* Update measurements */ - lchan->meas.rssi_sum += rssi; - lchan->meas.toa256_sum += toa256; - lchan->meas.num++; - - /* 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; - - /* Check for complete set of bursts */ - if ((*mask & 0xf) != 0xf) { - LOGP(DSCHD, LOGL_ERROR, "Received incomplete data frame at " - "fn=%u (%u/%u) for %s\n", *first_fn, - (*first_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. */ - } - - /* Attempt to decode */ - rc = gsm0503_xcch_decode(l2, buffer, &n_errors, &n_bits_total); - if (rc) { - LOGP(DSCHD, LOGL_ERROR, "Received bad data frame at fn=%u " - "(%u/%u) for %s\n", *first_fn, - (*first_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) { - /* Forget processed primitive */ - sched_prim_drop(lchan); - - /* Reset mask */ - *mask = 0x00; - - /* Confirm data sending */ - sched_send_dt_conf(trx, ts, lchan, fn, false); - } - - return 0; -} diff --git a/src/host/trxcon/sched_mframe.c b/src/host/trxcon/sched_mframe.c deleted file mode 100644 index 9b759af3..00000000 --- a/src/host/trxcon/sched_mframe.c +++ /dev/null @@ -1,2101 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: channel combinations, burst mapping - * - * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> - * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> - * (C) 2015 by Harald Welte <laforge@gnumonks.org> - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - */ - -#include <osmocom/gsm/gsm_utils.h> - -#include "sched_trx.h" - -/* Non-combined CCCH */ -static const struct trx_frame frame_bcch[51] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_BCCH, 0, TRXC_RACH, 0 }, - { TRXC_BCCH, 1, TRXC_RACH, 0 }, - { TRXC_BCCH, 2, TRXC_RACH, 0 }, - { TRXC_BCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_IDLE, 0, TRXC_RACH, 0 }, -}; - -/* Combined CCCH+SDCCH4 */ -static const struct trx_frame frame_bcch_sdcch4[102] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, - { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, - { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, - { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, - { TRXC_BCCH, 2, TRXC_RACH, 0 }, - { TRXC_BCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_SACCH4_2, 0 }, - { TRXC_CCCH, 1, TRXC_SACCH4_2, 1 }, - { TRXC_CCCH, 2, TRXC_SACCH4_2, 2 }, - { TRXC_CCCH, 3, TRXC_SACCH4_2, 3 }, - { TRXC_FCCH, 0, TRXC_SACCH4_3, 0 }, - { TRXC_SCH, 0, TRXC_SACCH4_3, 1 }, - { TRXC_CCCH, 0, TRXC_SACCH4_3, 2 }, - { TRXC_CCCH, 1, TRXC_SACCH4_3, 3 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, - { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, - { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, - { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, - { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, - { TRXC_SACCH4_0, 0, TRXC_SDCCH4_1, 1 }, - { TRXC_SACCH4_0, 1, TRXC_SDCCH4_1, 2 }, - { TRXC_SACCH4_0, 2, TRXC_SDCCH4_1, 3 }, - { TRXC_SACCH4_0, 3, TRXC_RACH, 0 }, - { TRXC_SACCH4_1, 0, TRXC_RACH, 0 }, - { TRXC_SACCH4_1, 1, TRXC_SDCCH4_2, 0 }, - { TRXC_SACCH4_1, 2, TRXC_SDCCH4_2, 1 }, - { TRXC_SACCH4_1, 3, TRXC_SDCCH4_2, 2 }, - { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 }, - - { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, - { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, - { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, - { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, - { TRXC_BCCH, 2, TRXC_RACH, 0 }, - { TRXC_BCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_SACCH4_0, 0 }, - { TRXC_CCCH, 1, TRXC_SACCH4_0, 1 }, - { TRXC_CCCH, 2, TRXC_SACCH4_0, 2 }, - { TRXC_CCCH, 3, TRXC_SACCH4_0, 3 }, - { TRXC_FCCH, 0, TRXC_SACCH4_1, 0 }, - { TRXC_SCH, 0, TRXC_SACCH4_1, 1 }, - { TRXC_CCCH, 0, TRXC_SACCH4_1, 2 }, - { TRXC_CCCH, 1, TRXC_SACCH4_1, 3 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, - { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, - { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, - { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, - { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, - { TRXC_SACCH4_2, 0, TRXC_SDCCH4_1, 1 }, - { TRXC_SACCH4_2, 1, TRXC_SDCCH4_1, 2 }, - { TRXC_SACCH4_2, 2, TRXC_SDCCH4_1, 3 }, - { TRXC_SACCH4_2, 3, TRXC_RACH, 0 }, - { TRXC_SACCH4_3, 0, TRXC_RACH, 0 }, - { TRXC_SACCH4_3, 1, TRXC_SDCCH4_2, 0 }, - { TRXC_SACCH4_3, 2, TRXC_SDCCH4_2, 1 }, - { TRXC_SACCH4_3, 3, TRXC_SDCCH4_2, 2 }, - { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 }, -}; - -static const struct trx_frame frame_bcch_sdcch4_cbch[102] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, - { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, - { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, - { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, - { TRXC_BCCH, 2, TRXC_RACH, 0 }, - { TRXC_BCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_IDLE, 0 }, - { TRXC_CCCH, 1, TRXC_IDLE, 1 }, - { TRXC_CCCH, 2, TRXC_IDLE, 2 }, - { TRXC_CCCH, 3, TRXC_IDLE, 3 }, - { TRXC_FCCH, 0, TRXC_SACCH4_3, 0 }, - { TRXC_SCH, 0, TRXC_SACCH4_3, 1 }, - { TRXC_CCCH, 0, TRXC_SACCH4_3, 2 }, - { TRXC_CCCH, 1, TRXC_SACCH4_3, 3 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, - { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, - { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, - { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, - { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, - { TRXC_SACCH4_0, 0, TRXC_SDCCH4_1, 1 }, - { TRXC_SACCH4_0, 1, TRXC_SDCCH4_1, 2 }, - { TRXC_SACCH4_0, 2, TRXC_SDCCH4_1, 3 }, - { TRXC_SACCH4_0, 3, TRXC_RACH, 0 }, - { TRXC_SACCH4_1, 0, TRXC_RACH, 0 }, - { TRXC_SACCH4_1, 1, TRXC_IDLE, 0 }, - { TRXC_SACCH4_1, 2, TRXC_IDLE, 1 }, - { TRXC_SACCH4_1, 3, TRXC_IDLE, 2 }, - { TRXC_IDLE, 0, TRXC_IDLE, 3 }, - - { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, - { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, - { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, - { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, - { TRXC_BCCH, 2, TRXC_RACH, 0 }, - { TRXC_BCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_SACCH4_0, 0 }, - { TRXC_CCCH, 1, TRXC_SACCH4_0, 1 }, - { TRXC_CCCH, 2, TRXC_SACCH4_0, 2 }, - { TRXC_CCCH, 3, TRXC_SACCH4_0, 3 }, - { TRXC_FCCH, 0, TRXC_SACCH4_1, 0 }, - { TRXC_SCH, 0, TRXC_SACCH4_1, 1 }, - { TRXC_CCCH, 0, TRXC_SACCH4_1, 2 }, - { TRXC_CCCH, 1, TRXC_SACCH4_1, 3 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_CCCH, 0, TRXC_RACH, 0 }, - { TRXC_CCCH, 1, TRXC_RACH, 0 }, - { TRXC_CCCH, 2, TRXC_RACH, 0 }, - { TRXC_CCCH, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, - { TRXC_FCCH, 0, TRXC_RACH, 0 }, - { TRXC_SCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 1, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 2, TRXC_RACH, 0 }, - { TRXC_SDCCH4_CBCH, 3, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, - { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, - { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, - { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, - { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, - { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, - { TRXC_IDLE, 0, TRXC_SDCCH4_1, 1 }, - { TRXC_IDLE, 1, TRXC_SDCCH4_1, 2 }, - { TRXC_IDLE, 2, TRXC_SDCCH4_1, 3 }, - { TRXC_IDLE, 3, TRXC_RACH, 0 }, - { TRXC_SACCH4_3, 0, TRXC_RACH, 0 }, - { TRXC_SACCH4_3, 1, TRXC_IDLE, 0 }, - { TRXC_SACCH4_3, 2, TRXC_IDLE, 1 }, - { TRXC_SACCH4_3, 3, TRXC_IDLE, 2 }, - { TRXC_IDLE, 0, TRXC_IDLE, 3 }, -}; - -static const struct trx_frame frame_sdcch8[102] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_SDCCH8_0, 0, TRXC_SACCH8_5, 0 }, - { TRXC_SDCCH8_0, 1, TRXC_SACCH8_5, 1 }, - { TRXC_SDCCH8_0, 2, TRXC_SACCH8_5, 2 }, - { TRXC_SDCCH8_0, 3, TRXC_SACCH8_5, 3 }, - { TRXC_SDCCH8_1, 0, TRXC_SACCH8_6, 0 }, - { TRXC_SDCCH8_1, 1, TRXC_SACCH8_6, 1 }, - { TRXC_SDCCH8_1, 2, TRXC_SACCH8_6, 2 }, - { TRXC_SDCCH8_1, 3, TRXC_SACCH8_6, 3 }, - { TRXC_SDCCH8_2, 0, TRXC_SACCH8_7, 0 }, - { TRXC_SDCCH8_2, 1, TRXC_SACCH8_7, 1 }, - { TRXC_SDCCH8_2, 2, TRXC_SACCH8_7, 2 }, - { TRXC_SDCCH8_2, 3, TRXC_SACCH8_7, 3 }, - { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, - { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, - { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, - { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, - { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, - { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, - { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, - { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, - { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 }, - { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 }, - { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 }, - { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 }, - { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, - { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, - { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, - { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, - { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, - { TRXC_SACCH8_0, 0, TRXC_SDCCH8_4, 1 }, - { TRXC_SACCH8_0, 1, TRXC_SDCCH8_4, 2 }, - { TRXC_SACCH8_0, 2, TRXC_SDCCH8_4, 3 }, - { TRXC_SACCH8_0, 3, TRXC_SDCCH8_5, 0 }, - { TRXC_SACCH8_1, 0, TRXC_SDCCH8_5, 1 }, - { TRXC_SACCH8_1, 1, TRXC_SDCCH8_5, 2 }, - { TRXC_SACCH8_1, 2, TRXC_SDCCH8_5, 3 }, - { TRXC_SACCH8_1, 3, TRXC_SDCCH8_6, 0 }, - { TRXC_SACCH8_2, 0, TRXC_SDCCH8_6, 1 }, - { TRXC_SACCH8_2, 1, TRXC_SDCCH8_6, 2 }, - { TRXC_SACCH8_2, 2, TRXC_SDCCH8_6, 3 }, - { TRXC_SACCH8_2, 3, TRXC_SDCCH8_7, 0 }, - { TRXC_SACCH8_3, 0, TRXC_SDCCH8_7, 1 }, - { TRXC_SACCH8_3, 1, TRXC_SDCCH8_7, 2 }, - { TRXC_SACCH8_3, 2, TRXC_SDCCH8_7, 3 }, - { TRXC_SACCH8_3, 3, TRXC_SACCH8_0, 0 }, - { TRXC_IDLE, 0, TRXC_SACCH8_0, 1 }, - { TRXC_IDLE, 0, TRXC_SACCH8_0, 2 }, - { TRXC_IDLE, 0, TRXC_SACCH8_0, 3 }, - - { TRXC_SDCCH8_0, 0, TRXC_SACCH8_1, 0 }, - { TRXC_SDCCH8_0, 1, TRXC_SACCH8_1, 1 }, - { TRXC_SDCCH8_0, 2, TRXC_SACCH8_1, 2 }, - { TRXC_SDCCH8_0, 3, TRXC_SACCH8_1, 3 }, - { TRXC_SDCCH8_1, 0, TRXC_SACCH8_2, 0 }, - { TRXC_SDCCH8_1, 1, TRXC_SACCH8_2, 1 }, - { TRXC_SDCCH8_1, 2, TRXC_SACCH8_2, 2 }, - { TRXC_SDCCH8_1, 3, TRXC_SACCH8_2, 3 }, - { TRXC_SDCCH8_2, 0, TRXC_SACCH8_3, 0 }, - { TRXC_SDCCH8_2, 1, TRXC_SACCH8_3, 1 }, - { TRXC_SDCCH8_2, 2, TRXC_SACCH8_3, 2 }, - { TRXC_SDCCH8_2, 3, TRXC_SACCH8_3, 3 }, - { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, - { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, - { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, - { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, - { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, - { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, - { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, - { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, - { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 }, - { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 }, - { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 }, - { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 }, - { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, - { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, - { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, - { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, - { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, - { TRXC_SACCH8_4, 0, TRXC_SDCCH8_4, 1 }, - { TRXC_SACCH8_4, 1, TRXC_SDCCH8_4, 2 }, - { TRXC_SACCH8_4, 2, TRXC_SDCCH8_4, 3 }, - { TRXC_SACCH8_4, 3, TRXC_SDCCH8_5, 0 }, - { TRXC_SACCH8_5, 0, TRXC_SDCCH8_5, 1 }, - { TRXC_SACCH8_5, 1, TRXC_SDCCH8_5, 2 }, - { TRXC_SACCH8_5, 2, TRXC_SDCCH8_5, 3 }, - { TRXC_SACCH8_5, 3, TRXC_SDCCH8_6, 0 }, - { TRXC_SACCH8_6, 0, TRXC_SDCCH8_6, 1 }, - { TRXC_SACCH8_6, 1, TRXC_SDCCH8_6, 2 }, - { TRXC_SACCH8_6, 2, TRXC_SDCCH8_6, 3 }, - { TRXC_SACCH8_6, 3, TRXC_SDCCH8_7, 0 }, - { TRXC_SACCH8_7, 0, TRXC_SDCCH8_7, 1 }, - { TRXC_SACCH8_7, 1, TRXC_SDCCH8_7, 2 }, - { TRXC_SACCH8_7, 2, TRXC_SDCCH8_7, 3 }, - { TRXC_SACCH8_7, 3, TRXC_SACCH8_4, 0 }, - { TRXC_IDLE, 0, TRXC_SACCH8_4, 1 }, - { TRXC_IDLE, 0, TRXC_SACCH8_4, 2 }, - { TRXC_IDLE, 0, TRXC_SACCH8_4, 3 }, -}; - -static const struct trx_frame frame_sdcch8_cbch[102] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_SDCCH8_0, 0, TRXC_SACCH8_5, 0 }, - { TRXC_SDCCH8_0, 1, TRXC_SACCH8_5, 1 }, - { TRXC_SDCCH8_0, 2, TRXC_SACCH8_5, 2 }, - { TRXC_SDCCH8_0, 3, TRXC_SACCH8_5, 3 }, - { TRXC_SDCCH8_1, 0, TRXC_SACCH8_6, 0 }, - { TRXC_SDCCH8_1, 1, TRXC_SACCH8_6, 1 }, - { TRXC_SDCCH8_1, 2, TRXC_SACCH8_6, 2 }, - { TRXC_SDCCH8_1, 3, TRXC_SACCH8_6, 3 }, - { TRXC_SDCCH8_CBCH, 0, TRXC_SACCH8_7, 0 }, - { TRXC_SDCCH8_CBCH, 1, TRXC_SACCH8_7, 1 }, - { TRXC_SDCCH8_CBCH, 2, TRXC_SACCH8_7, 2 }, - { TRXC_SDCCH8_CBCH, 3, TRXC_SACCH8_7, 3 }, - { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, - { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, - { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, - { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, - { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, - { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, - { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, - { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, - { TRXC_SDCCH8_5, 3, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_6, 0, TRXC_IDLE, 1 }, - { TRXC_SDCCH8_6, 1, TRXC_IDLE, 2 }, - { TRXC_SDCCH8_6, 2, TRXC_IDLE, 3 }, - { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, - { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, - { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, - { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, - { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, - { TRXC_SACCH8_0, 0, TRXC_SDCCH8_4, 1 }, - { TRXC_SACCH8_0, 1, TRXC_SDCCH8_4, 2 }, - { TRXC_SACCH8_0, 2, TRXC_SDCCH8_4, 3 }, - { TRXC_SACCH8_0, 3, TRXC_SDCCH8_5, 0 }, - { TRXC_SACCH8_1, 0, TRXC_SDCCH8_5, 1 }, - { TRXC_SACCH8_1, 1, TRXC_SDCCH8_5, 2 }, - { TRXC_SACCH8_1, 2, TRXC_SDCCH8_5, 3 }, - { TRXC_SACCH8_1, 3, TRXC_SDCCH8_6, 0 }, - { TRXC_IDLE, 0, TRXC_SDCCH8_6, 1 }, - { TRXC_IDLE, 1, TRXC_SDCCH8_6, 2 }, - { TRXC_IDLE, 2, TRXC_SDCCH8_6, 3 }, - { TRXC_IDLE, 3, TRXC_SDCCH8_7, 0 }, - { TRXC_SACCH8_3, 0, TRXC_SDCCH8_7, 1 }, - { TRXC_SACCH8_3, 1, TRXC_SDCCH8_7, 2 }, - { TRXC_SACCH8_3, 2, TRXC_SDCCH8_7, 3 }, - { TRXC_SACCH8_3, 3, TRXC_SACCH8_0, 0 }, - { TRXC_IDLE, 0, TRXC_SACCH8_0, 1 }, - { TRXC_IDLE, 0, TRXC_SACCH8_0, 2 }, - { TRXC_IDLE, 0, TRXC_SACCH8_0, 3 }, - - { TRXC_SDCCH8_0, 0, TRXC_SACCH8_1, 0 }, - { TRXC_SDCCH8_0, 1, TRXC_SACCH8_1, 1 }, - { TRXC_SDCCH8_0, 2, TRXC_SACCH8_1, 2 }, - { TRXC_SDCCH8_0, 3, TRXC_SACCH8_1, 3 }, - { TRXC_SDCCH8_1, 0, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_1, 1, TRXC_IDLE, 1 }, - { TRXC_SDCCH8_1, 2, TRXC_IDLE, 2 }, - { TRXC_SDCCH8_1, 3, TRXC_IDLE, 3 }, - { TRXC_SDCCH8_CBCH, 0, TRXC_SACCH8_3, 0 }, - { TRXC_SDCCH8_CBCH, 1, TRXC_SACCH8_3, 1 }, - { TRXC_SDCCH8_CBCH, 2, TRXC_SACCH8_3, 2 }, - { TRXC_SDCCH8_CBCH, 3, TRXC_SACCH8_3, 3 }, - { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, - { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, - { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, - { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, - { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, - { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, - { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, - { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, - { TRXC_SDCCH8_5, 3, TRXC_IDLE, 0 }, - { TRXC_SDCCH8_6, 0, TRXC_IDLE, 1 }, - { TRXC_SDCCH8_6, 1, TRXC_IDLE, 2 }, - { TRXC_SDCCH8_6, 2, TRXC_IDLE, 3 }, - { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, - { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, - { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, - { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, - { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, - { TRXC_SACCH8_4, 0, TRXC_SDCCH8_4, 1 }, - { TRXC_SACCH8_4, 1, TRXC_SDCCH8_4, 2 }, - { TRXC_SACCH8_4, 2, TRXC_SDCCH8_4, 3 }, - { TRXC_SACCH8_4, 3, TRXC_SDCCH8_5, 0 }, - { TRXC_SACCH8_5, 0, TRXC_SDCCH8_5, 1 }, - { TRXC_SACCH8_5, 1, TRXC_SDCCH8_5, 2 }, - { TRXC_SACCH8_5, 2, TRXC_SDCCH8_5, 3 }, - { TRXC_SACCH8_5, 3, TRXC_SDCCH8_6, 0 }, - { TRXC_SACCH8_6, 0, TRXC_SDCCH8_6, 1 }, - { TRXC_SACCH8_6, 1, TRXC_SDCCH8_6, 2 }, - { TRXC_SACCH8_6, 2, TRXC_SDCCH8_6, 3 }, - { TRXC_SACCH8_6, 3, TRXC_SDCCH8_7, 0 }, - { TRXC_SACCH8_7, 0, TRXC_SDCCH8_7, 1 }, - { TRXC_SACCH8_7, 1, TRXC_SDCCH8_7, 2 }, - { TRXC_SACCH8_7, 2, TRXC_SDCCH8_7, 3 }, - { TRXC_SACCH8_7, 3, TRXC_SACCH8_4, 0 }, - { TRXC_IDLE, 0, TRXC_SACCH8_4, 1 }, - { TRXC_IDLE, 0, TRXC_SACCH8_4, 2 }, - { TRXC_IDLE, 0, TRXC_SACCH8_4, 3 }, -}; - -static const struct trx_frame frame_tchf_ts0[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, -}; - -static const struct trx_frame frame_tchf_ts1[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, -}; - -static const struct trx_frame frame_tchf_ts2[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, -}; - -static const struct trx_frame frame_tchf_ts3[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, -}; - -static const struct trx_frame frame_tchf_ts4[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, -}; - -static const struct trx_frame frame_tchf_ts5[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, -}; - -static const struct trx_frame frame_tchf_ts6[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, -}; - -static const struct trx_frame frame_tchf_ts7[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_TCHF, 0, TRXC_TCHF, 0 }, - { TRXC_TCHF, 1, TRXC_TCHF, 1 }, - { TRXC_TCHF, 2, TRXC_TCHF, 2 }, - { TRXC_TCHF, 3, TRXC_TCHF, 3 }, - { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, -}; - -static const struct trx_frame frame_tchh_ts01[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, -}; - -static const struct trx_frame frame_tchh_ts23[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, -}; - -static const struct trx_frame frame_tchh_ts45[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, -}; - -static const struct trx_frame frame_tchh_ts67[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, - { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, - { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, - { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, - { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, -}; - -static const struct trx_frame frame_pdch[104] = { - /* dl_chan dl_bid ul_chan ul_bid */ - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PTCCH, 0, TRXC_PTCCH, 0 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PTCCH, 1, TRXC_PTCCH, 1 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PTCCH, 2, TRXC_PTCCH, 2 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PTCCH, 3, TRXC_PTCCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, - { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, - { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, - { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, - { TRXC_IDLE, 0, TRXC_IDLE, 0 }, -}; - -/* Logical channel mask for a single channel */ -#define M64(x) \ - ((uint64_t) 0x01 << x) - -/* Logical channel mask for BCCH+CCCH */ -#define M64_BCCH_CCCH \ - (uint64_t) 0x00 \ - | M64(TRXC_FCCH) \ - | M64(TRXC_SCH) \ - | M64(TRXC_BCCH) \ - | M64(TRXC_RACH) \ - | M64(TRXC_CCCH) - -/* Logical channel mask for SDCCH4 (with SACCH, all sub-channels) */ -#define M64_SDCCH4 \ - (uint64_t) 0x00 \ - | M64(TRXC_SDCCH4_0) | M64(TRXC_SACCH4_0) \ - | M64(TRXC_SDCCH4_1) | M64(TRXC_SACCH4_1) \ - | M64(TRXC_SDCCH4_2) | M64(TRXC_SACCH4_2) \ - | M64(TRXC_SDCCH4_3) | M64(TRXC_SACCH4_3) - -/* Logical channel mask for SDCCH8 (with SACCH, all sub-channels) */ -#define M64_SDCCH8 \ - (uint64_t) 0x00 \ - | M64(TRXC_SDCCH8_0) | M64(TRXC_SACCH8_0) \ - | M64(TRXC_SDCCH8_1) | M64(TRXC_SACCH8_1) \ - | M64(TRXC_SDCCH8_2) | M64(TRXC_SACCH8_2) \ - | M64(TRXC_SDCCH8_3) | M64(TRXC_SACCH8_3) \ - | M64(TRXC_SDCCH8_4) | M64(TRXC_SACCH8_4) \ - | M64(TRXC_SDCCH8_5) | M64(TRXC_SACCH8_5) \ - | M64(TRXC_SDCCH8_6) | M64(TRXC_SACCH8_6) \ - | M64(TRXC_SDCCH8_7) | M64(TRXC_SACCH8_7) - -/* Logical channel mask for TCH/F (with SACCH) */ -#define M64_TCHF \ - (uint64_t) 0x00 \ - | M64(TRXC_TCHF) | M64(TRXC_SACCHTF) - -/* Logical channel mask for TCH/H (with SACCH, all sub-channels) */ -#define M64_TCHH \ - (uint64_t) 0x00 \ - | M64(TRXC_TCHH_0) | M64(TRXC_SACCHTH_0) \ - | M64(TRXC_TCHH_1) | M64(TRXC_SACCHTH_1) - -/** - * A few notes about frame count: - * - * 26 frame multiframe - traffic multiframe - * 51 frame multiframe - control multiframe - * - * 102 = 2 x 51 frame multiframe - * 104 = 4 x 26 frame multiframe - */ -static const struct trx_multiframe layouts[] = { - { - GSM_PCHAN_NONE, "NONE", - 0, 0xff, - 0x00, - NULL - }, - { - GSM_PCHAN_CCCH, "BCCH+CCCH", - 51, 0xff, - M64_BCCH_CCCH, - frame_bcch - }, - { - GSM_PCHAN_CCCH_SDCCH4, "BCCH+CCCH+SDCCH/4+SACCH/4", - 102, 0xff, - M64_BCCH_CCCH | M64_SDCCH4, - frame_bcch_sdcch4 - }, - { - GSM_PCHAN_CCCH_SDCCH4_CBCH, "BCCH+CCCH+SDCCH/4+SACCH/4+CBCH", - 102, 0xff, - M64_BCCH_CCCH | M64_SDCCH4 | M64(TRXC_SDCCH4_CBCH), - frame_bcch_sdcch4_cbch - }, - { - GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH/8+SACCH/8", - 102, 0xff, - M64_SDCCH8, - frame_sdcch8 - }, - { - GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH/8+SACCH/8+CBCH", - 102, 0xff, - M64_SDCCH8 | M64(TRXC_SDCCH8_CBCH), - frame_sdcch8_cbch - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x01, - M64_TCHF, - frame_tchf_ts0 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x02, - M64_TCHF, - frame_tchf_ts1 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x04, - M64_TCHF, - frame_tchf_ts2 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x08, - M64_TCHF, - frame_tchf_ts3 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x10, - M64_TCHF, - frame_tchf_ts4 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x20, - M64_TCHF, - frame_tchf_ts5 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x40, - M64_TCHF, - frame_tchf_ts6 - }, - { - GSM_PCHAN_TCH_F, "TCH/F+SACCH", - 104, 0x80, - M64_TCHF, - frame_tchf_ts7 - }, - { - GSM_PCHAN_TCH_H, "TCH/H+SACCH", - 104, 0x03, - M64_TCHH, - frame_tchh_ts01 - }, - { - GSM_PCHAN_TCH_H, "TCH/H+SACCH", - 104, 0x0c, - M64_TCHH, - frame_tchh_ts23 - }, - { - GSM_PCHAN_TCH_H, "TCH/H+SACCH", - 104, 0x30, - M64_TCHH, - frame_tchh_ts45 - }, - { - GSM_PCHAN_TCH_H, "TCH/H+SACCH", - 104, 0xc0, - M64_TCHH, - frame_tchh_ts67 - }, - { - GSM_PCHAN_PDCH, "PDCH", - 104, 0xff, - M64(TRXC_PDTCH) | M64(TRXC_PTCCH), - frame_pdch - }, -}; - -const struct trx_multiframe *sched_mframe_layout( - enum gsm_phys_chan_config config, int tn) -{ - int i, ts_allowed; - - for (i = 0; i < ARRAY_SIZE(layouts); i++) { - ts_allowed = layouts[i].slotmask & (0x01 << tn); - if (layouts[i].chan_config == config && ts_allowed) - return &layouts[i]; - } - - return NULL; -} diff --git a/src/host/trxcon/sched_prim.c b/src/host/trxcon/sched_prim.c deleted file mode 100644 index 50dfd6e9..00000000 --- a/src/host/trxcon/sched_prim.c +++ /dev/null @@ -1,611 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: primitive management - * - * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com> - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#include <errno.h> -#include <stdlib.h> -#include <string.h> -#include <talloc.h> - -#include <osmocom/core/msgb.h> -#include <osmocom/core/logging.h> -#include <osmocom/core/linuxlist.h> - -#include <osmocom/gsm/protocol/gsm_04_08.h> - -#include "scheduler.h" -#include "sched_trx.h" -#include "trx_if.h" -#include "logging.h" - -/** - * Initializes a new primitive by allocating memory - * and filling some meta-information (e.g. lchan type). - * - * @param ctx parent talloc context - * @param prim external prim pointer (will point to the allocated prim) - * @param pl_len prim payload length - * @param chan_nr RSL channel description (used to set a proper chan) - * @param link_id RSL link description (used to set a proper chan) - * @return zero in case of success, otherwise a error number - */ -int sched_prim_init(void *ctx, struct trx_ts_prim **prim, - size_t pl_len, uint8_t chan_nr, uint8_t link_id) -{ - enum trx_lchan_type lchan_type; - struct trx_ts_prim *new_prim; - uint8_t len; - - /* Determine lchan type */ - lchan_type = sched_trx_chan_nr2lchan_type(chan_nr, link_id); - if (!lchan_type) { - LOGP(DSCH, LOGL_ERROR, "Couldn't determine lchan type " - "for chan_nr=%02x and link_id=%02x\n", chan_nr, link_id); - return -EINVAL; - } - - /* How much memory do we need? */ - len = sizeof(struct trx_ts_prim); /* Primitive header */ - len += pl_len; /* Requested payload size */ - - /* Allocate a new primitive */ - new_prim = talloc_zero_size(ctx, len); - if (new_prim == NULL) { - LOGP(DSCH, LOGL_ERROR, "Failed to allocate memory\n"); - return -ENOMEM; - } - - /* Init primitive header */ - new_prim->payload_len = pl_len; - new_prim->chan = lchan_type; - - /* Set external pointer */ - *prim = new_prim; - - return 0; -} - -/** - * Adds a primitive to the end of transmit queue of a particular - * timeslot, whose index is parsed from chan_nr. - * - * @param trx TRX instance - * @param prim to be enqueued primitive - * @param chan_nr RSL channel description - * @return zero in case of success, otherwise a error number - */ -int sched_prim_push(struct trx_instance *trx, - struct trx_ts_prim *prim, uint8_t chan_nr) -{ - struct trx_ts *ts; - uint8_t tn; - - /* Determine TS index */ - tn = chan_nr & 0x7; - - /* Check whether required timeslot is allocated and configured */ - ts = trx->ts_list[tn]; - if (ts == NULL || ts->mf_layout == NULL) { - LOGP(DSCH, LOGL_ERROR, "Timeslot %u isn't configured\n", tn); - return -EINVAL; - } - - /** - * Change talloc context of primitive - * from trx to the parent ts - */ - talloc_steal(ts, prim); - - /* Add primitive to TS transmit queue */ - llist_add_tail(&prim->list, &ts->tx_prims); - - return 0; -} - -/** - * Composes a new primitive using either cached (if populated), - * or "dummy" Measurement Report message. - * - * @param lchan lchan to assign a primitive - * @return SACCH primitive to be transmitted - */ -static struct trx_ts_prim *prim_compose_mr(struct trx_lchan_state *lchan) -{ - struct trx_ts_prim *prim; - uint8_t *mr_src_ptr; - bool cached; - int rc; - - /* "Dummy" Measurement Report */ - static const uint8_t meas_rep_dummy[] = { - /* L1 SACCH pseudo-header */ - 0x0f, 0x00, - - /* LAPDm header */ - 0x01, 0x03, 0x49, - - /* Measurement report */ - 0x06, 0x15, 0x36, 0x36, 0x01, 0xC0, - - /* TODO: Padding? Randomize if so */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }; - - /* Allocate a new primitive */ - rc = sched_prim_init(lchan, &prim, GSM_MACBLOCK_LEN, - trx_lchan_desc[lchan->type].chan_nr, TRX_CH_LID_SACCH); - OSMO_ASSERT(rc == 0); - - /* Check if the MR cache is populated (verify LAPDm header) */ - cached = (lchan->sacch.mr_cache[2] != 0x00 - && lchan->sacch.mr_cache[3] != 0x00 - && lchan->sacch.mr_cache[4] != 0x00); - if (cached) { /* Use the cached one */ - mr_src_ptr = lchan->sacch.mr_cache; - lchan->sacch.mr_cache_usage++; - } else { /* Use "dummy" one */ - mr_src_ptr = (uint8_t *) meas_rep_dummy; - } - - /* Compose a new Measurement Report primitive */ - memcpy(prim->payload, mr_src_ptr, GSM_MACBLOCK_LEN); - -#if 0 - /** - * Update the L1 SACCH pseudo-header (only for cached MRs) - * - * FIXME: this would require having access to the trx_instance, - * what can be achieved either by chain-passing the pointer - * through sched_prim_dequeue(), or by adding some - * back-pointers to the logical channel state. - * - * 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] = trx->tx_power; - prim->payload[1] = trx->ta; - } -#endif - - /* Inform about the cache usage count */ - if (cached && lchan->sacch.mr_cache_usage > 5) { - LOGP(DSCHD, LOGL_NOTICE, "SACCH MR cache usage count=%u > 5 " - "on lchan=%s => ancient measurements, please fix!\n", - lchan->sacch.mr_cache_usage, - trx_lchan_desc[lchan->type].name); - } - - LOGP(DSCHD, LOGL_NOTICE, "Using a %s Measurement Report " - "on lchan=%s\n", (cached ? "cached" : "dummy"), - trx_lchan_desc[lchan->type].name); - - return prim; -} - -/** - * Dequeues a SACCH primitive from transmit queue, if present. - * Otherwise dequeues a cached Measurement Report (the last - * received one). Finally, if the cache is empty, a "dummy" - * measurement report is used. - * - * According to 3GPP TS 04.08, section 3.4.1, SACCH channel - * accompanies either a traffic or a signaling channel. It - * has the particularity that continuous transmission must - * occur in both directions, so on the Uplink direction - * measurement result messages are sent at each possible - * occasion when nothing else has to be sent. The LAPDm - * fill frames (0x01, 0x03, 0x01, 0x2b, ...) are not - * applicable on SACCH channels! - * - * Unfortunately, 3GPP TS 04.08 doesn't clearly state - * which "else messages" besides Measurement Reports - * can be send by the MS on SACCH channels. However, - * in sub-clause 3.4.1 it's stated that the interval - * between two successive measurement result messages - * shall not exceed one L2 frame. - * - * @param queue transmit queue to take a prim from - * @param lchan lchan to assign a primitive - * @return SACCH primitive to be transmitted - */ -static struct trx_ts_prim *prim_dequeue_sacch(struct llist_head *queue, - struct trx_lchan_state *lchan) -{ - struct trx_ts_prim *prim_nmr = NULL; - struct trx_ts_prim *prim_mr = NULL; - struct trx_ts_prim *prim; - bool mr_now; - - /* Shall we transmit MR now? */ - mr_now = !lchan->sacch.mr_tx_last; - -#define PRIM_IS_MR(prim) \ - (prim->payload[5] == GSM48_PDISC_RR \ - && prim->payload[6] == GSM48_MT_RR_MEAS_REP) - - /* Iterate over all primitives in the queue */ - llist_for_each_entry(prim, queue, list) { - /* We are looking for particular channel */ - if (prim->chan != lchan->type) - continue; - - /* Look for a Measurement Report */ - if (!prim_mr && PRIM_IS_MR(prim)) - prim_mr = prim; - - /* Look for anything else */ - if (!prim_nmr && !PRIM_IS_MR(prim)) - prim_nmr = prim; - - /* Should we look further? */ - if (mr_now && prim_mr) - break; /* MR was found */ - else if (!mr_now && prim_nmr) - break; /* something else was found */ - } - - LOGP(DSCHD, LOGL_DEBUG, "SACCH MR selection on lchan=%s: " - "mr_tx_last=%d prim_mr=%p prim_nmr=%p\n", - trx_lchan_desc[lchan->type].name, - lchan->sacch.mr_tx_last, - prim_mr, prim_nmr); - - /* Prioritize non-MR prim if possible */ - if (mr_now && prim_mr) - prim = prim_mr; - else if (!mr_now && prim_nmr) - prim = prim_nmr; - else if (!mr_now && prim_mr) - prim = prim_mr; - else /* Nothing was found */ - prim = NULL; - - /* Have we found what we were looking for? */ - if (prim) /* Dequeue if so */ - llist_del(&prim->list); - else /* Otherwise compose a new MR */ - prim = prim_compose_mr(lchan); - - /* Update the cached report */ - if (prim == prim_mr) { - memcpy(lchan->sacch.mr_cache, - prim->payload, GSM_MACBLOCK_LEN); - lchan->sacch.mr_cache_usage = 0; - - LOGP(DSCHD, LOGL_DEBUG, "SACCH MR cache has been updated " - "for lchan=%s\n", trx_lchan_desc[lchan->type].name); - } - - /* Update the MR transmission state */ - lchan->sacch.mr_tx_last = PRIM_IS_MR(prim); - - LOGP(DSCHD, LOGL_DEBUG, "SACCH decision on lchan=%s: %s\n", - trx_lchan_desc[lchan->type].name, PRIM_IS_MR(prim) ? - "Measurement Report" : "data frame"); - - return prim; -} - -/* Dequeues a primitive of a given channel type */ -static struct trx_ts_prim *prim_dequeue_one(struct llist_head *queue, - enum trx_lchan_type lchan_type) -{ - struct trx_ts_prim *prim; - - /** - * There is no need to use the 'safe' list iteration here - * as an item removal is immediately followed by return. - */ - llist_for_each_entry(prim, queue, list) { - if (prim->chan == lchan_type) { - llist_del(&prim->list); - return prim; - } - } - - return NULL; -} - -/** - * Dequeues either a FACCH, or a speech TCH primitive - * of a given channel type (Lm or Bm). - * - * Note: we could avoid 'lchan_type' parameter and just - * check the prim's channel type using CHAN_IS_TCH(), - * but the current approach is a bit more flexible, - * and allows one to have both sub-slots of TCH/H - * enabled on same timeslot e.g. for testing... - * - * @param queue transmit queue to take a prim from - * @param lchan_type required channel type of a primitive, - * e.g. TRXC_TCHF, TRXC_TCHH_0, or TRXC_TCHH_1 - * @param facch FACCH (true) or speech (false) prim? - * @return either a FACCH, or a TCH primitive if found, - * otherwise NULL - */ -static struct trx_ts_prim *prim_dequeue_tch(struct llist_head *queue, - enum trx_lchan_type lchan_type, bool facch) -{ - struct trx_ts_prim *prim; - - /** - * There is no need to use the 'safe' list iteration here - * as an item removal is immediately followed by return. - */ - llist_for_each_entry(prim, queue, list) { - if (prim->chan != lchan_type) - continue; - - /* Either FACCH, or not FACCH */ - if (PRIM_IS_FACCH(prim) != facch) - continue; - - llist_del(&prim->list); - return prim; - } - - return NULL; -} - -/** - * Dequeues either a TCH/F, or a FACCH/F prim (preferred). - * If a FACCH/F prim is found, one TCH/F prim is being - * dropped (i.e. replaced). - * - * @param queue a transmit queue to take a prim from - * @return either a FACCH/F, or a TCH/F primitive, - * otherwise NULL - */ -static struct trx_ts_prim *prim_dequeue_tch_f(struct llist_head *queue) -{ - struct trx_ts_prim *facch; - struct trx_ts_prim *tch; - - /* Attempt to find a pair of both FACCH/F and TCH/F frames */ - facch = prim_dequeue_tch(queue, TRXC_TCHF, true); - tch = prim_dequeue_tch(queue, TRXC_TCHF, false); - - /* Prioritize FACCH/F, if found */ - if (facch) { - /* One TCH/F prim is replaced */ - if (tch) - talloc_free(tch); - return facch; - } else if (tch) { - /* Only TCH/F prim was found */ - return tch; - } else { - /* Nothing was found, e.g. when only SACCH frames are in queue */ - return NULL; - } -} - -/** - * Dequeues either a TCH/H, or a FACCH/H prim (preferred). - * If a FACCH/H prim is found, two TCH/H prims are being - * dropped (i.e. replaced). - * - * According to GSM 05.02, the following blocks can be used - * to carry FACCH/H data (see clause 7, table 1 of 9): - * - * UL FACCH/H0: - * B0(0,2,4,6,8,10), B1(8,10,13,15,17,19), B2(17,19,21,23,0,2) - * - * UL FACCH/H1: - * B0(1,3,5,7,9,11), B1(9,11,14,16,18,20), B2(18,20,22,24,1,3) - * - * where the numbers within brackets are fn % 26. - * - * @param queue transmit queue to take a prim from - * @param fn the current frame number - * @param lchan_type required channel type of a primitive, - * @return either a FACCH/H, or a TCH/H primitive, - * otherwise NULL - */ -static struct trx_ts_prim *prim_dequeue_tch_h(struct llist_head *queue, - uint32_t fn, enum trx_lchan_type lchan_type) -{ - struct trx_ts_prim *facch; - struct trx_ts_prim *tch; - bool facch_now; - - /* May we initiate an UL FACCH/H frame transmission now? */ - facch_now = sched_tchh_facch_start(lchan_type, fn, true); - if (!facch_now) /* Just dequeue a TCH/H prim */ - goto no_facch; - - /* If there are no FACCH/H prims in the queue */ - facch = prim_dequeue_tch(queue, lchan_type, true); - if (!facch) /* Just dequeue a TCH/H prim */ - goto no_facch; - - /* FACCH/H prim replaces two TCH/F prims */ - tch = prim_dequeue_tch(queue, lchan_type, false); - if (tch) { - /* At least one TCH/H prim is dropped */ - talloc_free(tch); - - /* Attempt to find another */ - tch = prim_dequeue_tch(queue, lchan_type, false); - if (tch) /* Drop the second TCH/H prim */ - talloc_free(tch); - } - - return facch; - -no_facch: - return prim_dequeue_tch(queue, lchan_type, false); -} - -/** - * Dequeues a single primitive of required type - * from a specified transmit queue. - * - * @param queue a transmit queue to take a prim from - * @param fn the current frame number (used for FACCH/H) - * @param lchan logical channel state - * @return a primitive or NULL if not found - */ -struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue, - uint32_t fn, struct trx_lchan_state *lchan) -{ - /* SACCH is unorthodox, see 3GPP TS 04.08, section 3.4.1 */ - if (CHAN_IS_SACCH(lchan->type)) - return prim_dequeue_sacch(queue, lchan); - - /* There is nothing to dequeue */ - if (llist_empty(queue)) - return NULL; - - switch (lchan->type) { - /* TCH/F requires FACCH/F prioritization */ - case TRXC_TCHF: - return prim_dequeue_tch_f(queue); - - /* FACCH/H prioritization is a bit more complex */ - case TRXC_TCHH_0: - case TRXC_TCHH_1: - return prim_dequeue_tch_h(queue, fn, lchan->type); - - /* Other kinds of logical channels */ - default: - return prim_dequeue_one(queue, lchan->type); - } -} - -/** - * Drops the current primitive of specified logical channel - * - * @param lchan a logical channel to drop prim from - */ -void sched_prim_drop(struct trx_lchan_state *lchan) -{ - /* Forget this primitive */ - talloc_free(lchan->prim); - lchan->prim = NULL; -} - -/** - * Assigns a dummy primitive to a lchan depending on its type. - * Could be used when there is nothing to transmit, but - * CBTX (Continuous Burst Transmission) is assumed. - * - * @param lchan lchan to assign a primitive - * @return zero in case of success, otherwise a error code - */ -int sched_prim_dummy(struct trx_lchan_state *lchan) -{ - enum trx_lchan_type chan = lchan->type; - uint8_t tch_mode = lchan->tch_mode; - struct trx_ts_prim *prim; - uint8_t prim_buffer[40]; - size_t prim_len = 0; - int i; - - /** - * TS 144.006, section 8.4.2.3 "Fill frames" - * A fill frame is a UI command frame for SAPI 0, P=0 - * and with an information field of 0 octet length. - */ - static const uint8_t lapdm_fill_frame[] = { - 0x01, 0x03, 0x01, 0x2b, - /* Pending part is to be randomized */ - }; - - /* Make sure that there is no existing primitive */ - OSMO_ASSERT(lchan->prim == NULL); - /* Not applicable for SACCH! */ - OSMO_ASSERT(!CHAN_IS_SACCH(lchan->type)); - - /** - * Determine what actually should be generated: - * TCH in GSM48_CMODE_SIGN: LAPDm fill frame; - * TCH in other modes: silence frame; - * other channels: LAPDm fill frame. - */ - if (CHAN_IS_TCH(chan) && TCH_MODE_IS_SPEECH(tch_mode)) { - /* Bad frame indication */ - prim_len = sched_bad_frame_ind(prim_buffer, lchan); - } else if (CHAN_IS_TCH(chan) && TCH_MODE_IS_DATA(tch_mode)) { - /* FIXME: should we do anything for CSD? */ - return 0; - } else { - /* Copy LAPDm fill frame's header */ - memcpy(prim_buffer, lapdm_fill_frame, sizeof(lapdm_fill_frame)); - - /** - * TS 144.006, section 5.2 "Frame delimitation and fill bits" - * Except for the first octet containing fill bits which shall - * be set to the binary value "00101011", each fill bit should - * be set to a random value when sent by the network. - */ - for (i = sizeof(lapdm_fill_frame); i < GSM_MACBLOCK_LEN; i++) - prim_buffer[i] = (uint8_t) rand(); - - /* Define a prim length */ - prim_len = GSM_MACBLOCK_LEN; - } - - /* Nothing to allocate / assign */ - if (!prim_len) - return 0; - - /* Allocate a new primitive */ - prim = talloc_zero_size(lchan, sizeof(struct trx_ts_prim) + prim_len); - if (prim == NULL) - return -ENOMEM; - - /* Init primitive header */ - prim->payload_len = prim_len; - prim->chan = lchan->type; - - /* Fill in the payload */ - memcpy(prim->payload, prim_buffer, prim_len); - - /* Assign the current prim */ - lchan->prim = prim; - - LOGP(DSCHD, LOGL_DEBUG, "Transmitting a dummy / silence frame " - "on lchan=%s\n", trx_lchan_desc[chan].name); - - return 0; -} - -/** - * Flushes a queue of primitives - * - * @param list list of prims going to be flushed - */ -void sched_prim_flush_queue(struct llist_head *list) -{ - struct trx_ts_prim *prim, *prim_next; - - llist_for_each_entry_safe(prim, prim_next, list, list) { - llist_del(&prim->list); - talloc_free(prim); - } -} diff --git a/src/host/trxcon/sched_trx.c b/src/host/trxcon/sched_trx.c deleted file mode 100644 index 37d1acf6..00000000 --- a/src/host/trxcon/sched_trx.c +++ /dev/null @@ -1,704 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * TDMA scheduler: GSM PHY routines - * - * (C) 2017-2019 by Vadim Yanitskiy <axilirator@gmail.com> - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#include <error.h> -#include <errno.h> -#include <string.h> -#include <talloc.h> -#include <stdbool.h> - -#include <osmocom/gsm/a5.h> -#include <osmocom/gsm/protocol/gsm_08_58.h> -#include <osmocom/core/bits.h> -#include <osmocom/core/msgb.h> -#include <osmocom/core/logging.h> -#include <osmocom/core/linuxlist.h> - -#include "l1ctl_proto.h" -#include "scheduler.h" -#include "sched_trx.h" -#include "trx_if.h" -#include "logging.h" - -static void sched_frame_clck_cb(struct trx_sched *sched) -{ - struct trx_instance *trx = (struct trx_instance *) sched->data; - const struct trx_frame *frame; - struct trx_lchan_state *lchan; - trx_lchan_tx_func *handler; - enum trx_lchan_type chan; - uint8_t offset, bid; - struct trx_ts *ts; - uint32_t fn; - int i; - - /* Iterate over timeslot list */ - for (i = 0; i < TRX_TS_COUNT; i++) { - /* Timeslot is not allocated */ - ts = trx->ts_list[i]; - if (ts == NULL) - continue; - - /* Timeslot is not configured */ - if (ts->mf_layout == NULL) - continue; - - /** - * Advance frame number, giving the transceiver more - * time until a burst must be transmitted... - */ - fn = TDMA_FN_SUM(sched->fn_counter_proc, - sched->fn_counter_advance); - - /* Get frame from multiframe */ - offset = fn % ts->mf_layout->period; - frame = ts->mf_layout->frames + offset; - - /* Get required info from frame */ - bid = frame->ul_bid; - chan = frame->ul_chan; - handler = trx_lchan_desc[chan].tx_fn; - - /* Omit lchans without handler */ - if (!handler) - continue; - - /* Make sure that lchan was allocated and activated */ - lchan = sched_trx_find_lchan(ts, chan); - if (lchan == NULL) - continue; - - /* Omit inactive lchans */ - if (!lchan->active) - continue; - - /** - * If we aren't processing any primitive yet, - * attempt to obtain a new one from queue - */ - if (lchan->prim == NULL) - lchan->prim = sched_prim_dequeue(&ts->tx_prims, fn, lchan); - - /* TODO: report TX buffers health to the higher layers */ - - /* If CBTX (Continuous Burst Transmission) is assumed */ - if (trx_lchan_desc[chan].flags & TRX_CH_FLAG_CBTX) { - /** - * Probably, a TX buffer is empty. Nevertheless, - * we shall continuously transmit anything on - * CBTX channels. - */ - if (lchan->prim == NULL) - sched_prim_dummy(lchan); - } - - /* If there is no primitive, do nothing */ - if (lchan->prim == NULL) - continue; - - /* Handover RACH needs to be handled regardless of the - * current channel type and the associated handler. */ - if (PRIM_IS_RACH(lchan->prim) && lchan->prim->chan != TRXC_RACH) - handler = trx_lchan_desc[TRXC_RACH].tx_fn; - - /* Poke lchan handler */ - handler(trx, ts, lchan, fn, bid); - } -} - -int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance) -{ - struct trx_sched *sched; - - if (!trx) - return -EINVAL; - - LOGP(DSCH, LOGL_NOTICE, "Init scheduler\n"); - - /* Obtain a scheduler instance from TRX */ - sched = &trx->sched; - - /* Register frame clock callback */ - sched->clock_cb = sched_frame_clck_cb; - - /* Set pointers */ - sched = &trx->sched; - sched->data = trx; - - /* Set frame counter advance */ - sched->fn_counter_advance = fn_advance; - - return 0; -} - -int sched_trx_shutdown(struct trx_instance *trx) -{ - int i; - - if (!trx) - return -EINVAL; - - LOGP(DSCH, LOGL_NOTICE, "Shutdown scheduler\n"); - - /* Free all potentially allocated timeslots */ - for (i = 0; i < TRX_TS_COUNT; i++) - sched_trx_del_ts(trx, i); - - return 0; -} - -int sched_trx_reset(struct trx_instance *trx, bool reset_clock) -{ - int i; - - if (!trx) - return -EINVAL; - - LOGP(DSCH, LOGL_NOTICE, "Reset scheduler %s\n", - reset_clock ? "and clock counter" : ""); - - /* Free all potentially allocated timeslots */ - for (i = 0; i < TRX_TS_COUNT; i++) - sched_trx_del_ts(trx, i); - - /* Stop and reset clock counter if required */ - if (reset_clock) - sched_clck_reset(&trx->sched); - - return 0; -} - -struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn) -{ - /* Make sure that ts isn't allocated yet */ - if (trx->ts_list[tn] != NULL) { - LOGP(DSCH, LOGL_ERROR, "Timeslot #%u already allocated\n", tn); - return NULL; - } - - LOGP(DSCH, LOGL_NOTICE, "Add a new TDMA timeslot #%u\n", tn); - - /* Allocate a new one */ - trx->ts_list[tn] = talloc_zero(trx, struct trx_ts); - - /* Assign TS index */ - trx->ts_list[tn]->index = tn; - - return trx->ts_list[tn]; -} - -void sched_trx_del_ts(struct trx_instance *trx, int tn) -{ - struct trx_lchan_state *lchan, *lchan_next; - struct trx_ts *ts; - - /* Find ts in list */ - ts = trx->ts_list[tn]; - if (ts == NULL) - return; - - LOGP(DSCH, LOGL_NOTICE, "Delete TDMA timeslot #%u\n", tn); - - /* Deactivate all logical channels */ - sched_trx_deactivate_all_lchans(ts); - - /* Free channel states */ - llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) { - llist_del(&lchan->list); - talloc_free(lchan); - } - - /* Flush queue primitives for TX */ - sched_prim_flush_queue(&ts->tx_prims); - - /* Remove ts from list and free memory */ - trx->ts_list[tn] = NULL; - talloc_free(ts); - - /* Notify transceiver about that */ - trx_if_cmd_setslot(trx, tn, 0); -} - -#define LAYOUT_HAS_LCHAN(layout, lchan) \ - (layout->lchan_mask & ((uint64_t) 0x01 << lchan)) - -int sched_trx_configure_ts(struct trx_instance *trx, int tn, - enum gsm_phys_chan_config config) -{ - struct trx_lchan_state *lchan; - enum trx_lchan_type type; - struct trx_ts *ts; - - /* Try to find specified ts */ - ts = trx->ts_list[tn]; - if (ts != NULL) { - /* Reconfiguration of existing one */ - sched_trx_reset_ts(trx, tn); - } else { - /* Allocate a new one if doesn't exist */ - ts = sched_trx_add_ts(trx, tn); - if (ts == NULL) - return -ENOMEM; - } - - /* Choose proper multiframe layout */ - ts->mf_layout = sched_mframe_layout(config, tn); - if (ts->mf_layout->chan_config != config) - return -EINVAL; - - LOGP(DSCH, LOGL_NOTICE, "(Re)configure TDMA timeslot #%u as %s\n", - tn, ts->mf_layout->name); - - /* Init queue primitives for TX */ - INIT_LLIST_HEAD(&ts->tx_prims); - /* Init logical channels list */ - INIT_LLIST_HEAD(&ts->lchans); - - /* Allocate channel states */ - for (type = 0; type < _TRX_CHAN_MAX; type++) { - if (!LAYOUT_HAS_LCHAN(ts->mf_layout, type)) - continue; - - /* Allocate a channel state */ - lchan = talloc_zero(ts, struct trx_lchan_state); - if (!lchan) - return -ENOMEM; - - /* Set channel type */ - lchan->type = type; - - /* Add to the list of channel states */ - llist_add_tail(&lchan->list, &ts->lchans); - - /* Enable channel automatically if required */ - if (trx_lchan_desc[type].flags & TRX_CH_FLAG_AUTO) - sched_trx_activate_lchan(ts, type); - } - - /* Notify transceiver about TS activation */ - /* FIXME: set proper channel type */ - trx_if_cmd_setslot(trx, tn, 1); - - return 0; -} - -int sched_trx_reset_ts(struct trx_instance *trx, int tn) -{ - struct trx_lchan_state *lchan, *lchan_next; - struct trx_ts *ts; - - /* Try to find specified ts */ - ts = trx->ts_list[tn]; - if (ts == NULL) - return -EINVAL; - - /* Flush TS frame counter */ - ts->mf_last_fn = 0; - - /* Undefine multiframe layout */ - ts->mf_layout = NULL; - - /* Flush queue primitives for TX */ - sched_prim_flush_queue(&ts->tx_prims); - - /* Deactivate all logical channels */ - sched_trx_deactivate_all_lchans(ts); - - /* Free channel states */ - llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) { - llist_del(&lchan->list); - talloc_free(lchan); - } - - /* Notify transceiver about that */ - trx_if_cmd_setslot(trx, tn, 0); - - return 0; -} - -int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo, - uint8_t *key, uint8_t key_len) -{ - struct trx_lchan_state *lchan; - - /* Prevent NULL-pointer deference */ - if (!ts) - return -EINVAL; - - /* Make sure we can store this key */ - if (key_len > MAX_A5_KEY_LEN) - return -ERANGE; - - /* Iterate over all allocated logical channels */ - llist_for_each_entry(lchan, &ts->lchans, list) { - /* Omit inactive channels */ - if (!lchan->active) - continue; - - /* Set key length and algorithm */ - lchan->a5.key_len = key_len; - lchan->a5.algo = algo; - - /* Copy requested key */ - if (key_len) - memcpy(lchan->a5.key, key, key_len); - } - - return 0; -} - -struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts, - enum trx_lchan_type chan) -{ - struct trx_lchan_state *lchan; - - llist_for_each_entry(lchan, &ts->lchans, list) - if (lchan->type == chan) - return lchan; - - return NULL; -} - -int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode) -{ - const struct trx_lchan_desc *lchan_desc; - struct trx_lchan_state *lchan; - int rc = 0; - - /* Prevent NULL-pointer deference */ - if (ts == NULL) { - LOGP(DSCH, LOGL_ERROR, "Timeslot isn't configured\n"); - return -EINVAL; - } - - /* Iterate over all allocated lchans */ - llist_for_each_entry(lchan, &ts->lchans, list) { - lchan_desc = &trx_lchan_desc[lchan->type]; - - if (lchan_desc->chan_nr == (chan_nr & 0xf8)) { - if (active) { - rc |= sched_trx_activate_lchan(ts, lchan->type); - lchan->tch_mode = tch_mode; - } else - rc |= sched_trx_deactivate_lchan(ts, lchan->type); - } - } - - return rc; -} - -int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan) -{ - const struct trx_lchan_desc *lchan_desc = &trx_lchan_desc[chan]; - struct trx_lchan_state *lchan; - - /* Try to find requested logical channel */ - lchan = sched_trx_find_lchan(ts, chan); - if (lchan == NULL) - return -EINVAL; - - if (lchan->active) { - LOGP(DSCH, LOGL_ERROR, "Logical channel %s already activated " - "on ts=%d\n", trx_lchan_desc[chan].name, ts->index); - return -EINVAL; - } - - LOGP(DSCH, LOGL_NOTICE, "Activating lchan=%s " - "on ts=%d\n", trx_lchan_desc[chan].name, ts->index); - - /* Conditionally allocate memory for bursts */ - if (lchan_desc->rx_fn && lchan_desc->burst_buf_size > 0) { - lchan->rx_bursts = talloc_zero_size(lchan, - lchan_desc->burst_buf_size); - if (lchan->rx_bursts == NULL) - return -ENOMEM; - } - - if (lchan_desc->tx_fn && lchan_desc->burst_buf_size > 0) { - lchan->tx_bursts = talloc_zero_size(lchan, - lchan_desc->burst_buf_size); - if (lchan->tx_bursts == NULL) - return -ENOMEM; - } - - /* Finally, update channel status */ - lchan->active = 1; - - return 0; -} - -static void sched_trx_reset_lchan(struct trx_lchan_state *lchan) -{ - /* Prevent NULL-pointer deference */ - OSMO_ASSERT(lchan != NULL); - - /* Reset internal state variables */ - lchan->rx_burst_mask = 0x00; - lchan->tx_burst_mask = 0x00; - lchan->rx_first_fn = 0; - - /* Free burst memory */ - talloc_free(lchan->rx_bursts); - talloc_free(lchan->tx_bursts); - - lchan->rx_bursts = NULL; - lchan->tx_bursts = NULL; - - /* Forget the current prim */ - sched_prim_drop(lchan); - - /* Channel specific stuff */ - if (CHAN_IS_TCH(lchan->type)) { - lchan->dl_ongoing_facch = 0; - lchan->ul_facch_blocks = 0; - - lchan->tch_mode = GSM48_CMODE_SIGN; - - /* Reset AMR state */ - memset(&lchan->amr, 0x00, sizeof(lchan->amr)); - } else if (CHAN_IS_SACCH(lchan->type)) { - /* Reset SACCH state */ - memset(&lchan->sacch, 0x00, sizeof(lchan->sacch)); - } - - /* Reset ciphering state */ - memset(&lchan->a5, 0x00, sizeof(lchan->a5)); -} - -int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan) -{ - struct trx_lchan_state *lchan; - - /* Try to find requested logical channel */ - lchan = sched_trx_find_lchan(ts, chan); - if (lchan == NULL) - return -EINVAL; - - if (!lchan->active) { - LOGP(DSCH, LOGL_ERROR, "Logical channel %s already deactivated " - "on ts=%d\n", trx_lchan_desc[chan].name, ts->index); - return -EINVAL; - } - - LOGP(DSCH, LOGL_DEBUG, "Deactivating lchan=%s " - "on ts=%d\n", trx_lchan_desc[chan].name, ts->index); - - /* Reset internal state, free memory */ - sched_trx_reset_lchan(lchan); - - /* Update activation flag */ - lchan->active = 0; - - return 0; -} - -void sched_trx_deactivate_all_lchans(struct trx_ts *ts) -{ - struct trx_lchan_state *lchan; - - LOGP(DSCH, LOGL_DEBUG, "Deactivating all logical channels " - "on ts=%d\n", ts->index); - - llist_for_each_entry(lchan, &ts->lchans, list) { - /* Omit inactive channels */ - if (!lchan->active) - continue; - - /* Reset internal state, free memory */ - sched_trx_reset_lchan(lchan); - - /* Update activation flag */ - lchan->active = 0; - } -} - -enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr) -{ - uint8_t cbits = chan_nr >> 3; - - if (cbits == ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs) - return GSM_PCHAN_TCH_F; - else if ((cbits & 0x1e) == ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0)) - return GSM_PCHAN_TCH_H; - else if ((cbits & 0x1c) == ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0)) - return GSM_PCHAN_CCCH_SDCCH4; - else if ((cbits & 0x18) == ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0)) - return GSM_PCHAN_SDCCH8_SACCH8C; - else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH4) - return GSM_PCHAN_CCCH_SDCCH4_CBCH; - else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH8) - return GSM_PCHAN_SDCCH8_SACCH8C_CBCH; - else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH) - return GSM_PCHAN_PDCH; - - return GSM_PCHAN_NONE; -} - -enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr, - uint8_t link_id) -{ - int i; - - /* Iterate over all known lchan types */ - for (i = 0; i < _TRX_CHAN_MAX; i++) - if (trx_lchan_desc[i].chan_nr == (chan_nr & 0xf8)) - if (trx_lchan_desc[i].link_id == link_id) - return i; - - return TRXC_IDLE; -} - -static void sched_trx_a5_burst_dec(struct trx_lchan_state *lchan, - uint32_t fn, sbit_t *burst) -{ - ubit_t ks[114]; - int i; - - /* Generate keystream for a DL burst */ - osmo_a5(lchan->a5.algo, lchan->a5.key, fn, ks, NULL); - - /* Apply keystream over ciphertext */ - for (i = 0; i < 57; i++) { - if (ks[i]) - burst[i + 3] *= -1; - if (ks[i + 57]) - burst[i + 88] *= -1; - } -} - -static void sched_trx_a5_burst_enc(struct trx_lchan_state *lchan, - uint32_t fn, ubit_t *burst) -{ - ubit_t ks[114]; - int i; - - /* Generate keystream for an UL burst */ - osmo_a5(lchan->a5.algo, lchan->a5.key, fn, NULL, ks); - - /* Apply keystream over plaintext */ - for (i = 0; i < 57; i++) { - burst[i + 3] ^= ks[i]; - burst[i + 88] ^= ks[i + 57]; - } -} - -int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn, - uint32_t burst_fn, sbit_t *bits, uint16_t nbits, - int8_t rssi, int16_t toa256) -{ - struct trx_lchan_state *lchan; - const struct trx_frame *frame; - struct trx_ts *ts; - - trx_lchan_rx_func *handler; - enum trx_lchan_type chan; - uint32_t fn, elapsed; - uint8_t offset, bid; - - /* Check whether required timeslot is allocated and configured */ - ts = trx->ts_list[tn]; - if (ts == NULL || ts->mf_layout == NULL) { - LOGP(DSCHD, LOGL_DEBUG, "TDMA timeslot #%u isn't configured, " - "ignoring burst...\n", tn); - return -EINVAL; - } - - /* Calculate how many frames have been elapsed */ - elapsed = TDMA_FN_SUB(burst_fn, ts->mf_last_fn); - - /** - * If not too many frames have been elapsed, - * start counting from last fn + 1 - */ - if (elapsed < 10) - fn = TDMA_FN_INC(ts->mf_last_fn); - else - fn = burst_fn; - - while (1) { - /* Get frame from multiframe */ - offset = fn % ts->mf_layout->period; - frame = ts->mf_layout->frames + offset; - - /* Get required info from frame */ - bid = frame->dl_bid; - chan = frame->dl_chan; - handler = trx_lchan_desc[chan].rx_fn; - - /* Omit bursts which have no handler, like IDLE bursts */ - if (!handler) - goto next_frame; - - /* Find required channel state */ - lchan = sched_trx_find_lchan(ts, chan); - if (lchan == NULL) - goto next_frame; - - /* Ensure that channel is active */ - if (!lchan->active) - goto next_frame; - - /* Reached current fn */ - if (fn == burst_fn) { - /* Perform A5/X decryption if required */ - if (lchan->a5.algo) - sched_trx_a5_burst_dec(lchan, fn, bits); - - /* Put burst to handler */ - handler(trx, ts, lchan, fn, bid, bits, rssi, toa256); - } - -next_frame: - /* Reached current fn */ - if (fn == burst_fn) - break; - - fn = TDMA_FN_INC(fn); - } - - /* Set last processed frame number */ - ts->mf_last_fn = fn; - - 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; -} diff --git a/src/host/trxcon/sched_trx.h b/src/host/trxcon/sched_trx.h deleted file mode 100644 index 6ef9ce47..00000000 --- a/src/host/trxcon/sched_trx.h +++ /dev/null @@ -1,365 +0,0 @@ -#pragma once - -#include <stdint.h> -#include <stdbool.h> - -#include <osmocom/core/bits.h> -#include <osmocom/core/utils.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 - -/* 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_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, sbit_t *bits, - int8_t rssi, int16_t toa256); - -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 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; -}; - -/* 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 Frame number of first burst */ - uint32_t rx_first_fn; - /*! \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; - - struct { - /*! \brief Number of measurements */ - unsigned int num; - /*! \brief Sum of RSSI values */ - float rssi_sum; - /*! \brief Sum of TOA values */ - int32_t toa256_sum; - } meas; - - /*! \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; -}; - -struct trx_ts { - /*! \brief Timeslot index within a frame (0..7) */ - uint8_t index; - /*! \brief Last received frame number */ - uint32_t mf_last_fn; - - /*! \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; -}; - -/* 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 burst_fn, sbit_t *bits, uint16_t nbits, - int8_t rssi, int16_t toa256); -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]; - -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); - -/* 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) diff --git a/src/host/trxcon/scheduler.h b/src/host/trxcon/scheduler.h deleted file mode 100644 index 7ab17ab5..00000000 --- a/src/host/trxcon/scheduler.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include <stdint.h> -#include <time.h> - -#include <osmocom/core/timer.h> - -#define FRAME_DURATION_uS 4615 - -#define GSM_SUPERFRAME (26 * 51) -#define GSM_HYPERFRAME (2048 * GSM_SUPERFRAME) - -/* TDMA frame number arithmetics */ -#define TDMA_FN_SUM(a, b) \ - ((a + b) % GSM_HYPERFRAME) -#define TDMA_FN_SUB(a, b) \ - ((a + GSM_HYPERFRAME - b) % GSM_HYPERFRAME) -#define TDMA_FN_INC(fn) \ - TDMA_FN_SUM(fn, 1) -#define TDMA_FN_MIN(a, b) \ - (a < b ? a : b) -#define TDMA_FN_DIFF(a, b) \ - TDMA_FN_MIN(TDMA_FN_SUB(a, b), TDMA_FN_SUB(b, a)) - -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 */ - uint8_t 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 Frame counter */ - 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); diff --git a/src/host/trxcon/src/Makefile.am b/src/host/trxcon/src/Makefile.am new file mode 100644 index 00000000..7be7de62 --- /dev/null +++ b/src/host/trxcon/src/Makefile.am @@ -0,0 +1,64 @@ +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_prim.c \ + sched_trx.c \ + $(NULL) + + +noinst_LTLIBRARIES += libl1gprs.la + +libl1gprs_la_SOURCES = \ + l1gprs.c \ + $(NULL) + + +noinst_LTLIBRARIES += libtrxcon.la + +libtrxcon_la_SOURCES = \ + trxcon_inst.c \ + trxcon_fsm.c \ + trxcon_shim.c \ + l1ctl.c \ + $(NULL) + + +bin_PROGRAMS = trxcon + +trxcon_SOURCES = \ + l1ctl_server.c \ + trxcon_main.c \ + logging.c \ + trx_if.c \ + $(NULL) + +trxcon_LDADD = \ + libtrxcon.la \ + libl1sched.la \ + libl1gprs.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCODING_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) diff --git a/src/host/trxcon/src/l1ctl.c b/src/host/trxcon/src/l1ctl.c new file mode 100644 index 00000000..0f6bd1be --- /dev/null +++ b/src/host/trxcon/src/l1ctl.c @@ -0,0 +1,849 @@ +/* + * 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 <arpa/inet.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +#include <osmocom/bb/l1ctl_proto.h> +#include <osmocom/bb/trxcon/logging.h> +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> + +#define L1CTL_LENGTH 512 +#define L1CTL_HEADROOM 32 + +/* Logging categories configurable via trxcon_set_log_cfg() */ +int g_logc_l1c = DLGLOBAL; +int g_logc_l1d = DLGLOBAL; + +static const struct value_string l1ctl_ccch_mode_names[] = { + { CCCH_MODE_NONE, "NONE" }, + { CCCH_MODE_NON_COMBINED, "NON_COMBINED" }, + { CCCH_MODE_COMBINED, "COMBINED" }, + { CCCH_MODE_COMBINED_CBCH, "COMBINED_CBCH" }, + { 0, NULL }, +}; + +static const struct value_string l1ctl_reset_names[] = { + { L1CTL_RES_T_BOOT, "BOOT" }, + { L1CTL_RES_T_FULL, "FULL" }, + { L1CTL_RES_T_SCHED, "SCHED" }, + { 0, NULL }, +}; + +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_HEADROOM, + L1CTL_HEADROOM, "l1ctl_tx_msg"); + if (!msg) { + LOGP(g_logc_l1c, 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 trxcon_inst *trxcon, uint16_t band_arfcn, int dbm, int last) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + struct l1ctl_pm_conf *pmc; + struct msgb *msg; + + msg = l1ctl_alloc_msg(L1CTL_PM_CONF); + if (!msg) + return -ENOMEM; + + LOGPFSMSL(fi, g_logc_l1c, 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 trxcon_l1ctl_send(trxcon, msg); +} + +int l1ctl_tx_reset_ind(struct trxcon_inst *trxcon, uint8_t type) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + struct msgb *msg; + struct l1ctl_reset *res; + + msg = l1ctl_alloc_msg(L1CTL_RESET_IND); + if (!msg) + return -ENOMEM; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_DEBUG, "Send Reset Ind (%s)\n", + get_value_string(l1ctl_reset_names, type)); + + res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res)); + res->type = type; + + return trxcon_l1ctl_send(trxcon, msg); +} + +int l1ctl_tx_reset_conf(struct trxcon_inst *trxcon, uint8_t type) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + struct msgb *msg; + struct l1ctl_reset *res; + + msg = l1ctl_alloc_msg(L1CTL_RESET_CONF); + if (!msg) + return -ENOMEM; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_DEBUG, "Send Reset Conf (%s)\n", + get_value_string(l1ctl_reset_names, type)); + res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res)); + res->type = type; + + return trxcon_l1ctl_send(trxcon, 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 trxcon_inst *trxcon, uint16_t band_arfcn) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + 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, g_logc_l1c, LOGL_DEBUG, "Send FBSB Conf (timeout)\n"); + + return trxcon_l1ctl_send(trxcon, msg); +} + +int l1ctl_tx_fbsb_conf(struct trxcon_inst *trxcon, uint16_t band_arfcn, uint8_t bsic) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + 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, g_logc_l1c, LOGL_DEBUG, + "Send FBSB Conf (result=%u, bsic=%u)\n", + conf->result, conf->bsic); + + return trxcon_l1ctl_send(trxcon, msg); +} + +int l1ctl_tx_ccch_mode_conf(struct trxcon_inst *trxcon, 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 trxcon_l1ctl_send(trxcon, msg); +} + +/** + * Handles both L1CTL_DATA_IND and L1CTL_TRAFFIC_IND. + */ +int l1ctl_tx_dt_ind(struct trxcon_inst *trxcon, + const struct trxcon_param_rx_data_ind *ind) +{ + struct msgb *msg; + + msg = l1ctl_alloc_msg(ind->traffic ? L1CTL_TRAFFIC_IND : L1CTL_DATA_IND); + if (msg == NULL) + return -ENOMEM; + + 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(ind->band_arfcn), + .fire_crc = ind->data_len > 0 ? 0 : 2, + .rx_level = dbm2rxlev(ind->rssi), + .num_biterr = ind->n_errors, + /* TODO: set proper .snr */ + }; + + put_dl_info_hdr(msg, &dl_hdr); + + /* Copy the L2 payload if preset */ + if (ind->data && ind->data_len > 0) + memcpy(msgb_put(msg, ind->data_len), ind->data, ind->data_len); + + /* Put message to upper layers */ + return trxcon_l1ctl_send(trxcon, msg); +} + +int l1ctl_tx_rach_conf(struct trxcon_inst *trxcon, + const struct trxcon_param_tx_access_burst_cnf *cnf) +{ + struct msgb *msg; + + msg = l1ctl_alloc_msg(L1CTL_RACH_CONF); + if (msg == NULL) + return -ENOMEM; + + const struct l1ctl_info_dl dl_hdr = { + .band_arfcn = htons(cnf->band_arfcn), + .frame_nr = htonl(cnf->frame_nr), + }; + + put_dl_info_hdr(msg, &dl_hdr); + + return trxcon_l1ctl_send(trxcon, msg); +} + + +/** + * Handles both L1CTL_DATA_CONF and L1CTL_TRAFFIC_CONF. + */ +int l1ctl_tx_dt_conf(struct trxcon_inst *trxcon, + const struct trxcon_param_tx_data_cnf *cnf) +{ + struct msgb *msg; + + msg = l1ctl_alloc_msg(cnf->traffic ? L1CTL_TRAFFIC_CONF : L1CTL_DATA_CONF); + if (msg == NULL) + return -ENOMEM; + + const struct l1ctl_info_dl dl_hdr = { + .chan_nr = cnf->chan_nr, + .link_id = cnf->link_id, + .frame_nr = htonl(cnf->frame_nr), + .band_arfcn = htons(cnf->band_arfcn), + }; + + /* Copy DL frame header from source message */ + put_dl_info_hdr(msg, &dl_hdr); + + return trxcon_l1ctl_send(trxcon, 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(g_logc_l1c, LOGL_NOTICE, "Undandled CCCH mode (%u), " + "assuming non-combined configuration\n", mode); + return GSM_PCHAN_CCCH; + } +} + +static int l1ctl_rx_fbsb_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_fbsb_req *fbsb; + int rc = 0; + + fbsb = (const struct l1ctl_fbsb_req *)msg->l1h; + if (msgb_l1len(msg) < sizeof(*fbsb)) { + LOGPFSMSL(fi, g_logc_l1c, 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_fns = ntohs(fbsb->timeout), + .band_arfcn = ntohs(fbsb->band_arfcn), + }; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "Received FBSB request (%s %d, timeout %u TDMA FNs)\n", + arfcn2band_name(req.band_arfcn), + req.band_arfcn & ~ARFCN_FLAG_MASK, + req.timeout_fns); + + osmo_fsm_inst_dispatch(fi, TRXCON_EV_FBSB_SEARCH_REQ, &req); + +exit: + msgb_free(msg); + return rc; +} + +static int l1ctl_rx_pm_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_pm_req *pmr; + int rc = 0; + + pmr = (const struct l1ctl_pm_req *)msg->l1h; + if (msgb_l1len(msg) < sizeof(*pmr)) { + LOGPFSMSL(fi, g_logc_l1c, 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, g_logc_l1c, 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 trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_reset *res; + int rc = 0; + + res = (const struct l1ctl_reset *)msg->l1h; + if (msgb_l1len(msg) < sizeof(*res)) { + LOGPFSMSL(fi, g_logc_l1c, LOGL_ERROR, + "MSG too short Reset Req: %u\n", + msgb_l1len(msg)); + rc = -EINVAL; + goto exit; + } + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "Received reset request (%s)\n", + get_value_string(l1ctl_reset_names, 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, g_logc_l1c, LOGL_ERROR, + "Unknown L1CTL_RESET_REQ type\n"); + goto exit; + } + + /* Confirm */ + rc = l1ctl_tx_reset_conf(trxcon, res->type); + +exit: + msgb_free(msg); + return rc; +} + +static int l1ctl_rx_echo_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + struct l1ctl_hdr *l1h; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, "Recv Echo Req\n"); + LOGPFSMSL(fi, g_logc_l1c, 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 trxcon_l1ctl_send(trxcon, msg); +} + +static int l1ctl_rx_ccch_mode_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_ccch_mode_req *mode_req; + int rc; + + mode_req = (const struct l1ctl_ccch_mode_req *)msg->l1h; + if (msgb_l1len(msg) < sizeof(*mode_req)) { + LOGPFSMSL(fi, g_logc_l1c, LOGL_ERROR, + "MSG too short Reset Req: %u\n", + msgb_l1len(msg)); + rc = -EINVAL; + goto exit; + } + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, "Received CCCH mode request (%s)\n", + get_value_string(l1ctl_ccch_mode_names, mode_req->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(trxcon, mode_req->ccch_mode); + +exit: + msgb_free(msg); + return rc; +} + +static int l1ctl_rx_rach_req(struct trxcon_inst *trxcon, struct msgb *msg, bool is_11bit) +{ + struct trxcon_param_tx_access_burst_req req; + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_info_ul *ul; + + ul = (const 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, g_logc_l1c, 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, g_logc_l1c, 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, g_logc_l1c, 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_dch_est_req *req, + const struct l1ctl_h0 *h) +{ + req->h0.band_arfcn = ntohs(h->band_arfcn); + + LOGPFSMSL(fi, g_logc_l1c, 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_dch_est_req *req, + const struct l1ctl_h1 *h) +{ + unsigned int i; + + LOGPFSMSL(fi, g_logc_l1c, 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, g_logc_l1c, LOGL_ERROR, + "No channels in mobile allocation?!?\n"); + return -EINVAL; + } else if (h->n > ARRAY_SIZE(h->ma)) { + LOGPFSMSL(fi, g_logc_l1c, 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 trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_dm_est_req *est_req; + const struct l1ctl_info_ul *ul; + int rc; + + ul = (const struct l1ctl_info_ul *)msg->l1h; + est_req = (const struct l1ctl_dm_est_req *)ul->payload; + + struct trxcon_param_dch_est_req req = { + .chan_nr = ul->chan_nr, + .tch_mode = est_req->tch_mode, + .tsc = est_req->tsc, + .hopping = est_req->h, + }; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "Received L1CTL_DM_EST_REQ " + "(tn=%u, chan_nr=0x%02x, tsc=%u, tch_mode=%s)\n", + req.chan_nr & 0x07, req.chan_nr, req.tsc, + gsm48_chan_mode_name(est_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_DCH_EST_REQ, &req); + +exit: + msgb_free(msg); + return rc; +} + +static int l1ctl_rx_dm_rel_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, "Received L1CTL_DM_REL_REQ\n"); + + osmo_fsm_inst_dispatch(fi, TRXCON_EV_DCH_REL_REQ, NULL); + + msgb_free(msg); + return 0; +} + +/** + * Handles both L1CTL_DATA_REQ and L1CTL_TRAFFIC_REQ. + */ +static int l1ctl_rx_dt_req(struct trxcon_inst *trxcon, struct msgb *msg, bool traffic) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_info_ul *ul; + + /* Extract UL frame header */ + ul = (const struct l1ctl_info_ul *)msg->l1h; + msg->l2h = (uint8_t *)ul->payload; + + struct trxcon_param_tx_data_req req = { + .traffic = traffic, + .chan_nr = ul->chan_nr, + .link_id = ul->link_id & 0x40, + .data_len = msgb_l2len(msg), + .data = ul->payload, + }; + + LOGPFSMSL(fi, g_logc_l1d, 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: + 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 trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_par_req *par_req; + const struct l1ctl_info_ul *ul; + + ul = (const struct l1ctl_info_ul *)msg->l1h; + par_req = (const struct l1ctl_par_req *)ul->payload; + + LOGPFSMSL(fi, g_logc_l1c, 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 trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_tch_mode_req *mode_req; + int rc; + + mode_req = (const struct l1ctl_tch_mode_req *)msg->l1h; + + LOGPFSMSL(fi, g_logc_l1c, LOGL_NOTICE, + "Received L1CTL_TCH_MODE_REQ (tch_mode=%s, audio_mode=%u)\n", + gsm48_chan_mode_name(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 trxcon_l1ctl_send(trxcon, msg); +} + +static int l1ctl_rx_crypto_req(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct osmo_fsm_inst *fi = trxcon->fi; + const struct l1ctl_crypto_req *cr; + const struct l1ctl_info_ul *ul; + + ul = (const struct l1ctl_info_ul *)msg->l1h; + cr = (const 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, g_logc_l1c, 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 trxcon_l1ctl_receive(struct trxcon_inst *trxcon, struct msgb *msg) +{ + const struct l1ctl_hdr *l1h; + int rc; + + l1h = (const struct l1ctl_hdr *)msg->l1h; + msg->l1h = (uint8_t *)l1h->data; + + switch (l1h->msg_type) { + case L1CTL_FBSB_REQ: + return l1ctl_rx_fbsb_req(trxcon, msg); + case L1CTL_PM_REQ: + return l1ctl_rx_pm_req(trxcon, msg); + case L1CTL_RESET_REQ: + return l1ctl_rx_reset_req(trxcon, msg); + case L1CTL_ECHO_REQ: + return l1ctl_rx_echo_req(trxcon, msg); + case L1CTL_CCCH_MODE_REQ: + return l1ctl_rx_ccch_mode_req(trxcon, msg); + case L1CTL_RACH_REQ: + return l1ctl_rx_rach_req(trxcon, msg, false); + case L1CTL_EXT_RACH_REQ: + return l1ctl_rx_rach_req(trxcon, msg, true); + case L1CTL_DM_EST_REQ: + return l1ctl_rx_dm_est_req(trxcon, msg); + case L1CTL_DM_REL_REQ: + return l1ctl_rx_dm_rel_req(trxcon, msg); + case L1CTL_DATA_REQ: + return l1ctl_rx_dt_req(trxcon, msg, false); + case L1CTL_TRAFFIC_REQ: + return l1ctl_rx_dt_req(trxcon, msg, true); + case L1CTL_PARAM_REQ: + return l1ctl_rx_param_req(trxcon, msg); + case L1CTL_TCH_MODE_REQ: + return l1ctl_rx_tch_mode_req(trxcon, msg); + case L1CTL_CRYPTO_REQ: + return l1ctl_rx_crypto_req(trxcon, msg); + case L1CTL_GPRS_UL_TBF_CFG_REQ: + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_GPRS_UL_TBF_CFG_REQ, msg); + msgb_free(msg); + return rc; + case L1CTL_GPRS_DL_TBF_CFG_REQ: + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_GPRS_DL_TBF_CFG_REQ, msg); + msgb_free(msg); + return rc; + case L1CTL_GPRS_UL_BLOCK_REQ: + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_GPRS_UL_BLOCK_REQ, msg); + msgb_free(msg); + return rc; + /* Not (yet) handled messages */ + case L1CTL_NEIGH_PM_REQ: + case L1CTL_DM_FREQ_REQ: + case L1CTL_SIM_REQ: + LOGPFSMSL(trxcon->fi, g_logc_l1c, LOGL_NOTICE, + "Ignoring unsupported message (type=%u)\n", + l1h->msg_type); + msgb_free(msg); + return -ENOTSUP; + default: + LOGPFSMSL(trxcon->fi, g_logc_l1c, LOGL_ERROR, "Unknown MSG type %u: %s\n", + l1h->msg_type, osmo_hexdump(msgb_data(msg), msgb_length(msg))); + msgb_free(msg); + return -EINVAL; + } +} diff --git a/src/host/trxcon/src/l1ctl_server.c b/src/host/trxcon/src/l1ctl_server.c new file mode 100644 index 00000000..c0f10158 --- /dev/null +++ b/src/host/trxcon/src/l1ctl_server.c @@ -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 -EBADF; /* client fd is gone, avoid processing any other events. */ + } + + /* 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); +} diff --git a/src/host/trxcon/src/l1gprs.c b/src/host/trxcon/src/l1gprs.c new file mode 120000 index 00000000..0185f68b --- /dev/null +++ b/src/host/trxcon/src/l1gprs.c @@ -0,0 +1 @@ +../../../shared/l1gprs.c
\ No newline at end of file diff --git a/src/host/trxcon/logging.c b/src/host/trxcon/src/logging.c index 78915f21..e8730450 100644 --- a/src/host/trxcon/logging.c +++ b/src/host/trxcon/src/logging.c @@ -15,19 +15,16 @@ * 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/trxcon.h> +#include <osmocom/bb/trxcon/logging.h> -static struct log_info_cat trx_log_info_cat[] = { +static struct log_info_cat trxcon_log_info_cat[] = { [DAPP] = { .name = "DAPP", .description = "Application", @@ -46,8 +43,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, @@ -70,19 +67,37 @@ static struct log_info_cat trx_log_info_cat[] = { .color = "\033[1;36m", .enabled = 1, .loglevel = LOGL_NOTICE, }, + [DGPRS] = { + .name = "DGPRS", + .description = "L1 GPRS (MAC layer)", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, }; -static const struct log_info trx_log_info = { - .cat = trx_log_info_cat, - .num_cat = ARRAY_SIZE(trx_log_info_cat), +static const struct log_info trxcon_log_info = { + .cat = trxcon_log_info_cat, + .num_cat = ARRAY_SIZE(trxcon_log_info_cat), }; -int trx_log_init(void *tall_ctx, const char *category_mask) +static const int trxcon_log_cfg[] = { + [TRXCON_LOGC_FSM] = DAPP, + [TRXCON_LOGC_L1C] = DL1C, + [TRXCON_LOGC_L1D] = DL1D, + [TRXCON_LOGC_SCHC] = DSCH, + [TRXCON_LOGC_SCHD] = DSCHD, + [TRXCON_LOGC_GPRS] = DGPRS, +}; + +int trxcon_logging_init(void *tall_ctx, const char *category_mask) { - osmo_init_logging2(tall_ctx, &trx_log_info); + osmo_init_logging2(tall_ctx, &trxcon_log_info); + log_target_file_switch_to_wqueue(osmo_stderr_target); if (category_mask) log_parse_category_mask(osmo_stderr_target, category_mask); + trxcon_set_log_cfg(&trxcon_log_cfg[0], ARRAY_SIZE(trxcon_log_cfg)); + return 0; } diff --git a/src/host/trxcon/src/sched_lchan_common.c b/src/host/trxcon/src/sched_lchan_common.c new file mode 100644 index 00000000..2b1729ae --- /dev/null +++ b/src/host/trxcon/src/sched_lchan_common.c @@ -0,0 +1,137 @@ +/* + * 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 uint32_t *mask, int bits) +{ + static char buf[32 + 1]; + char *ptr = buf; + + OSMO_ASSERT(bits <= 32 && bits > 0); + + while (--bits >= 0) + *(ptr++) = (*mask & (1 << bits)) ? '*' : '.'; + *ptr = '\0'; + + return buf; +} + +bool l1sched_lchan_amr_prim_is_valid(struct l1sched_lchan_state *lchan, + struct msgb *msg, bool is_cmr) +{ + enum osmo_amr_type ft_codec; + uint8_t cmr_codec; + int ft, cmr, len; + + len = osmo_amr_rtp_dec(msgb_l2(msg), msgb_l2len(msg), + &cmr_codec, NULL, &ft_codec, NULL, NULL); + if (len < 0) { + LOGP_LCHAND(lchan, LOGL_ERROR, "Cannot send invalid AMR payload (%u): %s\n", + msgb_l2len(msg), msgb_hexdump_l2(msg)); + return false; + } + ft = -1; + cmr = -1; + for (unsigned int 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); + return false; + } + if (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); + return false; + } + 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; + } + + return true; +} diff --git a/src/host/trxcon/sched_lchan_desc.c b/src/host/trxcon/src/sched_lchan_desc.c index 667a88d6..db5446e3 100644 --- a/src/host/trxcon/sched_lchan_desc.c +++ b/src/host/trxcon/src/sched_lchan_desc.c @@ -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 * @@ -24,66 +25,62 @@ */ #include <osmocom/gsm/protocol/gsm_08_58.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, - sbit_t *bits, int8_t rssi, int16_t toa256); +int rx_data_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi); -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, - sbit_t *bits, int8_t rssi, int16_t toa256); +int rx_sch_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi); -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, - sbit_t *bits, int8_t rssi, int16_t toa256); +int rx_tchf_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi); -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, - sbit_t *bits, int8_t rssi, int16_t toa256); +int rx_tchh_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi); -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, - sbit_t *bits, int8_t rssi, int16_t toa256); +int rx_pdtch_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi); -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", .chan_nr = RSL_CHAN_BCCH, @@ -91,20 +88,20 @@ const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = { /* Rx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4), * regular interleaving (3GPP TS 05.02, clause 7, table 3): * a L2 frame is interleaved over 4 consecutive bursts. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_AUTO, + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, + .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", .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", .chan_nr = RSL_CHAN_PCH_AGCH, @@ -112,15 +109,15 @@ const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = { /* Rx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4), * regular interleaving (3GPP TS 05.02, clause 7, table 3): * a L2 frame is interleaved over 4 consecutive bursts. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_AUTO, + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, + .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", .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): @@ -133,21 +130,23 @@ 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, + .burst_buf_size = 24 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3), - .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): * - * - a traffic frame is interleaved over 6 consecutive bursts + * - a traffic frame is interleaved over 4 non-consecutive bursts + * using the even numbered bits of the first 2 bursts, + * and odd numbered bits of the last 2 bursts; + * - a FACCH/H frame is interleaved over 6 non-consecutive bursts * using the even numbered bits of the first 2 bursts, * all bits of the middle two 2 bursts, * and odd numbered bits of the last 2 bursts; @@ -157,389 +156,365 @@ 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, + .burst_buf_size = 24 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_TCHH_0, see above. */ - .burst_buf_size = 6 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_TCHH_0, see above. */ + .burst_buf_size = 24 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3), - .link_id = TRX_CH_LID_DEDIC, + .link_id = L1SCHED_CH_LID_DEDIC, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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", .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. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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)", .chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3), - .link_id = TRX_CH_LID_SACCH, + .link_id = L1SCHED_CH_LID_SACCH, - /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_CBTX, + /* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .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", .chan_nr = RSL_CHAN_OSMO_PDCH, - /* Rx and Tx, multiple coding schemes: CS-2..4 and MCS-1..9 (3GPP TS + /* Rx and Tx, multiple coding schemes: CS-1..4 and MCS-1..9 (3GPP TS * 05.03, chapter 5), regular interleaving as specified for xCCH. * NOTE: the burst buffer is three times bigger because the * payload of EDGE bursts is three times longer. */ - .burst_buf_size = 3 * 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_PDCH, + .burst_buf_size = 4 * GSM_NBITS_NB_8PSK_PAYLOAD, + .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", .chan_nr = RSL_CHAN_OSMO_PDCH, - - /* Same as for TRXC_BCCH (xCCH), see above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_PDCH, - .rx_fn = rx_data_fn, - .tx_fn = tx_data_fn, + .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 + * transfer mode. On Downlink, the network sends timing advance + * updates for several mobile stations. The coding scheme used + * for PTCCH/D messages is the same as for PDTCH CS-1. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, + .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", .chan_nr = RSL_CHAN_OSMO_CBCH4, - /* Same as for TRXC_BCCH (xCCH), but Rx only. See above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, - .flags = TRX_CH_FLAG_AUTO, + /* Same as for L1SCHED_BCCH (xCCH), but Rx only. See above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, + .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", .chan_nr = RSL_CHAN_OSMO_CBCH8, - /* Same as for TRXC_BCCH (xCCH), but Rx only. See above. */ - .burst_buf_size = 4 * GSM_BURST_PL_LEN, + /* Same as for L1SCHED_BCCH (xCCH), but Rx only. See above. */ + .burst_buf_size = 4 * GSM_NBITS_NB_GMSK_PAYLOAD, .rx_fn = rx_data_fn, }, }; diff --git a/src/host/trxcon/src/sched_lchan_pdtch.c b/src/host/trxcon/src/sched_lchan_pdtch.c new file mode 100644 index 00000000..5b884ddc --- /dev/null +++ b/src/host/trxcon/src/sched_lchan_pdtch.c @@ -0,0 +1,195 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: handlers for DL / UL bursts on logical channels + * + * (C) 2018-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/gsm0502.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_pdtch_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi) +{ + uint8_t l2[GPRS_L2_MAX_LEN]; + int n_errors, n_bits_total, rc; + sbit_t *bursts_p, *burst; + size_t l2_len; + uint32_t *mask; + + /* Set up pointers */ + mask = &lchan->rx_burst_mask; + bursts_p = lchan->rx_bursts; + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "Packet data received: fn=%u bid=%u\n", bi->fn, bi->bid); + + /* Align to the first burst of a block */ + if (*mask == 0x00 && bi->bid != 0) + return 0; + + /* Update mask */ + *mask |= (1 << bi->bid); + + /* Store the measurements */ + l1sched_lchan_meas_push(lchan, bi); + + /* Copy burst to buffer of 4 bursts */ + burst = bursts_p + bi->bid * 116; + memcpy(burst, bi->burst + 3, 58); + memcpy(burst + 58, bi->burst + 87, 58); + + /* Wait until complete set of bursts */ + if (bi->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) 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 ;) */ + } + + /* Keep the mask updated */ + *mask = *mask << 4; + + /* Attempt to decode */ + rc = gsm0503_pdtch_decode(l2, bursts_p, + NULL, &n_errors, &n_bits_total); + if (rc < 0) { + 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 */ + l1sched_lchan_emit_data_ind(lchan, l2, l2_len, n_errors, n_bits_total, true); + + return 0; +} + +static struct msgb *prim_dequeue_pdtch(struct l1sched_lchan_state *lchan, uint32_t fn) +{ + while (!llist_empty(&lchan->tx_prims)) { + struct msgb *msg = llist_first_entry(&lchan->tx_prims, struct msgb, list); + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + int ret = gsm0502_fncmp(prim->data_req.frame_nr, fn); + + if (OSMO_LIKELY(ret == 0)) { /* it's a match! */ + llist_del(&msg->list); + return msg; + } else if (ret > 0) { /* not now, come back later */ + break; + } /* else: the ship has sailed, drop your ticket */ + + LOGP_LCHAND(lchan, LOGL_ERROR, + "%s(): dropping stale Tx prim (current Fn=%u, prim Fn=%u): %s\n", + __func__, fn, prim->data_req.frame_nr, msgb_hexdump_l2(msg)); + llist_del(&msg->list); + msgb_free(msg); + } + + return NULL; +} + +int tx_pdtch_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br) +{ + ubit_t *bursts_p, *burst; + const uint8_t *tsc; + uint32_t *mask; + int rc; + + /* Set up pointers */ + mask = &lchan->tx_burst_mask; + bursts_p = lchan->tx_bursts; + + if (br->bid > 0) { + if ((*mask & 0x01) != 0x01) + return -ENOENT; + goto send_burst; + } + + *mask = *mask << 4; + + struct msgb *msg = prim_dequeue_pdtch(lchan, br->fn); + if (msg == NULL) + return -ENOENT; + + /* Encode payload */ + rc = gsm0503_pdtch_encode(bursts_p, msgb_l2(msg), msgb_l2len(msg)); + if (rc < 0) { + LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%u): %s\n", + msgb_l2len(msg), msgb_hexdump_l2(msg)); + msgb_free(msg); + return -EINVAL; + } + + /* Cache the prim, so that we can confirm it later (see below) */ + OSMO_ASSERT(lchan->prim == NULL); + lchan->prim = msg; + +send_burst: + /* Determine which burst should be sent */ + burst = bursts_p + 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, burst, 58); /* Payload 1/2 */ + memcpy(br->burst + 61, tsc, 26); /* TSC */ + memcpy(br->burst + 87, burst + 58, 58); /* Payload 2/2 */ + memset(br->burst + 145, 0, 3); /* TB */ + br->burst_len = GSM_NBITS_NB_GMSK_BURST; + + LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled at fn=%u burst=%u\n", br->fn, br->bid); + + if (br->bid == 3) { + /* Confirm data / traffic sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, lchan->prim, + GSM_TDMA_FN_SUB(br->fn, 3)); + lchan->prim = NULL; + } + + return 0; +} diff --git a/src/host/trxcon/src/sched_lchan_rach.c b/src/host/trxcon/src/sched_lchan_rach.c new file mode 100644 index 00000000..905f1d57 --- /dev/null +++ b/src/host/trxcon/src/sched_lchan_rach.c @@ -0,0 +1,136 @@ +/* + * 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 <stdbool.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/bits.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/coding/gsm0503_coding.h> + +#include <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 +#define RACH_SYNCH_SEQ_LEN 41 +#define RACH_PAYLOAD_LEN 36 + +/* Extended tail bits (BN0..BN7) */ +static const ubit_t rach_ext_tail_bits[] = { + 0, 0, 1, 1, 1, 0, 1, 0, +}; + +/* Synchronization (training) sequence types */ +enum rach_synch_seq_t { + RACH_SYNCH_SEQ_UNKNOWN = -1, + RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */ + RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */ + RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */ + RACH_SYNCH_SEQ_NUM +}; + +/* Synchronization (training) sequence bits */ +static const char rach_synch_seq_bits[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = { + [RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000", + [RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101", + [RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111", +}; + +/* Synchronization (training) sequence names */ +static struct value_string rach_synch_seq_names[] = { + { RACH_SYNCH_SEQ_UNKNOWN, "UNKNOWN" }, + { RACH_SYNCH_SEQ_TS0, "TS0: GSM, GMSK" }, + { RACH_SYNCH_SEQ_TS1, "TS1: EGPRS, 8-PSK" }, + { RACH_SYNCH_SEQ_TS2, "TS2: EGPRS, GMSK" }, + { 0, NULL }, +}; + +/* Obtain a to-be-transmitted RACH burst */ +int tx_rach_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br) +{ + const uint8_t bsic = lchan->ts->sched->bsic; + uint8_t *burst_ptr = br->burst; + uint8_t payload[36]; + int i, rc; + + if (llist_empty(&lchan->tx_prims)) + return 0; + + struct msgb *msg = llist_first_entry(&lchan->tx_prims, struct msgb, list); + struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + + /* Delay sending according to offset value */ + if (prim->rach_req.offset-- > 0) + return 0; + llist_del(&msg->list); + + /* Check requested synch. sequence */ + if (prim->rach_req.synch_seq >= RACH_SYNCH_SEQ_NUM) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Unknown RACH synch. sequence=0x%02x\n", + prim->rach_req.synch_seq); + msgb_free(msg); + return -ENOTSUP; + } + + /* Encode the payload */ + rc = gsm0503_rach_ext_encode(payload, prim->rach_req.ra, + bsic, prim->rach_req.is_11bit); + if (rc) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Could not encode %s-bit RACH burst (ra=%u bsic=%u)\n", + prim->rach_req.is_11bit ? "11" : "8", + prim->rach_req.ra, bsic); + msgb_free(msg); + return rc; + } + + /* BN0-7: extended tail bits */ + memcpy(burst_ptr, rach_ext_tail_bits, RACH_EXT_TAIL_BITS_LEN); + burst_ptr += RACH_EXT_TAIL_BITS_LEN; + + /* BN8-48: chosen synch. (training) sequence */ + for (i = 0; i < RACH_SYNCH_SEQ_LEN; i++) + *(burst_ptr++) = rach_synch_seq_bits[prim->rach_req.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, br->burst + GSM_NBITS_NB_GMSK_BURST - burst_ptr); + br->burst_len = GSM_NBITS_NB_GMSK_BURST; + + LOGP_LCHAND(lchan, LOGL_NOTICE, "Scheduled %s-bit RACH (%s) at fn=%u\n", + prim->rach_req.is_11bit ? "11" : "8", + get_value_string(rach_synch_seq_names, prim->rach_req.synch_seq), br->fn); + + /* Confirm RACH request (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + + return 0; +} diff --git a/src/host/trxcon/sched_lchan_sch.c b/src/host/trxcon/src/sched_lchan_sch.c index 9eed506b..e2420050 100644 --- a/src/host/trxcon/sched_lchan_sch.c +++ b/src/host/trxcon/src/sched_lchan_sch.c @@ -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,12 +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" +#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) { @@ -68,9 +61,23 @@ 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, - sbit_t *bits, int8_t rssi, int16_t toa256) +static int handle_sch_ind(struct l1sched_state *sched, uint32_t fn, uint8_t bsic) +{ + struct l1sched_prim *prim; + struct msgb *msg; + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_SCH, PRIM_OP_INDICATION); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->sch_ind.frame_nr = fn; + prim->sch_ind.bsic = bsic; + + return l1sched_prim_to_user(sched, msg); +} + +int rx_sch_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi) { sbit_t payload[2 * 39]; struct gsm_time time; @@ -79,55 +86,34 @@ int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts, int rc; /* Obtain payload from burst */ - memcpy(payload, bits + 3, 39); - memcpy(payload + 39, bits + 3 + 39 + 64, 39); + memcpy(payload, bi->burst + 3, 39); + memcpy(payload + 39, bi->burst + 3 + 39 + 64, 39); /* Attempt to decode */ 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", bi->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, bi->fn); /* 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); + if (time.fn != bi->fn) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Decoded fn=%u does not match sched_fn=%u\n", + time.fn, bi->fn); return -EINVAL; } - /* We don't need to send L1CTL_FBSB_CONF */ - if (trx->l1l->fbsb_conf_sent) - return 0; - - /* Send L1CTL_FBSB_CONF to higher layers */ - struct l1ctl_info_dl *data; - data = talloc_zero_size(ts, sizeof(struct l1ctl_info_dl)); - if (data == NULL) - return -ENOMEM; - - /* Fill in some downlink info */ - data->chan_nr = trx_lchan_desc[lchan->type].chan_nr | ts->index; - data->link_id = trx_lchan_desc[lchan->type].link_id; - data->band_arfcn = htons(trx->band_arfcn); - data->frame_nr = htonl(fn); - data->rx_level = -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; + /* Update BSIC value in the scheduler state */ + lchan->ts->sched->bsic = bsic; - return 0; + return handle_sch_ind(lchan->ts->sched, time.fn, bsic); } diff --git a/src/host/trxcon/src/sched_lchan_tchf.c b/src/host/trxcon/src/sched_lchan_tchf.c new file mode 100644 index 00000000..37e0cea3 --- /dev/null +++ b/src/host/trxcon/src/sched_lchan_tchf.c @@ -0,0 +1,441 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: handlers for DL / UL bursts on logical channels + * + * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * (C) 2021-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <errno.h> +#include <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/gsm/gsm0502.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> + +/* Burst Payload LENgth (short alias) */ +#define BPLEN GSM_NBITS_NB_GMSK_PAYLOAD + +/* Burst BUFfer capacity (in BPLEN units) */ +#define BUFMAX 24 + +/* Burst BUFfer position macros */ +#define BUFPOS(buf, n) &buf[(n) * BPLEN] +#define BUFTAIL8(buf) BUFPOS(buf, (BUFMAX - 8)) + +/* 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) */ +}; + +/* TDMA frame number of burst 'a' should be used as the table index. */ +static const uint8_t sched_tchf_ul_amr_cmi_map[26] = { + [0] = 1, /* TCH/F: a=0 */ + [8] = 1, /* TCH/F: a=8 */ + [17] = 1, /* TCH/F: a=17 */ +}; + +static int decode_fr_facch(struct l1sched_lchan_state *lchan) +{ + uint8_t data[GSM_MACBLOCK_LEN]; + int n_errors, n_bits_total; + int rc; + + rc = gsm0503_tch_fr_facch_decode(&data[0], BUFTAIL8(lchan->rx_bursts), + &n_errors, &n_bits_total); + if (rc != GSM_MACBLOCK_LEN) + return rc; + + /* calculate AVG of the measurements (FACCH/F takes 8 bursts) */ + l1sched_lchan_meas_avg(lchan, 8); + + l1sched_lchan_emit_data_ind(lchan, &data[0], GSM_MACBLOCK_LEN, + n_errors, n_bits_total, false); + + return GSM_MACBLOCK_LEN; +} + +int rx_tchf_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi) +{ + int n_errors = -1, n_bits_total = 0, rc; + sbit_t *bursts_p, *burst; + uint8_t tch_data[290]; + size_t tch_data_len; + uint32_t *mask; + int amr = 0; + uint8_t ft; + + /* Set up pointers */ + mask = &lchan->rx_burst_mask; + bursts_p = lchan->rx_bursts; + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "Traffic received: fn=%u bid=%u\n", bi->fn, bi->bid); + + if (bi->bid == 0) { + /* Shift the burst buffer by 4 bursts leftwards */ + memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 4), 20 * BPLEN); + memset(BUFPOS(bursts_p, 20), 0, 4 * BPLEN); + *mask = *mask << 4; + } else { + /* Align to the first burst of a block */ + if (*mask == 0x00) + return 0; + } + + /* Update mask */ + *mask |= (1 << bi->bid); + + /* Store the measurements */ + l1sched_lchan_meas_push(lchan, bi); + + /* Copy burst to end of buffer of 24 bursts */ + burst = BUFPOS(bursts_p, 20 + bi->bid); + memcpy(burst, bi->burst + 3, 58); + memcpy(burst + 58, bi->burst + 87, 58); + + /* Wait until complete set of bursts */ + if (bi->bid != 3) + return 0; + + /* Calculate AVG of the measurements */ + l1sched_lchan_meas_avg(lchan, 8); // XXX + + /* 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 ;) */ + + } + + /* TCH/F: speech and signalling frames are interleaved over 8 bursts, while + * CSD frames are interleaved over 22 bursts. Unless we're in CSD mode, + * decode only the last 8 bursts to avoid introducing additional delays. */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + case GSM48_CMODE_SPEECH_V1: /* FR */ + rc = gsm0503_tch_fr_decode(&tch_data[0], BUFTAIL8(bursts_p), + 1, 0, &n_errors, &n_bits_total); + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + rc = gsm0503_tch_fr_decode(&tch_data[0], BUFTAIL8(bursts_p), + 1, 1, &n_errors, &n_bits_total); + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + /* 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(&tch_data[amr], BUFTAIL8(bursts_p), + !sched_tchf_dl_amr_cmi_map[bi->fn % 26], + 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(&tch_data[0], + 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; + /* CSD (TCH/F14.4): 14.5 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_14k5: + /* FACCH/F does not steal TCH/F14.4 frames, but only disturbs some bits */ + decode_fr_facch(lchan); + rc = gsm0503_tch_fr144_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + break; + /* CSD (TCH/F9.6): 12.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_12k0: + /* FACCH/F does not steal TCH/F9.6 frames, but only disturbs some bits */ + decode_fr_facch(lchan); + rc = gsm0503_tch_fr96_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + break; + /* CSD (TCH/F4.8): 6.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_6k0: + /* FACCH/F does not steal TCH/F4.8 frames, but only disturbs some bits */ + decode_fr_facch(lchan); + rc = gsm0503_tch_fr48_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + break; + /* CSD (TCH/F2.4): 3.6 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_3k6: + /* TCH/F2.4 employs the same interleaving as TCH/FS (8 bursts), + * so FACCH/F *does* steal TCH/F2.4 frames completely. */ + if (decode_fr_facch(lchan) == GSM_MACBLOCK_LEN) + return 0; /* TODO: emit BFI? */ + rc = gsm0503_tch_fr24_decode(&tch_data[0], BUFTAIL8(bursts_p), + &n_errors, &n_bits_total); + break; + default: + LOGP_LCHAND(lchan, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode); + return -EINVAL; + } + + /* 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 (DATA.ind without payload) */ + tch_data_len = 0; + } else if (rc == GSM_MACBLOCK_LEN) { + /* FACCH received, forward it to the higher layers */ + l1sched_lchan_emit_data_ind(lchan, &tch_data[amr], GSM_MACBLOCK_LEN, + n_errors, n_bits_total, false); + + /* Send BFI (DATA.ind without payload) */ + if (lchan->tch_mode == GSM48_CMODE_SIGN) + return 0; + tch_data_len = 0; + } else { + /* A good TCH frame received */ + tch_data_len = rc; + } + + /* Send a traffic frame to the higher layers */ + return l1sched_lchan_emit_data_ind(lchan, &tch_data[0], tch_data_len, + n_errors, n_bits_total, true); +} + +int tx_tchf_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br) +{ + struct msgb *msg_facch, *msg_tch, *msg; + ubit_t *bursts_p, *burst; + const uint8_t *tsc; + uint32_t *mask; + int rc; + + /* Set up pointers */ + mask = &lchan->tx_burst_mask; + bursts_p = lchan->tx_bursts; + + if (br->bid > 0) { + if ((*mask & 0x01) != 0x01) + return -ENOENT; + goto send_burst; + } + + /* Shift the burst buffer by 4 bursts leftwards for interleaving */ + memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 4), 20 * BPLEN); + memset(BUFPOS(bursts_p, 20), 0, 4 * BPLEN); + *mask = *mask << 4; + + /* dequeue a pair of TCH and FACCH frames */ + msg_tch = l1sched_lchan_prim_dequeue_tch(lchan, false); + msg_facch = l1sched_lchan_prim_dequeue_tch(lchan, true); + /* prioritize FACCH over TCH */ + msg = (msg_facch != NULL) ? msg_facch : msg_tch; + + /* populate the buffer with bursts */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + if (msg == NULL) + msg = l1sched_lchan_prim_dummy_lapdm(lchan); + /* fall-through */ + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + if (msg == NULL) { + /* transmit a dummy speech block with inverted CRC3 */ + gsm0503_tch_fr_encode(bursts_p, NULL, 0, 1); + goto send_burst; + } + rc = gsm0503_tch_fr_encode(BUFPOS(bursts_p, 0), + msgb_l2(msg), + msgb_l2len(msg), 1); + break; + case GSM48_CMODE_SPEECH_AMR: + { + bool amr_fn_is_cmr = !sched_tchf_ul_amr_cmi_map[br->fn % 26]; + const uint8_t *data = msg ? msgb_l2(msg) : NULL; + size_t data_len = msg ? msgb_l2len(msg) : 0; + + if (msg == NULL) { + /* TODO: It's not clear what to do for TCH/AFS. + * TODO: Send dummy FACCH maybe? */ + goto send_burst; /* send something */ + } + + if (data_len != GSM_MACBLOCK_LEN) { /* TCH/AFS: speech */ + if (!l1sched_lchan_amr_prim_is_valid(lchan, msg, amr_fn_is_cmr)) + goto free_bad_msg; + /* pull the AMR header - sizeof(struct amr_hdr) */ + data_len -= 2; + data += 2; + } + + rc = gsm0503_tch_afs_encode(BUFPOS(bursts_p, 0), + data, data_len, + amr_fn_is_cmr, + lchan->amr.codec, + lchan->amr.codecs, + lchan->amr.ul_ft, + lchan->amr.ul_cmr); + break; + } + /* CSD (TCH/F14.4): 14.5 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_14k5: + if ((msg = msg_tch) != NULL) { + OSMO_ASSERT(msgb_l2len(msg) == 290); + gsm0503_tch_fr144_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm data sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } else { + ubit_t idle[290]; + memset(&idle[0], 0x01, sizeof(idle)); + gsm0503_tch_fr144_encode(BUFPOS(bursts_p, 0), &idle[0]); + } + if ((msg = msg_facch) != NULL) { + gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm FACCH sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } + goto send_burst; + /* CSD (TCH/F9.6): 12.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_12k0: + if ((msg = msg_tch) != NULL) { + OSMO_ASSERT(msgb_l2len(msg) == 4 * 60); + gsm0503_tch_fr96_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm data sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } else { + ubit_t idle[4 * 60]; + memset(&idle[0], 0x01, sizeof(idle)); + gsm0503_tch_fr96_encode(BUFPOS(bursts_p, 0), &idle[0]); + } + if ((msg = msg_facch) != NULL) { + gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm FACCH sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } + goto send_burst; + /* CSD (TCH/F4.8): 6.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_6k0: + if ((msg = msg_tch) != NULL) { + OSMO_ASSERT(msgb_l2len(msg) == 2 * 60); + gsm0503_tch_fr48_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm data sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } else { + ubit_t idle[2 * 60]; + memset(&idle[0], 0x01, sizeof(idle)); + gsm0503_tch_fr48_encode(BUFPOS(bursts_p, 0), &idle[0]); + } + if ((msg = msg_facch) != NULL) { + gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm FACCH sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } + goto send_burst; + /* CSD (TCH/F2.4): 3.6 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_3k6: + if ((msg = msg_facch) != NULL) { + /* FACCH/F does steal a TCH/F2.4 frame completely */ + rc = gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + } else if ((msg = msg_tch) != NULL) { + OSMO_ASSERT(msgb_l2len(msg) == 2 * 36); + rc = gsm0503_tch_fr24_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + } else { + ubit_t idle[2 * 36]; + memset(&idle[0], 0x01, sizeof(idle)); + gsm0503_tch_fr24_encode(BUFPOS(bursts_p, 0), &idle[0]); + goto send_burst; + } + break; + default: + LOGP_LCHAND(lchan, LOGL_ERROR, + "TCH mode %s is unknown or not supported\n", + gsm48_chan_mode_name(lchan->tch_mode)); + goto free_bad_msg; + } + + if (rc) { + LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%u): %s\n", + msgb_l2len(msg), msgb_hexdump_l2(msg)); +free_bad_msg: + msgb_free(msg_facch); + msgb_free(msg_tch); + return -EINVAL; + } + + /* Confirm data / traffic sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + msgb_free((msg == msg_facch) ? msg_tch : msg_facch); + +send_burst: + /* Determine which burst should be sent */ + burst = BUFPOS(bursts_p, br->bid); + + /* 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, burst, 58); /* Payload 1/2 */ + memcpy(br->burst + 61, tsc, 26); /* TSC */ + memcpy(br->burst + 87, burst + 58, 58); /* Payload 2/2 */ + memset(br->burst + 145, 0, 3); /* TB */ + br->burst_len = GSM_NBITS_NB_GMSK_BURST; + + LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled fn=%u burst=%u\n", br->fn, br->bid); + + return 0; +} diff --git a/src/host/trxcon/src/sched_lchan_tchh.c b/src/host/trxcon/src/sched_lchan_tchh.c new file mode 100644 index 00000000..99e26808 --- /dev/null +++ b/src/host/trxcon/src/sched_lchan_tchh.c @@ -0,0 +1,622 @@ +/* + * 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> + * (C) 2020-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <errno.h> +#include <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/gsm/gsm0502.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> + +/* Burst Payload LENgth (short alias) */ +#define BPLEN GSM_NBITS_NB_GMSK_PAYLOAD + +/* Burst BUFfer capacity (in BPLEN units) */ +#define BUFMAX 24 + +/* Burst BUFfer position macros */ +#define BUFPOS(buf, n) &buf[(n) * BPLEN] +#define BUFTAIL8(buf) BUFPOS(buf, (BUFMAX - 8)) + +/* 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 */ +}; + +/* TDMA frame number of burst 'a' is always used as the table index. */ +static const uint8_t sched_tchh_ul_amr_cmi_map[26] = { + [0] = 1, /* TCH/H(0): a=0 */ + [8] = 1, /* TCH/H(0): a=8 */ + [17] = 1, /* TCH/H(0): a=17 */ + + [1] = 1, /* TCH/H(1): a=1 */ + [9] = 1, /* TCH/H(1): a=9 */ + [18] = 1, /* TCH/H(1): a=18 */ +}; + +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) */ +}; + +/* 3GPP TS 45.002, table 2 in clause 7: Mapping tables for TCH/H2.4 and TCH/H4.8. + * + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * TCH/H(0): B0(0,2,4,6,8,10,13,15,17,19,21,23,0,2,4,6,8,10,13,15,17,19) + * TCH/H(1): B0(1,3,5,7,9,11,14,16,18,20,22,24,1,3,5,7,9,11,14,16,18,20) + * TCH/H(0): B1(8,10,13,15,17,19,21,23,0,2,4,6,8,10,13,15,17,19,21,23,0,2) + * TCH/H(1): B1(9,11,14,16,18,20,22,24,1,3,5,7,9,11,14,16,18,20,22,24,1,3) + * TCH/H(0): B2(17,19,21,23,0,2,4,6,8,10,13,15,17,19,21,23,0,2,4,6,8,10) + * TCH/H(1): B2(18,20,22,24,1,3,5,7,9,11,14,16,18,20,22,24,1,3,5,7,9,11) + * + * TDMA frame number of burst 'a' % 26 is the table index. + * This mapping is valid for both TCH/H(0) and TCH/H(1). */ +static const uint8_t sched_tchh_ul_csd_map[26] = { + [0] = 1, /* TCH/H(0): B0(0 ... 19) */ + [1] = 1, /* TCH/H(1): B0(1 ... 20) */ + [8] = 1, /* TCH/H(0): B1(8 ... 2) */ + [9] = 1, /* TCH/H(1): B1(9 ... 3) */ + [17] = 1, /* TCH/H(0): B2(17 ... 10) */ + [18] = 1, /* TCH/H(1): B2(18 ... 11) */ +}; + +/* TDMA frame number of burst 'v' % 26 is the table index. + * This mapping is valid for both TCH/H(0) and TCH/H(1). */ +static const uint8_t sched_tchh_dl_csd_map[26] = { + [19] = 1, /* TCH/H(0): B0(0 ... 19) */ + [20] = 1, /* TCH/H(1): B0(1 ... 20) */ + [2] = 1, /* TCH/H(0): B1(8 ... 2) */ + [3] = 1, /* TCH/H(1): B1(9 ... 3) */ + [10] = 1, /* TCH/H(0): B2(17 ... 10) */ + [11] = 1, /* TCH/H(1): B2(18 ... 11) */ +}; + +/** + * 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; +} + +static int decode_hr_facch(struct l1sched_lchan_state *lchan) +{ + uint8_t data[GSM_MACBLOCK_LEN]; + int n_errors, n_bits_total; + int rc; + + rc = gsm0503_tch_hr_facch_decode(&data[0], BUFTAIL8(lchan->rx_bursts), + &n_errors, &n_bits_total); + if (rc != GSM_MACBLOCK_LEN) + return rc; + + /* calculate AVG of the measurements (FACCH/H takes 6 bursts) */ + l1sched_lchan_meas_avg(lchan, 6); + + l1sched_lchan_emit_data_ind(lchan, &data[0], GSM_MACBLOCK_LEN, + n_errors, n_bits_total, false); + + return GSM_MACBLOCK_LEN; +} + +int rx_tchh_fn(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi) +{ + int n_errors = -1, n_bits_total = 0, rc; + sbit_t *bursts_p, *burst; + uint8_t tch_data[240]; + size_t tch_data_len; + uint32_t *mask; + int amr = 0; + uint8_t ft; + + /* Set up pointers */ + mask = &lchan->rx_burst_mask; + bursts_p = lchan->rx_bursts; + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "Traffic received: fn=%u bid=%u\n", bi->fn, bi->bid); + + if (bi->bid == 0) { + /* Shift the burst buffer by 2 bursts leftwards */ + memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 2), 20 * BPLEN); + memset(BUFPOS(bursts_p, 20), 0, 2 * BPLEN); + *mask = *mask << 2; + } + + if (*mask == 0x00) { + /* Align to the first burst */ + if (bi->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, bi->fn, 0)) + return 0; + } + } + + /* Update mask */ + *mask |= (1 << bi->bid); + + /* Store the measurements */ + l1sched_lchan_meas_push(lchan, bi); + + /* Copy burst to the end of buffer of 24 bursts */ + burst = BUFPOS(bursts_p, 20 + bi->bid); + memcpy(burst, bi->burst + 3, 58); + memcpy(burst + 58, bi->burst + 87, 58); + + /* Wait until the second burst */ + if (bi->bid != 1) + return 0; + + /* Wait for complete set of bursts */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + /* FACCH/H is interleaved over 6 bursts */ + if ((*mask & 0x3f) != 0x3f) + return 0; + break; + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + /* Data (CSD) is interleaved over 22 bursts */ + if ((*mask & 0x3fffff) != 0x3fffff) + return 0; + if (!sched_tchh_dl_csd_map[bi->fn % 26]) + return 0; /* CSD: skip decoding attempt, need 2 more bursts */ + break; + default: + /* Speech is interleaved over 4 bursts */ + if ((*mask & 0x0f) != 0x0f) + return 0; + break; + } + + /* Skip decoding attempt in case of FACCH/H */ + if (lchan->dl_ongoing_facch) { + /* Send BFI (DATA.ind without payload) for the 2nd stolen TCH frame */ + l1sched_lchan_meas_avg(lchan, 4); + l1sched_lchan_emit_data_ind(lchan, NULL, 0, 0, 0, true); + lchan->dl_ongoing_facch = false; + return 0; + } + + /* TCH/H: speech and signalling frames are interleaved over 4 and 6 bursts, + * respectively, while CSD frames are interleaved over 22 bursts. Unless + * we're in CSD mode, decode only the last 6 bursts to avoid introducing + * additional delays. */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + case GSM48_CMODE_SPEECH_V1: /* HR */ + rc = gsm0503_tch_hr_decode(&tch_data[0], BUFTAIL8(bursts_p), + !sched_tchh_dl_facch_map[bi->fn % 26], + &n_errors, &n_bits_total); + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + /* See comment in function rx_tchf_fn() */ + amr = 2; + rc = gsm0503_tch_ahs_decode_dtx(&tch_data[amr], BUFTAIL8(bursts_p), + !sched_tchh_dl_facch_map[bi->fn % 26], + !sched_tchh_dl_amr_cmi_map[bi->fn % 26], + 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(&tch_data[0], + 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; + /* CSD (TCH/H4.8): 6.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_6k0: + /* FACCH/H does not steal TCH/H4.8 frames, but only disturbs some bits */ + decode_hr_facch(lchan); + rc = gsm0503_tch_hr48_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + break; + /* CSD (TCH/H2.4): 3.6 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_3k6: + /* FACCH/H does not steal TCH/H2.4 frames, but only disturbs some bits */ + decode_hr_facch(lchan); + rc = gsm0503_tch_hr24_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + break; + default: + LOGP_LCHAND(lchan, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode); + return -EINVAL; + } + + /* 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 (DATA.ind without payload) */ + tch_data_len = 0; + } 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_lchan_emit_data_ind(lchan, &tch_data[amr], GSM_MACBLOCK_LEN, + n_errors, n_bits_total, false); + + /* Send BFI (DATA.ind without payload) for the 1st stolen TCH frame */ + if (lchan->tch_mode == GSM48_CMODE_SIGN) + return 0; + tch_data_len = 0; + } else { + /* A good TCH frame received */ + tch_data_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_lchan_emit_data_ind(lchan, &tch_data[0], tch_data_len, + n_errors, n_bits_total, true); +} + +int tx_tchh_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br) +{ + struct msgb *msg_facch, *msg_tch, *msg; + ubit_t *bursts_p, *burst; + const uint8_t *tsc; + uint32_t *mask; + int rc; + + /* Set up pointers */ + mask = &lchan->tx_burst_mask; + bursts_p = lchan->tx_bursts; + + if (br->bid > 0) { + if ((*mask & 0x01) != 0x01) + return -ENOENT; + goto send_burst; + } + + if (*mask == 0x00) { + /* Align transmission of the first frame */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + if (!l1sched_tchh_facch_start(lchan->type, br->fn, 1)) + return 0; + break; + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + if (!sched_tchh_ul_csd_map[br->fn % 26]) + return 0; + break; + } + } + + /* Shift the burst buffer by 2 bursts leftwards for interleaving */ + memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 2), 20 * BPLEN); + memset(BUFPOS(bursts_p, 20), 0, 2 * BPLEN); + *mask = *mask << 2; + + /* If FACCH/H blocks are still pending */ + if (lchan->ul_facch_blocks > 2) { + struct msgb *msg = l1sched_lchan_prim_dequeue_tch(lchan, false); + msgb_free(msg); /* drop 2nd TCH/HS block */ + goto send_burst; + } + + switch (lchan->tch_mode) { + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + /* CSD: skip dequeueing/encoding, send 2 more bursts */ + if (!sched_tchh_ul_csd_map[br->fn % 26]) + goto send_burst; + break; + } + + /* dequeue a pair of TCH and FACCH frames */ + msg_tch = l1sched_lchan_prim_dequeue_tch(lchan, false); + if (l1sched_tchh_facch_start(lchan->type, br->fn, true)) + msg_facch = l1sched_lchan_prim_dequeue_tch(lchan, true); + else + msg_facch = NULL; + /* prioritize FACCH over TCH */ + msg = (msg_facch != NULL) ? msg_facch : msg_tch; + + /* populate the buffer with bursts */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + if (!l1sched_tchh_facch_start(lchan->type, br->fn, 1)) + goto send_burst; /* XXX: should not happen */ + if (msg == NULL) + msg = l1sched_lchan_prim_dummy_lapdm(lchan); + /* fall-through */ + case GSM48_CMODE_SPEECH_V1: + if (msg == NULL) { + /* transmit a dummy speech block with inverted CRC3 */ + gsm0503_tch_hr_encode(bursts_p, NULL, 0); + goto send_burst; + } + rc = gsm0503_tch_hr_encode(BUFPOS(bursts_p, 0), + msgb_l2(msg), + msgb_l2len(msg)); + break; + case GSM48_CMODE_SPEECH_AMR: + { + bool amr_fn_is_cmr = !sched_tchh_ul_amr_cmi_map[br->fn % 26]; + const uint8_t *data = msg ? msgb_l2(msg) : NULL; + size_t data_len = msg ? msgb_l2len(msg) : 0; + + if (msg == NULL) { + /* TODO: It's not clear what to do for TCH/AHS. + * TODO: Send dummy FACCH maybe? */ + goto send_burst; /* send garbage */ + } + + if (data_len != GSM_MACBLOCK_LEN) { /* TCH/AHS: speech */ + if (!l1sched_lchan_amr_prim_is_valid(lchan, msg, amr_fn_is_cmr)) + goto free_bad_msg; + /* pull the AMR header - sizeof(struct amr_hdr) */ + data_len -= 2; + data += 2; + } + + rc = gsm0503_tch_ahs_encode(BUFPOS(bursts_p, 0), + data, data_len, + amr_fn_is_cmr, + lchan->amr.codec, + lchan->amr.codecs, + lchan->amr.ul_ft, + lchan->amr.ul_cmr); + break; + } + /* CSD (TCH/H4.8): 6.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_6k0: + if ((msg = msg_tch) != NULL) { + OSMO_ASSERT(msgb_l2len(msg) == 4 * 60); + gsm0503_tch_hr48_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm data sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } else { + ubit_t idle[4 * 60]; + memset(&idle[0], 0x01, sizeof(idle)); + gsm0503_tch_hr48_encode(BUFPOS(bursts_p, 0), &idle[0]); + } + if ((msg = msg_facch) != NULL) { + gsm0503_tch_hr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm FACCH sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } + goto send_burst; + /* CSD (TCH/H2.4): 3.6 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_3k6: + if ((msg = msg_tch) != NULL) { + OSMO_ASSERT(msgb_l2len(msg) == 4 * 36); + gsm0503_tch_hr24_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm data sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } else { + ubit_t idle[4 * 36]; + memset(&idle[0], 0x01, sizeof(idle)); + gsm0503_tch_hr24_encode(BUFPOS(bursts_p, 0), &idle[0]); + } + if ((msg = msg_facch) != NULL) { + gsm0503_tch_hr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg)); + /* Confirm FACCH sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + } + goto send_burst; + default: + LOGP_LCHAND(lchan, LOGL_ERROR, + "TCH mode %s is unknown or not supported\n", + gsm48_chan_mode_name(lchan->tch_mode)); + goto free_bad_msg; + } + + if (rc) { + LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%u): %s\n", + msgb_l2len(msg), msgb_hexdump_l2(msg)); +free_bad_msg: + msgb_free(msg_facch); + msgb_free(msg_tch); + return -EINVAL; + } + + if (msgb_l2len(msg) == GSM_MACBLOCK_LEN) + lchan->ul_facch_blocks = 6; + + /* Confirm data / traffic sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + msgb_free((msg == msg_facch) ? msg_tch : msg_facch); + +send_burst: + /* Determine which burst should be sent */ + burst = BUFPOS(bursts_p, br->bid); + + /* 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, burst, 58); /* Payload 1/2 */ + memcpy(br->burst + 61, tsc, 26); /* TSC */ + memcpy(br->burst + 87, burst + 58, 58); /* Payload 2/2 */ + memset(br->burst + 145, 0, 3); /* TB */ + br->burst_len = GSM_NBITS_NB_GMSK_BURST; + + 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--; + + return 0; +} diff --git a/src/host/trxcon/src/sched_lchan_xcch.c b/src/host/trxcon/src/sched_lchan_xcch.c new file mode 100644 index 00000000..52b5d1e6 --- /dev/null +++ b/src/host/trxcon/src/sched_lchan_xcch.c @@ -0,0 +1,181 @@ +/* + * 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, + const struct l1sched_burst_ind *bi) +{ + uint8_t l2[GSM_MACBLOCK_LEN]; + int n_errors, n_bits_total, rc; + sbit_t *bursts_p, *burst; + uint32_t *mask; + + /* Set up pointers */ + mask = &lchan->rx_burst_mask; + bursts_p = lchan->rx_bursts; + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "Data received: fn=%u bid=%u\n", bi->fn, bi->bid); + + /* Align to the first burst of a block */ + if (*mask == 0x00 && bi->bid != 0) + return 0; + + /* Update mask */ + *mask |= (1 << bi->bid); + + /* Store the measurements */ + l1sched_lchan_meas_push(lchan, bi); + + /* Copy burst to buffer of 4 bursts */ + burst = bursts_p + bi->bid * 116; + memcpy(burst, bi->burst + 3, 58); + memcpy(burst + 58, bi->burst + 87, 58); + + /* Wait until complete set of bursts */ + if (bi->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, bursts_p, &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_lchan_emit_data_ind(lchan, l2, rc ? 0 : GSM_MACBLOCK_LEN, + n_errors, n_bits_total, false); +} + +static struct msgb *prim_dequeue_xcch(struct l1sched_lchan_state *lchan) +{ + struct msgb *msg; + + if (L1SCHED_CHAN_IS_SACCH(lchan->type)) + return l1sched_lchan_prim_dequeue_sacch(lchan); + if ((msg = msgb_dequeue(&lchan->tx_prims)) == NULL) + return NULL; + + /* Check the prim payload length */ + if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) { + LOGP_LCHAND(lchan, LOGL_ERROR, + "Primitive has odd length %u (expected %u), so dropping...\n", + msgb_l2len(msg), GSM_MACBLOCK_LEN); + msgb_free(msg); + return NULL; + } + + return msg; +} + +int tx_data_fn(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br) +{ + ubit_t *bursts_p, *burst; + const uint8_t *tsc; + uint32_t *mask; + int rc; + + /* Set up pointers */ + mask = &lchan->tx_burst_mask; + bursts_p = lchan->tx_bursts; + + if (br->bid > 0) { + if ((*mask & 0x01) != 0x01) + return -ENOENT; + goto send_burst; + } + + *mask = *mask << 4; + + struct msgb *msg = prim_dequeue_xcch(lchan); + if (msg == NULL) + msg = l1sched_lchan_prim_dummy_lapdm(lchan); + OSMO_ASSERT(msg != NULL); + + /* Encode payload */ + rc = gsm0503_xcch_encode(bursts_p, msgb_l2(msg)); + if (rc) { + LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%u): %s\n", + msgb_l2len(msg), msgb_hexdump_l2(msg)); + msgb_free(msg); + return -EINVAL; + } + + /* Confirm data sending (pass ownership of the msgb/prim) */ + l1sched_lchan_emit_data_cnf(lchan, msg, br->fn); + +send_burst: + /* Determine which burst should be sent */ + burst = bursts_p + 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, burst, 58); /* Payload 1/2 */ + memcpy(br->burst + 61, tsc, 26); /* TSC */ + memcpy(br->burst + 87, burst + 58, 58); /* Payload 2/2 */ + memset(br->burst + 145, 0, 3); /* TB */ + br->burst_len = GSM_NBITS_NB_GMSK_BURST; + + LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled fn=%u burst=%u\n", br->fn, br->bid); + + return 0; +} diff --git a/src/host/trxcon/src/sched_mframe.c b/src/host/trxcon/src/sched_mframe.c new file mode 100644 index 00000000..0d95e0ac --- /dev/null +++ b/src/host/trxcon/src/sched_mframe.c @@ -0,0 +1,2102 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: channel combinations, burst mapping + * + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> + * (C) 2015 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/gsm/gsm_utils.h> + +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> + +/* Non-combined CCCH */ +static const struct l1sched_tdma_frame frame_bcch[51] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_RACH, 0 }, +}; + +/* Combined CCCH+SDCCH4 */ +static const struct l1sched_tdma_frame frame_bcch_sdcch4[102] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_3, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_3, 1 }, + { L1SCHED_BCCH, 0, L1SCHED_SDCCH4_3, 2 }, + { L1SCHED_BCCH, 1, L1SCHED_SDCCH4_3, 3 }, + { L1SCHED_BCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_2, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_2, 1 }, + { L1SCHED_CCCH, 2, L1SCHED_SACCH4_2, 2 }, + { L1SCHED_CCCH, 3, L1SCHED_SACCH4_2, 3 }, + { L1SCHED_FCCH, 0, L1SCHED_SACCH4_3, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SACCH4_3, 1 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_3, 2 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_3, 3 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 1, L1SCHED_SDCCH4_0, 0 }, + { L1SCHED_SDCCH4_3, 2, L1SCHED_SDCCH4_0, 1 }, + { L1SCHED_SDCCH4_3, 3, L1SCHED_SDCCH4_0, 2 }, + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_0, 3 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_1, 0 }, + { L1SCHED_SACCH4_0, 0, L1SCHED_SDCCH4_1, 1 }, + { L1SCHED_SACCH4_0, 1, L1SCHED_SDCCH4_1, 2 }, + { L1SCHED_SACCH4_0, 2, L1SCHED_SDCCH4_1, 3 }, + { L1SCHED_SACCH4_0, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_1, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_1, 1, L1SCHED_SDCCH4_2, 0 }, + { L1SCHED_SACCH4_1, 2, L1SCHED_SDCCH4_2, 1 }, + { L1SCHED_SACCH4_1, 3, L1SCHED_SDCCH4_2, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_SDCCH4_2, 3 }, + + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_3, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_3, 1 }, + { L1SCHED_BCCH, 0, L1SCHED_SDCCH4_3, 2 }, + { L1SCHED_BCCH, 1, L1SCHED_SDCCH4_3, 3 }, + { L1SCHED_BCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_0, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_0, 1 }, + { L1SCHED_CCCH, 2, L1SCHED_SACCH4_0, 2 }, + { L1SCHED_CCCH, 3, L1SCHED_SACCH4_0, 3 }, + { L1SCHED_FCCH, 0, L1SCHED_SACCH4_1, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SACCH4_1, 1 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_1, 2 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_1, 3 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_2, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 1, L1SCHED_SDCCH4_0, 0 }, + { L1SCHED_SDCCH4_3, 2, L1SCHED_SDCCH4_0, 1 }, + { L1SCHED_SDCCH4_3, 3, L1SCHED_SDCCH4_0, 2 }, + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_0, 3 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_1, 0 }, + { L1SCHED_SACCH4_2, 0, L1SCHED_SDCCH4_1, 1 }, + { L1SCHED_SACCH4_2, 1, L1SCHED_SDCCH4_1, 2 }, + { L1SCHED_SACCH4_2, 2, L1SCHED_SDCCH4_1, 3 }, + { L1SCHED_SACCH4_2, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_3, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_3, 1, L1SCHED_SDCCH4_2, 0 }, + { L1SCHED_SACCH4_3, 2, L1SCHED_SDCCH4_2, 1 }, + { L1SCHED_SACCH4_3, 3, L1SCHED_SDCCH4_2, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_SDCCH4_2, 3 }, +}; + +static const struct l1sched_tdma_frame frame_bcch_sdcch4_cbch[102] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_3, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_3, 1 }, + { L1SCHED_BCCH, 0, L1SCHED_SDCCH4_3, 2 }, + { L1SCHED_BCCH, 1, L1SCHED_SDCCH4_3, 3 }, + { L1SCHED_BCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_IDLE, 1 }, + { L1SCHED_CCCH, 2, L1SCHED_IDLE, 2 }, + { L1SCHED_CCCH, 3, L1SCHED_IDLE, 3 }, + { L1SCHED_FCCH, 0, L1SCHED_SACCH4_3, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SACCH4_3, 1 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_3, 2 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_3, 3 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 1, L1SCHED_SDCCH4_0, 0 }, + { L1SCHED_SDCCH4_3, 2, L1SCHED_SDCCH4_0, 1 }, + { L1SCHED_SDCCH4_3, 3, L1SCHED_SDCCH4_0, 2 }, + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_0, 3 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_1, 0 }, + { L1SCHED_SACCH4_0, 0, L1SCHED_SDCCH4_1, 1 }, + { L1SCHED_SACCH4_0, 1, L1SCHED_SDCCH4_1, 2 }, + { L1SCHED_SACCH4_0, 2, L1SCHED_SDCCH4_1, 3 }, + { L1SCHED_SACCH4_0, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_1, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_1, 1, L1SCHED_IDLE, 0 }, + { L1SCHED_SACCH4_1, 2, L1SCHED_IDLE, 1 }, + { L1SCHED_SACCH4_1, 3, L1SCHED_IDLE, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 3 }, + + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_3, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_3, 1 }, + { L1SCHED_BCCH, 0, L1SCHED_SDCCH4_3, 2 }, + { L1SCHED_BCCH, 1, L1SCHED_SDCCH4_3, 3 }, + { L1SCHED_BCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_BCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_0, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_0, 1 }, + { L1SCHED_CCCH, 2, L1SCHED_SACCH4_0, 2 }, + { L1SCHED_CCCH, 3, L1SCHED_SACCH4_0, 3 }, + { L1SCHED_FCCH, 0, L1SCHED_SACCH4_1, 0 }, + { L1SCHED_SCH, 0, L1SCHED_SACCH4_1, 1 }, + { L1SCHED_CCCH, 0, L1SCHED_SACCH4_1, 2 }, + { L1SCHED_CCCH, 1, L1SCHED_SACCH4_1, 3 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_CCCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_0, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_1, 3, L1SCHED_RACH, 0 }, + { L1SCHED_FCCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 1, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 2, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_CBCH, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SDCCH4_3, 1, L1SCHED_SDCCH4_0, 0 }, + { L1SCHED_SDCCH4_3, 2, L1SCHED_SDCCH4_0, 1 }, + { L1SCHED_SDCCH4_3, 3, L1SCHED_SDCCH4_0, 2 }, + { L1SCHED_FCCH, 0, L1SCHED_SDCCH4_0, 3 }, + { L1SCHED_SCH, 0, L1SCHED_SDCCH4_1, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_SDCCH4_1, 1 }, + { L1SCHED_IDLE, 1, L1SCHED_SDCCH4_1, 2 }, + { L1SCHED_IDLE, 2, L1SCHED_SDCCH4_1, 3 }, + { L1SCHED_IDLE, 3, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_3, 0, L1SCHED_RACH, 0 }, + { L1SCHED_SACCH4_3, 1, L1SCHED_IDLE, 0 }, + { L1SCHED_SACCH4_3, 2, L1SCHED_IDLE, 1 }, + { L1SCHED_SACCH4_3, 3, L1SCHED_IDLE, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 3 }, +}; + +static const struct l1sched_tdma_frame frame_sdcch8[102] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_SDCCH8_0, 0, L1SCHED_SACCH8_5, 0 }, + { L1SCHED_SDCCH8_0, 1, L1SCHED_SACCH8_5, 1 }, + { L1SCHED_SDCCH8_0, 2, L1SCHED_SACCH8_5, 2 }, + { L1SCHED_SDCCH8_0, 3, L1SCHED_SACCH8_5, 3 }, + { L1SCHED_SDCCH8_1, 0, L1SCHED_SACCH8_6, 0 }, + { L1SCHED_SDCCH8_1, 1, L1SCHED_SACCH8_6, 1 }, + { L1SCHED_SDCCH8_1, 2, L1SCHED_SACCH8_6, 2 }, + { L1SCHED_SDCCH8_1, 3, L1SCHED_SACCH8_6, 3 }, + { L1SCHED_SDCCH8_2, 0, L1SCHED_SACCH8_7, 0 }, + { L1SCHED_SDCCH8_2, 1, L1SCHED_SACCH8_7, 1 }, + { L1SCHED_SDCCH8_2, 2, L1SCHED_SACCH8_7, 2 }, + { L1SCHED_SDCCH8_2, 3, L1SCHED_SACCH8_7, 3 }, + { L1SCHED_SDCCH8_3, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 1, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 2, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 3, L1SCHED_SDCCH8_0, 0 }, + { L1SCHED_SDCCH8_4, 0, L1SCHED_SDCCH8_0, 1 }, + { L1SCHED_SDCCH8_4, 1, L1SCHED_SDCCH8_0, 2 }, + { L1SCHED_SDCCH8_4, 2, L1SCHED_SDCCH8_0, 3 }, + { L1SCHED_SDCCH8_4, 3, L1SCHED_SDCCH8_1, 0 }, + { L1SCHED_SDCCH8_5, 0, L1SCHED_SDCCH8_1, 1 }, + { L1SCHED_SDCCH8_5, 1, L1SCHED_SDCCH8_1, 2 }, + { L1SCHED_SDCCH8_5, 2, L1SCHED_SDCCH8_1, 3 }, + { L1SCHED_SDCCH8_5, 3, L1SCHED_SDCCH8_2, 0 }, + { L1SCHED_SDCCH8_6, 0, L1SCHED_SDCCH8_2, 1 }, + { L1SCHED_SDCCH8_6, 1, L1SCHED_SDCCH8_2, 2 }, + { L1SCHED_SDCCH8_6, 2, L1SCHED_SDCCH8_2, 3 }, + { L1SCHED_SDCCH8_6, 3, L1SCHED_SDCCH8_3, 0 }, + { L1SCHED_SDCCH8_7, 0, L1SCHED_SDCCH8_3, 1 }, + { L1SCHED_SDCCH8_7, 1, L1SCHED_SDCCH8_3, 2 }, + { L1SCHED_SDCCH8_7, 2, L1SCHED_SDCCH8_3, 3 }, + { L1SCHED_SDCCH8_7, 3, L1SCHED_SDCCH8_4, 0 }, + { L1SCHED_SACCH8_0, 0, L1SCHED_SDCCH8_4, 1 }, + { L1SCHED_SACCH8_0, 1, L1SCHED_SDCCH8_4, 2 }, + { L1SCHED_SACCH8_0, 2, L1SCHED_SDCCH8_4, 3 }, + { L1SCHED_SACCH8_0, 3, L1SCHED_SDCCH8_5, 0 }, + { L1SCHED_SACCH8_1, 0, L1SCHED_SDCCH8_5, 1 }, + { L1SCHED_SACCH8_1, 1, L1SCHED_SDCCH8_5, 2 }, + { L1SCHED_SACCH8_1, 2, L1SCHED_SDCCH8_5, 3 }, + { L1SCHED_SACCH8_1, 3, L1SCHED_SDCCH8_6, 0 }, + { L1SCHED_SACCH8_2, 0, L1SCHED_SDCCH8_6, 1 }, + { L1SCHED_SACCH8_2, 1, L1SCHED_SDCCH8_6, 2 }, + { L1SCHED_SACCH8_2, 2, L1SCHED_SDCCH8_6, 3 }, + { L1SCHED_SACCH8_2, 3, L1SCHED_SDCCH8_7, 0 }, + { L1SCHED_SACCH8_3, 0, L1SCHED_SDCCH8_7, 1 }, + { L1SCHED_SACCH8_3, 1, L1SCHED_SDCCH8_7, 2 }, + { L1SCHED_SACCH8_3, 2, L1SCHED_SDCCH8_7, 3 }, + { L1SCHED_SACCH8_3, 3, L1SCHED_SACCH8_0, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_0, 1 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_0, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_0, 3 }, + + { L1SCHED_SDCCH8_0, 0, L1SCHED_SACCH8_1, 0 }, + { L1SCHED_SDCCH8_0, 1, L1SCHED_SACCH8_1, 1 }, + { L1SCHED_SDCCH8_0, 2, L1SCHED_SACCH8_1, 2 }, + { L1SCHED_SDCCH8_0, 3, L1SCHED_SACCH8_1, 3 }, + { L1SCHED_SDCCH8_1, 0, L1SCHED_SACCH8_2, 0 }, + { L1SCHED_SDCCH8_1, 1, L1SCHED_SACCH8_2, 1 }, + { L1SCHED_SDCCH8_1, 2, L1SCHED_SACCH8_2, 2 }, + { L1SCHED_SDCCH8_1, 3, L1SCHED_SACCH8_2, 3 }, + { L1SCHED_SDCCH8_2, 0, L1SCHED_SACCH8_3, 0 }, + { L1SCHED_SDCCH8_2, 1, L1SCHED_SACCH8_3, 1 }, + { L1SCHED_SDCCH8_2, 2, L1SCHED_SACCH8_3, 2 }, + { L1SCHED_SDCCH8_2, 3, L1SCHED_SACCH8_3, 3 }, + { L1SCHED_SDCCH8_3, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 1, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 2, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 3, L1SCHED_SDCCH8_0, 0 }, + { L1SCHED_SDCCH8_4, 0, L1SCHED_SDCCH8_0, 1 }, + { L1SCHED_SDCCH8_4, 1, L1SCHED_SDCCH8_0, 2 }, + { L1SCHED_SDCCH8_4, 2, L1SCHED_SDCCH8_0, 3 }, + { L1SCHED_SDCCH8_4, 3, L1SCHED_SDCCH8_1, 0 }, + { L1SCHED_SDCCH8_5, 0, L1SCHED_SDCCH8_1, 1 }, + { L1SCHED_SDCCH8_5, 1, L1SCHED_SDCCH8_1, 2 }, + { L1SCHED_SDCCH8_5, 2, L1SCHED_SDCCH8_1, 3 }, + { L1SCHED_SDCCH8_5, 3, L1SCHED_SDCCH8_2, 0 }, + { L1SCHED_SDCCH8_6, 0, L1SCHED_SDCCH8_2, 1 }, + { L1SCHED_SDCCH8_6, 1, L1SCHED_SDCCH8_2, 2 }, + { L1SCHED_SDCCH8_6, 2, L1SCHED_SDCCH8_2, 3 }, + { L1SCHED_SDCCH8_6, 3, L1SCHED_SDCCH8_3, 0 }, + { L1SCHED_SDCCH8_7, 0, L1SCHED_SDCCH8_3, 1 }, + { L1SCHED_SDCCH8_7, 1, L1SCHED_SDCCH8_3, 2 }, + { L1SCHED_SDCCH8_7, 2, L1SCHED_SDCCH8_3, 3 }, + { L1SCHED_SDCCH8_7, 3, L1SCHED_SDCCH8_4, 0 }, + { L1SCHED_SACCH8_4, 0, L1SCHED_SDCCH8_4, 1 }, + { L1SCHED_SACCH8_4, 1, L1SCHED_SDCCH8_4, 2 }, + { L1SCHED_SACCH8_4, 2, L1SCHED_SDCCH8_4, 3 }, + { L1SCHED_SACCH8_4, 3, L1SCHED_SDCCH8_5, 0 }, + { L1SCHED_SACCH8_5, 0, L1SCHED_SDCCH8_5, 1 }, + { L1SCHED_SACCH8_5, 1, L1SCHED_SDCCH8_5, 2 }, + { L1SCHED_SACCH8_5, 2, L1SCHED_SDCCH8_5, 3 }, + { L1SCHED_SACCH8_5, 3, L1SCHED_SDCCH8_6, 0 }, + { L1SCHED_SACCH8_6, 0, L1SCHED_SDCCH8_6, 1 }, + { L1SCHED_SACCH8_6, 1, L1SCHED_SDCCH8_6, 2 }, + { L1SCHED_SACCH8_6, 2, L1SCHED_SDCCH8_6, 3 }, + { L1SCHED_SACCH8_6, 3, L1SCHED_SDCCH8_7, 0 }, + { L1SCHED_SACCH8_7, 0, L1SCHED_SDCCH8_7, 1 }, + { L1SCHED_SACCH8_7, 1, L1SCHED_SDCCH8_7, 2 }, + { L1SCHED_SACCH8_7, 2, L1SCHED_SDCCH8_7, 3 }, + { L1SCHED_SACCH8_7, 3, L1SCHED_SACCH8_4, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_4, 1 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_4, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_4, 3 }, +}; + +static const struct l1sched_tdma_frame frame_sdcch8_cbch[102] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_SDCCH8_0, 0, L1SCHED_SACCH8_5, 0 }, + { L1SCHED_SDCCH8_0, 1, L1SCHED_SACCH8_5, 1 }, + { L1SCHED_SDCCH8_0, 2, L1SCHED_SACCH8_5, 2 }, + { L1SCHED_SDCCH8_0, 3, L1SCHED_SACCH8_5, 3 }, + { L1SCHED_SDCCH8_1, 0, L1SCHED_SACCH8_6, 0 }, + { L1SCHED_SDCCH8_1, 1, L1SCHED_SACCH8_6, 1 }, + { L1SCHED_SDCCH8_1, 2, L1SCHED_SACCH8_6, 2 }, + { L1SCHED_SDCCH8_1, 3, L1SCHED_SACCH8_6, 3 }, + { L1SCHED_SDCCH8_CBCH, 0, L1SCHED_SACCH8_7, 0 }, + { L1SCHED_SDCCH8_CBCH, 1, L1SCHED_SACCH8_7, 1 }, + { L1SCHED_SDCCH8_CBCH, 2, L1SCHED_SACCH8_7, 2 }, + { L1SCHED_SDCCH8_CBCH, 3, L1SCHED_SACCH8_7, 3 }, + { L1SCHED_SDCCH8_3, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 1, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 2, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 3, L1SCHED_SDCCH8_0, 0 }, + { L1SCHED_SDCCH8_4, 0, L1SCHED_SDCCH8_0, 1 }, + { L1SCHED_SDCCH8_4, 1, L1SCHED_SDCCH8_0, 2 }, + { L1SCHED_SDCCH8_4, 2, L1SCHED_SDCCH8_0, 3 }, + { L1SCHED_SDCCH8_4, 3, L1SCHED_SDCCH8_1, 0 }, + { L1SCHED_SDCCH8_5, 0, L1SCHED_SDCCH8_1, 1 }, + { L1SCHED_SDCCH8_5, 1, L1SCHED_SDCCH8_1, 2 }, + { L1SCHED_SDCCH8_5, 2, L1SCHED_SDCCH8_1, 3 }, + { L1SCHED_SDCCH8_5, 3, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_6, 0, L1SCHED_IDLE, 1 }, + { L1SCHED_SDCCH8_6, 1, L1SCHED_IDLE, 2 }, + { L1SCHED_SDCCH8_6, 2, L1SCHED_IDLE, 3 }, + { L1SCHED_SDCCH8_6, 3, L1SCHED_SDCCH8_3, 0 }, + { L1SCHED_SDCCH8_7, 0, L1SCHED_SDCCH8_3, 1 }, + { L1SCHED_SDCCH8_7, 1, L1SCHED_SDCCH8_3, 2 }, + { L1SCHED_SDCCH8_7, 2, L1SCHED_SDCCH8_3, 3 }, + { L1SCHED_SDCCH8_7, 3, L1SCHED_SDCCH8_4, 0 }, + { L1SCHED_SACCH8_0, 0, L1SCHED_SDCCH8_4, 1 }, + { L1SCHED_SACCH8_0, 1, L1SCHED_SDCCH8_4, 2 }, + { L1SCHED_SACCH8_0, 2, L1SCHED_SDCCH8_4, 3 }, + { L1SCHED_SACCH8_0, 3, L1SCHED_SDCCH8_5, 0 }, + { L1SCHED_SACCH8_1, 0, L1SCHED_SDCCH8_5, 1 }, + { L1SCHED_SACCH8_1, 1, L1SCHED_SDCCH8_5, 2 }, + { L1SCHED_SACCH8_1, 2, L1SCHED_SDCCH8_5, 3 }, + { L1SCHED_SACCH8_1, 3, L1SCHED_SDCCH8_6, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_SDCCH8_6, 1 }, + { L1SCHED_IDLE, 1, L1SCHED_SDCCH8_6, 2 }, + { L1SCHED_IDLE, 2, L1SCHED_SDCCH8_6, 3 }, + { L1SCHED_IDLE, 3, L1SCHED_SDCCH8_7, 0 }, + { L1SCHED_SACCH8_3, 0, L1SCHED_SDCCH8_7, 1 }, + { L1SCHED_SACCH8_3, 1, L1SCHED_SDCCH8_7, 2 }, + { L1SCHED_SACCH8_3, 2, L1SCHED_SDCCH8_7, 3 }, + { L1SCHED_SACCH8_3, 3, L1SCHED_SACCH8_0, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_0, 1 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_0, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_0, 3 }, + + { L1SCHED_SDCCH8_0, 0, L1SCHED_SACCH8_1, 0 }, + { L1SCHED_SDCCH8_0, 1, L1SCHED_SACCH8_1, 1 }, + { L1SCHED_SDCCH8_0, 2, L1SCHED_SACCH8_1, 2 }, + { L1SCHED_SDCCH8_0, 3, L1SCHED_SACCH8_1, 3 }, + { L1SCHED_SDCCH8_1, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_1, 1, L1SCHED_IDLE, 1 }, + { L1SCHED_SDCCH8_1, 2, L1SCHED_IDLE, 2 }, + { L1SCHED_SDCCH8_1, 3, L1SCHED_IDLE, 3 }, + { L1SCHED_SDCCH8_CBCH, 0, L1SCHED_SACCH8_3, 0 }, + { L1SCHED_SDCCH8_CBCH, 1, L1SCHED_SACCH8_3, 1 }, + { L1SCHED_SDCCH8_CBCH, 2, L1SCHED_SACCH8_3, 2 }, + { L1SCHED_SDCCH8_CBCH, 3, L1SCHED_SACCH8_3, 3 }, + { L1SCHED_SDCCH8_3, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 1, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 2, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_3, 3, L1SCHED_SDCCH8_0, 0 }, + { L1SCHED_SDCCH8_4, 0, L1SCHED_SDCCH8_0, 1 }, + { L1SCHED_SDCCH8_4, 1, L1SCHED_SDCCH8_0, 2 }, + { L1SCHED_SDCCH8_4, 2, L1SCHED_SDCCH8_0, 3 }, + { L1SCHED_SDCCH8_4, 3, L1SCHED_SDCCH8_1, 0 }, + { L1SCHED_SDCCH8_5, 0, L1SCHED_SDCCH8_1, 1 }, + { L1SCHED_SDCCH8_5, 1, L1SCHED_SDCCH8_1, 2 }, + { L1SCHED_SDCCH8_5, 2, L1SCHED_SDCCH8_1, 3 }, + { L1SCHED_SDCCH8_5, 3, L1SCHED_IDLE, 0 }, + { L1SCHED_SDCCH8_6, 0, L1SCHED_IDLE, 1 }, + { L1SCHED_SDCCH8_6, 1, L1SCHED_IDLE, 2 }, + { L1SCHED_SDCCH8_6, 2, L1SCHED_IDLE, 3 }, + { L1SCHED_SDCCH8_6, 3, L1SCHED_SDCCH8_3, 0 }, + { L1SCHED_SDCCH8_7, 0, L1SCHED_SDCCH8_3, 1 }, + { L1SCHED_SDCCH8_7, 1, L1SCHED_SDCCH8_3, 2 }, + { L1SCHED_SDCCH8_7, 2, L1SCHED_SDCCH8_3, 3 }, + { L1SCHED_SDCCH8_7, 3, L1SCHED_SDCCH8_4, 0 }, + { L1SCHED_SACCH8_4, 0, L1SCHED_SDCCH8_4, 1 }, + { L1SCHED_SACCH8_4, 1, L1SCHED_SDCCH8_4, 2 }, + { L1SCHED_SACCH8_4, 2, L1SCHED_SDCCH8_4, 3 }, + { L1SCHED_SACCH8_4, 3, L1SCHED_SDCCH8_5, 0 }, + { L1SCHED_SACCH8_5, 0, L1SCHED_SDCCH8_5, 1 }, + { L1SCHED_SACCH8_5, 1, L1SCHED_SDCCH8_5, 2 }, + { L1SCHED_SACCH8_5, 2, L1SCHED_SDCCH8_5, 3 }, + { L1SCHED_SACCH8_5, 3, L1SCHED_SDCCH8_6, 0 }, + { L1SCHED_SACCH8_6, 0, L1SCHED_SDCCH8_6, 1 }, + { L1SCHED_SACCH8_6, 1, L1SCHED_SDCCH8_6, 2 }, + { L1SCHED_SACCH8_6, 2, L1SCHED_SDCCH8_6, 3 }, + { L1SCHED_SACCH8_6, 3, L1SCHED_SDCCH8_7, 0 }, + { L1SCHED_SACCH8_7, 0, L1SCHED_SDCCH8_7, 1 }, + { L1SCHED_SACCH8_7, 1, L1SCHED_SDCCH8_7, 2 }, + { L1SCHED_SACCH8_7, 2, L1SCHED_SDCCH8_7, 3 }, + { L1SCHED_SACCH8_7, 3, L1SCHED_SACCH8_4, 0 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_4, 1 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_4, 2 }, + { L1SCHED_IDLE, 0, L1SCHED_SACCH8_4, 3 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts0[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts1[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts2[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts3[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts4[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts5[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts6[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, +}; + +static const struct l1sched_tdma_frame frame_tchf_ts7[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 1, L1SCHED_SACCHTF, 1 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 2, L1SCHED_SACCHTF, 2 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 3, L1SCHED_SACCHTF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_TCHF, 0, L1SCHED_TCHF, 0 }, + { L1SCHED_TCHF, 1, L1SCHED_TCHF, 1 }, + { L1SCHED_TCHF, 2, L1SCHED_TCHF, 2 }, + { L1SCHED_TCHF, 3, L1SCHED_TCHF, 3 }, + { L1SCHED_SACCHTF, 0, L1SCHED_SACCHTF, 0 }, +}; + +static const struct l1sched_tdma_frame frame_tchh_ts01[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 0, L1SCHED_SACCHTH_0, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 0, L1SCHED_SACCHTH_1, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 1, L1SCHED_SACCHTH_0, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 1, L1SCHED_SACCHTH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 2, L1SCHED_SACCHTH_0, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 2, L1SCHED_SACCHTH_1, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 3, L1SCHED_SACCHTH_0, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 3, L1SCHED_SACCHTH_1, 3 }, +}; + +static const struct l1sched_tdma_frame frame_tchh_ts23[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 3, L1SCHED_SACCHTH_0, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 3, L1SCHED_SACCHTH_1, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 0, L1SCHED_SACCHTH_0, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 0, L1SCHED_SACCHTH_1, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 1, L1SCHED_SACCHTH_0, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 1, L1SCHED_SACCHTH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 2, L1SCHED_SACCHTH_0, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 2, L1SCHED_SACCHTH_1, 2 }, +}; + +static const struct l1sched_tdma_frame frame_tchh_ts45[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 2, L1SCHED_SACCHTH_0, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 2, L1SCHED_SACCHTH_1, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 3, L1SCHED_SACCHTH_0, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 3, L1SCHED_SACCHTH_1, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 0, L1SCHED_SACCHTH_0, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 0, L1SCHED_SACCHTH_1, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 1, L1SCHED_SACCHTH_0, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 1, L1SCHED_SACCHTH_1, 1 }, +}; + +static const struct l1sched_tdma_frame frame_tchh_ts67[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 1, L1SCHED_SACCHTH_0, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 1, L1SCHED_SACCHTH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 2, L1SCHED_SACCHTH_0, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 2, L1SCHED_SACCHTH_1, 2 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 3, L1SCHED_SACCHTH_0, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 3, L1SCHED_SACCHTH_1, 3 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_0, 0, L1SCHED_SACCHTH_0, 0 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_TCHH_0, 0, L1SCHED_TCHH_0, 0 }, + { L1SCHED_TCHH_1, 0, L1SCHED_TCHH_1, 0 }, + { L1SCHED_TCHH_0, 1, L1SCHED_TCHH_0, 1 }, + { L1SCHED_TCHH_1, 1, L1SCHED_TCHH_1, 1 }, + { L1SCHED_SACCHTH_1, 0, L1SCHED_SACCHTH_1, 0 }, +}; + +static const struct l1sched_tdma_frame frame_pdch[104] = { + /* dl_chan dl_bid ul_chan ul_bid */ + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PTCCH, 0, L1SCHED_PTCCH, 0 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PTCCH, 1, L1SCHED_PTCCH, 1 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PTCCH, 2, L1SCHED_PTCCH, 2 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PTCCH, 3, L1SCHED_PTCCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_PDTCH, 0, L1SCHED_PDTCH, 0 }, + { L1SCHED_PDTCH, 1, L1SCHED_PDTCH, 1 }, + { L1SCHED_PDTCH, 2, L1SCHED_PDTCH, 2 }, + { L1SCHED_PDTCH, 3, L1SCHED_PDTCH, 3 }, + { L1SCHED_IDLE, 0, L1SCHED_IDLE, 0 }, +}; + +/* Logical channel mask for a single channel */ +#define M64(x) \ + ((uint64_t) 0x01 << x) + +/* Logical channel mask for BCCH+CCCH */ +#define M64_BCCH_CCCH \ + (uint64_t) 0x00 \ + | M64(L1SCHED_FCCH) \ + | M64(L1SCHED_SCH) \ + | M64(L1SCHED_BCCH) \ + | M64(L1SCHED_RACH) \ + | M64(L1SCHED_CCCH) + +/* Logical channel mask for SDCCH4 (with SACCH, all sub-channels) */ +#define M64_SDCCH4 \ + (uint64_t) 0x00 \ + | M64(L1SCHED_SDCCH4_0) | M64(L1SCHED_SACCH4_0) \ + | M64(L1SCHED_SDCCH4_1) | M64(L1SCHED_SACCH4_1) \ + | M64(L1SCHED_SDCCH4_2) | M64(L1SCHED_SACCH4_2) \ + | M64(L1SCHED_SDCCH4_3) | M64(L1SCHED_SACCH4_3) + +/* Logical channel mask for SDCCH8 (with SACCH, all sub-channels) */ +#define M64_SDCCH8 \ + (uint64_t) 0x00 \ + | M64(L1SCHED_SDCCH8_0) | M64(L1SCHED_SACCH8_0) \ + | M64(L1SCHED_SDCCH8_1) | M64(L1SCHED_SACCH8_1) \ + | M64(L1SCHED_SDCCH8_2) | M64(L1SCHED_SACCH8_2) \ + | M64(L1SCHED_SDCCH8_3) | M64(L1SCHED_SACCH8_3) \ + | M64(L1SCHED_SDCCH8_4) | M64(L1SCHED_SACCH8_4) \ + | M64(L1SCHED_SDCCH8_5) | M64(L1SCHED_SACCH8_5) \ + | M64(L1SCHED_SDCCH8_6) | M64(L1SCHED_SACCH8_6) \ + | M64(L1SCHED_SDCCH8_7) | M64(L1SCHED_SACCH8_7) + +/* Logical channel mask for TCH/F (with SACCH) */ +#define M64_TCHF \ + (uint64_t) 0x00 \ + | M64(L1SCHED_TCHF) | M64(L1SCHED_SACCHTF) + +/* Logical channel mask for TCH/H (with SACCH, all sub-channels) */ +#define M64_TCHH \ + (uint64_t) 0x00 \ + | M64(L1SCHED_TCHH_0) | M64(L1SCHED_SACCHTH_0) \ + | M64(L1SCHED_TCHH_1) | M64(L1SCHED_SACCHTH_1) + +/** + * A few notes about frame count: + * + * 26 frame multiframe - traffic multiframe + * 51 frame multiframe - control multiframe + * + * 102 = 2 x 51 frame multiframe + * 104 = 4 x 26 frame multiframe + */ +static const struct l1sched_tdma_multiframe layouts[] = { + { + GSM_PCHAN_NONE, "NONE", + 0, 0xff, + 0x00, + NULL + }, + { + GSM_PCHAN_CCCH, "BCCH+CCCH", + 51, 0xff, + M64_BCCH_CCCH, + frame_bcch + }, + { + GSM_PCHAN_CCCH_SDCCH4, "BCCH+CCCH+SDCCH/4+SACCH/4", + 102, 0xff, + M64_BCCH_CCCH | M64_SDCCH4, + frame_bcch_sdcch4 + }, + { + GSM_PCHAN_CCCH_SDCCH4_CBCH, "BCCH+CCCH+SDCCH/4+SACCH/4+CBCH", + 102, 0xff, + M64_BCCH_CCCH | M64_SDCCH4 | M64(L1SCHED_SDCCH4_CBCH), + frame_bcch_sdcch4_cbch + }, + { + GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH/8+SACCH/8", + 102, 0xff, + M64_SDCCH8, + frame_sdcch8 + }, + { + GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH/8+SACCH/8+CBCH", + 102, 0xff, + M64_SDCCH8 | M64(L1SCHED_SDCCH8_CBCH), + frame_sdcch8_cbch + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x01, + M64_TCHF, + frame_tchf_ts0 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x02, + M64_TCHF, + frame_tchf_ts1 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x04, + M64_TCHF, + frame_tchf_ts2 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x08, + M64_TCHF, + frame_tchf_ts3 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x10, + M64_TCHF, + frame_tchf_ts4 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x20, + M64_TCHF, + frame_tchf_ts5 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x40, + M64_TCHF, + frame_tchf_ts6 + }, + { + GSM_PCHAN_TCH_F, "TCH/F+SACCH", + 104, 0x80, + M64_TCHF, + frame_tchf_ts7 + }, + { + GSM_PCHAN_TCH_H, "TCH/H+SACCH", + 104, 0x03, + M64_TCHH, + frame_tchh_ts01 + }, + { + GSM_PCHAN_TCH_H, "TCH/H+SACCH", + 104, 0x0c, + M64_TCHH, + frame_tchh_ts23 + }, + { + GSM_PCHAN_TCH_H, "TCH/H+SACCH", + 104, 0x30, + M64_TCHH, + frame_tchh_ts45 + }, + { + GSM_PCHAN_TCH_H, "TCH/H+SACCH", + 104, 0xc0, + M64_TCHH, + frame_tchh_ts67 + }, + { + GSM_PCHAN_PDCH, "PDCH", + 104, 0xff, + M64(L1SCHED_PDTCH) | M64(L1SCHED_PTCCH), + frame_pdch + }, +}; + +const struct l1sched_tdma_multiframe * +l1sched_mframe_layout(enum gsm_phys_chan_config config, uint8_t tn) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(layouts); i++) { + if (layouts[i].chan_config != config) + continue; + if (~layouts[i].slotmask & (1 << tn)) + continue; + return &layouts[i]; + } + + return NULL; +} diff --git a/src/host/trxcon/src/sched_prim.c b/src/host/trxcon/src/sched_prim.c new file mode 100644 index 00000000..67be75e5 --- /dev/null +++ b/src/host/trxcon/src/sched_prim.c @@ -0,0 +1,410 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: primitive management + * + * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com> + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <talloc.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/prim.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/linuxlist.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> + +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> + +#define L1SCHED_PRIM_HEADROOM 64 +#define L1SCHED_PRIM_TAILROOM 512 + +osmo_static_assert(sizeof(struct l1sched_prim) <= L1SCHED_PRIM_HEADROOM, l1sched_prim_size); + +const struct value_string l1sched_prim_type_names[] = { + { L1SCHED_PRIM_T_DATA, "DATA" }, + { L1SCHED_PRIM_T_RACH, "RACH" }, + { L1SCHED_PRIM_T_SCH, "SCH" }, + { L1SCHED_PRIM_T_PCHAN_COMB, "PCHAN_COMB" }, + { 0, NULL }, +}; + +void l1sched_prim_init(struct msgb *msg, + enum l1sched_prim_type type, + enum osmo_prim_operation op) +{ + struct l1sched_prim *prim; + + msg->l2h = msg->data; + msg->l1h = msgb_push(msg, sizeof(*prim)); + + prim = l1sched_prim_from_msgb(msg); + osmo_prim_init(&prim->oph, 0, type, op, msg); +} + +struct msgb *l1sched_prim_alloc(enum l1sched_prim_type type, + enum osmo_prim_operation op) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(L1SCHED_PRIM_HEADROOM + L1SCHED_PRIM_TAILROOM, + L1SCHED_PRIM_HEADROOM, "l1sched_prim"); + if (msg == NULL) + return NULL; + + l1sched_prim_init(msg, type, op); + + return msg; +} + +/** + * Composes a new primitive from cached RR Measurement Report. + * + * @param lchan lchan to assign a primitive + * @return SACCH primitive to be transmitted + */ +static struct msgb *prim_compose_mr(struct l1sched_lchan_state *lchan) +{ + struct l1sched_prim *prim; + struct msgb *msg; + bool cached; + + /* Allocate a new primitive */ + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->data_req = (struct l1sched_prim_chdr) { + .chan_nr = l1sched_lchan_desc[lchan->type].chan_nr | lchan->ts->index, + .link_id = L1SCHED_CH_LID_SACCH, + }; + + /* Check if the MR cache is populated (verify LAPDm header) */ + cached = (lchan->sacch.mr_cache[2] != 0x00 + && lchan->sacch.mr_cache[3] != 0x00 + && lchan->sacch.mr_cache[4] != 0x00); + if (!cached) { + memcpy(&lchan->sacch.mr_cache[0], + &lchan->ts->sched->sacch_cache[0], + sizeof(lchan->sacch.mr_cache)); + } + + /* Compose a new Measurement Report primitive */ + memcpy(msgb_put(msg, GSM_MACBLOCK_LEN), + &lchan->sacch.mr_cache[0], + GSM_MACBLOCK_LEN); + + /* Inform about the cache usage count */ + if (++lchan->sacch.mr_cache_usage > 5) { + LOGP_LCHAND(lchan, LOGL_NOTICE, + "SACCH MR cache usage count=%u > 5 " + "=> ancient measurements, please fix!\n", + lchan->sacch.mr_cache_usage); + } + + LOGP_LCHAND(lchan, LOGL_NOTICE, "Using cached Measurement Report\n"); + + return msg; +} + +/** + * Dequeues a SACCH primitive from transmit queue, if present. + * Otherwise dequeues a cached Measurement Report (the last + * received one). Finally, if the cache is empty, a "dummy" + * measurement report is used. + * + * According to 3GPP TS 04.08, section 3.4.1, SACCH channel + * accompanies either a traffic or a signaling channel. It + * has the particularity that continuous transmission must + * occur in both directions, so on the Uplink direction + * measurement result messages are sent at each possible + * occasion when nothing else has to be sent. The LAPDm + * fill frames (0x01, 0x03, 0x01, 0x2b, ...) are not + * applicable on SACCH channels! + * + * Unfortunately, 3GPP TS 04.08 doesn't clearly state + * which "else messages" besides Measurement Reports + * can be send by the MS on SACCH channels. However, + * in sub-clause 3.4.1 it's stated that the interval + * between two successive measurement result messages + * shall not exceed one L2 frame. + * + * @param lchan lchan to assign a primitive + * @return SACCH primitive to be transmitted + */ +struct msgb *l1sched_lchan_prim_dequeue_sacch(struct l1sched_lchan_state *lchan) +{ + struct msgb *msg_nmr = NULL; + struct msgb *msg_mr = NULL; + struct msgb *msg; + bool mr_now; + + /* Shall we transmit MR now? */ + mr_now = !lchan->sacch.mr_tx_last; + +#define PRIM_MSGB_IS_MR(msg) \ + (l1sched_prim_data_from_msgb(msg)[5] == GSM48_PDISC_RR && \ + l1sched_prim_data_from_msgb(msg)[6] == GSM48_MT_RR_MEAS_REP) + + /* Iterate over all primitives in the queue */ + llist_for_each_entry(msg, &lchan->tx_prims, list) { + /* Look for a Measurement Report */ + if (!msg_mr && PRIM_MSGB_IS_MR(msg)) + msg_mr = msg; + + /* Look for anything else */ + if (!msg_nmr && !PRIM_MSGB_IS_MR(msg)) + msg_nmr = msg; + + /* Should we look further? */ + if (mr_now && msg_mr) + break; /* MR was found */ + else if (!mr_now && msg_nmr) + break; /* something else was found */ + } + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "SACCH MR selection: mr_tx_last=%d msg_mr=%p msg_nmr=%p\n", + lchan->sacch.mr_tx_last, msg_mr, msg_nmr); + + /* Prioritize non-MR prim if possible */ + if (mr_now && msg_mr) + msg = msg_mr; + else if (!mr_now && msg_nmr) + msg = msg_nmr; + else if (!mr_now && msg_mr) + msg = msg_mr; + else /* Nothing was found */ + msg = NULL; + + /* Have we found what we were looking for? */ + if (msg) /* Dequeue if so */ + llist_del(&msg->list); + else /* Otherwise compose a new MR */ + msg = prim_compose_mr(lchan); + + /* Update the cached report */ + if (msg == msg_mr) { + memcpy(lchan->sacch.mr_cache, msgb_l2(msg), GSM_MACBLOCK_LEN); + lchan->sacch.mr_cache_usage = 0; + + LOGP_LCHAND(lchan, LOGL_DEBUG, "SACCH MR cache has been updated\n"); + } + + /* Update the MR transmission state */ + lchan->sacch.mr_tx_last = PRIM_MSGB_IS_MR(msg); + + LOGP_LCHAND(lchan, LOGL_DEBUG, "SACCH decision: %s\n", + PRIM_MSGB_IS_MR(msg) ? "Measurement Report" : "data frame"); + + return msg; +} + +/** + * Dequeues either a FACCH, or a speech TCH primitive + * of a given channel type (Lm or Bm). + * + * @param lchan logical channel state + * @param facch FACCH (true) or speech (false) prim? + * @return either a FACCH, or a TCH primitive if found, + * otherwise NULL + */ +struct msgb *l1sched_lchan_prim_dequeue_tch(struct l1sched_lchan_state *lchan, bool facch) +{ + struct msgb *msg; + + /** + * There is no need to use the 'safe' list iteration here + * as an item removal is immediately followed by return. + */ + llist_for_each_entry(msg, &lchan->tx_prims, list) { + bool is_facch = msgb_l2len(msg) == GSM_MACBLOCK_LEN; + if (is_facch != facch) + continue; + + llist_del(&msg->list); + return msg; + } + + return NULL; +} + +/** + * Allocate a DATA.req with dummy LAPDm func=UI frame for the given logical channel. + * To be used when no suitable DATA.req is present in the Tx queue. + * + * @param lchan lchan to allocate a dummy primitive for + * @return an msgb with DATA.req primitive, or NULL + */ +struct msgb *l1sched_lchan_prim_dummy_lapdm(const struct l1sched_lchan_state *lchan) +{ + struct l1sched_prim *prim; + struct msgb *msg; + uint8_t *ptr; + + /* LAPDm func=UI is not applicable for SACCH */ + OSMO_ASSERT(!L1SCHED_CHAN_IS_SACCH(lchan->type)); + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->data_req = (struct l1sched_prim_chdr) { + .chan_nr = l1sched_lchan_desc[lchan->type].chan_nr | lchan->ts->index, + .link_id = l1sched_lchan_desc[lchan->type].link_id, + }; + + ptr = msgb_put(msg, GSM_MACBLOCK_LEN); + + /** + * TS 144.006, section 8.4.2.3 "Fill frames" + * A fill frame is a UI command frame for SAPI 0, P=0 + * and with an information field of 0 octet length. + */ + *(ptr++) = 0x01; + *(ptr++) = 0x03; + *(ptr++) = 0x01; + + /** + * TS 144.006, section 5.2 "Frame delimitation and fill bits" + * Except for the first octet containing fill bits which shall + * be set to the binary value "00101011", each fill bit should + * be set to a random value when sent by the network. + */ + *(ptr++) = 0x2b; + while (ptr < msg->tail) + *(ptr++) = (uint8_t)rand(); + + return msg; +} + +int l1sched_lchan_emit_data_ind(struct l1sched_lchan_state *lchan, + const uint8_t *data, size_t data_len, + int n_errors, int n_bits_total, + bool traffic) +{ + const struct l1sched_meas_set *meas = &lchan->meas_avg; + const struct l1sched_lchan_desc *lchan_desc; + struct l1sched_prim *prim; + struct msgb *msg; + + lchan_desc = &l1sched_lchan_desc[lchan->type]; + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_INDICATION); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->data_ind = (struct l1sched_prim_data_ind) { + .chdr = { + .frame_nr = meas->fn, + .chan_nr = lchan_desc->chan_nr | lchan->ts->index, + .link_id = lchan_desc->link_id, + .traffic = traffic, + }, + .toa256 = meas->toa256, + .rssi = meas->rssi, + .n_errors = n_errors, + .n_bits_total = n_bits_total, + }; + + if (data_len > 0) + memcpy(msgb_put(msg, data_len), data, data_len); + + return l1sched_prim_to_user(lchan->ts->sched, msg); +} + +int l1sched_lchan_emit_data_cnf(struct l1sched_lchan_state *lchan, + struct msgb *msg, uint32_t fn) +{ + struct l1sched_prim *prim; + + OSMO_ASSERT(msg != NULL); + + /* convert from DATA.req to DATA.cnf */ + prim = l1sched_prim_from_msgb(msg); + prim->oph.operation = PRIM_OP_CONFIRM; + + switch (prim->oph.primitive) { + case L1SCHED_PRIM_T_DATA: + prim->data_cnf.frame_nr = fn; + break; + case L1SCHED_PRIM_T_RACH: + prim->rach_cnf.chdr.frame_nr = fn; + break; + default: + /* shall not happen */ + OSMO_ASSERT(0); + } + + return l1sched_prim_to_user(lchan->ts->sched, msg); +} + +static int prim_enqeue(struct l1sched_state *sched, struct msgb *msg, + const struct l1sched_prim_chdr *chdr) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + struct l1sched_lchan_state *lchan; + + lchan = l1sched_find_lchan_by_chan_nr(sched, chdr->chan_nr, chdr->link_id); + if (OSMO_UNLIKELY(lchan == NULL || !lchan->active)) { + LOGP_SCHEDD(sched, LOGL_ERROR, + "No [active] lchan for primitive " L1SCHED_PRIM_STR_FMT " " + "(fn=%u, chan_nr=0x%02x, link_id=0x%02x, len=%u): %s\n", + L1SCHED_PRIM_STR_ARGS(prim), + chdr->frame_nr, chdr->chan_nr, chdr->link_id, + msgb_l2len(msg), msgb_hexdump_l2(msg)); + msgb_free(msg); + return -ENODEV; + } + + LOGP_LCHAND(lchan, LOGL_DEBUG, + "Enqueue primitive " L1SCHED_PRIM_STR_FMT " " + "(fn=%u, chan_nr=0x%02x, link_id=0x%02x, len=%u): %s\n", + L1SCHED_PRIM_STR_ARGS(prim), + chdr->frame_nr, chdr->chan_nr, chdr->link_id, + msgb_l2len(msg), msgb_hexdump_l2(msg)); + + msgb_enqueue(&lchan->tx_prims, msg); + return 0; +} + +int l1sched_prim_from_user(struct l1sched_state *sched, struct msgb *msg) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + + LOGP_SCHEDD(sched, LOGL_DEBUG, + "%s(): Rx " L1SCHED_PRIM_STR_FMT "\n", + __func__, L1SCHED_PRIM_STR_ARGS(prim)); + + switch (OSMO_PRIM_HDR(&prim->oph)) { + case OSMO_PRIM(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST): + return prim_enqeue(sched, msg, &prim->data_req); + case OSMO_PRIM(L1SCHED_PRIM_T_RACH, PRIM_OP_REQUEST): + return prim_enqeue(sched, msg, &prim->rach_req.chdr); + default: + LOGP_SCHEDD(sched, LOGL_ERROR, + "%s(): Unhandled primitive " L1SCHED_PRIM_STR_FMT "\n", + __func__, L1SCHED_PRIM_STR_ARGS(prim)); + msgb_free(msg); + return -ENOTSUP; + } +} diff --git a/src/host/trxcon/src/sched_trx.c b/src/host/trxcon/src/sched_trx.c new file mode 100644 index 00000000..f4124003 --- /dev/null +++ b/src/host/trxcon/src/sched_trx.c @@ -0,0 +1,894 @@ +/* + * OsmocomBB <-> SDR connection bridge + * TDMA scheduler: GSM PHY routines + * + * (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 <error.h> +#include <errno.h> +#include <string.h> +#include <talloc.h> +#include <stdbool.h> + +#include <osmocom/gsm/a5.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/core/bits.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/linuxlist.h> + +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> + +/* 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_ind(struct l1sched_state *sched, + uint8_t tn, enum gsm_phys_chan_config pchan) +{ + struct l1sched_prim *prim; + struct msgb *msg; + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_PCHAN_COMB, PRIM_OP_INDICATION); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->pchan_comb_ind.tn = tn; + prim->pchan_comb_ind.pchan = pchan; + + return l1sched_prim_to_user(sched, msg); +} + +static void l1sched_a5_burst_enc(struct l1sched_lchan_state *lchan, + struct l1sched_burst_req *br); + +/* Pull an Uplink burst from the scheduler and store it to br->burst[]. + * The TDMA Fn advance must be applied by the caller (if needed). + * The given *br must be initialized by the caller. */ +void l1sched_pull_burst(struct l1sched_state *sched, struct l1sched_burst_req *br) +{ + struct l1sched_ts *ts = sched->ts[br->tn]; + const struct l1sched_tdma_frame *frame; + struct l1sched_lchan_state *lchan; + l1sched_lchan_tx_func *handler; + enum l1sched_lchan_type chan; + unsigned int offset; + + /* Check if the given timeslot is configured */ + if (ts == NULL || ts->mf_layout == NULL) + return; + + /* Get frame from multiframe */ + offset = br->fn % ts->mf_layout->period; + frame = &ts->mf_layout->frames[offset]; + + /* Get required info from frame */ + br->bid = frame->ul_bid; + chan = frame->ul_chan; + handler = l1sched_lchan_desc[chan].tx_fn; + + /* Omit lchans without handler */ + if (handler == NULL) + return; + + /* Make sure that lchan is allocated and active */ + lchan = l1sched_find_lchan_by_type(ts, chan); + if (lchan == NULL || !lchan->active) + return; + + /* Handover RACH needs to be handled regardless of the + * current channel type and the associated handler. */ + struct msgb *msg = llist_first_entry_or_null(&lchan->tx_prims, struct msgb, list); + if (msg && l1sched_prim_type_from_msgb(msg) == L1SCHED_PRIM_T_RACH) + handler = l1sched_lchan_desc[L1SCHED_RACH].tx_fn; + + /* Poke lchan handler */ + handler(lchan, br); + + /* Perform A5/X burst encryption if required */ + if (lchan->a5.algo) + l1sched_a5_burst_enc(lchan, br); +} + +void l1sched_logging_init(int log_cat_common, int log_cat_data) +{ + l1sched_log_cat_common = log_cat_common; + l1sched_log_cat_data = log_cat_data; +} + +struct l1sched_state *l1sched_alloc(void *ctx, const struct l1sched_cfg *cfg, void *priv) +{ + struct l1sched_state *sched; + + sched = talloc(ctx, struct l1sched_state); + if (!sched) + return NULL; + + *sched = (struct l1sched_state) { + .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 (tn = 0; tn < ARRAY_SIZE(sched->ts); tn++) + l1sched_del_ts(sched, tn); + + talloc_free(sched); +} + +void l1sched_reset(struct l1sched_state *sched, bool reset_clock) +{ + unsigned int tn; + + if (sched == NULL) + return; + + LOGP_SCHEDC(sched, LOGL_NOTICE, "Reset scheduler %s\n", + reset_clock ? "and clock counter" : ""); + + /* Free all potentially allocated timeslots */ + for (tn = 0; tn < ARRAY_SIZE(sched->ts); tn++) + l1sched_del_ts(sched, tn); + + memcpy(&sched->sacch_cache[0], &meas_rep_dummy[0], sizeof(meas_rep_dummy)); +} + +struct l1sched_ts *l1sched_add_ts(struct l1sched_state *sched, int tn) +{ + /* Make sure that ts isn't allocated yet */ + if (sched->ts[tn] != NULL) { + LOGP_SCHEDC(sched, LOGL_ERROR, "Timeslot #%u already allocated\n", tn); + return NULL; + } + + LOGP_SCHEDC(sched, LOGL_NOTICE, "Add a new TDMA timeslot #%u\n", tn); + + sched->ts[tn] = talloc_zero(sched, struct l1sched_ts); + sched->ts[tn]->sched = sched; + sched->ts[tn]->index = tn; + + return sched->ts[tn]; +} + +void l1sched_del_ts(struct l1sched_state *sched, int tn) +{ + struct l1sched_lchan_state *lchan, *lchan_next; + struct l1sched_ts *ts; + + /* Find ts in list */ + ts = sched->ts[tn]; + if (ts == NULL) + return; + + LOGP_SCHEDC(sched, LOGL_NOTICE, "Delete TDMA timeslot #%u\n", tn); + + /* Deactivate all logical channels */ + l1sched_deactivate_all_lchans(ts); + + /* Free channel states */ + llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) { + llist_del(&lchan->list); + talloc_free(lchan); + } + + /* Remove ts from list and free memory */ + sched->ts[tn] = NULL; + talloc_free(ts); + + /* Notify transceiver about that */ + l1sched_cfg_pchan_comb_ind(sched, tn, GSM_PCHAN_NONE); +} + +#define LAYOUT_HAS_LCHAN(layout, lchan) \ + (layout->lchan_mask & ((uint64_t) 0x01 << lchan)) + +int l1sched_configure_ts(struct l1sched_state *sched, int tn, + enum gsm_phys_chan_config config) +{ + struct l1sched_lchan_state *lchan; + enum l1sched_lchan_type type; + struct l1sched_ts *ts; + + /* Try to find specified ts */ + ts = sched->ts[tn]; + if (ts != NULL) { + /* Reconfiguration of existing one */ + l1sched_reset_ts(sched, tn); + } else { + /* Allocate a new one if doesn't exist */ + ts = l1sched_add_ts(sched, tn); + if (ts == NULL) + return -ENOMEM; + } + + /* Choose proper multiframe layout */ + ts->mf_layout = l1sched_mframe_layout(config, tn); + if (!ts->mf_layout) + return -EINVAL; + if (ts->mf_layout->chan_config != config) + return -EINVAL; + + LOGP_SCHEDC(sched, LOGL_NOTICE, + "(Re)configure TDMA timeslot #%u as %s\n", + tn, ts->mf_layout->name); + + /* Init logical channels list */ + INIT_LLIST_HEAD(&ts->lchans); + + /* Allocate channel states */ + 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 l1sched_lchan_state); + if (!lchan) + return -ENOMEM; + + /* set backpointer */ + lchan->ts = ts; + + /* Set channel type */ + lchan->type = type; + + /* Init the Tx queue */ + INIT_LLIST_HEAD(&lchan->tx_prims); + + /* Add to the list of channel states */ + llist_add_tail(&lchan->list, &ts->lchans); + + /* Enable channel automatically if required */ + if (l1sched_lchan_desc[type].flags & L1SCHED_CH_FLAG_AUTO) + l1sched_activate_lchan(ts, type); + } + + /* Notify transceiver about TS activation */ + l1sched_cfg_pchan_comb_ind(sched, tn, config); + + return 0; +} + +int l1sched_reset_ts(struct l1sched_state *sched, int tn) +{ + struct l1sched_lchan_state *lchan, *lchan_next; + struct l1sched_ts *ts; + + /* Try to find specified ts */ + ts = sched->ts[tn]; + if (ts == NULL) + return -EINVAL; + + /* Undefine multiframe layout */ + ts->mf_layout = NULL; + + /* Deactivate all logical channels */ + l1sched_deactivate_all_lchans(ts); + + /* Free channel states */ + llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) { + llist_del(&lchan->list); + talloc_free(lchan); + } + + /* Notify transceiver about that */ + l1sched_cfg_pchan_comb_ind(sched, tn, GSM_PCHAN_NONE); + + return 0; +} + +int l1sched_start_ciphering(struct l1sched_ts *ts, uint8_t algo, + const uint8_t *key, uint8_t key_len) +{ + struct l1sched_lchan_state *lchan; + + /* Prevent NULL-pointer deference */ + if (!ts) + return -EINVAL; + + /* Make sure we can store this key */ + if (key_len > MAX_A5_KEY_LEN) + return -ERANGE; + + /* Iterate over all allocated logical channels */ + llist_for_each_entry(lchan, &ts->lchans, list) { + /* Omit inactive channels */ + if (!lchan->active) + continue; + + /* Set key length and algorithm */ + lchan->a5.key_len = key_len; + lchan->a5.algo = algo; + + /* Copy requested key */ + if (key_len) + memcpy(lchan->a5.key, key, key_len); + } + + return 0; +} + +struct l1sched_lchan_state *l1sched_find_lchan_by_type(struct l1sched_ts *ts, + enum l1sched_lchan_type type) +{ + struct l1sched_lchan_state *lchan; + + llist_for_each_entry(lchan, &ts->lchans, list) + if (lchan->type == type) + return lchan; + + return NULL; +} + +struct l1sched_lchan_state *l1sched_find_lchan_by_chan_nr(struct l1sched_state *sched, + uint8_t chan_nr, uint8_t link_id) +{ + const struct l1sched_ts *ts = sched->ts[chan_nr & 0x07]; + const struct l1sched_lchan_desc *lchan_desc; + struct l1sched_lchan_state *lchan; + + if (ts == NULL) + return NULL; + + llist_for_each_entry(lchan, &ts->lchans, list) { + lchan_desc = &l1sched_lchan_desc[lchan->type]; + if (lchan_desc->chan_nr != (chan_nr & RSL_CHAN_NR_MASK)) + continue; + if (lchan_desc->link_id != link_id) + continue; + return lchan; + } + + return NULL; +} + +int l1sched_set_lchans(struct l1sched_ts *ts, uint8_t chan_nr, + int active, uint8_t tch_mode, uint8_t tsc) +{ + const struct l1sched_lchan_desc *lchan_desc; + struct l1sched_lchan_state *lchan; + int rc = 0; + + /* Prevent NULL-pointer deference */ + OSMO_ASSERT(ts != NULL); + + /* Iterate over all allocated lchans */ + llist_for_each_entry(lchan, &ts->lchans, list) { + lchan_desc = &l1sched_lchan_desc[lchan->type]; + + if (lchan_desc->chan_nr == (chan_nr & RSL_CHAN_NR_MASK)) { + if (active) { + rc |= l1sched_activate_lchan(ts, lchan->type); + lchan->tch_mode = tch_mode; + lchan->tsc = tsc; + } else + rc |= l1sched_deactivate_lchan(ts, lchan->type); + } + } + + return rc; +} + +int l1sched_lchan_set_amr_cfg(struct l1sched_lchan_state *lchan, + uint8_t codecs_bitmask, uint8_t start_codec) +{ + int n = 0; + int acum = 0; + int pos; + + while ((pos = ffs(codecs_bitmask)) != 0) { + acum += pos; + LOGP_LCHANC(lchan, LOGL_DEBUG, "AMR codec[%u] = %u\n", n, acum - 1); + lchan->amr.codec[n++] = acum - 1; + codecs_bitmask >>= pos; + } + if (n == 0) { + LOGP_LCHANC(lchan, LOGL_ERROR, "Empty AMR codec mode bitmask!\n"); + return -EINVAL; + } + + lchan->amr.codecs = n; + lchan->amr.dl_ft = start_codec; + lchan->amr.dl_cmr = start_codec; + lchan->amr.ul_ft = start_codec; + lchan->amr.ul_cmr = start_codec; + + return 0; +} + +int l1sched_activate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan) +{ + const struct l1sched_lchan_desc *lchan_desc = &l1sched_lchan_desc[chan]; + struct l1sched_lchan_state *lchan; + + /* Try to find requested logical channel */ + lchan = l1sched_find_lchan_by_type(ts, chan); + if (lchan == NULL) + return -EINVAL; + + if (lchan->active) { + LOGP_LCHANC(lchan, LOGL_ERROR, "is already activated\n"); + return -EINVAL; + } + + LOGP_LCHANC(lchan, LOGL_NOTICE, "activating\n"); + + /* Conditionally allocate memory for bursts */ + if (lchan_desc->rx_fn && lchan_desc->burst_buf_size > 0) { + lchan->rx_bursts = talloc_zero_size(lchan, + lchan_desc->burst_buf_size); + if (lchan->rx_bursts == NULL) + return -ENOMEM; + } + + if (lchan_desc->tx_fn && lchan_desc->burst_buf_size > 0) { + lchan->tx_bursts = talloc_zero_size(lchan, + lchan_desc->burst_buf_size); + if (lchan->tx_bursts == NULL) + return -ENOMEM; + } + + /* Finally, update channel status */ + lchan->active = 1; + + return 0; +} + +static void l1sched_reset_lchan(struct l1sched_lchan_state *lchan) +{ + struct msgb *msg; + + /* Prevent NULL-pointer deference */ + OSMO_ASSERT(lchan != NULL); + + /* Print some TDMA statistics for Downlink */ + 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 */ + lchan->rx_burst_mask = 0x00; + lchan->tx_burst_mask = 0x00; + + /* Free burst memory */ + talloc_free(lchan->rx_bursts); + talloc_free(lchan->tx_bursts); + + lchan->rx_bursts = NULL; + lchan->tx_bursts = NULL; + + /* Flush the queue of pending Tx prims */ + while ((msg = msgb_dequeue(&lchan->tx_prims)) != NULL) { + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + + LOGP_LCHANC(lchan, LOGL_NOTICE, "%s(): dropping Tx prim (fn=%u): %s\n", + __func__, prim->data_req.frame_nr, msgb_hexdump_l2(msg)); + msgb_free(msg); + } + + /* Channel specific stuff */ + if (L1SCHED_CHAN_IS_TCH(lchan->type)) { + lchan->dl_ongoing_facch = 0; + lchan->ul_facch_blocks = 0; + + lchan->tch_mode = GSM48_CMODE_SIGN; + + /* Reset AMR state */ + memset(&lchan->amr, 0x00, sizeof(lchan->amr)); + } else if (L1SCHED_CHAN_IS_SACCH(lchan->type)) { + /* Reset SACCH state */ + memset(&lchan->sacch, 0x00, sizeof(lchan->sacch)); + } + + /* Reset ciphering state */ + memset(&lchan->a5, 0x00, sizeof(lchan->a5)); + + /* Reset TDMA frame statistics */ + memset(&lchan->tdma, 0x00, sizeof(lchan->tdma)); +} + +int l1sched_deactivate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan) +{ + struct l1sched_lchan_state *lchan; + + /* Try to find requested logical channel */ + lchan = l1sched_find_lchan_by_type(ts, chan); + if (lchan == NULL) + return -EINVAL; + + if (!lchan->active) { + LOGP_LCHANC(lchan, LOGL_ERROR, "is already deactivated\n"); + return -EINVAL; + } + + LOGP_LCHANC(lchan, LOGL_DEBUG, "deactivating\n"); + + /* Reset internal state, free memory */ + l1sched_reset_lchan(lchan); + + /* Update activation flag */ + lchan->active = 0; + + return 0; +} + +void l1sched_deactivate_all_lchans(struct l1sched_ts *ts) +{ + struct l1sched_lchan_state *lchan; + + 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 */ + if (!lchan->active) + continue; + + /* Reset internal state, free memory */ + l1sched_reset_lchan(lchan); + + /* Update activation flag */ + lchan->active = 0; + } +} + +enum gsm_phys_chan_config l1sched_chan_nr2pchan_config(uint8_t chan_nr) +{ + uint8_t cbits = chan_nr >> 3; + + if (cbits == ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs) + return GSM_PCHAN_TCH_F; + else if ((cbits & 0x1e) == ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0)) + return GSM_PCHAN_TCH_H; + else if ((cbits & 0x1c) == ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0)) + return GSM_PCHAN_CCCH_SDCCH4; + else if ((cbits & 0x18) == ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0)) + return GSM_PCHAN_SDCCH8_SACCH8C; + else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH4) + return GSM_PCHAN_CCCH_SDCCH4_CBCH; + else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH8) + return GSM_PCHAN_SDCCH8_SACCH8C_CBCH; + else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH) + return GSM_PCHAN_PDCH; + + return GSM_PCHAN_NONE; +} + +static void l1sched_a5_burst_dec(struct l1sched_lchan_state *lchan, + struct l1sched_burst_ind *bi) +{ + ubit_t ks[114]; + int i; + + /* Generate keystream for a DL burst */ + osmo_a5(lchan->a5.algo, lchan->a5.key, bi->fn, ks, NULL); + + /* Apply keystream over ciphertext */ + for (i = 0; i < 57; i++) { + if (ks[i]) + bi->burst[i + 3] *= -1; + if (ks[i + 57]) + bi->burst[i + 88] *= -1; + } +} + +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, br->fn, NULL, ks); + + /* Apply keystream over plaintext */ + for (i = 0; i < 57; i++) { + br->burst[i + 3] ^= ks[i]; + br->burst[i + 88] ^= ks[i + 57]; + } +} + +static int subst_frame_loss(struct l1sched_lchan_state *lchan, + l1sched_lchan_rx_func *handler, + uint32_t fn) +{ + const struct l1sched_tdma_multiframe *mf; + const struct l1sched_tdma_frame *fp; + int elapsed, i; + + /* Wait until at least one TDMA frame is processed */ + if (lchan->tdma.num_proc == 0) + return -EAGAIN; + + /* Short alias for the current multiframe */ + mf = lchan->ts->mf_layout; + + /* Calculate how many frames elapsed since the last received one. + * The algorithm is based on GSM::FNDelta() from osmo-trx. */ + elapsed = fn - lchan->tdma.last_proc; + if (elapsed >= GSM_TDMA_HYPERFRAME / 2) + elapsed -= GSM_TDMA_HYPERFRAME; + else if (elapsed < -GSM_TDMA_HYPERFRAME / 2) + elapsed += GSM_TDMA_HYPERFRAME; + + /* Check TDMA frame order (wrong order is possible with fake_trx.py, see OS#4658) */ + if (elapsed < 0) { + /* This burst has already been substituted by a dummy burst (all bits set to zero), + * so better drop it. Otherwise we risk to get undefined behavior in handler(). */ + LOGP_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_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_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; + } + + struct l1sched_burst_ind bi = { + .fn = lchan->tdma.last_proc, + .tn = lchan->ts->index, + .toa256 = 0, + .rssi = -120, + .burst = { 0 }, + .burst_len = GSM_NBITS_NB_GMSK_BURST, + }; + + /* Traverse from fp till the current frame */ + for (i = 0; i < elapsed - 1; i++) { + fp = &mf->frames[GSM_TDMA_FN_INC(bi.fn) % mf->period]; + if (fp->dl_chan != lchan->type) + continue; + + LOGP_LCHANC(lchan, LOGL_NOTICE, + "Substituting lost TDMA frame fn=%u\n", bi.fn); + + bi.bid = fp->dl_bid; + handler(lchan, &bi); + + /* Update TDMA frame statistics */ + lchan->tdma.last_proc = bi.fn; + lchan->tdma.num_proc++; + lchan->tdma.num_lost++; + } + + return 0; +} + +int l1sched_handle_rx_burst(struct l1sched_state *sched, + struct l1sched_burst_ind *bi) +{ + struct l1sched_lchan_state *lchan; + const struct l1sched_tdma_frame *frame; + struct l1sched_ts *ts = sched->ts[bi->tn]; + + l1sched_lchan_rx_func *handler; + enum l1sched_lchan_type chan; + uint8_t offset; + int rc; + + /* Check whether required timeslot is allocated and configured */ + if (ts == NULL || ts->mf_layout == NULL) { + LOGP_SCHEDD(sched, LOGL_DEBUG, + "Timeslot #%u isn't configured, ignoring burst...\n", bi->tn); + return -EINVAL; + } + + /* Get frame from multiframe */ + offset = bi->fn % ts->mf_layout->period; + frame = ts->mf_layout->frames + offset; + + /* Get required info from frame */ + bi->bid = frame->dl_bid; + chan = frame->dl_chan; + handler = l1sched_lchan_desc[chan].rx_fn; + + /* Omit bursts which have no handler, like IDLE bursts. + * TODO: handle noise indications during IDLE frames. */ + if (!handler) + return -ENODEV; + + /* Find required channel state */ + lchan = l1sched_find_lchan_by_type(ts, chan); + if (lchan == NULL) + return -ENODEV; + + /* Ensure that channel is active */ + if (!lchan->active) + return 0; + + /* Compensate lost TDMA frames (if any) */ + rc = subst_frame_loss(lchan, handler, bi->fn); + if (rc == -EALREADY) + return rc; + + /* Perform A5/X decryption if required */ + if (lchan->a5.algo) + l1sched_a5_burst_dec(lchan, bi); + + /* Put burst to handler */ + handler(lchan, bi); + + /* Update TDMA frame statistics */ + lchan->tdma.last_proc = bi->fn; + + if (++lchan->tdma.num_proc == 0) { + /* Theoretically, we may have an integer overflow of num_proc counter. + * As a consequence, subst_frame_loss() will be unable to compensate + * one (potentionally lost) Downlink burst. On practice, it would + * happen once in 4615 * 10e-6 * (2 ^ 32 - 1) seconds or ~6 years. */ + LOGP_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 l1sched_handle_rx_probe(struct l1sched_state *sched, + struct l1sched_probe *probe) +{ + struct l1sched_ts *ts = sched->ts[probe->tn]; + const struct l1sched_tdma_frame *frame; + struct l1sched_lchan_state *lchan; + unsigned int offset; + + /* Check whether required timeslot is allocated and configured */ + if (ts == NULL || ts->mf_layout == NULL) + return -EINVAL; + + /* Get frame from multiframe */ + offset = probe->fn % ts->mf_layout->period; + frame = &ts->mf_layout->frames[offset]; + + if (l1sched_lchan_desc[frame->dl_chan].rx_fn == NULL) + return -ENODEV; + + /* Find the appropriate logical channel */ + lchan = l1sched_find_lchan_by_type(ts, frame->dl_chan); + if (lchan == NULL) + return -ENODEV; + + if (lchan->active) + probe->flags |= L1SCHED_PROBE_F_ACTIVE; + + 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 l1sched_lchan_meas_push(struct l1sched_lchan_state *lchan, + const struct l1sched_burst_ind *bi) +{ + 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) + hist->head = MEAS_HIST_FIRST(hist); + else + hist->head++; + + *hist->head = (struct l1sched_meas_set) { + .fn = bi->fn, + .toa256 = bi->toa256, + .rssi = bi->rssi, + }; +} + +/* Calculate the AVG of n measurements from the history */ +void l1sched_lchan_meas_avg(struct l1sched_lchan_state *lchan, unsigned int n) +{ + 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; + + OSMO_ASSERT(n > 0 && n <= ARRAY_SIZE(hist->buf)); + OSMO_ASSERT(meas != NULL); + + /* Traverse backwards up to n entries, calculate the sum */ + for (i = 0; i < n; i++) { + toa256_sum += meas->toa256; + rssi_sum += meas->rssi; + + /* Do not go below the first burst */ + if (i + 1 == n) + break; + + if (meas == MEAS_HIST_FIRST(hist)) + meas = MEAS_HIST_LAST(hist); + else + meas--; + } + + /* Calculate the AVG */ + lchan->meas_avg.toa256 = toa256_sum / n; + lchan->meas_avg.rssi = rssi_sum / n; + + /* As a bonus, store TDMA frame number of the first burst */ + lchan->meas_avg.fn = meas->fn; +} diff --git a/src/host/trxcon/trx_if.c b/src/host/trxcon/src/trx_if.c index 55d70341..fad10264 100644 --- a/src/host/trxcon/trx_if.c +++ b/src/host/trxcon/src/trx_if.c @@ -4,6 +4,7 @@ * * Copyright (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> * Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com> + * Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * * All Rights Reserved * @@ -38,12 +39,17 @@ #include <osmocom/core/fsm.h> #include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/gsm0502.h> -#include "l1ctl.h" -#include "trxcon.h" -#include "trx_if.h" -#include "logging.h" -#include "scheduler.h" +#include <osmocom/bb/trxcon/trx_if.h> +#include <osmocom/bb/trxcon/logging.h> + +#define TRXDv0_HDR_LEN 8 + +#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? */ @@ -52,8 +58,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] = { @@ -62,25 +68,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, @@ -143,13 +150,13 @@ static void trx_ctrl_send(struct trx_instance *trx) tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list); /* 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); /* 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 */ @@ -167,13 +174,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; } @@ -212,7 +219,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) @@ -242,23 +249,22 @@ static int trx_ctrl_cmd(struct trx_instance *trx, int critical, * RSP POWERON <status> */ -int trx_if_cmd_echo(struct trx_instance *trx) +static 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) +static 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) +static 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"); +#if 0 + if (trx->powered_up) return -EAGAIN; - } +#endif return trx_ctrl_cmd(trx, 1, "POWERON", ""); } @@ -273,9 +279,25 @@ 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) +static int trx_if_cmd_setslot(struct trx_instance *trx, + const struct trxcon_phyif_cmdp_setslot *cmdp) { - 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", + cmdp->tn, chan_types[cmdp->pchan]); } /* @@ -290,28 +312,30 @@ int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type) * RSP (RX/TX)TUNE <status> <kHz> */ -int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn) +static int trx_if_cmd_rxtune(struct trx_instance *trx, + const struct trxcon_phyif_cmdp_setfreq_h0 *cmdp) { uint16_t freq10; /* RX is downlink on MS side */ - freq10 = gsm_arfcn2freq10(band_arfcn, 0); + freq10 = gsm_arfcn2freq10(cmdp->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", cmdp->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) +static int trx_if_cmd_txtune(struct trx_instance *trx, + const struct trxcon_phyif_cmdp_setfreq_h0 *cmdp) { uint16_t freq10; /* TX is uplink on MS side */ - freq10 = gsm_arfcn2freq10(band_arfcn, 1); + freq10 = gsm_arfcn2freq10(cmdp->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", cmdp->band_arfcn); return -ENOTSUP; } @@ -330,19 +354,16 @@ int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn) * RSP MEASURE <status> <kHz> <dB> */ -int trx_if_cmd_measure(struct trx_instance *trx, - uint16_t band_arfcn_start, uint16_t band_arfcn_stop) +static int trx_if_cmd_measure(struct trx_instance *trx, + const struct trxcon_phyif_cmdp_measure *cmdp) { 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); + freq10 = gsm_arfcn2freq10(cmdp->band_arfcn, 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", cmdp->band_arfcn); return -ENOTSUP; } @@ -359,23 +380,22 @@ static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp) 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); + if (band_arfcn == 0xffff) { + LOGPFSML(trx->fi, LOGL_ERROR, + "Failed to parse ARFCN from RSP MEASURE: %s\n", resp); return; } - /* Send L1CTL_PM_CONF */ - l1ctl_tx_pm_conf(trx->l1l, band_arfcn, dbm, - band_arfcn == trx->pm_band_arfcn_stop); + const struct trxcon_phyif_rsp rsp = { + .type = TRXCON_PHYIF_CMDT_MEASURE, + .param.measure = { + .band_arfcn = band_arfcn, + .dbm = dbm, + }, + }; - /* Schedule a next measurement */ - if (band_arfcn != trx->pm_band_arfcn_stop) - trx_if_cmd_measure(trx, ++band_arfcn, trx->pm_band_arfcn_stop); + trxcon_phyif_handle_rsp(trx->priv, &rsp); } /* @@ -391,53 +411,70 @@ static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp) * RSP SETTA <status> <TA> */ -int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta) +static int trx_if_cmd_setta(struct trx_instance *trx, + const struct trxcon_phyif_cmdp_setta *cmdp) { - return trx_ctrl_cmd(trx, 0, "SETTA", "%d", ta); + return trx_ctrl_cmd(trx, 0, "SETTA", "%d", cmdp->ta); } /* - * Frequency Hopping parameters indication + * Frequency Hopping parameters indication. + * + * SETFH instructs transceiver to enable frequency hopping mode + * using the given HSN, MAIO, and Mobile Allocation parameters. * - * SETFH instructs transceiver to enable frequency - * hopping mode using the given parameters. - * CMD SETFH <HSN> <MAIO> <CH1> <CH2> [... <CHN>] + * 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) +static int trx_if_cmd_setfh(struct trx_instance *trx, + const struct trxcon_phyif_cmdp_setfreq_h1 *cmdp) { - char ma_buf[100]; + /* 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; - /* No channels, WTF?!? */ - if (!ma_len) + /* Make sure that Mobile Allocation has at least one ARFCN */ + if (!cmdp->ma_len || cmdp->ma == NULL) { + LOGPFSML(trx->fi, LOGL_ERROR, "Mobile Allocation is empty?!?\n"); return -EINVAL; + } - /** - * Compose a sequence of channels (mobile allocation) - * FIXME: the length of a CTRL command is limited to 128 symbols, - * so we may have some problems if there are many channels... - */ - for (i = 0, ptr = ma_buf; i < ma_len; i++) { - /* Append a channel */ - rc = snprintf(ptr, ma_buf + sizeof(ma_buf) - ptr, "%u ", ma[i]); - if (rc < 0) - return rc; + /* Compose a sequence of Rx/Tx frequencies (mobile allocation) */ + for (i = 0, ptr = ma_buf; i < cmdp->ma_len; i++) { + /* Convert ARFCN to a pair of Rx/Tx frequencies (Hz * 10) */ + rx_freq = gsm_arfcn2freq10(cmdp->ma[i], 0); /* Rx: Downlink */ + tx_freq = gsm_arfcn2freq10(cmdp->ma[i], 1); /* Tx: Uplink */ + if (rx_freq == 0xffff || tx_freq == 0xffff) { + LOGPFSML(trx->fi, LOGL_ERROR, "Failed to convert ARFCN %u " + "to a pair of Rx/Tx frequencies\n", + cmdp->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 */ + LOGPFSML(trx->fi, LOGL_ERROR, "Not enough room to encode " + "Mobile Allocation (N=%u)\n", cmdp->ma_len); + return -ENOSPC; + } /* Move pointer */ + ma_buf_len -= rc; ptr += rc; - - /* Prevent buffer overflow */ - if (ptr >= (ma_buf + 100)) - return -EIO; } /* Overwrite the last space */ *(ptr - 1) = '\0'; - return trx_ctrl_cmd(trx, 1, "SETFH", "%u %u %s", hsn, maio, ma_buf); + return trx_ctrl_cmd(trx, 1, "SETFH", "%u %u %s", cmdp->hsn, cmdp->maio, ma_buf); } /* Get response from CTRL socket */ @@ -446,18 +483,18 @@ 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[1500], *p; + 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); + LOGPFSML(trx->fi, LOGL_ERROR, "read() failed with rc=%zd\n", read_len); return read_len; } buf[read_len] = '\0'; 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; } @@ -465,15 +502,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; } @@ -482,7 +518,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; @@ -491,7 +527,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); @@ -502,18 +538,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); @@ -525,11 +561,53 @@ 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; } +int trx_if_handle_phyif_cmd(struct trx_instance *trx, const struct trxcon_phyif_cmd *cmd) +{ + int rc; + + switch (cmd->type) { + case TRXCON_PHYIF_CMDT_RESET: + if ((rc = trx_if_cmd_poweroff(trx)) != 0) + return rc; + rc = trx_if_cmd_echo(trx); + break; + case TRXCON_PHYIF_CMDT_POWERON: + rc = trx_if_cmd_poweron(trx); + break; + case TRXCON_PHYIF_CMDT_POWEROFF: + rc = trx_if_cmd_poweroff(trx); + break; + case TRXCON_PHYIF_CMDT_MEASURE: + rc = trx_if_cmd_measure(trx, &cmd->param.measure); + break; + case TRXCON_PHYIF_CMDT_SETFREQ_H0: + if ((rc = trx_if_cmd_rxtune(trx, &cmd->param.setfreq_h0)) != 0) + return rc; + if ((rc = trx_if_cmd_txtune(trx, &cmd->param.setfreq_h0)) != 0) + return rc; + break; + case TRXCON_PHYIF_CMDT_SETFREQ_H1: + rc = trx_if_cmd_setfh(trx, &cmd->param.setfreq_h1); + break; + case TRXCON_PHYIF_CMDT_SETSLOT: + rc = trx_if_cmd_setslot(trx, &cmd->param.setslot); + break; + case TRXCON_PHYIF_CMDT_SETTA: + rc = trx_if_cmd_setta(trx, &cmd->param.setta); + break; + default: + LOGPFSML(trx->fi, LOGL_ERROR, + "Unhandled PHYIF command type=0x%02x\n", cmd->type); + rc = -ENODEV; + } + + return rc; +} + /* ------------------------------------------------------------------------ */ /* Data interface handlers */ /* ------------------------------------------------------------------------ */ @@ -555,60 +633,93 @@ rsp_error: static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what) { struct trx_instance *trx = ofd->data; - uint8_t buf[256]; - sbit_t bits[148]; - int8_t rssi, tn; - int16_t toa256; - uint32_t fn; + struct trxcon_phyif_burst_ind bi; + uint8_t buf[TRXD_BUF_SIZE]; ssize_t read_len; + sbit_t *burst; read_len = read(ofd->fd, buf, sizeof(buf)); if (read_len <= 0) { - LOGP(DTRXD, LOGL_ERROR, "read() failed with rc=%zd\n", read_len); + strerror_r(errno, (char *)buf, sizeof(buf)); + LOGPFSMSL(trx->fi, DTRXD, LOGL_ERROR, + "read() failed on TRXD with rc=%zd (%s)\n", + read_len, (const char *)buf); return read_len; } - if (read_len != 158) { - LOGP(DTRXD, LOGL_ERROR, "Got data message with invalid " - "length '%zd'\n", read_len); + if (read_len < TRXDv0_HDR_LEN) { + LOGPFSMSL(trx->fi, DTRXD, LOGL_ERROR, + "Got malformed TRXD PDU (short length=%zd)\n", read_len); return -EINVAL; } - tn = buf[0]; - fn = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4]; - 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); + if ((buf[0] >> 4) != 0) { + LOGPFSMSL(trx->fi, DTRXD, LOGL_ERROR, + "Got TRXD PDU with unexpected version\n"); + return -ENOTSUP; + } - if (tn >= 8) { - LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn); + read_len -= TRXDv0_HDR_LEN; + switch (read_len) { + /* TRXDv0 PDUs may have 2 dummy bytes at the end */ + case GSM_NBITS_NB_GMSK_BURST + 2: + case GSM_NBITS_NB_8PSK_BURST + 2: + read_len -= 2; + break; + case GSM_NBITS_NB_GMSK_BURST: + case GSM_NBITS_NB_8PSK_BURST: + break; + default: + LOGPFSMSL(trx->fi, DTRXD, LOGL_ERROR, + "Got TRXD PDU unexpected burst length=%zd\n", read_len); return -EINVAL; } - if (fn >= 2715648) { - LOGP(DTRXD, LOGL_ERROR, "Illegal FN %u\n", fn); + burst = (sbit_t *)&buf[8]; + + bi = (struct trxcon_phyif_burst_ind) { + .tn = buf[0] & 0x07, + .fn = osmo_load32be(buf + 1), + .rssi = -(int8_t) buf[5], + .toa256 = (int16_t) (buf[6] << 8) | buf[7], + .burst = burst, /* at least GSM_NBITS_NB_GMSK_BURST */ + .burst_len = read_len, + }; + + /* Convert ubits {254..0} to sbits {-127..127} in-place */ + for (unsigned int i = 0; i < bi.burst_len; i++) { + if (buf[8 + i] == 255) + burst[i] = -127; + else + burst[i] = 127 - buf[8 + i]; + } + + if (bi.fn >= GSM_TDMA_HYPERFRAME) { + LOGPFSMSL(trx->fi, DTRXD, LOGL_ERROR, "Illegal FN %u\n", bi.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", + bi.tn, bi.fn, bi.rssi, bi.toa256); - /* Poke scheduler */ - sched_trx_handle_rx_burst(trx, tn, fn, bits, 148, rssi, toa256); + trxcon_phyif_handle_burst_ind(trx->priv, &bi); - /* Correct local clock counter */ - if (fn % 51 == 0) - sched_clck_handle(&trx->sched, fn); + struct trxcon_phyif_rts_ind rts = { + .fn = GSM_TDMA_FN_SUM(bi.fn, trx->fn_advance), + .tn = bi.tn, + }; + + trxcon_phyif_handle_rts_ind(trx->priv, &rts); 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_handle_phyif_burst_req(struct trx_instance *trx, + const struct trxcon_phyif_burst_req *br) { - uint8_t buf[256]; + uint8_t buf[TRXD_BUF_SIZE]; + size_t length; /** * We must be sure that we have clock, @@ -617,55 +728,62 @@ int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn, * TODO: introduce proper state machines for both * transceiver and its TRXC interface. */ - if (trx->fsm->state != TRX_STATE_ACTIVE) { - LOGP(DTRXD, LOGL_ERROR, "Ignoring TX data, " - "transceiver isn't ready\n"); +#if 0 + 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; - buf[1] = (fn >> 24) & 0xff; - buf[2] = (fn >> 16) & 0xff; - buf[3] = (fn >> 8) & 0xff; - buf[4] = (fn >> 0) & 0xff; - 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); 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_if_open(const struct trx_if_params *params) { + const unsigned int offset = params->instance * 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(params->parent_fi, LOGL_NOTICE, + "Init transceiver interface (%s:%u/%u)\n", + params->remote_host, params->base_port, + params->instance); - /* 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, params->parent_fi, + params->parent_term_event); + if (fi == NULL) { + LOGPFSML(params->parent_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(params->parent_fi, LOGL_ERROR, "Failed to allocate memory\n"); + osmo_fsm_inst_free(fi); return NULL; } @@ -673,32 +791,40 @@ struct trx_instance *trx_if_open(void *tall_ctx, INIT_LLIST_HEAD(&trx->trx_ctrl_list); /* 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); + rc = trx_udp_open(trx, &trx->trx_ofd_ctrl, /* TRXC */ + params->local_host, params->base_port + 101 + offset, + params->remote_host, params->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, trx_data_rx_cb); + rc = trx_udp_open(trx, &trx->trx_ofd_data, /* TRXD */ + params->local_host, params->base_port + 102 + offset, + params->remote_host, params->base_port + 2 + offset, + trx_data_rx_cb); if (rc < 0) goto udp_error; + trx->fn_advance = params->fn_advance; + trx->priv = params->priv; + 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(params->parent_fi, LOGL_ERROR, "Couldn't establish UDP connection\n"); + osmo_fsm_inst_free(trx->fi); return NULL; } /* Flush pending control messages */ -void trx_if_flush_ctrl(struct trx_instance *trx) +static 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)) { @@ -711,21 +837,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 */ trx_udp_close(&trx->trx_ofd_ctrl); trx_udp_close(&trx->trx_ofd_data); /* Free memory */ - osmo_fsm_inst_free(trx->fsm); + trx->fi->priv = NULL; talloc_free(trx); } diff --git a/src/host/trxcon/src/trxcon_fsm.c b/src/host/trxcon/src/trxcon_fsm.c new file mode 100644 index 00000000..95458838 --- /dev/null +++ b/src/host/trxcon/src/trxcon_fsm.c @@ -0,0 +1,822 @@ +/* + * OsmocomBB <-> SDR connection bridge + * The trxcon state machine (see 3GPP TS 44.004, section 5.1) + * + * (C) 2022-2023 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 <osmocom/core/fsm.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/logging.h> +#include <osmocom/gsm/gsm0502.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> +#include <osmocom/bb/trxcon/phyif.h> +#include <osmocom/bb/trxcon/l1ctl.h> +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1gprs.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; + struct trxcon_phyif_cmd phycmd = { }; + + 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: + TALLOC_FREE(trxcon->fi_data); + if (fi->state != TRXCON_ST_RESET) + osmo_fsm_inst_state_chg(fi, TRXCON_ST_RESET, 0, 0); + l1sched_reset(trxcon->sched, true); + + /* Reset the L1 parameters */ + trxcon->l1p.band_arfcn = 0xffff; + trxcon->l1p.tx_power = 0; + trxcon->l1p.ta = 0; + + phycmd.type = TRXCON_PHYIF_CMDT_RESET; + trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd); + 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: + phycmd.type = TRXCON_PHYIF_CMDT_SETSLOT; + phycmd.param.setslot.tn = req->pchan_comb.tn; + phycmd.param.setslot.pchan = req->pchan_comb.pchan; + trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd); + break; + case TRXCON_PHY_CFGT_TX_PARAMS: + if (trxcon->l1p.ta != req->tx_params.timing_advance) { + phycmd.type = TRXCON_PHYIF_CMDT_SETTA; + phycmd.param.setta.ta = req->tx_params.timing_advance; + trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd); + } + 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_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, trxcon->l1p.band_arfcn); + osmo_fsm_inst_state_chg(fi, TRXCON_ST_RESET, 0, 0); + return 0; + default: + OSMO_ASSERT(0); + } +} + +static void handle_full_power_scan_req(struct osmo_fsm_inst *fi, + const struct trxcon_param_full_power_scan_req *req) +{ + struct trxcon_inst *trxcon = fi->priv; + enum gsm_band band_start, band_stop; + + if (trxcon->fi_data != NULL) { + LOGPFSML(fi, LOGL_ERROR, "Full power scan is already in progress\n"); + return; + } + + /* The start and stop ARFCNs must be in the same band */ + if (gsm_arfcn2band_rc(req->band_arfcn_start, &band_start) != 0 || + gsm_arfcn2band_rc(req->band_arfcn_stop, &band_stop) != 0 || + band_start != band_stop) { + LOGPFSML(fi, LOGL_ERROR, "Full power scan request has invalid params\n"); + return; + } + + trxcon->fi_data = talloc_memdup(fi, req, sizeof(*req)); + OSMO_ASSERT(trxcon->fi_data != NULL); + + /* trxcon_st_full_power_scan_onenter() sends the initial TRXCON_PHYIF_CMDT_MEASURE */ + osmo_fsm_inst_state_chg(fi, TRXCON_ST_FULL_POWER_SCAN, 0, 0); /* TODO: timeout */ +} + +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; + unsigned long timeout_fns, timeout_ms; + + /* Some PHYs need additional time to tune (in TDMA FNs) */ + timeout_fns = req->timeout_fns + trxcon->phy_quirks.fbsb_extend_fns; + timeout_ms = timeout_fns * GSM_TDMA_FN_DURATION_uS / 1000; + osmo_fsm_inst_state_chg_ms(fi, TRXCON_ST_FBSB_SEARCH, 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) { + const struct trxcon_phyif_cmd phycmd = { + .type = TRXCON_PHYIF_CMDT_SETFREQ_H0, + .param.setfreq_h0 = { + .band_arfcn = req->band_arfcn, + }, + }; + + /* Update current ARFCN */ + trxcon->l1p.band_arfcn = req->band_arfcn; + + /* Tune transceiver to required ARFCN */ + trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd); + } + + const struct trxcon_phyif_cmd phycmd = { .type = TRXCON_PHYIF_CMDT_POWERON }; + trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd); + break; + } + case TRXCON_EV_FULL_POWER_SCAN_REQ: + handle_full_power_scan_req(fi, (const struct trxcon_param_full_power_scan_req *)data); + break; + default: + OSMO_ASSERT(0); + } +} + +static void trxcon_st_full_power_scan_onenter(struct osmo_fsm_inst *fi, + uint32_t prev_state) +{ + const struct trxcon_inst *trxcon = fi->priv; + const struct trxcon_param_full_power_scan_req *req = trxcon->fi_data; + + /* req->band_arfcn_start holds the current ARFCN */ + const struct trxcon_phyif_cmd phycmd = { + .type = TRXCON_PHYIF_CMDT_MEASURE, + .param.measure = { + .band_arfcn = req->band_arfcn_start, + }, + }; + + trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd); +} + +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: + { + struct trxcon_param_full_power_scan_req *req = trxcon->fi_data; + const struct trxcon_param_full_power_scan_res *res = data; + + if (req == NULL) { + LOGPFSML(fi, LOGL_ERROR, "Rx unexpected power scan result\n"); + break; + } + + /* req->band_arfcn_start holds the expected ARFCN */ + if (res->band_arfcn != req->band_arfcn_start) { + LOGPFSML(fi, LOGL_ERROR, "Rx power scan result " + "with unexpected ARFCN %u (expected %u)\n", + res->band_arfcn & ~ARFCN_FLAG_MASK, + req->band_arfcn_start & ~ARFCN_FLAG_MASK); + break; + } + + if (res->band_arfcn < req->band_arfcn_stop) { + l1ctl_tx_pm_conf(trxcon, res->band_arfcn, res->dbm, false); + /* trxcon_st_full_power_scan_onenter() sends the next TRXCON_PHYIF_CMDT_MEASURE */ + req->band_arfcn_start = res->band_arfcn + 1; + osmo_fsm_inst_state_chg(fi, TRXCON_ST_FULL_POWER_SCAN, 0, 0); /* TODO: timeout */ + } else { + l1ctl_tx_pm_conf(trxcon, res->band_arfcn, res->dbm, true); + LOGPFSML(fi, LOGL_INFO, "Full power scan completed\n"); + TALLOC_FREE(trxcon->fi_data); + } + break; + } + case TRXCON_EV_FULL_POWER_SCAN_REQ: + handle_full_power_scan_req(fi, (const struct trxcon_param_full_power_scan_req *)data); + 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, 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; + struct l1sched_prim *prim; + struct msgb *msg; + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_RACH, PRIM_OP_REQUEST); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->rach_req = (struct l1sched_prim_rach) { + .chdr = { + .chan_nr = req->chan_nr, + .link_id = req->link_id, + }, + .synch_seq = req->synch_seq, + .offset = req->offset, + .is_11bit = req->is_11bit, + .ra = req->ra, + }; + + l1sched_prim_from_user(trxcon->sched, msg); +} + +static void handle_dch_est_req(struct osmo_fsm_inst *fi, + const struct trxcon_param_dch_est_req *req) +{ + struct trxcon_inst *trxcon = fi->priv; + enum gsm_phys_chan_config config; + struct l1sched_ts *ts; + int rc; + + 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) { + const struct trxcon_phyif_cmd phycmd = { + .type = TRXCON_PHYIF_CMDT_SETFREQ_H1, + .param.setfreq_h1 = { + .hsn = req->h1.hsn, + .maio = req->h1.maio, + .ma = &req->h1.ma[0], + .ma_len = req->h1.n, + }, + }; + + /* Apply the freq. hopping parameters */ + if (trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd) != 0) + return; + + /* Set current ARFCN to an invalid value */ + trxcon->l1p.band_arfcn = 0xffff; + } else { + const struct trxcon_phyif_cmd phycmd = { + .type = TRXCON_PHYIF_CMDT_SETFREQ_H0, + .param.setfreq_h0 = { + .band_arfcn = req->h0.band_arfcn, + }, + }; + + /* Tune transceiver to required ARFCN */ + if (trxcon_phyif_handle_cmd(trxcon->phyif, &phycmd) != 0) + return; + + /* Update current ARFCN */ + trxcon->l1p.band_arfcn = req->h0.band_arfcn; + } + + /* Remove all active timeslots */ + l1sched_reset(trxcon->sched, false); + + 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; + } + + /* Store TSC for subsequent PDCH timeslot activation(s) */ + trxcon->l1p.tsc = req->tsc; + + 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); +} + +static void trxcon_st_bcch_ccch_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_TX_ACCESS_BURST_CNF: + l1ctl_tx_rach_conf(trxcon, (const struct trxcon_param_tx_access_burst_cnf *)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; + struct l1sched_ts *ts = trxcon->sched->ts[0]; + + /* Make sure that TS0 is allocated and configured */ + 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_DCH_EST_REQ: + handle_dch_est_req(fi, (const struct trxcon_param_dch_est_req *)data); + break; + case TRXCON_EV_RX_DATA_IND: + l1ctl_tx_dt_ind(trxcon, (const struct trxcon_param_rx_data_ind *)data); + 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_TX_ACCESS_BURST_CNF: + l1ctl_tx_rach_conf(trxcon, (const struct trxcon_param_tx_access_burst_cnf *)data); + break; + case TRXCON_EV_DCH_EST_REQ: + handle_dch_est_req(fi, (const struct trxcon_param_dch_est_req *)data); + break; + case TRXCON_EV_DCH_REL_REQ: + l1sched_reset(trxcon->sched, false); + /* TODO: switch to (not implemented) TRXCON_ST_DCH_TUNING? */ + 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; + if (req->mode == GSM48_CMODE_SPEECH_AMR) { + int rc = l1sched_lchan_set_amr_cfg(lchan, + req->amr.codecs_bitmask, + req->amr.start_codec); + if (rc) + continue; + } + lchan->tch_mode = req->mode; + 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_DATA_REQ: + { + const struct trxcon_param_tx_data_req *req = data; + struct l1sched_prim *prim; + struct msgb *msg; + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->data_req = (struct l1sched_prim_chdr) { + .chan_nr = req->chan_nr, + .link_id = req->link_id, + .traffic = req->traffic, + }; + + memcpy(msgb_put(msg, req->data_len), req->data, req->data_len); + l1sched_prim_from_user(trxcon->sched, msg); + break; + } + case TRXCON_EV_TX_DATA_CNF: + l1ctl_tx_dt_conf(trxcon, (const struct trxcon_param_tx_data_cnf *)data); + break; + case TRXCON_EV_RX_DATA_IND: + l1ctl_tx_dt_ind(trxcon, (const struct trxcon_param_rx_data_ind *)data); + break; + default: + OSMO_ASSERT(0); + } +} + +static void handle_tbf_cfg_req(struct trxcon_inst *trxcon, uint8_t tn, bool active) +{ + struct l1sched_state *sched = trxcon->sched; + + if (active) { + struct l1sched_lchan_state *lchan; + struct l1sched_ts *ts; + + if (sched->ts[tn] != NULL) /* already enabled */ + return; + if (l1sched_configure_ts(sched, tn, GSM_PCHAN_PDCH) != 0) + return; + OSMO_ASSERT(sched->ts[tn] != NULL); + ts = sched->ts[tn]; + + l1sched_activate_lchan(ts, L1SCHED_PDTCH); + l1sched_activate_lchan(ts, L1SCHED_PTCCH); + llist_for_each_entry(lchan, &ts->lchans, list) + lchan->tsc = trxcon->l1p.tsc; + } else { + l1sched_del_ts(sched, tn); + } +} + +static void trxcon_l1gprs_state_changed_cb(struct l1gprs_pdch *pdch, bool active) +{ + handle_tbf_cfg_req(pdch->gprs->priv, pdch->tn, active); +} + +static void trxcon_st_packet_data_onenter(struct osmo_fsm_inst *fi, + uint32_t prev_state) +{ + struct trxcon_inst *trxcon = fi->priv; + + OSMO_ASSERT(trxcon->gprs == NULL); + trxcon->gprs = l1gprs_state_alloc(trxcon, trxcon->log_prefix, trxcon); + l1gprs_state_set_pdch_changed_cb(trxcon->gprs, trxcon_l1gprs_state_changed_cb); + OSMO_ASSERT(trxcon->gprs != NULL); +} + +static void trxcon_st_packet_data_onleave(struct osmo_fsm_inst *fi, + uint32_t next_state) +{ + struct trxcon_inst *trxcon = fi->priv; + + l1gprs_state_free(trxcon->gprs); + trxcon->gprs = NULL; +} + +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_TX_ACCESS_BURST_CNF: + l1ctl_tx_rach_conf(trxcon, (const struct trxcon_param_tx_access_burst_cnf *)data); + break; + case TRXCON_EV_GPRS_UL_TBF_CFG_REQ: + l1gprs_handle_ul_tbf_cfg_req(trxcon->gprs, (struct msgb *)data); + break; + case TRXCON_EV_GPRS_DL_TBF_CFG_REQ: + l1gprs_handle_dl_tbf_cfg_req(trxcon->gprs, (struct msgb *)data); + break; + case TRXCON_EV_GPRS_UL_BLOCK_REQ: + { + struct l1gprs_prim_ul_block_req block_req; + struct l1sched_prim *prim; + struct msgb *msg = data; + + if (l1gprs_handle_ul_block_req(trxcon->gprs, &block_req, msg) != 0) + return; + + msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST); + OSMO_ASSERT(msg != NULL); + + prim = l1sched_prim_from_msgb(msg); + prim->data_req = (struct l1sched_prim_chdr) { + .frame_nr = block_req.hdr.fn, + .chan_nr = RSL_CHAN_OSMO_PDCH | block_req.hdr.tn, + .link_id = 0x00, + }; + + memcpy(msgb_put(msg, block_req.data_len), block_req.data, block_req.data_len); + l1sched_prim_from_user(trxcon->sched, msg); + break; + } + case TRXCON_EV_TX_DATA_CNF: + { + const struct trxcon_param_tx_data_cnf *cnf = data; + struct msgb *msg; + + msg = l1gprs_handle_ul_block_cnf(trxcon->gprs, + cnf->frame_nr, cnf->chan_nr & 0x07, + cnf->data, cnf->data_len); + if (msg != NULL) + trxcon_l1ctl_send(trxcon, msg); + break; + } + case TRXCON_EV_RX_DATA_IND: + { + const struct trxcon_param_rx_data_ind *ind = data; + struct l1gprs_prim_dl_block_ind block_ind; + struct msgb *msg; + uint8_t usf = 0xff; + + block_ind = (struct l1gprs_prim_dl_block_ind) { + .hdr = { + .fn = ind->frame_nr, + .tn = ind->chan_nr & 0x07, + }, + .meas = { + /* .ber10k is set below */ + .ci_cb = 180, /* 18 dB */ + .rx_lev = dbm2rxlev(ind->rssi), + }, + .data_len = ind->data_len, + .data = ind->data, + }; + + if (ind->n_bits_total == 0) + block_ind.meas.ber10k = 10000; + else + block_ind.meas.ber10k = 10000 * ind->n_errors / ind->n_bits_total; + + msg = l1gprs_handle_dl_block_ind(trxcon->gprs, &block_ind, &usf); + if (msg != NULL) + trxcon_l1ctl_send(trxcon, msg); + /* Every fn % 13 == 12 we have either a PTCCH or an IDLE slot, thus + * every fn % 13 == 8 we add 5 frames, or 4 frames othrwise. The + * resulting value is first fn of the next block. */ + const uint32_t rts_fn = GSM_TDMA_FN_SUM(ind->frame_nr, (ind->frame_nr % 13 == 8) ? 5 : 4); + msg = l1gprs_handle_rts_ind(trxcon->gprs, rts_fn, ind->chan_nr & 0x07, usf); + if (msg != NULL) + trxcon_l1ctl_send(trxcon, msg); + break; + } + case TRXCON_EV_DCH_EST_REQ: + handle_dch_est_req(fi, (const struct trxcon_param_dch_est_req *)data); + break; + case TRXCON_EV_DCH_REL_REQ: + l1sched_reset(trxcon->sched, false); + /* TODO: switch to (not implemented) TRXCON_ST_DCH_TUNING? */ + 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); + /* Clean up GPRS L1 state */ + l1gprs_state_free(trxcon->gprs); + + /* Close active connections */ + if (trxcon->l2if != NULL) + trxcon_l1ctl_close(trxcon); + if (trxcon->phyif != NULL) + trxcon_phyif_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) + | S(TRXCON_ST_FULL_POWER_SCAN), + .in_event_mask = S(TRXCON_EV_FULL_POWER_SCAN_RES) + | S(TRXCON_EV_FULL_POWER_SCAN_REQ), + .onenter = &trxcon_st_full_power_scan_onenter, + .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_TX_ACCESS_BURST_CNF) + | S(TRXCON_EV_DCH_EST_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_DEDICATED) + | S(TRXCON_ST_PACKET_DATA), + .in_event_mask = S(TRXCON_EV_DCH_REL_REQ) + | S(TRXCON_EV_DCH_EST_REQ) + | S(TRXCON_EV_TX_ACCESS_BURST_REQ) + | S(TRXCON_EV_TX_ACCESS_BURST_CNF) + | S(TRXCON_EV_SET_TCH_MODE_REQ) + | S(TRXCON_EV_TX_DATA_REQ) + | S(TRXCON_EV_TX_DATA_CNF) + | 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_DEDICATED) + | S(TRXCON_ST_PACKET_DATA), + .in_event_mask = S(TRXCON_EV_DCH_REL_REQ) + | S(TRXCON_EV_DCH_EST_REQ) + | S(TRXCON_EV_TX_ACCESS_BURST_REQ) + | S(TRXCON_EV_TX_ACCESS_BURST_CNF) + | S(TRXCON_EV_GPRS_UL_TBF_CFG_REQ) + | S(TRXCON_EV_GPRS_DL_TBF_CFG_REQ) + | S(TRXCON_EV_GPRS_UL_BLOCK_REQ) + | S(TRXCON_EV_RX_DATA_IND) + | S(TRXCON_EV_TX_DATA_CNF), + .onenter = &trxcon_st_packet_data_onenter, + .onleave = &trxcon_st_packet_data_onleave, + .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_TX_ACCESS_BURST_CNF), + OSMO_VALUE_STRING(TRXCON_EV_UPDATE_SACCH_CACHE_REQ), + OSMO_VALUE_STRING(TRXCON_EV_DCH_EST_REQ), + OSMO_VALUE_STRING(TRXCON_EV_DCH_REL_REQ), + OSMO_VALUE_STRING(TRXCON_EV_TX_DATA_REQ), + OSMO_VALUE_STRING(TRXCON_EV_TX_DATA_CNF), + OSMO_VALUE_STRING(TRXCON_EV_RX_DATA_IND), + OSMO_VALUE_STRING(TRXCON_EV_CRYPTO_REQ), + OSMO_VALUE_STRING(TRXCON_EV_GPRS_UL_TBF_CFG_REQ), + OSMO_VALUE_STRING(TRXCON_EV_GPRS_DL_TBF_CFG_REQ), + OSMO_VALUE_STRING(TRXCON_EV_GPRS_UL_BLOCK_REQ), + { 0, NULL } +}; + +struct osmo_fsm trxcon_fsm_def = { + .name = "trxcon", + .states = trxcon_fsm_states, + .num_states = ARRAY_SIZE(trxcon_fsm_states), + .log_subsys = DLGLOBAL, + .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); +} diff --git a/src/host/trxcon/src/trxcon_inst.c b/src/host/trxcon/src/trxcon_inst.c new file mode 100644 index 00000000..b7ff5810 --- /dev/null +++ b/src/host/trxcon/src/trxcon_inst.c @@ -0,0 +1,107 @@ +/* + * 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 <stdint.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/talloc.h> + +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> +#include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h> +#include <osmocom/bb/l1gprs.h> + +extern int g_logc_l1c; +extern int g_logc_l1d; + +void trxcon_set_log_cfg(const int *logc, unsigned int logc_num) +{ + int schc = DLGLOBAL; + int schd = DLGLOBAL; + + for (unsigned int i = 0; i < logc_num; i++) { + switch ((enum trxcon_log_cat)i) { + case TRXCON_LOGC_FSM: + trxcon_fsm_def.log_subsys = logc[i]; + break; + case TRXCON_LOGC_L1C: + g_logc_l1c = logc[i]; + break; + case TRXCON_LOGC_L1D: + g_logc_l1d = logc[i]; + break; + case TRXCON_LOGC_SCHC: + schc = logc[i]; + break; + case TRXCON_LOGC_SCHD: + schd = logc[i]; + break; + case TRXCON_LOGC_GPRS: + l1gprs_logging_init(logc[i]); + break; + } + } + + l1sched_logging_init(schc, schd); +} + +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 scheduler */ + const struct l1sched_cfg sched_cfg = { + .log_prefix = trxcon->log_prefix, + }; + + trxcon->sched = l1sched_alloc(trxcon, &sched_cfg, trxcon); + if (trxcon->sched == NULL) { + trxcon_inst_free(trxcon); + return NULL; + } + + trxcon->phy_quirks.fbsb_extend_fns = 0; + + 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); +} diff --git a/src/host/trxcon/src/trxcon_main.c b/src/host/trxcon/src/trxcon_main.c new file mode 100644 index 00000000..3901e336 --- /dev/null +++ b/src/host/trxcon/src/trxcon_main.c @@ -0,0 +1,423 @@ +/* + * 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 <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/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> +#include <osmocom/bb/trxcon/phyif.h> +#include <osmocom/bb/trxcon/trx_if.h> +#include <osmocom/bb/trxcon/logging.h> +#include <osmocom/bb/trxcon/l1ctl_server.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; + + /* PHY quirk: FBSB timeout extension (in TDMA FNs) */ + unsigned int phyq_fbsb_extend_fns; + + /* 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 = 0, + .phyq_fbsb_extend_fns = 0, +}; + +static void *tall_trxcon_ctx = NULL; + +int trxcon_phyif_handle_burst_req(void *phyif, const struct trxcon_phyif_burst_req *br) +{ + return trx_if_handle_phyif_burst_req(phyif, br); +} + +int trxcon_phyif_handle_cmd(void *phyif, const struct trxcon_phyif_cmd *cmd) +{ + return trx_if_handle_phyif_cmd(phyif, cmd); +} + +void trxcon_phyif_close(void *phyif) +{ + trx_if_close(phyif); +} + +void trxcon_l1ctl_close(struct trxcon_inst *trxcon) +{ + /* 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, trxcon->fi); + l1ctl_client_conn_close(trxcon->l2if); +} + +int trxcon_l1ctl_send(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct l1ctl_client *l1c = trxcon->l2if; + + return l1ctl_client_send(l1c, msg); +} + +static int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg) +{ + struct trxcon_inst *trxcon = l1c->priv; + + return trxcon_l1ctl_receive(trxcon, msg); +} + +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; + trxcon->l2if = l1c; + + const struct trx_if_params trxcon_phyif_params = { + .local_host = app_data.trx_bind_ip, + .remote_host = app_data.trx_remote_ip, + .base_port = app_data.trx_base_port, + .fn_advance = app_data.trx_fn_advance, + .instance = trxcon->id, + + .parent_fi = trxcon->fi, + .parent_term_event = TRXCON_EV_PHYIF_FAILURE, + .priv = trxcon, + }; + + /* Init transceiver interface */ + trxcon->phyif = trx_if_open(&trxcon_phyif_params); + if (trxcon->phyif == NULL) { + /* TRXCON_EV_PHYIF_FAILURE triggers l1ctl_client_conn_close() */ + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_PHYIF_FAILURE, NULL); + return; + } + + trxcon->gsmtap = app_data.gsmtap; + trxcon->phy_quirks.fbsb_extend_fns = app_data.phyq_fbsb_extend_fns; +} + +static void l1ctl_conn_close_cb(struct l1ctl_client *l1c) +{ + struct trxcon_inst *trxcon = l1c->priv; + + if (trxcon == NULL || trxcon->fi == NULL) + return; + + osmo_fsm_inst_dispatch(trxcon->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 0)\n"); + printf(" -F --fbsb-extend FBSB timeout extension (in TDMA FNs, default 0)\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) { + char *endptr = NULL; + 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'}, + {"fbsb-extend", 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:F:s:g:C:Dh", + long_options, &option_index); + if (c == -1) + break; + + errno = 0; + + 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 = strtoul(optarg, &endptr, 10); + if (errno || *endptr != '\0') { + fprintf(stderr, "Failed to parse -p/--trx-port=%s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'f': + app_data.trx_fn_advance = strtoul(optarg, &endptr, 10); + if (errno || *endptr != '\0') { + fprintf(stderr, "Failed to parse -f/--trx-advance=%s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'F': + app_data.phyq_fbsb_extend_fns = strtoul(optarg, &endptr, 10); + if (errno || *endptr != '\0') { + fprintf(stderr, "Failed to parse -F/--fbsb-extend=%s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 's': + app_data.bind_socket = optarg; + break; + case 'g': + app_data.gsmtap_ip = optarg; + break; + case 'C': + app_data.max_clients = strtoul(optarg, &endptr, 10); + if (errno || *endptr != '\0') { + fprintf(stderr, "Failed to parse -C/--max-clients=%s\n", optarg); + exit(EXIT_FAILURE); + } + 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 */ + trxcon_logging_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); + + 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) { + struct log_target *lt; + + 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 Um logging\n"); + goto exit; + } + + lt = log_target_create_gsmtap(app_data.gsmtap_ip, GSMTAP_UDP_PORT, + "trxcon", false, false); + if (lt == NULL) { + LOGP(DAPP, LOGL_ERROR, "Failed to init GSMTAP logging target\n"); + goto exit; + } else { + log_add_target(lt); + } + + /* 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; +} diff --git a/src/host/trxcon/src/trxcon_shim.c b/src/host/trxcon/src/trxcon_shim.c new file mode 100644 index 00000000..ed2d402e --- /dev/null +++ b/src/host/trxcon/src/trxcon_shim.c @@ -0,0 +1,262 @@ +/* + * OsmocomBB <-> SDR connection bridge + * + * (C) 2022-2023 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 <stdint.h> +#include <errno.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/gsm/rsl.h> + +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> +#include <osmocom/bb/trxcon/phyif.h> +#include <osmocom/bb/l1sched/l1sched.h> + +static void trxcon_gsmtap_send(struct trxcon_inst *trxcon, + const struct l1sched_prim_chdr *chdr, + const uint8_t *data, size_t data_len, + int8_t signal_dbm, uint8_t snr, bool uplink) +{ + uint16_t band_arfcn = trxcon->l1p.band_arfcn; + uint8_t chan_type, ss, tn; + + if (uplink) + band_arfcn |= ARFCN_UPLINK; + if (rsl_dec_chan_nr(chdr->chan_nr, &chan_type, &ss, &tn) != 0) + return; + chan_type = chantype_rsl2gsmtap2(chan_type, chdr->link_id, chdr->traffic); + + gsmtap_send(trxcon->gsmtap, band_arfcn, tn, chan_type, ss, + chdr->frame_nr, signal_dbm, snr, + data, data_len); +} + +/* External L1 API for the scheduler */ +int l1sched_handle_burst_req(struct l1sched_state *sched, + const struct l1sched_burst_req *br) +{ + struct trxcon_inst *trxcon = sched->priv; + const struct trxcon_phyif_burst_req phybr = { + .fn = br->fn, + .tn = br->tn, + .pwr = br->pwr, + .burst = &br->burst[0], + .burst_len = br->burst_len, + }; + + return trxcon_phyif_handle_burst_req(trxcon->phyif, &phybr); +} + +/* External L2 API for the scheduler */ +static int handle_prim_data_ind(struct trxcon_inst *trxcon, struct msgb *msg) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + struct trxcon_param_rx_data_ind ind = { + .traffic = prim->data_ind.chdr.traffic, + .chan_nr = prim->data_ind.chdr.chan_nr, + .link_id = prim->data_ind.chdr.link_id, + .band_arfcn = trxcon->l1p.band_arfcn, + .frame_nr = prim->data_ind.chdr.frame_nr, + .toa256 = prim->data_ind.toa256, + .rssi = prim->data_ind.rssi, + .n_errors = prim->data_ind.n_errors, + .n_bits_total = prim->data_ind.n_bits_total, + .data_len = msgb_l2len(msg), + .data = msgb_l2(msg), + }; + + if (trxcon->gsmtap != NULL && ind.data_len > 0) { + trxcon_gsmtap_send(trxcon, &prim->data_ind.chdr, + ind.data, ind.data_len, + ind.rssi, 0, false); + } + + return osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_RX_DATA_IND, &ind); +} + +static int handle_prim_data_cnf(struct trxcon_inst *trxcon, struct msgb *msg) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + struct trxcon_param_tx_data_cnf cnf = { + .traffic = prim->data_cnf.traffic, + .chan_nr = prim->data_cnf.chan_nr, + .link_id = prim->data_cnf.link_id, + .band_arfcn = trxcon->l1p.band_arfcn, + .frame_nr = prim->data_cnf.frame_nr, + .data_len = msgb_l2len(msg), + .data = msgb_l2(msg), + }; + + if (trxcon->gsmtap != NULL) { + trxcon_gsmtap_send(trxcon, &prim->data_cnf, + msgb_l2(msg), msgb_l2len(msg), + 0, 0, true); + } + + return osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_TX_DATA_CNF, &cnf); +} + +static int handle_prim_rach_cnf(struct trxcon_inst *trxcon, struct msgb *msg) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + struct trxcon_param_tx_access_burst_cnf cnf = { + .band_arfcn = trxcon->l1p.band_arfcn, + .frame_nr = prim->rach_cnf.chdr.frame_nr, + }; + + if (trxcon->gsmtap != NULL) { + if (prim->rach_cnf.is_11bit) { + msgb_put_u8(msg, (uint8_t)(prim->rach_cnf.ra >> 3)); + msgb_put_u8(msg, (uint8_t)(prim->rach_cnf.ra & 0x07)); + } else { + msgb_put_u8(msg, (uint8_t)(prim->rach_cnf.ra)); + } + + trxcon_gsmtap_send(trxcon, &prim->rach_cnf.chdr, + msgb_l2(msg), msgb_l2len(msg), + 0, 0, true); + } + + return osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_TX_ACCESS_BURST_CNF, &cnf); +} + +int l1sched_prim_to_user(struct l1sched_state *sched, struct msgb *msg) +{ + const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg); + struct trxcon_inst *trxcon = sched->priv; + int rc = 0; + + LOGPFSML(trxcon->fi, LOGL_DEBUG, + "%s(): Rx " L1SCHED_PRIM_STR_FMT "\n", + __func__, L1SCHED_PRIM_STR_ARGS(prim)); + + switch (OSMO_PRIM_HDR(&prim->oph)) { + case OSMO_PRIM(L1SCHED_PRIM_T_DATA, PRIM_OP_INDICATION): + rc = handle_prim_data_ind(trxcon, msg); + break; + case OSMO_PRIM(L1SCHED_PRIM_T_DATA, PRIM_OP_CONFIRM): + rc = handle_prim_data_cnf(trxcon, msg); + break; + case OSMO_PRIM(L1SCHED_PRIM_T_RACH, PRIM_OP_CONFIRM): + rc = handle_prim_rach_cnf(trxcon, msg); + break; + case OSMO_PRIM(L1SCHED_PRIM_T_SCH, PRIM_OP_INDICATION): + if (trxcon->fi->state == TRXCON_ST_FBSB_SEARCH) + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_FBSB_SEARCH_RES, NULL); + break; + case OSMO_PRIM(L1SCHED_PRIM_T_PCHAN_COMB, PRIM_OP_INDICATION): + { + struct trxcon_param_set_phy_config_req req = { + .type = TRXCON_PHY_CFGT_PCHAN_COMB, + .pchan_comb = { + .tn = prim->pchan_comb_ind.tn, + .pchan = prim->pchan_comb_ind.pchan, + }, + }; + + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_SET_PHY_CONFIG_REQ, &req); + break; + } + default: + LOGPFSML(trxcon->fi, LOGL_ERROR, + "%s(): Unhandled primitive " L1SCHED_PRIM_STR_FMT "\n", + __func__, L1SCHED_PRIM_STR_ARGS(prim)); + rc = -ENOTSUP; + } + + msgb_free(msg); + return rc; +} + +/* External L1 API for the PHYIF */ +int trxcon_phyif_handle_rts_ind(void *priv, const struct trxcon_phyif_rts_ind *rts) +{ + struct trxcon_inst *trxcon = priv; + struct l1sched_burst_req br = { + .fn = rts->fn, + .tn = rts->tn, + .burst_len = 0, /* NOPE.ind */ + }; + + l1sched_pull_burst(trxcon->sched, &br); + return l1sched_handle_burst_req(trxcon->sched, &br); +} + +int trxcon_phyif_handle_rtr_ind(void *priv, const struct trxcon_phyif_rtr_ind *ind, + struct trxcon_phyif_rtr_rsp *rsp) +{ + struct trxcon_inst *trxcon = priv; + struct l1sched_probe probe = { + .fn = ind->fn, + .tn = ind->tn, + }; + + l1sched_handle_rx_probe(trxcon->sched, &probe); + + memset(rsp, 0x00, sizeof(*rsp)); + + if (probe.flags & L1SCHED_PROBE_F_ACTIVE) + rsp->flags |= TRXCON_PHYIF_RTR_F_ACTIVE; + + return 0; +} + +int trxcon_phyif_handle_burst_ind(void *priv, const struct trxcon_phyif_burst_ind *phybi) +{ + struct trxcon_inst *trxcon = priv; + struct l1sched_burst_ind bi = { + .fn = phybi->fn, + .tn = phybi->tn, + .toa256 = phybi->toa256, + .rssi = phybi->rssi, + /* .burst[] is populated below */ + .burst_len = phybi->burst_len, + }; + + OSMO_ASSERT(phybi->burst_len <= sizeof(bi.burst)); + memcpy(&bi.burst[0], phybi->burst, phybi->burst_len); + + /* Poke scheduler */ + return l1sched_handle_rx_burst(trxcon->sched, &bi); +} + +int trxcon_phyif_handle_rsp(void *priv, const struct trxcon_phyif_rsp *rsp) +{ + struct trxcon_inst *trxcon = priv; + + switch (rsp->type) { + case TRXCON_PHYIF_CMDT_MEASURE: + { + const struct trxcon_phyif_rspp_measure *meas = &rsp->param.measure; + struct trxcon_param_full_power_scan_res res = { + .band_arfcn = meas->band_arfcn, + .dbm = meas->dbm, + }; + + return osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_FULL_POWER_SCAN_RES, &res); + } + default: + LOGPFSML(trxcon->fi, LOGL_ERROR, + "Unhandled PHYIF response (type 0x%02x)\n", rsp->type); + return -ENODEV; + } +} diff --git a/src/host/trxcon/trx_if.h b/src/host/trxcon/trx_if.h deleted file mode 100644 index a44600d9..00000000 --- a/src/host/trxcon/trx_if.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include <osmocom/core/linuxlist.h> -#include <osmocom/core/select.h> -#include <osmocom/core/timer.h> -#include <osmocom/core/fsm.h> - -#include "scheduler.h" -#include "sched_trx.h" - -/* Forward declaration to avoid mutual include */ -struct l1ctl_link; - -enum trx_fsm_states { - TRX_STATE_OFFLINE = 0, - TRX_STATE_IDLE, - TRX_STATE_ACTIVE, - TRX_STATE_RSP_WAIT, -}; - -struct trx_instance { - struct osmo_fd trx_ofd_ctrl; - struct osmo_fd trx_ofd_data; - - struct osmo_timer_list trx_ctrl_timer; - struct llist_head trx_ctrl_list; - struct osmo_fsm_inst *fsm; - - /* HACK: we need proper state machines */ - uint32_t prev_state; - bool powered_up; - - /* GSM L1 specific */ - uint16_t pm_band_arfcn_start; - uint16_t pm_band_arfcn_stop; - uint16_t band_arfcn; - uint8_t tx_power; - uint8_t bsic; - uint8_t tsc; - int8_t ta; - - /* Scheduler stuff */ - struct trx_sched sched; - struct trx_ts *ts_list[TRX_TS_COUNT]; - - /* Bind L1CTL link */ - struct l1ctl_link *l1l; -}; - -struct trx_ctrl_msg { - struct llist_head list; - char cmd[128]; - int retry_cnt; - int critical; - int cmd_len; -}; - -struct trx_instance *trx_if_open(void *tall_ctx, - const char *local_host, const char *remote_host, uint16_t port); -void trx_if_flush_ctrl(struct trx_instance *trx); -void trx_if_close(struct trx_instance *trx); - -int trx_if_cmd_poweron(struct trx_instance *trx); -int trx_if_cmd_poweroff(struct trx_instance *trx); -int trx_if_cmd_echo(struct trx_instance *trx); - -int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta); - -int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn); -int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn); - -int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type); -int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn, - uint8_t maio, uint16_t *ma, size_t ma_len); - -int trx_if_cmd_measure(struct trx_instance *trx, - uint16_t band_arfcn_start, uint16_t band_arfcn_stop); - -int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn, - uint8_t pwr, const ubit_t *bits); diff --git a/src/host/trxcon/trxcon.c b/src/host/trxcon/trxcon.c deleted file mode 100644 index 8e371df1..00000000 --- a/src/host/trxcon/trxcon.c +++ /dev/null @@ -1,343 +0,0 @@ -/* - * OsmocomBB <-> SDR connection bridge - * - * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com> - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#include <stdio.h> -#include <stdlib.h> -#include <stdint.h> -#include <string.h> -#include <getopt.h> -#include <unistd.h> -#include <signal.h> -#include <time.h> - -#include <arpa/inet.h> - -#include <osmocom/core/fsm.h> -#include <osmocom/core/msgb.h> -#include <osmocom/core/talloc.h> -#include <osmocom/core/signal.h> -#include <osmocom/core/select.h> -#include <osmocom/core/application.h> - -#include <osmocom/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-2017 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; -} app_data; - -static void *tall_trxcon_ctx = 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 Scheduler clock advance (default 20)\n"); - printf(" -s --socket Listening socket for layer23 (default /tmp/osmocom_l2)\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'}, - {"daemonize", 0, 0, 'D'}, - {0, 0, 0, 0} - }; - - c = getopt_long(argc, argv, "d:b:i:p:f:s: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 '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 = 20; - - app_data.debug_mask = NULL; - app_data.daemonize = 0; - app_data.quit = 0; -} - -static void signal_handler(int signal) -{ - fprintf(stderr, "signal %u received\n", signal); - - switch (signal) { - case SIGINT: - app_data.quit++; - break; - case SIGABRT: - case SIGUSR1: - case SIGUSR2: - talloc_report_full(tall_trxcon_ctx, stderr); - break; - default: - break; - } -} - -int main(int argc, char **argv) -{ - int rc = 0; - - printf("%s", COPYRIGHT); - init_defaults(); - handle_options(argc, argv); - - /* Track the use of talloc NULL memory contexts */ - talloc_enable_null_tracking(); - - /* Init talloc memory management system */ - tall_trxcon_ctx = talloc_init("trxcon context"); - msgb_talloc_ctx_init(tall_trxcon_ctx, 0); - - /* Setup signal handlers */ - signal(SIGINT, &signal_handler); - signal(SIGUSR1, &signal_handler); - signal(SIGUSR2, &signal_handler); - osmo_init_ignore_signals(); - - /* Init logging system */ - trx_log_init(tall_trxcon_ctx, app_data.debug_mask); - - /* Allocate the application state machine */ - osmo_fsm_register(&trxcon_fsm_def); - 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)); - - while (!app_data.quit) - osmo_select_main(0); - -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; -} diff --git a/src/host/trxcon/trxcon.h b/src/host/trxcon/trxcon.h deleted file mode 100644 index f66a6285..00000000 --- a/src/host/trxcon/trxcon.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#define GEN_MASK(state) (0x01 << state) - -extern struct osmo_fsm_inst *trxcon_fsm; - -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, -}; |