aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Wild <ewild@sysmocom.de>2022-05-07 15:36:47 +0200
committerEric Wild <ewild@sysmocom.de>2022-05-09 01:18:13 +0200
commitd40300ec63a80c2d8babd8926dbb72ba1a2a8e4a (patch)
treef756d9601e6b9eb6403155f209a112bafba47ac9
parent1a19caf0029728b85b3c7c3dd3a4afd105cb15e3 (diff)
4sps kinda works
fcch corr before sch sync close enough pointless?
-rw-r--r--CommonLibs/config_defs.h1
-rw-r--r--GSM/GSMCommon.cpp2
-rw-r--r--GSM/GSMCommon.h4
-rw-r--r--Makefile.am1
-rw-r--r--Makefile.common1
-rw-r--r--Transceiver52M/Makefile.am11
-rw-r--r--Transceiver52M/Transceiver2.cpp1198
-rw-r--r--Transceiver52M/Transceiver2.h258
-rw-r--r--Transceiver52M/device/common/radioDevice.h3
-rw-r--r--Transceiver52M/device/uhd/UHDDevice.cpp71
-rw-r--r--Transceiver52M/device/uhd/UHDDevice.h9
-rw-r--r--Transceiver52M/fbsb.cpp148
-rw-r--r--Transceiver52M/fbsb.h0
-rw-r--r--Transceiver52M/l1if.cpp124
-rw-r--r--Transceiver52M/l1if.h74
-rw-r--r--Transceiver52M/osmo-trx-org.cpp718
-rw-r--r--Transceiver52M/osmo-trx.cpp801
-rw-r--r--Transceiver52M/radioClock.cpp35
-rw-r--r--Transceiver52M/radioClock.h3
-rw-r--r--Transceiver52M/radioInterface.cpp33
-rw-r--r--Transceiver52M/radioInterface.h10
-rw-r--r--Transceiver52M/radioInterfaceMulti.cpp2
-rw-r--r--Transceiver52M/radioInterfaceResamp.cpp2
-rw-r--r--Transceiver52M/radioVector.cpp19
-rw-r--r--Transceiver52M/radioVector.h3
-rw-r--r--Transceiver52M/sch.c299
-rw-r--r--Transceiver52M/sch.h27
-rw-r--r--Transceiver52M/sigProcLib.cpp138
-rw-r--r--Transceiver52M/sigProcLib.h11
-rw-r--r--configure.ac1
-rw-r--r--trxcon/.gitignore27
-rw-r--r--trxcon/Makefile.am58
-rw-r--r--trxcon/l1ctl.c918
-rw-r--r--trxcon/l1ctl.h26
-rw-r--r--trxcon/l1ctl_link.c316
-rw-r--r--trxcon/l1ctl_link.h48
-rw-r--r--trxcon/l1ctl_proto.h387
-rw-r--r--trxcon/logging.c88
-rw-r--r--trxcon/logging.h17
-rw-r--r--trxcon/sched_clck.c206
-rw-r--r--trxcon/sched_lchan_common.c232
-rw-r--r--trxcon/sched_lchan_desc.c622
-rw-r--r--trxcon/sched_lchan_pdtch.c201
-rw-r--r--trxcon/sched_lchan_rach.c187
-rw-r--r--trxcon/sched_lchan_sch.c139
-rw-r--r--trxcon/sched_lchan_tchf.c303
-rw-r--r--trxcon/sched_lchan_tchh.c501
-rw-r--r--trxcon/sched_lchan_xcch.c217
-rw-r--r--trxcon/sched_mframe.c2101
-rw-r--r--trxcon/sched_prim.c617
-rw-r--r--trxcon/sched_trx.c841
-rw-r--r--trxcon/sched_trx.h405
-rw-r--r--trxcon/scheduler.h38
-rw-r--r--trxcon/trx_if.c737
-rw-r--r--trxcon/trx_if.c.backup.c816
-rw-r--r--trxcon/trx_if.h84
-rw-r--r--trxcon/trxcon.c391
-rw-r--r--trxcon/trxcon.h21
58 files changed, 13983 insertions, 568 deletions
diff --git a/CommonLibs/config_defs.h b/CommonLibs/config_defs.h
index 33be73f..db15017 100644
--- a/CommonLibs/config_defs.h
+++ b/CommonLibs/config_defs.h
@@ -45,6 +45,7 @@ struct trx_cfg {
enum ReferenceType clock_ref;
enum FillerType filler;
bool multi_arfcn;
+ bool ms;
double offset;
double freq_offset_khz;
double rssi_offset;
diff --git a/GSM/GSMCommon.cpp b/GSM/GSMCommon.cpp
index 5e9e4ae..59548b2 100644
--- a/GSM/GSMCommon.cpp
+++ b/GSM/GSMCommon.cpp
@@ -61,6 +61,8 @@ const BitVector GSM::gRACHSynchSequenceTS0("010010110111111110011001101010100011
const BitVector GSM::gRACHSynchSequenceTS1("01010100111110001000011000101111001001101"); /* EGPRS, 8-PSK */
const BitVector GSM::gRACHSynchSequenceTS2("11101111001001110101011000001101101110111"); /* EGPRS, GMSK */
+const BitVector GSM::gSCHSynchSequence("1011100101100010000001000000111100101101010001010111011000011011");
+
// |-head-||---------midamble----------------------||--------------data----------------||t|
const BitVector GSM::gRACHBurst("0011101001001011011111111001100110101010001111000110111101111110000111001001010110011000");
diff --git a/GSM/GSMCommon.h b/GSM/GSMCommon.h
index 48723b4..386365a 100644
--- a/GSM/GSMCommon.h
+++ b/GSM/GSMCommon.h
@@ -57,6 +57,10 @@ extern const BitVector gDummyBurst;
extern const BitVector gRACHSynchSequenceTS0;
extern const BitVector gRACHSynchSequenceTS1;
extern const BitVector gRACHSynchSequenceTS2;
+
+/** Synchronization burst sync sequence */
+extern const BitVector gSCHSynchSequence;
+
/** Random access burst synch. sequence, GSM 05.02 5.2.7 */
extern const BitVector gRACHBurst;
diff --git a/Makefile.am b/Makefile.am
index 6a3a955..46d3251 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -28,6 +28,7 @@ AM_CXXFLAGS = -Wall -pthread
# Order must be preserved
SUBDIRS = \
+ trxcon \
CommonLibs \
GSM \
Transceiver52M \
diff --git a/Makefile.common b/Makefile.common
index 1de9733..1135609 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -28,6 +28,7 @@ STD_DEFINES_AND_INCLUDES = \
COMMON_LA = $(top_builddir)/CommonLibs/libcommon.la
GSM_LA = $(top_builddir)/GSM/libGSM.la
+TRXCON_LA = $(top_builddir)/trxcon/libtrxcon.la
if ARCH_ARM
ARCH_LA = $(top_builddir)/Transceiver52M/arch/arm/libarch.la
diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am
index 7dad159..a205d6b 100644
--- a/Transceiver52M/Makefile.am
+++ b/Transceiver52M/Makefile.am
@@ -30,6 +30,8 @@ AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_C
noinst_LTLIBRARIES = libtransceiver_common.la
COMMON_SOURCES = \
+ fbsb.cpp \
+ l1if.cpp \
radioInterface.cpp \
radioVector.cpp \
radioClock.cpp \
@@ -37,10 +39,12 @@ COMMON_SOURCES = \
sigProcLib.cpp \
signalVector.cpp \
Transceiver.cpp \
+ Transceiver2.cpp \
ChannelizerBase.cpp \
Channelizer.cpp \
Synthesis.cpp \
- proto_trxd.c
+ proto_trxd.c \
+ sch.c
libtransceiver_common_la_SOURCES = \
$(COMMON_SOURCES) \
@@ -57,6 +61,7 @@ noinst_HEADERS = \
sigProcLib.h \
signalVector.h \
Transceiver.h \
+ Transceiver2.h \
Resampler.h \
ChannelizerBase.h \
Channelizer.h \
@@ -68,6 +73,7 @@ COMMON_LDADD = \
$(ARCH_LA) \
$(GSM_LA) \
$(COMMON_LA) \
+ $(TRXCON_LA) \
$(FFTWF_LIBS) \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOCTRL_LIBS) \
@@ -81,7 +87,8 @@ osmo_trx_uhd_SOURCES = osmo-trx.cpp
osmo_trx_uhd_LDADD = \
$(builddir)/device/uhd/libdevice.la \
$(COMMON_LDADD) \
- $(UHD_LIBS)
+ $(UHD_LIBS) \
+ $(TRXCON_LA)
osmo_trx_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS)
endif
diff --git a/Transceiver52M/Transceiver2.cpp b/Transceiver52M/Transceiver2.cpp
new file mode 100644
index 0000000..595508e
--- /dev/null
+++ b/Transceiver52M/Transceiver2.cpp
@@ -0,0 +1,1198 @@
+/*
+* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
+*
+* This software is distributed under the terms of the GNU Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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 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 General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "osmocom/core/bits.h"
+#include <stdio.h>
+#include <Logger.h>
+#include "Transceiver2.h"
+
+extern "C" {
+#include "sch.h"
+}
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "l1if.h"
+
+using namespace GSM;
+
+#define USB_LATENCY_INTRVL 10,0
+
+#if USE_UHD
+# define USB_LATENCY_MIN 6,7
+#else
+# define USB_LATENCY_MIN 1,1
+#endif
+
+/* Clock indication interval in frames */
+#define CLK_IND_INTERVAL 100
+
+/* Number of running values use in noise average */
+#define NOISE_CNT 20
+#define FREQ_CNT 20
+
+TransceiverState::TransceiverState()
+ : mRetrans(false), mFreqOffsets(FREQ_CNT), mode(Transceiver2::TRX_MODE_OFF)
+{
+ for (int i = 0; i < 8; i++) {
+ chanType[i] = Transceiver2::NONE;
+ fillerModulus[i] = 26;
+ prevFrame[i] = NULL;
+
+ for (int n = 0; n < 102; n++)
+ fillerTable[n][i] = NULL;
+ }
+}
+
+TransceiverState::~TransceiverState()
+{
+ for (int i = 0; i < 8; i++) {
+ for (int n = 0; n < 102; n++)
+ delete fillerTable[n][i];
+ }
+}
+
+void TransceiverState::init(size_t slot, signalVector *burst, bool fill)
+{
+ signalVector *filler;
+
+ for (int i = 0; i < 102; i++) {
+ if (fill)
+ filler = new signalVector(*burst);
+ else
+ filler = new signalVector(burst->size());
+
+ fillerTable[i][slot] = filler;
+ }
+}
+
+Transceiver2::Transceiver2(int wBasePort,
+ const char *TRXAddress,
+ size_t wSPS, size_t wChans,
+ GSM::Time wTransmitLatency,
+ RadioInterface *wRadioInterface)
+ : rx_sps(4), tx_sps(4), mAddr(TRXAddress),
+ mTransmitLatency(wTransmitLatency),
+ mRadioInterface(wRadioInterface), mChans(wChans),
+ mOn(false), mTxFreq(0.0), mRxFreq(0.0), mPower(-10), mMaxExpectedDelay(0),
+ mBSIC(-1)
+{
+ GSM::Time startTime(random() % gHyperframe,0);
+
+ mLowerLoopThread = new Thread(32768);
+
+ mTransmitDeadlineClock = startTime;
+ mLastClockUpdateTime = startTime;
+ mLatencyUpdateTime = startTime;
+ mRadioInterface->getClock()->set(startTime);
+
+ txFullScale = mRadioInterface->fullScaleInputValue();
+ rxFullScale = mRadioInterface->fullScaleOutputValue();
+
+ for (int i = 0; i < 8; i++)
+ mRxSlotMask[i] = 0;
+}
+
+Transceiver2::~Transceiver2()
+{
+ stop();
+
+ sigProcLibDestroy();
+
+ for (size_t i = 0; i < mChans; i++) {
+ mTxPriorityQueues[i].clear();
+ }
+}
+
+bool Transceiver2::init(bool filler)
+{
+
+ if (!mChans) {
+ LOG(ALERT) << "No channels assigned";
+ return false;
+ }
+
+ if (!sigProcLibSetup()) {
+ LOG(ALERT) << "Failed to initialize signal processing library";
+ return false;
+ }
+
+ mControlServiceLoopThreads.resize(mChans);
+ mTxPriorityQueueServiceLoopThreads.resize(mChans);
+ mRxServiceLoopThreads.resize(mChans);
+
+ mTxPriorityQueues.resize(mChans);
+ mReceiveFIFO.resize(mChans);
+ mStates.resize(mChans);
+
+ /* Filler table retransmissions - support only on channel 0 */
+ if (filler)
+ mStates[0].mRetrans = true;
+
+
+ for (size_t i = 0; i < mChans; i++) {
+ mControlServiceLoopThreads[i] = new Thread(32768);
+ mTxPriorityQueueServiceLoopThreads[i] = new Thread(32768);
+ mRxServiceLoopThreads[i] = new Thread(32768);
+
+ for (size_t n = 0; n < 8; n++) {
+ // burst = modulateBurst(gDummyBurst, 8 + (n % 4 == 0), tx_sps);
+ // scaleVector(*burst, txFullScale);
+ // mStates[i].init(n, burst, filler && !i);
+ // delete burst;
+ }
+ }
+
+ return true;
+}
+
+void Transceiver2::addRadioVector(size_t chan, BitVector &bits,
+ int RSSI, GSM::Time &wTime)
+{
+ signalVector *burst;
+ radioVector *radio_burst;
+
+ if (chan >= mTxPriorityQueues.size()) {
+ LOG(ALERT) << "Invalid channel " << chan;
+ return;
+ }
+
+ if (wTime.TN() > 7) {
+ LOG(ALERT) << "Received burst with invalid slot " << wTime.TN();
+ return;
+ }
+
+ if (mStates[0].mode != TRX_MODE_BTS)
+ return;
+
+ burst = modulateBurst(bits, 8 + (wTime.TN() % 4 == 0), tx_sps);
+ scaleVector(*burst, txFullScale * pow(10, -RSSI / 10));
+
+ radio_burst = new radioVector(wTime, burst);
+
+ mTxPriorityQueues[chan].write(radio_burst);
+}
+
+void Transceiver2::updateFillerTable(size_t chan, radioVector *burst)
+{
+ int TN, modFN;
+ TransceiverState *state = &mStates[chan];
+
+ TN = burst->getTime().TN();
+ modFN = burst->getTime().FN() % state->fillerModulus[TN];
+
+ delete state->fillerTable[modFN][TN];
+ state->fillerTable[modFN][TN] = burst->getVector();
+ burst->setVector(NULL);
+}
+
+void Transceiver2::pushRadioVector(GSM::Time &nowTime)
+{
+ int TN, modFN;
+ radioVector *burst;
+ TransceiverState *state;
+ std::vector<signalVector *> bursts(mChans);
+ std::vector<bool> zeros(mChans, false);
+ std::vector<bool> filler(mChans, true);
+
+ for (size_t i = 0; i < mChans; i ++) {
+ state = &mStates[i];
+
+ while ((burst = mTxPriorityQueues[i].getStaleBurst(nowTime))) {
+ LOG(NOTICE) << "dumping STALE burst in TRX->USRP interface";
+ if (state->mRetrans)
+ updateFillerTable(i, burst);
+ delete burst;
+ }
+
+ TN = nowTime.TN();
+ modFN = nowTime.FN() % state->fillerModulus[TN];
+
+ bursts[i] = state->fillerTable[modFN][TN];
+ if (state->mode == TRX_MODE_BTS)
+ zeros[i] = state->chanType[TN] == NONE;
+
+ if ((burst = mTxPriorityQueues[i].getCurrentBurst(nowTime))) {
+ bursts[i] = burst->getVector();
+
+ if (state->mRetrans) {
+ updateFillerTable(i, burst);
+ } else {
+ burst->setVector(NULL);
+ filler[i] = false;
+ }
+
+ delete burst;
+ }
+ }
+
+ mRadioInterface->driveTransmitRadio(bursts, zeros);
+
+ for (size_t i = 0; i < mChans; i++) {
+ if (!filler[i])
+ delete bursts[i];
+ }
+}
+
+void Transceiver2::setModulus(size_t timeslot, size_t chan)
+{
+ TransceiverState *state = &mStates[chan];
+
+ switch (state->chanType[timeslot]) {
+ case NONE:
+ case I:
+ case II:
+ case III:
+ case FILL:
+ state->fillerModulus[timeslot] = 26;
+ break;
+ case IV:
+ case VI:
+ case V:
+ state->fillerModulus[timeslot] = 51;
+ break;
+ //case V:
+ case VII:
+ state->fillerModulus[timeslot] = 102;
+ break;
+ case XIII:
+ state->fillerModulus[timeslot] = 52;
+ break;
+ default:
+ break;
+ }
+}
+
+
+CorrType Transceiver2::expectedCorrType(GSM::Time currTime,
+ size_t chan)
+{
+ TransceiverState *state = &mStates[chan];
+ unsigned burstTN = currTime.TN();
+ unsigned burstFN = currTime.FN();
+
+ if (state->mode == TRX_MODE_MS_TRACK) {
+ /* 102 modulus case currently unhandled */
+ if (state->fillerModulus[burstTN] > 52)
+ return OFF;
+
+ int modFN = burstFN % state->fillerModulus[burstTN];
+ unsigned long long reg = (unsigned long long) 1 << modFN;
+ if (reg & mRxSlotMask[burstTN])
+ return TSC;
+ else
+ return OFF;
+ }
+
+ switch (state->chanType[burstTN]) {
+ case NONE:
+ return OFF;
+ break;
+ case FILL:
+ return IDLE;
+ break;
+ case I:
+ return TSC;
+ /*if (burstFN % 26 == 25)
+ return IDLE;
+ else
+ return TSC;*/
+ break;
+ case II:
+ return TSC;
+ break;
+ case III:
+ return TSC;
+ break;
+ case IV:
+ case VI:
+ return RACH;
+ break;
+ case V: {
+ int mod51 = burstFN % 51;
+ if ((mod51 <= 36) && (mod51 >= 14))
+ return RACH;
+ else if ((mod51 == 4) || (mod51 == 5))
+ return RACH;
+ else if ((mod51 == 45) || (mod51 == 46))
+ return RACH;
+ else
+ return TSC;
+ break;
+ }
+ case VII:
+ if ((burstFN % 51 <= 14) && (burstFN % 51 >= 12))
+ return IDLE;
+ else
+ return TSC;
+ break;
+ case XIII: {
+ int mod52 = burstFN % 52;
+ if ((mod52 == 12) || (mod52 == 38))
+ return RACH;
+ else if ((mod52 == 25) || (mod52 == 51))
+ return IDLE;
+ else
+ return TSC;
+ break;
+ }
+ case LOOPBACK:
+ if ((burstFN % 51 <= 50) && (burstFN % 51 >=48))
+ return IDLE;
+ else
+ return TSC;
+ break;
+ default:
+ return OFF;
+ break;
+ }
+}
+
+
+/* Detect SCH synchronization sequence within a burst */
+bool Transceiver2::detectSCH(TransceiverState *state,
+ signalVector &burst,
+ struct estim_burst_params *ebp)
+{
+ int shift;
+ sch_detect_type full;
+ float mag, threshold = 5.0;
+
+ full = (state->mode == TRX_MODE_MS_TRACK) ?
+ sch_detect_type::SCH_DETECT_NARROW : sch_detect_type::SCH_DETECT_FULL;
+
+ if (!detectSCHBurst(burst, threshold, rx_sps, full, ebp))
+ return false;
+
+ std::clog << "SCH : Timing offset " << ebp->toa << " symbols" << std::endl;
+
+ mag = fabsf(ebp->toa);
+ if (mag < 1.0f)
+ return true;
+
+ shift = (int) (mag / 2.0f);
+ if (!shift)
+ shift++;
+
+ shift = ebp->toa > 0 ? shift : -shift;
+ std::clog << "SCH : shift -> " << shift << " symbols" << std::endl;
+ mRadioInterface->applyOffset(shift);
+ return false;
+}
+
+#define SCH_BIT_SCALE 64
+
+/* Decode SCH burst */
+bool Transceiver2::decodeSCH(SoftVector *burst, GSM::Time *time)
+{
+ int fn;
+ struct sch_info sch;
+ ubit_t info[GSM_SCH_INFO_LEN];
+ sbit_t data[GSM_SCH_CODED_LEN];
+
+ if (burst->size() < 156) {
+ std::clog << "Invalid SCH burst length" << std::endl;
+ return false;
+ }
+
+ float_to_sbit(&(*burst)[3], &data[0], SCH_BIT_SCALE, 39);
+ float_to_sbit(&(*burst)[106], &data[39], SCH_BIT_SCALE, 39);
+
+ if (!gsm_sch_decode(info, data)) {
+ gsm_sch_parse(info, &sch);
+
+ mBSIC = sch.bsic;
+ mTSC = mBSIC & 0x7;
+
+// std::clog << "SCH : Decoded values" << std::endl;
+// std::clog << " BSIC: " << sch.bsic << std::endl;
+// std::clog << " T1 : " << sch.t1 << std::endl;
+// std::clog << " T2 : " << sch.t2 << std::endl;
+// std::clog << " T3p : " << sch.t3p << std::endl;
+// std::clog << " FN : " << gsm_sch_to_fn(&sch) << std::endl;
+
+ fn = gsm_sch_to_fn(&sch);
+ if (fn < 0) {
+ std::clog << "SCH : Failed to convert FN " << std::endl;
+ return false;
+ }
+
+ time->FN(fn);
+ time->TN(0);
+ } else {
+ std::clog << "Invalid SCH decode!!" << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+#define FCCH_OFFSET_LIMIT 5e3
+#define FCCH_ADJUST_LIMIT 20.0
+
+/* Apply FCCH frequency correction */
+bool Transceiver2::correctFCCH(TransceiverState *state, signalVector *burst)
+{
+ double offset;
+
+ if (!burst)
+ return false;
+
+ offset = gsm_fcch_offset((float *) burst->begin(), burst->size());
+ //std::cout << "XXXX FCCH: Frequency offset " << offset << " Hz" << std::endl;
+ if (offset > FCCH_OFFSET_LIMIT)
+ return false;
+
+ state->mFreqOffsets.insert(offset);
+
+ if (state->mFreqOffsets.full()) {
+ double avg = state->mFreqOffsets.avg();
+ std::clog << "FCCH: Frequency offset " << avg << " Hz" << std::endl;
+
+ if (fabs(avg) > FCCH_ADJUST_LIMIT) {
+ mRadioInterface->tuneRxOffset(-avg);
+ state->mFreqOffsets.reset();
+ }
+ }
+
+ return true;
+}
+
+/*
+ * Pull bursts from the FIFO and handle according to the slot
+ * and burst correlation type. Equalzation is currently disabled.
+ */
+SoftVector *Transceiver2::pullRadioVector(GSM::Time &wTime, int &RSSI,
+ int &timingOffset, size_t chan) __attribute__((optnone))
+{
+ struct estim_burst_params ebp;
+ int rc;
+ float pow, max = -1.0, avg = 1.0;
+ int max_i = -1;
+ signalVector *burst;
+ SoftVector *bits = NULL;
+ TransceiverState *state = &mStates[chan];
+
+ GSM::Time sch_time, burst_time, diff_time;
+
+ /* Blocking FIFO read */
+ radioVector *radio_burst = mReceiveFIFO[chan]->read();
+ if (!radio_burst)
+ return NULL;
+
+ /* Set time and determine correlation type */
+ burst_time = radio_burst->getTime();
+
+#if 0
+ if (state->mode == TRX_MODE_MS_ACQUIRE) {
+ switch(fbsb_acq_buf.s.load()) {
+ case fbsb_par::fbsb_state::IDLE:
+ case fbsb_par::fbsb_state::INIT:
+ fbsb_acq_buf.s.store(fbsb_par::fbsb_state::ACQ);
+ case fbsb_par::fbsb_state::ACQ:
+ fbsb_acq_buf.take(radio_burst->getVector()->begin(), radio_burst->getVector()->size(), burst_time);
+
+ if(!fbsb_acq_buf.done()) {
+ delete radio_burst;
+ return nullptr;
+ }
+ fbsb_acq_buf.s.store(fbsb_par::fbsb_state::ACQ_COMPL);
+// break;
+ case fbsb_par::fbsb_state::ACQ_COMPL:
+
+
+ {
+ complex famp = 0;
+ int found_index;
+ auto foundat = fbsb_acq_buf.fcch(&famp, &found_index, false);
+ std::cerr << "@ " << found_index << std::endl;
+ foundat = found_index;
+
+ auto framelen = (3 + 142 + 3 + 8.25); // 1sps!;
+ int searchbegin = 8*framelen-20;
+ int searchend = 10*framelen+20;
+
+ if(famp.abs() < 2000 || foundat+searchend > fbsb_acq_buf.sz()) {
+ mReceiveFIFO[chan]->clear();
+ delete radio_burst;
+ fbsb_acq_buf.reset();
+ fbsb_acq_buf.s.store(fbsb_par::fbsb_state::ACQ);
+ return nullptr;
+ }
+ correctFCCH_raw(state, &fbsb_acq_buf.fbsb_buf[foundat], 98);
+
+ burst = new signalVector(searchend-searchbegin, GSM::gRACHSynchSequence.size());
+// burst = new signalVector(fbsb_acq_buf.fbsb_buf, foundat+searchbegin, searchend-searchbegin);
+ memcpy(burst->begin(), &fbsb_acq_buf.fbsb_buf[foundat+searchbegin], (searchend-searchbegin) * sizeof(complex));
+ success = detectSCH(state, *burst, amp, toa, 0); // will "fail" if sample adjustment is required
+ std::cerr << "###detected sch: " << success << "at toa " << toa << std::endl;
+ mReceiveFIFO[chan]->clear();
+ delete radio_burst;
+
+ if(toa > 0) {
+ int how_many_ts = (found_index+searchbegin)/framelen;
+// int how_many_fn = how_many_ts/8;
+ auto t = fbsb_acq_buf.rcvClock[how_many_ts];
+
+ diff_time = GSM::Time(sch_time.FN() - t.FN(),-t.TN());
+ mRadioInterface->adjustClock(diff_time);
+ mTransmitDeadlineClock = RadioClock::adjust(
+ mTransmitDeadlineClock,
+ diff_time);
+ }
+ if(!success) {
+ fbsb_acq_buf.reset();
+ fbsb_acq_buf.s.store(fbsb_par::fbsb_state::ACQ);
+ }
+ else
+ fbsb_acq_buf.s.store(fbsb_par::fbsb_state::DONE);
+ return nullptr;
+ }
+ case fbsb_par::fbsb_state::DONE:
+ break;
+ default:
+ // fbsb_acq_buf.s = fbsb_par::fbsb_state::WAIT;
+ return nullptr;
+ break;
+ /* no-op */
+ }
+
+ }
+#endif
+ CorrType type = expectedCorrType(burst_time, chan);
+
+ switch (state->mode) {
+ case TRX_MODE_MS_ACQUIRE:
+ type = SCH;
+ break;
+ case TRX_MODE_MS_TRACK:
+ if (gsm_sch_check_fn(burst_time.FN()) && burst_time.TN() == 0)
+ type = SCH;
+ else if(burst_time.TN() == 0 && !gsm_fcch_check_fn(burst_time.FN())) // all ts0, but not fcch or sch..
+ type = TSC;
+ else if (type == OFF)
+ goto release;
+ break;
+ case TRX_MODE_BTS:
+ if ((type == TSC) || (type == RACH))
+ break;
+ case TRX_MODE_OFF:
+ default:
+ goto release;
+ }
+
+ /* Select the diversity channel with highest energy */
+ for (size_t i = 0; i < radio_burst->chans(); i++) {
+ float pow = energyDetect(*radio_burst->getVector(i), 20 * rx_sps);
+ if (pow > max) {
+ max = pow;
+ max_i = i;
+ }
+ avg += pow;
+ }
+
+ if (max_i < 0) {
+ LOG(ALERT) << "Received empty burst";
+ goto release;
+ }
+
+ /* Average noise on diversity paths and update global levels */
+ burst = radio_burst->getVector(max_i);
+ avg = sqrt(avg / radio_burst->chans());
+
+
+ /* Detect normal or RACH bursts */
+ if (type == SCH) {
+ rc = detectSCH(state, *burst, &ebp);
+ rc = rc > 0 ? rc : -1;
+ } else {
+ rc = detectAnyBurst(*burst, mTSC, BURST_THRESH, rx_sps, type, mMaxExpectedDelay, &ebp);
+ if(rc > 0) {
+ type = (CorrType)rc;
+ }
+ }
+
+ if (rc < 0)
+ goto release;
+
+
+ /* Ignore noise threshold on MS mode for now */
+ //if ((type == SCH) || (avg - state->mNoiseLev > 0.0))
+ bits = demodAnyBurst(*burst, type, rx_sps, &ebp);
+
+ /* MS: Decode SCH and adjust GSM clock */
+ if ((type != TSC) &&
+ ((state->mode == TRX_MODE_MS_ACQUIRE) ||
+ (state->mode == TRX_MODE_MS_TRACK))) {
+ correctFCCH(state, state->prevFrame[burst_time.TN()]->getVector());
+
+ if (decodeSCH(bits, &sch_time)) {
+ if (state->mode == TRX_MODE_MS_ACQUIRE) {
+ diff_time = GSM::Time(sch_time.FN() - burst_time.FN(),
+ -burst_time.TN());
+ mRadioInterface->adjustClock(diff_time);
+ mTransmitDeadlineClock = RadioClock::adjust(
+ mTransmitDeadlineClock,
+ diff_time);
+ state->mode = TRX_MODE_MS_TRACK;
+
+ burst_time = sch_time; //sch bits passed up might have a different time, so upper layers complain
+ std::clog << "SCH : Locking GSM clock " << std::endl;
+ } else {
+ //std::clog << "SCH : Read SCH at FN " << burst_time.FN() << " FN51 " << burst_time.FN() % 51 << std::endl;
+ wTime = burst_time;
+ RSSI = (int) floor(20.0 * log10(rxFullScale / avg));
+ timingOffset = (int) round(ebp.toa * 256.0 / rx_sps);
+ return bits;
+ }
+ }
+ else
+ std::clog << "SCH : FAIL!!!!! SCH at FN " << burst_time.FN() << std::endl;
+
+ goto release;
+ }
+
+ wTime = burst_time;
+ RSSI = (int) floor(20.0 * log10(rxFullScale / avg));
+ timingOffset = (int) round(ebp.toa * 256.0 / rx_sps);
+
+ delete state->prevFrame[burst_time.TN()];
+ state->prevFrame[burst_time.TN()] = radio_burst;
+
+ return bits;
+
+release:
+ delete state->prevFrame[burst_time.TN()];
+ state->prevFrame[burst_time.TN()] = radio_burst;
+ delete bits;
+ return NULL;
+}
+
+void Transceiver2::start()
+{
+ TransceiverChannel *chan;
+
+ for (size_t i = 0; i < mControlServiceLoopThreads.size(); i++) {
+ chan = new TransceiverChannel(this, i);
+ mControlServiceLoopThreads[i]->start((void * (*)(void*))
+ ControlServiceLoopAdapter, (void*) chan);
+ }
+}
+
+void Transceiver2::stop()
+{
+
+ if (!mOn)
+ return;
+
+ LOG(NOTICE) << "Stopping the transceiver";
+ mLowerLoopThread->cancel();
+ mLowerLoopThread->join();
+ delete mLowerLoopThread;
+
+
+ for (size_t i = 0; i < mChans; i++) {
+ mRxServiceLoopThreads[i]->cancel();
+ mTxPriorityQueueServiceLoopThreads[i]->cancel();
+ }
+
+ LOG(INFO) << "Stopping the device";
+ mRadioInterface->stop();
+
+ for (size_t i = 0; i < mChans; i++) {
+ mRxServiceLoopThreads[i]->join();
+ mTxPriorityQueueServiceLoopThreads[i]->join();
+ delete mRxServiceLoopThreads[i];
+ delete mTxPriorityQueueServiceLoopThreads[i];
+
+ mTxPriorityQueues[i].clear();
+ }
+
+ mOn = false;
+ LOG(NOTICE) << "Transceiver stopped";
+}
+
+
+void Transceiver2::reset()
+{
+ for (size_t i = 0; i < mTxPriorityQueues.size(); i++)
+ mTxPriorityQueues[i].clear();
+}
+
+
+void Transceiver2::driveControl(size_t chan)
+{
+ //char response[MAX_PACKET_LENGTH];
+
+ // check control socket
+// char buffer[MAX_PACKET_LENGTH];
+// int msgLen = -1;
+// buffer[0] = '\0';
+
+
+//// msgLen = mCtrlSockets[chan]->read(buffer);
+
+// if (msgLen < 1) {
+// return;
+// }
+
+ auto m = pop_c();
+ if(!m)
+ return;
+
+auto response = (TRX_C*)malloc(sizeof(TRX_C));
+response->cmd[0] = '\0';
+commandhandler(m->cmd, response->cmd, chan);
+free(m);
+std::clog << "response is " << response->cmd << std::endl;
+push_c(response);
+ //mCtrlSockets[chan]->write(response, strlen(response) + 1);
+}
+
+void Transceiver2::commandhandler(char* buffer, char* response, int chan)
+{
+ int MAX_PACKET_LENGTH = TRXC_BUF_SIZE;
+
+ char cmdcheck[4];
+ char command[MAX_PACKET_LENGTH];
+
+
+ sscanf(buffer,"%3s %s",cmdcheck,command);
+
+ if (!chan)
+ writeClockInterface();
+
+ if (strcmp(cmdcheck,"CMD")!=0) {
+ LOG(WARNING) << "bogus message on control interface";
+ return;
+ }
+ std::clog << "command is " << buffer << std::endl << std::flush;
+
+ if (strcmp(command,"MEASURE")==0) {
+ msleep(100);
+ int freq;
+ sscanf(buffer,"%3s %s %d",cmdcheck,command,&freq);
+ sprintf(response,"RSP MEASURE 0 %d -80",freq);
+ }
+ else if (strcmp(command,"ECHO")==0) {
+ msleep(100);
+ sprintf(response,"RSP ECHO 0");
+ }
+ else if (strcmp(command,"POWEROFF")==0) {
+ // turn off transmitter/demod
+ sprintf(response,"RSP POWEROFF 0");
+ }
+ else if (strcmp(command,"POWERON")==0) {
+ // turn on transmitter/demod
+ if (!mTxFreq || !mRxFreq)
+ sprintf(response,"RSP POWERON 1");
+ else {
+ sprintf(response,"RSP POWERON 0");
+ if (!chan && !mOn) {
+ // Prepare for thread start
+ mPower = -20;
+ mRadioInterface->start();
+
+ // Start radio interface threads.
+ mLowerLoopThread->start((void * (*)(void*))
+ LowerLoopAdapter,(void*) this);
+
+ for (size_t i = 0; i < mChans; i++) {
+ TransceiverChannel *chan = new TransceiverChannel(this, i);
+ mRxServiceLoopThreads[i]->start((void * (*)(void*))
+ RxUpperLoopAdapter, (void*) chan);
+
+ // chan = new TransceiverChannel(this, i);
+ // mTxPriorityQueueServiceLoopThreads[i]->start((void * (*)(void*))
+ // TxUpperLoopAdapter, (void*) chan);
+ }
+
+ writeClockInterface();
+ mOn = true;
+ }
+ }
+ }
+ else if (strcmp(command,"SETMAXDLY")==0) {
+ //set expected maximum time-of-arrival
+ int maxDelay;
+ sscanf(buffer,"%3s %s %d",cmdcheck,command,&maxDelay);
+ mMaxExpectedDelay = maxDelay; // 1 GSM symbol is approx. 1 km
+ sprintf(response,"RSP SETMAXDLY 0 %d",maxDelay);
+ }
+ else if (strcmp(command,"SETRXGAIN")==0) {
+ //set expected maximum time-of-arrival
+ int newGain;
+ sscanf(buffer,"%3s %s %d",cmdcheck,command,&newGain);
+ newGain = mRadioInterface->setRxGain(newGain, chan);
+ sprintf(response,"RSP SETRXGAIN 0 %d",newGain);
+ }
+ else if (strcmp(command,"NOISELEV")==0) {
+ if (mOn) {
+ float lev = 0;//mStates[chan].mNoiseLev;
+ sprintf(response,"RSP NOISELEV 0 %d",
+ (int) round(20.0 * log10(rxFullScale / lev)));
+ }
+ else {
+ sprintf(response,"RSP NOISELEV 1 0");
+ }
+ }
+ else if (!strcmp(command, "SETPOWER")) {
+ // set output power in dB
+ int dbPwr;
+ sscanf(buffer, "%3s %s %d", cmdcheck, command, &dbPwr);
+ if (!mOn)
+ sprintf(response, "RSP SETPOWER 1 %d", dbPwr);
+ else {
+ mPower = dbPwr;
+ mRadioInterface->setPowerAttenuation(mPower, chan);
+ sprintf(response, "RSP SETPOWER 0 %d", dbPwr);
+ }
+ }
+ else if (!strcmp(command,"ADJPOWER")) {
+ // adjust power in dB steps
+ int dbStep;
+ sscanf(buffer, "%3s %s %d", cmdcheck, command, &dbStep);
+ if (!mOn)
+ sprintf(response, "RSP ADJPOWER 1 %d", mPower);
+ else {
+ mPower += dbStep;
+ mRadioInterface->setPowerAttenuation(mPower, chan);
+ sprintf(response, "RSP ADJPOWER 0 %d", mPower);
+ }
+ }
+ else if (strcmp(command,"RXTUNE")==0) {
+ // tune receiver
+ int freqKhz;
+ sscanf(buffer,"%3s %s %d",cmdcheck,command,&freqKhz);
+ mRxFreq = freqKhz * 1e3;
+ if (!mRadioInterface->tuneRx(mRxFreq, chan)) {
+ LOG(ALERT) << "RX failed to tune";
+ sprintf(response,"RSP RXTUNE 1 %d",freqKhz);
+ }
+ else
+ sprintf(response,"RSP RXTUNE 0 %d",freqKhz);
+ }
+ else if (strcmp(command,"TXTUNE")==0) {
+ // tune txmtr
+ int freqKhz;
+ sscanf(buffer,"%3s %s %d",cmdcheck,command,&freqKhz);
+ mTxFreq = freqKhz * 1e3;
+ if (!mRadioInterface->tuneTx(mTxFreq, chan)) {
+ LOG(ALERT) << "TX failed to tune";
+ sprintf(response,"RSP TXTUNE 1 %d",freqKhz);
+ }
+ else
+ sprintf(response,"RSP TXTUNE 0 %d",freqKhz);
+ }
+ else if (!strcmp(command,"SETTSC")) {
+ // set TSC
+ unsigned TSC;
+ sscanf(buffer, "%3s %s %d", cmdcheck, command, &TSC);
+ if (mOn)
+ sprintf(response, "RSP SETTSC 1 %d", TSC);
+ else if (chan && (TSC != mTSC))
+ sprintf(response, "RSP SETTSC 1 %d", TSC);
+ else {
+ mTSC = TSC;
+ //generateMidamble(rx_sps, TSC);
+ sprintf(response,"RSP SETTSC 0 %d", TSC);
+ }
+ }
+ else if (!strcmp(command,"GETBSIC")) {
+ if (mBSIC < 0)
+ sprintf(response, "RSP GETBSIC 1");
+ else
+ sprintf(response, "RSP GETBSIC 0 %d", mBSIC);
+ }
+ else if (strcmp(command,"SETSLOT")==0) {
+ // set TSC
+ int corrCode;
+ int timeslot;
+ sscanf(buffer,"%3s %s %d %d",cmdcheck,command,&timeslot,&corrCode);
+ if ((timeslot < 0) || (timeslot > 7)) {
+ LOG(WARNING) << "bogus message on control interface";
+ sprintf(response,"RSP SETSLOT 1 %d %d",timeslot,corrCode);
+ return;
+ }
+ mStates[chan].chanType[timeslot] = (ChannelCombination) corrCode;
+ setModulus(timeslot, chan);
+ sprintf(response,"RSP SETSLOT 0 %d %d",timeslot,corrCode);
+ }
+ else if (!strcmp(command,"SETRXMASK")) {
+ int slot;
+ unsigned long long mask;
+ sscanf(buffer,"%3s %s %d 0x%llx", cmdcheck, command, &slot, &mask);
+ if ((slot < 0) || (slot > 7)) {
+ sprintf(response, "RSP SETRXMASK 1");
+ } else {
+ mRxSlotMask[slot] = mask;
+ sprintf(response, "RSP SETRXMASK 0 %d 0x%llx", slot, mask);
+ }
+ }
+ else if (!strcmp(command, "SYNC")) {
+ msleep(10);
+ mStates[0].mode = TRX_MODE_MS_ACQUIRE;
+ sprintf(response,"RSP SYNC 0");
+ mMaxExpectedDelay = 10;
+ mRadioInterface->setRxGain(30, 0);
+ msleep(10);
+ }
+ else {
+ LOG(WARNING) << "bogus command " << command << " on control interface.";
+ }
+
+ //mCtrlSockets[chan]->write(response, strlen(response) + 1);
+}
+
+bool Transceiver2::driveTxPriorityQueue(size_t chan)
+{
+ auto burst = pop_d();
+ if(!burst)
+ return true;
+
+ auto currTime = GSM::Time(burst->fn,burst->ts);
+ int RSSI = (int) burst->txlev;
+
+ static BitVector newBurst(gSlotLen);
+ BitVector::iterator itr = newBurst.begin();
+ auto *bufferItr = burst->symbols;
+ while (itr < newBurst.end())
+ *itr++ = *bufferItr++;
+
+ addRadioVector(chan, newBurst, RSSI, currTime);
+ free(burst);
+ return true;
+
+
+
+// char buffer[gSlotLen+50];
+
+// // check data socket
+// size_t msgLen = mDataSockets[chan]->read(buffer);
+
+// if (msgLen!=gSlotLen+1+4+1) {
+// LOG(ERR) << "badly formatted packet on GSM->TRX interface";
+// return false;
+// }
+
+// int timeSlot = (int) buffer[0];
+// uint64_t frameNum = 0;
+// for (int i = 0; i < 4; i++)
+// frameNum = (frameNum << 8) | (0x0ff & buffer[i+1]);
+
+// LOG(DEBUG) << "rcvd. burst at: " << GSM::Time(frameNum,timeSlot);
+
+// int RSSI = (int) buffer[5];
+// static BitVector newBurst(gSlotLen);
+// BitVector::iterator itr = newBurst.begin();
+// char *bufferItr = buffer+6;
+// while (itr < newBurst.end())
+// *itr++ = *bufferItr++;
+
+// GSM::Time currTime = GSM::Time(frameNum,timeSlot);
+
+// addRadioVector(chan, newBurst, RSSI, currTime);
+
+ return true;
+
+
+}
+
+void Transceiver2::driveReceiveRadio()
+{
+ if (!mRadioInterface->driveReceiveRadio())
+ usleep(100000);
+}
+
+void Transceiver2::driveReceiveFIFO(size_t chan)
+{
+ SoftVector *rxBurst = NULL;
+ int RSSI;
+ int TOA; // in 1/256 of a symbol
+ GSM::Time burstTime;
+
+ rxBurst = pullRadioVector(burstTime, RSSI, TOA, chan);
+
+ if (rxBurst) {
+ auto response = (trxd_from_trx*)malloc(sizeof(trxd_from_trx));
+#if 0
+ if( !gsm_fcch_check_fn(burstTime.FN()) && !gsm_sch_check_fn(burstTime.FN()))
+ std::cerr << "burst parameters: "
+ << " time: " << burstTime
+ << " RSSI: " << RSSI
+ << " TOA: " << TOA
+ << " bits: " << *rxBurst << std::endl;
+#endif
+ response->ts = burstTime.TN();
+ response->fn = burstTime.FN();
+ response->rssi = RSSI;
+ response->toa = TOA;
+
+ SoftVector::const_iterator burstItr = rxBurst->begin();
+ if(gsm_sch_check_fn(burstTime.FN())) {
+ for (unsigned int i = 0; i < gSlotLen; i++)
+ ((int8_t*)response->symbols)[i] = round(((*burstItr++)-0.5) * 64.0);
+ } else {
+ // invert and fix to +-127 sbits
+ for (int i = 0; i < 148; i++)
+ ((int8_t*)response->symbols)[i] = *burstItr++ > 0.0f ? -127 : 127;
+ }
+
+ delete rxBurst;
+ push_d(response);
+
+ }
+}
+
+void Transceiver2::driveTxFIFO()
+{
+
+ /**
+ Features a carefully controlled latency mechanism, to
+ assure that transmit packets arrive at the radio/USRP
+ before they need to be transmitted.
+
+ Deadline clock indicates the burst that needs to be
+ pushed into the FIFO right NOW. If transmit queue does
+ not have a burst, stick in filler data.
+ */
+
+
+ RadioClock *radioClock = (mRadioInterface->getClock());
+
+ if (mOn) {
+ //radioClock->wait(); // wait until clock updates
+ LOG(DEBUG) << "radio clock " << radioClock->get();
+ while (radioClock->get() + mTransmitLatency > mTransmitDeadlineClock) {
+ // if underrun, then we're not providing bursts to radio/USRP fast
+ // enough. Need to increase latency by one GSM frame.
+ if (mRadioInterface->getWindowType() == RadioDevice::TX_WINDOW_USRP1) {
+ if (mRadioInterface->isUnderrun()) {
+ // only update latency at the defined frame interval
+ if (radioClock->get() > mLatencyUpdateTime + GSM::Time(USB_LATENCY_INTRVL)) {
+ mTransmitLatency = mTransmitLatency + GSM::Time(1,0);
+ LOG(INFO) << "new latency: " << mTransmitLatency;
+ mLatencyUpdateTime = radioClock->get();
+ }
+ }
+ else {
+ // if underrun hasn't occurred in the last sec (216 frames) drop
+ // transmit latency by a timeslot
+ if (mTransmitLatency > GSM::Time(USB_LATENCY_MIN)) {
+ if (radioClock->get() > mLatencyUpdateTime + GSM::Time(216,0)) {
+ mTransmitLatency.decTN();
+ //LOG(INFO) << "reduced latency: " << mTransmitLatency;
+ mLatencyUpdateTime = radioClock->get();
+ }
+ }
+ }
+ }
+ // time to push burst to transmit FIFO
+ pushRadioVector(mTransmitDeadlineClock);
+
+ mTransmitDeadlineClock.incTN();
+
+ if (!mTransmitDeadlineClock.TN() &&
+ !(mTransmitDeadlineClock.FN() % CLK_IND_INTERVAL)) {
+ writeClockInterface();
+ }
+ }
+ }
+
+ radioClock->wait();
+}
+
+
+
+void Transceiver2::writeClockInterface()
+{
+ mLastClockUpdateTime = mTransmitDeadlineClock;
+}
+
+void *RxUpperLoopAdapter(TransceiverChannel *chan)
+{
+ Transceiver2 *trx = chan->trx;
+ size_t num = chan->num;
+
+ delete chan;
+
+ // trx->setPriority(0.42);
+
+ while (1) {
+ trx->driveReceiveFIFO(num);
+ pthread_testcancel();
+ }
+ return NULL;
+}
+
+void *LowerLoopAdapter(Transceiver2 *transceiver)
+{
+ // transceiver->setPriority(0.45);
+
+ while (1) {
+ transceiver->driveReceiveRadio();
+ //transceiver->driveTxFIFO();
+ pthread_testcancel();
+ }
+ return NULL;
+}
+
+void *ControlServiceLoopAdapter(TransceiverChannel *chan)
+{
+ Transceiver2 *trx = chan->trx;
+ size_t num = chan->num;
+
+ delete chan;
+
+ while (1) {
+ trx->driveControl(num);
+ pthread_testcancel();
+ }
+ return NULL;
+}
+
+void *TxUpperLoopAdapter(TransceiverChannel *chan)
+{
+ Transceiver2 *trx = chan->trx;
+ size_t num = chan->num;
+
+ delete chan;
+
+ // trx->setPriority(0.40);
+
+ while (1) {
+ bool stale = false;
+ // Flush the UDP packets until a successful transfer.
+ while (!trx->driveTxPriorityQueue(num)) {
+ stale = true;
+ }
+ if (!num && stale) {
+ // If a packet was stale, remind the GSM stack of the clock.
+ trx->writeClockInterface();
+ }
+ pthread_testcancel();
+ }
+ return NULL;
+}
diff --git a/Transceiver52M/Transceiver2.h b/Transceiver52M/Transceiver2.h
new file mode 100644
index 0000000..e5579f4
--- /dev/null
+++ b/Transceiver52M/Transceiver2.h
@@ -0,0 +1,258 @@
+/*
+* Copyright 2008 Free Software Foundation, Inc.
+*
+* This software is distributed under the terms of the GNU Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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 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 General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "radioInterface.h"
+#include "Interthread.h"
+#include "GSMCommon.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+extern "C" {
+#include <osmocom/core/signal.h>
+#include <osmocom/core/select.h>
+#include "config_defs.h"
+}
+
+class Transceiver2;
+
+/** Channel descriptor for transceiver object and channel number pair */
+struct TransceiverChannel {
+ TransceiverChannel(Transceiver2 *trx, int num)
+ {
+ this->trx = trx;
+ this->num = num;
+ }
+
+ ~TransceiverChannel()
+ {
+ }
+
+ Transceiver2 *trx;
+ size_t num;
+};
+
+/** Internal transceiver state variables */
+struct TransceiverState {
+ TransceiverState();
+ ~TransceiverState();
+
+ /* Initialize a multiframe slot in the filler table */
+ void init(size_t slot, signalVector *burst, bool fill);
+
+ int chanType[8];
+
+ /* The filler table */
+ signalVector *fillerTable[102][8];
+ int fillerModulus[8];
+ bool mRetrans;
+
+ /* Received noise energy levels */
+ noiseVector mFreqOffsets;
+
+ /* Store pointers to previous frame */
+ radioVector *prevFrame[8];
+
+ /* Transceiver mode */
+ int mode;
+};
+
+/** The Transceiver class, responsible for physical layer of basestation */
+class Transceiver2 {
+private:
+
+ int rx_sps, tx_sps;
+ std::string mAddr;
+ GSM::Time mTransmitLatency; ///< latency between basestation clock and transmit deadline clock
+ GSM::Time mLatencyUpdateTime; ///< last time latency was updated
+
+ std::vector<VectorQueue> mTxPriorityQueues; ///< priority queue of transmit bursts received from GSM core
+ std::vector<VectorFIFO *> mReceiveFIFO; ///< radioInterface FIFO of receive bursts
+
+ std::vector<Thread *> mRxServiceLoopThreads; ///< thread to pull bursts into receive FIFO
+ Thread *mLowerLoopThread; ///< thread to pull bursts into receive FIFO
+ std::vector<Thread *> mControlServiceLoopThreads; ///< thread to process control messages from GSM core
+ std::vector<Thread *> mTxPriorityQueueServiceLoopThreads; ///< thread to process transmit bursts from GSM core
+
+ GSM::Time mTransmitDeadlineClock; ///< deadline for pushing bursts into transmit FIFO
+ GSM::Time mLastClockUpdateTime; ///< last time clock update was sent up to core
+
+ RadioInterface *mRadioInterface; ///< associated radioInterface object
+ double txFullScale; ///< full scale input to radio
+ double rxFullScale; ///< full scale output to radio
+
+ /** modulate and add a burst to the transmit queue */
+ void addRadioVector(size_t chan, BitVector &bits,
+ int RSSI, GSM::Time &wTime);
+
+ /** Update filler table */
+ void updateFillerTable(size_t chan, radioVector *burst);
+
+ /** Push modulated burst into transmit FIFO corresponding to a particular timestamp */
+ void pushRadioVector(GSM::Time &nowTime);
+
+ /** Pull and demodulate a burst from the receive FIFO */
+ SoftVector *pullRadioVector(GSM::Time &wTime, int &RSSI,
+ int &timingOffset, size_t chan = 0);
+
+ /** Set modulus for specific timeslot */
+ void setModulus(size_t timeslot, size_t chan);
+
+ /** return the expected burst type for the specified timestamp */
+ CorrType expectedCorrType(GSM::Time currTime, size_t chan);
+
+ /** send messages over the clock socket */
+ void writeClockInterface(void);
+
+
+ bool detectSCH(TransceiverState *state,
+ signalVector &burst,
+ struct estim_burst_params *ebp);
+
+ bool decodeSCH(SoftVector *burst, GSM::Time *time);
+ bool correctFCCH(TransceiverState *state, signalVector *burst);
+
+ size_t mChans;
+
+ bool mOn; ///< flag to indicate that transceiver is powered on
+ double mTxFreq; ///< the transmit frequency
+ double mRxFreq; ///< the receive frequency
+ int mPower; ///< the transmit power in dB
+ unsigned mTSC; ///< the midamble sequence code
+ unsigned mMaxExpectedDelay; ///< maximum TOA offset in GSM symbols
+ unsigned long long mRxSlotMask[8]; ///< MS - enabled multiframe slot mask
+ int mBSIC; ///< MS - detected BSIC
+
+ std::vector<TransceiverState> mStates;
+
+public:
+
+ /** Transceiver constructor
+ @param wBasePort base port number of UDP sockets
+ @param TRXAddress IP address of the TRX manager, as a string
+ @param wSPS number of samples per GSM symbol
+ @param wTransmitLatency initial setting of transmit latency
+ @param radioInterface associated radioInterface object
+ */
+ Transceiver2(int wBasePort,
+ const char *TRXAddress,
+ size_t wSPS, size_t chans,
+ GSM::Time wTransmitLatency,
+ RadioInterface *wRadioInterface);
+
+ /** Destructor */
+ ~Transceiver2();
+
+ /** start the Transceiver */
+ void start();
+ bool init(bool filler);
+
+ /** attach the radioInterface receive FIFO */
+ bool receiveFIFO(VectorFIFO *wFIFO, size_t chan)
+ {
+ if (chan >= mReceiveFIFO.size())
+ return false;
+
+ mReceiveFIFO[chan] = wFIFO;
+ return true;
+ }
+
+ /** accessor for number of channels */
+ size_t numChans() const { return mChans; };
+
+ /** Codes for channel combinations */
+ typedef enum {
+ FILL, ///< Channel is transmitted, but unused
+ I, ///< TCH/FS
+ II, ///< TCH/HS, idle every other slot
+ III, ///< TCH/HS
+ IV, ///< FCCH+SCH+CCCH+BCCH, uplink RACH
+ V, ///< FCCH+SCH+CCCH+BCCH+SDCCH/4+SACCH/4, uplink RACH+SDCCH/4
+ VI, ///< CCCH+BCCH, uplink RACH
+ VII, ///< SDCCH/8 + SACCH/8
+ VIII, ///< TCH/F + FACCH/F + SACCH/M
+ IX, ///< TCH/F + SACCH/M
+ X, ///< TCH/FD + SACCH/MD
+ XI, ///< PBCCH+PCCCH+PDTCH+PACCH+PTCCH
+ XII, ///< PCCCH+PDTCH+PACCH+PTCCH
+ XIII, ///< PDTCH+PACCH+PTCCH
+ NONE, ///< Channel is inactive, default
+ LOOPBACK ///< similar go VII, used in loopback testing
+ } ChannelCombination;
+
+ enum {
+ TRX_MODE_OFF,
+ TRX_MODE_BTS,
+ TRX_MODE_MS_ACQUIRE,
+ TRX_MODE_MS_TRACK,
+ };
+
+ void commandhandler(char *buffer, char *response, int chan);
+ void stop();
+protected:
+ /** drive lower receive I/O and burst generation */
+ void driveReceiveRadio();
+
+ /** drive demodulation of GSM bursts */
+ void driveReceiveFIFO(size_t chan);
+
+ /** drive transmission of GSM bursts */
+ void driveTxFIFO();
+
+ /** drive handling of control messages from GSM core */
+ void driveControl(size_t chan);
+
+ /**
+ drive modulation and sorting of GSM bursts from GSM core
+ @return true if a burst was transferred successfully
+ */
+ bool driveTxPriorityQueue(size_t chan);
+
+ friend void *RxUpperLoopAdapter(TransceiverChannel *);
+
+ friend void *TxUpperLoopAdapter(TransceiverChannel *);
+
+ friend void *LowerLoopAdapter(Transceiver2 *);
+
+ friend void *ControlServiceLoopAdapter(TransceiverChannel *);
+
+
+ void reset();
+
+ /** set priority on current thread */
+ //void setPriority(float prio = 0.5) { mRadioInterface->setPriority(prio); }
+
+};
+
+void *RxUpperLoopAdapter(TransceiverChannel *);
+
+/** Main drive threads */
+void *LowerLoopAdapter(Transceiver2 *);
+
+/** control message handler thread loop */
+void *ControlServiceLoopAdapter(TransceiverChannel *);
+
+/** transmit queueing thread loop */
+void *TxUpperLoopAdapter(TransceiverChannel *);
+
diff --git a/Transceiver52M/device/common/radioDevice.h b/Transceiver52M/device/common/radioDevice.h
index 3f5da1f..1e19bdc 100644
--- a/Transceiver52M/device/common/radioDevice.h
+++ b/Transceiver52M/device/common/radioDevice.h
@@ -101,6 +101,9 @@ class RadioDevice {
/** Set the receiver frequency */
virtual bool setRxFreq(double wFreq, size_t chan = 0) = 0;
+ /** Adjust the receiver offset */
+ virtual bool setRxOffset(double wOffset, size_t chan = 0) = 0;
+
/** Returns the starting write Timestamp*/
virtual TIMESTAMP initialWriteTimestamp(void)=0;
diff --git a/Transceiver52M/device/uhd/UHDDevice.cpp b/Transceiver52M/device/uhd/UHDDevice.cpp
index f109660..d8b969a 100644
--- a/Transceiver52M/device/uhd/UHDDevice.cpp
+++ b/Transceiver52M/device/uhd/UHDDevice.cpp
@@ -557,7 +557,7 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
#ifdef USE_UHD_3_11
uhd::log::add_logger("OsmoTRX", &uhd_log_handler);
uhd::log::set_log_level(uhd::log::debug);
- uhd::log::set_console_level(uhd::log::off);
+ uhd::log::set_console_level(uhd::log::debug);
uhd::log::set_logger_level("OsmoTRX", uhd::log::debug);
#else
uhd::msg::register_handler(&uhd_msg_handler);
@@ -870,7 +870,7 @@ int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
if (rc < 0) {
LOGC(DDEV, ERROR) << rx_buffers[0]->str_code(rc);
LOGC(DDEV, ERROR) << rx_buffers[0]->str_status(timestamp);
- return 0;
+ return len;
}
// Receive samples from the usrp until we have enough
@@ -975,13 +975,14 @@ int uhd_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun
bool uhd_device::updateAlignment(TIMESTAMP timestamp)
{
+ aligned = false;
return true;
}
uhd::tune_request_t uhd_device::select_freq(double freq, size_t chan, bool tx)
{
double rf_spread, rf_freq;
- std::vector<double> freqs;
+ std::vector<tune_result> freqs;
uhd::tune_request_t treq(freq);
if (dev_type == UMTRX) {
@@ -1013,17 +1014,17 @@ uhd::tune_request_t uhd_device::select_freq(double freq, size_t chan, bool tx)
freqs = rx_freqs;
/* Tune directly if other channel isn't tuned */
- if (freqs[!chan] < 10.0)
+ if (freqs[!chan].freq < 10.0)
return treq;
/* Find center frequency between channels */
- rf_spread = fabs(freqs[!chan] - freq);
+ rf_spread = fabs(freqs[!chan].freq - freq);
if (rf_spread > dev_param_map.at(dev_key(B210, tx_sps, rx_sps)).mcr) {
LOGC(DDEV, ALERT) << rf_spread << "Hz tuning spread not supported\n";
return treq;
}
- rf_freq = (freqs[!chan] + freq) / 2.0f;
+ rf_freq = (freqs[!chan].freq + freq) / 2.0f;
treq.rf_freq_policy = uhd::tune_request_t::POLICY_MANUAL;
treq.target_freq = freq;
@@ -1041,11 +1042,13 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx)
if (tx) {
tres = usrp_dev->set_tx_freq(treq, chan);
- tx_freqs[chan] = usrp_dev->get_tx_freq(chan);
+ tx_freqs[chan].uhd = tres;
+ tx_freqs[chan].freq = usrp_dev->get_tx_freq(chan);
str_dir = "Tx";
} else {
tres = usrp_dev->set_rx_freq(treq, chan);
- rx_freqs[chan] = usrp_dev->get_rx_freq(chan);
+ rx_freqs[chan].uhd = tres;
+ rx_freqs[chan].freq = usrp_dev->get_rx_freq(chan);
str_dir = "Rx";
}
LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << str_dir << "): " << tres.to_pp_string() << std::endl;
@@ -1059,13 +1062,15 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx)
*/
if (treq.rf_freq_policy == uhd::tune_request_t::POLICY_MANUAL) {
if (tx) {
- treq = select_freq(tx_freqs[!chan], !chan, true);
+ treq = select_freq(tx_freqs[!chan].freq, !chan, true);
tres = usrp_dev->set_tx_freq(treq, !chan);
- tx_freqs[!chan] = usrp_dev->get_tx_freq(!chan);
+ tx_freqs[chan].uhd = tres;
+ tx_freqs[!chan].freq = usrp_dev->get_tx_freq(!chan);
} else {
- treq = select_freq(rx_freqs[!chan], !chan, false);
+ treq = select_freq(rx_freqs[!chan].freq, !chan, false);
tres = usrp_dev->set_rx_freq(treq, !chan);
- rx_freqs[!chan] = usrp_dev->get_rx_freq(!chan);
+ tx_freqs[chan].uhd = tres;
+ rx_freqs[!chan].freq = usrp_dev->get_rx_freq(!chan);
}
LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << str_dir << "): " << tres.to_pp_string() << std::endl;
@@ -1105,6 +1110,20 @@ bool uhd_device::setTxFreq(double wFreq, size_t chan)
return true;
}
+bool uhd_device::setRxOffset(double wOffset, size_t chan)
+{
+ uhd::tune_result_t tres;
+ uhd::tune_request_t treq(rx_freqs[chan].freq - wOffset);
+
+ treq.rf_freq_policy = uhd::tune_request_t::POLICY_MANUAL;
+ treq.rf_freq = rx_freqs[chan].uhd.actual_rf_freq;
+
+ tres = usrp_dev->set_rx_freq(treq, chan);
+ rx_freqs[chan].freq = usrp_dev->get_rx_freq(chan);
+
+ return true;
+}
+
bool uhd_device::setRxFreq(double wFreq, size_t chan)
{
uint16_t req_arfcn;
@@ -1116,19 +1135,19 @@ bool uhd_device::setRxFreq(double wFreq, size_t chan)
}
ScopedLock lock(tune_lock);
- req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 1);
- if (req_arfcn == 0xffff) {
- LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Rx Frequency " << wFreq / 1000 << " kHz";
- return false;
- }
- if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
- LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Rx Frequency " << wFreq
- << " Hz (ARFCN " << req_arfcn << " )";
- return false;
- }
+ // req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 1);
+ // if (req_arfcn == 0xffff) {
+ // LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Rx Frequency " << wFreq / 1000 << " kHz";
+ // return false;
+ // }
+ // if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
+ // LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Rx Frequency " << wFreq
+ // << " Hz (ARFCN " << req_arfcn << " )";
+ // return false;
+ // }
- if (!set_band(req_band))
- return false;
+ // if (!set_band(req_band))
+ // return false;
return set_freq(wFreq, chan, false);
}
@@ -1140,7 +1159,7 @@ double uhd_device::getTxFreq(size_t chan)
return 0.0;
}
- return tx_freqs[chan];
+ return tx_freqs[chan].freq;
}
double uhd_device::getRxFreq(size_t chan)
@@ -1150,7 +1169,7 @@ double uhd_device::getRxFreq(size_t chan)
return 0.0;
}
- return rx_freqs[chan];
+ return rx_freqs[chan].freq;
}
bool uhd_device::setRxAntenna(const std::string &ant, size_t chan)
diff --git a/Transceiver52M/device/uhd/UHDDevice.h b/Transceiver52M/device/uhd/UHDDevice.h
index 659fd18..d23911d 100644
--- a/Transceiver52M/device/uhd/UHDDevice.h
+++ b/Transceiver52M/device/uhd/UHDDevice.h
@@ -80,6 +80,12 @@ struct dev_band_desc {
*/
class uhd_device : public RadioDevice {
public:
+
+ struct tune_result {
+ uhd::tune_result_t uhd;
+ double freq;
+ };
+
uhd_device(size_t tx_sps, size_t rx_sps, InterfaceType type,
size_t chan_num, double offset,
const std::vector<std::string>& tx_paths,
@@ -102,6 +108,7 @@ public:
bool setTxFreq(double wFreq, size_t chan);
bool setRxFreq(double wFreq, size_t chan);
+ bool setRxOffset(double wOffset, size_t chan);
TIMESTAMP initialWriteTimestamp();
TIMESTAMP initialReadTimestamp();
@@ -159,7 +166,7 @@ protected:
double rx_gain_min, rx_gain_max;
std::vector<double> tx_gains, rx_gains;
- std::vector<double> tx_freqs, rx_freqs;
+ std::vector<tune_result> tx_freqs, rx_freqs;
bool band_ass_curr_sess; /* true if "band" was set after last POWEROFF */
enum gsm_band band;
struct dev_band_desc band_desc;
diff --git a/Transceiver52M/fbsb.cpp b/Transceiver52M/fbsb.cpp
new file mode 100644
index 0000000..870b28c
--- /dev/null
+++ b/Transceiver52M/fbsb.cpp
@@ -0,0 +1,148 @@
+#include <atomic>
+#include "radioInterface.h"
+#include "Interthread.h"
+#include "GSMCommon.h"
+//#include "Sockets.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+
+static const auto tssize = 156.25;
+static const auto framee_to_read = 12;
+static const auto ts_to_read = framee_to_read * 8;
+static const int fbsb_chunk_len = ts_to_read * 2 * tssize; // contiguous fcch+sch guaranteed
+static const auto corr_offset = 0;
+
+struct fbsb_par {
+
+ enum class fbsb_state {
+ IDLE,
+ INIT,
+ ACQ,
+ WAIT,
+ ACQ_COMPL,
+ DONE
+ };
+
+ int pos;
+ int time_idx;
+ std::atomic<fbsb_state> s;
+ GSM::Time rcvClock[ts_to_read];
+ complex fbsb_buf[fbsb_chunk_len+corr_offset];
+ complex conjbuf[fbsb_chunk_len+corr_offset];
+ complex avgbuf[fbsb_chunk_len+corr_offset];
+ void addclk(GSM::Time c) { rcvClock[time_idx] = c; time_idx++; return;}
+public:
+ fbsb_par() : s(fbsb_state::IDLE) {
+ reset();
+ };
+ bool done() {return !(time_idx < ts_to_read);}
+ void take(void* addr, int num_cplx_sps, GSM::Time c) {
+ memcpy(fbsb_buf+pos+corr_offset,addr, num_cplx_sps * sizeof(complex));
+ pos += num_cplx_sps;
+ assert(pos < fbsb_chunk_len);
+ rcvClock[time_idx] = c;
+ time_idx++;
+ }
+ void reset() {pos = 0;
+ time_idx = 0;
+// s = fbsb_state::IDLE;
+ memset(fbsb_buf, 0, sizeof(fbsb_buf));
+ memset(conjbuf, 0, sizeof(conjbuf));
+ memset(avgbuf, 0, sizeof(avgbuf));
+ }
+ int sz () {return fbsb_chunk_len;}
+ int off () {return corr_offset;}
+
+ // from osmotrx sigproc
+ complex fastPeakDetect(complex* rxBurst, float* index, int N)
+ {
+ float val, max = 0.0f;
+ complex amp;
+ int _index = -1;
+
+ for (size_t i = 0; i < N; i++) {
+ val = rxBurst[i].abs();
+ if (val > max) {
+ max = val;
+ _index = i;
+ amp = rxBurst[i];
+ }
+ }
+
+ if (index)
+ *index = (float)_index;
+
+ return amp;
+ }
+
+ void conj_with_lag(complex* in, complex* out, int lag, int offset, int N) {
+ int total_offset = offset + lag;
+ for (int s = 0; s < N; s++)
+ out[s] = in[s + total_offset] * in[s + total_offset - lag].conj();
+ }
+
+ auto ac_sum_with_lag(complex* in, int lag, int offset, int N) {
+ complex v(0,0);
+ auto total_offset = offset + lag;
+ for (auto s = 0; s < N; s++)
+ v += in[s + total_offset] * in[s + total_offset - lag].conj();
+ return atan2(v.imag(), v.real());
+ }
+
+
+ bool running_avg_opt(complex* in, complex* out, int avg_window, int val_to_find, int* idx, int N) {
+ bool found = false;
+ complex avg0 = 0;
+ complex scale(avg_window, avg_window);
+ for (auto i = 0; i < avg_window; i++)
+ avg0 += in[i];
+ out[0] = avg0 / scale;
+
+ //skip first
+ for (auto i = 1; i < N - avg_window; i++) {
+ avg0 += in[i-1] - in[i+avg_window];
+ auto tmp = avg0 / scale;
+ out[i] = tmp;
+ if (!found && tmp.abs() > val_to_find) {
+ found = !found;
+ *idx = i;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ int fcch(complex* amp, int* found_idx, bool dump) {
+ conj_with_lag(fbsb_buf, conjbuf, 3, off(), sz());
+
+ running_avg_opt(conjbuf, avgbuf, 48, 1.5e6, found_idx, sz()-off());
+ float pos;
+ auto r = fastPeakDetect(avgbuf, &pos, sz()-off());
+ *found_idx = *found_idx+off();
+ std::cerr << "fcch found at " << pos << " amp: " << r.abs() << std::endl;
+ *amp = r;
+
+ if(dump) {
+ {
+ auto f = fopen("inbuf.cfile", "wb");
+ fwrite(fbsb_buf, sz(), 1, f);
+ fclose(f);
+ }
+ {
+ auto f = fopen("conjbuf.cfile", "wb");
+ fwrite(conjbuf, sz(), 1, f);
+ fclose(f);
+ }
+ {
+ auto f = fopen("schfcch.cfile", "wb");
+ fwrite(avgbuf, sz(), 1, f);
+ fclose(f);
+ }
+ exit(0);
+ }
+ return pos;
+ }
+};
+
diff --git a/Transceiver52M/fbsb.h b/Transceiver52M/fbsb.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Transceiver52M/fbsb.h
diff --git a/Transceiver52M/l1if.cpp b/Transceiver52M/l1if.cpp
new file mode 100644
index 0000000..440f6e7
--- /dev/null
+++ b/Transceiver52M/l1if.cpp
@@ -0,0 +1,124 @@
+#include <mutex>
+#include <queue>
+#include <deque>
+#include <condition_variable>
+#include <iostream>
+
+extern "C" {
+
+#include <unistd.h>
+#include <sys/eventfd.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+}
+#include "l1if.h"
+
+using namespace std;
+using namespace std::chrono_literals;
+
+template<typename Data>
+class spsc_q{
+
+ std::queue<Data> m_q;
+ std::mutex m_mtx;
+ std::condition_variable m_cond;
+ bool killme;
+
+public:
+ spsc_q() : killme{ false } { }
+
+ void push(Data i){
+ std::unique_lock<std::mutex> lock(m_mtx);
+ m_q.push(i);
+ m_cond.notify_one();
+ }
+
+ Data pop(){
+ std::unique_lock<std::mutex> lock(m_mtx);
+ m_cond.wait_for(lock, 100ms, [&](){ return !m_q.empty() || killme; });
+
+ if (killme || m_q.empty()){
+ return {};
+ }
+
+ Data x = m_q.front();
+ m_q.pop();
+
+ return x;
+ }
+
+ void stop(){
+ killme = true;
+ m_cond.notify_all();
+ }
+
+ auto sz() { return m_q.size(); }
+};
+
+
+/*
+ * trxif_from_trx_c <-> push_c
+ * trxif_to_trx_c <-> pop_c
+ * trxif_from_trx_d <-> push_d
+ * trxif_to_trx_d <-> pop_d
+ * ...
+ *
+ *
+ */
+class trxl1if {
+public:
+ spsc_q<TRX_C*> c_to_trx;
+ spsc_q<TRX_C*> c_from_trx;
+
+ spsc_q<trxd_to_trx*> d_to_trx;
+ spsc_q<trxd_from_trx*> d_from_trx;
+
+ struct osmo_fd g_event_ofd_C;
+ struct osmo_fd g_event_ofd_D;
+};
+
+trxl1if trxif;
+
+void push_c(TRX_C* i) {
+ uint64_t one = 1;
+ int rc;
+ trxif.c_from_trx.push(i);
+ std::clog << trxif.c_from_trx.sz() << std::endl;
+ rc = ::write(trxif.g_event_ofd_C.fd, &one, sizeof(one));
+ return;
+};
+TRX_C* pop_c() {
+ return trxif.c_to_trx.pop();
+};
+void push_d(trxd_from_trx* i) {
+ uint64_t one = 1;
+ int rc;
+ trxif.d_from_trx.push(i);
+ rc = ::write(trxif.g_event_ofd_D.fd, &one, sizeof(one));
+ return;
+};
+trxd_to_trx* pop_d() {
+ return trxif.d_to_trx.pop();
+};
+
+extern "C" {
+char* trxif_from_trx_c() {
+ uint64_t one = 1;
+ ::read(trxif.g_event_ofd_C.fd, &one, sizeof(one));
+ return (char*)trxif.c_from_trx.pop();
+}
+void trxif_to_trx_c(char* msg) {
+ trxif.c_to_trx.push((TRX_C*)msg);
+}
+trxd_from_trx* trxif_from_trx_d() {
+ uint64_t one = 1;
+ ::read(trxif.g_event_ofd_D.fd, &one, sizeof(one));
+ return trxif.d_from_trx.pop();
+}
+void trxif_to_trx_d(trxd_to_trx* msg) {
+ trxif.d_to_trx.push(msg);
+}
+struct osmo_fd* get_c_fd() { return &trxif.g_event_ofd_C;}
+struct osmo_fd* get_d_fd() { return &trxif.g_event_ofd_D;}
+}
diff --git a/Transceiver52M/l1if.h b/Transceiver52M/l1if.h
new file mode 100644
index 0000000..d477e47
--- /dev/null
+++ b/Transceiver52M/l1if.h
@@ -0,0 +1,74 @@
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include <stdint.h>
+#ifdef __cplusplus
+}
+#endif
+
+/* ------------------------------------------------------------------------ */
+/* Data interface handlers */
+/* ------------------------------------------------------------------------ */
+/* DATA interface */
+/* */
+/* Messages on the data interface carry one radio burst per UDP message. */
+/* */
+/* Received Data Burst: */
+/* 1 byte timeslot index */
+/* 4 bytes GSM frame number, BE */
+/* 1 byte RSSI in -dBm */
+/* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE */
+/* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1" */
+/* 2 bytes are not used, but being sent by OsmoTRX */
+/* */
+/* Transmit Data Burst: */
+/* 1 byte timeslot index */
+/* 4 bytes GSM frame number, BE */
+/* 1 byte transmit level wrt ARFCN max, -dB (attenuation) */
+/* 148 bytes output symbol values, 0 & 1 */
+/* ------------------------------------------------------------------------ */
+
+struct trxd_to_trx {
+ uint8_t ts;
+ uint32_t fn;
+ uint8_t txlev;
+ uint8_t symbols[148];
+};
+
+struct trxd_from_trx {
+ uint8_t ts;
+ uint32_t fn;
+ uint8_t rssi;
+ uint16_t toa;
+ uint8_t symbols[148];
+ uint8_t pad[2];
+};
+
+#define TRXC_BUF_SIZE 1024
+
+struct TRX_C {
+ char cmd[TRXC_BUF_SIZE];
+};
+
+#ifdef __cplusplus
+void push_c(TRX_C* i);
+TRX_C* pop_c();
+
+void push_d(trxd_from_trx* i);
+trxd_to_trx* pop_d();
+
+#else
+
+
+char* trxif_from_trx_c();
+void trxif_to_trx_c(char* msg);
+
+struct trxd_from_trx* trxif_from_trx_d();
+void trxif_to_trx_d(struct trxd_to_trx* msg);
+
+struct osmo_fd* get_c_fd();
+struct osmo_fd* get_d_fd();
+
+#endif
+
diff --git a/Transceiver52M/osmo-trx-org.cpp b/Transceiver52M/osmo-trx-org.cpp
new file mode 100644
index 0000000..bd93ab7
--- /dev/null
+++ b/Transceiver52M/osmo-trx-org.cpp
@@ -0,0 +1,718 @@
+/*
+ * Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "Transceiver.h"
+#include "radioDevice.h"
+#include "Utils.h"
+
+#include <time.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sched.h>
+#include <vector>
+#include <string>
+#include <sstream>
+#include <iostream>
+#include <sys/signalfd.h>
+
+#include <GSMCommon.h>
+#include <Logger.h>
+
+extern "C" {
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/vty/misc.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/ctrl/control_vty.h>
+#include <osmocom/ctrl/ports.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/cpu_sched_vty.h>
+
+#include "convolve.h"
+#include "convert.h"
+#include "trx_vty.h"
+#include "debug.h"
+#include "osmo_signal.h"
+#include "trx_rate_ctr.h"
+}
+
+#define DEFAULT_CONFIG_FILE "osmo-trx.cfg"
+
+#define charp2str(a) ((a) ? std::string(a) : std::string(""))
+
+static char* config_file = (char*)DEFAULT_CONFIG_FILE;
+
+struct osmo_fd signal_ofd;
+volatile bool gshutdown = false;
+
+static void *tall_trx_ctx;
+static struct trx_ctx *g_trx_ctx;
+static struct ctrl_handle *g_ctrlh;
+
+static RadioDevice *usrp;
+static RadioInterface *radio;
+
+/* Create radio interface
+ * The interface consists of sample rate changes, frequency shifts,
+ * channel multiplexing, and other conversions. The transceiver core
+ * accepts input vectors sampled at multiples of the GSM symbol rate.
+ * The radio interface connects the main transceiver with the device
+ * object, which may be operating some other rate.
+ */
+RadioInterface *makeRadioInterface(struct trx_ctx *trx,
+ RadioDevice *usrp, int type)
+{
+ RadioInterface *radio = NULL;
+ size_t div = 1;
+ int offset = 3;
+
+ if (trx->cfg.ms) {
+ if (type != RadioDevice::NORMAL) {
+ LOG(ALERT) << "Unsupported configuration";
+ return NULL;
+ }
+
+ offset *= -1;
+ }
+
+ switch (type) {
+ case RadioDevice::NORMAL:
+ radio = new RadioInterface(usrp, trx->cfg.tx_sps,
+ trx->cfg.rx_sps, trx->cfg.num_chans, offset, offset);
+ break;
+ case RadioDevice::RESAMP_64M:
+ case RadioDevice::RESAMP_100M:
+ radio = new RadioInterfaceResamp(usrp, trx->cfg.tx_sps,
+ trx->cfg.rx_sps);
+ break;
+ case RadioDevice::MULTI_ARFCN:
+ radio = new RadioInterfaceMulti(usrp, trx->cfg.tx_sps,
+ trx->cfg.rx_sps, trx->cfg.num_chans);
+ break;
+ default:
+ LOG(ALERT) << "Unsupported radio interface configuration";
+ return NULL;
+ }
+
+ if (!radio->init(type)) {
+ LOG(ALERT) << "Failed to initialize radio interface";
+ return NULL;
+ }
+
+ return radio;
+}
+
+/* Callback function to be called every time we receive a signal from TRANSC */
+static int transc_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ switch (signal) {
+ case S_MAIN_STOP_REQUIRED:
+ gshutdown = true;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/* Create transceiver core
+ * The multi-threaded modem core operates at multiples of the GSM rate of
+ * 270.8333 ksps and consists of GSM specific modulation, demodulation,
+ * and decoding schemes. Also included are the socket interfaces for
+ * connecting to the upper layer stack.
+ */
+int makeTransceiver(struct trx_ctx *trx, RadioInterface *radio)
+{
+ VectorFIFO *fifo;
+
+ transceiver = new Transceiver2(&trx->cfg, GSM::Time(3,0), radio);
+ if (!transceiver->init()) {
+ LOG(ALERT) << "Failed to initialize transceiver";
+ return -1;
+ }
+
+ for (size_t i = 0; i < trx->cfg.num_chans; i++) {
+ fifo = radio->receiveFIFO(i);
+ if (fifo && transceiver->receiveFIFO(fifo, i))
+ continue;
+
+ LOG(ALERT) << "Could not attach FIFO to channel " << i;
+ return -1;
+ }
+ return 0;
+}
+
+static void sig_handler(int signo)
+{
+
+ if (gshutdown)
+ /* We are in the middle of shutdown process, avoid any kind of extra
+ action like printing */
+ return;
+
+ fprintf(stderr, "signal %d received\n", signo);
+ switch (signo) {
+ case SIGINT:
+ case SIGTERM:
+ fprintf(stderr, "shutting down\n");
+ gshutdown = true;
+ 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(tall_trx_ctx, stderr);
+ talloc_report_full(tall_trx_ctx, stderr);
+ signal(SIGABRT, SIG_DFL);
+ raise(SIGABRT);
+ break;
+ case SIGUSR1:
+ talloc_report(tall_trx_ctx, stderr);
+ talloc_report_full(tall_trx_ctx, stderr);
+ break;
+ case SIGUSR2:
+ talloc_report_full(tall_trx_ctx, stderr);
+ break;
+ case SIGHUP:
+ log_targets_reopen();
+ default:
+ break;
+ }
+
+}
+
+static int signalfd_callback(struct osmo_fd *ofd, unsigned int what)
+{
+ struct signalfd_siginfo fdsi;
+ ssize_t s;
+
+ s = read(ofd->fd, &fdsi, sizeof(struct signalfd_siginfo));
+ if (s < 0) {
+ LOG(FATAL) << "Failed to read from signalfd ("<< ofd->fd << "): " << errno;
+ gshutdown = true;
+ return 0;
+ }
+ sig_handler(fdsi.ssi_signo);
+ return 0;
+}
+
+static void setup_signal_handlers()
+{
+ sigset_t set;
+ int sfd;
+
+ signal(SIGABRT, &sig_handler);
+ osmo_init_ignore_signals();
+
+ /* Other threads created by this thread (main) will inherit a copy of the
+ signal mask. */
+ sigemptyset(&set);
+ sigaddset(&set, SIGINT);
+ sigaddset(&set, SIGTERM);
+ sigaddset(&set, SIGUSR1);
+ sigaddset(&set, SIGUSR2);
+ sigaddset(&set, SIGHUP);
+ if (pthread_sigmask(SIG_BLOCK, &set, NULL)) {
+ fprintf(stderr, "pthread_sigmask() failed.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if ((sfd = signalfd(-1, &set, 0)) == -1) {
+ fprintf(stderr, "signalfd() failed (%d).\n", errno);
+ exit(EXIT_FAILURE);
+ }
+
+ osmo_fd_setup(&signal_ofd, sfd, OSMO_FD_READ, signalfd_callback, NULL, 0);
+ if (osmo_fd_register(&signal_ofd) < 0) {
+ fprintf(stderr, "osmo_fd_register() failed.\n");
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void print_help()
+{
+ printf( "Some useful options:\n"
+ " -h, --help This text\n"
+ " -C, --config Filename The config file to use\n"
+ " -V, --version Print the version of OsmoTRX\n"
+ "\nVTY reference generation:\n"
+ " --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n"
+ " --vty-ref-xml Generate the VTY reference XML output and exit.\n"
+ );
+}
+
+static void print_deprecated(char opt)
+{
+ LOG(WARNING) << "Cmd line option '" << opt << "' is deprecated and will be soon removed."
+ << " Please use VTY cfg option instead."
+ << " All cmd line options are already being overridden by VTY options if set.";
+}
+
+static void handle_long_options(const char *prog_name, const int long_option)
+{
+ static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT;
+
+ switch (long_option) {
+ case 1:
+ vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg);
+ if (vty_ref_mode < 0) {
+ fprintf(stderr, "%s: Unknown VTY reference generation "
+ "mode '%s'\n", prog_name, optarg);
+ exit(2);
+ }
+ break;
+ case 2:
+ fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
+ get_value_string(vty_ref_gen_mode_names, vty_ref_mode),
+ get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
+ vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
+ exit(0);
+ default:
+ fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
+ exit(2);
+ }
+}
+
+static void handle_options(int argc, char **argv, struct trx_ctx* trx)
+{
+ int option;
+ unsigned int i;
+ std::vector<std::string> rx_paths, tx_paths;
+ bool rx_paths_set = false, tx_paths_set = false;
+ static int long_option = 0;
+ static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"config", 1, 0, 'C'},
+ {"version", 0, 0, 'V'},
+ {"vty-ref-mode", 1, &long_option, 1},
+ {"vty-ref-xml", 0, &long_option, 2},
+ {NULL, 0, 0, 0}
+ };
+
+ while ((option = getopt_long(argc, argv, "ha:l:i:j:p:c:dmxgfo:s:b:r:A:R:Set:y:z:C:V", long_options,
+ NULL)) != -1) {
+ switch (option) {
+ case 'h':
+ print_help();
+ exit(0);
+ break;
+ case 0:
+ handle_long_options(argv[0], long_option);
+ break;
+ case 'a':
+ print_deprecated(option);
+ osmo_talloc_replace_string(trx, &trx->cfg.dev_args, optarg);
+ break;
+ case 'l':
+ print_deprecated(option);
+ log_set_log_level(osmo_stderr_target, atoi(optarg));
+ break;
+ case 'i':
+ print_deprecated(option);
+ osmo_talloc_replace_string(trx, &trx->cfg.remote_addr, optarg);
+ break;
+ case 'j':
+ print_deprecated(option);
+ osmo_talloc_replace_string(trx, &trx->cfg.bind_addr, optarg);
+ break;
+ case 'p':
+ print_deprecated(option);
+ trx->cfg.base_port = atoi(optarg);
+ break;
+ case 'c':
+ print_deprecated(option);
+ trx->cfg.num_chans = atoi(optarg);
+ break;
+ case 'm':
+ print_deprecated(option);
+ trx->cfg.multi_arfcn = true;
+ break;
+ case 'x':
+ print_deprecated(option);
+ trx->cfg.clock_ref = REF_EXTERNAL;
+ break;
+ case 'g':
+ print_deprecated(option);
+ trx->cfg.clock_ref = REF_GPS;
+ break;
+ case 'f':
+ print_deprecated(option);
+ trx->cfg.filler = FILLER_DUMMY;
+ break;
+ case 'o':
+ print_deprecated(option);
+ trx->cfg.offset = atof(optarg);
+ break;
+ case 's':
+ print_deprecated(option);
+ trx->cfg.tx_sps = atoi(optarg);
+ break;
+ case 'b':
+ print_deprecated(option);
+ trx->cfg.rx_sps = atoi(optarg);
+ break;
+ case 'r':
+ print_deprecated(option);
+ trx->cfg.rtsc = atoi(optarg);
+ if (!trx->cfg.egprs) /* Don't override egprs which sets different filler */
+ trx->cfg.filler = FILLER_NORM_RAND;
+ break;
+ case 'A':
+ print_deprecated(option);
+ trx->cfg.rach_delay = atoi(optarg);
+ trx->cfg.filler = FILLER_ACCESS_RAND;
+ break;
+ case 'R':
+ print_deprecated(option);
+ trx->cfg.rssi_offset = atof(optarg);
+ trx->cfg.force_rssi_offset = true;
+ break;
+ case 'S':
+ print_deprecated(option);
+ trx->cfg.swap_channels = true;
+ break;
+ case 'e':
+ print_deprecated(option);
+ trx->cfg.egprs = true;
+ break;
+ case 't':
+ print_deprecated(option);
+ trx->cfg.sched_rr = atoi(optarg);
+ break;
+ case 'y':
+ print_deprecated(option);
+ tx_paths = comma_delimited_to_vector(optarg);
+ tx_paths_set = true;
+ break;
+ case 'z':
+ print_deprecated(option);
+ rx_paths = comma_delimited_to_vector(optarg);
+ rx_paths_set = true;
+ break;
+ case 'C':
+ config_file = optarg;
+ break;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ default:
+ goto bad_config;
+ }
+ }
+
+ trx->cfg.ms = true;
+
+ if (argc > optind) {
+ LOG(ERROR) << "Unsupported positional arguments on command line";
+ goto bad_config;
+ }
+
+ /* Cmd line option specific validation & setup */
+
+ if (trx->cfg.num_chans > TRX_CHAN_MAX) {
+ LOG(ERROR) << "Too many channels requested, maximum is " << TRX_CHAN_MAX;
+ goto bad_config;
+ }
+ if ((tx_paths_set && tx_paths.size() != trx->cfg.num_chans) ||
+ (rx_paths_set && rx_paths.size() != trx->cfg.num_chans)) {
+ LOG(ERROR) << "Num of channels and num of Rx/Tx Antennas doesn't match";
+ goto bad_config;
+ }
+ for (i = 0; i < trx->cfg.num_chans; i++) {
+ trx->cfg.chans[i].trx = trx;
+ trx->cfg.chans[i].idx = i;
+ if (tx_paths_set)
+ osmo_talloc_replace_string(trx, &trx->cfg.chans[i].tx_path, tx_paths[i].c_str());
+ if (rx_paths_set)
+ osmo_talloc_replace_string(trx, &trx->cfg.chans[i].rx_path, rx_paths[i].c_str());
+ }
+
+ return;
+
+bad_config:
+ print_help();
+ exit(0);
+}
+
+int trx_validate_config(struct trx_ctx *trx)
+{
+ if (trx->cfg.multi_arfcn && trx->cfg.num_chans > TRX_MCHAN_MAX) {
+ LOG(ERROR) << "Unsupported number of channels";
+ return -1;
+ }
+
+ /* Force 4 SPS for EDGE or multi-ARFCN configurations */
+ if ((trx->cfg.egprs || trx->cfg.multi_arfcn) &&
+ (trx->cfg.tx_sps!=4 || trx->cfg.rx_sps!=4)) {
+ LOG(ERROR) << "EDGE and Multi-Carrier options require 4 tx and rx sps. Check you config.";
+ return -1;
+ }
+
+ return 0;
+}
+
+static int set_sched_rr(unsigned int prio)
+{
+ struct sched_param param;
+ int rc;
+ memset(&param, 0, sizeof(param));
+ param.sched_priority = prio;
+ LOG(INFO) << "Setting SCHED_RR priority " << param.sched_priority
+ << ". This setting is DEPRECATED, please use 'policy rr " << param.sched_priority
+ << "' under the 'sched' VTY node instead.";
+ rc = sched_setscheduler(getpid(), SCHED_RR, &param);
+ if (rc != 0) {
+ LOG(ERROR) << "Config: Setting SCHED_RR failed";
+ return -1;
+ }
+ return 0;
+}
+
+static void print_simd_info(void)
+{
+#ifdef HAVE_SSE3
+ LOGP(DMAIN, LOGL_INFO, "SSE3 support compiled in");
+#ifdef HAVE___BUILTIN_CPU_SUPPORTS
+ if (__builtin_cpu_supports("sse3"))
+ LOGPC(DMAIN, LOGL_INFO, " and supported by CPU\n");
+ else
+ LOGPC(DMAIN, LOGL_INFO, ", but not supported by CPU\n");
+#else
+ LOGPC(DMAIN, LOGL_INFO, ", but runtime SIMD detection disabled\n");
+#endif
+#endif
+
+#ifdef HAVE_SSE4_1
+ LOGP(DMAIN, LOGL_INFO, "SSE4.1 support compiled in");
+#ifdef HAVE___BUILTIN_CPU_SUPPORTS
+ if (__builtin_cpu_supports("sse4.1"))
+ LOGPC(DMAIN, LOGL_INFO, " and supported by CPU\n");
+ else
+ LOGPC(DMAIN, LOGL_INFO, ", but not supported by CPU\n");
+#else
+ LOGPC(DMAIN, LOGL_INFO, ", but runtime SIMD detection disabled\n");
+#endif
+#endif
+
+#ifndef HAVE_ATOMIC_OPS
+#pragma message ("Built without atomic operation support. Using Mutex, it may affect performance!")
+ LOG(NOTICE) << "Built without atomic operation support. Using Mutex, it may affect performance!";
+#endif
+}
+
+static void print_config(struct trx_ctx *trx)
+{
+ unsigned int i;
+ std::ostringstream ost("");
+
+ ost << "Config Settings" << std::endl;
+ ost << " Log Level............... " << (unsigned int) osmo_stderr_target->loglevel << std::endl;
+ ost << " Device args............. " << charp2str(trx->cfg.dev_args) << std::endl;
+ ost << " TRX Base Port........... " << trx->cfg.base_port << std::endl;
+ ost << " TRX Address............. " << charp2str(trx->cfg.bind_addr) << std::endl;
+ ost << " GSM BTS Address......... " << charp2str(trx->cfg.remote_addr) << std::endl;
+ ost << " Channels................ " << trx->cfg.num_chans << std::endl;
+ ost << " Tx Samples-per-Symbol... " << trx->cfg.tx_sps << std::endl;
+ ost << " Rx Samples-per-Symbol... " << trx->cfg.rx_sps << std::endl;
+ ost << " EDGE support............ " << trx->cfg.egprs << std::endl;
+ ost << " Extended RACH support... " << trx->cfg.ext_rach << std::endl;
+ ost << " Reference............... " << trx->cfg.clock_ref << std::endl;
+ ost << " Filler Burst Type....... " << get_value_string(filler_names, trx->cfg.filler) << std::endl;
+ ost << " Filler Burst TSC........ " << trx->cfg.rtsc << std::endl;
+ ost << " Filler Burst RACH Delay. " << trx->cfg.rach_delay << std::endl;
+ ost << " Multi-Carrier........... " << trx->cfg.multi_arfcn << std::endl;
+ ost << " LO freq. offset......... " << trx->cfg.offset << std::endl;
+ if (trx->cfg.freq_offset_khz != 0)
+ ost << " Tune freq. offset....... " << trx->cfg.freq_offset_khz << std::endl;
+ ost << " RSSI to dBm offset...... " << trx->cfg.rssi_offset << (trx->cfg.force_rssi_offset ? "" : " (relative)") << std::endl;
+ ost << " Swap channels........... " << trx->cfg.swap_channels << std::endl;
+ ost << " Tx Antennas.............";
+ for (i = 0; i < trx->cfg.num_chans; i++) {
+ std::string p = charp2str(trx->cfg.chans[i].tx_path);
+ ost << " '" << ((p != "") ? p : "<default>") << "'";
+ }
+ ost << std::endl;
+ ost << " Rx Antennas.............";
+ for (i = 0; i < trx->cfg.num_chans; i++) {
+ std::string p = charp2str(trx->cfg.chans[i].rx_path);
+ ost << " '" << ((p != "") ? p : "<default>") << "'";
+ }
+ ost << std::endl;
+
+ LOG(INFO) << ost << std::endl;
+}
+
+static void trx_stop()
+{
+ LOG(NOTICE) << "Shutting down transceiver..." << std::endl;
+
+ delete transceiver;
+ delete radio;
+ delete usrp;
+}
+
+static int trx_start(struct trx_ctx *trx)
+{
+ int type, chans;
+ unsigned int i;
+ std::vector<std::string> rx_paths, tx_paths;
+ RadioDevice::InterfaceType iface = RadioDevice::NORMAL;
+
+ /* Create the low level device object */
+ if (trx->cfg.multi_arfcn)
+ iface = RadioDevice::MULTI_ARFCN;
+
+ /* Generate vector of rx/tx_path: */
+ for (i = 0; i < trx->cfg.num_chans; i++) {
+ rx_paths.push_back(charp2str(trx->cfg.chans[i].rx_path));
+ tx_paths.push_back(charp2str(trx->cfg.chans[i].tx_path));
+ }
+
+ usrp = RadioDevice::make(trx->cfg.tx_sps, trx->cfg.rx_sps, iface,
+ trx->cfg.num_chans, trx->cfg.offset,
+ tx_paths, rx_paths);
+ type = usrp->open(charp2str(trx->cfg.dev_args), trx->cfg.clock_ref, trx->cfg.swap_channels);
+ if (type < 0) {
+ LOG(ALERT) << "Failed to create radio device" << std::endl;
+ goto shutdown;
+ }
+
+ /* Setup the appropriate device interface */
+ radio = makeRadioInterface(trx, usrp, type);
+ if (!radio)
+ goto shutdown;
+
+ /* Create the transceiver core */
+ if (makeTransceiver(trx, radio) < 0)
+ goto shutdown;
+
+ chans = transceiver->numChans();
+ LOG(NOTICE) << "-- Transceiver active with "
+ << chans << " channel(s)" << std::endl;
+
+ return 0;
+
+shutdown:
+ trx_stop();
+ return -1;
+}
+
+int main(int argc, char *argv[])
+{
+ int rc;
+
+ tall_trx_ctx = talloc_named_const(NULL, 0, "OsmoTRX");
+ msgb_talloc_ctx_init(tall_trx_ctx, 0);
+ g_vty_info.tall_ctx = tall_trx_ctx;
+
+ setup_signal_handlers();
+
+ g_trx_ctx = vty_trx_ctx_alloc(tall_trx_ctx);
+
+ convolve_init();
+ convert_init();
+
+ osmo_init_logging2(tall_trx_ctx, &log_info);
+ log_enable_multithread();
+ osmo_stats_init(tall_trx_ctx);
+ vty_init(&g_vty_info);
+ logging_vty_add_cmds();
+ ctrl_vty_init(tall_trx_ctx);
+ osmo_cpu_sched_vty_init(tall_trx_ctx);
+ trx_vty_init(g_trx_ctx);
+
+ osmo_talloc_vty_add_cmds();
+ osmo_stats_vty_add_cmds();
+
+ handle_options(argc, argv, g_trx_ctx);
+
+ rate_ctr_init(tall_trx_ctx);
+
+ rc = vty_read_config_file(config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to open config file: '%s'\n", config_file);
+ exit(2);
+ }
+
+ rc = telnet_init_dynif(tall_trx_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_TRX);
+ if (rc < 0)
+ exit(1);
+
+ g_ctrlh = ctrl_interface_setup_dynip(NULL, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_TRX, NULL);
+ if (!g_ctrlh) {
+ LOG(ERROR) << "Failed to create CTRL interface.\n";
+ exit(1);
+ }
+
+ /* Backward compatibility: Hack to have 1 channel allocated by default.
+ * Can be Dropped once we * get rid of "-c" cmdline param */
+ if (g_trx_ctx->cfg.num_chans == 0) {
+ g_trx_ctx->cfg.num_chans = 1;
+ g_trx_ctx->cfg.chans[0].trx = g_trx_ctx;
+ g_trx_ctx->cfg.chans[0].idx = 0;
+ LOG(ERROR) << "No explicit channel config found. Make sure you" \
+ " configure channels in VTY config. Using 1 channel as default," \
+ " but expect your config to break in the future.";
+ }
+
+ print_simd_info();
+ print_config(g_trx_ctx);
+
+ if (trx_validate_config(g_trx_ctx) < 0) {
+ LOG(ERROR) << "Config failure - exiting";
+ return EXIT_FAILURE;
+ }
+
+ if (g_trx_ctx->cfg.sched_rr) {
+ if (set_sched_rr(g_trx_ctx->cfg.sched_rr) < 0)
+ return EXIT_FAILURE;
+ }
+
+ osmo_signal_register_handler(SS_MAIN, transc_sig_cb, NULL);
+ trx_rate_ctr_init(tall_trx_ctx, g_trx_ctx);
+
+ srandom(time(NULL));
+
+ if(trx_start(g_trx_ctx) < 0)
+ return EXIT_FAILURE;
+
+ while (!gshutdown)
+ osmo_select_main(0);
+
+ trx_stop();
+
+ osmo_fd_unregister(&signal_ofd);
+ osmo_fd_close(&signal_ofd);
+ osmo_signal_unregister_handler(SS_MAIN, transc_sig_cb, NULL);
+ return 0;
+}
diff --git a/Transceiver52M/osmo-trx.cpp b/Transceiver52M/osmo-trx.cpp
index b6f378b..4872f62 100644
--- a/Transceiver52M/osmo-trx.cpp
+++ b/Transceiver52M/osmo-trx.cpp
@@ -1,8 +1,6 @@
/*
* Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>
*
- * SPDX-License-Identifier: LGPL-2.1+
- *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
@@ -12,27 +10,23 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
-#include "Transceiver.h"
+#include "Transceiver2.h"
#include "radioDevice.h"
-#include "Utils.h"
#include <time.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
-#include <getopt.h>
-#include <sched.h>
-#include <vector>
-#include <string>
-#include <sstream>
-#include <iostream>
-#include <sys/signalfd.h>
#include <GSMCommon.h>
#include <Logger.h>
@@ -61,21 +55,101 @@ extern "C" {
#include "trx_rate_ctr.h"
}
-#define DEFAULT_CONFIG_FILE "osmo-trx.cfg"
-#define charp2str(a) ((a) ? std::string(a) : std::string(""))
+//#include <mempool.cpp>
+//atomicstackpool<NUM_ALLOCS,ALLOC_SZ> ALLOCPRIO a0;
+
+/* Samples-per-symbol for downlink path
+ * 4 - Uses precision modulator (more computation, less distortion)
+ * 1 - Uses minimized modulator (less computation, more distortion)
+ *
+ * Other values are invalid. Receive path (uplink) is always
+ * downsampled to 1 sps. Default to 4 sps for all cases except for
+ * ARM and non-SIMD enabled architectures.
+ */
+#if defined(HAVE_NEON) || !defined(HAVE_SSE3)
+#define DEFAULT_SPS 1
+#else
+#define DEFAULT_SPS 4
+#endif
+
+/* Default configuration parameters
+ * Note that these values are only used if the particular key does not
+ * exist in the configuration database. IP port and address values will
+ * typically be overwritten by the OpenBTS.db values. Other values will
+ * not be in the database by default.
+ */
+#define DEFAULT_TRX_PORT 5700
+#define DEFAULT_TRX_IP "127.0.0.1"
+#define DEFAULT_EXTREF false
+#define DEFAULT_DIVERSITY false
+#define DEFAULT_CHANS 1
+
+struct trx_config {
+ std::string log_level;
+ std::string addr;
+ std::string dev_args;
+ unsigned port;
+ unsigned sps;
+ unsigned chans;
+ bool extref;
+ bool filler;
+ bool diversity;
+ bool ms;
+ double offset;
+};
+
+
+
+extern "C" volatile bool gshutdown = false;
+
+
+
+/* Setup configuration values
+ * Don't query the existence of the Log.Level because it's a
+ * mandatory value. That is, if it doesn't exist, the configuration
+ * table will crash or will have already crashed. Everything else we
+ * can survive without and use default values if the database entries
+ * are empty.
+ */
+bool trx_setup_config(struct trx_config *config)
+{
+ std::string refstr, fillstr, divstr, msstr;
-static char* config_file = (char*)DEFAULT_CONFIG_FILE;
+ config->log_level = "foo";
+ config->port = DEFAULT_TRX_PORT;
+ config->addr = DEFAULT_TRX_IP;
+ //config->extref = DEFAULT_EXTREF;
+ config->diversity = DEFAULT_DIVERSITY;
+ config->sps = 4;//DEFAULT_SPS;
+ config->chans = DEFAULT_CHANS;
-struct osmo_fd signal_ofd;
-volatile bool gshutdown = false;
+ /* Diversity only supported on 2 channels */
+ if (config->diversity)
+ config->chans = 2;
-static void *tall_trx_ctx;
-static struct trx_ctx *g_trx_ctx;
-static struct ctrl_handle *g_ctrlh;
+ refstr = config->extref ? "Enabled" : "Disabled";
+ fillstr = config->filler ? "Enabled" : "Disabled";
+ divstr = config->diversity ? "Enabled" : "Disabled";
+ msstr = config->ms ? "Enabled" : "Disabled";
-static RadioDevice *usrp;
-static RadioInterface *radio;
+ std::ostringstream ost("");
+ ost << "Config Settings" << std::endl;
+ ost << " Log Level............... " << config->log_level << std::endl;
+ ost << " Device args............. " << config->dev_args << std::endl;
+ ost << " TRX Base Port........... " << config->port << std::endl;
+ ost << " TRX Address............. " << config->addr << std::endl;
+ ost << " Channels................ " << config->chans << std::endl;
+ ost << " Samples-per-Symbol...... " << config->sps << std::endl;
+ ost << " External Reference...... " << refstr << std::endl;
+ ost << " C0 Filler Table......... " << fillstr << std::endl;
+ ost << " Diversity............... " << divstr << std::endl;
+ ost << " MS Mode................. " << msstr << std::endl;
+ ost << " Tuning offset........... " << config->offset << std::endl;
+ std::cout << ost.str() << std::endl;
+
+ return true;
+}
/* Create radio interface
* The interface consists of sample rate changes, frequency shifts,
@@ -84,24 +158,26 @@ static RadioInterface *radio;
* The radio interface connects the main transceiver with the device
* object, which may be operating some other rate.
*/
-RadioInterface *makeRadioInterface(struct trx_ctx *trx,
+RadioInterface *makeRadioInterface(struct trx_config *config,
RadioDevice *usrp, int type)
{
RadioInterface *radio = NULL;
+ size_t div = 1;
+ int offset = 3;
+
+ if (config->ms) {
+ if (type != RadioDevice::NORMAL) {
+ LOG(ALERT) << "Unsupported configuration";
+ return NULL;
+ }
+
+ offset *= -1;
+ }
switch (type) {
case RadioDevice::NORMAL:
- radio = new RadioInterface(usrp, trx->cfg.tx_sps,
- trx->cfg.rx_sps, trx->cfg.num_chans);
- break;
- case RadioDevice::RESAMP_64M:
- case RadioDevice::RESAMP_100M:
- radio = new RadioInterfaceResamp(usrp, trx->cfg.tx_sps,
- trx->cfg.rx_sps);
- break;
- case RadioDevice::MULTI_ARFCN:
- radio = new RadioInterfaceMulti(usrp, trx->cfg.tx_sps,
- trx->cfg.rx_sps, trx->cfg.num_chans);
+ radio = new RadioInterface(usrp, config->sps, config->sps,
+ config->chans, div, offset);
break;
default:
LOG(ALERT) << "Unsupported radio interface configuration";
@@ -116,590 +192,257 @@ RadioInterface *makeRadioInterface(struct trx_ctx *trx,
return radio;
}
-/* Callback function to be called every time we receive a signal from TRANSC */
-static int transc_sig_cb(unsigned int subsys, unsigned int signal,
- void *handler_data, void *signal_data)
-{
- switch (signal) {
- case S_MAIN_STOP_REQUIRED:
- gshutdown = true;
- break;
- default:
- break;
- }
- return 0;
-}
-
/* Create transceiver core
* The multi-threaded modem core operates at multiples of the GSM rate of
* 270.8333 ksps and consists of GSM specific modulation, demodulation,
* and decoding schemes. Also included are the socket interfaces for
* connecting to the upper layer stack.
*/
-int makeTransceiver(struct trx_ctx *trx, RadioInterface *radio)
+Transceiver2 *makeTransceiver(struct trx_config *config, RadioInterface *radio)
{
+ Transceiver2 *trx;
VectorFIFO *fifo;
- transceiver = new Transceiver(&trx->cfg, GSM::Time(3,0), radio);
- if (!transceiver->init()) {
+ trx = new Transceiver2(config->port, config->addr.c_str(), config->sps,
+ config->chans, GSM::Time(3,0), radio);
+ if (!trx->init(config->filler)) {
LOG(ALERT) << "Failed to initialize transceiver";
- return -1;
+ delete trx;
+ return NULL;
}
- for (size_t i = 0; i < trx->cfg.num_chans; i++) {
+ for (size_t i = 0; i < config->chans; i++) {
fifo = radio->receiveFIFO(i);
- if (fifo && transceiver->receiveFIFO(fifo, i))
+ if (fifo && trx->receiveFIFO(fifo, i))
continue;
LOG(ALERT) << "Could not attach FIFO to channel " << i;
- return -1;
- }
- return 0;
-}
-
-static void sig_handler(int signo)
-{
-
- if (gshutdown)
- /* We are in the middle of shutdown process, avoid any kind of extra
- action like printing */
- return;
-
- fprintf(stderr, "signal %d received\n", signo);
- switch (signo) {
- case SIGINT:
- case SIGTERM:
- fprintf(stderr, "shutting down\n");
- gshutdown = true;
- 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(tall_trx_ctx, stderr);
- talloc_report_full(tall_trx_ctx, stderr);
- signal(SIGABRT, SIG_DFL);
- raise(SIGABRT);
- break;
- case SIGUSR1:
- talloc_report(tall_trx_ctx, stderr);
- talloc_report_full(tall_trx_ctx, stderr);
- break;
- case SIGUSR2:
- talloc_report_full(tall_trx_ctx, stderr);
- break;
- case SIGHUP:
- log_targets_reopen();
- default:
- break;
+ delete trx;
+ return NULL;
}
+ return trx;
}
-static int signalfd_callback(struct osmo_fd *ofd, unsigned int what)
+static void sig_handler(int signo)
{
- struct signalfd_siginfo fdsi;
- ssize_t s;
-
- s = read(ofd->fd, &fdsi, sizeof(struct signalfd_siginfo));
- if (s < 0) {
- LOG(FATAL) << "Failed to read from signalfd ("<< ofd->fd << "): " << errno;
- gshutdown = true;
- return 0;
- }
- sig_handler(fdsi.ssi_signo);
- return 0;
+ fprintf(stdout, "Received shutdown signal");
+ gshutdown = true;
}
static void setup_signal_handlers()
{
- sigset_t set;
- int sfd;
-
- signal(SIGABRT, &sig_handler);
- osmo_init_ignore_signals();
-
- /* Other threads created by this thread (main) will inherit a copy of the
- signal mask. */
- sigemptyset(&set);
- sigaddset(&set, SIGINT);
- sigaddset(&set, SIGTERM);
- sigaddset(&set, SIGUSR1);
- sigaddset(&set, SIGUSR2);
- sigaddset(&set, SIGHUP);
- if (pthread_sigmask(SIG_BLOCK, &set, NULL)) {
- fprintf(stderr, "pthread_sigmask() failed.\n");
- exit(EXIT_FAILURE);
- }
-
- if ((sfd = signalfd(-1, &set, 0)) == -1) {
- fprintf(stderr, "signalfd() failed (%d).\n", errno);
+ if (signal(SIGINT, sig_handler) == SIG_ERR) {
+ fprintf(stderr, "Failed to install SIGINT signal handler\n");
exit(EXIT_FAILURE);
}
-
- osmo_fd_setup(&signal_ofd, sfd, OSMO_FD_READ, signalfd_callback, NULL, 0);
- if (osmo_fd_register(&signal_ofd) < 0) {
- fprintf(stderr, "osmo_fd_register() failed.\n");
- exit(EXIT_FAILURE);
+ if (signal(SIGTERM, sig_handler) == SIG_ERR) {
+ fprintf(stderr, "Couldn't install SIGTERM signal handler\n");
+ exit( EXIT_FAILURE);
}
}
static void print_help()
{
- printf( "Some useful options:\n"
- " -h, --help This text\n"
- " -C, --config Filename The config file to use\n"
- " -V, --version Print the version of OsmoTRX\n"
- "\nVTY reference generation:\n"
- " --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n"
- " --vty-ref-xml Generate the VTY reference XML output and exit.\n"
- );
+ fprintf(stdout, "Options:\n"
+ " -h This text\n"
+ " -a UHD device args\n"
+ " -l Logging level (%s)\n"
+ " -i IP address of GSM core\n"
+ " -p Base port number\n"
+ " -d Enable dual channel diversity receiver\n"
+ " -x Enable external 10 MHz reference\n"
+ " -s Samples-per-symbol (1 or 4)\n"
+ " -c Number of ARFCN channels (default=1)\n"
+ " -f Enable C0 filler table\n"
+ " -m Enable MS mode\n"
+ " -o Set baseband frequency offset (default=auto)\n",
+ "EMERG, ALERT, CRT, ERR, WARNING, NOTICE, INFO, DEBUG");
}
-static void print_deprecated(char opt)
+static void handle_options(int argc, char **argv, struct trx_config *config)
{
- LOG(WARNING) << "Cmd line option '" << opt << "' is deprecated and will be soon removed."
- << " Please use VTY cfg option instead."
- << " All cmd line options are already being overridden by VTY options if set.";
-}
+ int option;
-static void handle_long_options(const char *prog_name, const int long_option)
-{
- static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT;
-
- switch (long_option) {
- case 1:
- vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg);
- if (vty_ref_mode < 0) {
- fprintf(stderr, "%s: Unknown VTY reference generation "
- "mode '%s'\n", prog_name, optarg);
- exit(2);
- }
- break;
- case 2:
- fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
- get_value_string(vty_ref_gen_mode_names, vty_ref_mode),
- get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
- vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
- exit(0);
- default:
- fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
- exit(2);
- }
-}
+ optind=1;
-static void handle_options(int argc, char **argv, struct trx_ctx* trx)
-{
- int option;
- unsigned int i;
- std::vector<std::string> rx_paths, tx_paths;
- bool rx_paths_set = false, tx_paths_set = false;
- static int long_option = 0;
- static struct option long_options[] = {
- {"help", 0, 0, 'h'},
- {"config", 1, 0, 'C'},
- {"version", 0, 0, 'V'},
- {"vty-ref-mode", 1, &long_option, 1},
- {"vty-ref-xml", 0, &long_option, 2},
- {NULL, 0, 0, 0}
- };
-
- while ((option = getopt_long(argc, argv, "ha:l:i:j:p:c:dmxgfo:s:b:r:A:R:Set:y:z:C:V", long_options,
- NULL)) != -1) {
+ config->port = 0;
+ config->sps = 0;
+ config->chans = 0;
+ config->extref = true;
+ config->filler = false;
+ config->diversity = false;
+ config->ms = true;
+ config->offset = 0.0;
+
+ while ((option = getopt(argc, argv, "xo")) != -1) {
switch (option) {
- case 'h':
- print_help();
- exit(0);
- break;
- case 0:
- handle_long_options(argv[0], long_option);
- break;
- case 'a':
- print_deprecated(option);
- osmo_talloc_replace_string(trx, &trx->cfg.dev_args, optarg);
- break;
- case 'l':
- print_deprecated(option);
- log_set_log_level(osmo_stderr_target, atoi(optarg));
- break;
- case 'i':
- print_deprecated(option);
- osmo_talloc_replace_string(trx, &trx->cfg.remote_addr, optarg);
- break;
- case 'j':
- print_deprecated(option);
- osmo_talloc_replace_string(trx, &trx->cfg.bind_addr, optarg);
- break;
- case 'p':
- print_deprecated(option);
- trx->cfg.base_port = atoi(optarg);
- break;
- case 'c':
- print_deprecated(option);
- trx->cfg.num_chans = atoi(optarg);
- break;
- case 'm':
- print_deprecated(option);
- trx->cfg.multi_arfcn = true;
- break;
case 'x':
- print_deprecated(option);
- trx->cfg.clock_ref = REF_EXTERNAL;
- break;
- case 'g':
- print_deprecated(option);
- trx->cfg.clock_ref = REF_GPS;
- break;
- case 'f':
- print_deprecated(option);
- trx->cfg.filler = FILLER_DUMMY;
+ config->extref = true;
break;
case 'o':
- print_deprecated(option);
- trx->cfg.offset = atof(optarg);
- break;
- case 's':
- print_deprecated(option);
- trx->cfg.tx_sps = atoi(optarg);
- break;
- case 'b':
- print_deprecated(option);
- trx->cfg.rx_sps = atoi(optarg);
- break;
- case 'r':
- print_deprecated(option);
- trx->cfg.rtsc = atoi(optarg);
- if (!trx->cfg.egprs) /* Don't override egprs which sets different filler */
- trx->cfg.filler = FILLER_NORM_RAND;
- break;
- case 'A':
- print_deprecated(option);
- trx->cfg.rach_delay = atoi(optarg);
- trx->cfg.filler = FILLER_ACCESS_RAND;
- break;
- case 'R':
- print_deprecated(option);
- trx->cfg.rssi_offset = atof(optarg);
- trx->cfg.force_rssi_offset = true;
- break;
- case 'S':
- print_deprecated(option);
- trx->cfg.swap_channels = true;
- break;
- case 'e':
- print_deprecated(option);
- trx->cfg.egprs = true;
- break;
- case 't':
- print_deprecated(option);
- trx->cfg.sched_rr = atoi(optarg);
- break;
- case 'y':
- print_deprecated(option);
- tx_paths = comma_delimited_to_vector(optarg);
- tx_paths_set = true;
- break;
- case 'z':
- print_deprecated(option);
- rx_paths = comma_delimited_to_vector(optarg);
- rx_paths_set = true;
- break;
- case 'C':
- config_file = optarg;
- break;
- case 'V':
- print_version(1);
- exit(0);
+ config->offset = atof(optarg);
break;
default:
- goto bad_config;
+ print_help();
+ exit(0);
}
}
-
- if (argc > optind) {
- LOG(ERROR) << "Unsupported positional arguments on command line";
- goto bad_config;
- }
-
- /* Cmd line option specific validation & setup */
-
- if (trx->cfg.num_chans > TRX_CHAN_MAX) {
- LOG(ERROR) << "Too many channels requested, maximum is " << TRX_CHAN_MAX;
- goto bad_config;
- }
- if ((tx_paths_set && tx_paths.size() != trx->cfg.num_chans) ||
- (rx_paths_set && rx_paths.size() != trx->cfg.num_chans)) {
- LOG(ERROR) << "Num of channels and num of Rx/Tx Antennas doesn't match";
- goto bad_config;
- }
- for (i = 0; i < trx->cfg.num_chans; i++) {
- trx->cfg.chans[i].trx = trx;
- trx->cfg.chans[i].idx = i;
- if (tx_paths_set)
- osmo_talloc_replace_string(trx, &trx->cfg.chans[i].tx_path, tx_paths[i].c_str());
- if (rx_paths_set)
- osmo_talloc_replace_string(trx, &trx->cfg.chans[i].rx_path, rx_paths[i].c_str());
- }
-
- return;
-
-bad_config:
- print_help();
- exit(0);
}
-int trx_validate_config(struct trx_ctx *trx)
+const char* commands[] {
+"CMD POWEROFF",
+//"CMD RXTUNE 1782000",
+"CMD RXTUNE 931400",
+//"CMD TXTUNE 1877000",
+"CMD TXTUNE 931900",
+"CMD SETTSC 7",
+"CMD POWERON",
+"CMD SETRXGAIN 30",
+"CMD SETPOWER 0",
+//"CMD SETSLOT 6 7",
+//"CMD SETSLOT 7 13",
+//"CMD NOHANDOVER 1 0",
+"CMD SYNC",
+"CMD GETBSIC",
+};
+
+const char* commands2[] {
+"CMD GETBSIC",
+};
+
+RadioDevice *usrp;
+RadioInterface *radio = NULL;
+Transceiver2 *trx = NULL;
+
+ void* tall_trx_ctx;
+ struct trx_ctx* g_trx_ctx;
+
+int trx_main(int argc, char *argv[])
{
- if (trx->cfg.multi_arfcn && trx->cfg.num_chans > TRX_MCHAN_MAX) {
- LOG(ERROR) << "Unsupported number of channels";
- return -1;
- }
+ int type, chans;
- /* Force 4 SPS for EDGE or multi-ARFCN configurations */
- if ((trx->cfg.egprs || trx->cfg.multi_arfcn) &&
- (trx->cfg.tx_sps!=4 || trx->cfg.rx_sps!=4)) {
- LOG(ERROR) << "EDGE and Multi-Carrier options require 4 tx and rx sps. Check you config.";
- return -1;
- }
+ struct trx_config config;
- return 0;
-}
+ std::cerr << "starting.." << std::endl;
-static int set_sched_rr(unsigned int prio)
-{
- struct sched_param param;
- int rc;
- memset(&param, 0, sizeof(param));
- param.sched_priority = prio;
- LOG(INFO) << "Setting SCHED_RR priority " << param.sched_priority
- << ". This setting is DEPRECATED, please use 'policy rr " << param.sched_priority
- << "' under the 'sched' VTY node instead.";
- rc = sched_setscheduler(getpid(), SCHED_RR, &param);
- if (rc != 0) {
- LOG(ERROR) << "Config: Setting SCHED_RR failed";
- return -1;
- }
- return 0;
-}
+ convolve_init();
+ convert_init();
-static void print_simd_info(void)
-{
-#ifdef HAVE_SSE3
- LOGP(DMAIN, LOGL_INFO, "SSE3 support compiled in");
-#ifdef HAVE___BUILTIN_CPU_SUPPORTS
- if (__builtin_cpu_supports("sse3"))
- LOGPC(DMAIN, LOGL_INFO, " and supported by CPU\n");
- else
- LOGPC(DMAIN, LOGL_INFO, ", but not supported by CPU\n");
-#else
- LOGPC(DMAIN, LOGL_INFO, ", but runtime SIMD detection disabled\n");
-#endif
-#endif
-#ifdef HAVE_SSE4_1
- LOGP(DMAIN, LOGL_INFO, "SSE4.1 support compiled in");
-#ifdef HAVE___BUILTIN_CPU_SUPPORTS
- if (__builtin_cpu_supports("sse4.1"))
- LOGPC(DMAIN, LOGL_INFO, " and supported by CPU\n");
- else
- LOGPC(DMAIN, LOGL_INFO, ", but not supported by CPU\n");
-#else
- LOGPC(DMAIN, LOGL_INFO, ", but runtime SIMD detection disabled\n");
-#endif
-#endif
+ tall_trx_ctx = talloc_named_const(NULL, 0, "OsmoTRX");
+ msgb_talloc_ctx_init(tall_trx_ctx, 0);
+ g_vty_info.tall_ctx = tall_trx_ctx;
-#ifndef HAVE_ATOMIC_OPS
-#pragma message ("Built without atomic operation support. Using Mutex, it may affect performance!")
- LOG(NOTICE) << "Built without atomic operation support. Using Mutex, it may affect performance!";
-#endif
-}
+ //setup_signal_handlers();
-static void print_config(struct trx_ctx *trx)
-{
- unsigned int i;
- std::ostringstream ost("");
+ g_trx_ctx = vty_trx_ctx_alloc(tall_trx_ctx);
- ost << "Config Settings" << std::endl;
- ost << " Log Level............... " << (unsigned int) osmo_stderr_target->loglevel << std::endl;
- ost << " Device args............. " << charp2str(trx->cfg.dev_args) << std::endl;
- ost << " TRX Base Port........... " << trx->cfg.base_port << std::endl;
- ost << " TRX Address............. " << charp2str(trx->cfg.bind_addr) << std::endl;
- ost << " GSM BTS Address......... " << charp2str(trx->cfg.remote_addr) << std::endl;
- ost << " Channels................ " << trx->cfg.num_chans << std::endl;
- ost << " Tx Samples-per-Symbol... " << trx->cfg.tx_sps << std::endl;
- ost << " Rx Samples-per-Symbol... " << trx->cfg.rx_sps << std::endl;
- ost << " EDGE support............ " << trx->cfg.egprs << std::endl;
- ost << " Extended RACH support... " << trx->cfg.ext_rach << std::endl;
- ost << " Reference............... " << trx->cfg.clock_ref << std::endl;
- ost << " Filler Burst Type....... " << get_value_string(filler_names, trx->cfg.filler) << std::endl;
- ost << " Filler Burst TSC........ " << trx->cfg.rtsc << std::endl;
- ost << " Filler Burst RACH Delay. " << trx->cfg.rach_delay << std::endl;
- ost << " Multi-Carrier........... " << trx->cfg.multi_arfcn << std::endl;
- ost << " LO freq. offset......... " << trx->cfg.offset << std::endl;
- if (trx->cfg.freq_offset_khz != 0)
- ost << " Tune freq. offset....... " << trx->cfg.freq_offset_khz << std::endl;
- ost << " RSSI to dBm offset...... " << trx->cfg.rssi_offset << (trx->cfg.force_rssi_offset ? "" : " (relative)") << std::endl;
- ost << " Swap channels........... " << trx->cfg.swap_channels << std::endl;
- ost << " Tx Antennas.............";
- for (i = 0; i < trx->cfg.num_chans; i++) {
- std::string p = charp2str(trx->cfg.chans[i].tx_path);
- ost << " '" << ((p != "") ? p : "<default>") << "'";
- }
- ost << std::endl;
- ost << " Rx Antennas.............";
- for (i = 0; i < trx->cfg.num_chans; i++) {
- std::string p = charp2str(trx->cfg.chans[i].rx_path);
- ost << " '" << ((p != "") ? p : "<default>") << "'";
- }
- ost << std::endl;
+ osmo_init_logging2(tall_trx_ctx, &log_info);
+ log_enable_multithread();
+ //osmo_stats_init(tall_trx_ctx);
+ vty_init(&g_vty_info);
+ logging_vty_add_cmds();
+ //ctrl_vty_init(tall_trx_ctx);
+ osmo_cpu_sched_vty_init(tall_trx_ctx);
+ trx_vty_init(g_trx_ctx);
- LOG(INFO) << ost << std::endl;
-}
+ osmo_talloc_vty_add_cmds();
+ //osmo_stats_vty_add_cmds();
-static void trx_stop()
-{
- LOG(NOTICE) << "Shutting down transceiver..." << std::endl;
- delete transceiver;
- delete radio;
- delete usrp;
-}
+ sched_param sch_params;
+ sch_params.sched_priority = 19;
+ pthread_setschedparam(pthread_self(), SCHED_RR, &sch_params);
-static int trx_start(struct trx_ctx *trx)
-{
- int type, chans;
- unsigned int i;
- std::vector<std::string> rx_paths, tx_paths;
- RadioDevice::InterfaceType iface = RadioDevice::NORMAL;
+ handle_options(argc, argv, &config);
- /* Create the low level device object */
- if (trx->cfg.multi_arfcn)
- iface = RadioDevice::MULTI_ARFCN;
+ //setup_signal_handlers();
- /* Generate vector of rx/tx_path: */
- for (i = 0; i < trx->cfg.num_chans; i++) {
- rx_paths.push_back(charp2str(trx->cfg.chans[i].rx_path));
- tx_paths.push_back(charp2str(trx->cfg.chans[i].tx_path));
+ /* Check database sanity */
+ if (!trx_setup_config(&config)) {
+ std::cerr << "Config: Database failure - exiting" << std::endl;
+ return EXIT_FAILURE;
}
- usrp = RadioDevice::make(trx->cfg.tx_sps, trx->cfg.rx_sps, iface,
- trx->cfg.num_chans, trx->cfg.offset,
- tx_paths, rx_paths);
- type = usrp->open(charp2str(trx->cfg.dev_args), trx->cfg.clock_ref, trx->cfg.swap_channels);
+ //gLogInit("transceiver", config.log_level.c_str(), LOG_LOCAL7);
+
+ srandom(time(NULL));
+
+ /* Create the low level device object */
+ usrp = RadioDevice::make(config.sps, config.sps, RadioDevice::NORMAL, config.chans,
+ config.offset);
+ type = usrp->open(config.dev_args, config.extref, false);
if (type < 0) {
LOG(ALERT) << "Failed to create radio device" << std::endl;
goto shutdown;
}
/* Setup the appropriate device interface */
- radio = makeRadioInterface(trx, usrp, type);
+ radio = makeRadioInterface(&config, usrp, type);
if (!radio)
goto shutdown;
/* Create the transceiver core */
- if (makeTransceiver(trx, radio) < 0)
+ trx = makeTransceiver(&config, radio);
+ if (!trx)
goto shutdown;
- chans = transceiver->numChans();
- LOG(NOTICE) << "-- Transceiver active with "
- << chans << " channel(s)" << std::endl;
-
- return 0;
-
-shutdown:
- trx_stop();
- return -1;
-}
-
-int main(int argc, char *argv[])
-{
- int rc;
-
- tall_trx_ctx = talloc_named_const(NULL, 0, "OsmoTRX");
- msgb_talloc_ctx_init(tall_trx_ctx, 0);
- g_vty_info.tall_ctx = tall_trx_ctx;
-
- setup_signal_handlers();
-
- g_trx_ctx = vty_trx_ctx_alloc(tall_trx_ctx);
-
- convolve_init();
- convert_init();
+ trx->start();
- osmo_init_logging2(tall_trx_ctx, &log_info);
- log_enable_multithread();
- osmo_stats_init(tall_trx_ctx);
- vty_init(&g_vty_info);
- logging_vty_add_cmds();
- ctrl_vty_init(tall_trx_ctx);
- osmo_cpu_sched_vty_init(tall_trx_ctx);
- trx_vty_init(g_trx_ctx);
-
- osmo_talloc_vty_add_cmds();
- osmo_stats_vty_add_cmds();
-
- handle_options(argc, argv, g_trx_ctx);
-
- rate_ctr_init(tall_trx_ctx);
-
- rc = vty_read_config_file(config_file, NULL);
- if (rc < 0) {
- fprintf(stderr, "Failed to open config file: '%s'\n", config_file);
- exit(2);
- }
+ chans = trx->numChans();
+ std::cout << "-- Transceiver active with "
+ << chans << " channel(s)" << std::endl;
- rc = telnet_init_dynif(tall_trx_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_TRX);
- if (rc < 0)
- exit(1);
- g_ctrlh = ctrl_interface_setup_dynip(NULL, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_TRX, NULL);
- if (!g_ctrlh) {
- LOG(ERROR) << "Failed to create CTRL interface.\n";
- exit(1);
+#if 0
+ for(auto i: commands) {
+ int MAX_PACKET_LENGTH = 100;
+ char response[MAX_PACKET_LENGTH];
+ char buffer[MAX_PACKET_LENGTH];
+ memset(response, 0, sizeof(response));
+ memset(buffer, 0, sizeof(buffer));
+ sprintf(buffer, "%s", i);
+ trx->commandhandler(buffer, response, 0);
+ std::cerr << i << "\tr: " << response << " ##" << std::endl;
}
- /* Backward compatibility: Hack to have 1 channel allocated by default.
- * Can be Dropped once we * get rid of "-c" cmdline param */
- if (g_trx_ctx->cfg.num_chans == 0) {
- g_trx_ctx->cfg.num_chans = 1;
- g_trx_ctx->cfg.chans[0].trx = g_trx_ctx;
- g_trx_ctx->cfg.chans[0].idx = 0;
- LOG(ERROR) << "No explicit channel config found. Make sure you" \
- " configure channels in VTY config. Using 1 channel as default," \
- " but expect your config to break in the future.";
+ sleep(30);
+
+ for(auto i: commands2) {
+ int MAX_PACKET_LENGTH = 100;
+ char response[MAX_PACKET_LENGTH];
+ char buffer[MAX_PACKET_LENGTH];
+ memset(response, 0, sizeof(response));
+ memset(buffer, 0, sizeof(buffer));
+ sprintf(buffer, "%s", i);
+ trx->commandhandler(buffer, response, 0);
+ std::cerr << i << "\tr: " << response << " ##" << std::endl;
}
+#endif
- print_simd_info();
- print_config(g_trx_ctx);
+// while (!gshutdown)
+// sleep(1);
- if (trx_validate_config(g_trx_ctx) < 0) {
- LOG(ERROR) << "Config failure - exiting";
- return EXIT_FAILURE;
- }
-
- if (g_trx_ctx->cfg.sched_rr) {
- if (set_sched_rr(g_trx_ctx->cfg.sched_rr) < 0)
- return EXIT_FAILURE;
- }
+shutdown:
+// std::cout << "Shutting down transceiver..." << std::endl;
- osmo_signal_register_handler(SS_MAIN, transc_sig_cb, NULL);
- trx_rate_ctr_init(tall_trx_ctx, g_trx_ctx);
+// delete trx;
+// delete radio;
+// delete usrp;
- srandom(time(NULL));
+ return 0;
+}
- if(trx_start(g_trx_ctx) < 0)
- return EXIT_FAILURE;
+extern "C" void init_external_transceiver(int argc, char **argv) {
+ trx_main(argc, argv);
+}
- while (!gshutdown)
- osmo_select_main(0);
+extern "C" void stop_trx() {
+ std::cout << "Shutting down transceiver..." << std::endl;
- trx_stop();
+ delete trx;
+ delete radio;
+ delete usrp;
- osmo_fd_unregister(&signal_ofd);
- osmo_fd_close(&signal_ofd);
- osmo_signal_unregister_handler(SS_MAIN, transc_sig_cb, NULL);
- return 0;
}
diff --git a/Transceiver52M/radioClock.cpp b/Transceiver52M/radioClock.cpp
index 2befd0d..61255c0 100644
--- a/Transceiver52M/radioClock.cpp
+++ b/Transceiver52M/radioClock.cpp
@@ -30,6 +30,41 @@ void RadioClock::set(const GSM::Time& wTime)
updateSignal.signal();
}
+GSM::Time RadioClock::adjust(GSM::Time &wBase, GSM::Time &wOffset)
+{
+ int tn_diff, fn_diff = 0;
+
+ /* Modulo TN adustment */
+ tn_diff = wBase.TN() + wOffset.TN();
+ if (tn_diff < 0) {
+ tn_diff += 8;
+ fn_diff--;
+ } else if (tn_diff >= 8) {
+ tn_diff -= 8;
+ fn_diff++;
+ }
+
+ /* Modulo FN adjustment */
+ fn_diff += wBase.FN() + wOffset.FN();
+ if (fn_diff < 0)
+ fn_diff += GSM::gHyperframe;
+ else if ((unsigned) fn_diff >= GSM::gHyperframe)
+ fn_diff = fn_diff - GSM::gHyperframe;
+
+ return GSM::Time(fn_diff, tn_diff);
+}
+
+void RadioClock::adjust(GSM::Time& wOffset)
+{
+ mLock.lock();
+
+ mClock = adjust(mClock, wOffset);
+ updateSignal.signal();
+
+ mLock.unlock();
+}
+
+
void RadioClock::incTN()
{
ScopedLock lock(mLock);
diff --git a/Transceiver52M/radioClock.h b/Transceiver52M/radioClock.h
index 472f43d..9dd2e43 100644
--- a/Transceiver52M/radioClock.h
+++ b/Transceiver52M/radioClock.h
@@ -28,7 +28,10 @@
class RadioClock {
public:
+ static GSM::Time adjust(GSM::Time &base, GSM::Time &offset);
+
void set(const GSM::Time& wTime);
+ void adjust(GSM::Time &wOffset);
void incTN();
GSM::Time get();
void wait();
diff --git a/Transceiver52M/radioInterface.cpp b/Transceiver52M/radioInterface.cpp
index 875245d..70f098e 100644
--- a/Transceiver52M/radioInterface.cpp
+++ b/Transceiver52M/radioInterface.cpp
@@ -41,7 +41,8 @@ RadioInterface::RadioInterface(RadioDevice *wDevice, size_t tx_sps,
int wReceiveOffset, GSM::Time wStartTime)
: mDevice(wDevice), mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans),
underrun(false), overrun(false), writeTimestamp(0), readTimestamp(0),
- receiveOffset(wReceiveOffset), mOn(false)
+ receiveOffset(wReceiveOffset), shiftOffset(0), shiftUpdate(false),
+ mOn(false)
{
mClock.set(wStartTime);
}
@@ -165,11 +166,21 @@ bool RadioInterface::tuneTx(double freq, size_t chan)
return mDevice->setTxFreq(freq, chan);
}
+void RadioInterface::adjustClock(GSM::Time &offset)
+{
+ mClock.adjust(offset);
+}
+
bool RadioInterface::tuneRx(double freq, size_t chan)
{
return mDevice->setRxFreq(freq, chan);
}
+bool RadioInterface::tuneRxOffset(double offset, size_t chan)
+{
+ return mDevice->setRxOffset(offset, chan);
+}
+
/** synchronization thread loop */
void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface)
{
@@ -256,7 +267,10 @@ int RadioInterface::driveReceiveRadio()
return -1;
GSM::Time rcvClock = mClock.get();
- rcvClock.decTN(receiveOffset);
+ if (receiveOffset < 0)
+ rcvClock.incTN(-receiveOffset);
+ else
+ rcvClock.decTN(receiveOffset);
unsigned tN = rcvClock.TN();
int recvSz = recvBuffer[0]->getAvailSamples();
const int symbolsPerSlot = gSlotLen + 8;
@@ -309,6 +323,12 @@ bool RadioInterface::isUnderrun()
return retVal;
}
+void RadioInterface::applyOffset(int offset)
+{
+ shiftOffset += offset;
+ shiftUpdate = true;
+}
+
VectorFIFO* RadioInterface::receiveFIFO(size_t chan)
{
if (chan >= mReceiveFIFO.size())
@@ -341,7 +361,7 @@ int RadioInterface::pullBuffer()
numRecv = mDevice->readSamples(convertRecvBuffer,
segmentLen,
&overrun,
- readTimestamp,
+ readTimestamp + mSPSRx * shiftOffset,
&local_underrun);
if ((size_t) numRecv != segmentLen) {
@@ -376,11 +396,16 @@ bool RadioInterface::pushBuffer()
segmentLen * 2);
}
+ if (shiftUpdate) {
+ mDevice->updateAlignment(0);
+ shiftUpdate = false;
+ }
+
/* Send the all samples in the send buffer */
numSent = mDevice->writeSamples(convertSendBuffer,
segmentLen,
&local_underrun,
- writeTimestamp);
+ writeTimestamp + mSPSTx * shiftOffset);
osmo_trx_sync_or_and_fetch(&underrun, local_underrun);
writeTimestamp += numSent;
diff --git a/Transceiver52M/radioInterface.h b/Transceiver52M/radioInterface.h
index efe5606..f05a376 100644
--- a/Transceiver52M/radioInterface.h
+++ b/Transceiver52M/radioInterface.h
@@ -56,7 +56,8 @@ protected:
RadioClock mClock; ///< the basestation clock!
int receiveOffset; ///< offset b/w transmit and receive GSM timestamps, in timeslots
-
+ int shiftOffset;
+ bool shiftUpdate;
bool mOn; ///< indicates radio is on
private:
@@ -93,6 +94,7 @@ public:
/** check for underrun, resets underrun value */
bool isUnderrun();
+ void applyOffset(int offset);
/** return the receive FIFO */
VectorFIFO* receiveFIFO(size_t chan = 0);
@@ -100,12 +102,18 @@ public:
/** return the basestation clock */
RadioClock* getClock(void) { return &mClock;};
+ /** apply an offset to the main clock */
+ void adjustClock(GSM::Time &offset);
+
/** set transmit frequency */
virtual bool tuneTx(double freq, size_t chan = 0);
/** set receive frequency */
virtual bool tuneRx(double freq, size_t chan = 0);
+ /** set frequency correction */
+ virtual bool tuneRxOffset(double offset, size_t chan = 0);
+
/** set receive gain */
virtual double setRxGain(double dB, size_t chan = 0);
diff --git a/Transceiver52M/radioInterfaceMulti.cpp b/Transceiver52M/radioInterfaceMulti.cpp
index eaf0886..a419086 100644
--- a/Transceiver52M/radioInterfaceMulti.cpp
+++ b/Transceiver52M/radioInterfaceMulti.cpp
@@ -258,7 +258,7 @@ int RadioInterfaceMulti::pullBuffer()
num = mDevice->readSamples(convertRecvBuffer,
outerRecvBuffer->size(),
&overrun,
- readTimestamp,
+ readTimestamp + shiftOffset,
&local_underrun);
if (num != channelizer->inputLen()) {
LOG(ALERT) << "Receive error " << num << ", " << channelizer->inputLen();
diff --git a/Transceiver52M/radioInterfaceResamp.cpp b/Transceiver52M/radioInterfaceResamp.cpp
index b92432f..d377022 100644
--- a/Transceiver52M/radioInterfaceResamp.cpp
+++ b/Transceiver52M/radioInterfaceResamp.cpp
@@ -174,7 +174,7 @@ int RadioInterfaceResamp::pullBuffer()
num_recv = mDevice->readSamples(convertRecvBuffer,
resamp_outchunk,
&overrun,
- readTimestamp,
+ readTimestamp + shiftOffset,
&local_underrun);
if (num_recv != (int) resamp_outchunk) {
LOG(ALERT) << "Receive error " << num_recv;
diff --git a/Transceiver52M/radioVector.cpp b/Transceiver52M/radioVector.cpp
index 68e42c5..2e85d7a 100644
--- a/Transceiver52M/radioVector.cpp
+++ b/Transceiver52M/radioVector.cpp
@@ -85,6 +85,9 @@ float noiseVector::avg() const
{
float val = 0.0;
+ if (!size())
+ return 0.0f;
+
for (size_t i = 0; i < size(); i++)
val += (*this)[i];
@@ -93,8 +96,10 @@ float noiseVector::avg() const
bool noiseVector::insert(float val)
{
- if (!size())
- return false;
+ if (size() < max) {
+ push_back(val);
+ return true;
+ }
if (itr >= this->size())
itr = 0;
@@ -104,6 +109,16 @@ bool noiseVector::insert(float val)
return true;
}
+bool noiseVector::full() const
+{
+ return size() >= max;
+}
+
+void noiseVector::reset()
+{
+ resize(0);
+}
+
GSM::Time VectorQueue::nextTime() const
{
GSM::Time retVal;
diff --git a/Transceiver52M/radioVector.h b/Transceiver52M/radioVector.h
index 84e3987..2cb2806 100644
--- a/Transceiver52M/radioVector.h
+++ b/Transceiver52M/radioVector.h
@@ -52,10 +52,13 @@ class noiseVector : std::vector<float> {
public:
noiseVector(size_t size = 0);
bool insert(float val);
+ bool full() const;
float avg() const;
+ void reset();
private:
size_t itr;
+ size_t max;
};
class VectorFIFO : public InterthreadQueue<radioVector> { };
diff --git a/Transceiver52M/sch.c b/Transceiver52M/sch.c
new file mode 100644
index 0000000..5d2b5c6
--- /dev/null
+++ b/Transceiver52M/sch.c
@@ -0,0 +1,299 @@
+#include <complex.h>
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/conv.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/crcgen.h>
+
+#include "sch.h"
+
+/* GSM 04.08, 9.1.30 Synchronization channel information */
+struct sch_packed_info {
+ ubit_t t1_hi[2];
+ ubit_t bsic[6];
+ ubit_t t1_md[8];
+ ubit_t t3p_hi[2];
+ ubit_t t2[5];
+ ubit_t t1_lo[1];
+ ubit_t t3p_lo[1];
+} __attribute__((packed));
+
+struct sch_burst {
+ sbit_t tail0[3];
+ sbit_t data0[39];
+ sbit_t etsc[64];
+ sbit_t data1[39];
+ sbit_t tail1[3];
+ sbit_t guard[8];
+} __attribute__((packed));
+
+static const uint8_t sch_next_output[][2] = {
+ { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 },
+ { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 },
+ { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 },
+ { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 },
+};
+
+static const uint8_t sch_next_state[][2] = {
+ { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 },
+ { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 },
+ { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 },
+ { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 },
+};
+
+static const struct osmo_conv_code gsm_conv_sch = {
+ .N = 2,
+ .K = 5,
+ .len = GSM_SCH_UNCODED_LEN,
+ .next_output = sch_next_output,
+ .next_state = sch_next_state,
+};
+
+const struct osmo_crc16gen_code gsm0503_sch_crc10 = {
+ .bits = 10,
+ .poly = 0x175,
+ .init = 0x000,
+ .remainder = 0x3ff,
+};
+
+#define GSM_MAX_BURST_LEN 157 * 4
+#define GSM_SYM_RATE (1625e3 / 6) * 4
+
+/* Pre-generated FCCH measurement tone */
+static complex float fcch_ref[GSM_MAX_BURST_LEN];
+
+int float_to_sbit(const float *in, sbit_t *out, float scale, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ out[i] = (in[i] - 0.5f) * scale;
+ }
+
+ return 0;
+}
+
+/* Check if FN contains a FCCH burst */
+int gsm_fcch_check_fn(int fn)
+{
+ int fn51 = fn % 51;
+
+ switch (fn51) {
+ case 0:
+ case 10:
+ case 20:
+ case 30:
+ case 40:
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Check if FN contains a SCH burst */
+int gsm_sch_check_fn(int fn)
+{
+ int fn51 = fn % 51;
+
+ switch (fn51) {
+ case 1:
+ case 11:
+ case 21:
+ case 31:
+ case 41:
+ return 1;
+ }
+
+ return 0;
+}
+
+/* SCH (T1, T2, T3p) to full FN value */
+int gsm_sch_to_fn(struct sch_info *sch)
+{
+ int t1 = sch->t1;
+ int t2 = sch->t2;
+ int t3p = sch->t3p;
+
+ if ((t1 < 0) || (t2 < 0) || (t3p < 0))
+ return -1;
+ int tt;
+ int t3 = t3p * 10 + 1;
+
+ if (t3 < t2)
+ tt = (t3 + 26) - t2;
+ else
+ tt = (t3 - t2) % 26;
+
+ return t1 * 51 * 26 + tt * 51 + t3;
+}
+
+/* Parse encoded SCH message */
+int gsm_sch_parse(const uint8_t *info, struct sch_info *desc)
+{
+ struct sch_packed_info *p = (struct sch_packed_info *) info;
+
+ desc->bsic = (p->bsic[0] << 0) | (p->bsic[1] << 1) |
+ (p->bsic[2] << 2) | (p->bsic[3] << 3) |
+ (p->bsic[4] << 4) | (p->bsic[5] << 5);
+
+ desc->t1 = (p->t1_lo[0] << 0) | (p->t1_md[0] << 1) |
+ (p->t1_md[1] << 2) | (p->t1_md[2] << 3) |
+ (p->t1_md[3] << 4) | (p->t1_md[4] << 5) |
+ (p->t1_md[5] << 6) | (p->t1_md[6] << 7) |
+ (p->t1_md[7] << 8) | (p->t1_hi[0] << 9) |
+ (p->t1_hi[1] << 10);
+
+ desc->t2 = (p->t2[0] << 0) | (p->t2[1] << 1) |
+ (p->t2[2] << 2) | (p->t2[3] << 3) |
+ (p->t2[4] << 4);
+
+ desc->t3p = (p->t3p_lo[0] << 0) | (p->t3p_hi[0] << 1) |
+ (p->t3p_hi[1] << 2);
+
+ return 0;
+}
+
+/* From osmo-bts */
+int gsm_sch_decode(uint8_t *info, sbit_t *data)
+{
+ int rc;
+ ubit_t uncoded[GSM_SCH_UNCODED_LEN];
+
+ osmo_conv_decode(&gsm_conv_sch, data, uncoded);
+
+ rc = osmo_crc16gen_check_bits(&gsm0503_sch_crc10,
+ uncoded, GSM_SCH_INFO_LEN,
+ uncoded + GSM_SCH_INFO_LEN);
+ if (rc)
+ return -1;
+
+ memcpy(info, uncoded, GSM_SCH_INFO_LEN * sizeof(ubit_t));
+
+ return 0;
+}
+
+#define FCCH_TAIL_BITS_LEN 3*4
+#define FCCH_DATA_LEN 100*4// 142
+#if 1
+/* Compute FCCH frequency offset */
+double org_gsm_fcch_offset(float *burst, int len)
+{
+ int i, start, end;
+ float a, b, c, d, ang, avg = 0.0f;
+ double freq;
+
+ if (len > GSM_MAX_BURST_LEN)
+ len = GSM_MAX_BURST_LEN;
+
+ for (i = 0; i < len; i++) {
+ a = burst[2 * i + 0];
+ b = burst[2 * i + 1];
+ c = crealf(fcch_ref[i]);
+ d = cimagf(fcch_ref[i]);
+
+ burst[2 * i + 0] = a * c - b * d;
+ burst[2 * i + 1] = a * d + b * c;
+ }
+
+ start = FCCH_TAIL_BITS_LEN;
+ end = start + FCCH_DATA_LEN;
+
+ for (i = start; i < end; i++) {
+ a = cargf(burst[2 * (i - 1) + 0] +
+ burst[2 * (i - 1) + 1] * I);
+ b = cargf(burst[2 * i + 0] +
+ burst[2 * i + 1] * I);
+
+ ang = b - a;
+
+ if (ang > M_PI)
+ ang -= 2 * M_PI;
+ else if (ang < -M_PI)
+ ang += 2 * M_PI;
+
+ avg += ang;
+ }
+
+ avg /= (float) (end - start);
+ freq = avg / (2 * M_PI) * GSM_SYM_RATE;
+
+ return freq;
+}
+
+
+static const int L1 = 3;
+static const int L2 = 32;
+static const int N1 = 92;
+static const int N2 = 92;
+
+static struct { int8_t r; int8_t s; } P_inv_table[3+32];
+
+void pinv(int P, int8_t* r, int8_t* s, int L1, int L2) {
+ for (int i = 0; i < L1; i++)
+ for (int j = 0; j < L2; j++)
+ if (P == L2 * i - L1 * j) {
+ *r = i;
+ *s = j;
+ return;
+ }
+}
+
+
+float ac_sum_with_lag( complex float* in, int lag, int offset, int N) {
+ complex float v = 0 + 0*I;
+ int total_offset = offset + lag;
+ for (int s = 0; s < N; s++)
+ v += in[s + total_offset] * conjf(in[s + total_offset - lag]);
+ return cargf(v);
+}
+
+
+double gsm_fcch_offset(float *burst, int len)
+{
+ int start;
+
+ const float fs = 13. / 48. * 1e6 * 4;
+ const float expected_fcch_val = ((2 * M_PI) / (fs)) * 67700;
+
+ if (len > GSM_MAX_BURST_LEN)
+ len = GSM_MAX_BURST_LEN;
+
+ start = FCCH_TAIL_BITS_LEN+10 * 4;
+ float alpha_one = ac_sum_with_lag((complex float*)burst, L1, start, N1);
+ float alpha_two = ac_sum_with_lag((complex float*)burst, L2, start, N2);
+
+ float P_unrounded = (L1 * alpha_two - L2 * alpha_one) / (2 * M_PI);
+ int P = roundf(P_unrounded);
+
+ int8_t r = 0, s = 0;
+ pinv(P, &r, &s, L1, L2);
+
+ float omegal1 = (alpha_one + 2 * M_PI * r) / L1;
+ float omegal2 = (alpha_two + 2 * M_PI * s) / L2;
+
+ float rv = org_gsm_fcch_offset(burst, len);
+ //return rv;
+
+ float reval = GSM_SYM_RATE / (2 * M_PI) * (expected_fcch_val - (omegal1+omegal2)/2);
+ //fprintf(stderr, "XX rv %f %f %f %f\n", rv, reval, omegal1 / (2 * M_PI) * fs, omegal2 / (2 * M_PI) * fs);
+
+ //fprintf(stderr, "XX rv %f %f\n", rv, reval);
+
+ return -reval;
+}
+#endif
+/* Generate FCCH measurement tone */
+static __attribute__((constructor)) void init()
+{
+ int i;
+ double freq = 0.25;
+
+ for (i = 0; i < GSM_MAX_BURST_LEN; i++) {
+ fcch_ref[i] = sin(2 * M_PI * freq * (double) i) +
+ cos(2 * M_PI * freq * (double) i) * I;
+ }
+
+}
diff --git a/Transceiver52M/sch.h b/Transceiver52M/sch.h
new file mode 100644
index 0000000..445792b
--- /dev/null
+++ b/Transceiver52M/sch.h
@@ -0,0 +1,27 @@
+#ifndef _SCH_H_
+#define _SCH_H_
+
+#include <osmocom/core/bits.h>
+
+struct sch_info {
+ int bsic;
+ int t1;
+ int t2;
+ int t3p;
+};
+
+#define GSM_SCH_INFO_LEN 25
+#define GSM_SCH_UNCODED_LEN 35
+#define GSM_SCH_CODED_LEN 78
+
+int gsm_sch_decode(uint8_t *sb_info, sbit_t *burst);
+int gsm_sch_parse(const uint8_t *sb_info, struct sch_info *desc);
+int gsm_sch_to_fn(struct sch_info *sch);
+int gsm_sch_check_fn(int fn);
+int gsm_fcch_check_fn(int fn);
+
+double gsm_fcch_offset(float *burst, int len);
+
+int float_to_sbit(const float *in, sbit_t *out, float scale, int len);
+
+#endif /* _SCH_H_ */
diff --git a/Transceiver52M/sigProcLib.cpp b/Transceiver52M/sigProcLib.cpp
index df87f94..792c781 100644
--- a/Transceiver52M/sigProcLib.cpp
+++ b/Transceiver52M/sigProcLib.cpp
@@ -98,6 +98,7 @@ struct CorrelationSequence {
signalVector *sequence;
void *buffer;
+ void *history;
float toa;
complex gain;
};
@@ -129,6 +130,7 @@ struct PulseSequence {
static CorrelationSequence *gMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
static CorrelationSequence *gEdgeMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
static CorrelationSequence *gRACHSequences[] = {NULL,NULL,NULL};
+static CorrelationSequence *gSCHSequence = NULL;
static PulseSequence *GSMPulse1 = NULL;
static PulseSequence *GSMPulse4 = NULL;
@@ -151,6 +153,9 @@ void sigProcLibDestroy()
gRACHSequences[i] = NULL;
}
+ delete gSCHSequence;
+ gSCHSequence = NULL;
+
delete GMSKRotation1;
delete GMSKReverseRotation1;
delete GMSKRotation4;
@@ -315,6 +320,8 @@ static signalVector *convolve(const signalVector *x, const signalVector *h,
append = true;
break;
case CUSTOM:
+
+ // FIXME: x->getstart?
if (start < h->size() - 1) {
head = h->size() - start;
append = true;
@@ -1384,6 +1391,70 @@ release:
return status;
}
+bool generateSCHSequence(int sps)
+{
+ bool status = true;
+ float toa;
+ complex *data = NULL;
+ signalVector *autocorr = NULL;
+ signalVector *seq0 = NULL, *seq1 = NULL, *_seq1 = NULL;
+
+ delete gSCHSequence;
+
+ seq0 = modulateBurst(gSCHSynchSequence, 0, sps, false);
+ if (!seq0)
+ return false;
+
+ seq1 = modulateBurst(gSCHSynchSequence, 0, sps, true);
+ if (!seq1) {
+ status = false;
+ goto release;
+ }
+
+ conjugateVector(*seq1);
+
+ /* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */
+ data = (complex *) convolve_h_alloc(seq1->size());
+ _seq1 = new signalVector(data, 0, seq1->size());
+ _seq1->setAligned(true);
+ memcpy(_seq1->begin(), seq1->begin(), seq1->size() * sizeof(complex));
+
+ autocorr = convolve(seq0, _seq1, autocorr, NO_DELAY);
+ if (!autocorr) {
+ status = false;
+ goto release;
+ }
+
+ gSCHSequence = new CorrelationSequence;
+ gSCHSequence->sequence = _seq1;
+ gSCHSequence->buffer = data;
+ gSCHSequence->gain = peakDetect(*autocorr, &toa, NULL);
+ gSCHSequence->history = new complex[_seq1->size()];
+
+ /* For 1 sps only
+ * (Half of correlation length - 1) + midpoint of pulse shaping filer
+ * 20.5 = (64 / 2 - 1) + 1.5
+ */
+ if (sps == 1)
+ gSCHSequence->toa = toa - 32.5;
+ else
+ gSCHSequence->toa = 0.0;
+
+release:
+ delete autocorr;
+ delete seq0;
+ delete seq1;
+
+ if (!status) {
+ delete _seq1;
+ free(data);
+ gSCHSequence = NULL;
+ }
+
+ return status;
+}
+
+
/*
* Peak-to-average computation +/- range from peak in symbols
*/
@@ -1469,6 +1540,9 @@ static float computeCI(const signalVector *burst, const CorrelationSequence *syn
/* Integer position where the sequence starts */
const int ps = start + 1 - N + (int)roundf(toa);
+ if(ps < 0) // might be -22 for toa 40 with N=64, if off by a lot during sch ms sync
+ return 0;
+
/* Estimate Signal power */
S = 0.0f;
for (int i=0, j=ps; i<(int)N; i++,j++)
@@ -1652,6 +1726,66 @@ static int detectRACHBurst(const signalVector &burst, float threshold, int sps,
return rc;
}
+int detectSCHBurst(signalVector &burst,
+ float thresh,
+ int sps,
+ sch_detect_type state, struct estim_burst_params *ebp)
+{
+ int rc, start, target, head, tail, len;
+ float _toa;
+ complex _amp;
+ signalVector *corr, *_burst;
+ CorrelationSequence *sync;
+
+ if ((sps != 1) && (sps != 4))
+ return -1;
+
+ target = 3 + 39 + 64;
+
+ switch (state) {
+ case sch_detect_type::SCH_DETECT_NARROW:
+ head = 4;
+ tail = 4;
+ break;
+ case sch_detect_type::SCH_DETECT_FULL:
+ default:
+ head = target - 1;
+ tail = 39 + 3 + 9;
+ break;
+ }
+
+ start = (target - head) * 1 - 1;
+ len = (head + tail) * 1;
+ sync = gSCHSequence;
+ corr = new signalVector(len);
+
+ _burst = new signalVector(burst, sync->sequence->size(), 5);
+
+ memcpy(_burst->begin() - sync->sequence->size(), sync->history,
+ sync->sequence->size() * sizeof(complex));
+
+ memcpy(sync->history, &burst.begin()[burst.size() - sync->sequence->size()],
+ sync->sequence->size() * sizeof(complex));
+
+ rc = detectBurst(*_burst, *corr, sync,
+ thresh, sps, start, len, ebp);
+ delete corr;
+
+ if (rc < 0) {
+ return -1;
+ } else if (!rc) {
+ ebp->amp = 0.0f;
+ ebp->toa = 0.0f;
+ return 0;
+ }
+
+ /* Subtract forward search bits from delay */
+ ebp->toa = ebp->toa - head;
+
+ return rc;
+}
+
+
/*
* Normal burst detection
*
@@ -1670,7 +1804,7 @@ static int analyzeTrafficBurst(const signalVector &burst, unsigned tsc, float th
return -SIGERR_UNSUPPORTED;
target = 3 + 58 + 16 + 5;
- head = 6;
+ head = 10;
tail = 6 + max_toa;
sync = gMidambles[tsc];
@@ -1921,6 +2055,8 @@ bool sigProcLibSetup()
generateRACHSequence(&gRACHSequences[1], gRACHSynchSequenceTS1, 1);
generateRACHSequence(&gRACHSequences[2], gRACHSynchSequenceTS2, 1);
+ generateSCHSequence(1);
+
for (int tsc = 0; tsc < 8; tsc++) {
generateMidamble(1, tsc);
gEdgeMidambles[tsc] = generateEdgeMidamble(tsc);
diff --git a/Transceiver52M/sigProcLib.h b/Transceiver52M/sigProcLib.h
index 0c3c7c6..73d4704 100644
--- a/Transceiver52M/sigProcLib.h
+++ b/Transceiver52M/sigProcLib.h
@@ -31,6 +31,7 @@ enum CorrType{
TSC, ///< timeslot should contain a normal burst
EXT_RACH, ///< timeslot should contain an extended access burst
RACH, ///< timeslot should contain an access burst
+ SCH,
EDGE, ///< timeslot should contain an EDGE burst
IDLE ///< timeslot is an idle (or dummy) burst
};
@@ -133,6 +134,16 @@ int detectAnyBurst(const signalVector &burst,
unsigned max_toa,
struct estim_burst_params *ebp);
+enum class sch_detect_type {
+ SCH_DETECT_FULL,
+ SCH_DETECT_NARROW,
+};
+
+int detectSCHBurst(signalVector &rxBurst,
+ float detectThreshold,
+ int sps,
+ sch_detect_type state, struct estim_burst_params *ebp);
+
/** Demodulate burst basde on type and output soft bits */
SoftVector *demodAnyBurst(const signalVector &burst, CorrType type,
int sps, struct estim_burst_params *ebp);
diff --git a/configure.ac b/configure.ac
index 422ad65..30a52f9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -312,6 +312,7 @@ AC_MSG_RESULT([LDFLAGS="$LDFLAGS"])
dnl Output files
AC_CONFIG_FILES([\
Makefile \
+ trxcon/Makefile \
CommonLibs/Makefile \
GSM/Makefile \
Transceiver52M/Makefile \
diff --git a/trxcon/.gitignore b/trxcon/.gitignore
new file mode 100644
index 0000000..fe90e43
--- /dev/null
+++ b/trxcon/.gitignore
@@ -0,0 +1,27 @@
+# autoreconf by-products
+*.in
+
+aclocal.m4
+autom4te.cache/
+configure
+depcomp
+install-sh
+missing
+compile
+
+# configure by-products
+.deps/
+Makefile
+
+config.status
+version.h
+
+# build by-products
+*.o
+*.a
+
+trxcon
+
+# various
+.version
+.tarball-version
diff --git a/trxcon/Makefile.am b/trxcon/Makefile.am
new file mode 100644
index 0000000..76165a8
--- /dev/null
+++ b/trxcon/Makefile.am
@@ -0,0 +1,58 @@
+#AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
+
+include $(top_srcdir)/Makefile.common
+
+AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
+AM_CXXFLAGS = -Wall -O3 -g -ldl -lpthread
+
+# versioning magic
+BUILT_SOURCES = $(top_srcdir)/.version
+$(top_srcdir)/.version:
+ echo $(VERSION) > $@-t && mv $@-t $@
+dist-hook:
+ echo $(VERSION) > $(distdir)/.tarball-version
+
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOCODING_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(NULL)
+
+noinst_LTLIBRARIES = libtrxcon.la
+
+libtrxcon_la_SOURCES = \
+ l1ctl_link.c \
+ l1ctl.c \
+ trx_if.c \
+ logging.c \
+ trxcon.c \
+ $(NULL)
+
+# Scheduler
+libtrxcon_la_SOURCES += \
+ sched_lchan_common.c \
+ sched_lchan_pdtch.c \
+ sched_lchan_desc.c \
+ sched_lchan_xcch.c \
+ sched_lchan_tchf.c \
+ sched_lchan_tchh.c \
+ sched_lchan_rach.c \
+ sched_lchan_sch.c \
+ sched_mframe.c \
+ sched_clck.c \
+ sched_prim.c \
+ sched_trx.c \
+ $(NULL)
+
+libtrxcon_la_LIBADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOCODING_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(NULL)
+
diff --git a/trxcon/l1ctl.c b/trxcon/l1ctl.c
new file mode 100644
index 0000000..f39bf03
--- /dev/null
+++ b/trxcon/l1ctl.c
@@ -0,0 +1,918 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * GSM L1 control interface handlers
+ *
+ * (C) 2014 by Sylvain Munaut <tnt@246tNt.com>
+ * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+
+#include "logging.h"
+#include "l1ctl_link.h"
+#include "l1ctl_proto.h"
+
+#include "trx_if.h"
+#include "sched_trx.h"
+
+static const char *arfcn2band_name(uint16_t arfcn)
+{
+ enum gsm_band band;
+
+ if (gsm_arfcn2band_rc(arfcn, &band) < 0)
+ return "(invalid)";
+
+ return gsm_band_name(band);
+}
+
+static struct msgb *l1ctl_alloc_msg(uint8_t msg_type)
+{
+ struct l1ctl_hdr *l1h;
+ struct msgb *msg;
+
+ /**
+ * Each L1CTL message gets its own length pushed in front
+ * before sending. This is why we need this small headroom.
+ */
+ msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_MSG_LEN_FIELD,
+ L1CTL_MSG_LEN_FIELD, "l1ctl_tx_msg");
+ if (!msg) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n");
+ return NULL;
+ }
+
+ msg->l1h = msgb_put(msg, sizeof(*l1h));
+ l1h = (struct l1ctl_hdr *) msg->l1h;
+ l1h->msg_type = msg_type;
+
+ return msg;
+}
+
+int l1ctl_tx_pm_conf(struct l1ctl_link *l1l, uint16_t band_arfcn,
+ int dbm, int last)
+{
+ struct l1ctl_pm_conf *pmc;
+ struct msgb *msg;
+
+ msg = l1ctl_alloc_msg(L1CTL_PM_CONF);
+ if (!msg)
+ return -ENOMEM;
+
+ LOGP(DL1C, LOGL_DEBUG, "Send PM Conf (%s %d = %d dBm)\n",
+ arfcn2band_name(band_arfcn),
+ band_arfcn &~ ARFCN_FLAG_MASK, dbm);
+
+ pmc = (struct l1ctl_pm_conf *) msgb_put(msg, sizeof(*pmc));
+ pmc->band_arfcn = htons(band_arfcn);
+ pmc->pm[0] = dbm2rxlev(dbm);
+ pmc->pm[1] = 0;
+
+ if (last) {
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->l1h;
+ l1h->flags |= L1CTL_F_DONE;
+ }
+
+ return l1ctl_link_send(l1l, msg);
+}
+
+int l1ctl_tx_reset_ind(struct l1ctl_link *l1l, uint8_t type)
+{
+ struct msgb *msg;
+ struct l1ctl_reset *res;
+
+ msg = l1ctl_alloc_msg(L1CTL_RESET_IND);
+ if (!msg)
+ return -ENOMEM;
+
+ LOGP(DL1C, LOGL_DEBUG, "Send Reset Ind (%u)\n", type);
+
+ res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
+ res->type = type;
+
+ return l1ctl_link_send(l1l, msg);
+}
+
+int l1ctl_tx_reset_conf(struct l1ctl_link *l1l, uint8_t type)
+{
+ struct msgb *msg;
+ struct l1ctl_reset *res;
+
+ msg = l1ctl_alloc_msg(L1CTL_RESET_CONF);
+ if (!msg)
+ return -ENOMEM;
+
+ LOGP(DL1C, LOGL_DEBUG, "Send Reset Conf (%u)\n", type);
+ res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
+ res->type = type;
+
+ return l1ctl_link_send(l1l, msg);
+}
+
+static struct l1ctl_info_dl *put_dl_info_hdr(struct msgb *msg, struct l1ctl_info_dl *dl_info)
+{
+ size_t len = sizeof(struct l1ctl_info_dl);
+ struct l1ctl_info_dl *dl = (struct l1ctl_info_dl *) msgb_put(msg, len);
+
+ if (dl_info) /* Copy DL info provided by handler */
+ memcpy(dl, dl_info, len);
+ else /* Init DL info header */
+ memset(dl, 0x00, len);
+
+ return dl;
+}
+
+/* Fill in FBSB payload: BSIC and sync result */
+static struct l1ctl_fbsb_conf *fbsb_conf_make(struct msgb *msg, uint8_t result, uint8_t bsic)
+{
+ struct l1ctl_fbsb_conf *conf = (struct l1ctl_fbsb_conf *) msgb_put(msg, sizeof(*conf));
+
+ LOGP(DL1C, LOGL_DEBUG, "Send FBSB Conf (result=%u, bsic=%u)\n", result, bsic);
+
+ conf->result = result;
+ conf->bsic = bsic;
+
+ return conf;
+}
+
+int l1ctl_tx_fbsb_conf(struct l1ctl_link *l1l, uint8_t result,
+ struct l1ctl_info_dl *dl_info, uint8_t bsic)
+{
+ struct l1ctl_fbsb_conf *conf;
+ struct msgb *msg;
+
+ msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
+ if (msg == NULL)
+ return -ENOMEM;
+
+ put_dl_info_hdr(msg, dl_info);
+ talloc_free(dl_info);
+
+ conf = fbsb_conf_make(msg, result, bsic);
+
+ /* FIXME: set proper value */
+ conf->initial_freq_err = 0;
+
+ /* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */
+ l1l->fbsb_conf_sent = true;
+
+ /* Abort FBSB expire timer */
+ if (osmo_timer_pending(&l1l->fbsb_timer))
+ osmo_timer_del(&l1l->fbsb_timer);
+
+ return l1ctl_link_send(l1l, msg);
+}
+
+int l1ctl_tx_ccch_mode_conf(struct l1ctl_link *l1l, uint8_t mode)
+{
+ struct l1ctl_ccch_mode_conf *conf;
+ struct msgb *msg;
+
+ msg = l1ctl_alloc_msg(L1CTL_CCCH_MODE_CONF);
+ if (msg == NULL)
+ return -ENOMEM;
+
+ conf = (struct l1ctl_ccch_mode_conf *) msgb_put(msg, sizeof(*conf));
+ conf->ccch_mode = mode;
+
+ return l1ctl_link_send(l1l, msg);
+}
+
+/**
+ * Handles both L1CTL_DATA_IND and L1CTL_TRAFFIC_IND.
+ */
+int l1ctl_tx_dt_ind(struct l1ctl_link *l1l, struct l1ctl_info_dl *data,
+ uint8_t *l2, size_t l2_len, bool traffic)
+{
+ struct msgb *msg;
+ uint8_t *msg_l2;
+
+ msg = l1ctl_alloc_msg(traffic ?
+ L1CTL_TRAFFIC_IND : L1CTL_DATA_IND);
+ if (msg == NULL)
+ return -ENOMEM;
+
+ put_dl_info_hdr(msg, data);
+
+ /* Copy the L2 payload if preset */
+ if (l2 && l2_len > 0) {
+ msg_l2 = (uint8_t *) msgb_put(msg, l2_len);
+ memcpy(msg_l2, l2, l2_len);
+ }
+
+ /* Put message to upper layers */
+ return l1ctl_link_send(l1l, msg);
+}
+
+int l1ctl_tx_rach_conf(struct l1ctl_link *l1l,
+ uint16_t band_arfcn, uint32_t fn)
+{
+ struct l1ctl_info_dl *dl;
+ struct msgb *msg;
+
+ msg = l1ctl_alloc_msg(L1CTL_RACH_CONF);
+ if (msg == NULL)
+ return -ENOMEM;
+
+ dl = put_dl_info_hdr(msg, NULL);
+ memset(dl, 0x00, sizeof(*dl));
+
+ dl->band_arfcn = htons(band_arfcn);
+ dl->frame_nr = htonl(fn);
+
+ return l1ctl_link_send(l1l, msg);
+}
+
+
+/**
+ * Handles both L1CTL_DATA_CONF and L1CTL_TRAFFIC_CONF.
+ */
+int l1ctl_tx_dt_conf(struct l1ctl_link *l1l,
+ struct l1ctl_info_dl *data, bool traffic)
+{
+ struct msgb *msg;
+
+ msg = l1ctl_alloc_msg(traffic ?
+ L1CTL_TRAFFIC_CONF : L1CTL_DATA_CONF);
+ if (msg == NULL)
+ return -ENOMEM;
+
+ /* Copy DL frame header from source message */
+ put_dl_info_hdr(msg, data);
+
+ return l1ctl_link_send(l1l, msg);
+}
+
+static enum gsm_phys_chan_config l1ctl_ccch_mode2pchan_config(enum ccch_mode mode)
+{
+ switch (mode) {
+ /* TODO: distinguish extended BCCH */
+ case CCCH_MODE_NON_COMBINED:
+ case CCCH_MODE_NONE:
+ return GSM_PCHAN_CCCH;
+
+ case CCCH_MODE_COMBINED:
+ return GSM_PCHAN_CCCH_SDCCH4;
+ case CCCH_MODE_COMBINED_CBCH:
+ return GSM_PCHAN_CCCH_SDCCH4_CBCH;
+
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "Undandled CCCH mode (%u), "
+ "assuming non-combined configuration\n", mode);
+ return GSM_PCHAN_CCCH;
+ }
+}
+
+/* FBSB expire timer */
+static void fbsb_timer_cb(void *data)
+{
+ struct l1ctl_link *l1l = (struct l1ctl_link *) data;
+ struct l1ctl_info_dl *dl;
+ struct msgb *msg;
+
+ msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
+ if (msg == NULL)
+ return;
+
+ LOGP(DL1C, LOGL_NOTICE, "FBSB timer fired for ARFCN %u\n", l1l->trx->band_arfcn &~ ARFCN_FLAG_MASK);
+
+ dl = put_dl_info_hdr(msg, NULL);
+
+ /* Fill in current ARFCN */
+ dl->band_arfcn = htons(l1l->trx->band_arfcn);
+
+ fbsb_conf_make(msg, 255, 0);
+
+ /* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */
+ l1l->fbsb_conf_sent = true;
+
+ l1ctl_link_send(l1l, msg);
+}
+
+static int l1ctl_rx_fbsb_req(struct l1ctl_link *l1l, struct msgb *msg)
+{
+ enum gsm_phys_chan_config ch_config;
+ struct l1ctl_fbsb_req *fbsb;
+ uint16_t band_arfcn;
+ uint16_t timeout;
+ int rc = 0;
+
+ fbsb = (struct l1ctl_fbsb_req *) msg->l1h;
+ if (msgb_l1len(msg) < sizeof(*fbsb)) {
+ LOGP(DL1C, LOGL_ERROR, "MSG too short FBSB Req: %u\n",
+ msgb_l1len(msg));
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ ch_config = l1ctl_ccch_mode2pchan_config(fbsb->ccch_mode);
+ band_arfcn = ntohs(fbsb->band_arfcn);
+ timeout = ntohs(fbsb->timeout);
+
+ LOGP(DL1C, LOGL_NOTICE, "Received FBSB request (%s %d)\n",
+ arfcn2band_name(band_arfcn),
+ band_arfcn &~ ARFCN_FLAG_MASK);
+
+ /* Reset scheduler and clock counter */
+ sched_trx_reset(l1l->trx, true);
+
+ /* Configure a single timeslot */
+ sched_trx_configure_ts(l1l->trx, 0, ch_config);
+
+ /* Ask SCH handler to send L1CTL_FBSB_CONF */
+ l1l->fbsb_conf_sent = false;
+
+ /* Only if current ARFCN differs */
+// if (l1l->trx->band_arfcn != band_arfcn) {
+ /* Update current ARFCN */
+ l1l->trx->band_arfcn = band_arfcn;
+
+ /* Tune transceiver to required ARFCN */
+ trx_if_cmd_rxtune(l1l->trx, band_arfcn);
+ trx_if_cmd_txtune(l1l->trx, band_arfcn);
+// }
+
+ /* Transceiver might have been powered on before, e.g.
+ * in case of sending L1CTL_FBSB_REQ due to signal loss. */
+ if (!l1l->trx->powered_up)
+ trx_if_cmd_poweron(l1l->trx);
+
+ trx_if_cmd_sync(l1l->trx);
+
+ /* Start FBSB expire timer */
+ l1l->fbsb_timer.data = l1l;
+ l1l->fbsb_timer.cb = fbsb_timer_cb;
+ LOGP(DL1C, LOGL_INFO, "Starting FBSB timer %u ms\n", timeout * GSM_TDMA_FN_DURATION_uS / 1000);
+ osmo_timer_schedule(&l1l->fbsb_timer, 35,
+ timeout * GSM_TDMA_FN_DURATION_uS);
+
+exit:
+ msgb_free(msg);
+ return rc;
+}
+
+static int l1ctl_rx_pm_req(struct l1ctl_link *l1l, struct msgb *msg)
+{
+ uint16_t band_arfcn_start, band_arfcn_stop;
+ struct l1ctl_pm_req *pmr;
+ int rc = 0;
+
+ pmr = (struct l1ctl_pm_req *) msg->l1h;
+ if (msgb_l1len(msg) < sizeof(*pmr)) {
+ LOGP(DL1C, LOGL_ERROR, "MSG too short PM Req: %u\n",
+ msgb_l1len(msg));
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ band_arfcn_start = ntohs(pmr->range.band_arfcn_from);
+ band_arfcn_stop = ntohs(pmr->range.band_arfcn_to);
+
+ LOGP(DL1C, LOGL_NOTICE, "Received power measurement "
+ "request (%s: %d -> %d)\n",
+ arfcn2band_name(band_arfcn_start),
+ band_arfcn_start &~ ARFCN_FLAG_MASK,
+ band_arfcn_stop &~ ARFCN_FLAG_MASK);
+
+ /* Send measurement request to transceiver */
+ rc = trx_if_cmd_measure(l1l->trx, band_arfcn_start, band_arfcn_stop);
+
+exit:
+ msgb_free(msg);
+ return rc;
+}
+
+static int l1ctl_rx_reset_req(struct l1ctl_link *l1l, struct msgb *msg)
+{
+ struct l1ctl_reset *res;
+ int rc = 0;
+
+ res = (struct l1ctl_reset *) msg->l1h;
+ if (msgb_l1len(msg) < sizeof(*res)) {
+ LOGP(DL1C, LOGL_ERROR, "MSG too short Reset Req: %u\n",
+ msgb_l1len(msg));
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ LOGP(DL1C, LOGL_NOTICE, "Received reset request (%u)\n",
+ res->type);
+
+ switch (res->type) {
+ case L1CTL_RES_T_FULL:
+ /* TODO: implement trx_if_reset() */
+ trx_if_cmd_poweroff(l1l->trx);
+ trx_if_cmd_echo(l1l->trx);
+
+ /* Fall through */
+ case L1CTL_RES_T_SCHED:
+ sched_trx_reset(l1l->trx, true);
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "Unknown L1CTL_RESET_REQ type\n");
+ goto exit;
+ }
+
+ /* Confirm */
+ rc = l1ctl_tx_reset_conf(l1l, res->type);
+
+exit:
+ msgb_free(msg);
+ return rc;
+}
+
+static int l1ctl_rx_echo_req(struct l1ctl_link *l1l, struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h;
+
+ LOGP(DL1C, LOGL_NOTICE, "Recv Echo Req\n");
+ LOGP(DL1C, LOGL_NOTICE, "Send Echo Conf\n");
+
+ /* Nothing to do, just send it back */
+ l1h = (struct l1ctl_hdr *) msg->l1h;
+ l1h->msg_type = L1CTL_ECHO_CONF;
+ msg->data = msg->l1h;
+
+ return l1ctl_link_send(l1l, msg);
+}
+
+static int l1ctl_rx_ccch_mode_req(struct l1ctl_link *l1l, struct msgb *msg)
+{
+ enum gsm_phys_chan_config ch_config;
+ struct l1ctl_ccch_mode_req *req;
+ struct trx_ts *ts;
+ int rc = 0;
+
+ req = (struct l1ctl_ccch_mode_req *) msg->l1h;
+ if (msgb_l1len(msg) < sizeof(*req)) {
+ LOGP(DL1C, LOGL_ERROR, "MSG too short Reset Req: %u\n",
+ msgb_l1len(msg));
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ LOGP(DL1C, LOGL_NOTICE, "Received CCCH mode request (%u)\n",
+ req->ccch_mode); /* TODO: add value-string for ccch_mode */
+
+ /* Make sure that TS0 is allocated and configured */
+ ts = l1l->trx->ts_list[0];
+ if (ts == NULL || ts->mf_layout == NULL) {
+ LOGP(DL1C, LOGL_ERROR, "TS0 is not configured");
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Choose corresponding channel combination */
+ ch_config = l1ctl_ccch_mode2pchan_config(req->ccch_mode);
+
+ /* Do nothing if the current mode matches required */
+ if (ts->mf_layout->chan_config != ch_config)
+ rc = sched_trx_configure_ts(l1l->trx, 0, ch_config);
+
+ /* Confirm reconfiguration */
+ if (!rc)
+ rc = l1ctl_tx_ccch_mode_conf(l1l, req->ccch_mode);
+
+exit:
+ msgb_free(msg);
+ return rc;
+}
+
+static int l1ctl_rx_rach_req(struct l1ctl_link *l1l, struct msgb *msg, bool ext)
+{
+ struct l1ctl_ext_rach_req *ext_req;
+ struct l1ctl_rach_req *req;
+ struct l1ctl_info_ul *ul;
+ struct trx_ts_prim *prim;
+ size_t len;
+ int rc;
+
+ ul = (struct l1ctl_info_ul *) msg->l1h;
+
+ /* Is it extended (11-bit) RACH or not? */
+ if (ext) {
+ ext_req = (struct l1ctl_ext_rach_req *) ul->payload;
+ ext_req->offset = ntohs(ext_req->offset);
+ ext_req->ra11 = ntohs(ext_req->ra11);
+ len = sizeof(*ext_req);
+
+ LOGP(DL1C, LOGL_NOTICE, "Received extended (11-bit) RACH request "
+ "(offset=%u, synch_seq=%u, ra11=0x%02hx)\n",
+ ext_req->offset, ext_req->synch_seq, ext_req->ra11);
+ } else {
+ req = (struct l1ctl_rach_req *) ul->payload;
+ req->offset = ntohs(req->offset);
+ len = sizeof(*req);
+
+ LOGP(DL1C, LOGL_NOTICE, "Received regular (8-bit) RACH request "
+ "(offset=%u, ra=0x%02x)\n", req->offset, req->ra);
+ }
+
+ /* The controlling L1CTL side always does include the UL info header,
+ * but may leave it empty. We assume RACH is on TS0 in this case. */
+ if (ul->chan_nr == 0x00) {
+ LOGP(DL1C, LOGL_NOTICE, "The UL info header is empty, "
+ "assuming RACH is on TS0\n");
+ ul->chan_nr = RSL_CHAN_RACH;
+ }
+
+ /* Init a new primitive */
+ rc = sched_prim_init(l1l->trx, &prim, len, ul->chan_nr, ul->link_id);
+ if (rc)
+ goto exit;
+
+ /**
+ * Push this primitive to the transmit queue.
+ * Indicated timeslot needs to be configured.
+ */
+ rc = sched_prim_push(l1l->trx, prim, ul->chan_nr);
+ if (rc) {
+ talloc_free(prim);
+ goto exit;
+ }
+
+ /* Fill in the payload */
+ memcpy(prim->payload, ul->payload, len);
+
+exit:
+ msgb_free(msg);
+ return rc;
+}
+
+static int l1ctl_proc_est_req_h0(struct trx_instance *trx, struct l1ctl_h0 *h)
+{
+ uint16_t band_arfcn;
+ int rc = 0;
+
+ band_arfcn = ntohs(h->band_arfcn);
+
+ LOGP(DL1C, LOGL_NOTICE, "L1CTL_DM_EST_REQ indicates a single "
+ "ARFCN=%u channel\n", band_arfcn &~ ARFCN_FLAG_MASK);
+
+ /* Do we need to retune? */
+ if (trx->band_arfcn == band_arfcn)
+ return 0;
+
+ /* Tune transceiver to required ARFCN */
+ rc |= trx_if_cmd_rxtune(trx, band_arfcn);
+ rc |= trx_if_cmd_txtune(trx, band_arfcn);
+ if (rc)
+ return rc;
+
+ /* Update current ARFCN */
+ trx->band_arfcn = band_arfcn;
+
+ return 0;
+}
+
+static int l1ctl_proc_est_req_h1(struct trx_instance *trx, struct l1ctl_h1 *h)
+{
+ uint16_t ma[64];
+ int i, rc;
+
+ LOGP(DL1C, LOGL_NOTICE, "L1CTL_DM_EST_REQ indicates a Frequency "
+ "Hopping (hsn=%u, maio=%u, chans=%u) channel\n",
+ h->hsn, h->maio, h->n);
+
+ /* No channels?!? */
+ if (!h->n) {
+ LOGP(DL1C, LOGL_ERROR, "No channels in mobile allocation?!?\n");
+ return -EINVAL;
+ } else if (h->n > ARRAY_SIZE(ma)) {
+ LOGP(DL1C, LOGL_ERROR, "More than 64 channels in mobile allocation?!?\n");
+ return -EINVAL;
+ }
+
+ /* Convert from network to host byte order */
+ for (i = 0; i < h->n; i++)
+ ma[i] = ntohs(h->ma[i]);
+
+ /* Forward hopping parameters to TRX */
+ rc = trx_if_cmd_setfh(trx, h->hsn, h->maio, ma, h->n);
+ if (rc)
+ return rc;
+
+ /**
+ * TODO: update the state of trx_instance somehow
+ * in order to indicate that it is in hopping mode...
+ */
+ return 0;
+}
+
+static int l1ctl_rx_dm_est_req(struct l1ctl_link *l1l, struct msgb *msg)
+{
+ enum gsm_phys_chan_config config;
+ struct l1ctl_dm_est_req *est_req;
+ struct l1ctl_info_ul *ul;
+ struct trx_ts *ts;
+ uint8_t chan_nr, tn;
+ int rc;
+
+ ul = (struct l1ctl_info_ul *) msg->l1h;
+ est_req = (struct l1ctl_dm_est_req *) ul->payload;
+
+ chan_nr = ul->chan_nr;
+ tn = chan_nr & 0x07;
+
+ LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_DM_EST_REQ "
+ "(tn=%u, chan_nr=0x%02x, tsc=%u, tch_mode=0x%02x)\n",
+ tn, chan_nr, est_req->tsc, est_req->tch_mode);
+
+ /* Determine channel config */
+ config = sched_trx_chan_nr2pchan_config(chan_nr);
+ if (config == GSM_PCHAN_NONE) {
+ LOGP(DL1C, LOGL_ERROR, "Couldn't determine channel config\n");
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Frequency hopping? */
+ if (est_req->h)
+ rc = l1ctl_proc_est_req_h1(l1l->trx, &est_req->h1);
+ else /* Single ARFCN */
+ rc = l1ctl_proc_est_req_h0(l1l->trx, &est_req->h0);
+ if (rc)
+ goto exit;
+
+ /* Update TSC (Training Sequence Code) */
+ l1l->trx->tsc = est_req->tsc;
+
+ /* Configure requested TS */
+ rc = sched_trx_configure_ts(l1l->trx, tn, config);
+ ts = l1l->trx->ts_list[tn];
+ if (rc) {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Deactivate all lchans */
+ sched_trx_deactivate_all_lchans(ts);
+
+ /* Activate only requested lchans */
+ rc = sched_trx_set_lchans(ts, chan_nr, 1, est_req->tch_mode);
+ if (rc) {
+ LOGP(DL1C, LOGL_ERROR, "Couldn't activate requested lchans\n");
+ rc = -EINVAL;
+ goto exit;
+ }
+
+exit:
+ msgb_free(msg);
+ return rc;
+}
+
+static int l1ctl_rx_dm_rel_req(struct l1ctl_link *l1l, struct msgb *msg)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_DM_REL_REQ, "
+ "switching back to CCCH\n");
+
+ /* Reset scheduler */
+ sched_trx_reset(l1l->trx, false);
+
+ msgb_free(msg);
+ return 0;
+}
+
+/**
+ * Handles both L1CTL_DATA_REQ and L1CTL_TRAFFIC_REQ.
+ */
+static int l1ctl_rx_dt_req(struct l1ctl_link *l1l,
+ struct msgb *msg, bool traffic)
+{
+ struct l1ctl_info_ul *ul;
+ struct trx_ts_prim *prim;
+ uint8_t chan_nr, link_id;
+ size_t payload_len;
+ int rc;
+
+ /* Extract UL frame header */
+ ul = (struct l1ctl_info_ul *) msg->l1h;
+
+ /* Calculate the payload len */
+ msg->l2h = ul->payload;
+ payload_len = msgb_l2len(msg);
+
+ /* Obtain channel description */
+ chan_nr = ul->chan_nr;
+ link_id = ul->link_id & 0x40;
+
+ LOGP(DL1D, LOGL_DEBUG, "Recv %s Req (chan_nr=0x%02x, "
+ "link_id=0x%02x, len=%zu)\n", traffic ? "TRAFFIC" : "DATA",
+ chan_nr, link_id, payload_len);
+
+ /* Init a new primitive */
+ rc = sched_prim_init(l1l->trx, &prim, payload_len,
+ chan_nr, link_id);
+ if (rc)
+ goto exit;
+
+ /* Push this primitive to transmit queue */
+ rc = sched_prim_push(l1l->trx, prim, chan_nr);
+ if (rc) {
+ talloc_free(prim);
+ goto exit;
+ }
+
+ /* Fill in the payload */
+ memcpy(prim->payload, ul->payload, payload_len);
+
+exit:
+ msgb_free(msg);
+ return rc;
+}
+
+static int l1ctl_rx_param_req(struct l1ctl_link *l1l, struct msgb *msg)
+{
+ struct l1ctl_par_req *par_req;
+ struct l1ctl_info_ul *ul;
+
+ ul = (struct l1ctl_info_ul *) msg->l1h;
+ par_req = (struct l1ctl_par_req *) ul->payload;
+
+ LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_PARAM_REQ "
+ "(ta=%d, tx_power=%u)\n", par_req->ta, par_req->tx_power);
+
+ /* Instruct TRX to use new TA value */
+ if (l1l->trx->ta != par_req->ta) {
+ trx_if_cmd_setta(l1l->trx, par_req->ta);
+ l1l->trx->ta = par_req->ta;
+ }
+
+ l1l->trx->tx_power = par_req->tx_power;
+
+ msgb_free(msg);
+ return 0;
+}
+
+static int l1ctl_rx_tch_mode_req(struct l1ctl_link *l1l, struct msgb *msg)
+{
+ struct l1ctl_tch_mode_req *req;
+ struct trx_lchan_state *lchan;
+ struct trx_ts *ts;
+ int i;
+
+ req = (struct l1ctl_tch_mode_req *) msg->l1h;
+
+ LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_TCH_MODE_REQ "
+ "(tch_mode=%u, audio_mode=%u)\n", req->tch_mode, req->audio_mode);
+
+ /* Iterate over timeslot list */
+ for (i = 0; i < TRX_TS_COUNT; i++) {
+ /* Timeslot is not allocated */
+ ts = l1l->trx->ts_list[i];
+ if (ts == NULL)
+ continue;
+
+ /* Timeslot is not configured */
+ if (ts->mf_layout == NULL)
+ continue;
+
+ /* Iterate over all allocated lchans */
+ llist_for_each_entry(lchan, &ts->lchans, list) {
+ /* Omit inactive channels */
+ if (!lchan->active)
+ continue;
+
+ /* Set TCH mode */
+ lchan->tch_mode = req->tch_mode;
+ }
+ }
+
+ /* TODO: do we need to care about audio_mode? */
+
+ /* Re-use the original message as confirmation */
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ l1h->msg_type = L1CTL_TCH_MODE_CONF;
+
+ return l1ctl_link_send(l1l, msg);
+}
+
+static int l1ctl_rx_crypto_req(struct l1ctl_link *l1l, struct msgb *msg)
+{
+ struct l1ctl_crypto_req *req;
+ struct l1ctl_info_ul *ul;
+ struct trx_ts *ts;
+ uint8_t tn;
+ int rc = 0;
+
+ ul = (struct l1ctl_info_ul *) msg->l1h;
+ req = (struct l1ctl_crypto_req *) ul->payload;
+
+ LOGP(DL1C, LOGL_NOTICE, "L1CTL_CRYPTO_REQ (algo=A5/%u, key_len=%u)\n",
+ req->algo, req->key_len);
+
+ /* Determine TS index */
+ tn = ul->chan_nr & 0x7;
+
+ /* Make sure that required TS is allocated and configured */
+ ts = l1l->trx->ts_list[tn];
+ if (ts == NULL || ts->mf_layout == NULL) {
+ LOGP(DL1C, LOGL_ERROR, "TS %u is not configured\n", tn);
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Poke scheduler */
+ rc = sched_trx_start_ciphering(ts, req->algo, req->key, req->key_len);
+ if (rc) {
+ LOGP(DL1C, LOGL_ERROR, "Couldn't configure ciphering\n");
+ rc = -EINVAL;
+ goto exit;
+ }
+
+exit:
+ msgb_free(msg);
+ return rc;
+}
+
+int l1ctl_rx_cb(struct l1ctl_link *l1l, struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h;
+
+ l1h = (struct l1ctl_hdr *) msg->l1h;
+ msg->l1h = l1h->data;
+
+ switch (l1h->msg_type) {
+ case L1CTL_FBSB_REQ:
+ return l1ctl_rx_fbsb_req(l1l, msg);
+ case L1CTL_PM_REQ:
+ return l1ctl_rx_pm_req(l1l, msg);
+ case L1CTL_RESET_REQ:
+ return l1ctl_rx_reset_req(l1l, msg);
+ case L1CTL_ECHO_REQ:
+ return l1ctl_rx_echo_req(l1l, msg);
+ case L1CTL_CCCH_MODE_REQ:
+ return l1ctl_rx_ccch_mode_req(l1l, msg);
+ case L1CTL_RACH_REQ:
+ return l1ctl_rx_rach_req(l1l, msg, false);
+ case L1CTL_EXT_RACH_REQ:
+ return l1ctl_rx_rach_req(l1l, msg, true);
+ case L1CTL_DM_EST_REQ:
+ return l1ctl_rx_dm_est_req(l1l, msg);
+ case L1CTL_DM_REL_REQ:
+ return l1ctl_rx_dm_rel_req(l1l, msg);
+ case L1CTL_DATA_REQ:
+ return l1ctl_rx_dt_req(l1l, msg, false);
+ case L1CTL_TRAFFIC_REQ:
+ return l1ctl_rx_dt_req(l1l, msg, true);
+ case L1CTL_PARAM_REQ:
+ return l1ctl_rx_param_req(l1l, msg);
+ case L1CTL_TCH_MODE_REQ:
+ return l1ctl_rx_tch_mode_req(l1l, msg);
+ case L1CTL_CRYPTO_REQ:
+ return l1ctl_rx_crypto_req(l1l, msg);
+
+ /* Not (yet) handled messages */
+ case L1CTL_NEIGH_PM_REQ:
+ case L1CTL_DATA_TBF_REQ:
+ case L1CTL_TBF_CFG_REQ:
+ case L1CTL_DM_FREQ_REQ:
+ case L1CTL_SIM_REQ:
+ LOGP(DL1C, LOGL_NOTICE, "Ignoring unsupported message "
+ "(type=%u)\n", l1h->msg_type);
+ msgb_free(msg);
+ return -ENOTSUP;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "Unknown MSG type %u: %s\n", l1h->msg_type,
+ osmo_hexdump(msgb_data(msg), msgb_length(msg)));
+ msgb_free(msg);
+ return -EINVAL;
+ }
+}
+
+void l1ctl_shutdown_cb(struct l1ctl_link *l1l)
+{
+ /* Abort FBSB expire timer */
+ if (osmo_timer_pending(&l1l->fbsb_timer))
+ osmo_timer_del(&l1l->fbsb_timer);
+}
diff --git a/trxcon/l1ctl.h b/trxcon/l1ctl.h
new file mode 100644
index 0000000..48bbe09
--- /dev/null
+++ b/trxcon/l1ctl.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/msgb.h>
+
+#include "l1ctl_link.h"
+#include "l1ctl_proto.h"
+
+/* Event handlers */
+int l1ctl_rx_cb(struct l1ctl_link *l1l, struct msgb *msg);
+void l1ctl_shutdown_cb(struct l1ctl_link *l1l);
+
+int l1ctl_tx_fbsb_conf(struct l1ctl_link *l1l, uint8_t result,
+ struct l1ctl_info_dl *dl_info, uint8_t bsic);
+int l1ctl_tx_ccch_mode_conf(struct l1ctl_link *l1l, uint8_t mode);
+int l1ctl_tx_pm_conf(struct l1ctl_link *l1l, uint16_t band_arfcn,
+ int dbm, int last);
+int l1ctl_tx_reset_conf(struct l1ctl_link *l1l, uint8_t type);
+int l1ctl_tx_reset_ind(struct l1ctl_link *l1l, uint8_t type);
+
+int l1ctl_tx_dt_ind(struct l1ctl_link *l1l, struct l1ctl_info_dl *data,
+ uint8_t *l2, size_t l2_len, bool traffic);
+int l1ctl_tx_dt_conf(struct l1ctl_link *l1l,
+ struct l1ctl_info_dl *data, bool traffic);
+int l1ctl_tx_rach_conf(struct l1ctl_link *l1l,
+ uint16_t band_arfcn, uint32_t fn);
diff --git a/trxcon/l1ctl_link.c b/trxcon/l1ctl_link.c
new file mode 100644
index 0000000..4c406d6
--- /dev/null
+++ b/trxcon/l1ctl_link.c
@@ -0,0 +1,316 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * GSM L1 control socket (/tmp/osmocom_l2) handlers
+ *
+ * (C) 2013 by Sylvain Munaut <tnt@246tNt.com>
+ * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/write_queue.h>
+
+#include "trxcon.h"
+#include "logging.h"
+#include "l1ctl_link.h"
+#include "l1ctl.h"
+
+static struct value_string l1ctl_evt_names[] = {
+ { 0, NULL } /* no events? */
+};
+
+static struct osmo_fsm_state l1ctl_fsm_states[] = {
+ [L1CTL_STATE_IDLE] = {
+ .out_state_mask = GEN_MASK(L1CTL_STATE_CONNECTED),
+ .name = "IDLE",
+ },
+ [L1CTL_STATE_CONNECTED] = {
+ .out_state_mask = GEN_MASK(L1CTL_STATE_IDLE),
+ .name = "CONNECTED",
+ },
+};
+
+static struct osmo_fsm l1ctl_fsm = {
+ .name = "l1ctl_link_fsm",
+ .states = l1ctl_fsm_states,
+ .num_states = ARRAY_SIZE(l1ctl_fsm_states),
+ .log_subsys = DL1C,
+ .event_names = l1ctl_evt_names,
+};
+
+static int l1ctl_link_read_cb(struct osmo_fd *bfd)
+{
+ struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data;
+ struct msgb *msg;
+ uint16_t len;
+ int rc;
+
+ /* Attempt to read from socket */
+ rc = read(bfd->fd, &len, L1CTL_MSG_LEN_FIELD);
+ if (rc < L1CTL_MSG_LEN_FIELD) {
+ LOGP(DL1D, LOGL_NOTICE, "L1CTL has lost connection\n");
+ if (rc >= 0)
+ rc = -EIO;
+ l1ctl_link_close_conn(l1l);
+ return rc;
+ }
+
+ /* Check message length */
+ len = ntohs(len);
+ if (len > L1CTL_LENGTH) {
+ LOGP(DL1D, LOGL_ERROR, "Length is too big: %u\n", len);
+ return -EINVAL;
+ }
+
+ /* Allocate a new msg */
+ msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM,
+ L1CTL_HEADROOM, "l1ctl_rx_msg");
+ if (!msg) {
+ LOGP(DL1D, LOGL_ERROR, "Failed to allocate msg\n");
+ return -ENOMEM;
+ }
+
+ msg->l1h = msgb_put(msg, len);
+ rc = read(bfd->fd, msg->l1h, msgb_l1len(msg));
+ if (rc != len) {
+ LOGP(DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: "
+ "%s\n", len, rc, strerror(errno));
+ msgb_free(msg);
+ return rc;
+ }
+
+ /* Debug print */
+ LOGP(DL1D, LOGL_DEBUG, "RX: '%s'\n",
+ osmo_hexdump(msg->data, msg->len));
+
+ /* Call L1CTL handler */
+ l1ctl_rx_cb(l1l, msg);
+
+ return 0;
+}
+
+static int l1ctl_link_write_cb(struct osmo_fd *bfd, struct msgb *msg)
+{
+ int len;
+
+ if (bfd->fd <= 0)
+ return -EINVAL;
+
+ len = write(bfd->fd, msg->data, msg->len);
+ if (len != msg->len) {
+ LOGP(DL1D, LOGL_ERROR, "Failed to write data: "
+ "written (%d) < msg_len (%d)\n", len, msg->len);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Connection handler */
+static int l1ctl_link_accept(struct osmo_fd *bfd, unsigned int flags)
+{
+ struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data;
+ struct osmo_fd *conn_bfd = &l1l->wq.bfd;
+ struct sockaddr_un un_addr;
+ socklen_t len;
+ int cfd;
+
+ len = sizeof(un_addr);
+ cfd = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
+ if (cfd < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to accept a new connection\n");
+ return -1;
+ }
+
+ /* Check if we already have an active connection */
+ if (conn_bfd->fd != -1) {
+ LOGP(DL1C, LOGL_NOTICE, "A new connection rejected: "
+ "we already have another active\n");
+ close(cfd);
+ return 0;
+ }
+
+ osmo_wqueue_init(&l1l->wq, 100);
+ INIT_LLIST_HEAD(&conn_bfd->list);
+
+ l1l->wq.write_cb = l1ctl_link_write_cb;
+ l1l->wq.read_cb = l1ctl_link_read_cb;
+ osmo_fd_setup(conn_bfd, cfd, OSMO_FD_READ, osmo_wqueue_bfd_cb, l1l, 0);
+
+ if (osmo_fd_register(conn_bfd) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to register new connection fd\n");
+ close(conn_bfd->fd);
+ conn_bfd->fd = -1;
+ return -1;
+ }
+
+ osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_CONNECT, l1l);
+ osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_CONNECTED, 0, 0);
+
+ LOGP(DL1C, LOGL_NOTICE, "L1CTL has a new connection\n");
+
+ return 0;
+}
+
+int l1ctl_link_send(struct l1ctl_link *l1l, struct msgb *msg)
+{
+ uint8_t *len;
+
+ /* Debug print */
+ LOGP(DL1D, LOGL_DEBUG, "TX: '%s'\n",
+ osmo_hexdump(msg->data, msg->len));
+
+ if (msg->l1h != msg->data)
+ LOGP(DL1D, LOGL_INFO, "Message L1 header != Message Data\n");
+
+ /* Prepend 16-bit length before sending */
+ len = msgb_push(msg, L1CTL_MSG_LEN_FIELD);
+ osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len);
+
+ if (osmo_wqueue_enqueue(&l1l->wq, msg) != 0) {
+ LOGP(DL1D, LOGL_ERROR, "Failed to enqueue msg!\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int l1ctl_link_close_conn(struct l1ctl_link *l1l)
+{
+ struct osmo_fd *conn_bfd = &l1l->wq.bfd;
+
+ if (conn_bfd->fd <= 0)
+ return -EINVAL;
+
+ /* Close connection socket */
+ osmo_fd_unregister(conn_bfd);
+ close(conn_bfd->fd);
+ conn_bfd->fd = -1;
+
+ /* Clear pending messages */
+ osmo_wqueue_clear(&l1l->wq);
+
+ osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_DISCONNECT, l1l);
+ osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_IDLE, 0, 0);
+
+ return 0;
+}
+
+struct l1ctl_link *l1ctl_link_init(void *tall_ctx, const char *sock_path)
+{
+ struct l1ctl_link *l1l;
+ struct osmo_fd *bfd;
+ int rc;
+
+ LOGP(DL1C, LOGL_NOTICE, "Init L1CTL link (%s)\n", sock_path);
+
+ l1l = talloc_zero(tall_ctx, struct l1ctl_link);
+ if (!l1l) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n");
+ return NULL;
+ }
+
+ /* Allocate a new dedicated state machine */
+ l1l->fsm = osmo_fsm_inst_alloc(&l1ctl_fsm, l1l,
+ NULL, LOGL_DEBUG, "l1ctl_link");
+ if (l1l->fsm == NULL) {
+ LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance "
+ "of FSM '%s'\n", l1ctl_fsm.name);
+ talloc_free(l1l);
+ return NULL;
+ }
+
+ /* Create a socket and bind handlers */
+ bfd = &l1l->listen_bfd;
+
+ /* Bind connection handler */
+ osmo_fd_setup(bfd, -1, OSMO_FD_READ, l1ctl_link_accept, l1l, 0);
+
+ rc = osmo_sock_unix_init_ofd(bfd, SOCK_STREAM, 0, sock_path,
+ OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n",
+ strerror(errno));
+ osmo_fsm_inst_free(l1l->fsm);
+ talloc_free(l1l);
+ return NULL;
+ }
+
+ /* Bind shutdown handler */
+ l1l->shutdown_cb = l1ctl_shutdown_cb;
+
+ /**
+ * To be able to accept first connection and
+ * drop others, it should be set to -1
+ */
+ l1l->wq.bfd.fd = -1;
+
+ return l1l;
+}
+
+void l1ctl_link_shutdown(struct l1ctl_link *l1l)
+{
+ struct osmo_fd *listen_bfd;
+
+ /* May be unallocated due to init error */
+ if (!l1l)
+ return;
+
+ LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL link\n");
+
+ /* Call shutdown callback */
+ if (l1l->shutdown_cb != NULL)
+ l1l->shutdown_cb(l1l);
+
+ listen_bfd = &l1l->listen_bfd;
+
+ /* Check if we have an established connection */
+ if (l1l->wq.bfd.fd != -1)
+ l1ctl_link_close_conn(l1l);
+
+ /* Unbind listening socket */
+ if (listen_bfd->fd != -1) {
+ osmo_fd_unregister(listen_bfd);
+ close(listen_bfd->fd);
+ listen_bfd->fd = -1;
+ }
+
+ osmo_fsm_inst_free(l1l->fsm);
+ talloc_free(l1l);
+}
+
+static __attribute__((constructor)) void on_dso_load(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&l1ctl_fsm) == 0);
+}
diff --git a/trxcon/l1ctl_link.h b/trxcon/l1ctl_link.h
new file mode 100644
index 0000000..a333e40
--- /dev/null
+++ b/trxcon/l1ctl_link.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/fsm.h>
+
+#define L1CTL_LENGTH 256
+#define L1CTL_HEADROOM 32
+
+/**
+ * Each L1CTL message gets its own length pushed
+ * as two bytes in front before sending.
+ */
+#define L1CTL_MSG_LEN_FIELD 2
+
+/* Forward declaration to avoid mutual include */
+struct trx_instance;
+
+enum l1ctl_fsm_states {
+ L1CTL_STATE_IDLE = 0,
+ L1CTL_STATE_CONNECTED,
+};
+
+struct l1ctl_link {
+ struct osmo_fsm_inst *fsm;
+ struct osmo_fd listen_bfd;
+ struct osmo_wqueue wq;
+
+ /* Bind TRX instance */
+ struct trx_instance *trx;
+
+ /* L1CTL handlers specific */
+ struct osmo_timer_list fbsb_timer;
+ bool fbsb_conf_sent;
+
+ /* Shutdown callback */
+ void (*shutdown_cb)(struct l1ctl_link *l1l);
+};
+
+struct l1ctl_link *l1ctl_link_init(void *tall_ctx, const char *sock_path);
+void l1ctl_link_shutdown(struct l1ctl_link *l1l);
+
+int l1ctl_link_send(struct l1ctl_link *l1l, struct msgb *msg);
+int l1ctl_link_close_conn(struct l1ctl_link *l1l);
diff --git a/trxcon/l1ctl_proto.h b/trxcon/l1ctl_proto.h
new file mode 100644
index 0000000..cf41ac7
--- /dev/null
+++ b/trxcon/l1ctl_proto.h
@@ -0,0 +1,387 @@
+/* Messages to be sent between the different layers */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef __L1CTL_PROTO_H__
+#define __L1CTL_PROTO_H__
+
+enum {
+ _L1CTL_NONE = 0,
+ L1CTL_FBSB_REQ,
+ L1CTL_FBSB_CONF,
+ L1CTL_DATA_IND,
+ L1CTL_RACH_REQ,
+ L1CTL_DM_EST_REQ,
+ L1CTL_DATA_REQ,
+ L1CTL_RESET_IND,
+ L1CTL_PM_REQ, /* power measurement */
+ L1CTL_PM_CONF, /* power measurement */
+ L1CTL_ECHO_REQ,
+ L1CTL_ECHO_CONF,
+ L1CTL_RACH_CONF,
+ L1CTL_RESET_REQ,
+ L1CTL_RESET_CONF,
+ L1CTL_DATA_CONF,
+ L1CTL_CCCH_MODE_REQ,
+ L1CTL_CCCH_MODE_CONF,
+ L1CTL_DM_REL_REQ,
+ L1CTL_PARAM_REQ,
+ L1CTL_DM_FREQ_REQ,
+ L1CTL_CRYPTO_REQ,
+ L1CTL_SIM_REQ,
+ L1CTL_SIM_CONF,
+ L1CTL_TCH_MODE_REQ,
+ L1CTL_TCH_MODE_CONF,
+ L1CTL_NEIGH_PM_REQ,
+ L1CTL_NEIGH_PM_IND,
+ L1CTL_TRAFFIC_REQ,
+ L1CTL_TRAFFIC_CONF,
+ L1CTL_TRAFFIC_IND,
+ L1CTL_BURST_IND,
+
+ /* configure TBF for uplink/downlink */
+ L1CTL_TBF_CFG_REQ,
+ L1CTL_TBF_CFG_CONF,
+
+ L1CTL_DATA_TBF_REQ,
+ L1CTL_DATA_TBF_CONF,
+
+ /* Extended (11-bit) RACH (see 3GPP TS 05.02, section 5.2.7) */
+ L1CTL_EXT_RACH_REQ,
+};
+
+enum ccch_mode {
+ CCCH_MODE_NONE = 0,
+ CCCH_MODE_NON_COMBINED,
+ CCCH_MODE_COMBINED,
+ CCCH_MODE_COMBINED_CBCH,
+};
+
+enum neigh_mode {
+ NEIGH_MODE_NONE = 0,
+ NEIGH_MODE_PM,
+ NEIGH_MODE_SB,
+};
+
+enum l1ctl_coding_scheme {
+ L1CTL_CS_NONE,
+ L1CTL_CS1,
+ L1CTL_CS2,
+ L1CTL_CS3,
+ L1CTL_CS4,
+ L1CTL_MCS1,
+ L1CTL_MCS2,
+ L1CTL_MCS3,
+ L1CTL_MCS4,
+ L1CTL_MCS5,
+ L1CTL_MCS6,
+ L1CTL_MCS7,
+ L1CTL_MCS8,
+ L1CTL_MCS9,
+};
+
+/*
+ * NOTE: struct size. We do add manual padding out of the believe
+ * that it will avoid some unaligned access.
+ */
+
+/* there are no more messages in a sequence */
+#define L1CTL_F_DONE 0x01
+
+struct l1ctl_hdr {
+ uint8_t msg_type;
+ uint8_t flags;
+ uint8_t padding[2];
+ uint8_t data[0];
+} __attribute__((packed));
+
+/*
+ * downlink info ... down from the BTS..
+ */
+struct l1ctl_info_dl {
+ /* GSM 08.58 channel number (9.3.1) */
+ uint8_t chan_nr;
+ /* GSM 08.58 link identifier (9.3.2) */
+ uint8_t link_id;
+ /* the ARFCN and the band. FIXME: what about MAIO? */
+ uint16_t band_arfcn;
+
+ uint32_t frame_nr;
+
+ uint8_t rx_level; /* 0 .. 63 in typical GSM notation (dBm+110) */
+ uint8_t snr; /* Signal/Noise Ration (dB) */
+ uint8_t num_biterr;
+ uint8_t fire_crc;
+
+ uint8_t payload[0];
+} __attribute__((packed));
+
+/* new CCCH was found. This is following the header */
+struct l1ctl_fbsb_conf {
+ int16_t initial_freq_err;
+ uint8_t result;
+ uint8_t bsic;
+ /* FIXME: contents of cell_info ? */
+} __attribute__((packed));
+
+/* CCCH mode was changed */
+struct l1ctl_ccch_mode_conf {
+ uint8_t ccch_mode; /* enum ccch_mode */
+ uint8_t padding[3];
+} __attribute__((packed));
+
+/* 3GPP TS 44.014, section 5.1 (Calypso specific numbers) */
+enum l1ctl_tch_loop_mode {
+ L1CTL_TCH_LOOP_OPEN = 0x00,
+ L1CTL_TCH_LOOP_A = 0x01,
+ L1CTL_TCH_LOOP_B = 0x02,
+ L1CTL_TCH_LOOP_C = 0x03,
+ L1CTL_TCH_LOOP_D = 0x04,
+ L1CTL_TCH_LOOP_E = 0x05,
+ L1CTL_TCH_LOOP_F = 0x06,
+ L1CTL_TCH_LOOP_I = 0x07,
+};
+
+/* TCH mode was changed */
+struct l1ctl_tch_mode_conf {
+ uint8_t tch_mode; /* enum tch_mode */
+ uint8_t audio_mode;
+ uint8_t tch_loop_mode; /* enum l1ctl_tch_loop_mode */
+ uint8_t padding[1];
+} __attribute__((packed));
+
+/* data on the CCCH was found. This is following the header */
+struct l1ctl_data_ind {
+ uint8_t data[23];
+} __attribute__((packed));
+
+/* traffic from the network */
+struct l1ctl_traffic_ind {
+ uint8_t data[0];
+} __attribute__((packed));
+
+/*
+ * uplink info
+ */
+struct l1ctl_info_ul {
+ /* GSM 08.58 channel number (9.3.1) */
+ uint8_t chan_nr;
+ /* GSM 08.58 link identifier (9.3.2) */
+ uint8_t link_id;
+ uint8_t padding[2];
+
+ uint8_t payload[0];
+} __attribute__((packed));
+
+struct l1ctl_info_ul_tbf {
+ /* references l1ctl_tbf_cfg_req.tbf_nr */
+ uint8_t tbf_nr;
+ uint8_t coding_scheme;
+ uint8_t padding[2];
+ /* RLC/MAC block, size determines CS */
+ uint8_t payload[0];
+} __attribute__((packed));
+
+/*
+ * msg for FBSB_REQ
+ * the l1_info_ul header is in front
+ */
+struct l1ctl_fbsb_req {
+ uint16_t band_arfcn;
+ uint16_t timeout; /* in TDMA frames */
+
+ uint16_t freq_err_thresh1;
+ uint16_t freq_err_thresh2;
+
+ uint8_t num_freqerr_avg;
+ uint8_t flags; /* L1CTL_FBSB_F_* */
+ uint8_t sync_info_idx;
+ uint8_t ccch_mode; /* enum ccch_mode */
+ uint8_t rxlev_exp; /* expected signal level */
+} __attribute__((packed));
+
+#define L1CTL_FBSB_F_FB0 (1 << 0)
+#define L1CTL_FBSB_F_FB1 (1 << 1)
+#define L1CTL_FBSB_F_SB (1 << 2)
+#define L1CTL_FBSB_F_FB01SB (L1CTL_FBSB_F_FB0|L1CTL_FBSB_F_FB1|L1CTL_FBSB_F_SB)
+
+/*
+ * msg for CCCH_MODE_REQ
+ * the l1_info_ul header is in front
+ */
+struct l1ctl_ccch_mode_req {
+ uint8_t ccch_mode; /* enum ccch_mode */
+ uint8_t padding[3];
+} __attribute__((packed));
+
+/*
+ * msg for TCH_MODE_REQ
+ * the l1_info_ul header is in front
+ */
+struct l1ctl_tch_mode_req {
+ uint8_t tch_mode; /* enum gsm48_chan_mode */
+#define AUDIO_TX_MICROPHONE (1<<0)
+#define AUDIO_TX_TRAFFIC_REQ (1<<1)
+#define AUDIO_RX_SPEAKER (1<<2)
+#define AUDIO_RX_TRAFFIC_IND (1<<3)
+ uint8_t audio_mode;
+ uint8_t tch_loop_mode; /* enum l1ctl_tch_loop_mode */
+ uint8_t padding[1];
+} __attribute__((packed));
+
+/* the l1_info_ul header is in front */
+struct l1ctl_rach_req {
+ uint8_t ra;
+ uint8_t combined;
+ uint16_t offset;
+} __attribute__((packed));
+
+
+/* the l1_info_ul header is in front */
+struct l1ctl_ext_rach_req {
+ uint16_t ra11;
+ uint8_t synch_seq;
+ uint8_t combined;
+ uint16_t offset;
+} __attribute__((packed));
+
+/* the l1_info_ul header is in front */
+struct l1ctl_par_req {
+ int8_t ta;
+ uint8_t tx_power;
+ uint8_t padding[2];
+} __attribute__((packed));
+
+struct l1ctl_h0 {
+ uint16_t band_arfcn;
+} __attribute__((packed));
+
+struct l1ctl_h1 {
+ uint8_t hsn;
+ uint8_t maio;
+ uint8_t n;
+ uint8_t _padding[1];
+ uint16_t ma[64];
+} __attribute__((packed));
+
+struct l1ctl_dm_est_req {
+ uint8_t tsc;
+ uint8_t h;
+ union {
+ struct l1ctl_h0 h0;
+ struct l1ctl_h1 h1;
+ };
+ uint8_t tch_mode;
+ uint8_t audio_mode;
+} __attribute__((packed));
+
+struct l1ctl_dm_freq_req {
+ uint16_t fn;
+ uint8_t tsc;
+ uint8_t h;
+ union {
+ struct l1ctl_h0 h0;
+ struct l1ctl_h1 h1;
+ };
+} __attribute__((packed));
+
+struct l1ctl_crypto_req {
+ uint8_t algo;
+ uint8_t key_len;
+ uint8_t key[0];
+} __attribute__((packed));
+
+struct l1ctl_pm_req {
+ uint8_t type;
+ uint8_t padding[3];
+
+ union {
+ struct {
+ uint16_t band_arfcn_from;
+ uint16_t band_arfcn_to;
+ } range;
+ };
+} __attribute__((packed));
+
+#define BI_FLG_DUMMY (1 << 4)
+#define BI_FLG_SACCH (1 << 5)
+
+struct l1ctl_burst_ind {
+ uint32_t frame_nr;
+ uint16_t band_arfcn; /* ARFCN + band + ul indicator */
+ uint8_t chan_nr; /* GSM 08.58 channel number (9.3.1) */
+ uint8_t flags; /* BI_FLG_xxx + burst_id = 2LSBs */
+ uint8_t rx_level; /* 0 .. 63 in typical GSM notation (dBm+110) */
+ uint8_t snr; /* Reported SNR >> 8 (0-255) */
+ uint8_t bits[15]; /* 114 bits + 2 steal bits. Filled MSB first */
+} __attribute__((packed));
+
+/* a single L1CTL_PM response */
+struct l1ctl_pm_conf {
+ uint16_t band_arfcn;
+ uint8_t pm[2];
+} __attribute__((packed));
+
+enum l1ctl_reset_type {
+ L1CTL_RES_T_BOOT, /* only _IND */
+ L1CTL_RES_T_FULL,
+ L1CTL_RES_T_SCHED,
+};
+
+/* argument to L1CTL_RESET_REQ and L1CTL_RESET_IND */
+struct l1ctl_reset {
+ uint8_t type;
+ uint8_t pad[3];
+} __attribute__((packed));
+
+struct l1ctl_neigh_pm_req {
+ uint8_t n;
+ uint8_t padding[1];
+ uint16_t band_arfcn[64];
+ uint8_t tn[64];
+} __attribute__((packed));
+
+/* neighbour cell measurement results */
+struct l1ctl_neigh_pm_ind {
+ uint16_t band_arfcn;
+ uint8_t pm[2];
+ uint8_t tn;
+ uint8_t padding;
+} __attribute__((packed));
+
+/* traffic data to network */
+struct l1ctl_traffic_req {
+ uint8_t data[0];
+} __attribute__((packed));
+
+struct l1ctl_tbf_cfg_req {
+ /* future support for multiple concurrent TBFs. 0 for now */
+ uint8_t tbf_nr;
+ /* is this about an UL TBF (1) or DL (0) */
+ uint8_t is_uplink;
+ uint8_t padding[2];
+
+ /* one USF for each TN, or 255 for invalid/unused */
+ uint8_t usf[8];
+} __attribute__((packed));
+
+#endif /* __L1CTL_PROTO_H__ */
diff --git a/trxcon/logging.c b/trxcon/logging.c
new file mode 100644
index 0000000..78915f2
--- /dev/null
+++ b/trxcon/logging.c
@@ -0,0 +1,88 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ *
+ * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+
+#include "logging.h"
+
+static struct log_info_cat trx_log_info_cat[] = {
+ [DAPP] = {
+ .name = "DAPP",
+ .description = "Application",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DL1C] = {
+ .name = "DL1C",
+ .description = "Layer 1 control interface",
+ .color = "\033[1;31m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DL1D] = {
+ .name = "DL1D",
+ .description = "Layer 1 data",
+ .color = "\033[1;31m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DTRX] = {
+ .name = "DTRX",
+ .description = "Transceiver control interface",
+ .color = "\033[1;33m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DTRXD] = {
+ .name = "DTRXD",
+ .description = "Transceiver data interface",
+ .color = "\033[1;33m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DSCH] = {
+ .name = "DSCH",
+ .description = "Scheduler management",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DSCHD] = {
+ .name = "DSCHD",
+ .description = "Scheduler data",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+};
+
+static const struct log_info trx_log_info = {
+ .cat = trx_log_info_cat,
+ .num_cat = ARRAY_SIZE(trx_log_info_cat),
+};
+
+int trx_log_init(void *tall_ctx, const char *category_mask)
+{
+ osmo_init_logging2(tall_ctx, &trx_log_info);
+
+ if (category_mask)
+ log_parse_category_mask(osmo_stderr_target, category_mask);
+
+ return 0;
+}
diff --git a/trxcon/logging.h b/trxcon/logging.h
new file mode 100644
index 0000000..152c346
--- /dev/null
+++ b/trxcon/logging.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <osmocom/core/logging.h>
+
+#define DEBUG_DEFAULT "DAPP:DL1C:DL1D:DTRX:DTRXD:DSCH:DSCHD"
+
+enum {
+ DAPP,
+ DL1C,
+ DL1D,
+ DTRX,
+ DTRXD,
+ DSCH,
+ DSCHD,
+};
+
+int trx_log_init(void *tall_ctx, const char *category_mask);
diff --git a/trxcon/sched_clck.c b/trxcon/sched_clck.c
new file mode 100644
index 0000000..9476ccd
--- /dev/null
+++ b/trxcon/sched_clck.c
@@ -0,0 +1,206 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * TDMA scheduler: clock synchronization
+ *
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * (C) 2015 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/timer_compat.h>
+#include <osmocom/gsm/a5.h>
+
+#include "scheduler.h"
+#include "logging.h"
+#include "trx_if.h"
+
+#define MAX_FN_SKEW 50
+#define TRX_LOSS_FRAMES 400
+
+static void sched_clck_tick(void *data)
+{
+ struct trx_sched *sched = (struct trx_sched *) data;
+ struct timespec tv_now, *tv_clock, elapsed;
+ int64_t elapsed_us;
+ const struct timespec frame_duration = { .tv_sec = 0, .tv_nsec = GSM_TDMA_FN_DURATION_nS };
+
+ /* Check if transceiver is still alive */
+ if (sched->fn_counter_lost++ == TRX_LOSS_FRAMES) {
+ LOGP(DSCH, LOGL_DEBUG, "No more clock from transceiver\n");
+ sched->state = SCH_CLCK_STATE_WAIT;
+
+ return;
+ }
+
+ /* Get actual / previous frame time */
+ osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now);
+ tv_clock = &sched->clock;
+
+ timespecsub(&tv_now, tv_clock, &elapsed);
+ elapsed_us = (elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000);
+
+ /* If someone played with clock, or if the process stalled */
+ if (elapsed_us > GSM_TDMA_FN_DURATION_uS * MAX_FN_SKEW || elapsed_us < 0) {
+ LOGP(DSCH, LOGL_NOTICE, "PC clock skew: "
+ "elapsed uS %" PRId64 "\n", elapsed_us);
+
+ sched->state = SCH_CLCK_STATE_WAIT;
+
+ return;
+ }
+
+ /* Schedule next FN clock */
+ while (elapsed_us > GSM_TDMA_FN_DURATION_uS / 2) {
+ timespecadd(tv_clock, &frame_duration, tv_clock);
+ elapsed_us -= GSM_TDMA_FN_DURATION_uS;
+
+ GSM_TDMA_FN_INC(sched->fn_counter_proc);
+
+ /* Call frame callback */
+ if (sched->clock_cb)
+ sched->clock_cb(sched);
+ }
+
+ osmo_timer_schedule(&sched->clock_timer, 0,
+ GSM_TDMA_FN_DURATION_uS - elapsed_us);
+}
+
+static void sched_clck_correct(struct trx_sched *sched,
+ struct timespec *tv_now, uint32_t fn)
+{
+ sched->fn_counter_proc = fn;
+
+ /* Call frame callback */
+ if (sched->clock_cb)
+ sched->clock_cb(sched);
+
+ /* Schedule first FN clock */
+ sched->clock = *tv_now;
+ memset(&sched->clock_timer, 0, sizeof(sched->clock_timer));
+
+ sched->clock_timer.cb = sched_clck_tick;
+ sched->clock_timer.data = sched;
+ osmo_timer_schedule(&sched->clock_timer, 0, GSM_TDMA_FN_DURATION_uS);
+}
+
+int sched_clck_handle(struct trx_sched *sched, uint32_t fn)
+{
+ struct timespec tv_now, *tv_clock, elapsed;
+ int64_t elapsed_us, elapsed_fn;
+
+ /* Reset lost counter */
+ sched->fn_counter_lost = 0;
+
+ /* Get actual / previous frame time */
+ osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now);
+ tv_clock = &sched->clock;
+
+ /* If this is the first CLCK IND */
+ if (sched->state == SCH_CLCK_STATE_WAIT) {
+ sched_clck_correct(sched, &tv_now, fn);
+
+ LOGP(DSCH, LOGL_DEBUG, "Initial clock received: fn=%u\n", fn);
+ sched->state = SCH_CLCK_STATE_OK;
+
+ return 0;
+ }
+
+ LOGP(DSCH, LOGL_NOTICE, "Clock indication: fn=%u\n", fn);
+
+ osmo_timer_del(&sched->clock_timer);
+
+ /* Calculate elapsed time / frames since last processed fn */
+ timespecsub(&tv_now, tv_clock, &elapsed);
+ elapsed_us = (elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000);
+ elapsed_fn = GSM_TDMA_FN_SUB(fn, sched->fn_counter_proc);
+
+ if (elapsed_fn >= 135774)
+ elapsed_fn -= GSM_TDMA_HYPERFRAME;
+
+ /* Check for max clock skew */
+ if (elapsed_fn > MAX_FN_SKEW || elapsed_fn < -MAX_FN_SKEW) {
+ LOGP(DSCH, LOGL_NOTICE, "GSM clock skew: old fn=%u, "
+ "new fn=%u\n", sched->fn_counter_proc, fn);
+
+ sched_clck_correct(sched, &tv_now, fn);
+ return 0;
+ }
+
+ LOGP(DSCH, LOGL_INFO, "GSM clock jitter: %" PRId64 "\n",
+ elapsed_fn * GSM_TDMA_FN_DURATION_uS - elapsed_us);
+
+ /* Too many frames have been processed already */
+ if (elapsed_fn < 0) {
+ struct timespec duration;
+ /**
+ * Set clock to the time or last FN should
+ * have been transmitted
+ */
+ duration.tv_nsec = (0 - elapsed_fn) * GSM_TDMA_FN_DURATION_nS;
+ duration.tv_sec = duration.tv_nsec / 1000000000;
+ duration.tv_nsec = duration.tv_nsec % 1000000000;
+ timespecadd(&tv_now, &duration, tv_clock);
+
+ /* Set time to the time our next FN has to be transmitted */
+ osmo_timer_schedule(&sched->clock_timer, 0,
+ GSM_TDMA_FN_DURATION_uS * (1 - elapsed_fn));
+
+ return 0;
+ }
+
+ /* Transmit what we still need to transmit */
+ while (fn != sched->fn_counter_proc) {
+ GSM_TDMA_FN_INC(sched->fn_counter_proc);
+
+ /* Call frame callback */
+ if (sched->clock_cb)
+ sched->clock_cb(sched);
+ }
+
+ /* Schedule next FN to be transmitted */
+ *tv_clock = tv_now;
+ osmo_timer_schedule(&sched->clock_timer, 0, GSM_TDMA_FN_DURATION_uS);
+
+ return 0;
+}
+
+void sched_clck_reset(struct trx_sched *sched)
+{
+ /* Reset internal state */
+ sched->state = SCH_CLCK_STATE_WAIT;
+
+ /* Stop clock timer */
+ osmo_timer_del(&sched->clock_timer);
+
+ /* Flush counters */
+ sched->fn_counter_proc = 0;
+ sched->fn_counter_lost = 0;
+}
diff --git a/trxcon/sched_lchan_common.c b/trxcon/sched_lchan_common.c
new file mode 100644
index 0000000..ae43ca9
--- /dev/null
+++ b/trxcon/sched_lchan_common.c
@@ -0,0 +1,232 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * TDMA scheduler: common routines for lchan handlers
+ *
+ * (C) 2017-2020 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <talloc.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/gsmtap.h>
+
+#include <osmocom/codec/codec.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+
+#include "l1ctl_proto.h"
+#include "scheduler.h"
+#include "sched_trx.h"
+#include "logging.h"
+#include "trxcon.h"
+#include "trx_if.h"
+#include "l1ctl.h"
+
+/* GSM 05.02 Chapter 5.2.3 Normal Burst (NB) */
+const uint8_t sched_nb_training_bits[8][26] = {
+ {
+ 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0,
+ 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1,
+ },
+ {
+ 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1,
+ 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1,
+ },
+ {
+ 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1,
+ 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0,
+ },
+ {
+ 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0,
+ 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0,
+ },
+ {
+ 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0,
+ 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1,
+ },
+ {
+ 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0,
+ 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0,
+ },
+ {
+ 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1,
+ 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1,
+ },
+ {
+ 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0,
+ 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0,
+ },
+};
+
+/* Get a string representation of the burst buffer's completeness.
+ * Examples: " ****.." (incomplete, 4/6 bursts)
+ * " ****" (complete, all 4 bursts)
+ * "**.***.." (incomplete, 5/8 bursts) */
+const char *burst_mask2str(const uint8_t *mask, int bits)
+{
+ /* TODO: CSD is interleaved over 22 bursts, so the mask needs to be extended */
+ static char buf[8 + 1];
+ char *ptr = buf;
+
+ OSMO_ASSERT(bits <= 8 && bits > 0);
+
+ while (--bits >= 0)
+ *(ptr++) = (*mask & (1 << bits)) ? '*' : '.';
+ *ptr = '\0';
+
+ return buf;
+}
+
+int sched_gsmtap_send(enum trx_lchan_type lchan_type, uint32_t fn, uint8_t tn,
+ uint16_t band_arfcn, int8_t signal_dbm, uint8_t snr,
+ const uint8_t *data, size_t data_len)
+{
+ const struct trx_lchan_desc *lchan_desc = &trx_lchan_desc[lchan_type];
+
+ /* GSMTAP logging may not be enabled */
+ if (gsmtap == NULL)
+ return 0;
+
+ /* Omit frames with unknown channel type */
+ if (lchan_desc->gsmtap_chan_type == GSMTAP_CHANNEL_UNKNOWN)
+ return 0;
+
+ /* TODO: distinguish GSMTAP_CHANNEL_PCH and GSMTAP_CHANNEL_AGCH */
+ return gsmtap_send(gsmtap, band_arfcn, tn, lchan_desc->gsmtap_chan_type,
+ lchan_desc->ss_nr, fn, signal_dbm, snr, data, data_len);
+}
+
+int sched_send_dt_ind(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint8_t *l2, size_t l2_len,
+ int bit_error_count, bool dec_failed, bool traffic)
+{
+ const struct trx_meas_set *meas = &lchan->meas_avg;
+ const struct trx_lchan_desc *lchan_desc;
+ struct l1ctl_info_dl dl_hdr;
+
+ /* Set up pointers */
+ lchan_desc = &trx_lchan_desc[lchan->type];
+
+ /* Fill in known downlink info */
+ dl_hdr.chan_nr = lchan_desc->chan_nr | ts->index;
+ dl_hdr.link_id = lchan_desc->link_id;
+ dl_hdr.band_arfcn = htons(trx->band_arfcn);
+ dl_hdr.num_biterr = bit_error_count;
+
+ /* sched_trx_meas_avg() gives us TDMA frame number of the first burst */
+ dl_hdr.frame_nr = htonl(meas->fn);
+
+ /* RX level: 0 .. 63 in typical GSM notation (dBm + 110) */
+ dl_hdr.rx_level = dbm2rxlev(meas->rssi);
+
+ /* FIXME: set proper values */
+ dl_hdr.snr = 0;
+
+ /* Mark frame as broken if so */
+ dl_hdr.fire_crc = dec_failed ? 2 : 0;
+
+ /* Put a packet to higher layers */
+ l1ctl_tx_dt_ind(trx->l1l, &dl_hdr, l2, l2_len, traffic);
+
+ /* Optional GSMTAP logging */
+ if (l2_len > 0 && (!traffic || lchan_desc->chan_nr == RSL_CHAN_OSMO_PDCH)) {
+ sched_gsmtap_send(lchan->type, meas->fn, ts->index,
+ trx->band_arfcn, meas->rssi, 0, l2, l2_len);
+ }
+
+ return 0;
+}
+
+int sched_send_dt_conf(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, bool traffic)
+{
+ const struct trx_lchan_desc *lchan_desc;
+ struct l1ctl_info_dl dl_hdr;
+
+ /* Set up pointers */
+ lchan_desc = &trx_lchan_desc[lchan->type];
+
+ /* Zero-initialize DL header, because we don't set all fields */
+ memset(&dl_hdr, 0x00, sizeof(struct l1ctl_info_dl));
+
+ /* Fill in known downlink info */
+ dl_hdr.chan_nr = lchan_desc->chan_nr | ts->index;
+ dl_hdr.link_id = lchan_desc->link_id;
+ dl_hdr.band_arfcn = htons(trx->band_arfcn);
+ dl_hdr.frame_nr = htonl(fn);
+
+ l1ctl_tx_dt_conf(trx->l1l, &dl_hdr, traffic);
+
+ /* Optional GSMTAP logging */
+ if (!traffic || lchan_desc->chan_nr == RSL_CHAN_OSMO_PDCH) {
+ sched_gsmtap_send(lchan->type, fn, ts->index,
+ trx->band_arfcn | ARFCN_UPLINK,
+ 0, 0, lchan->prim->payload,
+ lchan->prim->payload_len);
+ }
+
+ return 0;
+}
+
+/**
+ * Composes a bad frame indication message
+ * according to the current tch_mode.
+ *
+ * @param l2 Caller-allocated byte array
+ * @param lchan Logical channel to generate BFI for
+ * @return How much bytes were written
+ */
+size_t sched_bad_frame_ind(uint8_t *l2, struct trx_lchan_state *lchan)
+{
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == TRXC_TCHF) { /* Full Rate */
+ memset(l2, 0x00, GSM_FR_BYTES);
+ l2[0] = 0xd0;
+ return GSM_FR_BYTES;
+ } else { /* Half Rate */
+ memset(l2 + 1, 0x00, GSM_HR_BYTES);
+ l2[0] = 0x70; /* F = 0, FT = 111 */
+ return GSM_HR_BYTES + 1;
+ }
+ case GSM48_CMODE_SPEECH_EFR: /* Enhanced Full Rate */
+ memset(l2, 0x00, GSM_EFR_BYTES);
+ l2[0] = 0xc0;
+ return GSM_EFR_BYTES;
+ case GSM48_CMODE_SPEECH_AMR: /* Adaptive Multi Rate */
+ /* FIXME: AMR is not implemented yet */
+ return 0;
+ case GSM48_CMODE_SIGN:
+ LOGP(DSCH, LOGL_ERROR, "BFI is not allowed in signalling mode\n");
+ return 0;
+ default:
+ LOGP(DSCH, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
+ return 0;
+ }
+}
diff --git a/trxcon/sched_lchan_desc.c b/trxcon/sched_lchan_desc.c
new file mode 100644
index 0000000..2c3c3b2
--- /dev/null
+++ b/trxcon/sched_lchan_desc.c
@@ -0,0 +1,622 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * TDMA scheduler: logical channels, RX / TX handlers
+ *
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * (C) 2015 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/core/gsmtap.h>
+
+#include "sched_trx.h"
+
+/* Forward declaration of handlers */
+int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
+ const sbit_t *bits, const struct trx_meas_set *meas);
+
+int tx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
+
+int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
+ const sbit_t *bits, const struct trx_meas_set *meas);
+
+int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
+
+int rx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
+ const sbit_t *bits, const struct trx_meas_set *meas);
+
+int tx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
+
+int rx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
+ const sbit_t *bits, const struct trx_meas_set *meas);
+
+int tx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
+
+int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
+ const sbit_t *bits, const struct trx_meas_set *meas);
+
+int tx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
+
+const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = {
+ [TRXC_IDLE] = {
+ .name = "IDLE",
+ .desc = "Idle channel",
+ /* The MS needs to perform neighbour measurements during
+ * IDLE slots, however this is not implemented (yet). */
+ },
+ [TRXC_FCCH] = {
+ .name = "FCCH", /* 3GPP TS 05.02, section 3.3.2.1 */
+ .desc = "Frequency correction channel",
+ /* Handled by transceiver, nothing to do. */
+ },
+ [TRXC_SCH] = {
+ .name = "SCH", /* 3GPP TS 05.02, section 3.3.2.2 */
+ .desc = "Synchronization channel",
+
+ /* 3GPP TS 05.03, section 4.7. Handled by transceiver,
+ * however we still need to parse BSIC (BCC / NCC). */
+ .flags = TRX_CH_FLAG_AUTO,
+ .rx_fn = rx_sch_fn,
+ },
+ [TRXC_BCCH] = {
+ .name = "BCCH", /* 3GPP TS 05.02, section 3.3.2.3 */
+ .desc = "Broadcast control channel",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_BCCH,
+ .chan_nr = RSL_CHAN_BCCH,
+
+ /* Rx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4),
+ * regular interleaving (3GPP TS 05.02, clause 7, table 3):
+ * a L2 frame is interleaved over 4 consecutive bursts. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_AUTO,
+ .rx_fn = rx_data_fn,
+ },
+ [TRXC_RACH] = {
+ .name = "RACH", /* 3GPP TS 05.02, section 3.3.3.1 */
+ .desc = "Random access channel",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_RACH,
+ .chan_nr = RSL_CHAN_RACH,
+
+ /* Tx only, RACH convolutional coding (3GPP TS 05.03, section 4.6). */
+ .flags = TRX_CH_FLAG_AUTO,
+ .tx_fn = tx_rach_fn,
+ },
+ [TRXC_CCCH] = {
+ .name = "CCCH", /* 3GPP TS 05.02, section 3.3.3.1 */
+ .desc = "Common control channel",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_CCCH,
+ .chan_nr = RSL_CHAN_PCH_AGCH,
+
+ /* Rx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4),
+ * regular interleaving (3GPP TS 05.02, clause 7, table 3):
+ * a L2 frame is interleaved over 4 consecutive bursts. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_AUTO,
+ .rx_fn = rx_data_fn,
+ },
+ [TRXC_TCHF] = {
+ .name = "TCH/F", /* 3GPP TS 05.02, section 3.2 */
+ .desc = "Full Rate traffic channel",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_TCH_F,
+ .chan_nr = RSL_CHAN_Bm_ACCHs,
+ .link_id = TRX_CH_LID_DEDIC,
+
+ /* Rx and Tx, multiple convolutional coding types (3GPP TS 05.03,
+ * chapter 3), block diagonal interleaving (3GPP TS 05.02, clause 7):
+ *
+ * - a traffic frame is interleaved over 8 consecutive bursts
+ * using the even numbered bits of the first 4 bursts
+ * and odd numbered bits of the last 4 bursts;
+ * - a FACCH/F frame 'steals' (replaces) one traffic frame,
+ * interleaving is done in the same way.
+ *
+ * The MS shall continuously transmit bursts, even if there is nothing
+ * to send, unless DTX (Discontinuous Transmission) is used. */
+ .burst_buf_size = 8 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_tchf_fn,
+ .tx_fn = tx_tchf_fn,
+ },
+ [TRXC_TCHH_0] = {
+ .name = "TCH/H(0)", /* 3GPP TS 05.02, section 3.2 */
+ .desc = "Half Rate traffic channel (sub-channel 0)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H,
+ .chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 0,
+
+ /* Rx and Tx, multiple convolutional coding types (3GPP TS 05.03,
+ * chapter 3), block diagonal interleaving (3GPP TS 05.02, clause 7):
+ *
+ * - a traffic frame is interleaved over 4 non-consecutive bursts
+ * using the even numbered bits of the first 2 bursts,
+ * and odd numbered bits of the last 2 bursts;
+ * - a FACCH/H frame is interleaved over 6 non-consecutive bursts
+ * using the even numbered bits of the first 2 bursts,
+ * all bits of the middle two 2 bursts,
+ * and odd numbered bits of the last 2 bursts;
+ * - a FACCH/H frame 'steals' (replaces) two traffic frames,
+ * interleaving is done over 4 consecutive bursts,
+ * the same as given for a TCH/FS.
+ *
+ * The MS shall continuously transmit bursts, even if there is nothing
+ * to send, unless DTX (Discontinuous Transmission) is used. */
+ .burst_buf_size = 6 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_tchh_fn,
+ .tx_fn = tx_tchh_fn,
+ },
+ [TRXC_TCHH_1] = {
+ .name = "TCH/H(1)", /* 3GPP TS 05.02, section 3.2 */
+ .desc = "Half Rate traffic channel (sub-channel 1)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H,
+ .chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 1,
+
+ /* Same as for TRXC_TCHH_0, see above. */
+ .burst_buf_size = 6 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_tchh_fn,
+ .tx_fn = tx_tchh_fn,
+ },
+ [TRXC_SDCCH4_0] = {
+ .name = "SDCCH/4(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Stand-alone dedicated control channel (sub-channel 0)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
+ .chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 0,
+
+ /* Same as for TRXC_BCCH (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SDCCH4_1] = {
+ .name = "SDCCH/4(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Stand-alone dedicated control channel (sub-channel 1)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
+ .chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 1,
+
+ /* Same as for TRXC_BCCH (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SDCCH4_2] = {
+ .name = "SDCCH/4(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Stand-alone dedicated control channel (sub-channel 2)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
+ .chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 2,
+
+ /* Same as for TRXC_BCCH (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SDCCH4_3] = {
+ .name = "SDCCH/4(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Stand-alone dedicated control channel (sub-channel 3)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
+ .chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 3,
+
+ /* Same as for TRXC_BCCH (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SDCCH8_0] = {
+ .name = "SDCCH/8(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Stand-alone dedicated control channel (sub-channel 0)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 0,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SDCCH8_1] = {
+ .name = "SDCCH/8(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Stand-alone dedicated control channel (sub-channel 1)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 1,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SDCCH8_2] = {
+ .name = "SDCCH/8(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Stand-alone dedicated control channel (sub-channel 2)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 2,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SDCCH8_3] = {
+ .name = "SDCCH/8(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Stand-alone dedicated control channel (sub-channel 3)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 3,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SDCCH8_4] = {
+ .name = "SDCCH/8(4)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Stand-alone dedicated control channel (sub-channel 4)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 4,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SDCCH8_5] = {
+ .name = "SDCCH/8(5)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Stand-alone dedicated control channel (sub-channel 5)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 5,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SDCCH8_6] = {
+ .name = "SDCCH/8(6)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Stand-alone dedicated control channel (sub-channel 6)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 6,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SDCCH8_7] = {
+ .name = "SDCCH/8(7)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Stand-alone dedicated control channel (sub-channel 7)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3),
+ .link_id = TRX_CH_LID_DEDIC,
+ .ss_nr = 7,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCHTF] = {
+ .name = "SACCH/TF", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow TCH/F associated control channel",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_TCH_F | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_Bm_ACCHs,
+ .link_id = TRX_CH_LID_SACCH,
+
+ /* Same as for TRXC_BCCH (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCHTH_0] = {
+ .name = "SACCH/TH(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow TCH/H associated control channel (sub-channel 0)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 0,
+
+ /* Same as for TRXC_BCCH (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCHTH_1] = {
+ .name = "SACCH/TH(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow TCH/H associated control channel (sub-channel 1)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 1,
+
+ /* Same as for TRXC_BCCH (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCH4_0] = {
+ .name = "SACCH/4(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow SDCCH/4 associated control channel (sub-channel 0)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 0,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCH4_1] = {
+ .name = "SACCH/4(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow SDCCH/4 associated control channel (sub-channel 1)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 1,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCH4_2] = {
+ .name = "SACCH/4(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow SDCCH/4 associated control channel (sub-channel 2)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 2,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCH4_3] = {
+ .name = "SACCH/4(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow SDCCH/4 associated control channel (sub-channel 3)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 3,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCH8_0] = {
+ .name = "SACCH/8(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow SDCCH/8 associated control channel (sub-channel 0)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 0,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCH8_1] = {
+ .name = "SACCH/8(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow SDCCH/8 associated control channel (sub-channel 1)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 1,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCH8_2] = {
+ .name = "SACCH/8(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow SDCCH/8 associated control channel (sub-channel 2)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 2,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCH8_3] = {
+ .name = "SACCH/8(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow SDCCH/8 associated control channel (sub-channel 3)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 3,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCH8_4] = {
+ .name = "SACCH/8(4)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow SDCCH/8 associated control channel (sub-channel 4)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 4,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCH8_5] = {
+ .name = "SACCH/8(5)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow SDCCH/8 associated control channel (sub-channel 5)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 5,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCH8_6] = {
+ .name = "SACCH/8(6)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow SDCCH/8 associated control channel (sub-channel 6)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 6,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_SACCH8_7] = {
+ .name = "SACCH/8(7)", /* 3GPP TS 05.02, section 3.3.4.1 */
+ .desc = "Slow SDCCH/8 associated control channel (sub-channel 7)",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
+ .chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3),
+ .link_id = TRX_CH_LID_SACCH,
+ .ss_nr = 7,
+
+ /* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_CBTX,
+ .rx_fn = rx_data_fn,
+ .tx_fn = tx_data_fn,
+ },
+ [TRXC_PDTCH] = {
+ .name = "PDTCH", /* 3GPP TS 05.02, sections 3.2.4, 3.3.2.4 */
+ .desc = "Packet data traffic & control channel",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_PDTCH,
+ .chan_nr = RSL_CHAN_OSMO_PDCH,
+
+ /* Rx and Tx, multiple coding schemes: CS-1..4 and MCS-1..9 (3GPP TS
+ * 05.03, chapter 5), regular interleaving as specified for xCCH.
+ * NOTE: the burst buffer is three times bigger because the
+ * payload of EDGE bursts is three times longer. */
+ .burst_buf_size = 3 * 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_PDCH,
+ .rx_fn = rx_pdtch_fn,
+ .tx_fn = tx_pdtch_fn,
+ },
+ [TRXC_PTCCH] = {
+ .name = "PTCCH", /* 3GPP TS 05.02, section 3.3.4.2 */
+ .desc = "Packet Timing advance control channel",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_PTCCH,
+ .chan_nr = RSL_CHAN_OSMO_PDCH,
+ .link_id = TRX_CH_LID_PTCCH,
+
+ /* On the Uplink, mobile stations transmit random Access Bursts
+ * to allow estimation of the timing advance for one MS in packet
+ * transfer mode. On Downlink, the network sends timing advance
+ * updates for several mobile stations. The coding scheme used
+ * for PTCCH/D messages is the same as for PDTCH CS-1. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_PDCH,
+ .rx_fn = rx_pdtch_fn,
+ .tx_fn = tx_rach_fn,
+ },
+ [TRXC_SDCCH4_CBCH] = {
+ .name = "SDCCH/4(CBCH)", /* 3GPP TS 05.02, section 3.3.5 */
+ .desc = "Cell Broadcast channel on SDCCH/4",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_CBCH51,
+ .chan_nr = RSL_CHAN_OSMO_CBCH4,
+ .ss_nr = 2,
+
+ /* Same as for TRXC_BCCH (xCCH), but Rx only. See above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .flags = TRX_CH_FLAG_AUTO,
+ .rx_fn = rx_data_fn,
+ },
+ [TRXC_SDCCH8_CBCH] = {
+ .name = "SDCCH/8(CBCH)", /* 3GPP TS 05.02, section 3.3.5 */
+ .desc = "Cell Broadcast channel on SDCCH/8",
+ .gsmtap_chan_type = GSMTAP_CHANNEL_CBCH52,
+ .chan_nr = RSL_CHAN_OSMO_CBCH8,
+ .ss_nr = 2,
+
+ /* Same as for TRXC_BCCH (xCCH), but Rx only. See above. */
+ .burst_buf_size = 4 * GSM_BURST_PL_LEN,
+ .rx_fn = rx_data_fn,
+ },
+};
diff --git a/trxcon/sched_lchan_pdtch.c b/trxcon/sched_lchan_pdtch.c
new file mode 100644
index 0000000..6a68489
--- /dev/null
+++ b/trxcon/sched_lchan_pdtch.c
@@ -0,0 +1,201 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * TDMA scheduler: handlers for DL / UL bursts on logical channels
+ *
+ * (C) 2018-2020 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/bits.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include "l1ctl_proto.h"
+#include "scheduler.h"
+#include "sched_trx.h"
+#include "logging.h"
+#include "trx_if.h"
+#include "l1ctl.h"
+
+int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
+ const sbit_t *bits, const struct trx_meas_set *meas)
+{
+ const struct trx_lchan_desc *lchan_desc;
+ uint8_t l2[GPRS_L2_MAX_LEN], *mask;
+ int n_errors, n_bits_total, rc;
+ sbit_t *buffer, *offset;
+ size_t l2_len;
+
+ /* Set up pointers */
+ lchan_desc = &trx_lchan_desc[lchan->type];
+ mask = &lchan->rx_burst_mask;
+ buffer = lchan->rx_bursts;
+
+ LOGP(DSCHD, LOGL_DEBUG, "Packet data received on %s: "
+ "fn=%u ts=%u bid=%u\n", lchan_desc->name, fn, ts->index, bid);
+
+ /* Align to the first burst of a block */
+ if (*mask == 0x00 && bid != 0)
+ return 0;
+
+ /* Update mask */
+ *mask |= (1 << bid);
+
+ /* Store the measurements */
+ sched_trx_meas_push(lchan, meas);
+
+ /* Copy burst to buffer of 4 bursts */
+ offset = buffer + bid * 116;
+ memcpy(offset, bits + 3, 58);
+ memcpy(offset + 58, bits + 87, 58);
+
+ /* Wait until complete set of bursts */
+ if (bid != 3)
+ return 0;
+
+ /* Calculate AVG of the measurements */
+ sched_trx_meas_avg(lchan, 4);
+
+ /* Check for complete set of bursts */
+ if ((*mask & 0xf) != 0xf) {
+ LOGP(DSCHD, LOGL_ERROR, "Received incomplete (%s) data frame at "
+ "fn=%u (%u/%u) for %s\n",
+ burst_mask2str(mask, 4), lchan->meas_avg.fn,
+ lchan->meas_avg.fn % ts->mf_layout->period,
+ ts->mf_layout->period,
+ lchan_desc->name);
+ /* NOTE: do not abort here, give it a try. Maybe we're lucky ;) */
+ }
+
+ /* Keep the mask updated */
+ *mask = *mask << 4;
+
+ /* Attempt to decode */
+ rc = gsm0503_pdtch_decode(l2, buffer,
+ NULL, &n_errors, &n_bits_total);
+ if (rc < 0) {
+ LOGP(DSCHD, LOGL_ERROR, "Received bad packet data frame "
+ "at fn=%u (%u/%u) for %s\n", lchan->meas_avg.fn,
+ lchan->meas_avg.fn % ts->mf_layout->period,
+ ts->mf_layout->period,
+ lchan_desc->name);
+ }
+
+ /* Determine L2 length */
+ l2_len = rc > 0 ? rc : 0;
+
+ /* Send a L2 frame to the higher layers */
+ sched_send_dt_ind(trx, ts, lchan,
+ l2, l2_len, n_errors, rc < 0, true);
+
+ return 0;
+}
+
+
+int tx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
+{
+ const struct trx_lchan_desc *lchan_desc;
+ ubit_t burst[GSM_BURST_LEN];
+ ubit_t *buffer, *offset;
+ const uint8_t *tsc;
+ uint8_t *mask;
+ int rc;
+
+ /* Set up pointers */
+ lchan_desc = &trx_lchan_desc[lchan->type];
+ mask = &lchan->tx_burst_mask;
+ buffer = lchan->tx_bursts;
+
+ if (bid > 0) {
+ /* If we have encoded bursts */
+ if (*mask)
+ goto send_burst;
+ else
+ return 0;
+ }
+
+ /* Encode payload */
+ rc = gsm0503_pdtch_encode(buffer, lchan->prim->payload,
+ lchan->prim->payload_len);
+ if (rc < 0) {
+ LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
+ lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
+ lchan->prim->payload_len));
+
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+
+ return -EINVAL;
+ }
+
+send_burst:
+ /* Determine which burst should be sent */
+ offset = buffer + bid * 116;
+
+ /* Update mask */
+ *mask |= (1 << bid);
+
+ /* Choose proper TSC */
+ tsc = sched_nb_training_bits[trx->tsc];
+
+ /* Compose a new burst */
+ memset(burst, 0, 3); /* TB */
+ memcpy(burst + 3, offset, 58); /* Payload 1/2 */
+ memcpy(burst + 61, tsc, 26); /* TSC */
+ memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
+ memset(burst + 145, 0, 3); /* TB */
+
+ LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
+ lchan_desc->name, fn, ts->index, bid);
+
+ /* Forward burst to scheduler */
+ rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
+ if (rc) {
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+
+ /* Reset mask */
+ *mask = 0x00;
+
+ return rc;
+ }
+
+ /* If we have sent the last (4/4) burst */
+ if ((*mask & 0x0f) == 0x0f) {
+ /* Confirm data / traffic sending */
+ sched_send_dt_conf(trx, ts, lchan, fn, true);
+
+ /* Forget processed primitive */
+ sched_prim_drop(lchan);
+
+ /* Reset mask */
+ *mask = 0x00;
+ }
+
+ return 0;
+}
diff --git a/trxcon/sched_lchan_rach.c b/trxcon/sched_lchan_rach.c
new file mode 100644
index 0000000..fe5821b
--- /dev/null
+++ b/trxcon/sched_lchan_rach.c
@@ -0,0 +1,187 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * TDMA scheduler: handlers for DL / UL bursts on logical channels
+ *
+ * (C) 2017-2019 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/bits.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include "l1ctl_proto.h"
+#include "scheduler.h"
+#include "sched_trx.h"
+#include "logging.h"
+#include "trx_if.h"
+#include "l1ctl.h"
+
+/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)" */
+#define RACH_EXT_TAIL_BITS_LEN 8
+#define RACH_SYNCH_SEQ_LEN 41
+#define RACH_PAYLOAD_LEN 36
+
+/* Extended tail bits (BN0..BN7) */
+static const ubit_t rach_ext_tail_bits[] = {
+ 0, 0, 1, 1, 1, 0, 1, 0,
+};
+
+/* Synchronization (training) sequence types */
+enum rach_synch_seq_t {
+ RACH_SYNCH_SEQ_UNKNOWN = -1,
+ RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */
+ RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */
+ RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */
+ RACH_SYNCH_SEQ_NUM
+};
+
+/* Synchronization (training) sequence bits */
+static const char rach_synch_seq_bits[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = {
+ [RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000",
+ [RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101",
+ [RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111",
+};
+
+/* Synchronization (training) sequence names */
+static struct value_string rach_synch_seq_names[] = {
+ { RACH_SYNCH_SEQ_UNKNOWN, "UNKNOWN" },
+ { RACH_SYNCH_SEQ_TS0, "TS0: GSM, GMSK" },
+ { RACH_SYNCH_SEQ_TS1, "TS1: EGPRS, 8-PSK" },
+ { RACH_SYNCH_SEQ_TS2, "TS2: EGPRS, GMSK" },
+ { 0, NULL },
+};
+
+/* Obtain a to-be-transmitted RACH burst */
+int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
+{
+ struct l1ctl_ext_rach_req *ext_req = NULL;
+ struct l1ctl_rach_req *req = NULL;
+ enum rach_synch_seq_t synch_seq;
+ uint8_t burst[GSM_BURST_LEN];
+ uint8_t *burst_ptr = burst;
+ uint8_t payload[36];
+ int i, rc;
+
+ /* Is it extended (11-bit) RACH or not? */
+ if (PRIM_IS_RACH11(lchan->prim)) {
+ ext_req = (struct l1ctl_ext_rach_req *) lchan->prim->payload;
+ synch_seq = ext_req->synch_seq;
+
+ /* Check requested synch. sequence */
+ if (synch_seq >= RACH_SYNCH_SEQ_NUM) {
+ LOGP(DSCHD, LOGL_ERROR, "Unknown RACH synch. sequence=0x%02x\n", synch_seq);
+
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+ return -ENOTSUP;
+ }
+
+ /* Delay sending according to offset value */
+ if (ext_req->offset-- > 0)
+ return 0;
+
+ /* Encode extended (11-bit) payload */
+ rc = gsm0503_rach_ext_encode(payload, ext_req->ra11, trx->bsic, true);
+ if (rc) {
+ LOGP(DSCHD, LOGL_ERROR, "Could not encode extended RACH burst "
+ "(ra=%u bsic=%u)\n", ext_req->ra11, trx->bsic);
+
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+ return rc;
+ }
+ } else if (PRIM_IS_RACH8(lchan->prim)) {
+ req = (struct l1ctl_rach_req *) lchan->prim->payload;
+ synch_seq = RACH_SYNCH_SEQ_TS0;
+
+ /* Delay sending according to offset value */
+ if (req->offset-- > 0)
+ return 0;
+
+ /* Encode regular (8-bit) payload */
+ rc = gsm0503_rach_ext_encode(payload, req->ra, trx->bsic, false);
+ if (rc) {
+ LOGP(DSCHD, LOGL_ERROR, "Could not encode RACH burst "
+ "(ra=%u bsic=%u)\n", req->ra, trx->bsic);
+
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+ return rc;
+ }
+ } else {
+ LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu (expected %zu or %zu), "
+ "so dropping...\n", lchan->prim->payload_len,
+ sizeof(*req), sizeof(*ext_req));
+ sched_prim_drop(lchan);
+ return -EINVAL;
+ }
+
+
+ /* BN0-7: extended tail bits */
+ memcpy(burst_ptr, rach_ext_tail_bits, RACH_EXT_TAIL_BITS_LEN);
+ burst_ptr += RACH_EXT_TAIL_BITS_LEN;
+
+ /* BN8-48: chosen synch. (training) sequence */
+ for (i = 0; i < RACH_SYNCH_SEQ_LEN; i++)
+ *(burst_ptr++) = rach_synch_seq_bits[synch_seq][i] == '1';
+
+ /* BN49-84: encrypted bits (the payload) */
+ memcpy(burst_ptr, payload, RACH_PAYLOAD_LEN);
+ burst_ptr += RACH_PAYLOAD_LEN;
+
+ /* BN85-156: tail bits & extended guard period */
+ memset(burst_ptr, 0, burst + GSM_BURST_LEN - burst_ptr);
+
+ LOGP(DSCHD, LOGL_NOTICE, "Transmitting %s RACH (%s) on fn=%u, tn=%u, lchan=%s\n",
+ PRIM_IS_RACH11(lchan->prim) ? "extended (11-bit)" : "regular (8-bit)",
+ get_value_string(rach_synch_seq_names, synch_seq), fn,
+ ts->index, trx_lchan_desc[lchan->type].name);
+
+ /* Forward burst to scheduler */
+ rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
+ if (rc) {
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+
+ return rc;
+ }
+
+ /* Confirm RACH request */
+ l1ctl_tx_rach_conf(trx->l1l, trx->band_arfcn, fn);
+
+ /* Optional GSMTAP logging */
+ sched_gsmtap_send(lchan->type, fn, ts->index,
+ trx->band_arfcn | ARFCN_UPLINK, 0, 0,
+ PRIM_IS_RACH11(lchan->prim) ? (uint8_t *) &ext_req->ra11 : &req->ra,
+ PRIM_IS_RACH11(lchan->prim) ? 2 : 1);
+
+ /* Forget processed primitive */
+ sched_prim_drop(lchan);
+
+ return 0;
+}
diff --git a/trxcon/sched_lchan_sch.c b/trxcon/sched_lchan_sch.c
new file mode 100644
index 0000000..4fe07ea
--- /dev/null
+++ b/trxcon/sched_lchan_sch.c
@@ -0,0 +1,139 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * TDMA scheduler: handlers for DL / UL bursts on logical channels
+ *
+ * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <talloc.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/bits.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include "l1ctl_proto.h"
+#include "scheduler.h"
+#include "sched_trx.h"
+#include "logging.h"
+#include "trx_if.h"
+#include "l1ctl.h"
+
+__attribute__((xray_always_instrument)) __attribute__((noinline))
+static int gsm0503_sch_decode_xray(uint8_t *sb_info, const sbit_t *burst)
+{
+ return gsm0503_sch_decode(sb_info, burst);
+}
+
+static void decode_sb(struct gsm_time *time, uint8_t *bsic, uint8_t *sb_info)
+{
+ uint8_t t3p;
+ uint32_t sb;
+
+ sb = ((uint32_t)sb_info[3] << 24)
+ | (sb_info[2] << 16)
+ | (sb_info[1] << 8)
+ | sb_info[0];
+
+ *bsic = (sb >> 2) & 0x3f;
+
+ /* TS 05.02 Chapter 3.3.2.2.1 SCH Frame Numbers */
+ time->t1 = ((sb >> 23) & 0x01)
+ | ((sb >> 7) & 0x1fe)
+ | ((sb << 9) & 0x600);
+
+ time->t2 = (sb >> 18) & 0x1f;
+
+ t3p = ((sb >> 24) & 0x01) | ((sb >> 15) & 0x06);
+ time->t3 = t3p * 10 + 1;
+
+ /* TS 05.02 Chapter 4.3.3 TDMA frame number */
+ time->fn = gsm_gsmtime2fn(time);
+}
+
+int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
+ const sbit_t *bits, const struct trx_meas_set *meas)
+{
+ sbit_t payload[2 * 39];
+ struct gsm_time time;
+ uint8_t sb_info[4];
+ uint8_t bsic;
+ int rc;
+
+ /* Obtain payload from burst */
+ memcpy(payload, bits + 3, 39);
+ memcpy(payload + 39, bits + 3 + 39 + 64, 39);
+
+ /* Attempt to decode */
+ rc = gsm0503_sch_decode_xray(sb_info, payload);
+ if (rc) {
+ LOGP(DSCHD, LOGL_ERROR, "Received bad SCH burst at fn=%u\n", fn);
+ return rc;
+ }
+
+ /* Decode BSIC and TDMA frame number */
+ decode_sb(&time, &bsic, sb_info);
+
+ LOGP(DSCHD, LOGL_DEBUG, "Received SCH: bsic=%u, fn=%u, sched_fn=%u\n",
+ bsic, time.fn, trx->sched.fn_counter_proc);
+
+ /* Check if decoded frame number matches */
+ if (time.fn != fn) {
+ LOGP(DSCHD, LOGL_ERROR, "Decoded fn=%u does not match "
+ "fn=%u provided by scheduler\n", time.fn, fn);
+ return -EINVAL;
+ }
+
+ /* We don't need to send L1CTL_FBSB_CONF */
+ if (trx->l1l->fbsb_conf_sent)
+ return 0;
+
+ /* Send L1CTL_FBSB_CONF to higher layers */
+ struct l1ctl_info_dl *data;
+ data = talloc_zero_size(ts, sizeof(struct l1ctl_info_dl));
+ if (data == NULL)
+ return -ENOMEM;
+
+ /* Fill in some downlink info */
+ data->chan_nr = trx_lchan_desc[lchan->type].chan_nr | ts->index;
+ data->link_id = trx_lchan_desc[lchan->type].link_id;
+ data->band_arfcn = htons(trx->band_arfcn);
+ data->frame_nr = htonl(fn);
+ data->rx_level = -(meas->rssi);
+
+ /* FIXME: set proper values */
+ data->num_biterr = 0;
+ data->fire_crc = 0;
+ data->snr = 0;
+
+ l1ctl_tx_fbsb_conf(trx->l1l, 0, data, bsic);
+
+ /* Update BSIC value of trx_instance */
+ trx->bsic = bsic;
+
+ return 0;
+}
diff --git a/trxcon/sched_lchan_tchf.c b/trxcon/sched_lchan_tchf.c
new file mode 100644
index 0000000..c5362f0
--- /dev/null
+++ b/trxcon/sched_lchan_tchf.c
@@ -0,0 +1,303 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * TDMA scheduler: handlers for DL / UL bursts on logical channels
+ *
+ * (C) 2017-2020 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/bits.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/coding/gsm0503_coding.h>
+#include <osmocom/codec/codec.h>
+
+#include "l1ctl_proto.h"
+#include "scheduler.h"
+#include "sched_trx.h"
+#include "logging.h"
+#include "trx_if.h"
+#include "l1ctl.h"
+
+int rx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
+ const sbit_t *bits, const struct trx_meas_set *meas)
+{
+ const struct trx_lchan_desc *lchan_desc;
+ int n_errors = -1, n_bits_total, rc;
+ sbit_t *buffer, *offset;
+ uint8_t l2[128], *mask;
+ size_t l2_len;
+
+ /* Set up pointers */
+ lchan_desc = &trx_lchan_desc[lchan->type];
+ mask = &lchan->rx_burst_mask;
+ buffer = lchan->rx_bursts;
+
+ LOGP(DSCHD, LOGL_DEBUG, "Traffic received on %s: fn=%u ts=%u bid=%u\n",
+ lchan_desc->name, fn, ts->index, bid);
+
+ /* Align to the first burst of a block */
+ if (*mask == 0x00 && bid != 0)
+ return 0;
+
+ /* Update mask */
+ *mask |= (1 << bid);
+
+ /* Store the measurements */
+ sched_trx_meas_push(lchan, meas);
+
+ /* Copy burst to end of buffer of 8 bursts */
+ offset = buffer + bid * 116 + 464;
+ memcpy(offset, bits + 3, 58);
+ memcpy(offset + 58, bits + 87, 58);
+
+ /* Wait until complete set of bursts */
+ if (bid != 3)
+ return 0;
+
+ /* Calculate AVG of the measurements */
+ sched_trx_meas_avg(lchan, 8);
+
+ /* Check for complete set of bursts */
+ if ((*mask & 0xff) != 0xff) {
+ LOGP(DSCHD, LOGL_ERROR, "Received incomplete (%s) traffic frame at "
+ "fn=%u (%u/%u) for %s\n",
+ burst_mask2str(mask, 8), lchan->meas_avg.fn,
+ lchan->meas_avg.fn % ts->mf_layout->period,
+ ts->mf_layout->period,
+ lchan_desc->name);
+ /* NOTE: do not abort here, give it a try. Maybe we're lucky ;) */
+
+ }
+
+ /* Keep the mask updated */
+ *mask = *mask << 4;
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SPEECH_V1: /* FR */
+ rc = gsm0503_tch_fr_decode(l2, buffer,
+ 1, 0, &n_errors, &n_bits_total);
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ rc = gsm0503_tch_fr_decode(l2, buffer,
+ 1, 1, &n_errors, &n_bits_total);
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ /**
+ * TODO: AMR requires a dedicated loop,
+ * which will be implemented later...
+ */
+ LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet\n");
+ return -ENOTSUP;
+ default:
+ LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
+ return -EINVAL;
+ }
+
+ /* Shift buffer by 4 bursts for interleaving */
+ memcpy(buffer, buffer + 464, 464);
+
+ /* Check decoding result */
+ if (rc < 4) {
+ LOGP(DSCHD, LOGL_ERROR, "Received bad TCH frame ending at "
+ "fn=%u for %s\n", fn, lchan_desc->name);
+
+ /* Send BFI */
+ goto bfi;
+ } else if (rc == GSM_MACBLOCK_LEN) {
+ /* FACCH received, forward it to the higher layers */
+ sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
+ n_errors, false, false);
+
+ /* Send BFI substituting a stolen TCH frame */
+ n_errors = -1; /* ensure fake measurements */
+ goto bfi;
+ } else {
+ /* A good TCH frame received */
+ l2_len = rc;
+ }
+
+ /* Send a traffic frame to the higher layers */
+ return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
+ n_errors, false, true);
+
+bfi:
+ /* Didn't try to decode, fake measurements */
+ if (n_errors < 0) {
+ lchan->meas_avg = (struct trx_meas_set) {
+ .fn = lchan->meas_avg.fn,
+ .toa256 = 0,
+ .rssi = -110,
+ };
+
+ /* No bursts => no errors */
+ n_errors = 0;
+ }
+
+ /* BFI is not applicable in signalling mode */
+ if (lchan->tch_mode == GSM48_CMODE_SIGN)
+ return sched_send_dt_ind(trx, ts, lchan, NULL, 0,
+ n_errors, true, false);
+
+ /* Bad frame indication */
+ l2_len = sched_bad_frame_ind(l2, lchan);
+
+ /* Send a BFI frame to the higher layers */
+ return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
+ n_errors, true, true);
+}
+
+int tx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
+{
+ const struct trx_lchan_desc *lchan_desc;
+ ubit_t burst[GSM_BURST_LEN];
+ ubit_t *buffer, *offset;
+ const uint8_t *tsc;
+ uint8_t *mask;
+ size_t l2_len;
+ int rc;
+
+ /* Set up pointers */
+ lchan_desc = &trx_lchan_desc[lchan->type];
+ mask = &lchan->tx_burst_mask;
+ buffer = lchan->tx_bursts;
+
+ /* If we have encoded bursts */
+ if (*mask)
+ goto send_burst;
+
+ /* Wait until a first burst in period */
+ if (bid > 0)
+ return 0;
+
+ /* Check the current TCH mode */
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SPEECH_V1: /* FR */
+ l2_len = GSM_FR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ l2_len = GSM_EFR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ /**
+ * TODO: AMR requires a dedicated loop,
+ * which will be implemented later...
+ */
+ LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet, "
+ "dropping frame...\n");
+
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+
+ return -ENOTSUP;
+ default:
+ LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u, "
+ "dropping frame...\n", lchan->tch_mode);
+
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+
+ return -EINVAL;
+ }
+
+ /* Determine and check the payload length */
+ if (lchan->prim->payload_len == GSM_MACBLOCK_LEN) {
+ l2_len = GSM_MACBLOCK_LEN; /* FACCH */
+ } else if (lchan->prim->payload_len != l2_len) {
+ LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu "
+ "(expected %zu for TCH or %u for FACCH), so dropping...\n",
+ lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
+
+ sched_prim_drop(lchan);
+ return -EINVAL;
+ }
+
+ /* Shift buffer by 4 bursts back for interleaving */
+ memcpy(buffer, buffer + 464, 464);
+
+ /* Encode payload */
+ rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, l2_len, 1);
+ if (rc) {
+ LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
+ lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
+ lchan->prim->payload_len));
+
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+
+ return -EINVAL;
+ }
+
+send_burst:
+ /* Determine which burst should be sent */
+ offset = buffer + bid * 116;
+
+ /* Update mask */
+ *mask |= (1 << bid);
+
+ /* Choose proper TSC */
+ tsc = sched_nb_training_bits[trx->tsc];
+
+ /* Compose a new burst */
+ memset(burst, 0, 3); /* TB */
+ memcpy(burst + 3, offset, 58); /* Payload 1/2 */
+ memcpy(burst + 61, tsc, 26); /* TSC */
+ memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
+ memset(burst + 145, 0, 3); /* TB */
+
+ LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
+ lchan_desc->name, fn, ts->index, bid);
+
+ /* Forward burst to scheduler */
+ rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
+ if (rc) {
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+
+ /* Reset mask */
+ *mask = 0x00;
+
+ return rc;
+ }
+
+ /* If we have sent the last (4/4) burst */
+ if (*mask == 0x0f) {
+ /* Confirm data / traffic sending */
+ sched_send_dt_conf(trx, ts, lchan, fn, PRIM_IS_TCH(lchan->prim));
+
+ /* Forget processed primitive */
+ sched_prim_drop(lchan);
+
+ /* Reset mask */
+ *mask = 0x00;
+ }
+
+ return 0;
+}
diff --git a/trxcon/sched_lchan_tchh.c b/trxcon/sched_lchan_tchh.c
new file mode 100644
index 0000000..b6f0708
--- /dev/null
+++ b/trxcon/sched_lchan_tchh.c
@@ -0,0 +1,501 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * TDMA scheduler: handlers for DL / UL bursts on logical channels
+ *
+ * (C) 2018-2020 by Vadim Yanitskiy <axilirator@gmail.com>
+ * (C) 2018 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/bits.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/coding/gsm0503_coding.h>
+#include <osmocom/codec/codec.h>
+
+#include "l1ctl_proto.h"
+#include "scheduler.h"
+#include "sched_trx.h"
+#include "logging.h"
+#include "trx_if.h"
+#include "l1ctl.h"
+
+static const uint8_t tch_h0_traffic_block_map[3][4] = {
+ /* B0(0,2,4,6), B1(4,6,8,10), B2(8,10,0,2) */
+ { 0, 2, 4, 6 },
+ { 4, 6, 8, 10 },
+ { 8, 10, 0, 2 },
+};
+
+static const uint8_t tch_h1_traffic_block_map[3][4] = {
+ /* B0(1,3,5,7), B1(5,7,9,11), B2(9,11,1,3) */
+ { 1, 3, 5, 7 },
+ { 5, 7, 9, 11 },
+ { 9, 11, 1, 3 },
+};
+
+static const uint8_t tch_h0_dl_facch_block_map[3][6] = {
+ /* B0(4,6,8,10,13,15), B1(13,15,17,19,21,23), B2(21,23,0,2,4,6) */
+ { 4, 6, 8, 10, 13, 15 },
+ { 13, 15, 17, 19, 21, 23 },
+ { 21, 23, 0, 2, 4, 6 },
+};
+
+static const uint8_t tch_h0_ul_facch_block_map[3][6] = {
+ /* B0(0,2,4,6,8,10), B1(8,10,13,15,17,19), B2(17,19,21,23,0,2) */
+ { 0, 2, 4, 6, 8, 10 },
+ { 8, 10, 13, 15, 17, 19 },
+ { 17, 19, 21, 23, 0, 2 },
+};
+
+static const uint8_t tch_h1_dl_facch_block_map[3][6] = {
+ /* B0(5,7,9,11,14,16), B1(14,16,18,20,22,24), B2(22,24,1,3,5,7) */
+ { 5, 7, 9, 11, 14, 16 },
+ { 14, 16, 18, 20, 22, 24 },
+ { 22, 24, 1, 3, 5, 7 },
+};
+
+const uint8_t tch_h1_ul_facch_block_map[3][6] = {
+ /* B0(1,3,5,7,9,11), B1(9,11,14,16,18,20), B2(18,20,22,24,1,3) */
+ { 1, 3, 5, 7, 9, 11 },
+ { 9, 11, 14, 16, 18, 20 },
+ { 18, 20, 22, 24, 1, 3 },
+};
+
+/**
+ * Can a TCH/H block transmission be initiated / finished
+ * on a given frame number and a given channel type?
+ *
+ * See GSM 05.02, clause 7, table 1
+ *
+ * @param chan channel type (TRXC_TCHH_0 or TRXC_TCHH_1)
+ * @param fn the current frame number
+ * @param ul Uplink or Downlink?
+ * @param facch FACCH/H or traffic?
+ * @param start init or end of transmission?
+ * @return true (yes) or false (no)
+ */
+bool sched_tchh_block_map_fn(enum trx_lchan_type chan,
+ uint32_t fn, bool ul, bool facch, bool start)
+{
+ uint8_t fn_mf;
+ int i = 0;
+
+ /* Just to be sure */
+ OSMO_ASSERT(chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1);
+
+ /* Calculate a modulo */
+ fn_mf = facch ? (fn % 26) : (fn % 13);
+
+#define MAP_GET_POS(map) \
+ (start ? 0 : ARRAY_SIZE(map[i]) - 1)
+
+#define BLOCK_MAP_FN(map) \
+ do { \
+ if (map[i][MAP_GET_POS(map)] == fn_mf) \
+ return true; \
+ } while (++i < ARRAY_SIZE(map))
+
+ /* Choose a proper block map */
+ if (facch) {
+ if (ul) {
+ if (chan == TRXC_TCHH_0)
+ BLOCK_MAP_FN(tch_h0_ul_facch_block_map);
+ else
+ BLOCK_MAP_FN(tch_h1_ul_facch_block_map);
+ } else {
+ if (chan == TRXC_TCHH_0)
+ BLOCK_MAP_FN(tch_h0_dl_facch_block_map);
+ else
+ BLOCK_MAP_FN(tch_h1_dl_facch_block_map);
+ }
+ } else {
+ if (chan == TRXC_TCHH_0)
+ BLOCK_MAP_FN(tch_h0_traffic_block_map);
+ else
+ BLOCK_MAP_FN(tch_h1_traffic_block_map);
+ }
+
+ return false;
+}
+
+/**
+ * Calculates a frame number of the first burst
+ * using given frame number of the last burst.
+ *
+ * See GSM 05.02, clause 7, table 1
+ *
+ * @param chan channel type (TRXC_TCHH_0 or TRXC_TCHH_1)
+ * @param last_fn frame number of the last burst
+ * @param facch FACCH/H or traffic?
+ * @return either frame number of the first burst,
+ * or fn=last_fn if calculation failed
+ */
+uint32_t sched_tchh_block_dl_first_fn(enum trx_lchan_type chan,
+ uint32_t last_fn, bool facch)
+{
+ uint8_t fn_mf, fn_diff;
+ int i = 0;
+
+ /* Just to be sure */
+ OSMO_ASSERT(chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1);
+
+ /* Calculate a modulo */
+ fn_mf = facch ? (last_fn % 26) : (last_fn % 13);
+
+#define BLOCK_FIRST_FN(map) \
+ do { \
+ if (map[i][ARRAY_SIZE(map[i]) - 1] == fn_mf) { \
+ fn_diff = GSM_TDMA_FN_DIFF(fn_mf, map[i][0]); \
+ return GSM_TDMA_FN_SUB(last_fn, fn_diff); \
+ } \
+ } while (++i < ARRAY_SIZE(map))
+
+ /* Choose a proper block map */
+ if (facch) {
+ if (chan == TRXC_TCHH_0)
+ BLOCK_FIRST_FN(tch_h0_dl_facch_block_map);
+ else
+ BLOCK_FIRST_FN(tch_h1_dl_facch_block_map);
+ } else {
+ if (chan == TRXC_TCHH_0)
+ BLOCK_FIRST_FN(tch_h0_traffic_block_map);
+ else
+ BLOCK_FIRST_FN(tch_h1_traffic_block_map);
+ }
+
+ LOGP(DSCHD, LOGL_ERROR, "Failed to calculate TDMA "
+ "frame number of the first burst of %s block, "
+ "using the current fn=%u\n", facch ?
+ "FACCH/H" : "TCH/H", last_fn);
+
+ /* Couldn't calculate the first fn, return the last */
+ return last_fn;
+}
+
+int rx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
+ const sbit_t *bits, const struct trx_meas_set *meas)
+{
+ const struct trx_lchan_desc *lchan_desc;
+ int n_errors = -1, n_bits_total, rc;
+ sbit_t *buffer, *offset;
+ uint8_t l2[128], *mask;
+ size_t l2_len;
+
+ /* Set up pointers */
+ lchan_desc = &trx_lchan_desc[lchan->type];
+ mask = &lchan->rx_burst_mask;
+ buffer = lchan->rx_bursts;
+
+ LOGP(DSCHD, LOGL_DEBUG, "Traffic received on %s: fn=%u ts=%u bid=%u\n",
+ lchan_desc->name, fn, ts->index, bid);
+
+ if (*mask == 0x00) {
+ /* Align to the first burst */
+ if (bid > 0)
+ return 0;
+
+ /* Align reception of the first FACCH/H frame */
+ if (lchan->tch_mode == GSM48_CMODE_SIGN) {
+ if (!sched_tchh_facch_start(lchan->type, fn, 0))
+ return 0;
+ } else { /* or TCH/H traffic frame */
+ if (!sched_tchh_traffic_start(lchan->type, fn, 0))
+ return 0;
+ }
+ }
+
+ /* Update mask */
+ *mask |= (1 << bid);
+
+ /* Store the measurements */
+ sched_trx_meas_push(lchan, meas);
+
+ /* Copy burst to the end of buffer of 6 bursts */
+ offset = buffer + bid * 116 + 464;
+ memcpy(offset, bits + 3, 58);
+ memcpy(offset + 58, bits + 87, 58);
+
+ /* Wait until the second burst */
+ if (bid != 1)
+ return 0;
+
+ /* Wait for complete set of bursts */
+ if (lchan->tch_mode == GSM48_CMODE_SIGN) {
+ /* FACCH/H is interleaved over 6 bursts */
+ if ((*mask & 0x3f) != 0x3f)
+ goto bfi_shift;
+ } else {
+ /* Traffic is interleaved over 4 bursts */
+ if ((*mask & 0x0f) != 0x0f)
+ goto bfi_shift;
+ }
+
+ /* Skip decoding attempt in case of FACCH/H */
+ if (lchan->dl_ongoing_facch) {
+ lchan->dl_ongoing_facch = false;
+ goto bfi_shift; /* 2/2 BFI */
+ }
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SPEECH_V1: /* HR */
+ rc = gsm0503_tch_hr_decode(l2, buffer,
+ !sched_tchh_facch_end(lchan->type, fn, 0),
+ &n_errors, &n_bits_total);
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ /**
+ * TODO: AMR requires a dedicated loop,
+ * which will be implemented later...
+ */
+ LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet\n");
+ return -ENOTSUP;
+ default:
+ LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
+ return -EINVAL;
+ }
+
+ /* Shift buffer by 4 bursts for interleaving */
+ memcpy(buffer, buffer + 232, 232);
+ memcpy(buffer + 232, buffer + 464, 232);
+
+ /* Shift burst mask */
+ *mask = *mask << 2;
+
+ /* Check decoding result */
+ if (rc < 4) {
+ /* Calculate AVG of the measurements (assuming 4 bursts) */
+ sched_trx_meas_avg(lchan, 4);
+
+ LOGP(DSCHD, LOGL_ERROR, "Received bad TCH frame (%s) "
+ "at fn=%u on %s (rc=%d)\n", burst_mask2str(mask, 6),
+ lchan->meas_avg.fn, lchan_desc->name, rc);
+
+ /* Send BFI */
+ goto bfi;
+ } else if (rc == GSM_MACBLOCK_LEN) {
+ /* Skip decoding of the next 2 stolen bursts */
+ lchan->dl_ongoing_facch = true;
+
+ /* Calculate AVG of the measurements (FACCH/H takes 6 bursts) */
+ sched_trx_meas_avg(lchan, 6);
+
+ /* FACCH/H received, forward to the higher layers */
+ sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
+ n_errors, false, false);
+
+ /* Send BFI substituting 1/2 stolen TCH frames */
+ n_errors = -1; /* ensure fake measurements */
+ goto bfi;
+ } else {
+ /* A good TCH frame received */
+ l2_len = rc;
+
+ /* Calculate AVG of the measurements (traffic takes 4 bursts) */
+ sched_trx_meas_avg(lchan, 4);
+ }
+
+ /* Send a traffic frame to the higher layers */
+ return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
+ n_errors, false, true);
+
+bfi_shift:
+ /* Shift buffer */
+ memcpy(buffer, buffer + 232, 232);
+ memcpy(buffer + 232, buffer + 464, 232);
+
+ /* Shift burst mask */
+ *mask = *mask << 2;
+
+bfi:
+ /* Didn't try to decode, fake measurements */
+ if (n_errors < 0) {
+ lchan->meas_avg = (struct trx_meas_set) {
+ .fn = sched_tchh_block_dl_first_fn(lchan->type, fn, false),
+ .toa256 = 0,
+ .rssi = -110,
+ };
+
+ /* No bursts => no errors */
+ n_errors = 0;
+ }
+
+ /* BFI is not applicable in signalling mode */
+ if (lchan->tch_mode == GSM48_CMODE_SIGN)
+ return sched_send_dt_ind(trx, ts, lchan, NULL, 0,
+ n_errors, true, false);
+
+ /* Bad frame indication */
+ l2_len = sched_bad_frame_ind(l2, lchan);
+
+ /* Send a BFI frame to the higher layers */
+ return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
+ n_errors, true, true);
+}
+
+int tx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
+{
+ const struct trx_lchan_desc *lchan_desc;
+ ubit_t burst[GSM_BURST_LEN];
+ ubit_t *buffer, *offset;
+ const uint8_t *tsc;
+ uint8_t *mask;
+ size_t l2_len;
+ int rc;
+
+ /* Set up pointers */
+ lchan_desc = &trx_lchan_desc[lchan->type];
+ mask = &lchan->tx_burst_mask;
+ buffer = lchan->tx_bursts;
+
+ if (bid > 0) {
+ /* Align to the first burst */
+ if (*mask == 0x00)
+ return 0;
+ goto send_burst;
+ }
+
+ if (*mask == 0x00) {
+ /* Align transmission of the first FACCH/H frame */
+ if (lchan->tch_mode == GSM48_CMODE_SIGN)
+ if (!sched_tchh_facch_start(lchan->type, fn, 1))
+ return 0;
+ }
+
+ /* Shift buffer by 2 bursts back for interleaving */
+ memcpy(buffer, buffer + 232, 232);
+
+ /* Also shift TX burst mask */
+ *mask = *mask << 2;
+
+ /* If FACCH/H blocks are still pending */
+ if (lchan->ul_facch_blocks > 2) {
+ memcpy(buffer + 232, buffer + 464, 232);
+ goto send_burst;
+ }
+
+ /* Check the current TCH mode */
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SPEECH_V1: /* HR */
+ l2_len = GSM_HR_BYTES + 1;
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ /**
+ * TODO: AMR requires a dedicated loop,
+ * which will be implemented later...
+ */
+ LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet, "
+ "dropping frame...\n");
+
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+ return -ENOTSUP;
+ default:
+ LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u, "
+ "dropping frame...\n", lchan->tch_mode);
+
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+ return -EINVAL;
+ }
+
+ /* Determine payload length */
+ if (PRIM_IS_FACCH(lchan->prim)) {
+ l2_len = GSM_MACBLOCK_LEN; /* FACCH */
+ } else if (lchan->prim->payload_len != l2_len) {
+ LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu "
+ "(expected %zu for TCH or %u for FACCH), so dropping...\n",
+ lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
+
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+ return -EINVAL;
+ }
+
+ /* Encode the payload */
+ rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, l2_len);
+ if (rc) {
+ LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
+ lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
+ lchan->prim->payload_len));
+
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+ return -EINVAL;
+ }
+
+ /* A FACCH/H frame occupies 6 bursts */
+ if (PRIM_IS_FACCH(lchan->prim))
+ lchan->ul_facch_blocks = 6;
+
+send_burst:
+ /* Determine which burst should be sent */
+ offset = buffer + bid * 116;
+
+ /* Update mask */
+ *mask |= (1 << bid);
+
+ /* Choose proper TSC */
+ tsc = sched_nb_training_bits[trx->tsc];
+
+ /* Compose a new burst */
+ memset(burst, 0, 3); /* TB */
+ memcpy(burst + 3, offset, 58); /* Payload 1/2 */
+ memcpy(burst + 61, tsc, 26); /* TSC */
+ memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
+ memset(burst + 145, 0, 3); /* TB */
+
+ LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
+ lchan_desc->name, fn, ts->index, bid);
+
+ /* Forward burst to transceiver */
+ sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
+
+ /* In case of a FACCH/H frame, one block less */
+ if (lchan->ul_facch_blocks)
+ lchan->ul_facch_blocks--;
+
+ if ((*mask & 0x0f) == 0x0f) {
+ /**
+ * If no more FACCH/H blocks pending,
+ * confirm data / traffic sending
+ */
+ if (!lchan->ul_facch_blocks)
+ sched_send_dt_conf(trx, ts, lchan, fn,
+ PRIM_IS_TCH(lchan->prim));
+
+ /* Forget processed primitive */
+ sched_prim_drop(lchan);
+ }
+
+ return 0;
+}
diff --git a/trxcon/sched_lchan_xcch.c b/trxcon/sched_lchan_xcch.c
new file mode 100644
index 0000000..da7f037
--- /dev/null
+++ b/trxcon/sched_lchan_xcch.c
@@ -0,0 +1,217 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * TDMA scheduler: handlers for DL / UL bursts on logical channels
+ *
+ * (C) 2017-2020 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/bits.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include "l1ctl_proto.h"
+#include "scheduler.h"
+#include "sched_trx.h"
+#include "logging.h"
+#include "trx_if.h"
+#include "l1ctl.h"
+
+__attribute__((xray_always_instrument)) __attribute__((noinline))
+static int gsm0503_xcch_decode_xray(uint8_t *l2_data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total) {
+ return gsm0503_xcch_decode(l2_data, bursts, n_errors, n_bits_total);
+}
+
+__attribute__((xray_always_instrument)) __attribute__((noinline)) int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
+ const sbit_t *bits, const struct trx_meas_set *meas)
+{
+ const struct trx_lchan_desc *lchan_desc;
+ uint8_t l2[GSM_MACBLOCK_LEN], *mask;
+ int n_errors, n_bits_total, rc;
+ sbit_t *buffer, *offset;
+
+ /* Set up pointers */
+ lchan_desc = &trx_lchan_desc[lchan->type];
+ mask = &lchan->rx_burst_mask;
+ buffer = lchan->rx_bursts;
+
+ LOGP(DSCHD, LOGL_DEBUG, "Data received on %s: fn=%u ts=%u bid=%u\n",
+ lchan_desc->name, fn, ts->index, bid);
+
+ /* Align to the first burst of a block */
+ if (*mask == 0x00 && bid != 0)
+ return 0;
+
+ /* Update mask */
+ *mask |= (1 << bid);
+
+ /* Store the measurements */
+ sched_trx_meas_push(lchan, meas);
+
+ /* Copy burst to buffer of 4 bursts */
+ offset = buffer + bid * 116;
+ memcpy(offset, bits + 3, 58);
+ memcpy(offset + 58, bits + 87, 58);
+
+ /* Wait until complete set of bursts */
+ if (bid != 3)
+ return 0;
+
+ /* Calculate AVG of the measurements */
+ sched_trx_meas_avg(lchan, 4);
+
+ /* Check for complete set of bursts */
+ if ((*mask & 0xf) != 0xf) {
+ LOGP(DSCHD, LOGL_ERROR, "Received incomplete (%s) data frame at "
+ "fn=%u (%u/%u) for %s\n",
+ burst_mask2str(mask, 4), lchan->meas_avg.fn,
+ lchan->meas_avg.fn % ts->mf_layout->period,
+ ts->mf_layout->period,
+ lchan_desc->name);
+ /* NOTE: xCCH has an insane amount of redundancy for error
+ * correction, so even just 2 valid bursts might be enough
+ * to reconstruct some L2 frames. This is why we do not
+ * abort here. */
+ }
+
+ /* Keep the mask updated */
+ *mask = *mask << 4;
+
+ /* Attempt to decode */
+ rc = gsm0503_xcch_decode_xray(l2, buffer, &n_errors, &n_bits_total);
+ if (rc) {
+ LOGP(DSCHD, LOGL_ERROR, "Received bad data frame with %d errors at fn=%u "
+ "(%u/%u) for %s\n", n_errors, lchan->meas_avg.fn,
+ lchan->meas_avg.fn % ts->mf_layout->period,
+ ts->mf_layout->period,
+ lchan_desc->name);
+
+ /**
+ * We should anyway send dummy frame for
+ * proper measurement reporting...
+ */
+ return sched_send_dt_ind(trx, ts, lchan, NULL, 0,
+ n_errors, true, false);
+ }
+
+ /* Send a L2 frame to the higher layers */
+ return sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
+ n_errors, false, false);
+}
+
+int tx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
+{
+ const struct trx_lchan_desc *lchan_desc;
+ ubit_t burst[GSM_BURST_LEN];
+ ubit_t *buffer, *offset;
+ const uint8_t *tsc;
+ uint8_t *mask;
+ int rc;
+
+ /* Set up pointers */
+ lchan_desc = &trx_lchan_desc[lchan->type];
+ mask = &lchan->tx_burst_mask;
+ buffer = lchan->tx_bursts;
+
+ if (bid > 0) {
+ /* If we have encoded bursts */
+ if (*mask)
+ goto send_burst;
+ else
+ return 0;
+ }
+
+ /* Check the prim payload length */
+ if (lchan->prim->payload_len != GSM_MACBLOCK_LEN) {
+ LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu (expected %u), "
+ "so dropping...\n", lchan->prim->payload_len, GSM_MACBLOCK_LEN);
+
+ sched_prim_drop(lchan);
+ return -EINVAL;
+ }
+
+ /* Encode payload */
+ rc = gsm0503_xcch_encode(buffer, lchan->prim->payload);
+ if (rc) {
+ LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
+ lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
+ lchan->prim->payload_len));
+
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+
+ return -EINVAL;
+ }
+
+send_burst:
+ /* Determine which burst should be sent */
+ offset = buffer + bid * 116;
+
+ /* Update mask */
+ *mask |= (1 << bid);
+
+ /* Choose proper TSC */
+ tsc = sched_nb_training_bits[trx->tsc];
+
+ /* Compose a new burst */
+ memset(burst, 0, 3); /* TB */
+ memcpy(burst + 3, offset, 58); /* Payload 1/2 */
+ memcpy(burst + 61, tsc, 26); /* TSC */
+ memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
+ memset(burst + 145, 0, 3); /* TB */
+
+ LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
+ lchan_desc->name, fn, ts->index, bid);
+
+ /* Forward burst to scheduler */
+ rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
+ if (rc) {
+ /* Forget this primitive */
+ sched_prim_drop(lchan);
+
+ /* Reset mask */
+ *mask = 0x00;
+
+ return rc;
+ }
+
+ /* If we have sent the last (4/4) burst */
+ if ((*mask & 0x0f) == 0x0f) {
+ /* Confirm data sending */
+ sched_send_dt_conf(trx, ts, lchan, fn, false);
+
+ /* Forget processed primitive */
+ sched_prim_drop(lchan);
+
+ /* Reset mask */
+ *mask = 0x00;
+ }
+
+ return 0;
+}
diff --git a/trxcon/sched_mframe.c b/trxcon/sched_mframe.c
new file mode 100644
index 0000000..9b759af
--- /dev/null
+++ b/trxcon/sched_mframe.c
@@ -0,0 +1,2101 @@
+/*
+ * 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/trxcon/sched_prim.c b/trxcon/sched_prim.c
new file mode 100644
index 0000000..9473320
--- /dev/null
+++ b/trxcon/sched_prim.c
@@ -0,0 +1,617 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * TDMA scheduler: primitive management
+ *
+ * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <talloc.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/linuxlist.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include "scheduler.h"
+#include "sched_trx.h"
+#include "trx_if.h"
+#include "logging.h"
+
+/**
+ * Initializes a new primitive by allocating memory
+ * and filling some meta-information (e.g. lchan type).
+ *
+ * @param ctx parent talloc context
+ * @param prim external prim pointer (will point to the allocated prim)
+ * @param pl_len prim payload length
+ * @param chan_nr RSL channel description (used to set a proper chan)
+ * @param link_id RSL link description (used to set a proper chan)
+ * @return zero in case of success, otherwise a error number
+ */
+int sched_prim_init(void *ctx, struct trx_ts_prim **prim,
+ size_t pl_len, uint8_t chan_nr, uint8_t link_id)
+{
+ enum trx_lchan_type lchan_type;
+ struct trx_ts_prim *new_prim;
+ uint8_t len;
+
+ /* Determine lchan type */
+ lchan_type = sched_trx_chan_nr2lchan_type(chan_nr, link_id);
+ if (!lchan_type) {
+ LOGP(DSCH, LOGL_ERROR, "Couldn't determine lchan type "
+ "for chan_nr=%02x and link_id=%02x\n", chan_nr, link_id);
+ return -EINVAL;
+ }
+
+ /* How much memory do we need? */
+ len = sizeof(struct trx_ts_prim); /* Primitive header */
+ len += pl_len; /* Requested payload size */
+
+ /* Allocate a new primitive */
+ new_prim = talloc_zero_size(ctx, len);
+ if (new_prim == NULL) {
+ LOGP(DSCH, LOGL_ERROR, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ /* Init primitive header */
+ new_prim->payload_len = pl_len;
+ new_prim->chan = lchan_type;
+
+ /* Set external pointer */
+ *prim = new_prim;
+
+ return 0;
+}
+
+/**
+ * Adds a primitive to the end of transmit queue of a particular
+ * timeslot, whose index is parsed from chan_nr.
+ *
+ * @param trx TRX instance
+ * @param prim to be enqueued primitive
+ * @param chan_nr RSL channel description
+ * @return zero in case of success, otherwise a error number
+ */
+int sched_prim_push(struct trx_instance *trx,
+ struct trx_ts_prim *prim, uint8_t chan_nr)
+{
+ struct trx_ts *ts;
+ uint8_t tn;
+
+ /* Determine TS index */
+ tn = chan_nr & 0x7;
+
+ /* Check whether required timeslot is allocated and configured */
+ ts = trx->ts_list[tn];
+ if (ts == NULL || ts->mf_layout == NULL) {
+ LOGP(DSCH, LOGL_ERROR, "Timeslot %u isn't configured\n", tn);
+ return -EINVAL;
+ }
+
+ /**
+ * Change talloc context of primitive
+ * from trx to the parent ts
+ */
+ talloc_steal(ts, prim);
+
+ /* Add primitive to TS transmit queue */
+ llist_add_tail(&prim->list, &ts->tx_prims);
+
+ return 0;
+}
+
+/**
+ * Composes a new primitive using either cached (if populated),
+ * or "dummy" Measurement Report message.
+ *
+ * @param lchan lchan to assign a primitive
+ * @return SACCH primitive to be transmitted
+ */
+static struct trx_ts_prim *prim_compose_mr(struct trx_lchan_state *lchan)
+{
+ struct trx_ts_prim *prim;
+ uint8_t *mr_src_ptr;
+ bool cached;
+ int rc;
+
+ /* "Dummy" Measurement Report */
+ static const uint8_t meas_rep_dummy[] = {
+ /* L1 SACCH pseudo-header */
+ 0x0f, 0x00,
+
+ /* LAPDm header */
+ 0x01, 0x03, 0x49,
+
+ /* RR Management messages, Measurement Report */
+ 0x06, 0x15,
+
+ /* Measurement results (see 3GPP TS 44.018, section 10.5.2.20):
+ * 0... .... = BA-USED: 0
+ * .0.. .... = DTX-USED: DTX was not used
+ * ..11 0110 = RXLEV-FULL-SERVING-CELL: -57 <= x < -56 dBm (54)
+ * 0... .... = 3G-BA-USED: 0
+ * .1.. .... = MEAS-VALID: The measurement results are not valid
+ * ..11 0110 = RXLEV-SUB-SERVING-CELL: -57 <= x < -56 dBm (54)
+ * 0... .... = SI23_BA_USED: 0
+ * .000 .... = RXQUAL-FULL-SERVING-CELL: BER < 0.2%, Mean value 0.14% (0)
+ * .... 000. = RXQUAL-SUB-SERVING-CELL: BER < 0.2%, Mean value 0.14% (0)
+ * .... ...1 11.. .... = NO-NCELL-M: Neighbour cell information not available */
+ 0x36, 0x76, 0x01, 0xc0,
+
+ /* 0** -- Padding with zeroes */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+
+ /* Allocate a new primitive */
+ rc = sched_prim_init(lchan, &prim, GSM_MACBLOCK_LEN,
+ trx_lchan_desc[lchan->type].chan_nr, TRX_CH_LID_SACCH);
+ OSMO_ASSERT(rc == 0);
+
+ /* Check if the MR cache is populated (verify LAPDm header) */
+ cached = (lchan->sacch.mr_cache[2] != 0x00
+ && lchan->sacch.mr_cache[3] != 0x00
+ && lchan->sacch.mr_cache[4] != 0x00);
+ if (cached) { /* Use the cached one */
+ mr_src_ptr = lchan->sacch.mr_cache;
+ lchan->sacch.mr_cache_usage++;
+ } else { /* Use "dummy" one */
+ mr_src_ptr = (uint8_t *) meas_rep_dummy;
+ }
+
+ /* Compose a new Measurement Report primitive */
+ memcpy(prim->payload, mr_src_ptr, GSM_MACBLOCK_LEN);
+
+ /**
+ * Update the L1 SACCH pseudo-header (only for cached MRs)
+ *
+ * TODO: filling of the actual values into cached Measurement
+ * Reports would break the distance spoofing feature. If it
+ * were known whether the spoofing is enabled or not, we could
+ * decide whether to update the cached L1 SACCH header here.
+ */
+ if (!cached) {
+ prim->payload[0] = lchan->ts->trx->tx_power;
+ prim->payload[1] = lchan->ts->trx->ta;
+ }
+
+ /* Inform about the cache usage count */
+ if (cached && lchan->sacch.mr_cache_usage > 5) {
+ LOGP(DSCHD, LOGL_NOTICE, "SACCH MR cache usage count=%u > 5 "
+ "on lchan=%s => ancient measurements, please fix!\n",
+ lchan->sacch.mr_cache_usage,
+ trx_lchan_desc[lchan->type].name);
+ }
+
+ LOGP(DSCHD, LOGL_NOTICE, "Using a %s Measurement Report "
+ "on lchan=%s\n", (cached ? "cached" : "dummy"),
+ trx_lchan_desc[lchan->type].name);
+
+ return prim;
+}
+
+/**
+ * Dequeues a SACCH primitive from transmit queue, if present.
+ * Otherwise dequeues a cached Measurement Report (the last
+ * received one). Finally, if the cache is empty, a "dummy"
+ * measurement report is used.
+ *
+ * According to 3GPP TS 04.08, section 3.4.1, SACCH channel
+ * accompanies either a traffic or a signaling channel. It
+ * has the particularity that continuous transmission must
+ * occur in both directions, so on the Uplink direction
+ * measurement result messages are sent at each possible
+ * occasion when nothing else has to be sent. The LAPDm
+ * fill frames (0x01, 0x03, 0x01, 0x2b, ...) are not
+ * applicable on SACCH channels!
+ *
+ * Unfortunately, 3GPP TS 04.08 doesn't clearly state
+ * which "else messages" besides Measurement Reports
+ * can be send by the MS on SACCH channels. However,
+ * in sub-clause 3.4.1 it's stated that the interval
+ * between two successive measurement result messages
+ * shall not exceed one L2 frame.
+ *
+ * @param queue transmit queue to take a prim from
+ * @param lchan lchan to assign a primitive
+ * @return SACCH primitive to be transmitted
+ */
+static struct trx_ts_prim *prim_dequeue_sacch(struct llist_head *queue,
+ struct trx_lchan_state *lchan)
+{
+ struct trx_ts_prim *prim_nmr = NULL;
+ struct trx_ts_prim *prim_mr = NULL;
+ struct trx_ts_prim *prim;
+ bool mr_now;
+
+ /* Shall we transmit MR now? */
+ mr_now = !lchan->sacch.mr_tx_last;
+
+#define PRIM_IS_MR(prim) \
+ (prim->payload[5] == GSM48_PDISC_RR \
+ && prim->payload[6] == GSM48_MT_RR_MEAS_REP)
+
+ /* Iterate over all primitives in the queue */
+ llist_for_each_entry(prim, queue, list) {
+ /* We are looking for particular channel */
+ if (prim->chan != lchan->type)
+ continue;
+
+ /* Look for a Measurement Report */
+ if (!prim_mr && PRIM_IS_MR(prim))
+ prim_mr = prim;
+
+ /* Look for anything else */
+ if (!prim_nmr && !PRIM_IS_MR(prim))
+ prim_nmr = prim;
+
+ /* Should we look further? */
+ if (mr_now && prim_mr)
+ break; /* MR was found */
+ else if (!mr_now && prim_nmr)
+ break; /* something else was found */
+ }
+
+ LOGP(DSCHD, LOGL_DEBUG, "SACCH MR selection on lchan=%s: "
+ "mr_tx_last=%d prim_mr=%p prim_nmr=%p\n",
+ trx_lchan_desc[lchan->type].name,
+ lchan->sacch.mr_tx_last,
+ prim_mr, prim_nmr);
+
+ /* Prioritize non-MR prim if possible */
+ if (mr_now && prim_mr)
+ prim = prim_mr;
+ else if (!mr_now && prim_nmr)
+ prim = prim_nmr;
+ else if (!mr_now && prim_mr)
+ prim = prim_mr;
+ else /* Nothing was found */
+ prim = NULL;
+
+ /* Have we found what we were looking for? */
+ if (prim) /* Dequeue if so */
+ llist_del(&prim->list);
+ else /* Otherwise compose a new MR */
+ prim = prim_compose_mr(lchan);
+
+ /* Update the cached report */
+ if (prim == prim_mr) {
+ memcpy(lchan->sacch.mr_cache,
+ prim->payload, GSM_MACBLOCK_LEN);
+ lchan->sacch.mr_cache_usage = 0;
+
+ LOGP(DSCHD, LOGL_DEBUG, "SACCH MR cache has been updated "
+ "for lchan=%s\n", trx_lchan_desc[lchan->type].name);
+ }
+
+ /* Update the MR transmission state */
+ lchan->sacch.mr_tx_last = PRIM_IS_MR(prim);
+
+ LOGP(DSCHD, LOGL_DEBUG, "SACCH decision on lchan=%s: %s\n",
+ trx_lchan_desc[lchan->type].name, PRIM_IS_MR(prim) ?
+ "Measurement Report" : "data frame");
+
+ return prim;
+}
+
+/* Dequeues a primitive of a given channel type */
+static struct trx_ts_prim *prim_dequeue_one(struct llist_head *queue,
+ enum trx_lchan_type lchan_type)
+{
+ struct trx_ts_prim *prim;
+
+ /**
+ * There is no need to use the 'safe' list iteration here
+ * as an item removal is immediately followed by return.
+ */
+ llist_for_each_entry(prim, queue, list) {
+ if (prim->chan == lchan_type) {
+ llist_del(&prim->list);
+ return prim;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * Dequeues either a FACCH, or a speech TCH primitive
+ * of a given channel type (Lm or Bm).
+ *
+ * Note: we could avoid 'lchan_type' parameter and just
+ * check the prim's channel type using CHAN_IS_TCH(),
+ * but the current approach is a bit more flexible,
+ * and allows one to have both sub-slots of TCH/H
+ * enabled on same timeslot e.g. for testing...
+ *
+ * @param queue transmit queue to take a prim from
+ * @param lchan_type required channel type of a primitive,
+ * e.g. TRXC_TCHF, TRXC_TCHH_0, or TRXC_TCHH_1
+ * @param facch FACCH (true) or speech (false) prim?
+ * @return either a FACCH, or a TCH primitive if found,
+ * otherwise NULL
+ */
+static struct trx_ts_prim *prim_dequeue_tch(struct llist_head *queue,
+ enum trx_lchan_type lchan_type, bool facch)
+{
+ struct trx_ts_prim *prim;
+
+ /**
+ * There is no need to use the 'safe' list iteration here
+ * as an item removal is immediately followed by return.
+ */
+ llist_for_each_entry(prim, queue, list) {
+ if (prim->chan != lchan_type)
+ continue;
+
+ /* Either FACCH, or not FACCH */
+ if (PRIM_IS_FACCH(prim) != facch)
+ continue;
+
+ llist_del(&prim->list);
+ return prim;
+ }
+
+ return NULL;
+}
+
+/**
+ * Dequeues either a TCH/F, or a FACCH/F prim (preferred).
+ * If a FACCH/F prim is found, one TCH/F prim is being
+ * dropped (i.e. replaced).
+ *
+ * @param queue a transmit queue to take a prim from
+ * @return either a FACCH/F, or a TCH/F primitive,
+ * otherwise NULL
+ */
+static struct trx_ts_prim *prim_dequeue_tch_f(struct llist_head *queue)
+{
+ struct trx_ts_prim *facch;
+ struct trx_ts_prim *tch;
+
+ /* Attempt to find a pair of both FACCH/F and TCH/F frames */
+ facch = prim_dequeue_tch(queue, TRXC_TCHF, true);
+ tch = prim_dequeue_tch(queue, TRXC_TCHF, false);
+
+ /* Prioritize FACCH/F, if found */
+ if (facch) {
+ /* One TCH/F prim is replaced */
+ if (tch)
+ talloc_free(tch);
+ return facch;
+ } else if (tch) {
+ /* Only TCH/F prim was found */
+ return tch;
+ } else {
+ /* Nothing was found, e.g. when only SACCH frames are in queue */
+ return NULL;
+ }
+}
+
+/**
+ * Dequeues either a TCH/H, or a FACCH/H prim (preferred).
+ * If a FACCH/H prim is found, two TCH/H prims are being
+ * dropped (i.e. replaced).
+ *
+ * According to GSM 05.02, the following blocks can be used
+ * to carry FACCH/H data (see clause 7, table 1 of 9):
+ *
+ * UL FACCH/H0:
+ * B0(0,2,4,6,8,10), B1(8,10,13,15,17,19), B2(17,19,21,23,0,2)
+ *
+ * UL FACCH/H1:
+ * B0(1,3,5,7,9,11), B1(9,11,14,16,18,20), B2(18,20,22,24,1,3)
+ *
+ * where the numbers within brackets are fn % 26.
+ *
+ * @param queue transmit queue to take a prim from
+ * @param fn the current frame number
+ * @param lchan_type required channel type of a primitive,
+ * @return either a FACCH/H, or a TCH/H primitive,
+ * otherwise NULL
+ */
+static struct trx_ts_prim *prim_dequeue_tch_h(struct llist_head *queue,
+ uint32_t fn, enum trx_lchan_type lchan_type)
+{
+ struct trx_ts_prim *facch;
+ struct trx_ts_prim *tch;
+ bool facch_now;
+
+ /* May we initiate an UL FACCH/H frame transmission now? */
+ facch_now = sched_tchh_facch_start(lchan_type, fn, true);
+ if (!facch_now) /* Just dequeue a TCH/H prim */
+ goto no_facch;
+
+ /* If there are no FACCH/H prims in the queue */
+ facch = prim_dequeue_tch(queue, lchan_type, true);
+ if (!facch) /* Just dequeue a TCH/H prim */
+ goto no_facch;
+
+ /* FACCH/H prim replaces two TCH/F prims */
+ tch = prim_dequeue_tch(queue, lchan_type, false);
+ if (tch) {
+ /* At least one TCH/H prim is dropped */
+ talloc_free(tch);
+
+ /* Attempt to find another */
+ tch = prim_dequeue_tch(queue, lchan_type, false);
+ if (tch) /* Drop the second TCH/H prim */
+ talloc_free(tch);
+ }
+
+ return facch;
+
+no_facch:
+ return prim_dequeue_tch(queue, lchan_type, false);
+}
+
+/**
+ * Dequeues a single primitive of required type
+ * from a specified transmit queue.
+ *
+ * @param queue a transmit queue to take a prim from
+ * @param fn the current frame number (used for FACCH/H)
+ * @param lchan logical channel state
+ * @return a primitive or NULL if not found
+ */
+struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue,
+ uint32_t fn, struct trx_lchan_state *lchan)
+{
+ /* SACCH is unorthodox, see 3GPP TS 04.08, section 3.4.1 */
+ if (CHAN_IS_SACCH(lchan->type))
+ return prim_dequeue_sacch(queue, lchan);
+
+ /* There is nothing to dequeue */
+ if (llist_empty(queue))
+ return NULL;
+
+ switch (lchan->type) {
+ /* TCH/F requires FACCH/F prioritization */
+ case TRXC_TCHF:
+ return prim_dequeue_tch_f(queue);
+
+ /* FACCH/H prioritization is a bit more complex */
+ case TRXC_TCHH_0:
+ case TRXC_TCHH_1:
+ return prim_dequeue_tch_h(queue, fn, lchan->type);
+
+ /* Other kinds of logical channels */
+ default:
+ return prim_dequeue_one(queue, lchan->type);
+ }
+}
+
+/**
+ * Drops the current primitive of specified logical channel
+ *
+ * @param lchan a logical channel to drop prim from
+ */
+void sched_prim_drop(struct trx_lchan_state *lchan)
+{
+ /* Forget this primitive */
+ talloc_free(lchan->prim);
+ lchan->prim = NULL;
+}
+
+/**
+ * Assigns a dummy primitive to a lchan depending on its type.
+ * Could be used when there is nothing to transmit, but
+ * CBTX (Continuous Burst Transmission) is assumed.
+ *
+ * @param lchan lchan to assign a primitive
+ * @return zero in case of success, otherwise a error code
+ */
+int sched_prim_dummy(struct trx_lchan_state *lchan)
+{
+ enum trx_lchan_type chan = lchan->type;
+ uint8_t tch_mode = lchan->tch_mode;
+ struct trx_ts_prim *prim;
+ uint8_t prim_buffer[40];
+ size_t prim_len = 0;
+ int i;
+
+ /**
+ * TS 144.006, section 8.4.2.3 "Fill frames"
+ * A fill frame is a UI command frame for SAPI 0, P=0
+ * and with an information field of 0 octet length.
+ */
+ static const uint8_t lapdm_fill_frame[] = {
+ 0x01, 0x03, 0x01, 0x2b,
+ /* Pending part is to be randomized */
+ };
+
+ /* Make sure that there is no existing primitive */
+ OSMO_ASSERT(lchan->prim == NULL);
+ /* Not applicable for SACCH! */
+ OSMO_ASSERT(!CHAN_IS_SACCH(lchan->type));
+
+ /**
+ * Determine what actually should be generated:
+ * TCH in GSM48_CMODE_SIGN: LAPDm fill frame;
+ * TCH in other modes: silence frame;
+ * other channels: LAPDm fill frame.
+ */
+ if (CHAN_IS_TCH(chan) && TCH_MODE_IS_SPEECH(tch_mode)) {
+ /* Bad frame indication */
+ prim_len = sched_bad_frame_ind(prim_buffer, lchan);
+ } else if (CHAN_IS_TCH(chan) && TCH_MODE_IS_DATA(tch_mode)) {
+ /* FIXME: should we do anything for CSD? */
+ return 0;
+ } else {
+ /* Copy LAPDm fill frame's header */
+ memcpy(prim_buffer, lapdm_fill_frame, sizeof(lapdm_fill_frame));
+
+ /**
+ * TS 144.006, section 5.2 "Frame delimitation and fill bits"
+ * Except for the first octet containing fill bits which shall
+ * be set to the binary value "00101011", each fill bit should
+ * be set to a random value when sent by the network.
+ */
+ for (i = sizeof(lapdm_fill_frame); i < GSM_MACBLOCK_LEN; i++)
+ prim_buffer[i] = (uint8_t) rand();
+
+ /* Define a prim length */
+ prim_len = GSM_MACBLOCK_LEN;
+ }
+
+ /* Nothing to allocate / assign */
+ if (!prim_len)
+ return 0;
+
+ /* Allocate a new primitive */
+ prim = talloc_zero_size(lchan, sizeof(struct trx_ts_prim) + prim_len);
+ if (prim == NULL)
+ return -ENOMEM;
+
+ /* Init primitive header */
+ prim->payload_len = prim_len;
+ prim->chan = lchan->type;
+
+ /* Fill in the payload */
+ memcpy(prim->payload, prim_buffer, prim_len);
+
+ /* Assign the current prim */
+ lchan->prim = prim;
+
+ LOGP(DSCHD, LOGL_DEBUG, "Transmitting a dummy / silence frame "
+ "on lchan=%s\n", trx_lchan_desc[chan].name);
+
+ return 0;
+}
+
+/**
+ * Flushes a queue of primitives
+ *
+ * @param list list of prims going to be flushed
+ */
+void sched_prim_flush_queue(struct llist_head *list)
+{
+ struct trx_ts_prim *prim, *prim_next;
+
+ llist_for_each_entry_safe(prim, prim_next, list, list) {
+ llist_del(&prim->list);
+ talloc_free(prim);
+ }
+}
diff --git a/trxcon/sched_trx.c b/trxcon/sched_trx.c
new file mode 100644
index 0000000..0025e0c
--- /dev/null
+++ b/trxcon/sched_trx.c
@@ -0,0 +1,841 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * TDMA scheduler: GSM PHY routines
+ *
+ * (C) 2017-2019 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <error.h>
+#include <errno.h>
+#include <string.h>
+#include <talloc.h>
+#include <stdbool.h>
+
+#include <osmocom/gsm/a5.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/linuxlist.h>
+
+#include "l1ctl_proto.h"
+#include "scheduler.h"
+#include "sched_trx.h"
+#include "trx_if.h"
+#include "logging.h"
+
+static void sched_frame_clck_cb(struct trx_sched *sched)
+{
+ struct trx_instance *trx = (struct trx_instance *) sched->data;
+ const struct trx_frame *frame;
+ struct trx_lchan_state *lchan;
+ trx_lchan_tx_func *handler;
+ enum trx_lchan_type chan;
+ uint8_t offset, bid;
+ struct trx_ts *ts;
+ uint32_t fn;
+ int i;
+
+ /* Iterate over timeslot list */
+ for (i = 0; i < TRX_TS_COUNT; i++) {
+ /* Timeslot is not allocated */
+ ts = trx->ts_list[i];
+ if (ts == NULL)
+ continue;
+
+ /* Timeslot is not configured */
+ if (ts->mf_layout == NULL)
+ continue;
+
+ /**
+ * Advance frame number, giving the transceiver more
+ * time until a burst must be transmitted...
+ */
+ fn = GSM_TDMA_FN_SUM(sched->fn_counter_proc, sched->fn_counter_advance);
+
+ /* Get frame from multiframe */
+ offset = fn % ts->mf_layout->period;
+ frame = ts->mf_layout->frames + offset;
+
+ /* Get required info from frame */
+ bid = frame->ul_bid;
+ chan = frame->ul_chan;
+ handler = trx_lchan_desc[chan].tx_fn;
+
+ /* Omit lchans without handler */
+ if (!handler)
+ continue;
+
+ /* Make sure that lchan was allocated and activated */
+ lchan = sched_trx_find_lchan(ts, chan);
+ if (lchan == NULL)
+ continue;
+
+ /* Omit inactive lchans */
+ if (!lchan->active)
+ continue;
+
+ /**
+ * If we aren't processing any primitive yet,
+ * attempt to obtain a new one from queue
+ */
+ if (lchan->prim == NULL)
+ lchan->prim = sched_prim_dequeue(&ts->tx_prims, fn, lchan);
+
+ /* TODO: report TX buffers health to the higher layers */
+
+ /* If CBTX (Continuous Burst Transmission) is assumed */
+ if (trx_lchan_desc[chan].flags & TRX_CH_FLAG_CBTX) {
+ /**
+ * Probably, a TX buffer is empty. Nevertheless,
+ * we shall continuously transmit anything on
+ * CBTX channels.
+ */
+ if (lchan->prim == NULL)
+ sched_prim_dummy(lchan);
+ }
+
+ /* If there is no primitive, do nothing */
+ if (lchan->prim == NULL)
+ continue;
+
+ /* Handover RACH needs to be handled regardless of the
+ * current channel type and the associated handler. */
+ if (PRIM_IS_RACH(lchan->prim) && lchan->prim->chan != TRXC_RACH)
+ handler = trx_lchan_desc[TRXC_RACH].tx_fn;
+
+ /* Poke lchan handler */
+ handler(trx, ts, lchan, fn, bid);
+ }
+}
+
+int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance)
+{
+ struct trx_sched *sched;
+
+ if (!trx)
+ return -EINVAL;
+
+ LOGP(DSCH, LOGL_NOTICE, "Init scheduler\n");
+
+ /* Obtain a scheduler instance from TRX */
+ sched = &trx->sched;
+
+ /* Register frame clock callback */
+ sched->clock_cb = sched_frame_clck_cb;
+
+ /* Set pointers */
+ sched = &trx->sched;
+ sched->data = trx;
+
+ /* Set frame counter advance */
+ sched->fn_counter_advance = fn_advance;
+
+ return 0;
+}
+
+int sched_trx_shutdown(struct trx_instance *trx)
+{
+ int i;
+
+ if (!trx)
+ return -EINVAL;
+
+ LOGP(DSCH, LOGL_NOTICE, "Shutdown scheduler\n");
+
+ /* Free all potentially allocated timeslots */
+ for (i = 0; i < TRX_TS_COUNT; i++)
+ sched_trx_del_ts(trx, i);
+
+ return 0;
+}
+
+int sched_trx_reset(struct trx_instance *trx, bool reset_clock)
+{
+ int i;
+
+ if (!trx)
+ return -EINVAL;
+
+ LOGP(DSCH, LOGL_NOTICE, "Reset scheduler %s\n",
+ reset_clock ? "and clock counter" : "");
+
+ /* Free all potentially allocated timeslots */
+ for (i = 0; i < TRX_TS_COUNT; i++)
+ sched_trx_del_ts(trx, i);
+
+ /* Stop and reset clock counter if required */
+ if (reset_clock)
+ sched_clck_reset(&trx->sched);
+
+ return 0;
+}
+
+struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn)
+{
+ /* Make sure that ts isn't allocated yet */
+ if (trx->ts_list[tn] != NULL) {
+ LOGP(DSCH, LOGL_ERROR, "Timeslot #%u already allocated\n", tn);
+ return NULL;
+ }
+
+ LOGP(DSCH, LOGL_NOTICE, "Add a new TDMA timeslot #%u\n", tn);
+
+ /* Allocate a new one */
+ trx->ts_list[tn] = talloc_zero(trx, struct trx_ts);
+
+ /* Add backpointer */
+ trx->ts_list[tn]->trx = trx;
+
+ /* Assign TS index */
+ trx->ts_list[tn]->index = tn;
+
+ return trx->ts_list[tn];
+}
+
+void sched_trx_del_ts(struct trx_instance *trx, int tn)
+{
+ struct trx_lchan_state *lchan, *lchan_next;
+ struct trx_ts *ts;
+
+ /* Find ts in list */
+ ts = trx->ts_list[tn];
+ if (ts == NULL)
+ return;
+
+ LOGP(DSCH, LOGL_NOTICE, "Delete TDMA timeslot #%u\n", tn);
+
+ /* Deactivate all logical channels */
+ sched_trx_deactivate_all_lchans(ts);
+
+ /* Free channel states */
+ llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
+ llist_del(&lchan->list);
+ talloc_free(lchan);
+ }
+
+ /* Flush queue primitives for TX */
+ sched_prim_flush_queue(&ts->tx_prims);
+
+ /* Remove ts from list and free memory */
+ trx->ts_list[tn] = NULL;
+ talloc_free(ts);
+
+ /* Notify transceiver about that */
+ trx_if_cmd_setslot(trx, tn, 0);
+}
+
+#define LAYOUT_HAS_LCHAN(layout, lchan) \
+ (layout->lchan_mask & ((uint64_t) 0x01 << lchan))
+
+int sched_trx_configure_ts(struct trx_instance *trx, int tn,
+ enum gsm_phys_chan_config config)
+{
+ struct trx_lchan_state *lchan;
+ enum trx_lchan_type type;
+ struct trx_ts *ts;
+
+ /* Try to find specified ts */
+ ts = trx->ts_list[tn];
+ if (ts != NULL) {
+ /* Reconfiguration of existing one */
+ sched_trx_reset_ts(trx, tn);
+ } else {
+ /* Allocate a new one if doesn't exist */
+ ts = sched_trx_add_ts(trx, tn);
+ if (ts == NULL)
+ return -ENOMEM;
+ }
+
+ /* Choose proper multiframe layout */
+ ts->mf_layout = sched_mframe_layout(config, tn);
+ if (!ts->mf_layout)
+ return -EINVAL;
+ if (ts->mf_layout->chan_config != config)
+ return -EINVAL;
+
+ LOGP(DSCH, LOGL_NOTICE, "(Re)configure TDMA timeslot #%u as %s\n",
+ tn, ts->mf_layout->name);
+
+ /* Init queue primitives for TX */
+ INIT_LLIST_HEAD(&ts->tx_prims);
+ /* Init logical channels list */
+ INIT_LLIST_HEAD(&ts->lchans);
+
+ /* Allocate channel states */
+ for (type = 0; type < _TRX_CHAN_MAX; type++) {
+ if (!LAYOUT_HAS_LCHAN(ts->mf_layout, type))
+ continue;
+
+ /* Allocate a channel state */
+ lchan = talloc_zero(ts, struct trx_lchan_state);
+ if (!lchan)
+ return -ENOMEM;
+
+ /* set backpointer */
+ lchan->ts = ts;
+
+ /* Set channel type */
+ lchan->type = type;
+
+ /* Add to the list of channel states */
+ llist_add_tail(&lchan->list, &ts->lchans);
+
+ /* Enable channel automatically if required */
+ if (trx_lchan_desc[type].flags & TRX_CH_FLAG_AUTO)
+ sched_trx_activate_lchan(ts, type);
+ }
+
+ /* Notify transceiver about TS activation */
+ /* FIXME: set proper channel type */
+ trx_if_cmd_setslot(trx, tn, 1);
+
+ return 0;
+}
+
+int sched_trx_reset_ts(struct trx_instance *trx, int tn)
+{
+ struct trx_lchan_state *lchan, *lchan_next;
+ struct trx_ts *ts;
+
+ /* Try to find specified ts */
+ ts = trx->ts_list[tn];
+ if (ts == NULL)
+ return -EINVAL;
+
+ /* Undefine multiframe layout */
+ ts->mf_layout = NULL;
+
+ /* Flush queue primitives for TX */
+ sched_prim_flush_queue(&ts->tx_prims);
+
+ /* Deactivate all logical channels */
+ sched_trx_deactivate_all_lchans(ts);
+
+ /* Free channel states */
+ llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
+ llist_del(&lchan->list);
+ talloc_free(lchan);
+ }
+
+ /* Notify transceiver about that */
+ trx_if_cmd_setslot(trx, tn, 0);
+
+ return 0;
+}
+
+int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo,
+ uint8_t *key, uint8_t key_len)
+{
+ struct trx_lchan_state *lchan;
+
+ /* Prevent NULL-pointer deference */
+ if (!ts)
+ return -EINVAL;
+
+ /* Make sure we can store this key */
+ if (key_len > MAX_A5_KEY_LEN)
+ return -ERANGE;
+
+ /* Iterate over all allocated logical channels */
+ llist_for_each_entry(lchan, &ts->lchans, list) {
+ /* Omit inactive channels */
+ if (!lchan->active)
+ continue;
+
+ /* Set key length and algorithm */
+ lchan->a5.key_len = key_len;
+ lchan->a5.algo = algo;
+
+ /* Copy requested key */
+ if (key_len)
+ memcpy(lchan->a5.key, key, key_len);
+ }
+
+ return 0;
+}
+
+struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts,
+ enum trx_lchan_type chan)
+{
+ struct trx_lchan_state *lchan;
+
+ llist_for_each_entry(lchan, &ts->lchans, list)
+ if (lchan->type == chan)
+ return lchan;
+
+ return NULL;
+}
+
+int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode)
+{
+ const struct trx_lchan_desc *lchan_desc;
+ struct trx_lchan_state *lchan;
+ int rc = 0;
+
+ /* Prevent NULL-pointer deference */
+ if (ts == NULL) {
+ LOGP(DSCH, LOGL_ERROR, "Timeslot isn't configured\n");
+ return -EINVAL;
+ }
+
+ /* Iterate over all allocated lchans */
+ llist_for_each_entry(lchan, &ts->lchans, list) {
+ lchan_desc = &trx_lchan_desc[lchan->type];
+
+ if (lchan_desc->chan_nr == (chan_nr & 0xf8)) {
+ if (active) {
+ rc |= sched_trx_activate_lchan(ts, lchan->type);
+ lchan->tch_mode = tch_mode;
+ } else
+ rc |= sched_trx_deactivate_lchan(ts, lchan->type);
+ }
+ }
+
+ return rc;
+}
+
+int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
+{
+ const struct trx_lchan_desc *lchan_desc = &trx_lchan_desc[chan];
+ struct trx_lchan_state *lchan;
+
+ /* Try to find requested logical channel */
+ lchan = sched_trx_find_lchan(ts, chan);
+ if (lchan == NULL)
+ return -EINVAL;
+
+ if (lchan->active) {
+ LOGP(DSCH, LOGL_ERROR, "Logical channel %s already activated "
+ "on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
+ return -EINVAL;
+ }
+
+ LOGP(DSCH, LOGL_NOTICE, "Activating lchan=%s "
+ "on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
+
+ /* Conditionally allocate memory for bursts */
+ if (lchan_desc->rx_fn && lchan_desc->burst_buf_size > 0) {
+ lchan->rx_bursts = talloc_zero_size(lchan,
+ lchan_desc->burst_buf_size);
+ if (lchan->rx_bursts == NULL)
+ return -ENOMEM;
+ }
+
+ if (lchan_desc->tx_fn && lchan_desc->burst_buf_size > 0) {
+ lchan->tx_bursts = talloc_zero_size(lchan,
+ lchan_desc->burst_buf_size);
+ if (lchan->tx_bursts == NULL)
+ return -ENOMEM;
+ }
+
+ /* Finally, update channel status */
+ lchan->active = 1;
+
+ return 0;
+}
+
+static void sched_trx_reset_lchan(struct trx_lchan_state *lchan)
+{
+ /* Prevent NULL-pointer deference */
+ OSMO_ASSERT(lchan != NULL);
+
+ /* Print some TDMA statistics for Downlink */
+ if (trx_lchan_desc[lchan->type].rx_fn && lchan->active) {
+ LOGP(DSCH, LOGL_DEBUG, "TDMA statistics for lchan=%s on ts=%u: "
+ "%lu DL frames have been processed, "
+ "%lu lost (compensated), last fn=%u\n",
+ trx_lchan_desc[lchan->type].name, lchan->ts->index,
+ lchan->tdma.num_proc, lchan->tdma.num_lost,
+ lchan->tdma.last_proc);
+ }
+
+ /* Reset internal state variables */
+ lchan->rx_burst_mask = 0x00;
+ lchan->tx_burst_mask = 0x00;
+
+ /* Free burst memory */
+ talloc_free(lchan->rx_bursts);
+ talloc_free(lchan->tx_bursts);
+
+ lchan->rx_bursts = NULL;
+ lchan->tx_bursts = NULL;
+
+ /* Forget the current prim */
+ sched_prim_drop(lchan);
+
+ /* Channel specific stuff */
+ if (CHAN_IS_TCH(lchan->type)) {
+ lchan->dl_ongoing_facch = 0;
+ lchan->ul_facch_blocks = 0;
+
+ lchan->tch_mode = GSM48_CMODE_SIGN;
+
+ /* Reset AMR state */
+ memset(&lchan->amr, 0x00, sizeof(lchan->amr));
+ } else if (CHAN_IS_SACCH(lchan->type)) {
+ /* Reset SACCH state */
+ memset(&lchan->sacch, 0x00, sizeof(lchan->sacch));
+ }
+
+ /* Reset ciphering state */
+ memset(&lchan->a5, 0x00, sizeof(lchan->a5));
+
+ /* Reset TDMA frame statistics */
+ memset(&lchan->tdma, 0x00, sizeof(lchan->tdma));
+}
+
+int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
+{
+ struct trx_lchan_state *lchan;
+
+ /* Try to find requested logical channel */
+ lchan = sched_trx_find_lchan(ts, chan);
+ if (lchan == NULL)
+ return -EINVAL;
+
+ if (!lchan->active) {
+ LOGP(DSCH, LOGL_ERROR, "Logical channel %s already deactivated "
+ "on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
+ return -EINVAL;
+ }
+
+ LOGP(DSCH, LOGL_DEBUG, "Deactivating lchan=%s "
+ "on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
+
+ /* Reset internal state, free memory */
+ sched_trx_reset_lchan(lchan);
+
+ /* Update activation flag */
+ lchan->active = 0;
+
+ return 0;
+}
+
+void sched_trx_deactivate_all_lchans(struct trx_ts *ts)
+{
+ struct trx_lchan_state *lchan;
+
+ LOGP(DSCH, LOGL_DEBUG, "Deactivating all logical channels "
+ "on ts=%d\n", ts->index);
+
+ llist_for_each_entry(lchan, &ts->lchans, list) {
+ /* Omit inactive channels */
+ if (!lchan->active)
+ continue;
+
+ /* Reset internal state, free memory */
+ sched_trx_reset_lchan(lchan);
+
+ /* Update activation flag */
+ lchan->active = 0;
+ }
+}
+
+enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr)
+{
+ uint8_t cbits = chan_nr >> 3;
+
+ if (cbits == ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs)
+ return GSM_PCHAN_TCH_F;
+ else if ((cbits & 0x1e) == ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0))
+ return GSM_PCHAN_TCH_H;
+ else if ((cbits & 0x1c) == ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0))
+ return GSM_PCHAN_CCCH_SDCCH4;
+ else if ((cbits & 0x18) == ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0))
+ return GSM_PCHAN_SDCCH8_SACCH8C;
+ else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH4)
+ return GSM_PCHAN_CCCH_SDCCH4_CBCH;
+ else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH8)
+ return GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
+ else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH)
+ return GSM_PCHAN_PDCH;
+
+ return GSM_PCHAN_NONE;
+}
+
+enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr,
+ uint8_t link_id)
+{
+ int i;
+
+ /* Iterate over all known lchan types */
+ for (i = 0; i < _TRX_CHAN_MAX; i++)
+ if (trx_lchan_desc[i].chan_nr == (chan_nr & 0xf8))
+ if (trx_lchan_desc[i].link_id == link_id)
+ return i;
+
+ return TRXC_IDLE;
+}
+
+static void sched_trx_a5_burst_dec(struct trx_lchan_state *lchan,
+ uint32_t fn, sbit_t *burst)
+{
+ ubit_t ks[114];
+ int i;
+
+ /* Generate keystream for a DL burst */
+ osmo_a5(lchan->a5.algo, lchan->a5.key, fn, ks, NULL);
+
+ /* Apply keystream over ciphertext */
+ for (i = 0; i < 57; i++) {
+ if (ks[i])
+ burst[i + 3] *= -1;
+ if (ks[i + 57])
+ burst[i + 88] *= -1;
+ }
+}
+
+static void sched_trx_a5_burst_enc(struct trx_lchan_state *lchan,
+ uint32_t fn, ubit_t *burst)
+{
+ ubit_t ks[114];
+ int i;
+
+ /* Generate keystream for an UL burst */
+ osmo_a5(lchan->a5.algo, lchan->a5.key, fn, NULL, ks);
+
+ /* Apply keystream over plaintext */
+ for (i = 0; i < 57; i++) {
+ burst[i + 3] ^= ks[i];
+ burst[i + 88] ^= ks[i + 57];
+ }
+}
+
+static int subst_frame_loss(struct trx_lchan_state *lchan,
+ trx_lchan_rx_func *handler,
+ uint32_t fn)
+{
+ const struct trx_multiframe *mf;
+ const struct trx_frame *fp;
+ int elapsed, i;
+
+ /* Wait until at least one TDMA frame is processed */
+ if (lchan->tdma.num_proc == 0)
+ return -EAGAIN;
+
+ /* Short alias for the current multiframe */
+ mf = lchan->ts->mf_layout;
+
+ /* Calculate how many frames elapsed since the last received one.
+ * The algorithm is based on GSM::FNDelta() from osmo-trx. */
+ elapsed = fn - lchan->tdma.last_proc;
+ if (elapsed >= GSM_TDMA_HYPERFRAME / 2)
+ elapsed -= GSM_TDMA_HYPERFRAME;
+ else if (elapsed < -GSM_TDMA_HYPERFRAME / 2)
+ elapsed += GSM_TDMA_HYPERFRAME;
+
+ /* Check TDMA frame order (wrong order is possible with fake_trx.py, see OS#4658) */
+ if (elapsed < 0) {
+ /* This burst has already been substituted by a dummy burst (all bits set to zero),
+ * so better drop it. Otherwise we risk to get undefined behavior in handler(). */
+ LOGP(DSCHD, LOGL_ERROR, "(%s) Rx burst with fn=%u older than the last "
+ "processed fn=%u (see OS#4658) => dropping\n",
+ trx_lchan_desc[lchan->type].name,
+ fn, lchan->tdma.last_proc);
+ return -EALREADY;
+ }
+
+ /* Check how many frames we (potentially) need to compensate */
+ if (elapsed > mf->period) {
+ LOGP(DSCHD, LOGL_NOTICE, "Too many (>%u) contiguous TDMA frames elapsed (%d) "
+ "since the last processed fn=%u (current %u)\n",
+ mf->period, elapsed, lchan->tdma.last_proc, fn);
+ return -EIO;
+ } else if (elapsed == 0) {
+ LOGP(DSCHD, LOGL_ERROR, "No TDMA frames elapsed since the last processed "
+ "fn=%u, must be a bug?\n", lchan->tdma.last_proc);
+ return -EIO;
+ }
+
+ static const sbit_t bits[148] = { 0 };
+ struct trx_meas_set fake_meas = {
+ .fn = lchan->tdma.last_proc,
+ .rssi = -120,
+ .toa256 = 0,
+ };
+
+ /* Traverse from fp till the current frame */
+ for (i = 0; i < elapsed - 1; i++) {
+ fp = &mf->frames[GSM_TDMA_FN_INC(fake_meas.fn) % mf->period];
+ if (fp->dl_chan != lchan->type)
+ continue;
+
+ LOGP(DSCHD, LOGL_NOTICE, "Substituting lost TDMA frame %u on %s\n",
+ fake_meas.fn, trx_lchan_desc[lchan->type].name);
+
+ handler(lchan->ts->trx, lchan->ts, lchan,
+ fake_meas.fn, fp->dl_bid,
+ bits, &fake_meas);
+
+ /* Update TDMA frame statistics */
+ lchan->tdma.last_proc = fake_meas.fn;
+ lchan->tdma.num_proc++;
+ lchan->tdma.num_lost++;
+ }
+
+ return 0;
+}
+
+int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
+ uint32_t fn, sbit_t *bits, uint16_t nbits,
+ const struct trx_meas_set *meas)
+{
+ struct trx_lchan_state *lchan;
+ const struct trx_frame *frame;
+ struct trx_ts *ts;
+
+ trx_lchan_rx_func *handler;
+ enum trx_lchan_type chan;
+ uint8_t offset, bid;
+ int rc;
+
+ /* Check whether required timeslot is allocated and configured */
+ ts = trx->ts_list[tn];
+ if (ts == NULL || ts->mf_layout == NULL) {
+ LOGP(DSCHD, LOGL_DEBUG, "TDMA timeslot #%u isn't configured, "
+ "ignoring burst...\n", tn);
+ return -EINVAL;
+ }
+
+ /* Get frame from multiframe */
+ offset = fn % ts->mf_layout->period;
+ frame = ts->mf_layout->frames + offset;
+
+ /* Get required info from frame */
+ bid = frame->dl_bid;
+ chan = frame->dl_chan;
+ handler = trx_lchan_desc[chan].rx_fn;
+
+ /* Omit bursts which have no handler, like IDLE bursts.
+ * TODO: handle noise indications during IDLE frames. */
+ if (!handler)
+ return -ENODEV;
+
+ /* Find required channel state */
+ lchan = sched_trx_find_lchan(ts, chan);
+ if (lchan == NULL)
+ return -ENODEV;
+
+ /* Ensure that channel is active */
+ if (!lchan->active)
+ return 0;
+
+ /* Compensate lost TDMA frames (if any) */
+ rc = subst_frame_loss(lchan, handler, fn);
+ if (rc == -EALREADY)
+ return rc;
+
+ /* Perform A5/X decryption if required */
+ if (lchan->a5.algo)
+ sched_trx_a5_burst_dec(lchan, fn, bits);
+
+ /* Put burst to handler */
+ handler(trx, ts, lchan, fn, bid, bits, meas);
+
+ /* Update TDMA frame statistics */
+ lchan->tdma.last_proc = fn;
+
+ if (++lchan->tdma.num_proc == 0) {
+ /* Theoretically, we may have an integer overflow of num_proc counter.
+ * As a consequence, subst_frame_loss() will be unable to compensate
+ * one (potentionally lost) Downlink burst. On practice, it would
+ * happen once in 4615 * 10e-6 * (2 ^ 32 - 1) seconds or ~6 years. */
+ LOGP(DSCHD, LOGL_NOTICE, "Too many TDMA frames have been processed. "
+ "Are you running trxcon for more than 6 years?!?\n");
+ lchan->tdma.num_proc = 1;
+ }
+
+ return 0;
+}
+
+int sched_trx_handle_tx_burst(struct trx_instance *trx,
+ struct trx_ts *ts, struct trx_lchan_state *lchan,
+ uint32_t fn, ubit_t *bits)
+{
+ int rc;
+
+ /* Perform A5/X burst encryption if required */
+ if (lchan->a5.algo)
+ sched_trx_a5_burst_enc(lchan, fn, bits);
+
+ /* Forward burst to transceiver */
+ rc = trx_if_tx_burst(trx, ts->index, fn, trx->tx_power, bits);
+ if (rc) {
+ LOGP(DSCHD, LOGL_ERROR, "Could not send burst to transceiver\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+#define MEAS_HIST_FIRST(hist) \
+ (&hist->buf[0])
+#define MEAS_HIST_LAST(hist) \
+ (MEAS_HIST_FIRST(hist) + ARRAY_SIZE(hist->buf) - 1)
+
+/* Add a new set of measurements to the history */
+void sched_trx_meas_push(struct trx_lchan_state *lchan, const struct trx_meas_set *meas)
+{
+ struct trx_lchan_meas_hist *hist = &lchan->meas_hist;
+
+ /* Find a new position where to store the measurements */
+ if (hist->head == MEAS_HIST_LAST(hist) || hist->head == NULL)
+ hist->head = MEAS_HIST_FIRST(hist);
+ else
+ hist->head++;
+
+ *hist->head = *meas;
+}
+
+/* Calculate the AVG of n measurements from the history */
+void sched_trx_meas_avg(struct trx_lchan_state *lchan, unsigned int n)
+{
+ struct trx_lchan_meas_hist *hist = &lchan->meas_hist;
+ struct trx_meas_set *meas = hist->head;
+ int toa256_sum = 0;
+ int rssi_sum = 0;
+ int i;
+
+ OSMO_ASSERT(n > 0 && n <= ARRAY_SIZE(hist->buf));
+ OSMO_ASSERT(meas != NULL);
+
+ /* Traverse backwards up to n entries, calculate the sum */
+ for (i = 0; i < n; i++) {
+ toa256_sum += meas->toa256;
+ rssi_sum += meas->rssi;
+
+ /* Do not go below the first burst */
+ if (i + 1 == n)
+ break;
+
+ if (meas == MEAS_HIST_FIRST(hist))
+ meas = MEAS_HIST_LAST(hist);
+ else
+ meas--;
+ }
+
+ /* Calculate the AVG */
+ lchan->meas_avg.toa256 = toa256_sum / n;
+ lchan->meas_avg.rssi = rssi_sum / n;
+
+ /* As a bonus, store TDMA frame number of the first burst */
+ lchan->meas_avg.fn = meas->fn;
+}
diff --git a/trxcon/sched_trx.h b/trxcon/sched_trx.h
new file mode 100644
index 0000000..fb7ecd4
--- /dev/null
+++ b/trxcon/sched_trx.h
@@ -0,0 +1,405 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/linuxlist.h>
+
+#include "logging.h"
+#include "scheduler.h"
+
+#define GSM_BURST_LEN 148
+#define GSM_BURST_PL_LEN 116
+
+#define GPRS_BURST_LEN GSM_BURST_LEN
+#define EDGE_BURST_LEN 444
+
+#define GPRS_L2_MAX_LEN 54
+#define EDGE_L2_MAX_LEN 155
+
+#define TRX_CH_LID_DEDIC 0x00
+#define TRX_CH_LID_SACCH 0x40
+
+/* Osmocom-specific extension for PTCCH (see 3GPP TS 45.002, section 3.3.4.2).
+ * Shall be used to distinguish PTCCH and PDTCH channels on a PDCH time-slot. */
+#define TRX_CH_LID_PTCCH 0x80
+
+/* Is a channel related to PDCH (GPRS) */
+#define TRX_CH_FLAG_PDCH (1 << 0)
+/* Should a channel be activated automatically */
+#define TRX_CH_FLAG_AUTO (1 << 1)
+/* Is continuous burst transmission assumed */
+#define TRX_CH_FLAG_CBTX (1 << 2)
+
+#define MAX_A5_KEY_LEN (128 / 8)
+#define TRX_TS_COUNT 8
+
+/* Forward declaration to avoid mutual include */
+struct trx_lchan_state;
+struct trx_meas_set;
+struct trx_instance;
+struct trx_ts;
+
+enum trx_burst_type {
+ TRX_BURST_GMSK,
+ TRX_BURST_8PSK,
+};
+
+/**
+ * These types define the different channels on a multiframe.
+ * Each channel has queues and can be activated individually.
+ */
+enum trx_lchan_type {
+ TRXC_IDLE = 0,
+ TRXC_FCCH,
+ TRXC_SCH,
+ TRXC_BCCH,
+ TRXC_RACH,
+ TRXC_CCCH,
+ TRXC_TCHF,
+ TRXC_TCHH_0,
+ TRXC_TCHH_1,
+ TRXC_SDCCH4_0,
+ TRXC_SDCCH4_1,
+ TRXC_SDCCH4_2,
+ TRXC_SDCCH4_3,
+ TRXC_SDCCH8_0,
+ TRXC_SDCCH8_1,
+ TRXC_SDCCH8_2,
+ TRXC_SDCCH8_3,
+ TRXC_SDCCH8_4,
+ TRXC_SDCCH8_5,
+ TRXC_SDCCH8_6,
+ TRXC_SDCCH8_7,
+ TRXC_SACCHTF,
+ TRXC_SACCHTH_0,
+ TRXC_SACCHTH_1,
+ TRXC_SACCH4_0,
+ TRXC_SACCH4_1,
+ TRXC_SACCH4_2,
+ TRXC_SACCH4_3,
+ TRXC_SACCH8_0,
+ TRXC_SACCH8_1,
+ TRXC_SACCH8_2,
+ TRXC_SACCH8_3,
+ TRXC_SACCH8_4,
+ TRXC_SACCH8_5,
+ TRXC_SACCH8_6,
+ TRXC_SACCH8_7,
+ TRXC_PDTCH,
+ TRXC_PTCCH,
+ TRXC_SDCCH4_CBCH,
+ TRXC_SDCCH8_CBCH,
+ _TRX_CHAN_MAX
+};
+
+typedef int trx_lchan_rx_func(struct trx_instance *trx,
+ struct trx_ts *ts, struct trx_lchan_state *lchan,
+ uint32_t fn, uint8_t bid, const sbit_t *bits,
+ const struct trx_meas_set *meas);
+
+typedef int trx_lchan_tx_func(struct trx_instance *trx,
+ struct trx_ts *ts, struct trx_lchan_state *lchan,
+ uint32_t fn, uint8_t bid);
+
+struct trx_lchan_desc {
+ /*! \brief Human-readable name */
+ const char *name;
+ /*! \brief Human-readable description */
+ const char *desc;
+
+ /*! \brief Channel Number (like in RSL) */
+ uint8_t chan_nr;
+ /*! \brief Link ID (like in RSL) */
+ uint8_t link_id;
+ /*! \brief Sub-slot number (for SDCCH and TCH/H) */
+ uint8_t ss_nr;
+ /*! \brief GSMTAP channel type (see GSMTAP_CHANNEL_*) */
+ uint8_t gsmtap_chan_type;
+
+ /*! \brief How much memory do we need to store bursts */
+ size_t burst_buf_size;
+ /*! \brief Channel specific flags */
+ uint8_t flags;
+
+ /*! \brief Function to call when burst received from PHY */
+ trx_lchan_rx_func *rx_fn;
+ /*! \brief Function to call when data received from L2 */
+ trx_lchan_tx_func *tx_fn;
+};
+
+struct trx_frame {
+ /*! \brief Downlink TRX channel type */
+ enum trx_lchan_type dl_chan;
+ /*! \brief Downlink block ID */
+ uint8_t dl_bid;
+ /*! \brief Uplink TRX channel type */
+ enum trx_lchan_type ul_chan;
+ /*! \brief Uplink block ID */
+ uint8_t ul_bid;
+};
+
+struct trx_multiframe {
+ /*! \brief Channel combination */
+ enum gsm_phys_chan_config chan_config;
+ /*! \brief Human-readable name */
+ const char *name;
+ /*! \brief Repeats how many frames */
+ uint8_t period;
+ /*! \brief Applies to which timeslots */
+ uint8_t slotmask;
+ /*! \brief Contains which lchans */
+ uint64_t lchan_mask;
+ /*! \brief Pointer to scheduling structure */
+ const struct trx_frame *frames;
+};
+
+struct trx_meas_set {
+ /*! \brief TDMA frame number of the first burst this set belongs to */
+ uint32_t fn;
+ /*! \brief ToA256 (Timing of Arrival, 1/256 of a symbol) */
+ int16_t toa256;
+ /*! \brief RSSI (Received Signal Strength Indication) */
+ int8_t rssi;
+};
+
+/* Simple ring buffer (up to 8 unique measurements) */
+struct trx_lchan_meas_hist {
+ struct trx_meas_set buf[8];
+ struct trx_meas_set *head;
+};
+
+/* States each channel on a multiframe */
+struct trx_lchan_state {
+ /*! \brief Channel type */
+ enum trx_lchan_type type;
+ /*! \brief Channel status */
+ uint8_t active;
+ /*! \brief Link to a list of channels */
+ struct llist_head list;
+
+ /*! \brief Burst type: GMSK or 8PSK */
+ enum trx_burst_type burst_type;
+ /*! \brief Mask of received bursts */
+ uint8_t rx_burst_mask;
+ /*! \brief Mask of transmitted bursts */
+ uint8_t tx_burst_mask;
+ /*! \brief Burst buffer for RX */
+ sbit_t *rx_bursts;
+ /*! \brief Burst buffer for TX */
+ ubit_t *tx_bursts;
+
+ /*! \brief A primitive being sent */
+ struct trx_ts_prim *prim;
+
+ /*! \brief Mode for TCH channels (see GSM48_CMODE_*) */
+ uint8_t tch_mode;
+
+ /*! \brief FACCH/H on downlink */
+ bool dl_ongoing_facch;
+ /*! \brief pending FACCH/H blocks on Uplink */
+ uint8_t ul_facch_blocks;
+
+ /*! \brief Downlink measurements history */
+ struct trx_lchan_meas_hist meas_hist;
+ /*! \brief AVG measurements of the last received block */
+ struct trx_meas_set meas_avg;
+
+ /*! \brief TDMA loss detection state */
+ struct {
+ /*! \brief Last processed TDMA frame number */
+ uint32_t last_proc;
+ /*! \brief Number of processed TDMA frames */
+ unsigned long num_proc;
+ /*! \brief Number of lost TDMA frames */
+ unsigned long num_lost;
+ } tdma;
+
+ /*! \brief SACCH state */
+ struct {
+ /*! \brief Cached measurement report (last received) */
+ uint8_t mr_cache[GSM_MACBLOCK_LEN];
+ /*! \brief Cache usage counter */
+ uint8_t mr_cache_usage;
+ /*! \brief Was a MR transmitted last time? */
+ bool mr_tx_last;
+ } sacch;
+
+ /* AMR specific */
+ struct {
+ /*! \brief 4 possible codecs for AMR */
+ uint8_t codec[4];
+ /*! \brief Number of possible codecs */
+ uint8_t codecs;
+ /*! \brief Current uplink FT index */
+ uint8_t ul_ft;
+ /*! \brief Current downlink FT index */
+ uint8_t dl_ft;
+ /*! \brief Current uplink CMR index */
+ uint8_t ul_cmr;
+ /*! \brief Current downlink CMR index */
+ uint8_t dl_cmr;
+ /*! \brief If AMR loop is enabled */
+ uint8_t amr_loop;
+ /*! \brief Number of bit error rates */
+ uint8_t ber_num;
+ /*! \brief Sum of bit error rates */
+ float ber_sum;
+ } amr;
+
+ /*! \brief A5/X encryption state */
+ struct {
+ uint8_t key[MAX_A5_KEY_LEN];
+ uint8_t key_len;
+ uint8_t algo;
+ } a5;
+
+ /* TS that this lchan belongs to */
+ struct trx_ts *ts;
+};
+
+struct trx_ts {
+ /*! \brief Timeslot index within a frame (0..7) */
+ uint8_t index;
+
+ /*! \brief Pointer to multiframe layout */
+ const struct trx_multiframe *mf_layout;
+ /*! \brief Channel states for logical channels */
+ struct llist_head lchans;
+ /*! \brief Queue primitives for TX */
+ struct llist_head tx_prims;
+ /* backpointer to its TRX */
+ struct trx_instance *trx;
+};
+
+/* Represents one TX primitive in the queue of trx_ts */
+struct trx_ts_prim {
+ /*! \brief Link to queue of TS */
+ struct llist_head list;
+ /*! \brief Logical channel type */
+ enum trx_lchan_type chan;
+ /*! \brief Payload length */
+ size_t payload_len;
+ /*! \brief Payload */
+ uint8_t payload[0];
+};
+
+extern const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX];
+const struct trx_multiframe *sched_mframe_layout(
+ enum gsm_phys_chan_config config, int tn);
+
+/* Scheduler management functions */
+int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance);
+int sched_trx_reset(struct trx_instance *trx, bool reset_clock);
+int sched_trx_shutdown(struct trx_instance *trx);
+
+/* Timeslot management functions */
+struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn);
+void sched_trx_del_ts(struct trx_instance *trx, int tn);
+int sched_trx_reset_ts(struct trx_instance *trx, int tn);
+int sched_trx_configure_ts(struct trx_instance *trx, int tn,
+ enum gsm_phys_chan_config config);
+int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo,
+ uint8_t *key, uint8_t key_len);
+
+/* Logical channel management functions */
+enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr);
+enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr,
+ uint8_t link_id);
+
+void sched_trx_deactivate_all_lchans(struct trx_ts *ts);
+int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode);
+int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan);
+int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan);
+struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts,
+ enum trx_lchan_type chan);
+
+/* Primitive management functions */
+int sched_prim_init(void *ctx, struct trx_ts_prim **prim,
+ size_t pl_len, uint8_t chan_nr, uint8_t link_id);
+int sched_prim_push(struct trx_instance *trx,
+ struct trx_ts_prim *prim, uint8_t chan_nr);
+
+#define TCH_MODE_IS_SPEECH(mode) \
+ (mode == GSM48_CMODE_SPEECH_V1 \
+ || mode == GSM48_CMODE_SPEECH_EFR \
+ || mode == GSM48_CMODE_SPEECH_AMR)
+
+#define TCH_MODE_IS_DATA(mode) \
+ (mode == GSM48_CMODE_DATA_14k5 \
+ || mode == GSM48_CMODE_DATA_12k0 \
+ || mode == GSM48_CMODE_DATA_6k0 \
+ || mode == GSM48_CMODE_DATA_3k6)
+
+#define CHAN_IS_TCH(chan) \
+ (chan == TRXC_TCHF || chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1)
+
+#define CHAN_IS_SACCH(chan) \
+ (trx_lchan_desc[chan].link_id & TRX_CH_LID_SACCH)
+
+/* FIXME: we need a better way to identify / distinguish primitives */
+#define PRIM_IS_RACH11(prim) \
+ (prim->payload_len == sizeof(struct l1ctl_ext_rach_req))
+
+#define PRIM_IS_RACH8(prim) \
+ (prim->payload_len == sizeof(struct l1ctl_rach_req))
+
+#define PRIM_IS_RACH(prim) \
+ (PRIM_IS_RACH8(prim) || PRIM_IS_RACH11(prim))
+
+#define PRIM_IS_TCH(prim) \
+ (CHAN_IS_TCH(prim->chan) && prim->payload_len != GSM_MACBLOCK_LEN)
+
+#define PRIM_IS_FACCH(prim) \
+ (CHAN_IS_TCH(prim->chan) && prim->payload_len == GSM_MACBLOCK_LEN)
+
+struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue,
+ uint32_t fn, struct trx_lchan_state *lchan);
+int sched_prim_dummy(struct trx_lchan_state *lchan);
+void sched_prim_drop(struct trx_lchan_state *lchan);
+void sched_prim_flush_queue(struct llist_head *list);
+
+int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
+ uint32_t fn, sbit_t *bits, uint16_t nbits,
+ const struct trx_meas_set *meas);
+int sched_trx_handle_tx_burst(struct trx_instance *trx,
+ struct trx_ts *ts, struct trx_lchan_state *lchan,
+ uint32_t fn, ubit_t *bits);
+
+/* Shared declarations for lchan handlers */
+extern const uint8_t sched_nb_training_bits[8][26];
+
+const char *burst_mask2str(const uint8_t *mask, int bits);
+size_t sched_bad_frame_ind(uint8_t *l2, struct trx_lchan_state *lchan);
+int sched_send_dt_ind(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint8_t *l2, size_t l2_len,
+ int bit_error_count, bool dec_failed, bool traffic);
+int sched_send_dt_conf(struct trx_instance *trx, struct trx_ts *ts,
+ struct trx_lchan_state *lchan, uint32_t fn, bool traffic);
+int sched_gsmtap_send(enum trx_lchan_type lchan_type, uint32_t fn, uint8_t tn,
+ uint16_t band_arfcn, int8_t signal_dbm, uint8_t snr,
+ const uint8_t *data, size_t data_len);
+
+/* Interleaved TCH/H block TDMA frame mapping */
+uint32_t sched_tchh_block_dl_first_fn(enum trx_lchan_type chan,
+ uint32_t last_fn, bool facch);
+bool sched_tchh_block_map_fn(enum trx_lchan_type chan,
+ uint32_t fn, bool ul, bool facch, bool start);
+
+#define sched_tchh_traffic_start(chan, fn, ul) \
+ sched_tchh_block_map_fn(chan, fn, ul, 0, 1)
+#define sched_tchh_traffic_end(chan, fn, ul) \
+ sched_tchh_block_map_fn(chan, fn, ul, 0, 0)
+
+#define sched_tchh_facch_start(chan, fn, ul) \
+ sched_tchh_block_map_fn(chan, fn, ul, 1, 1)
+#define sched_tchh_facch_end(chan, fn, ul) \
+ sched_tchh_block_map_fn(chan, fn, ul, 1, 0)
+
+/* Measurement history */
+void sched_trx_meas_push(struct trx_lchan_state *lchan, const struct trx_meas_set *meas);
+void sched_trx_meas_avg(struct trx_lchan_state *lchan, unsigned int n);
diff --git a/trxcon/scheduler.h b/trxcon/scheduler.h
new file mode 100644
index 0000000..43127cc
--- /dev/null
+++ b/trxcon/scheduler.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <stdint.h>
+#include <time.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/gsm0502.h>
+
+enum tdma_sched_clck_state {
+ SCH_CLCK_STATE_WAIT,
+ SCH_CLCK_STATE_OK,
+};
+
+/* Forward structure declaration */
+struct trx_sched;
+
+/*! \brief One scheduler instance */
+struct trx_sched {
+ /*! \brief Clock state */
+ enum tdma_sched_clck_state state;
+ /*! \brief Local clock source */
+ struct timespec clock;
+ /*! \brief Count of processed frames */
+ uint32_t fn_counter_proc;
+ /*! \brief Local frame counter advance */
+ uint32_t fn_counter_advance;
+ /*! \brief Count of lost frames */
+ uint32_t fn_counter_lost;
+ /*! \brief Frame callback timer */
+ struct osmo_timer_list clock_timer;
+ /*! \brief Frame callback */
+ void (*clock_cb)(struct trx_sched *sched);
+ /*! \brief Private data (e.g. pointer to trx instance) */
+ void *data;
+};
+
+int sched_clck_handle(struct trx_sched *sched, uint32_t fn);
+void sched_clck_reset(struct trx_sched *sched);
diff --git a/trxcon/trx_if.c b/trxcon/trx_if.c
new file mode 100644
index 0000000..84694c8
--- /dev/null
+++ b/trxcon/trx_if.c
@@ -0,0 +1,737 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * Transceiver interface handlers
+ *
+ * Copyright (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netinet/in.h>
+
+#include <sys/eventfd.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include "l1ctl.h"
+#include "trxcon.h"
+#include "trx_if.h"
+#include "logging.h"
+#include "scheduler.h"
+
+#include "../Transceiver52M/l1if.h"
+
+static struct value_string trx_evt_names[] = {
+ { 0, NULL } /* no events? */
+};
+
+static struct osmo_fsm_state trx_fsm_states[] = {
+ [TRX_STATE_OFFLINE] = {
+ .out_state_mask = (
+ GEN_MASK(TRX_STATE_IDLE) |
+ GEN_MASK(TRX_STATE_RSP_WAIT)),
+ .name = "OFFLINE",
+ },
+ [TRX_STATE_IDLE] = {
+ .out_state_mask = UINT32_MAX,
+ .name = "IDLE",
+ },
+ [TRX_STATE_ACTIVE] = {
+ .out_state_mask = (
+ GEN_MASK(TRX_STATE_IDLE) |
+ GEN_MASK(TRX_STATE_RSP_WAIT)),
+ .name = "ACTIVE",
+ },
+ [TRX_STATE_RSP_WAIT] = {
+ .out_state_mask = (
+ GEN_MASK(TRX_STATE_IDLE) |
+ GEN_MASK(TRX_STATE_ACTIVE) |
+ GEN_MASK(TRX_STATE_OFFLINE)),
+ .name = "RSP_WAIT",
+ },
+};
+
+static struct osmo_fsm trx_fsm = {
+ .name = "trx_interface_fsm",
+ .states = trx_fsm_states,
+ .num_states = ARRAY_SIZE(trx_fsm_states),
+ .log_subsys = DTRX,
+ .event_names = trx_evt_names,
+};
+
+static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local,
+ uint16_t port_local, const char *host_remote, uint16_t port_remote,
+ int (*cb)(struct osmo_fd *fd, unsigned int what))
+{
+ int rc;
+
+ ofd->data = priv;
+ ofd->fd = -1;
+ ofd->cb = cb;
+
+ /* Init UDP Connection */
+ rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, host_local, port_local,
+ host_remote, port_remote,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
+ return rc;
+}
+
+static void trx_udp_close(struct osmo_fd *ofd)
+{
+ if (ofd->fd > 0) {
+ osmo_fd_unregister(ofd);
+ close(ofd->fd);
+ ofd->fd = -1;
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+/* Control (CTRL) interface handlers */
+/* ------------------------------------------------------------------------ */
+/* Commands on the Per-ARFCN Control Interface */
+/* */
+/* The per-ARFCN control interface uses a command-response protocol. */
+/* Commands are NULL-terminated ASCII strings, one per UDP socket. */
+/* Each command has a corresponding response. */
+/* Every command is of the form: */
+/* */
+/* CMD <cmdtype> [params] */
+/* */
+/* The <cmdtype> is the actual command. */
+/* Parameters are optional depending on the commands type. */
+/* Every response is of the form: */
+/* */
+/* RSP <cmdtype> <status> [result] */
+/* */
+/* The <status> is 0 for success and a non-zero error code for failure. */
+/* Successful responses may include results, depending on the command type. */
+/* ------------------------------------------------------------------------ */
+
+static void trx_ctrl_timer_cb(void *data);
+
+/* Send first CTRL message and start timer */
+static void trx_ctrl_send(struct trx_instance *trx)
+{
+ struct trx_ctrl_msg *tcm;
+
+ if (llist_empty(&trx->trx_ctrl_list))
+ return;
+ tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
+
+ char* cmd = malloc(TRXC_BUF_SIZE);
+ memcpy(cmd, tcm->cmd, TRXC_BUF_SIZE);
+
+ /* Send command */
+ LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
+ trxif_to_trx_c(cmd);
+// send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0);
+
+ /* Trigger state machine */
+ if (trx->fsm->state != TRX_STATE_RSP_WAIT) {
+ trx->prev_state = trx->fsm->state;
+ osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_RSP_WAIT, 0, 0);
+ }
+
+ /* Start expire timer */
+ trx->trx_ctrl_timer.data = trx;
+ trx->trx_ctrl_timer.cb = trx_ctrl_timer_cb;
+ osmo_timer_schedule(&trx->trx_ctrl_timer, 2, 0);
+}
+
+static void trx_ctrl_timer_cb(void *data)
+{
+ struct trx_instance *trx = (struct trx_instance *) data;
+ struct trx_ctrl_msg *tcm;
+
+ /* Queue may be cleaned at this moment */
+ if (llist_empty(&trx->trx_ctrl_list))
+ return;
+
+ LOGP(DTRX, LOGL_NOTICE, "No response from transceiver...\n");
+
+ tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
+ if (++tcm->retry_cnt > 3) {
+ LOGP(DTRX, LOGL_NOTICE, "Transceiver offline\n");
+ osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_OFFLINE, 0, 0);
+ osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_OFFLINE, trx);
+ return;
+ }
+
+ /* Attempt to send a command again */
+ trx_ctrl_send(trx);
+}
+
+/* Add a new CTRL command to the trx_ctrl_list */
+static int trx_ctrl_cmd(struct trx_instance *trx, int critical,
+ const char *cmd, const char *fmt, ...)
+{
+ struct trx_ctrl_msg *tcm;
+ int len, pending = 0;
+ va_list ap;
+
+ /* TODO: make sure that transceiver online */
+
+ if (!llist_empty(&trx->trx_ctrl_list))
+ pending = 1;
+
+ /* Allocate a message */
+ tcm = talloc_zero(trx, struct trx_ctrl_msg);
+ if (!tcm)
+ return -ENOMEM;
+
+ /* Fill in command arguments */
+ if (fmt && fmt[0]) {
+ len = snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s ", cmd);
+ va_start(ap, fmt);
+ vsnprintf(tcm->cmd + len, sizeof(tcm->cmd) - len - 1, fmt, ap);
+ va_end(ap);
+ } else {
+ snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s", cmd);
+ }
+
+ tcm->cmd_len = strlen(cmd);
+ tcm->critical = critical;
+ llist_add_tail(&tcm->list, &trx->trx_ctrl_list);
+ LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd);
+
+ /* Send message, if no pending messages */
+ if (!pending)
+ trx_ctrl_send(trx);
+
+ return 0;
+}
+
+/*
+ * Power Control
+ *
+ * ECHO is used to check transceiver availability.
+ * CMD ECHO
+ * RSP ECHO <status>
+ *
+ * POWEROFF shuts off transmitter power and stops the demodulator.
+ * CMD POWEROFF
+ * RSP POWEROFF <status>
+ *
+ * POWERON starts the transmitter and starts the demodulator.
+ * Initial power level is very low.
+ * This command fails if the transmitter and receiver are not yet tuned.
+ * This command fails if the transmit or receive frequency creates a conflict
+ * with another ARFCN that is already running.
+ * If the transceiver is already on, it response with success to this command.
+ * CMD POWERON
+ * RSP POWERON <status>
+ */
+
+int trx_if_cmd_sync(struct trx_instance *trx)
+{
+ return trx_ctrl_cmd(trx, 1, "SYNC", "");
+}
+
+int trx_if_cmd_echo(struct trx_instance *trx)
+{
+ return trx_ctrl_cmd(trx, 1, "ECHO", "");
+}
+
+int trx_if_cmd_poweroff(struct trx_instance *trx)
+{
+ return trx_ctrl_cmd(trx, 1, "POWEROFF", "");
+}
+
+int trx_if_cmd_poweron(struct trx_instance *trx)
+{
+ if (trx->powered_up) {
+ /* FIXME: this should be handled by the FSM, not here! */
+ LOGP(DTRX, LOGL_ERROR, "Suppressing POWERON as we're already powered up\n");
+ return -EAGAIN;
+ }
+ return trx_ctrl_cmd(trx, 1, "POWERON", "");
+}
+
+/*
+ * Timeslot Control
+ *
+ * SETSLOT sets the format of the uplink timeslots in the ARFCN.
+ * The <timeslot> indicates the timeslot of interest.
+ * The <chantype> indicates the type of channel that occupies the timeslot.
+ * A chantype of zero indicates the timeslot is off.
+ * CMD SETSLOT <timeslot> <chantype>
+ * RSP SETSLOT <status> <timeslot> <chantype>
+ */
+
+int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type)
+{
+ return trx_ctrl_cmd(trx, 1, "SETSLOT", "%u %u", tn, type);
+}
+
+/*
+ * Tuning Control
+ *
+ * (RX/TX)TUNE tunes the receiver to a given frequency in kHz.
+ * This command fails if the receiver is already running.
+ * (To re-tune you stop the radio, re-tune, and restart.)
+ * This command fails if the transmit or receive frequency
+ * creates a conflict with another ARFCN that is already running.
+ * CMD (RX/TX)TUNE <kHz>
+ * RSP (RX/TX)TUNE <status> <kHz>
+ */
+
+int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn)
+{
+ uint16_t freq10;
+
+ /* RX is downlink on MS side */
+ freq10 = gsm_arfcn2freq10(band_arfcn, 0);
+ if (freq10 == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
+ return -ENOTSUP;
+ }
+
+ return trx_ctrl_cmd(trx, 1, "RXTUNE", "%u", freq10 * 100);
+}
+
+int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn)
+{
+ uint16_t freq10;
+
+ /* TX is uplink on MS side */
+ freq10 = gsm_arfcn2freq10(band_arfcn, 1);
+ if (freq10 == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
+ return -ENOTSUP;
+ }
+
+ return trx_ctrl_cmd(trx, 1, "TXTUNE", "%u", freq10 * 100);
+}
+
+/*
+ * Power measurement
+ *
+ * MEASURE instructs the transceiver to perform a power
+ * measurement on specified frequency. After receiving this
+ * request, transceiver should quickly re-tune to requested
+ * frequency, measure power level and re-tune back to the
+ * previous frequency.
+ * CMD MEASURE <kHz>
+ * RSP MEASURE <status> <kHz> <dB>
+ */
+
+int trx_if_cmd_measure(struct trx_instance *trx,
+ uint16_t band_arfcn_start, uint16_t band_arfcn_stop)
+{
+ uint16_t freq10;
+
+ /* Update ARFCN range for measurement */
+ trx->pm_band_arfcn_start = band_arfcn_start;
+ trx->pm_band_arfcn_stop = band_arfcn_stop;
+
+ /* Calculate a frequency for current ARFCN (DL) */
+ freq10 = gsm_arfcn2freq10(band_arfcn_start, 0);
+ if (freq10 == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn_start);
+ return -ENOTSUP;
+ }
+
+ return trx_ctrl_cmd(trx, 1, "MEASURE", "%u", freq10 * 100);
+}
+
+static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp)
+{
+ unsigned int freq10;
+ uint16_t band_arfcn;
+ int dbm;
+
+ /* Parse freq. and power level */
+ sscanf(resp, "%u %d", &freq10, &dbm);
+ freq10 /= 100;
+
+ /* Check received ARFCN against expected */
+ band_arfcn = gsm_freq102arfcn((uint16_t) freq10, 0);
+ if (band_arfcn != trx->pm_band_arfcn_start) {
+ LOGP(DTRX, LOGL_ERROR, "Power measurement error: "
+ "response ARFCN=%u doesn't match expected ARFCN=%u\n",
+ band_arfcn &~ ARFCN_FLAG_MASK,
+ trx->pm_band_arfcn_start &~ ARFCN_FLAG_MASK);
+ return;
+ }
+
+ /* Send L1CTL_PM_CONF */
+ l1ctl_tx_pm_conf(trx->l1l, band_arfcn, dbm,
+ band_arfcn == trx->pm_band_arfcn_stop);
+
+ /* Schedule a next measurement */
+ if (band_arfcn != trx->pm_band_arfcn_stop)
+ trx_if_cmd_measure(trx, ++band_arfcn, trx->pm_band_arfcn_stop);
+}
+
+/*
+ * Timing Advance control
+ *
+ * SETTA instructs the transceiver to transmit bursts in
+ * advance calculated from requested TA value. This value is
+ * normally between 0 and 63, with each step representing
+ * an advance of one bit period (about 3.69 microseconds).
+ * Since OsmocomBB has a special feature, which allows one
+ * to spoof the distance from BTS, the range is extended.
+ * CMD SETTA <-128..127>
+ * RSP SETTA <status> <TA>
+ */
+
+int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta)
+{
+ return trx_ctrl_cmd(trx, 0, "SETTA", "%d", ta);
+}
+
+/*
+ * Frequency Hopping parameters indication.
+ *
+ * SETFH instructs transceiver to enable frequency hopping mode
+ * using the given HSN, MAIO, and Mobile Allocation parameters.
+ *
+ * CMD SETFH <HSN> <MAIO> <RXF1> <TXF1> [... <RXFN> <TXFN>]
+ *
+ * where <RXFN> and <TXFN> is a pair of Rx/Tx frequencies (in kHz)
+ * corresponding to one ARFCN the Mobile Allocation. Note that the
+ * channel list is expected to be sorted in ascending order.
+ */
+
+int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
+ uint8_t maio, uint16_t *ma, size_t ma_len)
+{
+ /* Reserve some room for CMD SETFH <HSN> <MAIO> */
+ char ma_buf[TRXC_BUF_SIZE - 24];
+ size_t ma_buf_len = sizeof(ma_buf) - 1;
+ uint16_t rx_freq, tx_freq;
+ char *ptr;
+ int i, rc;
+
+ /* Make sure that Mobile Allocation has at least one ARFCN */
+ if (!ma_len || ma == NULL) {
+ LOGP(DTRX, LOGL_ERROR, "Mobile Allocation is empty?!?\n");
+ return -EINVAL;
+ }
+
+ /* Compose a sequence of Rx/Tx frequencies (mobile allocation) */
+ for (i = 0, ptr = ma_buf; i < ma_len; i++) {
+ /* Convert ARFCN to a pair of Rx/Tx frequencies (Hz * 10) */
+ rx_freq = gsm_arfcn2freq10(ma[i], 0); /* Rx: Downlink */
+ tx_freq = gsm_arfcn2freq10(ma[i], 1); /* Tx: Uplink */
+ if (rx_freq == 0xffff || tx_freq == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "Failed to convert ARFCN %u "
+ "to a pair of Rx/Tx frequencies\n",
+ ma[i] & ~ARFCN_FLAG_MASK);
+ return -EINVAL;
+ }
+
+ /* Append a pair of Rx/Tx frequencies (in kHz) to the buffer */
+ rc = snprintf(ptr, ma_buf_len, "%u %u ", rx_freq * 100, tx_freq * 100);
+ if (rc < 0 || rc > ma_buf_len) { /* Prevent buffer overflow */
+ LOGP(DTRX, LOGL_ERROR, "Not enough room to encode "
+ "Mobile Allocation (N=%zu)\n", ma_len);
+ return -ENOSPC;
+ }
+
+ /* Move pointer */
+ ma_buf_len -= rc;
+ ptr += rc;
+ }
+
+ /* Overwrite the last space */
+ *(ptr - 1) = '\0';
+
+ return trx_ctrl_cmd(trx, 1, "SETFH", "%u %u %s", hsn, maio, ma_buf);
+}
+
+/* Get response from CTRL socket */
+static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct trx_instance *trx = ofd->data;
+ struct trx_ctrl_msg *tcm;
+ int resp, rsp_len;
+ char buf[TRXC_BUF_SIZE], *p;
+
+ char* response = trxif_from_trx_c();
+ if (!response) {
+ LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", response);
+ goto rsp_error;
+ }
+ memcpy(buf, response, TRXC_BUF_SIZE);
+ free(response);
+
+ if (!!strncmp(buf, "RSP ", 4)) {
+ LOGP(DTRX, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf);
+ return 0;
+ }
+
+ /* Calculate the length of response item */
+ p = strchr(buf + 4, ' ');
+ rsp_len = p ? p - buf - 4 : strlen(buf) - 4;
+
+ LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf);
+
+ /* Abort expire timer */
+ if (osmo_timer_pending(&trx->trx_ctrl_timer))
+ osmo_timer_del(&trx->trx_ctrl_timer);
+
+ /* Get command for response message */
+ if (llist_empty(&trx->trx_ctrl_list)) {
+ LOGP(DTRX, LOGL_NOTICE, "Response message without command\n");
+ return -EINVAL;
+ }
+
+ tcm = llist_entry(trx->trx_ctrl_list.next,
+ struct trx_ctrl_msg, list);
+
+ /* Check if response matches command */
+ if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) {
+ LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
+ "Response message '%s' does not match command "
+ "message '%s'\n", buf, tcm->cmd);
+ goto rsp_error;
+ }
+
+ /* Check for response code */
+ sscanf(p + 1, "%d", &resp);
+ if (resp) {
+ LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
+ "Transceiver rejected TRX command with "
+ "response: '%s'\n", buf);
+
+ if (tcm->critical)
+ goto rsp_error;
+ }
+
+ /* Trigger state machine */
+ if (!strncmp(tcm->cmd + 4, "POWERON", 7)) {
+ trx->powered_up = true;
+ osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_ACTIVE, 0, 0);
+ }
+ else if (!strncmp(tcm->cmd + 4, "POWEROFF", 8)) {
+ trx->powered_up = false;
+ osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
+ }
+ else if (!strncmp(tcm->cmd + 4, "MEASURE", 7))
+ trx_if_measure_rsp_cb(trx, buf + 14);
+ else if (!strncmp(tcm->cmd + 4, "ECHO", 4))
+ osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
+ else
+ osmo_fsm_inst_state_chg(trx->fsm, trx->prev_state, 0, 0);
+
+ /* Remove command from list */
+ llist_del(&tcm->list);
+ talloc_free(tcm);
+
+ /* Send next message, if any */
+ trx_ctrl_send(trx);
+
+ return 0;
+
+rsp_error:
+ /* Notify higher layers about the problem */
+ osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_RSP_ERROR, trx);
+ return -EIO;
+}
+
+/* ------------------------------------------------------------------------ */
+/* Data interface handlers */
+/* ------------------------------------------------------------------------ */
+/* DATA interface */
+/* */
+/* Messages on the data interface carry one radio burst per UDP message. */
+/* */
+/* Received Data Burst: */
+/* 1 byte timeslot index */
+/* 4 bytes GSM frame number, BE */
+/* 1 byte RSSI in -dBm */
+/* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE */
+/* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1" */
+/* 2 bytes are not used, but being sent by OsmoTRX */
+/* */
+/* Transmit Data Burst: */
+/* 1 byte timeslot index */
+/* 4 bytes GSM frame number, BE */
+/* 1 byte transmit level wrt ARFCN max, -dB (attenuation) */
+/* 148 bytes output symbol values, 0 & 1 */
+/* ------------------------------------------------------------------------ */
+
+static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct trx_instance *trx = ofd->data;
+ struct trx_meas_set meas;
+ uint8_t buf[TRXD_BUF_SIZE];
+ sbit_t bits[148];
+ int8_t rssi, tn;
+ int16_t toa256;
+ uint32_t fn;
+ ssize_t read_len;
+
+
+ struct trxd_from_trx* rcvd = trxif_from_trx_d();
+ if (!rcvd) {
+ LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", rcvd);
+ return rcvd;
+ }
+
+ tn = rcvd->ts;
+ fn = rcvd->fn;
+ rssi = -(int8_t) rcvd->rssi;
+ toa256 = (int16_t) rcvd->toa;
+
+ /* Copy and convert bits {254..0} to sbits {-127..127} */
+ //osmo_ubit2sbit(bits, rcvd->symbols, 148);
+ memcpy(bits, rcvd->symbols, 148);
+
+ free(rcvd);
+
+ if (tn >= 8) {
+ LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn);
+ return -EINVAL;
+ }
+
+ if (fn >= 2715648) {
+ LOGP(DTRXD, LOGL_ERROR, "Illegal FN %u\n", fn);
+ return -EINVAL;
+ }
+
+ LOGP(DTRXD, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%d\n",
+ tn, fn, rssi, toa256);
+
+ /* Group the measurements together */
+ meas = (struct trx_meas_set) {
+ .toa256 = toa256,
+ .rssi = rssi,
+ .fn = fn,
+ };
+
+ /* Poke scheduler */
+ sched_trx_handle_rx_burst(trx, tn, fn, bits, 148, &meas);
+
+ /* Correct local clock counter */
+ if (fn % 51 == 0)
+ sched_clck_handle(&trx->sched, fn);
+
+ return 0;
+}
+
+int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
+ uint8_t pwr, const ubit_t *bits)
+{
+ struct trxd_to_trx* t = malloc(sizeof(struct trxd_to_trx));
+ t->ts = tn;
+ t->fn = fn;
+ t->txlev = pwr;
+ memcpy(t->symbols, bits, 148);
+ trxif_to_trx_d(t);
+ return 0;
+}
+
+/* Init TRX interface (TRXC, TRXD sockets and FSM) */
+struct trx_instance *trx_if_open(void *tall_ctx,
+ const char *local_host, const char *remote_host,
+ uint16_t base_port)
+{
+ struct trx_instance *trx;
+ int rc;
+
+ LOGP(DTRX, LOGL_NOTICE, "Init transceiver interface "
+ "(%s:%u)\n", remote_host, base_port);
+
+ /* Try to allocate memory */
+ trx = talloc_zero(tall_ctx, struct trx_instance);
+ if (!trx) {
+ LOGP(DTRX, LOGL_ERROR, "Failed to allocate memory\n");
+ return NULL;
+ }
+
+ /* Allocate a new dedicated state machine */
+ trx->fsm = osmo_fsm_inst_alloc(&trx_fsm, trx,
+ NULL, LOGL_DEBUG, "trx_interface");
+ if (trx->fsm == NULL) {
+ LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance "
+ "of FSM '%s'\n", trx_fsm.name);
+ talloc_free(trx);
+ return NULL;
+ }
+
+ /* Initialize CTRL queue */
+ INIT_LLIST_HEAD(&trx->trx_ctrl_list);
+
+ rc = eventfd(0, 0);
+ osmo_fd_setup(get_c_fd(), rc, OSMO_FD_READ, trx_ctrl_read_cb, trx, 0);
+ osmo_fd_register(get_c_fd());
+
+ rc = eventfd(0, 0);
+ osmo_fd_setup(get_d_fd(), rc, OSMO_FD_READ, trx_data_rx_cb, trx, 0);
+ osmo_fd_register(get_d_fd());
+
+ return trx;
+}
+
+/* Flush pending control messages */
+void trx_if_flush_ctrl(struct trx_instance *trx)
+{
+ struct trx_ctrl_msg *tcm;
+
+ /* Reset state machine */
+ osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
+
+ /* Clear command queue */
+ while (!llist_empty(&trx->trx_ctrl_list)) {
+ tcm = llist_entry(trx->trx_ctrl_list.next,
+ struct trx_ctrl_msg, list);
+ llist_del(&tcm->list);
+ talloc_free(tcm);
+ }
+}
+
+void trx_if_close(struct trx_instance *trx)
+{
+ /* May be unallocated due to init error */
+ if (!trx)
+ return;
+
+ LOGP(DTRX, LOGL_NOTICE, "Shutdown transceiver interface\n");
+
+ /* Flush CTRL message list */
+ trx_if_flush_ctrl(trx);
+
+ /* Close sockets */
+ close(get_c_fd()->fd);
+ close(get_d_fd()->fd);
+
+ /* Free memory */
+ osmo_fsm_inst_free(trx->fsm);
+ talloc_free(trx);
+}
+
+static __attribute__((constructor)) void on_dso_load(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&trx_fsm) == 0);
+}
diff --git a/trxcon/trx_if.c.backup.c b/trxcon/trx_if.c.backup.c
new file mode 100644
index 0000000..6a937ed
--- /dev/null
+++ b/trxcon/trx_if.c.backup.c
@@ -0,0 +1,816 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * Transceiver interface handlers
+ *
+ * Copyright (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netinet/in.h>
+
+#include <sys/eventfd.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include "l1ctl.h"
+#include "trxcon.h"
+#include "trx_if.h"
+#include "logging.h"
+#include "scheduler.h"
+
+#include "../Transceiver52M/l1if.h"
+
+static struct value_string trx_evt_names[] = {
+ { 0, NULL } /* no events? */
+};
+
+static struct osmo_fsm_state trx_fsm_states[] = {
+ [TRX_STATE_OFFLINE] = {
+ .out_state_mask = (
+ GEN_MASK(TRX_STATE_IDLE) |
+ GEN_MASK(TRX_STATE_RSP_WAIT)),
+ .name = "OFFLINE",
+ },
+ [TRX_STATE_IDLE] = {
+ .out_state_mask = UINT32_MAX,
+ .name = "IDLE",
+ },
+ [TRX_STATE_ACTIVE] = {
+ .out_state_mask = (
+ GEN_MASK(TRX_STATE_IDLE) |
+ GEN_MASK(TRX_STATE_RSP_WAIT)),
+ .name = "ACTIVE",
+ },
+ [TRX_STATE_RSP_WAIT] = {
+ .out_state_mask = (
+ GEN_MASK(TRX_STATE_IDLE) |
+ GEN_MASK(TRX_STATE_ACTIVE) |
+ GEN_MASK(TRX_STATE_OFFLINE)),
+ .name = "RSP_WAIT",
+ },
+};
+
+static struct osmo_fsm trx_fsm = {
+ .name = "trx_interface_fsm",
+ .states = trx_fsm_states,
+ .num_states = ARRAY_SIZE(trx_fsm_states),
+ .log_subsys = DTRX,
+ .event_names = trx_evt_names,
+};
+
+static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local,
+ uint16_t port_local, const char *host_remote, uint16_t port_remote,
+ int (*cb)(struct osmo_fd *fd, unsigned int what))
+{
+ int rc;
+
+ ofd->data = priv;
+ ofd->fd = -1;
+ ofd->cb = cb;
+
+ /* Init UDP Connection */
+ rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, host_local, port_local,
+ host_remote, port_remote,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
+ return rc;
+}
+
+static void trx_udp_close(struct osmo_fd *ofd)
+{
+ if (ofd->fd > 0) {
+ osmo_fd_unregister(ofd);
+ close(ofd->fd);
+ ofd->fd = -1;
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+/* Control (CTRL) interface handlers */
+/* ------------------------------------------------------------------------ */
+/* Commands on the Per-ARFCN Control Interface */
+/* */
+/* The per-ARFCN control interface uses a command-response protocol. */
+/* Commands are NULL-terminated ASCII strings, one per UDP socket. */
+/* Each command has a corresponding response. */
+/* Every command is of the form: */
+/* */
+/* CMD <cmdtype> [params] */
+/* */
+/* The <cmdtype> is the actual command. */
+/* Parameters are optional depending on the commands type. */
+/* Every response is of the form: */
+/* */
+/* RSP <cmdtype> <status> [result] */
+/* */
+/* The <status> is 0 for success and a non-zero error code for failure. */
+/* Successful responses may include results, depending on the command type. */
+/* ------------------------------------------------------------------------ */
+
+static void trx_ctrl_timer_cb(void *data);
+
+/* Send first CTRL message and start timer */
+static void trx_ctrl_send(struct trx_instance *trx)
+{
+ struct trx_ctrl_msg *tcm;
+
+ if (llist_empty(&trx->trx_ctrl_list))
+ return;
+ tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
+
+ char* cmd = malloc(TRXC_BUF_SIZE);
+ memcpy(cmd, tcm->cmd, TRXC_BUF_SIZE);
+
+ /* Send command */
+ LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
+ trxif_to_trx_c(cmd);
+// send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0);
+
+ /* Trigger state machine */
+ if (trx->fsm->state != TRX_STATE_RSP_WAIT) {
+ trx->prev_state = trx->fsm->state;
+ osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_RSP_WAIT, 0, 0);
+ }
+
+ /* Start expire timer */
+ trx->trx_ctrl_timer.data = trx;
+ trx->trx_ctrl_timer.cb = trx_ctrl_timer_cb;
+ osmo_timer_schedule(&trx->trx_ctrl_timer, 2, 0);
+}
+
+static void trx_ctrl_timer_cb(void *data)
+{
+ struct trx_instance *trx = (struct trx_instance *) data;
+ struct trx_ctrl_msg *tcm;
+
+ /* Queue may be cleaned at this moment */
+ if (llist_empty(&trx->trx_ctrl_list))
+ return;
+
+ LOGP(DTRX, LOGL_NOTICE, "No response from transceiver...\n");
+
+ tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
+ if (++tcm->retry_cnt > 3) {
+ LOGP(DTRX, LOGL_NOTICE, "Transceiver offline\n");
+ osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_OFFLINE, 0, 0);
+ osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_OFFLINE, trx);
+ return;
+ }
+
+ /* Attempt to send a command again */
+ trx_ctrl_send(trx);
+}
+
+/* Add a new CTRL command to the trx_ctrl_list */
+static int trx_ctrl_cmd(struct trx_instance *trx, int critical,
+ const char *cmd, const char *fmt, ...)
+{
+ struct trx_ctrl_msg *tcm;
+ int len, pending = 0;
+ va_list ap;
+
+ /* TODO: make sure that transceiver online */
+
+ if (!llist_empty(&trx->trx_ctrl_list))
+ pending = 1;
+
+ /* Allocate a message */
+ tcm = talloc_zero(trx, struct trx_ctrl_msg);
+ if (!tcm)
+ return -ENOMEM;
+
+ /* Fill in command arguments */
+ if (fmt && fmt[0]) {
+ len = snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s ", cmd);
+ va_start(ap, fmt);
+ vsnprintf(tcm->cmd + len, sizeof(tcm->cmd) - len - 1, fmt, ap);
+ va_end(ap);
+ } else {
+ snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s", cmd);
+ }
+
+ tcm->cmd_len = strlen(cmd);
+ tcm->critical = critical;
+ llist_add_tail(&tcm->list, &trx->trx_ctrl_list);
+ LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd);
+
+ /* Send message, if no pending messages */
+ if (!pending)
+ trx_ctrl_send(trx);
+
+ return 0;
+}
+
+/*
+ * Power Control
+ *
+ * ECHO is used to check transceiver availability.
+ * CMD ECHO
+ * RSP ECHO <status>
+ *
+ * POWEROFF shuts off transmitter power and stops the demodulator.
+ * CMD POWEROFF
+ * RSP POWEROFF <status>
+ *
+ * POWERON starts the transmitter and starts the demodulator.
+ * Initial power level is very low.
+ * This command fails if the transmitter and receiver are not yet tuned.
+ * This command fails if the transmit or receive frequency creates a conflict
+ * with another ARFCN that is already running.
+ * If the transceiver is already on, it response with success to this command.
+ * CMD POWERON
+ * RSP POWERON <status>
+ */
+
+int trx_if_cmd_sync(struct trx_instance *trx)
+{
+ return trx_ctrl_cmd(trx, 1, "SYNC", "");
+}
+
+int trx_if_cmd_echo(struct trx_instance *trx)
+{
+ return trx_ctrl_cmd(trx, 1, "ECHO", "");
+}
+
+int trx_if_cmd_poweroff(struct trx_instance *trx)
+{
+ return trx_ctrl_cmd(trx, 1, "POWEROFF", "");
+}
+
+int trx_if_cmd_poweron(struct trx_instance *trx)
+{
+ if (trx->powered_up) {
+ /* FIXME: this should be handled by the FSM, not here! */
+ LOGP(DTRX, LOGL_ERROR, "Suppressing POWERON as we're already powered up\n");
+ return -EAGAIN;
+ }
+ return trx_ctrl_cmd(trx, 1, "POWERON", "");
+}
+
+/*
+ * Timeslot Control
+ *
+ * SETSLOT sets the format of the uplink timeslots in the ARFCN.
+ * The <timeslot> indicates the timeslot of interest.
+ * The <chantype> indicates the type of channel that occupies the timeslot.
+ * A chantype of zero indicates the timeslot is off.
+ * CMD SETSLOT <timeslot> <chantype>
+ * RSP SETSLOT <status> <timeslot> <chantype>
+ */
+
+int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type)
+{
+ return trx_ctrl_cmd(trx, 1, "SETSLOT", "%u %u", tn, type);
+}
+
+/*
+ * Tuning Control
+ *
+ * (RX/TX)TUNE tunes the receiver to a given frequency in kHz.
+ * This command fails if the receiver is already running.
+ * (To re-tune you stop the radio, re-tune, and restart.)
+ * This command fails if the transmit or receive frequency
+ * creates a conflict with another ARFCN that is already running.
+ * CMD (RX/TX)TUNE <kHz>
+ * RSP (RX/TX)TUNE <status> <kHz>
+ */
+
+int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn)
+{
+ uint16_t freq10;
+
+ /* RX is downlink on MS side */
+ freq10 = gsm_arfcn2freq10(band_arfcn, 0);
+ if (freq10 == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
+ return -ENOTSUP;
+ }
+
+ return trx_ctrl_cmd(trx, 1, "RXTUNE", "%u", freq10 * 100);
+}
+
+int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn)
+{
+ uint16_t freq10;
+
+ /* TX is uplink on MS side */
+ freq10 = gsm_arfcn2freq10(band_arfcn, 1);
+ if (freq10 == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
+ return -ENOTSUP;
+ }
+
+ return trx_ctrl_cmd(trx, 1, "TXTUNE", "%u", freq10 * 100);
+}
+
+/*
+ * Power measurement
+ *
+ * MEASURE instructs the transceiver to perform a power
+ * measurement on specified frequency. After receiving this
+ * request, transceiver should quickly re-tune to requested
+ * frequency, measure power level and re-tune back to the
+ * previous frequency.
+ * CMD MEASURE <kHz>
+ * RSP MEASURE <status> <kHz> <dB>
+ */
+
+int trx_if_cmd_measure(struct trx_instance *trx,
+ uint16_t band_arfcn_start, uint16_t band_arfcn_stop)
+{
+ uint16_t freq10;
+
+ /* Update ARFCN range for measurement */
+ trx->pm_band_arfcn_start = band_arfcn_start;
+ trx->pm_band_arfcn_stop = band_arfcn_stop;
+
+ /* Calculate a frequency for current ARFCN (DL) */
+ freq10 = gsm_arfcn2freq10(band_arfcn_start, 0);
+ if (freq10 == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn_start);
+ return -ENOTSUP;
+ }
+
+ return trx_ctrl_cmd(trx, 1, "MEASURE", "%u", freq10 * 100);
+}
+
+static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp)
+{
+ unsigned int freq10;
+ uint16_t band_arfcn;
+ int dbm;
+
+ /* Parse freq. and power level */
+ sscanf(resp, "%u %d", &freq10, &dbm);
+ freq10 /= 100;
+
+ /* Check received ARFCN against expected */
+ band_arfcn = gsm_freq102arfcn((uint16_t) freq10, 0);
+ if (band_arfcn != trx->pm_band_arfcn_start) {
+ LOGP(DTRX, LOGL_ERROR, "Power measurement error: "
+ "response ARFCN=%u doesn't match expected ARFCN=%u\n",
+ band_arfcn &~ ARFCN_FLAG_MASK,
+ trx->pm_band_arfcn_start &~ ARFCN_FLAG_MASK);
+ return;
+ }
+
+ /* Send L1CTL_PM_CONF */
+ l1ctl_tx_pm_conf(trx->l1l, band_arfcn, dbm,
+ band_arfcn == trx->pm_band_arfcn_stop);
+
+ /* Schedule a next measurement */
+ if (band_arfcn != trx->pm_band_arfcn_stop)
+ trx_if_cmd_measure(trx, ++band_arfcn, trx->pm_band_arfcn_stop);
+}
+
+/*
+ * Timing Advance control
+ *
+ * SETTA instructs the transceiver to transmit bursts in
+ * advance calculated from requested TA value. This value is
+ * normally between 0 and 63, with each step representing
+ * an advance of one bit period (about 3.69 microseconds).
+ * Since OsmocomBB has a special feature, which allows one
+ * to spoof the distance from BTS, the range is extended.
+ * CMD SETTA <-128..127>
+ * RSP SETTA <status> <TA>
+ */
+
+int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta)
+{
+ return trx_ctrl_cmd(trx, 0, "SETTA", "%d", ta);
+}
+
+/*
+ * Frequency Hopping parameters indication.
+ *
+ * SETFH instructs transceiver to enable frequency hopping mode
+ * using the given HSN, MAIO, and Mobile Allocation parameters.
+ *
+ * CMD SETFH <HSN> <MAIO> <RXF1> <TXF1> [... <RXFN> <TXFN>]
+ *
+ * where <RXFN> and <TXFN> is a pair of Rx/Tx frequencies (in kHz)
+ * corresponding to one ARFCN the Mobile Allocation. Note that the
+ * channel list is expected to be sorted in ascending order.
+ */
+
+int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
+ uint8_t maio, uint16_t *ma, size_t ma_len)
+{
+ /* Reserve some room for CMD SETFH <HSN> <MAIO> */
+ char ma_buf[TRXC_BUF_SIZE - 24];
+ size_t ma_buf_len = sizeof(ma_buf) - 1;
+ uint16_t rx_freq, tx_freq;
+ char *ptr;
+ int i, rc;
+
+ /* Make sure that Mobile Allocation has at least one ARFCN */
+ if (!ma_len || ma == NULL) {
+ LOGP(DTRX, LOGL_ERROR, "Mobile Allocation is empty?!?\n");
+ return -EINVAL;
+ }
+
+ /* Compose a sequence of Rx/Tx frequencies (mobile allocation) */
+ for (i = 0, ptr = ma_buf; i < ma_len; i++) {
+ /* Convert ARFCN to a pair of Rx/Tx frequencies (Hz * 10) */
+ rx_freq = gsm_arfcn2freq10(ma[i], 0); /* Rx: Downlink */
+ tx_freq = gsm_arfcn2freq10(ma[i], 1); /* Tx: Uplink */
+ if (rx_freq == 0xffff || tx_freq == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "Failed to convert ARFCN %u "
+ "to a pair of Rx/Tx frequencies\n",
+ ma[i] & ~ARFCN_FLAG_MASK);
+ return -EINVAL;
+ }
+
+ /* Append a pair of Rx/Tx frequencies (in kHz) to the buffer */
+ rc = snprintf(ptr, ma_buf_len, "%u %u ", rx_freq * 100, tx_freq * 100);
+ if (rc < 0 || rc > ma_buf_len) { /* Prevent buffer overflow */
+ LOGP(DTRX, LOGL_ERROR, "Not enough room to encode "
+ "Mobile Allocation (N=%zu)\n", ma_len);
+ return -ENOSPC;
+ }
+
+ /* Move pointer */
+ ma_buf_len -= rc;
+ ptr += rc;
+ }
+
+ /* Overwrite the last space */
+ *(ptr - 1) = '\0';
+
+ return trx_ctrl_cmd(trx, 1, "SETFH", "%u %u %s", hsn, maio, ma_buf);
+}
+
+/* Get response from CTRL socket */
+static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct trx_instance *trx = ofd->data;
+ struct trx_ctrl_msg *tcm;
+ int resp, rsp_len;
+ char buf[TRXC_BUF_SIZE], *p;
+ ssize_t read_len;
+
+// read_len = read(ofd->fd, buf, sizeof(buf) - 1);
+// if (read_len <= 0) {
+// LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
+// return read_len;
+// }
+// buf[read_len] = '\0';
+
+ LOGP(DTRX, LOGL_NOTICE, "C wat: %d\n", what);
+ char* response = trxif_from_trx_c();
+ if (!response) {
+ LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", response);
+ return response;
+ }
+ memcpy(buf, response, TRXC_BUF_SIZE);
+ free(response);
+
+ if (!!strncmp(buf, "RSP ", 4)) {
+ LOGP(DTRX, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf);
+ return 0;
+ }
+
+ /* Calculate the length of response item */
+ p = strchr(buf + 4, ' ');
+ rsp_len = p ? p - buf - 4 : strlen(buf) - 4;
+
+ LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf);
+
+ /* Abort expire timer */
+ if (osmo_timer_pending(&trx->trx_ctrl_timer))
+ osmo_timer_del(&trx->trx_ctrl_timer);
+
+ /* Get command for response message */
+ if (llist_empty(&trx->trx_ctrl_list)) {
+ LOGP(DTRX, LOGL_NOTICE, "Response message without command\n");
+ return -EINVAL;
+ }
+
+ tcm = llist_entry(trx->trx_ctrl_list.next,
+ struct trx_ctrl_msg, list);
+
+ /* Check if response matches command */
+ if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) {
+ LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
+ "Response message '%s' does not match command "
+ "message '%s'\n", buf, tcm->cmd);
+ goto rsp_error;
+ }
+
+ /* Check for response code */
+ sscanf(p + 1, "%d", &resp);
+ if (resp) {
+ LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
+ "Transceiver rejected TRX command with "
+ "response: '%s'\n", buf);
+
+ if (tcm->critical)
+ goto rsp_error;
+ }
+
+ /* Trigger state machine */
+ if (!strncmp(tcm->cmd + 4, "POWERON", 7)) {
+ trx->powered_up = true;
+ osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_ACTIVE, 0, 0);
+ }
+ else if (!strncmp(tcm->cmd + 4, "POWEROFF", 8)) {
+ trx->powered_up = false;
+ osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
+ }
+ else if (!strncmp(tcm->cmd + 4, "MEASURE", 7))
+ trx_if_measure_rsp_cb(trx, buf + 14);
+ else if (!strncmp(tcm->cmd + 4, "ECHO", 4))
+ osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
+ else
+ osmo_fsm_inst_state_chg(trx->fsm, trx->prev_state, 0, 0);
+
+ /* Remove command from list */
+ llist_del(&tcm->list);
+ talloc_free(tcm);
+
+ /* Send next message, if any */
+ trx_ctrl_send(trx);
+
+ return 0;
+
+rsp_error:
+ /* Notify higher layers about the problem */
+ osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_RSP_ERROR, trx);
+ return -EIO;
+}
+
+/* ------------------------------------------------------------------------ */
+/* Data interface handlers */
+/* ------------------------------------------------------------------------ */
+/* DATA interface */
+/* */
+/* Messages on the data interface carry one radio burst per UDP message. */
+/* */
+/* Received Data Burst: */
+/* 1 byte timeslot index */
+/* 4 bytes GSM frame number, BE */
+/* 1 byte RSSI in -dBm */
+/* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE */
+/* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1" */
+/* 2 bytes are not used, but being sent by OsmoTRX */
+/* */
+/* Transmit Data Burst: */
+/* 1 byte timeslot index */
+/* 4 bytes GSM frame number, BE */
+/* 1 byte transmit level wrt ARFCN max, -dB (attenuation) */
+/* 148 bytes output symbol values, 0 & 1 */
+/* ------------------------------------------------------------------------ */
+
+static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct trx_instance *trx = ofd->data;
+ struct trx_meas_set meas;
+ uint8_t buf[TRXD_BUF_SIZE];
+ sbit_t bits[148];
+ int8_t rssi, tn;
+ int16_t toa256;
+ uint32_t fn;
+ ssize_t read_len;
+
+
+
+// read_len = read(ofd->fd, buf, sizeof(buf));
+// if (read_len <= 0) {
+// LOGP(DTRXD, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
+// return read_len;
+// }
+
+// if (read_len != 158) {
+// LOGP(DTRXD, LOGL_ERROR, "Got data message with invalid "
+// "length '%zd'\n", read_len);
+// return -EINVAL;
+// }
+
+// tn = buf[0];
+// fn = osmo_load32be(buf + 1);
+// rssi = -(int8_t) buf[5];
+// toa256 = ((int16_t) (buf[6] << 8) | buf[7]);
+
+// /* Copy and convert bits {254..0} to sbits {-127..127} */
+// osmo_ubit2sbit(bits, buf + 8, 148);
+
+ struct trxd_from_trx* rcvd = trxif_from_trx_d();
+ if (!rcvd) {
+ LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", rcvd);
+ return rcvd;
+ }
+
+ tn = rcvd->ts;
+ fn = rcvd->fn;
+ rssi = -(int8_t) rcvd->rssi;
+ toa256 = (int16_t) rcvd->toa;
+
+ /* Copy and convert bits {254..0} to sbits {-127..127} */
+ //osmo_ubit2sbit(bits, rcvd->symbols, 148);
+ memcpy(bits, rcvd->symbols, 148);
+
+ free(rcvd);
+
+ if (tn >= 8) {
+ LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn);
+ return -EINVAL;
+ }
+
+ if (fn >= 2715648) {
+ LOGP(DTRXD, LOGL_ERROR, "Illegal FN %u\n", fn);
+ return -EINVAL;
+ }
+
+ LOGP(DTRXD, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%d\n",
+ tn, fn, rssi, toa256);
+
+ /* Group the measurements together */
+ meas = (struct trx_meas_set) {
+ .toa256 = toa256,
+ .rssi = rssi,
+ .fn = fn,
+ };
+
+ /* Poke scheduler */
+ sched_trx_handle_rx_burst(trx, tn, fn, bits, 148, &meas);
+
+ /* Correct local clock counter */
+ if (fn % 51 == 0)
+ sched_clck_handle(&trx->sched, fn);
+
+ return 0;
+}
+
+int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
+ uint8_t pwr, const ubit_t *bits)
+{
+// uint8_t buf[TRXD_BUF_SIZE];
+
+// /**
+// * We must be sure that we have clock,
+// * and we have sent all control data
+// *
+// * TODO: introduce proper state machines for both
+// * transceiver and its TRXC interface.
+// */
+//#if 0
+// if (trx->fsm->state != TRX_STATE_ACTIVE) {
+// LOGP(DTRXD, LOGL_ERROR, "Ignoring TX data, "
+// "transceiver isn't ready\n");
+// return -EAGAIN;
+// }
+//#endif
+
+// LOGP(DTRXD, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr);
+
+// buf[0] = tn;
+// osmo_store32be(fn, buf + 1);
+// buf[5] = pwr;
+
+// /* Copy ubits {0,1} */
+// memcpy(buf + 6, bits, 148);
+
+// /* Send data to transceiver */
+// send(trx->trx_ofd_data.fd, buf, 154, 0);
+
+ struct trxd_to_trx* t = malloc(sizeof(struct trxd_to_trx));
+ t->ts = tn;
+ t->fn = fn;
+ t->txlev = pwr;
+ memcpy(t->symbols, bits, 148);
+ trxif_to_trx_d(t);
+ return 0;
+}
+
+/* Init TRX interface (TRXC, TRXD sockets and FSM) */
+struct trx_instance *trx_if_open(void *tall_ctx,
+ const char *local_host, const char *remote_host,
+ uint16_t base_port)
+{
+ struct trx_instance *trx;
+ int rc;
+
+ LOGP(DTRX, LOGL_NOTICE, "Init transceiver interface "
+ "(%s:%u)\n", remote_host, base_port);
+
+ /* Try to allocate memory */
+ trx = talloc_zero(tall_ctx, struct trx_instance);
+ if (!trx) {
+ LOGP(DTRX, LOGL_ERROR, "Failed to allocate memory\n");
+ return NULL;
+ }
+
+ /* Allocate a new dedicated state machine */
+ trx->fsm = osmo_fsm_inst_alloc(&trx_fsm, trx,
+ NULL, LOGL_DEBUG, "trx_interface");
+ if (trx->fsm == NULL) {
+ LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance "
+ "of FSM '%s'\n", trx_fsm.name);
+ talloc_free(trx);
+ return NULL;
+ }
+
+ /* Initialize CTRL queue */
+ INIT_LLIST_HEAD(&trx->trx_ctrl_list);
+
+ rc = eventfd(0, 0);
+ osmo_fd_setup(get_c_fd(), rc, OSMO_FD_READ, trx_ctrl_read_cb, trx, 0);
+ osmo_fd_register(get_c_fd());
+
+ rc = eventfd(0, 0);
+ osmo_fd_setup(get_d_fd(), rc, OSMO_FD_READ, trx_data_rx_cb, trx, 0);
+ osmo_fd_register(get_d_fd());
+
+// /* Open sockets */
+// rc = trx_udp_open(trx, &trx->trx_ofd_ctrl, local_host,
+// base_port + 101, remote_host, base_port + 1, trx_ctrl_read_cb);
+// if (rc < 0)
+// goto udp_error;
+
+// rc = trx_udp_open(trx, &trx->trx_ofd_data, local_host,
+// base_port + 102, remote_host, base_port + 2, trx_data_rx_cb);
+// if (rc < 0)
+// goto udp_error;
+
+ return trx;
+
+//udp_error:
+// LOGP(DTRX, LOGL_ERROR, "Couldn't establish UDP connection\n");
+// osmo_fsm_inst_free(trx->fsm);
+// talloc_free(trx);
+// return NULL;
+}
+
+/* Flush pending control messages */
+void trx_if_flush_ctrl(struct trx_instance *trx)
+{
+ struct trx_ctrl_msg *tcm;
+
+ /* Reset state machine */
+ osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
+
+ /* Clear command queue */
+ while (!llist_empty(&trx->trx_ctrl_list)) {
+ tcm = llist_entry(trx->trx_ctrl_list.next,
+ struct trx_ctrl_msg, list);
+ llist_del(&tcm->list);
+ talloc_free(tcm);
+ }
+}
+
+void trx_if_close(struct trx_instance *trx)
+{
+ /* May be unallocated due to init error */
+ if (!trx)
+ return;
+
+ LOGP(DTRX, LOGL_NOTICE, "Shutdown transceiver interface\n");
+
+ /* Flush CTRL message list */
+ trx_if_flush_ctrl(trx);
+
+ /* Close sockets */
+ close(get_c_fd()->fd);
+ close(get_d_fd()->fd);
+
+// trx_udp_close(&trx->trx_ofd_ctrl);
+// trx_udp_close(&trx->trx_ofd_data);
+
+ /* Free memory */
+ osmo_fsm_inst_free(trx->fsm);
+ talloc_free(trx);
+}
+
+static __attribute__((constructor)) void on_dso_load(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&trx_fsm) == 0);
+}
diff --git a/trxcon/trx_if.h b/trxcon/trx_if.h
new file mode 100644
index 0000000..58f76ed
--- /dev/null
+++ b/trxcon/trx_if.h
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/fsm.h>
+
+#include "scheduler.h"
+#include "sched_trx.h"
+
+#define TRXC_BUF_SIZE 1024
+#define TRXD_BUF_SIZE 512
+
+/* Forward declaration to avoid mutual include */
+struct l1ctl_link;
+
+enum trx_fsm_states {
+ TRX_STATE_OFFLINE = 0,
+ TRX_STATE_IDLE,
+ TRX_STATE_ACTIVE,
+ TRX_STATE_RSP_WAIT,
+};
+
+struct trx_instance {
+// struct osmo_fd trx_ofd_ctrl;
+// struct osmo_fd trx_ofd_data;
+
+ struct osmo_timer_list trx_ctrl_timer;
+ struct llist_head trx_ctrl_list;
+ struct osmo_fsm_inst *fsm;
+
+ /* HACK: we need proper state machines */
+ uint32_t prev_state;
+ bool powered_up;
+
+ /* GSM L1 specific */
+ uint16_t pm_band_arfcn_start;
+ uint16_t pm_band_arfcn_stop;
+ uint16_t band_arfcn;
+ uint8_t tx_power;
+ uint8_t bsic;
+ uint8_t tsc;
+ int8_t ta;
+
+ /* Scheduler stuff */
+ struct trx_sched sched;
+ struct trx_ts *ts_list[TRX_TS_COUNT];
+
+ /* Bind L1CTL link */
+ struct l1ctl_link *l1l;
+};
+
+struct trx_ctrl_msg {
+ struct llist_head list;
+ char cmd[TRXC_BUF_SIZE];
+ int retry_cnt;
+ int critical;
+ int cmd_len;
+};
+
+struct trx_instance *trx_if_open(void *tall_ctx,
+ const char *local_host, const char *remote_host, uint16_t port);
+void trx_if_flush_ctrl(struct trx_instance *trx);
+void trx_if_close(struct trx_instance *trx);
+
+int trx_if_cmd_poweron(struct trx_instance *trx);
+int trx_if_cmd_poweroff(struct trx_instance *trx);
+int trx_if_cmd_echo(struct trx_instance *trx);
+int trx_if_cmd_sync(struct trx_instance *trx);
+
+int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta);
+
+int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn);
+int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn);
+
+int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type);
+int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
+ uint8_t maio, uint16_t *ma, size_t ma_len);
+
+int trx_if_cmd_measure(struct trx_instance *trx,
+ uint16_t band_arfcn_start, uint16_t band_arfcn_stop);
+
+int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
+ uint8_t pwr, const ubit_t *bits);
diff --git a/trxcon/trxcon.c b/trxcon/trxcon.c
new file mode 100644
index 0000000..3005a93
--- /dev/null
+++ b/trxcon/trxcon.c
@@ -0,0 +1,391 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ *
+ * (C) 2016-2020 by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <signal.h>
+#include <time.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/gsmtap.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include "trxcon.h"
+#include "trx_if.h"
+#include "logging.h"
+#include "l1ctl.h"
+#include "l1ctl_link.h"
+#include "l1ctl_proto.h"
+#include "scheduler.h"
+#include "sched_trx.h"
+
+#define COPYRIGHT \
+ "Copyright (C) 2016-2020 by Vadim Yanitskiy <axilirator@gmail.com>\n" \
+ "License GPLv2+: GNU GPL version 2 or later " \
+ "<http://gnu.org/licenses/gpl.html>\n" \
+ "This is free software: you are free to change and redistribute it.\n" \
+ "There is NO WARRANTY, to the extent permitted by law.\n\n"
+
+static struct {
+ const char *debug_mask;
+ int daemonize;
+ int quit;
+
+ /* L1CTL specific */
+ struct l1ctl_link *l1l;
+ const char *bind_socket;
+
+ /* TRX specific */
+ struct trx_instance *trx;
+ const char *trx_bind_ip;
+ const char *trx_remote_ip;
+ uint16_t trx_base_port;
+ uint32_t trx_fn_advance;
+ const char *gsmtap_ip;
+} app_data;
+
+static void *tall_trxcon_ctx = NULL;
+struct gsmtap_inst *gsmtap = NULL;
+struct osmo_fsm_inst *trxcon_fsm;
+
+static void trxcon_fsm_idle_action(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ if (event == L1CTL_EVENT_CONNECT)
+ osmo_fsm_inst_state_chg(trxcon_fsm, TRXCON_STATE_MANAGED, 0, 0);
+}
+
+static void trxcon_fsm_managed_action(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ switch (event) {
+ case L1CTL_EVENT_DISCONNECT:
+ osmo_fsm_inst_state_chg(trxcon_fsm, TRXCON_STATE_IDLE, 0, 0);
+
+ if (app_data.trx->fsm->state != TRX_STATE_OFFLINE) {
+ /* Reset scheduler and clock counter */
+ sched_trx_reset(app_data.trx, true);
+
+ /* TODO: implement trx_if_reset() */
+ trx_if_cmd_poweroff(app_data.trx);
+ trx_if_cmd_echo(app_data.trx);
+ }
+ break;
+ case TRX_EVENT_RSP_ERROR:
+ case TRX_EVENT_OFFLINE:
+ /* TODO: notify L2 & L3 about that */
+ break;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "Unhandled event %u\n", event);
+ }
+}
+
+static struct osmo_fsm_state trxcon_fsm_states[] = {
+ [TRXCON_STATE_IDLE] = {
+ .in_event_mask = GEN_MASK(L1CTL_EVENT_CONNECT),
+ .out_state_mask = GEN_MASK(TRXCON_STATE_MANAGED),
+ .name = "IDLE",
+ .action = trxcon_fsm_idle_action,
+ },
+ [TRXCON_STATE_MANAGED] = {
+ .in_event_mask = (
+ GEN_MASK(L1CTL_EVENT_DISCONNECT) |
+ GEN_MASK(TRX_EVENT_RSP_ERROR) |
+ GEN_MASK(TRX_EVENT_OFFLINE)),
+ .out_state_mask = GEN_MASK(TRXCON_STATE_IDLE),
+ .name = "MANAGED",
+ .action = trxcon_fsm_managed_action,
+ },
+};
+
+static const struct value_string app_evt_names[] = {
+ OSMO_VALUE_STRING(L1CTL_EVENT_CONNECT),
+ OSMO_VALUE_STRING(L1CTL_EVENT_DISCONNECT),
+ OSMO_VALUE_STRING(TRX_EVENT_OFFLINE),
+ OSMO_VALUE_STRING(TRX_EVENT_RSP_ERROR),
+ { 0, NULL }
+};
+
+static struct osmo_fsm trxcon_fsm_def = {
+ .name = "trxcon_app_fsm",
+ .states = trxcon_fsm_states,
+ .num_states = ARRAY_SIZE(trxcon_fsm_states),
+ .log_subsys = DAPP,
+ .event_names = app_evt_names,
+};
+
+static void print_usage(const char *app)
+{
+ printf("Usage: %s\n", app);
+}
+
+static void print_help(void)
+{
+ printf(" Some help...\n");
+ printf(" -h --help this text\n");
+ printf(" -d --debug Change debug flags. Default: %s\n", DEBUG_DEFAULT);
+ printf(" -b --trx-bind TRX bind IP address (default 0.0.0.0)\n");
+ printf(" -i --trx-remote TRX remote IP address (default 127.0.0.1)\n");
+ printf(" -p --trx-port Base port of TRX instance (default 6700)\n");
+ printf(" -f --trx-advance Uplink burst scheduling advance (default 3)\n");
+ printf(" -s --socket Listening socket for layer23 (default /tmp/osmocom_l2)\n");
+ printf(" -g --gsmtap-ip The destination IP used for GSMTAP (disabled by default)\n");
+ printf(" -D --daemonize Run as daemon\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+ while (1) {
+ int option_index = 0, c;
+ static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"debug", 1, 0, 'd'},
+ {"socket", 1, 0, 's'},
+ {"trx-bind", 1, 0, 'b'},
+ /* NOTE: 'trx-ip' is now an alias for 'trx-remote'
+ * due to backward compatibility reasons! */
+ {"trx-ip", 1, 0, 'i'},
+ {"trx-remote", 1, 0, 'i'},
+ {"trx-port", 1, 0, 'p'},
+ {"trx-advance", 1, 0, 'f'},
+ {"gsmtap-ip", 1, 0, 'g'},
+ {"daemonize", 0, 0, 'D'},
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long(argc, argv, "d:b:i:p:f:s:g:Dh",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_usage(argv[0]);
+ print_help();
+ exit(0);
+ break;
+ case 'd':
+ app_data.debug_mask = optarg;
+ break;
+ case 'b':
+ app_data.trx_bind_ip = optarg;
+ break;
+ case 'i':
+ app_data.trx_remote_ip = optarg;
+ break;
+ case 'p':
+ app_data.trx_base_port = atoi(optarg);
+ break;
+ case 'f':
+ app_data.trx_fn_advance = atoi(optarg);
+ break;
+ case 's':
+ app_data.bind_socket = optarg;
+ break;
+ case 'g':
+ app_data.gsmtap_ip = optarg;
+ break;
+ case 'D':
+ app_data.daemonize = 1;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static void init_defaults(void)
+{
+ app_data.bind_socket = "/tmp/osmocom_l2";
+ app_data.trx_remote_ip = "127.0.0.1";
+ app_data.trx_bind_ip = "0.0.0.0";
+ app_data.trx_base_port = 6700;
+ app_data.trx_fn_advance = 3;
+
+ app_data.debug_mask = NULL;
+ app_data.gsmtap_ip = NULL;
+ app_data.daemonize = 0;
+ app_data.quit = 0;
+}
+
+static void signal_handler(int signum)
+{
+ fprintf(stderr, "signal %u received\n", signum);
+
+ switch (signum) {
+ case SIGINT:
+ app_data.quit++;
+ break;
+ case SIGABRT:
+ /* in case of abort, we want to obtain a talloc report and
+ * then run default SIGABRT handler, who will generate coredump
+ * and abort the process. abort() should do this for us after we
+ * return, but program wouldn't exit if an external SIGABRT is
+ * received.
+ */
+ talloc_report_full(tall_trxcon_ctx, stderr);
+ signal(SIGABRT, SIG_DFL);
+ raise(SIGABRT);
+ break;
+ case SIGUSR1:
+ case SIGUSR2:
+ talloc_report_full(tall_trxcon_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+extern void init_external_transceiver(int argc, char **argv);
+extern void stop_trx();
+extern volatile bool gshutdown;
+
+int main(int argc, char **argv)
+{
+ int rc = 0;
+
+ printf("%s", COPYRIGHT);
+ init_defaults();
+ handle_options(argc, argv);
+
+ /* Track the use of talloc NULL memory contexts */
+ talloc_enable_null_tracking();
+
+ /* Init talloc memory management system */
+ tall_trxcon_ctx = talloc_init("trxcon context");
+ msgb_talloc_ctx_init(tall_trxcon_ctx, 0);
+
+ /* Setup signal handlers */
+// signal(SIGINT, &signal_handler);
+// signal(SIGABRT, &signal_handler);
+// signal(SIGUSR1, &signal_handler);
+// signal(SIGUSR2, &signal_handler);
+ osmo_init_ignore_signals();
+
+ /* Init logging system */
+ trx_log_init(tall_trxcon_ctx, app_data.debug_mask);
+
+ /* Configure pretty logging */
+ log_set_print_extended_timestamp(osmo_stderr_target, 1);
+ log_set_print_category_hex(osmo_stderr_target, 0);
+ log_set_print_category(osmo_stderr_target, 1);
+ log_set_print_level(osmo_stderr_target, 1);
+
+ /* Optional GSMTAP */
+ if (app_data.gsmtap_ip != NULL) {
+ gsmtap = gsmtap_source_init(app_data.gsmtap_ip, GSMTAP_UDP_PORT, 1);
+ if (!gsmtap) {
+ LOGP(DAPP, LOGL_ERROR, "Failed to init GSMTAP\n");
+ goto exit;
+ }
+ /* Suppress ICMP "destination unreachable" errors */
+ gsmtap_source_add_sink(gsmtap);
+ }
+
+ /* Allocate the application state machine */
+ OSMO_ASSERT(osmo_fsm_register(&trxcon_fsm_def) == 0);
+ trxcon_fsm = osmo_fsm_inst_alloc(&trxcon_fsm_def, tall_trxcon_ctx,
+ NULL, LOGL_DEBUG, "main");
+
+ /* Init L1CTL server */
+ app_data.l1l = l1ctl_link_init(tall_trxcon_ctx,
+ app_data.bind_socket);
+ if (app_data.l1l == NULL)
+ goto exit;
+
+ /* Init transceiver interface */
+ app_data.trx = trx_if_open(tall_trxcon_ctx,
+ app_data.trx_bind_ip, app_data.trx_remote_ip,
+ app_data.trx_base_port);
+ if (!app_data.trx)
+ goto exit;
+
+ /* Bind L1CTL with TRX and vice versa */
+ app_data.l1l->trx = app_data.trx;
+ app_data.trx->l1l = app_data.l1l;
+
+ /* Init scheduler */
+ rc = sched_trx_init(app_data.trx, app_data.trx_fn_advance);
+ if (rc)
+ goto exit;
+
+ LOGP(DAPP, LOGL_NOTICE, "Init complete\n");
+
+ if (app_data.daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ goto exit;
+ }
+ }
+
+ /* Initialize pseudo-random generator */
+ srand(time(NULL));
+
+ init_external_transceiver(argc, argv);
+
+ while (!app_data.quit)
+ osmo_select_main(0);
+
+ gshutdown = true;
+ stop_trx();
+
+
+exit:
+ /* Close active connections */
+ l1ctl_link_shutdown(app_data.l1l);
+ sched_trx_shutdown(app_data.trx);
+ trx_if_close(app_data.trx);
+
+ /* Shutdown main state machine */
+ osmo_fsm_inst_free(trxcon_fsm);
+
+ /* Deinitialize logging */
+ log_fini();
+
+ /**
+ * Print report for the root talloc context in order
+ * to be able to find and fix potential memory leaks.
+ */
+ talloc_report_full(tall_trxcon_ctx, stderr);
+ talloc_free(tall_trxcon_ctx);
+
+ /* Make both Valgrind and ASAN happy */
+ talloc_report_full(NULL, stderr);
+ talloc_disable_null_tracking();
+
+ return rc;
+}
diff --git a/trxcon/trxcon.h b/trxcon/trxcon.h
new file mode 100644
index 0000000..9a0792b
--- /dev/null
+++ b/trxcon/trxcon.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#define GEN_MASK(state) (0x01 << state)
+
+extern struct osmo_fsm_inst *trxcon_fsm;
+extern struct gsmtap_inst *gsmtap;
+
+enum trxcon_fsm_states {
+ TRXCON_STATE_IDLE = 0,
+ TRXCON_STATE_MANAGED,
+};
+
+enum trxcon_fsm_events {
+ /* L1CTL specific events */
+ L1CTL_EVENT_CONNECT,
+ L1CTL_EVENT_DISCONNECT,
+
+ /* TRX specific events */
+ TRX_EVENT_RSP_ERROR,
+ TRX_EVENT_OFFLINE,
+};