aboutsummaryrefslogtreecommitdiffstats
path: root/Transceiver52M
diff options
context:
space:
mode:
Diffstat (limited to 'Transceiver52M')
-rw-r--r--Transceiver52M/ChannelizerBase.cpp1
-rw-r--r--Transceiver52M/Complex.h2
-rw-r--r--Transceiver52M/Makefile.am78
-rw-r--r--Transceiver52M/Resampler.cpp6
-rw-r--r--Transceiver52M/Resampler.h4
-rw-r--r--Transceiver52M/Transceiver.cpp195
-rw-r--r--Transceiver52M/Transceiver.h39
-rw-r--r--Transceiver52M/arch/arm/convert.c4
-rw-r--r--Transceiver52M/arch/arm/convert_neon.S4
-rw-r--r--Transceiver52M/arch/arm/convolve.c4
-rw-r--r--Transceiver52M/arch/arm/convolve_neon.S4
-rw-r--r--Transceiver52M/arch/arm/mult.c4
-rw-r--r--Transceiver52M/arch/arm/mult_neon.S4
-rw-r--r--Transceiver52M/arch/arm/scale.c4
-rw-r--r--Transceiver52M/arch/arm/scale_neon.S4
-rw-r--r--Transceiver52M/arch/common/convert_base.c4
-rw-r--r--Transceiver52M/arch/common/convolve_base.c4
-rw-r--r--Transceiver52M/arch/x86/convert.c4
-rw-r--r--Transceiver52M/arch/x86/convert_sse_3.c4
-rw-r--r--Transceiver52M/arch/x86/convert_sse_3.h4
-rw-r--r--Transceiver52M/arch/x86/convert_sse_4_1.c4
-rw-r--r--Transceiver52M/arch/x86/convert_sse_4_1.h4
-rw-r--r--Transceiver52M/arch/x86/convolve.c12
-rw-r--r--Transceiver52M/arch/x86/convolve_sse_3.c4
-rw-r--r--Transceiver52M/arch/x86/convolve_sse_3.h4
-rw-r--r--Transceiver52M/device/Makefile.am4
-rw-r--r--Transceiver52M/device/bladerf/Makefile.am11
-rw-r--r--Transceiver52M/device/bladerf/bladerf.cpp611
-rw-r--r--Transceiver52M/device/bladerf/bladerf.h195
-rw-r--r--Transceiver52M/device/common/Makefile.am4
-rw-r--r--Transceiver52M/device/common/bandmanager.h137
-rw-r--r--Transceiver52M/device/common/radioDevice.h52
-rw-r--r--Transceiver52M/device/ipc/IPCDevice.cpp68
-rw-r--r--Transceiver52M/device/ipc/IPCDevice.h8
-rw-r--r--Transceiver52M/device/ipc/Makefile.am36
-rw-r--r--Transceiver52M/device/ipc/ipc-driver-test.c36
-rw-r--r--Transceiver52M/device/ipc/ipc_chan.c19
-rw-r--r--Transceiver52M/device/ipc/ipc_sock.c21
-rw-r--r--Transceiver52M/device/ipc/shm.h2
-rw-r--r--Transceiver52M/device/ipc/uhdwrap.cpp35
-rw-r--r--Transceiver52M/device/ipc/uhdwrap.h2
-rw-r--r--Transceiver52M/device/lms/LMSDevice.cpp181
-rw-r--r--Transceiver52M/device/lms/LMSDevice.h77
-rw-r--r--Transceiver52M/device/lms/Makefile.am2
-rw-r--r--Transceiver52M/device/uhd/Makefile.am2
-rw-r--r--Transceiver52M/device/uhd/UHDDevice.cpp202
-rw-r--r--Transceiver52M/device/uhd/UHDDevice.h57
-rw-r--r--Transceiver52M/device/usrp1/Makefile.am2
-rw-r--r--Transceiver52M/device/usrp1/USRPDevice.cpp42
-rw-r--r--Transceiver52M/device/usrp1/USRPDevice.h23
-rw-r--r--Transceiver52M/grgsm_vitac/constants.h149
-rw-r--r--Transceiver52M/grgsm_vitac/grgsm_vitac.cpp305
-rw-r--r--Transceiver52M/grgsm_vitac/grgsm_vitac.h89
-rw-r--r--Transceiver52M/grgsm_vitac/viterbi_detector.cc392
-rw-r--r--Transceiver52M/grgsm_vitac/viterbi_detector.h64
-rw-r--r--Transceiver52M/ms/bladerf_specific.h479
-rw-r--r--Transceiver52M/ms/itrq.h249
-rw-r--r--Transceiver52M/ms/l1ctl_server.c275
-rw-r--r--Transceiver52M/ms/l1ctl_server_cb.cpp71
-rw-r--r--Transceiver52M/ms/logging.c98
-rw-r--r--Transceiver52M/ms/ms.cpp160
-rw-r--r--Transceiver52M/ms/ms.h301
-rw-r--r--Transceiver52M/ms/ms_rx_lower.cpp420
-rw-r--r--Transceiver52M/ms/ms_trxcon_if.cpp78
-rw-r--r--Transceiver52M/ms/ms_trxcon_if.h42
-rw-r--r--Transceiver52M/ms/ms_upper.cpp512
-rw-r--r--Transceiver52M/ms/ms_upper.h48
-rw-r--r--Transceiver52M/ms/sch.c329
-rw-r--r--Transceiver52M/ms/sch.h48
-rw-r--r--Transceiver52M/ms/threadpool.h95
-rw-r--r--Transceiver52M/ms/threadsched.cpp104
-rw-r--r--Transceiver52M/ms/threadsched.h68
-rw-r--r--Transceiver52M/ms/uhd_specific.h279
-rw-r--r--Transceiver52M/osmo-trx.cpp188
-rw-r--r--Transceiver52M/proto_trxd.h10
-rw-r--r--Transceiver52M/radioBuffer.cpp3
-rw-r--r--Transceiver52M/radioInterface.cpp21
-rw-r--r--Transceiver52M/radioInterface.h17
-rw-r--r--Transceiver52M/radioInterfaceMulti.cpp37
-rw-r--r--Transceiver52M/radioInterfaceResamp.cpp9
-rw-r--r--Transceiver52M/radioVector.cpp9
-rw-r--r--Transceiver52M/radioVector.h4
-rw-r--r--Transceiver52M/sigProcLib.cpp343
-rw-r--r--Transceiver52M/sigProcLib.h18
84 files changed, 6758 insertions, 790 deletions
diff --git a/Transceiver52M/ChannelizerBase.cpp b/Transceiver52M/ChannelizerBase.cpp
index e6eeed2..9910091 100644
--- a/Transceiver52M/ChannelizerBase.cpp
+++ b/Transceiver52M/ChannelizerBase.cpp
@@ -244,6 +244,7 @@ ChannelizerBase::~ChannelizerBase()
free(subFilters[i]);
delete[] hist[i];
}
+ free(subFilters);
fft_free(fftInput);
fft_free(fftOutput);
diff --git a/Transceiver52M/Complex.h b/Transceiver52M/Complex.h
index 6e72346..597a26f 100644
--- a/Transceiver52M/Complex.h
+++ b/Transceiver52M/Complex.h
@@ -29,7 +29,7 @@ unlike the built-in complex<> templates, these inline most operations for speed
template<class Real> class Complex {
public:
-
+ typedef Real value_type;
Real r, i;
/**@name constructors */
diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am
index 7dad159..0b63b16 100644
--- a/Transceiver52M/Makefile.am
+++ b/Transceiver52M/Makefile.am
@@ -40,7 +40,9 @@ COMMON_SOURCES = \
ChannelizerBase.cpp \
Channelizer.cpp \
Synthesis.cpp \
- proto_trxd.c
+ proto_trxd.c \
+ grgsm_vitac/grgsm_vitac.cpp \
+ grgsm_vitac/viterbi_detector.cc
libtransceiver_common_la_SOURCES = \
$(COMMON_SOURCES) \
@@ -73,6 +75,47 @@ COMMON_LDADD = \
$(LIBOSMOCTRL_LIBS) \
$(LIBOSMOVTY_LIBS)
+if ENABLE_MS_TRX
+AM_CPPFLAGS += -I$(top_srcdir)/osmocom-bb/src/host/trxcon/include/
+AM_CPPFLAGS += -I${srcdir}
+
+TRXCON_LDADD = \
+ $(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libtrxcon.a \
+ $(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libl1sched.a \
+ $(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libl1gprs.a \
+ $(LIBOSMOCODING_LIBS)
+
+MS_LOWER_SRC = \
+ ms/sch.c \
+ ms/ms.cpp \
+ ms/threadsched.cpp \
+ ms/ms_rx_lower.cpp \
+ grgsm_vitac/grgsm_vitac.cpp \
+ grgsm_vitac/viterbi_detector.cc
+
+MS_UPPER_SRC = \
+ ms/ms_upper.cpp \
+ ms/l1ctl_server.c \
+ ms/logging.c \
+ ms/l1ctl_server_cb.cpp \
+ ms/ms_trxcon_if.cpp
+
+noinst_HEADERS += \
+ ms/ms.h \
+ ms/threadsched.h \
+ ms/bladerf_specific.h \
+ ms/uhd_specific.h \
+ ms/ms_upper.h \
+ ms/ms_trxcon_if.h \
+ ms/itrq.h \
+ ms/sch.h \
+ ms/threadpool.h \
+ grgsm_vitac/viterbi_detector.h \
+ grgsm_vitac/constants.h \
+ grgsm_vitac/grgsm_vitac.h
+
+endif
+
bin_PROGRAMS =
if DEVICE_UHD
@@ -83,6 +126,17 @@ osmo_trx_uhd_LDADD = \
$(COMMON_LDADD) \
$(UHD_LIBS)
osmo_trx_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS)
+
+#if ENABLE_MS_TRX
+#bin_PROGRAMS += osmo-trx-ms-uhd
+#osmo_trx_ms_uhd_SOURCES = $(MS_LOWER_SRC) $(MS_UPPER_SRC)
+#osmo_trx_ms_uhd_LDADD = \
+# $(builddir)/device/uhd/libdevice.la \
+# $(COMMON_LDADD) \
+# $(UHD_LIBS) \
+# $(TRXCON_LDADD)
+#osmo_trx_ms_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) -DBUILDUHD
+#endif
endif
if DEVICE_USRP1
@@ -105,6 +159,27 @@ osmo_trx_lms_LDADD = \
osmo_trx_lms_CPPFLAGS = $(AM_CPPFLAGS) $(LMS_CFLAGS)
endif
+if DEVICE_BLADE
+bin_PROGRAMS += osmo-trx-blade
+osmo_trx_blade_SOURCES = osmo-trx.cpp
+osmo_trx_blade_LDADD = \
+ $(builddir)/device/bladerf/libdevice.la \
+ $(COMMON_LDADD) \
+ $(BLADE_LIBS)
+osmo_trx_blade_CPPFLAGS = $(AM_CPPFLAGS) $(LMS_CFLAGS)
+
+if ENABLE_MS_TRX
+bin_PROGRAMS += osmo-trx-ms-blade
+osmo_trx_ms_blade_SOURCES = $(MS_LOWER_SRC) $(MS_UPPER_SRC)
+osmo_trx_ms_blade_LDADD = \
+ $(builddir)/device/bladerf/libdevice.la \
+ $(COMMON_LDADD) \
+ $(BLADE_LIBS) \
+ $(TRXCON_LDADD)
+osmo_trx_ms_blade_CPPFLAGS = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DBUILDBLADE
+endif
+endif
+
if DEVICE_IPC
bin_PROGRAMS += osmo-trx-ipc
osmo_trx_ipc_SOURCES = osmo-trx.cpp
@@ -113,4 +188,3 @@ osmo_trx_ipc_LDADD = \
$(COMMON_LDADD)
osmo_trx_ipc_CPPFLAGS = $(AM_CPPFLAGS)
endif
-
diff --git a/Transceiver52M/Resampler.cpp b/Transceiver52M/Resampler.cpp
index 2ca6406..841c3a9 100644
--- a/Transceiver52M/Resampler.cpp
+++ b/Transceiver52M/Resampler.cpp
@@ -13,10 +13,6 @@
* 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
*/
#include <stdlib.h>
@@ -36,7 +32,7 @@ extern "C" {
#define M_PI 3.14159265358979323846264338327f
#endif
-#define MAX_OUTPUT_LEN 4096
+#define MAX_OUTPUT_LEN 4096*4
using namespace std;
diff --git a/Transceiver52M/Resampler.h b/Transceiver52M/Resampler.h
index 5af8d26..e1962c7 100644
--- a/Transceiver52M/Resampler.h
+++ b/Transceiver52M/Resampler.h
@@ -13,10 +13,6 @@
* 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
*/
#ifndef _RESAMPLER_H_
diff --git a/Transceiver52M/Transceiver.cpp b/Transceiver52M/Transceiver.cpp
index 676ffde..f5ddd9b 100644
--- a/Transceiver52M/Transceiver.cpp
+++ b/Transceiver52M/Transceiver.cpp
@@ -29,6 +29,7 @@
#include <fstream>
#include "Transceiver.h"
#include <Logger.h>
+#include <grgsm_vitac/grgsm_vitac.h>
extern "C" {
#include "osmo_signal.h"
@@ -132,26 +133,22 @@ bool TransceiverState::init(FillerType filler, size_t sps, float scale, size_t r
return false;
}
-Transceiver::Transceiver(int wBasePort,
- const char *TRXAddress,
- const char *GSMcoreAddress,
- size_t tx_sps, size_t rx_sps, size_t chans,
+Transceiver::Transceiver(const struct trx_cfg *cfg,
GSM::Time wTransmitLatency,
- RadioInterface *wRadioInterface,
- double wRssiOffset, int wStackSize)
- : mBasePort(wBasePort), mLocalAddr(TRXAddress), mRemoteAddr(GSMcoreAddress),
- mClockSocket(-1), mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface),
- rssiOffset(wRssiOffset), stackSize(wStackSize),
- mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mExtRACH(false), mEdge(false),
- mOn(false), mForceClockInterface(false),
- mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0), mMaxExpectedDelayNB(0),
- mWriteBurstToDiskMask(0)
+ RadioInterface *wRadioInterface)
+ : mChans(cfg->num_chans), cfg(cfg),
+ mCtrlSockets(mChans), mClockSocket(-1),
+ mTxPriorityQueues(mChans), mReceiveFIFO(mChans),
+ mRxServiceLoopThreads(mChans), mRxLowerLoopThread(nullptr), mTxLowerLoopThread(nullptr),
+ mTxPriorityQueueServiceLoopThreads(mChans), mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface),
+ mOn(false),mForceClockInterface(false), mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0),
+ mMaxExpectedDelayNB(0), mWriteBurstToDiskMask(0), mVersionTRXD(mChans), mStates(mChans)
{
txFullScale = mRadioInterface->fullScaleInputValue();
rxFullScale = mRadioInterface->fullScaleOutputValue();
- for (int i = 0; i < 8; i++) {
- for (int j = 0; j < 8; j++)
+ for (size_t i = 0; i < ARRAY_SIZE(mHandover); i++) {
+ for (size_t j = 0; j < ARRAY_SIZE(mHandover[i]); j++)
mHandover[i][j] = false;
}
}
@@ -199,11 +196,9 @@ int Transceiver::ctrl_sock_cb(struct osmo_fd *bfd, unsigned int flags)
* are still expected to report clock indications through control channel
* activity.
*/
-bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay,
- bool edge, bool ext_rach)
+bool Transceiver::init()
{
int d_srcport, d_dstport, c_srcport, c_dstport;
-
if (!mChans) {
LOG(FATAL) << "No channels assigned";
return false;
@@ -214,41 +209,34 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay,
return false;
}
- mExtRACH = ext_rach;
- mEdge = edge;
+ initvita();
mDataSockets.resize(mChans, -1);
- mCtrlSockets.resize(mChans);
- mTxPriorityQueueServiceLoopThreads.resize(mChans);
- mRxServiceLoopThreads.resize(mChans);
- mTxPriorityQueues.resize(mChans);
- mReceiveFIFO.resize(mChans);
- mStates.resize(mChans);
- mVersionTRXD.resize(mChans);
/* Filler table retransmissions - support only on channel 0 */
- if (filler == FILLER_DUMMY)
+ if (cfg->filler == FILLER_DUMMY)
mStates[0].mRetrans = true;
/* Setup sockets */
mClockSocket = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
- mLocalAddr.c_str(), mBasePort,
- mRemoteAddr.c_str(), mBasePort + 100,
+ cfg->bind_addr, cfg->base_port,
+ cfg->remote_addr, cfg->base_port + 100,
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
if (mClockSocket < 0)
return false;
for (size_t i = 0; i < mChans; i++) {
int rv;
- c_srcport = mBasePort + 2 * i + 1;
- c_dstport = mBasePort + 2 * i + 101;
- d_srcport = mBasePort + 2 * i + 2;
- d_dstport = mBasePort + 2 * i + 102;
+ FillerType filler = cfg->filler;
+ c_srcport = cfg->base_port + 2 * i + 1;
+ c_dstport = cfg->base_port + 2 * i + 101;
+ d_srcport = cfg->base_port + 2 * i + 2;
+ d_dstport = cfg->base_port + 2 * i + 102;
rv = osmo_sock_init2_ofd(&mCtrlSockets[i].conn_bfd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
- mLocalAddr.c_str(), c_srcport,
- mRemoteAddr.c_str(), c_dstport,
+ cfg->bind_addr, c_srcport,
+ cfg->remote_addr, c_dstport,
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
if (rv < 0)
return false;
@@ -258,8 +246,8 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay,
mDataSockets[i] = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
- mLocalAddr.c_str(), d_srcport,
- mRemoteAddr.c_str(), d_dstport,
+ cfg->bind_addr, d_srcport,
+ cfg->remote_addr, d_dstport,
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
if (mDataSockets[i] < 0)
return false;
@@ -267,7 +255,7 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay,
if (i && filler == FILLER_DUMMY)
filler = FILLER_ZERO;
- mStates[i].init(filler, mSPSTx, txFullScale, rtsc, rach_delay);
+ mStates[i].init(filler, cfg->tx_sps, txFullScale, cfg->rtsc, cfg->rach_delay);
}
/* Randomize the central clock */
@@ -309,8 +297,8 @@ bool Transceiver::start()
}
/* Device is running - launch I/O threads */
- mRxLowerLoopThread = new Thread(stackSize);
- mTxLowerLoopThread = new Thread(stackSize);
+ mRxLowerLoopThread = new Thread(cfg->stack_size);
+ mTxLowerLoopThread = new Thread(cfg->stack_size);
mTxLowerLoopThread->start((void * (*)(void*))
TxLowerLoopAdapter,(void*) this);
mRxLowerLoopThread->start((void * (*)(void*))
@@ -321,14 +309,14 @@ bool Transceiver::start()
TrxChanThParams *params = (TrxChanThParams *)malloc(sizeof(struct TrxChanThParams));
params->trx = this;
params->num = i;
- mRxServiceLoopThreads[i] = new Thread(stackSize);
+ mRxServiceLoopThreads[i] = new Thread(cfg->stack_size);
mRxServiceLoopThreads[i]->start((void * (*)(void*))
RxUpperLoopAdapter, (void*) params);
params = (TrxChanThParams *)malloc(sizeof(struct TrxChanThParams));
params->trx = this;
params->num = i;
- mTxPriorityQueueServiceLoopThreads[i] = new Thread(stackSize);
+ mTxPriorityQueueServiceLoopThreads[i] = new Thread(cfg->stack_size);
mTxPriorityQueueServiceLoopThreads[i]->start((void * (*)(void*))
TxUpperLoopAdapter, (void*) params);
}
@@ -401,11 +389,11 @@ void Transceiver::addRadioVector(size_t chan, BitVector &bits,
/* Use the number of bits as the EDGE burst indicator */
if (bits.size() == EDGE_BURST_NBITS)
- burst = modulateEdgeBurst(bits, mSPSTx);
+ burst = modulateEdgeBurst(bits, cfg->tx_sps);
else
- burst = modulateBurst(bits, 8 + (wTime.TN() % 4 == 0), mSPSTx);
+ burst = modulateBurst(bits, 8 + (wTime.TN() % 4 == 0), cfg->tx_sps);
- scaleVector(*burst, txFullScale * pow(10, -RSSI / 10));
+ scaleVector(*burst, txFullScale * pow(10, (double) -RSSI / 20));
radio_burst = new radioVector(wTime, burst);
@@ -566,16 +554,16 @@ CorrType Transceiver::expectedCorrType(GSM::Time currTime,
break;
case IV:
case VI:
- return mExtRACH ? EXT_RACH : RACH;
+ return cfg->ext_rach ? EXT_RACH : RACH;
break;
case V: {
int mod51 = burstFN % 51;
if ((mod51 <= 36) && (mod51 >= 14))
- return mExtRACH ? EXT_RACH : RACH;
+ return cfg->ext_rach ? EXT_RACH : RACH;
else if ((mod51 == 4) || (mod51 == 5))
- return mExtRACH ? EXT_RACH : RACH;
+ return cfg->ext_rach ? EXT_RACH : RACH;
else if ((mod51 == 45) || (mod51 == 46))
- return mExtRACH ? EXT_RACH : RACH;
+ return cfg->ext_rach ? EXT_RACH : RACH;
else if (mHandover[burstTN][sdcch4_subslot[burstFN % 102]])
return RACH;
else
@@ -593,11 +581,11 @@ CorrType Transceiver::expectedCorrType(GSM::Time currTime,
case XIII: {
int mod52 = burstFN % 52;
if ((mod52 == 12) || (mod52 == 38))
- return mExtRACH ? EXT_RACH : RACH;
+ return RACH; /* RACH is always 8-bit on PTCCH/U */
else if ((mod52 == 25) || (mod52 == 51))
return IDLE;
else /* Enable 8-PSK burst detection if EDGE is enabled */
- return mEdge ? EDGE : TSC;
+ return cfg->egprs ? EDGE : TSC;
break;
}
case LOOPBACK:
@@ -622,6 +610,51 @@ void writeToFile(radioVector *radio_burst, size_t chan)
outfile.close();
}
+double Transceiver::rssiOffset(size_t chan)
+{
+ if (cfg->force_rssi_offset)
+ return cfg->rssi_offset;
+ return mRadioInterface->rssiOffset(chan) + cfg->rssi_offset;
+}
+
+static SoftVector *demodAnyBurst_va(const signalVector &burst, CorrType type, int sps, int rach_max_toa, int tsc)
+{
+ auto conved_beg = reinterpret_cast<const std::complex<float> *>(&burst.begin()[0]);
+ std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
+ float ncmax;
+ const unsigned burst_len_bits = 148 + 8;
+ char demodded_softbits[burst_len_bits];
+ SoftVector *bits = new SoftVector(burst_len_bits);
+
+ if (type == CorrType::TSC) {
+ auto rach_burst_start = get_norm_chan_imp_resp(conved_beg, chan_imp_resp, &ncmax, tsc);
+ rach_burst_start = std::max(rach_burst_start, 0);
+ detect_burst_nb(conved_beg, chan_imp_resp, rach_burst_start, demodded_softbits);
+ } else {
+ auto normal_burst_start = get_access_imp_resp(conved_beg, chan_imp_resp, &ncmax, 0);
+ normal_burst_start = std::max(normal_burst_start, 0);
+ detect_burst_ab(conved_beg, chan_imp_resp, normal_burst_start, demodded_softbits, rach_max_toa);
+ }
+
+ float *s = &bits->begin()[0];
+ for (unsigned int i = 0; i < 148; i++)
+ s[i] = demodded_softbits[i] * -1;
+ for (unsigned int i = 148; i < burst_len_bits; i++)
+ s[i] = 0;
+ return bits;
+}
+
+#define USE_VA
+
+#ifdef USE_VA
+// signalvector is owning despite claiming not to, but we can pretend, too..
+static void dummy_free(void *wData){};
+static void *dummy_alloc(size_t newSize)
+{
+ return 0;
+};
+#endif
+
/*
* Pull bursts from the FIFO and handle according to the slot
* and burst correlation type. Equalzation is currently disabled.
@@ -641,6 +674,10 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
SoftVector *rxBurst;
TransceiverState *state = &mStates[chan];
bool ctr_changed = false;
+ double rssi_offset;
+ static complex burst_shift_buffer[625];
+ static signalVector shift_vec(burst_shift_buffer, 0, 625, dummy_alloc, dummy_free);
+ signalVector *shvec_ptr = &shift_vec;
/* Blocking FIFO read */
radioVector *radio_burst = mReceiveFIFO[chan]->read();
@@ -650,7 +687,7 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
}
/* Set time and determine correlation type */
- burstTime = radio_burst->getTime();
+ burstTime = radio_burst->getTime() + cfg->ul_fn_offset;
CorrType type = expectedCorrType(burstTime, chan);
/* Initialize struct bi */
@@ -685,7 +722,7 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
/* 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 * mSPSRx);
+ float pow = energyDetect(*radio_burst->getVector(i), 20 * cfg->rx_sps);
if (pow > max) {
max = pow;
max_i = i;
@@ -710,8 +747,9 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
state->mNoiseLev = state->mNoises.avg();
}
- bi->rssi = 20.0 * log10(rxFullScale / avg) + rssiOffset;
- bi->noise = 20.0 * log10(rxFullScale / state->mNoiseLev) + rssiOffset;
+ rssi_offset = rssiOffset(chan);
+ bi->rssi = 20.0 * log10(rxFullScale / avg) + rssi_offset;
+ bi->noise = 20.0 * log10(rxFullScale / state->mNoiseLev) + rssi_offset;
if (type == IDLE)
goto ret_idle;
@@ -719,8 +757,15 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
max_toa = (type == RACH || type == EXT_RACH) ?
mMaxExpectedDelayAB : mMaxExpectedDelayNB;
+ if (cfg->use_va) {
+ // shifted burst copy to make the old demod and detection happy
+ std::copy(burst->begin() + 20, burst->end() - 20, shift_vec.begin());
+ } else {
+ shvec_ptr = burst;
+ }
+
/* Detect normal or RACH bursts */
- rc = detectAnyBurst(*burst, mTSC, BURST_THRESH, mSPSRx, type, max_toa, &ebp);
+ rc = detectAnyBurst(*shvec_ptr, mTSC, BURST_THRESH, cfg->rx_sps, type, max_toa, &ebp);
if (rc <= 0) {
if (rc == -SIGERR_CLIP) {
LOGCHAN(chan, DTRXDUL, INFO) << "Clipping detected on received RACH or Normal Burst";
@@ -734,11 +779,16 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
goto ret_idle;
}
- type = (CorrType) rc;
+ if (cfg->use_va) {
+ scaleVector(*burst, { (1. / (float)((1 << 14) - 1)), 0 });
+ rxBurst = demodAnyBurst_va(*burst, (CorrType)rc, cfg->rx_sps, max_toa, mTSC);
+ } else {
+ rxBurst = demodAnyBurst(*shvec_ptr, (CorrType)rc, cfg->rx_sps, &ebp);
+ }
+
bi->toa = ebp.toa;
bi->tsc = ebp.tsc;
bi->ci = ebp.ci;
- rxBurst = demodAnyBurst(*burst, mSPSRx, ebp.amp, ebp.toa, type);
/* EDGE demodulator returns 444 (gSlotLen * 3) bits */
if (rxBurst->size() == EDGE_BURST_NBITS) {
@@ -806,7 +856,7 @@ void Transceiver::ctrl_sock_send(ctrl_msg& m, int chan)
struct osmo_fd *conn_bfd = &s.conn_bfd;
s.txmsgqueue.push_back(m);
- conn_bfd->when |= OSMO_FD_WRITE;
+ osmo_fd_write_enable(conn_bfd);
}
int Transceiver::ctrl_sock_write(int chan)
@@ -821,7 +871,7 @@ int Transceiver::ctrl_sock_write(int chan)
while (s.txmsgqueue.size()) {
const ctrl_msg m = s.txmsgqueue.front();
- s.conn_bfd.when &= ~OSMO_FD_WRITE;
+ osmo_fd_write_disable(&s.conn_bfd);
/* try to send it over the socket */
rc = write(s.conn_bfd.fd, m.data, strlen(m.data) + 1);
@@ -829,7 +879,7 @@ int Transceiver::ctrl_sock_write(int chan)
goto close;
if (rc < 0) {
if (errno == EAGAIN) {
- s.conn_bfd.when |= OSMO_FD_WRITE;
+ osmo_fd_write_enable(&s.conn_bfd);
break;
}
goto close;
@@ -909,19 +959,18 @@ int Transceiver::ctrl_sock_handle_rx(int chan)
sprintf(response, "RSP NOHANDOVER 0 %u %u", ts, ss);
}
} else if (match_cmd(command, "SETMAXDLY", &params)) {
- //set expected maximum time-of-arrival
+ //set expected maximum time-of-arrival for Access Bursts
int maxDelay;
sscanf(params, "%d", &maxDelay);
mMaxExpectedDelayAB = maxDelay; // 1 GSM symbol is approx. 1 km
sprintf(response,"RSP SETMAXDLY 0 %d",maxDelay);
} else if (match_cmd(command, "SETMAXDLYNB", &params)) {
- //set expected maximum time-of-arrival
+ //set expected maximum time-of-arrival for Normal Bursts
int maxDelay;
sscanf(params, "%d", &maxDelay);
mMaxExpectedDelayNB = maxDelay; // 1 GSM symbol is approx. 1 km
sprintf(response,"RSP SETMAXDLYNB 0 %d",maxDelay);
} else if (match_cmd(command, "SETRXGAIN", &params)) {
- //set expected maximum time-of-arrival
int newGain;
sscanf(params, "%d", &newGain);
newGain = mRadioInterface->setRxGain(newGain, chan);
@@ -955,7 +1004,7 @@ int Transceiver::ctrl_sock_handle_rx(int chan)
// tune receiver
int freqKhz;
sscanf(params, "%d", &freqKhz);
- mRxFreq = freqKhz * 1e3;
+ mRxFreq = (freqKhz + cfg->freq_offset_khz) * 1e3;
if (!mRadioInterface->tuneRx(mRxFreq, chan)) {
LOGCHAN(chan, DTRXCTRL, FATAL) << "RX failed to tune";
sprintf(response,"RSP RXTUNE 1 %d",freqKhz);
@@ -966,7 +1015,7 @@ int Transceiver::ctrl_sock_handle_rx(int chan)
// tune txmtr
int freqKhz;
sscanf(params, "%d", &freqKhz);
- mTxFreq = freqKhz * 1e3;
+ mTxFreq = (freqKhz + cfg->freq_offset_khz) * 1e3;
if (!mRadioInterface->tuneTx(mTxFreq, chan)) {
LOGCHAN(chan, DTRXCTRL, FATAL) << "TX failed to tune";
sprintf(response,"RSP TXTUNE 1 %d",freqKhz);
@@ -1004,7 +1053,7 @@ int Transceiver::ctrl_sock_handle_rx(int chan)
LOGCHAN(chan, DTRXCTRL, INFO) << "BTS requests TRXD version switch: " << version_recv;
if (version_recv > TRX_DATA_FORMAT_VER) {
LOGCHAN(chan, DTRXCTRL, INFO) << "rejecting TRXD version " << version_recv
- << "in favor of " << TRX_DATA_FORMAT_VER;
+ << " in favor of " << TRX_DATA_FORMAT_VER;
sprintf(response, "RSP SETFORMAT %u %u", TRX_DATA_FORMAT_VER, version_recv);
} else {
LOGCHAN(chan, DTRXCTRL, NOTICE) << "switching to TRXD version " << version_recv;
@@ -1055,8 +1104,8 @@ bool Transceiver::driveTxPriorityQueue(size_t chan)
burstLen = gSlotLen;
break;
case sizeof(*dl) + EDGE_BURST_NBITS: /* EDGE burst */
- if (mSPSTx != 4) {
- LOGCHAN(chan, DTRXDDL, ERROR) << "EDGE burst received but SPS is set to " << mSPSTx;
+ if (cfg->tx_sps != 4) {
+ LOGCHAN(chan, DTRXDDL, ERROR) << "EDGE burst received but SPS is set to " << cfg->tx_sps;
return false;
}
burstLen = EDGE_BURST_NBITS;
@@ -1163,11 +1212,13 @@ void Transceiver::logRxBurst(size_t chan, const struct trx_ul_burst_ind *bi)
else os << "-";
}
+ double rssi_offset = rssiOffset(chan);
+
LOGCHAN(chan, DTRXDUL, DEBUG) << std::fixed << std::right
<< " time: " << unsigned(bi->tn) << ":" << bi->fn
- << " RSSI: " << std::setw(5) << std::setprecision(1) << (bi->rssi - rssiOffset)
+ << " RSSI: " << std::setw(5) << std::setprecision(1) << (bi->rssi - rssi_offset)
<< "dBFS/" << std::setw(6) << -bi->rssi << "dBm"
- << " noise: " << std::setw(5) << std::setprecision(1) << (bi->noise - rssiOffset)
+ << " noise: " << std::setw(5) << std::setprecision(1) << (bi->noise - rssi_offset)
<< "dBFS/" << std::setw(6) << -bi->noise << "dBm"
<< " TOA: " << std::setw(5) << std::setprecision(2) << bi->toa
<< " C/I: " << std::setw(5) << std::setprecision(2) << bi->ci << "dB"
diff --git a/Transceiver52M/Transceiver.h b/Transceiver52M/Transceiver.h
index 7a05ac5..babe420 100644
--- a/Transceiver52M/Transceiver.h
+++ b/Transceiver52M/Transceiver.h
@@ -80,7 +80,7 @@ struct TransceiverState {
/* Received noise energy levels */
float mNoiseLev;
- noiseVector mNoises;
+ avgVector mNoises;
/* Shadowed downlink attenuation */
int mPower;
@@ -100,27 +100,19 @@ struct TransceiverState {
class Transceiver {
public:
/** Transceiver constructor
- @param wBasePort base port number of UDP sockets
- @param TRXAddress IP address of the TRX, as a string
- @param GSMcoreAddress IP address of the GSM core, as a string
- @param wSPS number of samples per GSM symbol
+ @param cfg VTY populated config
@param wTransmitLatency initial setting of transmit latency
@param radioInterface associated radioInterface object
*/
- Transceiver(int wBasePort,
- const char *TRXAddress,
- const char *GSMcoreAddress,
- size_t tx_sps, size_t rx_sps, size_t chans,
+ Transceiver(const struct trx_cfg *cfg,
GSM::Time wTransmitLatency,
- RadioInterface *wRadioInterface,
- double wRssiOffset, int stackSize);
+ RadioInterface *wRadioInterface);
/** Destructor */
~Transceiver();
/** Start the control loop */
- bool init(FillerType filler, size_t rtsc, unsigned rach_delay,
- bool edge, bool ext_rach);
+ bool init(void);
/** attach the radioInterface receive FIFO */
bool receiveFIFO(VectorFIFO *wFIFO, size_t chan)
@@ -133,7 +125,7 @@ public:
}
/** accessor for number of channels */
- size_t numChans() const { return mChans; };
+ size_t numChans() const { return cfg->num_chans; };
/** Codes for channel combinations */
typedef enum {
@@ -156,7 +148,7 @@ public:
} ChannelCombination;
private:
-
+ size_t mChans;
struct ctrl_msg {
char data[101];
ctrl_msg() {};
@@ -170,17 +162,14 @@ struct ctrl_sock_state {
}
~ctrl_sock_state() {
if(conn_bfd.fd >= 0) {
+ osmo_fd_unregister(&conn_bfd);
close(conn_bfd.fd);
conn_bfd.fd = -1;
- osmo_fd_unregister(&conn_bfd);
}
}
};
- int mBasePort;
- std::string mLocalAddr;
- std::string mRemoteAddr;
-
+ const struct trx_cfg *cfg; ///< VTY populated config
std::vector<int> mDataSockets; ///< socket for writing to/reading from GSM core
std::vector<ctrl_sock_state> mCtrlSockets; ///< socket for writing/reading control commands from GSM core
int mClockSocket; ///< socket for writing clock updates to GSM core
@@ -202,9 +191,6 @@ struct ctrl_sock_state {
double txFullScale; ///< full scale input to radio
double rxFullScale; ///< full scale output to radio
- double rssiOffset; ///< RSSI to dBm conversion offset
- int stackSize; ///< stack size for threads, 0 = OS default
-
/** modulate and add a burst to the transmit queue */
void addRadioVector(size_t chan, BitVector &bits,
int RSSI, GSM::Time &wTime);
@@ -233,12 +219,7 @@ struct ctrl_sock_state {
/** drive handling of control messages from GSM core */
int ctrl_sock_handle_rx(int chan);
- int mSPSTx; ///< number of samples per Tx symbol
- int mSPSRx; ///< number of samples per Rx symbol
- size_t mChans;
- bool mExtRACH;
- bool mEdge;
bool mOn; ///< flag to indicate that transceiver is powered on
bool mForceClockInterface; ///< flag to indicate whether IND CLOCK shall be sent unconditionally after transceiver is started
bool mHandover[8][8]; ///< expect handover to the timeslot/subslot
@@ -280,7 +261,7 @@ protected:
friend void *RxLowerLoopAdapter(Transceiver *transceiver);
friend void *TxLowerLoopAdapter(Transceiver *transceiver);
-
+ double rssiOffset(size_t chan);
void reset();
void logRxBurst(size_t chan, const struct trx_ul_burst_ind *bi);
diff --git a/Transceiver52M/arch/arm/convert.c b/Transceiver52M/arch/arm/convert.c
index ace1b6f..7145579 100644
--- a/Transceiver52M/arch/arm/convert.c
+++ b/Transceiver52M/arch/arm/convert.c
@@ -13,10 +13,6 @@
* 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
*/
#include <malloc.h>
diff --git a/Transceiver52M/arch/arm/convert_neon.S b/Transceiver52M/arch/arm/convert_neon.S
index a1fbd40..6cf3c03 100644
--- a/Transceiver52M/arch/arm/convert_neon.S
+++ b/Transceiver52M/arch/arm/convert_neon.S
@@ -13,10 +13,6 @@
* 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
*/
.syntax unified
diff --git a/Transceiver52M/arch/arm/convolve.c b/Transceiver52M/arch/arm/convolve.c
index adb718d..c2611b4 100644
--- a/Transceiver52M/arch/arm/convolve.c
+++ b/Transceiver52M/arch/arm/convolve.c
@@ -13,10 +13,6 @@
* 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
*/
#include <malloc.h>
diff --git a/Transceiver52M/arch/arm/convolve_neon.S b/Transceiver52M/arch/arm/convolve_neon.S
index a3e1ba5..3209d60 100644
--- a/Transceiver52M/arch/arm/convolve_neon.S
+++ b/Transceiver52M/arch/arm/convolve_neon.S
@@ -13,10 +13,6 @@
* 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
diff --git a/Transceiver52M/arch/arm/mult.c b/Transceiver52M/arch/arm/mult.c
index 9851626..251b3c9 100644
--- a/Transceiver52M/arch/arm/mult.c
+++ b/Transceiver52M/arch/arm/mult.c
@@ -13,10 +13,6 @@
* 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
*/
#include <malloc.h>
diff --git a/Transceiver52M/arch/arm/mult_neon.S b/Transceiver52M/arch/arm/mult_neon.S
index 6318c50..b4cd229 100644
--- a/Transceiver52M/arch/arm/mult_neon.S
+++ b/Transceiver52M/arch/arm/mult_neon.S
@@ -13,10 +13,6 @@
* 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
*/
.syntax unified
diff --git a/Transceiver52M/arch/arm/scale.c b/Transceiver52M/arch/arm/scale.c
index a3214f7..d44ffc8 100644
--- a/Transceiver52M/arch/arm/scale.c
+++ b/Transceiver52M/arch/arm/scale.c
@@ -13,10 +13,6 @@
* 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
*/
#include <malloc.h>
diff --git a/Transceiver52M/arch/arm/scale_neon.S b/Transceiver52M/arch/arm/scale_neon.S
index f10de1e..c348f30 100644
--- a/Transceiver52M/arch/arm/scale_neon.S
+++ b/Transceiver52M/arch/arm/scale_neon.S
@@ -13,10 +13,6 @@
* 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
*/
.syntax unified
diff --git a/Transceiver52M/arch/common/convert_base.c b/Transceiver52M/arch/common/convert_base.c
index 9876e83..9a01a1e 100644
--- a/Transceiver52M/arch/common/convert_base.c
+++ b/Transceiver52M/arch/common/convert_base.c
@@ -13,10 +13,6 @@
* 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
*/
#include "convert.h"
diff --git a/Transceiver52M/arch/common/convolve_base.c b/Transceiver52M/arch/common/convolve_base.c
index bfda783..3765c5c 100644
--- a/Transceiver52M/arch/common/convolve_base.c
+++ b/Transceiver52M/arch/common/convolve_base.c
@@ -13,10 +13,6 @@
* 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
*/
#include <malloc.h>
diff --git a/Transceiver52M/arch/x86/convert.c b/Transceiver52M/arch/x86/convert.c
index bbcfd67..596233c 100644
--- a/Transceiver52M/arch/x86/convert.c
+++ b/Transceiver52M/arch/x86/convert.c
@@ -11,10 +11,6 @@
* 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
*/
#include <malloc.h>
diff --git a/Transceiver52M/arch/x86/convert_sse_3.c b/Transceiver52M/arch/x86/convert_sse_3.c
index 255db67..f00ecf5 100644
--- a/Transceiver52M/arch/x86/convert_sse_3.c
+++ b/Transceiver52M/arch/x86/convert_sse_3.c
@@ -11,10 +11,6 @@
* 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
*/
#include <malloc.h>
diff --git a/Transceiver52M/arch/x86/convert_sse_3.h b/Transceiver52M/arch/x86/convert_sse_3.h
index c2f87d7..b5abe47 100644
--- a/Transceiver52M/arch/x86/convert_sse_3.h
+++ b/Transceiver52M/arch/x86/convert_sse_3.h
@@ -11,10 +11,6 @@
* 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
*/
#pragma once
diff --git a/Transceiver52M/arch/x86/convert_sse_4_1.c b/Transceiver52M/arch/x86/convert_sse_4_1.c
index 42a235c..736a376 100644
--- a/Transceiver52M/arch/x86/convert_sse_4_1.c
+++ b/Transceiver52M/arch/x86/convert_sse_4_1.c
@@ -11,10 +11,6 @@
* 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
*/
#include <malloc.h>
diff --git a/Transceiver52M/arch/x86/convert_sse_4_1.h b/Transceiver52M/arch/x86/convert_sse_4_1.h
index 57a5efb..63305e5 100644
--- a/Transceiver52M/arch/x86/convert_sse_4_1.h
+++ b/Transceiver52M/arch/x86/convert_sse_4_1.h
@@ -11,10 +11,6 @@
* 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
*/
#pragma once
diff --git a/Transceiver52M/arch/x86/convolve.c b/Transceiver52M/arch/x86/convolve.c
index 81ea782..45a3719 100644
--- a/Transceiver52M/arch/x86/convolve.c
+++ b/Transceiver52M/arch/x86/convolve.c
@@ -11,10 +11,6 @@
* 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
*/
#include <malloc.h>
@@ -102,9 +98,9 @@ int convolve_real(const float *x, int x_len,
#ifndef __OPTIMIZE__
if (bounds_check(x_len, h_len, y_len, start, len) < 0)
return -1;
-
- memset(y, 0, len * 2 * sizeof(float));
#endif
+ memset(y, 0, len * 2 * sizeof(float));
+
switch (h_len) {
case 4:
c.conv_real4(x, x_len, h, h_len, y, y_len, start, len);
@@ -142,9 +138,9 @@ int convolve_complex(const float *x, int x_len,
#ifndef __OPTIMIZE__
if (bounds_check(x_len, h_len, y_len, start, len) < 0)
return -1;
-
- memset(y, 0, len * 2 * sizeof(float));
#endif
+ memset(y, 0, len * 2 * sizeof(float));
+
if (!(h_len % 8))
c.conv_cmplx_8n(x, x_len, h, h_len, y, y_len, start, len);
else if (!(h_len % 4))
diff --git a/Transceiver52M/arch/x86/convolve_sse_3.c b/Transceiver52M/arch/x86/convolve_sse_3.c
index 8fd3b5e..ca4dc71 100644
--- a/Transceiver52M/arch/x86/convolve_sse_3.c
+++ b/Transceiver52M/arch/x86/convolve_sse_3.c
@@ -11,10 +11,6 @@
* 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
*/
#include <malloc.h>
diff --git a/Transceiver52M/arch/x86/convolve_sse_3.h b/Transceiver52M/arch/x86/convolve_sse_3.h
index d929ef6..2cbc037 100644
--- a/Transceiver52M/arch/x86/convolve_sse_3.h
+++ b/Transceiver52M/arch/x86/convolve_sse_3.h
@@ -11,10 +11,6 @@
* 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
*/
#pragma once
diff --git a/Transceiver52M/device/Makefile.am b/Transceiver52M/device/Makefile.am
index 93ba7e3..9af18f7 100644
--- a/Transceiver52M/device/Makefile.am
+++ b/Transceiver52M/device/Makefile.am
@@ -17,3 +17,7 @@ endif
if DEVICE_LMS
SUBDIRS += lms
endif
+
+if DEVICE_BLADE
+SUBDIRS += bladerf
+endif
diff --git a/Transceiver52M/device/bladerf/Makefile.am b/Transceiver52M/device/bladerf/Makefile.am
new file mode 100644
index 0000000..e9917af
--- /dev/null
+++ b/Transceiver52M/device/bladerf/Makefile.am
@@ -0,0 +1,11 @@
+include $(top_srcdir)/Makefile.common
+
+AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(BLADE_CFLAGS)
+
+noinst_HEADERS = bladerf.h
+
+noinst_LTLIBRARIES = libdevice.la
+
+libdevice_la_SOURCES = bladerf.cpp
+libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la
diff --git a/Transceiver52M/device/bladerf/bladerf.cpp b/Transceiver52M/device/bladerf/bladerf.cpp
new file mode 100644
index 0000000..2f3c86a
--- /dev/null
+++ b/Transceiver52M/device/bladerf/bladerf.cpp
@@ -0,0 +1,611 @@
+/*
+ * Copyright 2022 sysmocom - s.f.m.c. GmbH
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * 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/>.
+ * See the COPYING file in the main directory for details.
+ */
+
+#include <map>
+#include <libbladeRF.h>
+#include "radioDevice.h"
+#include "bladerf.h"
+#include "Threads.h"
+#include "Logger.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+extern "C" {
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/vty/cpu_sched_vty.h>
+}
+
+#define SAMPLE_BUF_SZ (1 << 20)
+
+#define B2XX_TIMING_4_4SPS 6.18462e-5
+
+#define CHKRET() \
+ { \
+ if (status != 0) \
+ LOGC(DDEV, ERROR) << bladerf_strerror(status); \
+ }
+
+static const dev_map_t dev_param_map{
+ { std::make_tuple(blade_dev_type::BLADE2, 4, 4), { 1, 26e6, GSMRATE, B2XX_TIMING_4_4SPS, "B200 4 SPS" } },
+};
+
+static const power_map_t dev_band_nom_power_param_map{
+ { std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_850), { 89.75, 13.3, -7.5 } },
+ { std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_900), { 89.75, 13.3, -7.5 } },
+ { std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1800), { 89.75, 7.5, -11.0 } },
+ { std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1900), { 89.75, 7.7, -11.0 } },
+};
+
+/* So far measurements done for B210 show really close to linear relationship
+ * between gain and real output power, so we simply adjust the measured offset
+ */
+static double TxGain2TxPower(const dev_band_desc &desc, double tx_gain_db)
+{
+ return desc.nom_out_tx_power - (desc.nom_uhd_tx_gain - tx_gain_db);
+}
+static double TxPower2TxGain(const dev_band_desc &desc, double tx_power_dbm)
+{
+ return desc.nom_uhd_tx_gain - (desc.nom_out_tx_power - tx_power_dbm);
+}
+
+blade_device::blade_device(InterfaceType iface, const struct trx_cfg *cfg)
+ : RadioDevice(iface, cfg), band_manager(dev_band_nom_power_param_map, dev_param_map), dev(nullptr),
+ rx_gain_min(0.0), rx_gain_max(0.0), tx_spp(0), rx_spp(0), started(false), aligned(false), drop_cnt(0),
+ prev_ts(0), ts_initial(0), ts_offset(0), async_event_thrd(NULL)
+{
+}
+
+blade_device::~blade_device()
+{
+ if (dev) {
+ bladerf_enable_module(dev, BLADERF_CHANNEL_RX(0), false);
+ bladerf_enable_module(dev, BLADERF_CHANNEL_TX(0), false);
+ }
+
+ stop();
+
+ for (size_t i = 0; i < rx_buffers.size(); i++)
+ delete rx_buffers[i];
+}
+
+void blade_device::init_gains()
+{
+ double tx_gain_min, tx_gain_max;
+ int status;
+
+ const struct bladerf_range *r;
+ bladerf_get_gain_range(dev, BLADERF_RX, &r);
+
+ rx_gain_min = r->min;
+ rx_gain_max = r->max;
+ LOGC(DDEV, INFO) << "Supported Rx gain range [" << rx_gain_min << "; " << rx_gain_max << "]";
+
+ for (size_t i = 0; i < rx_gains.size(); i++) {
+ double gain = (rx_gain_min + rx_gain_max) / 2;
+ status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(i), BLADERF_GAIN_MGC);
+ CHKRET()
+ bladerf_gain_mode m;
+ bladerf_get_gain_mode(dev, BLADERF_CHANNEL_RX(i), &m);
+ LOGC(DDEV, INFO) << (m == BLADERF_GAIN_MANUAL ? "gain manual" : "gain AUTO");
+
+ status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0);
+ CHKRET()
+ int actual_gain;
+ status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(i), &actual_gain);
+ CHKRET()
+ LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale "
+ << r->scale << " actual " << actual_gain;
+ rx_gains[i] = actual_gain;
+
+ status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0);
+ CHKRET()
+ status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(i), &actual_gain);
+ CHKRET()
+ LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale "
+ << r->scale << " actual " << actual_gain;
+ rx_gains[i] = actual_gain;
+ }
+
+ status = bladerf_get_gain_range(dev, BLADERF_TX, &r);
+ CHKRET()
+ tx_gain_min = r->min;
+ tx_gain_max = r->max;
+ LOGC(DDEV, INFO) << "Supported Tx gain range [" << tx_gain_min << "; " << tx_gain_max << "]";
+
+ for (size_t i = 0; i < tx_gains.size(); i++) {
+ double gain = (tx_gain_min + tx_gain_max) / 2;
+ status = bladerf_set_gain(dev, BLADERF_CHANNEL_TX(i), 30);
+ CHKRET()
+ int actual_gain;
+ status = bladerf_get_gain(dev, BLADERF_CHANNEL_TX(i), &actual_gain);
+ CHKRET()
+ LOGC(DDEV, INFO) << "Default setting Tx gain for channel " << i << " to " << gain << " scale "
+ << r->scale << " actual " << actual_gain;
+ tx_gains[i] = actual_gain;
+ }
+
+ return;
+}
+
+void blade_device::set_rates()
+{
+ struct bladerf_rational_rate rate = { 0, static_cast<uint64_t>((1625e3 * 4)), 6 }, actual;
+ auto status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_RX(0), &rate, &actual);
+ CHKRET()
+ status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_TX(0), &rate, &actual);
+ CHKRET()
+
+ tx_rate = rx_rate = (double)rate.num / (double)rate.den;
+
+ LOGC(DDEV, INFO) << "Rates set to" << tx_rate << " / " << rx_rate;
+
+ bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth *)NULL);
+ bladerf_set_bandwidth(dev, BLADERF_CHANNEL_TX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth *)NULL);
+
+ ts_offset = 60; // FIXME: actual blade offset, should equal b2xx
+}
+
+double blade_device::setRxGain(double db, size_t chan)
+{
+ if (chan >= rx_gains.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return 0.0f;
+ }
+
+ bladerf_set_gain(dev, BLADERF_CHANNEL_RX(chan), 30); //db);
+ int actual_gain;
+ bladerf_get_gain(dev, BLADERF_CHANNEL_RX(chan), &actual_gain);
+
+ rx_gains[chan] = actual_gain;
+
+ LOGC(DDEV, INFO) << "Set RX gain to " << rx_gains[chan] << "dB (asked for " << db << "dB)";
+
+ return rx_gains[chan];
+}
+
+double blade_device::getRxGain(size_t chan)
+{
+ if (chan >= rx_gains.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return 0.0f;
+ }
+
+ return rx_gains[chan];
+}
+
+double blade_device::rssiOffset(size_t chan)
+{
+ double rssiOffset;
+ dev_band_desc desc;
+
+ if (chan >= rx_gains.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return 0.0f;
+ }
+
+ get_dev_band_desc(desc);
+ rssiOffset = rx_gains[chan] + desc.rxgain2rssioffset_rel;
+ return rssiOffset;
+}
+
+double blade_device::setPowerAttenuation(int atten, size_t chan)
+{
+ double tx_power, db;
+ dev_band_desc desc;
+
+ if (chan >= tx_gains.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel" << chan;
+ return 0.0f;
+ }
+
+ get_dev_band_desc(desc);
+ tx_power = desc.nom_out_tx_power - atten;
+ db = TxPower2TxGain(desc, tx_power);
+
+ bladerf_set_gain(dev, BLADERF_CHANNEL_TX(chan), 30);
+ int actual_gain;
+ bladerf_get_gain(dev, BLADERF_CHANNEL_RX(chan), &actual_gain);
+
+ tx_gains[chan] = actual_gain;
+
+ LOGC(DDEV, INFO)
+ << "Set TX gain to " << tx_gains[chan] << "dB, ~" << TxGain2TxPower(desc, tx_gains[chan]) << " dBm "
+ << "(asked for " << db << " dB, ~" << tx_power << " dBm)";
+
+ return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]);
+}
+double blade_device::getPowerAttenuation(size_t chan)
+{
+ dev_band_desc desc;
+ if (chan >= tx_gains.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return 0.0f;
+ }
+
+ get_dev_band_desc(desc);
+ return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]);
+}
+
+int blade_device::getNominalTxPower(size_t chan)
+{
+ dev_band_desc desc;
+ get_dev_band_desc(desc);
+
+ return desc.nom_out_tx_power;
+}
+
+int blade_device::open()
+{
+ bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_VERBOSE);
+ bladerf_set_usb_reset_on_open(true);
+ auto success = bladerf_open(&dev, cfg->dev_args);
+ if (success != 0) {
+ struct bladerf_devinfo *info;
+ auto num_devs = bladerf_get_device_list(&info);
+ LOGC(DDEV, ALERT) << "No bladerf devices found with identifier '" << cfg->dev_args << "'";
+ if (num_devs) {
+ for (int i = 0; i < num_devs; i++)
+ LOGC(DDEV, ALERT) << "Found device:" << info[i].product << " serial " << info[i].serial;
+ }
+
+ return -1;
+ }
+ if (strcmp("bladerf2", bladerf_get_board_name(dev))) {
+ LOGC(DDEV, ALERT) << "Only BladeRF2 supported! found:" << bladerf_get_board_name(dev);
+ return -1;
+ }
+
+ dev_type = blade_dev_type::BLADE2;
+ tx_window = TX_WINDOW_FIXED;
+ update_band_dev(dev_key(dev_type, tx_sps, rx_sps));
+
+ struct bladerf_devinfo info;
+ bladerf_get_devinfo(dev, &info);
+ LOGC(DDEV, INFO) << "Using discovered bladerf device " << info.serial;
+
+ tx_freqs.resize(chans);
+ rx_freqs.resize(chans);
+ tx_gains.resize(chans);
+ rx_gains.resize(chans);
+ rx_buffers.resize(chans);
+
+ switch (cfg->clock_ref) {
+ case REF_INTERNAL:
+ case REF_EXTERNAL:
+ break;
+ default:
+ LOGC(DDEV, ALERT) << "Invalid reference type";
+ return -1;
+ }
+
+ if (cfg->clock_ref == REF_EXTERNAL) {
+ bool is_locked;
+ int status = bladerf_set_pll_enable(dev, true);
+ CHKRET()
+ status = bladerf_set_pll_refclk(dev, 10000000);
+ CHKRET()
+ for (int i = 0; i < 20; i++) {
+ usleep(50 * 1000);
+ status = bladerf_get_pll_lock_state(dev, &is_locked);
+ CHKRET()
+ if (is_locked)
+ break;
+ }
+ if (!is_locked) {
+ LOGC(DDEV, ALERT) << "unable to lock refclk!";
+ return -1;
+ }
+ }
+
+ LOGC(DDEV, INFO)
+ << "Selected clock source is " << ((cfg->clock_ref == REF_INTERNAL) ? "internal" : "external 10Mhz");
+
+ set_rates();
+
+ /*
+ 1ts = 3/5200s
+ 1024*2 = small gap(~180us) every 9.23ms = every 16 ts? -> every 2 frames
+ 1024*1 = large gap(~627us) every 9.23ms = every 16 ts? -> every 2 frames
+
+ rif convertbuffer = 625*4 = 2500 -> 4 ts
+ rif rxtxbuf = 4 * segment(625*4) = 10000 -> 16 ts
+ */
+ const unsigned int num_buffers = 256;
+ const unsigned int buffer_size = 1024 * 4; /* Must be a multiple of 1024 */
+ const unsigned int num_transfers = 32;
+ const unsigned int timeout_ms = 3500;
+
+ bladerf_sync_config(dev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11_META, num_buffers, buffer_size, num_transfers,
+ timeout_ms);
+
+ bladerf_sync_config(dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11_META, num_buffers, buffer_size, num_transfers,
+ timeout_ms);
+
+ /* Number of samples per over-the-wire packet */
+ tx_spp = rx_spp = buffer_size;
+
+ size_t buf_len = SAMPLE_BUF_SZ / sizeof(uint32_t);
+ for (size_t i = 0; i < rx_buffers.size(); i++)
+ rx_buffers[i] = new smpl_buf(buf_len);
+
+ pkt_bufs = std::vector<std::vector<short> >(chans, std::vector<short>(2 * rx_spp));
+ for (size_t i = 0; i < pkt_bufs.size(); i++)
+ pkt_ptrs.push_back(&pkt_bufs[i].front());
+
+ init_gains();
+
+ return NORMAL;
+}
+
+bool blade_device::restart()
+{
+ /* Allow 100 ms delay to align multi-channel streams */
+ double delay = 0.2;
+ int status;
+
+ status = bladerf_enable_module(dev, BLADERF_CHANNEL_RX(0), true);
+ CHKRET()
+ status = bladerf_enable_module(dev, BLADERF_CHANNEL_TX(0), true);
+ CHKRET()
+
+ bladerf_timestamp now;
+ status = bladerf_get_timestamp(dev, BLADERF_RX, &now);
+ ts_initial = now + rx_rate * delay;
+ LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl;
+
+ return true;
+}
+
+bool blade_device::start()
+{
+ LOGC(DDEV, INFO) << "Starting USRP...";
+
+ if (started) {
+ LOGC(DDEV, ERROR) << "Device already started";
+ return false;
+ }
+
+ if (!restart())
+ return false;
+
+ started = true;
+ return true;
+}
+
+bool blade_device::stop()
+{
+ if (!started)
+ return false;
+
+ /* reset internal buffer timestamps */
+ for (size_t i = 0; i < rx_buffers.size(); i++)
+ rx_buffers[i]->reset();
+
+ band_reset();
+
+ started = false;
+ return true;
+}
+
+int blade_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun)
+{
+ ssize_t rc;
+ uint64_t ts;
+
+ if (bufs.size() != chans) {
+ LOGC(DDEV, ALERT) << "Invalid channel combination " << bufs.size();
+ return -1;
+ }
+
+ *overrun = false;
+ *underrun = false;
+
+ // Shift read time with respect to transmit clock
+ timestamp += ts_offset;
+
+ ts = timestamp;
+ LOGC(DDEV, DEBUG) << "Requested timestamp = " << ts;
+
+ // Check that timestamp is valid
+ rc = rx_buffers[0]->avail_smpls(timestamp);
+ if (rc < 0) {
+ LOGC(DDEV, ERROR) << rx_buffers[0]->str_code(rc);
+ LOGC(DDEV, ERROR) << rx_buffers[0]->str_status(timestamp);
+ return 0;
+ }
+
+ struct bladerf_metadata meta = {};
+ meta.timestamp = ts;
+
+ while (rx_buffers[0]->avail_smpls(timestamp) < len) {
+ thread_enable_cancel(false);
+ int status = bladerf_sync_rx(dev, pkt_ptrs[0], len, &meta, 200U);
+ thread_enable_cancel(true);
+
+ if (status != 0)
+ LOGC(DDEV, ERROR) << "RX broken: " << bladerf_strerror(status);
+ if (meta.flags & BLADERF_META_STATUS_OVERRUN)
+ LOGC(DDEV, ERROR) << "RX borken, OVERRUN: " << bladerf_strerror(status);
+
+ size_t num_smpls = meta.actual_count;
+ ;
+ ts = meta.timestamp;
+
+ for (size_t i = 0; i < rx_buffers.size(); i++) {
+ rc = rx_buffers[i]->write((short *)&pkt_bufs[i].front(), num_smpls, ts);
+
+ // Continue on local overrun, exit on other errors
+ if ((rc < 0)) {
+ LOGC(DDEV, ERROR) << rx_buffers[i]->str_code(rc);
+ LOGC(DDEV, ERROR) << rx_buffers[i]->str_status(timestamp);
+ if (rc != smpl_buf::ERROR_OVERFLOW)
+ return 0;
+ }
+ }
+ meta = {};
+ meta.timestamp = ts + num_smpls;
+ }
+
+ for (size_t i = 0; i < rx_buffers.size(); i++) {
+ rc = rx_buffers[i]->read(bufs[i], len, timestamp);
+ if ((rc < 0) || (rc != len)) {
+ LOGC(DDEV, ERROR) << rx_buffers[i]->str_code(rc);
+ LOGC(DDEV, ERROR) << rx_buffers[i]->str_status(timestamp);
+ return 0;
+ }
+ }
+
+ return len;
+}
+
+int blade_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun, unsigned long long timestamp)
+{
+ *underrun = false;
+ static bool first_tx = true;
+ struct bladerf_metadata meta = {};
+ if (first_tx) {
+ meta.timestamp = timestamp;
+ meta.flags = BLADERF_META_FLAG_TX_BURST_START;
+ first_tx = false;
+ }
+
+ thread_enable_cancel(false);
+ int status = bladerf_sync_tx(dev, (const void *)bufs[0], len, &meta, 200U);
+ thread_enable_cancel(true);
+
+ if (status != 0)
+ LOGC(DDEV, ERROR) << "TX broken: " << bladerf_strerror(status);
+
+ return len;
+}
+
+bool blade_device::updateAlignment(TIMESTAMP timestamp)
+{
+ return true;
+}
+
+bool blade_device::set_freq(double freq, size_t chan, bool tx)
+{
+ if (tx) {
+ bladerf_set_frequency(dev, BLADERF_CHANNEL_TX(chan), freq);
+ bladerf_frequency f;
+ bladerf_get_frequency(dev, BLADERF_CHANNEL_TX(chan), &f);
+ tx_freqs[chan] = f;
+ } else {
+ bladerf_set_frequency(dev, BLADERF_CHANNEL_RX(chan), freq);
+ bladerf_frequency f;
+ bladerf_get_frequency(dev, BLADERF_CHANNEL_RX(chan), &f);
+ rx_freqs[chan] = f;
+ }
+ LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << (tx ? "TX" : "RX") << "): " << std::endl;
+
+ return true;
+}
+
+bool blade_device::setTxFreq(double wFreq, size_t chan)
+{
+ if (chan >= tx_freqs.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return false;
+ }
+ ScopedLock lock(tune_lock);
+
+ if (!update_band_from_freq(wFreq, chan, true))
+ return false;
+
+ if (!set_freq(wFreq, chan, true))
+ return false;
+
+ return true;
+}
+
+bool blade_device::setRxFreq(double wFreq, size_t chan)
+{
+ if (chan >= rx_freqs.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return false;
+ }
+ ScopedLock lock(tune_lock);
+
+ if (!update_band_from_freq(wFreq, chan, false))
+ return false;
+
+ return set_freq(wFreq, chan, false);
+}
+
+double blade_device::getTxFreq(size_t chan)
+{
+ if (chan >= tx_freqs.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return 0.0;
+ }
+
+ return tx_freqs[chan];
+}
+
+double blade_device::getRxFreq(size_t chan)
+{
+ if (chan >= rx_freqs.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return 0.0;
+ }
+
+ return rx_freqs[chan];
+}
+
+bool blade_device::requiresRadioAlign()
+{
+ return false;
+}
+
+GSM::Time blade_device::minLatency()
+{
+ return GSM::Time(6, 7);
+}
+
+TIMESTAMP blade_device::initialWriteTimestamp()
+{
+ return ts_initial;
+}
+
+TIMESTAMP blade_device::initialReadTimestamp()
+{
+ return ts_initial;
+}
+
+double blade_device::fullScaleInputValue()
+{
+ return (double)2047;
+}
+
+double blade_device::fullScaleOutputValue()
+{
+ return (double)2047;
+}
+
+RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg)
+{
+ return new blade_device(type, cfg);
+}
diff --git a/Transceiver52M/device/bladerf/bladerf.h b/Transceiver52M/device/bladerf/bladerf.h
new file mode 100644
index 0000000..e32581e
--- /dev/null
+++ b/Transceiver52M/device/bladerf/bladerf.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2022 sysmocom - s.f.m.c. GmbH
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * 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/>.
+ * See the COPYING file in the main directory for details.
+ */
+
+#pragma once
+
+#include <map>
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "bandmanager.h"
+#include "radioDevice.h"
+#include "smpl_buf.h"
+
+extern "C" {
+#include <osmocom/gsm/gsm_utils.h>
+}
+
+enum class blade_dev_type { BLADE1, BLADE2 };
+
+struct dev_band_desc {
+ /* Maximum UHD Tx Gain which can be set/used without distorting the
+ output signal, and the resulting real output power measured when that
+ gain is used. Correct measured values only provided for B210 so far. */
+ double nom_uhd_tx_gain; /* dB */
+ double nom_out_tx_power; /* dBm */
+ /* Factor used to infer base real RSSI offset on the Rx path based on current
+ configured RxGain. The resulting rssiOffset is added to the per burst
+ calculated energy in upper layers. These values were empirically
+ found and may change based on multiple factors, see OS#4468.
+ rssiOffset = rxGain + rxgain2rssioffset_rel;
+ */
+ double rxgain2rssioffset_rel; /* dB */
+};
+
+/* Device parameter descriptor */
+struct dev_desc {
+ unsigned channels;
+ double mcr;
+ double rate;
+ double offset;
+ std::string desc_str;
+};
+
+using dev_key = std::tuple<blade_dev_type, int, int>;
+using dev_band_key = std::tuple<blade_dev_type, enum gsm_band>;
+using power_map_t = std::map<dev_band_key, dev_band_desc>;
+using dev_map_t = std::map<dev_key, dev_desc>;
+
+class blade_device : public RadioDevice, public band_manager<power_map_t, dev_map_t> {
+ public:
+ blade_device(InterfaceType iface, const struct trx_cfg *cfg);
+ ~blade_device();
+
+ int open();
+ bool start();
+ bool stop();
+ bool restart();
+ enum TxWindowType getWindowType()
+ {
+ return tx_window;
+ }
+
+ int readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun);
+
+ int writeSamples(std::vector<short *> &bufs, int len, bool *underrun, TIMESTAMP timestamp);
+
+ bool updateAlignment(TIMESTAMP timestamp);
+
+ bool setTxFreq(double wFreq, size_t chan);
+ bool setRxFreq(double wFreq, size_t chan);
+
+ TIMESTAMP initialWriteTimestamp();
+ TIMESTAMP initialReadTimestamp();
+
+ double fullScaleInputValue();
+ double fullScaleOutputValue();
+
+ double setRxGain(double db, size_t chan);
+ double getRxGain(size_t chan);
+ double maxRxGain(void)
+ {
+ return rx_gain_max;
+ }
+ double minRxGain(void)
+ {
+ return rx_gain_min;
+ }
+ double rssiOffset(size_t chan);
+
+ double setPowerAttenuation(int atten, size_t chan);
+ double getPowerAttenuation(size_t chan = 0);
+
+ int getNominalTxPower(size_t chan = 0);
+
+ double getTxFreq(size_t chan);
+ double getRxFreq(size_t chan);
+ double getRxFreq();
+
+ bool setRxAntenna(const std::string &ant, size_t chan)
+ {
+ return {};
+ };
+ std::string getRxAntenna(size_t chan)
+ {
+ return {};
+ };
+ bool setTxAntenna(const std::string &ant, size_t chan)
+ {
+ return {};
+ };
+ std::string getTxAntenna(size_t chan)
+ {
+ return {};
+ };
+
+ bool requiresRadioAlign();
+
+ GSM::Time minLatency();
+
+ inline double getSampleRate()
+ {
+ return tx_rate;
+ }
+
+ /** Receive and process asynchronous message
+ @return true if message received or false on timeout or error
+ */
+ bool recv_async_msg();
+
+ enum err_code {
+ ERROR_TIMING = -1,
+ ERROR_TIMEOUT = -2,
+ ERROR_UNRECOVERABLE = -3,
+ ERROR_UNHANDLED = -4,
+ };
+
+ protected:
+ struct bladerf *dev;
+ void *usrp_dev;
+
+ enum TxWindowType tx_window;
+ enum blade_dev_type dev_type;
+
+ double tx_rate, rx_rate;
+
+ double rx_gain_min, rx_gain_max;
+
+ std::vector<double> tx_gains, rx_gains;
+ std::vector<double> tx_freqs, rx_freqs;
+ size_t tx_spp, rx_spp;
+
+ bool started;
+ bool aligned;
+
+ size_t drop_cnt;
+ uint64_t prev_ts;
+
+ TIMESTAMP ts_initial, ts_offset;
+ std::vector<smpl_buf *> rx_buffers;
+ /* Sample buffers used to receive samples: */
+ std::vector<std::vector<short> > pkt_bufs;
+ /* Used to call UHD API: Buffer pointer of each elem in pkt_ptrs will
+ point to corresponding buffer of vector pkt_bufs. */
+ std::vector<short *> pkt_ptrs;
+
+ void init_gains();
+ void set_channels(bool swap);
+ void set_rates();
+ bool flush_recv(size_t num_pkts);
+
+ bool set_freq(double freq, size_t chan, bool tx);
+
+ Thread *async_event_thrd;
+ Mutex tune_lock;
+};
diff --git a/Transceiver52M/device/common/Makefile.am b/Transceiver52M/device/common/Makefile.am
index e14cc38..1a33592 100644
--- a/Transceiver52M/device/common/Makefile.am
+++ b/Transceiver52M/device/common/Makefile.am
@@ -1,10 +1,10 @@
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES)
-AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LMS_CFLAGS)
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS)
-noinst_HEADERS = radioDevice.h smpl_buf.h
+noinst_HEADERS = radioDevice.h smpl_buf.h bandmanager.h
noinst_LTLIBRARIES = libdevice_common.la
diff --git a/Transceiver52M/device/common/bandmanager.h b/Transceiver52M/device/common/bandmanager.h
new file mode 100644
index 0000000..d07f21d
--- /dev/null
+++ b/Transceiver52M/device/common/bandmanager.h
@@ -0,0 +1,137 @@
+#pragma once
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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 <string>
+#include <tuple>
+
+#include "Logger.h"
+
+extern "C" {
+#include <osmocom/gsm/gsm_utils.h>
+}
+
+template <typename powermapt, typename devmapt>
+class band_manager {
+ using powerkeyt = typename powermapt::key_type;
+ using powermappedt = typename powermapt::mapped_type;
+ using devkeyt = typename devmapt::key_type;
+ devkeyt m_dev_type;
+ const powermapt &m_power_map;
+ const devmapt &m_dev_map;
+ powerkeyt m_fallback;
+ enum gsm_band m_band;
+ powermappedt m_band_desc;
+ bool band_ass_curr_sess{}; /* true if "band" was set after last POWEROFF */
+
+ // looks up either first tuple element (->enum) or straight enum
+ template <typename T, typename std::enable_if<std::is_enum<T>::value>::type *dummy = nullptr>
+ auto key_helper(T &t) -> T
+ {
+ return t;
+ }
+
+ template <typename T>
+ auto key_helper(T t) -> typename std::tuple_element<0, T>::type
+ {
+ return std::get<0>(t);
+ }
+
+ void assign_band_desc(enum gsm_band req_band)
+ {
+ auto key = key_helper(m_dev_type);
+ auto fallback_key = key_helper(m_fallback);
+ auto it = m_power_map.find({ key, req_band });
+ if (it == m_power_map.end()) {
+ auto desc = m_dev_map.at(m_dev_type);
+ LOGC(DDEV, ERROR) << "No Tx Power measurements exist for device " << desc.desc_str
+ << " on band " << gsm_band_name(req_band) << ", using fallback..";
+ it = m_power_map.find({ fallback_key, req_band });
+ }
+ OSMO_ASSERT(it != m_power_map.end());
+ m_band_desc = it->second;
+ }
+
+ bool set_band(enum gsm_band req_band)
+ {
+ if (band_ass_curr_sess && req_band != m_band) {
+ LOGC(DDEV, ALERT) << "Requesting band " << gsm_band_name(req_band)
+ << " different from previous band " << gsm_band_name(m_band);
+ return false;
+ }
+
+ if (req_band != m_band) {
+ m_band = req_band;
+ assign_band_desc(m_band);
+ }
+ band_ass_curr_sess = true;
+ return true;
+ }
+
+ public:
+ band_manager(const devkeyt &dev_type, const powermapt &power_map, const devmapt &dev_map, powerkeyt fallback)
+ : m_dev_type(dev_type), m_power_map(power_map), m_dev_map(dev_map), m_fallback(fallback),
+ m_band((enum gsm_band)0)
+ {
+ }
+ band_manager(const powermapt &power_map, const devmapt &dev_map)
+ : m_dev_type(dev_map.begin()->first), m_power_map(power_map), m_dev_map(dev_map),
+ m_fallback(m_power_map.begin()->first), m_band((enum gsm_band)0)
+ {
+ }
+ void band_reset()
+ {
+ band_ass_curr_sess = false;
+ }
+
+ void update_band_dev(devkeyt dev_type) {
+ m_dev_type = dev_type;
+ }
+
+ void get_dev_band_desc(powermappedt &desc)
+ {
+ if (m_band == 0) {
+ LOGC(DDEV, ERROR)
+ << "Power parameters requested before Tx Frequency was set! Providing band 900 by default...";
+ assign_band_desc(GSM_BAND_900);
+ }
+ desc = m_band_desc;
+ }
+
+ bool update_band_from_freq(double wFreq, int chan, bool is_tx)
+ {
+ enum gsm_band req_band;
+ auto dirstr = is_tx ? "Tx" : "Rx";
+ auto req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, !is_tx);
+ if (req_arfcn == 0xffff) {
+ LOGCHAN(chan, DDEV, ALERT)
+ << "Unknown ARFCN for " << dirstr << " Frequency " << wFreq / 1000 << " kHz";
+ return false;
+ }
+ if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
+ LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for " << dirstr << " Frequency " << wFreq
+ << " Hz (ARFCN " << req_arfcn << " )";
+ return false;
+ }
+
+ return set_band(req_band);
+ }
+}; \ No newline at end of file
diff --git a/Transceiver52M/device/common/radioDevice.h b/Transceiver52M/device/common/radioDevice.h
index 45fcdad..5c962d1 100644
--- a/Transceiver52M/device/common/radioDevice.h
+++ b/Transceiver52M/device/common/radioDevice.h
@@ -51,13 +51,10 @@ class RadioDevice {
MULTI_ARFCN,
};
- static RadioDevice *make(size_t tx_sps, size_t rx_sps, InterfaceType type,
- size_t chans = 1, double offset = 0.0,
- const std::vector<std::string>& tx_paths = std::vector<std::string>(1, ""),
- const std::vector<std::string>& rx_paths = std::vector<std::string>(1, ""));
+ static RadioDevice *make(InterfaceType type, const struct trx_cfg *cfg);
/** Initialize the USRP */
- virtual int open(const std::string &args, int ref, bool swap_channels)=0;
+ virtual int open() = 0;
virtual ~RadioDevice() { }
@@ -125,6 +122,9 @@ class RadioDevice {
/** return minimum Rx Gain **/
virtual double minRxGain(void) = 0;
+ /** return base RSSI offset to apply for received samples **/
+ virtual double rssiOffset(size_t chan) = 0;
+
/** returns the Nominal transmit output power of the transceiver in dBm, negative on error **/
virtual int getNominalTxPower(size_t chan = 0) = 0;
@@ -161,24 +161,30 @@ class RadioDevice {
double lo_offset;
std::vector<std::string> tx_paths, rx_paths;
std::vector<struct device_counters> m_ctr;
-
- RadioDevice(size_t tx_sps, size_t rx_sps, InterfaceType type, size_t chan_num, double offset,
- const std::vector<std::string>& tx_paths,
- const std::vector<std::string>& rx_paths):
- tx_sps(tx_sps), rx_sps(rx_sps), iface(type), chans(chan_num), lo_offset(offset),
- tx_paths(tx_paths), rx_paths(rx_paths)
- {
- if (iface == MULTI_ARFCN) {
- LOGC(DDEV, INFO) << "Multi-ARFCN: "<< chan_num << " logical chans -> 1 physical chans";
- chans = 1;
- }
-
- m_ctr.resize(chans);
- for (size_t i = 0; i < chans; i++) {
- memset(&m_ctr[i], 0, sizeof(m_ctr[i]));
- m_ctr[i].chan = i;
- }
- }
+ const struct trx_cfg *cfg;
+
+#define charp2str(a) ((a) ? std::string(a) : std::string(""))
+
+ RadioDevice(InterfaceType type, const struct trx_cfg *cfg)
+ : tx_sps(cfg->tx_sps), rx_sps(cfg->rx_sps), iface(type), chans(cfg->num_chans), lo_offset(cfg->offset),
+ m_ctr(chans), cfg(cfg)
+ {
+ /* Generate vector of rx/tx_path: */
+ for (unsigned int i = 0; i < cfg->num_chans; i++) {
+ rx_paths.push_back(charp2str(cfg->chans[i].rx_path));
+ tx_paths.push_back(charp2str(cfg->chans[i].tx_path));
+ }
+
+ if (iface == MULTI_ARFCN) {
+ LOGC(DDEV, INFO) << "Multi-ARFCN: " << chans << " logical chans -> 1 physical chans";
+ chans = 1;
+ }
+
+ for (size_t i = 0; i < chans; i++) {
+ memset(&m_ctr[i], 0, sizeof(m_ctr[i]));
+ m_ctr[i].chan = i;
+ }
+ }
bool set_antennas() {
unsigned int i;
diff --git a/Transceiver52M/device/ipc/IPCDevice.cpp b/Transceiver52M/device/ipc/IPCDevice.cpp
index 32a46e5..1d2b89a 100644
--- a/Transceiver52M/device/ipc/IPCDevice.cpp
+++ b/Transceiver52M/device/ipc/IPCDevice.cpp
@@ -56,20 +56,13 @@ using namespace std;
static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags);
-IPCDevice::IPCDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
- const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths)
- : RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths), tx_attenuation(),
- tmp_state(IPC_IF_MSG_GREETING_REQ), shm(NULL), shm_dec(0), started(false)
+IPCDevice::IPCDevice(InterfaceType iface, const struct trx_cfg *cfg)
+ : RadioDevice(iface, cfg), sk_chan_state(chans, ipc_per_trx_sock_state()), tx_attenuation(),
+ tmp_state(IPC_IF_MSG_GREETING_REQ), shm(NULL), shm_dec(0), rx_buffers(chans), started(false), tx_gains(chans),
+ rx_gains(chans)
{
LOGC(DDEV, INFO) << "creating IPC device...";
- rx_gains.resize(chans);
- tx_gains.resize(chans);
-
- rx_buffers.resize(chans);
-
- sk_chan_state.resize(chans, ipc_per_trx_sock_state());
-
/* Set up per-channel Rx timestamp based Ring buffers */
for (size_t i = 0; i < rx_buffers.size(); i++)
rx_buffers[i] = new smpl_buf(SAMPLE_BUF_SZ / sizeof(uint32_t));
@@ -100,12 +93,14 @@ IPCDevice::~IPCDevice()
int IPCDevice::ipc_shm_connect(const char *shm_name)
{
int fd;
+ char err_buf[256];
size_t shm_len;
int rc;
LOGP(DDEV, LOGL_NOTICE, "Opening shm path %s\n", shm_name);
if ((fd = shm_open(shm_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)) < 0) {
- LOGP(DDEV, LOGL_ERROR, "shm_open %d: %s\n", errno, strerror(errno));
+ LOGP(DDEV, LOGL_ERROR, "shm_open %d: %s\n", errno,
+ strerror_r(errno, err_buf, sizeof(err_buf)));
rc = -errno;
goto err_shm_open;
}
@@ -113,7 +108,8 @@ int IPCDevice::ipc_shm_connect(const char *shm_name)
// Get size of the allocated memory
struct stat shm_stat;
if (fstat(fd, &shm_stat) < 0) {
- LOGP(DDEV, LOGL_ERROR, "fstat %d: %s\n", errno, strerror(errno));
+ LOGP(DDEV, LOGL_ERROR, "fstat %d: %s\n", errno,
+ strerror_r(errno, err_buf, sizeof(err_buf)));
rc = -errno;
goto err_mmap;
}
@@ -122,7 +118,8 @@ int IPCDevice::ipc_shm_connect(const char *shm_name)
LOGP(DDEV, LOGL_NOTICE, "mmaping shared memory fd %d (size=%zu)\n", fd, shm_len);
if ((shm = mmap(NULL, shm_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
- LOGP(DDEV, LOGL_ERROR, "mmap %d: %s\n", errno, strerror(errno));
+ LOGP(DDEV, LOGL_ERROR, "mmap %d: %s\n", errno,
+ strerror_r(errno, err_buf, sizeof(err_buf)));
rc = -errno;
goto err_mmap;
}
@@ -522,7 +519,7 @@ static int ipc_sock_send(struct ipc_per_trx_sock_state *state, struct msgb *msg)
return -EIO;
}
msgb_enqueue(&state->upqueue, msg);
- conn_bfd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(conn_bfd);
return 0;
}
@@ -539,9 +536,9 @@ void IPCDevice::ipc_sock_close(struct ipc_per_trx_sock_state *state)
LOGP(DDEV, LOGL_NOTICE, "IPC socket has LOST connection\n");
+ osmo_fd_unregister(bfd);
close(bfd->fd);
bfd->fd = -1;
- osmo_fd_unregister(bfd);
/* flush the queue */
while (!llist_empty(&state->upqueue)) {
@@ -659,7 +656,7 @@ int IPCDevice::ipc_sock_write(struct osmo_fd *bfd)
msg = llist_entry(master_sk_state.upqueue.next, struct msgb, list);
ipc_prim = (struct ipc_sk_if *)msg->data;
- bfd->when &= ~BSC_FD_WRITE;
+ osmo_fd_write_disable(bfd);
/* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
if (!msgb_length(msg)) {
@@ -676,7 +673,7 @@ int IPCDevice::ipc_sock_write(struct osmo_fd *bfd)
goto close;
if (rc < 0) {
if (errno == EAGAIN) {
- bfd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(bfd);
break;
}
goto close;
@@ -706,7 +703,7 @@ int IPCDevice::ipc_chan_sock_write(struct osmo_fd *bfd)
/* peek at the beginning of the queue */
msg = llist_entry(sk_chan_state[bfd->priv_nr].upqueue.next, struct msgb, list);
ipc_prim = (struct ipc_sk_chan_if *)msg->data;
- bfd->when &= ~BSC_FD_WRITE;
+ osmo_fd_write_disable(bfd);
/* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
if (!msgb_length(msg)) {
LOGP(DDEV, LOGL_ERROR,
@@ -722,7 +719,7 @@ int IPCDevice::ipc_chan_sock_write(struct osmo_fd *bfd)
goto close;
if (rc < 0) {
if (errno == EAGAIN) {
- bfd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(bfd);
break;
}
goto close;
@@ -746,12 +743,12 @@ static int ipc_sock_cb(struct osmo_fd *bfd, unsigned int flags)
IPCDevice *device = static_cast<IPCDevice *>(bfd->data);
int rc = 0;
- if (flags & BSC_FD_READ)
+ if (flags & OSMO_FD_READ)
rc = device->ipc_sock_read(bfd);
if (rc < 0)
return rc;
- if (flags & BSC_FD_WRITE)
+ if (flags & OSMO_FD_WRITE)
rc = device->ipc_sock_write(bfd);
return rc;
@@ -762,22 +759,23 @@ static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags)
IPCDevice *device = static_cast<IPCDevice *>(bfd->data);
int rc = 0;
- if (flags & BSC_FD_READ)
+ if (flags & OSMO_FD_READ)
rc = device->ipc_chan_sock_read(bfd);
if (rc < 0)
return rc;
- if (flags & BSC_FD_WRITE)
+ if (flags & OSMO_FD_WRITE)
rc = device->ipc_chan_sock_write(bfd);
return rc;
}
-int IPCDevice::open(const std::string &args, int ref, bool swap_channels)
+int IPCDevice::open()
{
std::string k, v;
std::string::size_type keyend;
int rc;
+ std::string args(cfg->dev_args);
if ((keyend = args.find('=')) != std::string::npos) {
k = args.substr(0, keyend++);
@@ -793,7 +791,7 @@ int IPCDevice::open(const std::string &args, int ref, bool swap_channels)
INIT_LLIST_HEAD(&master_sk_state.upqueue);
rc = osmo_sock_unix_init_ofd(&master_sk_state.conn_bfd, SOCK_SEQPACKET, 0, v.c_str(), OSMO_SOCK_F_CONNECT);
if (rc < 0) {
- LOGC(DDEV, ERROR) << "Failed to connect to the BTS (" << v << "). "
+ LOGC(DDEV, ERROR) << "Failed to connect to the IPC device (" << v << "). "
<< "Retrying...\n";
osmo_timer_setup(&master_sk_state.timer, ipc_sock_timeout, NULL);
osmo_timer_schedule(&master_sk_state.timer, 5, 0);
@@ -812,7 +810,7 @@ int IPCDevice::open(const std::string &args, int ref, bool swap_channels)
while (tmp_state != IPC_IF_MSG_INFO_CNF)
osmo_select_main(0);
- ipc_tx_open_req(&master_sk_state, chans, ref);
+ ipc_tx_open_req(&master_sk_state, chans, cfg->clock_ref);
/* Wait until confirmation is recieved */
while (tmp_state != IPC_IF_MSG_OPEN_CNF)
osmo_select_main(0);
@@ -835,6 +833,7 @@ void IPCDevice::manually_poll_sock_fds()
{
struct timeval wait = { 0, 100000 };
fd_set crfds, cwfds;
+ char err_buf[256];
int max_fd = 0;
FD_ZERO(&crfds);
@@ -849,7 +848,11 @@ void IPCDevice::manually_poll_sock_fds()
FD_SET(curr_fd->fd, &cwfds);
}
- select(max_fd + 1, &crfds, &cwfds, 0, &wait);
+ if (select(max_fd + 1, &crfds, &cwfds, 0, &wait) < 0) {
+ LOGP(DDEV, LOGL_ERROR, "select() failed: %s\n",
+ strerror_r(errno, err_buf, sizeof(err_buf)));
+ return;
+ }
for (unsigned int i = 0; i < chans; i++) {
int flags = 0;
@@ -1256,16 +1259,15 @@ bool IPCDevice::setRxFreq(double wFreq, size_t chan)
return send_chan_wait_rsp(chan, msg, IPC_IF_MSG_SETFREQ_CNF);
}
-RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset,
- const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths)
+RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg)
{
- if (tx_sps != rx_sps) {
+ if (cfg->tx_sps != cfg->rx_sps) {
LOGC(DDEV, ERROR) << "IPC Requires tx_sps == rx_sps";
return NULL;
}
- if (lo_offset != 0.0) {
+ if (cfg->offset != 0.0) {
LOGC(DDEV, ERROR) << "IPC doesn't support lo_offset";
return NULL;
}
- return new IPCDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
+ return new IPCDevice(type, cfg);
}
diff --git a/Transceiver52M/device/ipc/IPCDevice.h b/Transceiver52M/device/ipc/IPCDevice.h
index a7618f9..0cf3d46 100644
--- a/Transceiver52M/device/ipc/IPCDevice.h
+++ b/Transceiver52M/device/ipc/IPCDevice.h
@@ -115,12 +115,11 @@ class IPCDevice : public RadioDevice {
int ipc_chan_sock_write(osmo_fd *bfd);
/** Object constructor */
- IPCDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
- const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths);
+ IPCDevice(InterfaceType iface, const struct trx_cfg *cfg);
virtual ~IPCDevice() override;
/** Instantiate the IPC */
- virtual int open(const std::string &args, int ref, bool swap_channels) override;
+ virtual int open() override;
/** Start the IPC */
virtual bool start() override;
@@ -198,6 +197,9 @@ class IPCDevice : public RadioDevice {
/** return minimum Rx Gain **/
virtual double minRxGain(void) override;
+ /* FIXME: return rx_gains[chan] ? receive factor from IPC Driver? */
+ double rssiOffset(size_t chan) override { return 0.0f; };
+
double setPowerAttenuation(int atten, size_t chan) override;
double getPowerAttenuation(size_t chan = 0) override;
diff --git a/Transceiver52M/device/ipc/Makefile.am b/Transceiver52M/device/ipc/Makefile.am
index e4dc51a..c08621f 100644
--- a/Transceiver52M/device/ipc/Makefile.am
+++ b/Transceiver52M/device/ipc/Makefile.am
@@ -1,9 +1,8 @@
include $(top_srcdir)/Makefile.common
-AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
-AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
-AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
-AM_LDFLAGS = -lpthread -lrt
+AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(UHD_CFLAGS)
+AM_CXXFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(UHD_CFLAGS)
noinst_HEADERS = IPCDevice.h shm.h ipc_shm.h ipc_chan.h ipc_sock.h
@@ -14,29 +13,22 @@ endif
noinst_LTLIBRARIES = libdevice.la
libdevice_la_SOURCES = IPCDevice.cpp shm.c ipc_shm.c ipc_chan.c ipc_sock.c
-libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la
-libdevice_la_CXXFLAGS = $(AM_CXXFLAGS) -DIPCMAGIC
+libdevice_la_CPPFLAGS = $(AM_CPPFLAGS) -DIPCMAGIC
+libdevice_la_LIBADD = \
+ $(top_builddir)/Transceiver52M/device/common/libdevice_common.la \
+ -lpthread \
+ -lrt \
+ $(NULL)
if DEVICE_UHD
-#work around distclean issue on older autotools vers:
-#a direct build of ../uhd/UHDDevice.cpp tries to clean
-#../uhd/.dep/UHDDevice.Plo twice and fails
-uhddev_ipc.cpp:
- echo "#include \"../uhd/UHDDevice.cpp\"" >$@
-CLEANFILES= uhddev_ipc.cpp
-
bin_PROGRAMS = ipc-driver-test
#ipc_driver_test_SHORTNAME = drvt
-ipc_driver_test_SOURCES = ipc-driver-test.c uhdwrap.cpp ipc_shm.c ipc_chan.c ipc_sock.c uhddev_ipc.cpp
+ipc_driver_test_SOURCES = ipc-driver-test.c uhdwrap.cpp ../uhd/UHDDevice.cpp
ipc_driver_test_LDADD = \
- shm.lo \
+ libdevice.la \
+ $(COMMON_LA) \
$(LIBOSMOCORE_LIBS) \
- $(LIBOSMOCTRL_LIBS) \
- $(LIBOSMOVTY_LIBS)
-ipc_driver_test_CXXFLAGS = $(AM_CXXFLAGS) $(UHD_CFLAGS)
-ipc_driver_test_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS)
-ipc_driver_test_CFLAGS = $(AM_CFLAGS) $(UHD_CFLAGS)
-ipc_driver_test_LDFLAGS = $(AM_LDFLAGS) $(UHD_LIBS)
-ipc_driver_test_LDADD += $(top_builddir)/Transceiver52M/device/common/libdevice_common.la $(top_builddir)/CommonLibs/libcommon.la
+ $(UHD_LIBS) \
+ $(NULL)
endif
diff --git a/Transceiver52M/device/ipc/ipc-driver-test.c b/Transceiver52M/device/ipc/ipc-driver-test.c
index 44c8b0e..d8284c7 100644
--- a/Transceiver52M/device/ipc/ipc-driver-test.c
+++ b/Transceiver52M/device/ipc/ipc-driver-test.c
@@ -49,6 +49,7 @@
#include "ipc_sock.h"
#define DEFAULT_SHM_NAME "/osmo-trx-ipc-driver-shm2"
+#define IPC_SOCK_PATH_PREFIX "/tmp"
static void *tall_ctx;
struct ipc_sock_state *global_ipc_sock_state;
@@ -63,6 +64,11 @@ void *global_dev;
static struct ipc_shm_region *decoded_region;
+static struct {
+ int msocknum;
+ char *ud_prefix_dir;
+} cmdline_cfg;
+
static const struct log_info_cat default_categories[] = {
[DMAIN] = {
.name = "DMAIN",
@@ -185,8 +191,8 @@ static int ipc_tx_open_cnf(int rc, uint32_t num_chans, int32_t timingoffset)
chan_info = ipc_prim->u.open_cnf.chan_info;
for (i = 0; i < num_chans; i++) {
- snprintf(chan_info->chan_ipc_sk_path, sizeof(chan_info->chan_ipc_sk_path), "%s_%d",
- IPC_SOCK_PATH_PREFIX, i);
+ snprintf(chan_info->chan_ipc_sk_path, sizeof(chan_info->chan_ipc_sk_path),"%s/ipc_sock%d_%d",
+ cmdline_cfg.ud_prefix_dir, cmdline_cfg.msocknum, i);
/* FIXME: dynamc chan limit, currently 8 */
if (i < 8)
ipc_sock_init(chan_info->chan_ipc_sk_path, &global_ctrl_socks[i], ipc_chan_sock_accept, i);
@@ -394,10 +400,7 @@ int ipc_sock_init(const char *path, struct ipc_sock_state **global_state_var,
return -1;
}
- bfd->when = BSC_FD_READ;
- bfd->cb = sock_callback_fn;
- bfd->data = state;
- bfd->priv_nr = n;
+ osmo_fd_setup(bfd, bfd->fd, OSMO_FD_READ, sock_callback_fn, state, n);
rc = osmo_fd_register(bfd);
if (rc < 0) {
@@ -416,20 +419,20 @@ static void print_help(void)
{
printf("ipc-driver-test Usage:\n"
" -h --help This message\n"
+ " -u --unix-sk-dir DIR Existing directory where to create the Master socket\n"
" -n --sock-num NR Master socket suffix number NR\n");
}
-static int msocknum = 0;
-
static void handle_options(int argc, char **argv)
{
while (1) {
int option_index = 0, c;
const struct option long_options[] = { { "help", 0, 0, 'h' },
+ { "unix-sk-dir", 1, 0, 'u' },
{ "sock-num", 1, 0, 'n' },
{ 0, 0, 0, 0 } };
- c = getopt_long(argc, argv, "hn:", long_options, &option_index);
+ c = getopt_long(argc, argv, "hu:n:", long_options, &option_index);
if (c == -1)
break;
@@ -438,9 +441,13 @@ static void handle_options(int argc, char **argv)
print_help();
exit(0);
break;
+ case 'u':
+ cmdline_cfg.ud_prefix_dir = talloc_strdup(tall_ctx, optarg);
+ break;
case 'n':
- msocknum = atoi(optarg);
+ cmdline_cfg.msocknum = atoi(optarg);
break;
+
default:
exit(2);
break;
@@ -455,14 +462,19 @@ static void handle_options(int argc, char **argv)
int main(int argc, char **argv)
{
- char ipc_msock_path[sizeof(IPC_SOCK_PATH_PREFIX) + 3];
+ char ipc_msock_path[128];
tall_ctx = talloc_named_const(NULL, 0, "OsmoTRX");
msgb_talloc_ctx_init(tall_ctx, 0);
osmo_init_logging2(tall_ctx, &log_infox);
log_enable_multithread();
handle_options(argc, argv);
- snprintf(ipc_msock_path, sizeof(ipc_msock_path), "%s%d", IPC_SOCK_PATH_PREFIX, msocknum);
+
+ if (!cmdline_cfg.ud_prefix_dir)
+ cmdline_cfg.ud_prefix_dir = talloc_strdup(tall_ctx, IPC_SOCK_PATH_PREFIX);
+
+
+ snprintf(ipc_msock_path, sizeof(ipc_msock_path), "%s/ipc_sock%d", cmdline_cfg.ud_prefix_dir, cmdline_cfg.msocknum);
LOGP(DMAIN, LOGL_INFO, "Starting %s\n", argv[0]);
ipc_sock_init(ipc_msock_path, &global_ipc_sock_state, ipc_sock_accept, 0);
diff --git a/Transceiver52M/device/ipc/ipc_chan.c b/Transceiver52M/device/ipc/ipc_chan.c
index 9e35fb2..2a6f490 100644
--- a/Transceiver52M/device/ipc/ipc_chan.c
+++ b/Transceiver52M/device/ipc/ipc_chan.c
@@ -142,7 +142,7 @@ int ipc_chan_sock_send(struct msgb *msg, uint8_t chan_nr)
return -EIO;
}
msgb_enqueue(&state->upqueue, msg);
- conn_bfd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(conn_bfd);
return 0;
}
@@ -160,7 +160,7 @@ static int ipc_chan_sock_write(struct osmo_fd *bfd)
msg = llist_entry(state->upqueue.next, struct msgb, list);
ipc_prim = (struct ipc_sk_chan_if *)msg->data;
- bfd->when &= ~BSC_FD_WRITE;
+ osmo_fd_write_disable(bfd);
/* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
if (!msgb_length(msg)) {
@@ -177,7 +177,7 @@ static int ipc_chan_sock_write(struct osmo_fd *bfd)
goto close;
if (rc < 0) {
if (errno == EAGAIN) {
- bfd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(bfd);
break;
}
goto close;
@@ -200,12 +200,12 @@ static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags)
{
int rc = 0;
- if (flags & BSC_FD_READ)
+ if (flags & OSMO_FD_READ)
rc = ipc_chan_sock_read(bfd);
if (rc < 0)
return rc;
- if (flags & BSC_FD_WRITE)
+ if (flags & OSMO_FD_WRITE)
rc = ipc_chan_sock_write(bfd);
return rc;
@@ -231,18 +231,13 @@ int ipc_chan_sock_accept(struct osmo_fd *bfd, unsigned int flags)
"osmo-trx connects but we already have "
"another active connection ?!?\n");
/* We already have one IPC connected, this is all we support */
- state->listen_bfd.when &= ~BSC_FD_READ;
+ osmo_fd_read_disable(&state->listen_bfd);
close(rc);
return 0;
}
- conn_bfd->fd = rc;
- conn_bfd->when = BSC_FD_READ;
- conn_bfd->cb = ipc_chan_sock_cb;
- conn_bfd->data = state;
-
/* copy chan nr, required for proper bfd<->chan # mapping */
- conn_bfd->priv_nr = bfd->priv_nr;
+ osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, ipc_chan_sock_cb, state, bfd->priv_nr);
if (osmo_fd_register(conn_bfd) != 0) {
LOGP(DDEV, LOGL_ERROR,
diff --git a/Transceiver52M/device/ipc/ipc_sock.c b/Transceiver52M/device/ipc/ipc_sock.c
index 435dc3c..9e8ab82 100644
--- a/Transceiver52M/device/ipc/ipc_sock.c
+++ b/Transceiver52M/device/ipc/ipc_sock.c
@@ -84,7 +84,7 @@ int ipc_sock_send(struct msgb *msg)
return -EIO;
}
msgb_enqueue(&state->upqueue, msg);
- conn_bfd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(conn_bfd);
return 0;
}
@@ -97,12 +97,12 @@ void ipc_sock_close(struct ipc_sock_state *state)
ipc_exit_requested = 1;
+ osmo_fd_unregister(bfd);
close(bfd->fd);
bfd->fd = -1;
- osmo_fd_unregister(bfd);
/* re-enable the generation of ACCEPT for new connections */
- state->listen_bfd.when |= BSC_FD_READ;
+ osmo_fd_read_enable(&state->listen_bfd);
/* flush the queue */
while (!llist_empty(&state->upqueue)) {
@@ -172,7 +172,7 @@ static int ipc_sock_write(struct osmo_fd *bfd)
msg = llist_entry(state->upqueue.next, struct msgb, list);
ipc_prim = (struct ipc_sk_if *)msg->data;
- bfd->when &= ~BSC_FD_WRITE;
+ osmo_fd_write_disable(bfd);
/* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
if (!msgb_length(msg)) {
@@ -189,7 +189,7 @@ static int ipc_sock_write(struct osmo_fd *bfd)
goto close;
if (rc < 0) {
if (errno == EAGAIN) {
- bfd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(bfd);
break;
}
goto close;
@@ -212,12 +212,12 @@ static int ipc_sock_cb(struct osmo_fd *bfd, unsigned int flags)
{
int rc = 0;
- if (flags & BSC_FD_READ)
+ if (flags & OSMO_FD_READ)
rc = ipc_sock_read(bfd);
if (rc < 0)
return rc;
- if (flags & BSC_FD_WRITE)
+ if (flags & OSMO_FD_WRITE)
rc = ipc_sock_write(bfd);
return rc;
@@ -244,15 +244,12 @@ int ipc_sock_accept(struct osmo_fd *bfd, unsigned int flags)
"ip clent connects but we already have "
"another active connection ?!?\n");
/* We already have one IPC connected, this is all we support */
- state->listen_bfd.when &= ~BSC_FD_READ;
+ osmo_fd_read_disable(&state->listen_bfd);
close(rc);
return 0;
}
- conn_bfd->fd = rc;
- conn_bfd->when = BSC_FD_READ;
- conn_bfd->cb = ipc_sock_cb;
- conn_bfd->data = state;
+ osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, ipc_sock_cb, state, 0);
if (osmo_fd_register(conn_bfd) != 0) {
LOGP(DDEV, LOGL_ERROR,
diff --git a/Transceiver52M/device/ipc/shm.h b/Transceiver52M/device/ipc/shm.h
index fcddd54..46c3add 100644
--- a/Transceiver52M/device/ipc/shm.h
+++ b/Transceiver52M/device/ipc/shm.h
@@ -79,8 +79,6 @@ struct ipc_shm_region *ipc_shm_decode_region(void *tall_ctx, struct ipc_shm_raw_
//////////////////
// Master socket
//////////////////
-
-#define IPC_SOCK_PATH_PREFIX "/tmp/ipc_sock"
#define IPC_SOCK_API_VERSION 1
/* msg_type */
diff --git a/Transceiver52M/device/ipc/uhdwrap.cpp b/Transceiver52M/device/ipc/uhdwrap.cpp
index 130f80a..302f763 100644
--- a/Transceiver52M/device/ipc/uhdwrap.cpp
+++ b/Transceiver52M/device/ipc/uhdwrap.cpp
@@ -31,14 +31,16 @@ extern "C" {
#include "../uhd/UHDDevice.h"
#include "uhdwrap.h"
-#include "trx_vty.h"
#include "Logger.h"
#include "Threads.h"
#include "Utils.h"
-int uhd_wrap::open(const std::string &args, int ref, bool swap_channels)
+// no vty source for cfg params here, so we have to build our own
+static struct trx_cfg actual_cfg = {};
+
+int uhd_wrap::open()
{
- int rv = uhd_device::open(args, ref, swap_channels);
+ int rv = uhd_device::open();
samps_per_buff_rx = rx_stream->get_max_num_samps();
samps_per_buff_tx = tx_stream->get_max_num_samps();
channel_count = usrp_dev->get_rx_num_channels();
@@ -85,36 +87,33 @@ int uhd_wrap::wrap_read(TIMESTAMP *timestamp)
extern "C" void *uhdwrap_open(struct ipc_sk_if_open_req *open_req)
{
- unsigned int rx_sps, tx_sps;
+ actual_cfg.num_chans = open_req->num_chans;
+ actual_cfg.swap_channels = false;
+ /* FIXME: this is actually the sps value, not the sample rate!
+ * sample rate is looked up according to the sps rate by uhd backend */
+ actual_cfg.rx_sps = open_req->rx_sample_freq_num / open_req->rx_sample_freq_den;
+ actual_cfg.tx_sps = open_req->tx_sample_freq_num / open_req->tx_sample_freq_den;
/* FIXME: dev arg string* */
/* FIXME: rx frontend bw? */
/* FIXME: tx frontend bw? */
- ReferenceType cref;
switch (open_req->clockref) {
case FEATURE_MASK_CLOCKREF_EXTERNAL:
- cref = ReferenceType::REF_EXTERNAL;
+ actual_cfg.clock_ref = ReferenceType::REF_EXTERNAL;
break;
case FEATURE_MASK_CLOCKREF_INTERNAL:
default:
- cref = ReferenceType::REF_INTERNAL;
+ actual_cfg.clock_ref = ReferenceType::REF_INTERNAL;
break;
}
- std::vector<std::string> tx_paths;
- std::vector<std::string> rx_paths;
for (unsigned int i = 0; i < open_req->num_chans; i++) {
- tx_paths.push_back(open_req->chan_info[i].tx_path);
- rx_paths.push_back(open_req->chan_info[i].rx_path);
+ actual_cfg.chans[i].rx_path = open_req->chan_info[i].tx_path;
+ actual_cfg.chans[i].tx_path = open_req->chan_info[i].rx_path;
}
- /* FIXME: this is actually the sps value, not the sample rate!
- * sample rate is looked up according to the sps rate by uhd backend */
- rx_sps = open_req->rx_sample_freq_num / open_req->rx_sample_freq_den;
- tx_sps = open_req->tx_sample_freq_num / open_req->tx_sample_freq_den;
- uhd_wrap *uhd_wrap_dev =
- new uhd_wrap(tx_sps, rx_sps, RadioDevice::NORMAL, open_req->num_chans, 0.0, tx_paths, rx_paths);
- uhd_wrap_dev->open("", cref, false);
+ uhd_wrap *uhd_wrap_dev = new uhd_wrap(RadioDevice::NORMAL, &actual_cfg);
+ uhd_wrap_dev->open();
return uhd_wrap_dev;
}
diff --git a/Transceiver52M/device/ipc/uhdwrap.h b/Transceiver52M/device/ipc/uhdwrap.h
index e235fe7..44cd9aa 100644
--- a/Transceiver52M/device/ipc/uhdwrap.h
+++ b/Transceiver52M/device/ipc/uhdwrap.h
@@ -41,7 +41,7 @@ class uhd_wrap : public uhd_device {
// void ipc_sock_close() override {};
int wrap_read(TIMESTAMP *timestamp);
- virtual int open(const std::string &args, int ref, bool swap_channels) override;
+ virtual int open() override;
// bool start() override;
// bool stop() override;
diff --git a/Transceiver52M/device/lms/LMSDevice.cpp b/Transceiver52M/device/lms/LMSDevice.cpp
index 20b9856..7c220d2 100644
--- a/Transceiver52M/device/lms/LMSDevice.cpp
+++ b/Transceiver52M/device/lms/LMSDevice.cpp
@@ -23,7 +23,6 @@
#include <map>
-#include "trx_vty.h"
#include "Logger.h"
#include "Threads.h"
#include "LMSDevice.h"
@@ -32,6 +31,7 @@
#include <lime/LimeSuite.h>
extern "C" {
+#include "trx_vty.h"
#include "osmo_signal.h"
#include <osmocom/core/utils.h>
}
@@ -40,8 +40,6 @@ extern "C" {
#include "config.h"
#endif
-using namespace std;
-
#define MAX_ANTENNA_LIST_SIZE 10
#define GSM_CARRIER_BW 270000.0 /* 270kHz */
#define LMS_MIN_BW_SUPPORTED 2.5e6 /* 2.5mHz, minimum supported by LMS */
@@ -54,58 +52,28 @@ using namespace std;
#define LMS_DEV_SDR_MINI_PREFIX_NAME "LimeSDR-Mini"
#define LMS_DEV_NET_MICRO_PREFIX_NAME "LimeNET-Micro"
-/* Device parameter descriptor */
-struct dev_desc {
- /* Does LimeSuite allow switching the clock source for this device?
- * LimeSDR-Mini does not have switches but needs soldering to select
- * external/internal clock. Any call to LMS_SetClockFreq() will fail.
- */
- bool clock_src_switchable;
- /* Does LimeSuite allow using REF_INTERNAL for this device?
- * LimeNET-Micro does not like selecting internal clock
- */
- bool clock_src_int_usable;
- /* Sample rate coef (without having TX/RX samples per symbol into account) */
- double rate;
- /* Sample rate coef (without having TX/RX samples per symbol into account), if multi-arfcn is enabled */
- double rate_multiarfcn;
- /* Coefficient multiplied by TX sample rate in order to shift Tx time */
- double ts_offset_coef;
- /* Coefficient multiplied by TX sample rate in order to shift Tx time, if multi-arfcn is enabled */
- double ts_offset_coef_multiarfcn;
- /* Device Name Prefix as presented by LimeSuite API LMS_GetDeviceInfo() */
- std::string name_prefix;
-};
-static const std::map<enum lms_dev_type, struct dev_desc> dev_param_map {
+
+static const dev_map_t dev_param_map {
{ LMS_DEV_SDR_USB, { true, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_SDR_USB_PREFIX_NAME } },
{ LMS_DEV_SDR_MINI, { false, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 8.2e-5, LMS_DEV_SDR_MINI_PREFIX_NAME } },
{ LMS_DEV_NET_MICRO, { true, false, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_NET_MICRO_PREFIX_NAME } },
{ LMS_DEV_UNKNOWN, { true, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, "UNKNOWN" } },
};
-typedef std::tuple<lms_dev_type, enum gsm_band> dev_band_key;
-/* Maximum LimeSuite Tx Gain which can be set/used without distorting the output
- * signal, and the resulting real output power measured when that gain is used.
- */
-struct dev_band_desc {
- double nom_lms_tx_gain; /* dB */
- double nom_out_tx_power; /* dBm */
-};
-typedef std::map<dev_band_key, dev_band_desc>::const_iterator dev_band_map_it;
-static const std::map<dev_band_key, dev_band_desc> dev_band_nom_power_param_map {
- { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_850), { 73.0, 11.2 } },
- { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_900), { 73.0, 10.8 } },
- { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1800), { 65.0, -3.5 } }, /* FIXME: OS#4583: 1800Mhz is failing above TxGain=65, which is around -3.5dBm (already < 0 dBm) */
- { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1900), { 73.0, 1.7 } }, /* FIXME: OS#4583: 1900MHz is failing in all TxGain values */
- { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_850), { 66.0, 3.1 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */
- { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_900), { 66.0, 2.8 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */
- { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1800), { 66.0, -11.6 } }, /* OS#4583: Any of BAND1 or BAND2 is fine */
- { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1900), { 66.0, -9.2 } }, /* FIXME: OS#4583: Ensure BAND1 is used at startup */
- { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_850), { 71.0, 6.8 } },
- { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_900), { 71.0, 6.8 } },
- { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1800), { 65.0, -10.5 } }, /* OS#4583: TxGain=71 (-4.4dBm) FAIL rms phase errors ~10° */
- { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1900), { 71.0, -6.3 } }, /* FIXME: OS#4583: all FAIL, BAND1/BAND2 rms phase errors >23° */
+static const power_map_t dev_band_nom_power_param_map {
+ { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_850), { 73.0, 11.2, -6.0 } },
+ { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_900), { 73.0, 10.8, -6.0 } },
+ { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1800), { 65.0, -3.5, -17.0 } }, /* FIXME: OS#4583: 1800Mhz is failing above TxGain=65, which is around -3.5dBm (already < 0 dBm) */
+ { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1900), { 73.0, 1.7, -17.0 } }, /* FIXME: OS#4583: 1900MHz is failing in all TxGain values */
+ { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_850), { 66.0, 3.1, -6.0 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */
+ { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_900), { 66.0, 2.8, -6.0 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */
+ { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1800), { 66.0, -11.6, -17.0 } }, /* OS#4583: Any of BAND1 or BAND2 is fine */
+ { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1900), { 66.0, -9.2, -17.0 } }, /* FIXME: OS#4583: Ensure BAND1 is used at startup */
+ { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_850), { 71.0, 6.8, -6.0 } },
+ { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_900), { 71.0, 6.8, -6.0 } },
+ { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1800), { 65.0, -10.5, -17.0 } }, /* OS#4583: TxGain=71 (-4.4dBm) FAIL rms phase errors ~10° */
+ { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1900), { 71.0, -6.3, -17.0 } }, /* FIXME: OS#4583: all FAIL, BAND1/BAND2 rms phase errors >23° */
};
/* So far measurements done for B210 show really close to linear relationship
@@ -131,8 +99,8 @@ static enum lms_dev_type parse_dev_type(lms_device_t *m_lms_dev)
enum lms_dev_type dev_type = it->first;
struct dev_desc desc = it->second;
- if (strncmp(device_info->deviceName, desc.name_prefix.c_str(), desc.name_prefix.length()) == 0) {
- LOGC(DDEV, INFO) << "Device identified as " << desc.name_prefix;
+ if (strncmp(device_info->deviceName, desc.desc_str.c_str(), desc.desc_str.length()) == 0) {
+ LOGC(DDEV, INFO) << "Device identified as " << desc.desc_str;
return dev_type;
}
it++;
@@ -140,11 +108,10 @@ static enum lms_dev_type parse_dev_type(lms_device_t *m_lms_dev)
return LMS_DEV_UNKNOWN;
}
-LMSDevice::LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
- const std::vector<std::string>& tx_paths,
- const std::vector<std::string>& rx_paths):
- RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths),
- m_lms_dev(NULL), started(false), band((enum gsm_band)0), m_dev_type(LMS_DEV_UNKNOWN)
+LMSDevice::LMSDevice(InterfaceType iface, const struct trx_cfg *cfg)
+ : RadioDevice(iface, cfg),
+ band_manager(m_dev_type, dev_band_nom_power_param_map, dev_param_map, {LMS_DEV_SDR_USB, GSM_BAND_850}), m_lms_dev(NULL),
+ started(false), m_dev_type(LMS_DEV_UNKNOWN)
{
LOGC(DDEV, INFO) << "creating LMS device...";
@@ -210,7 +177,7 @@ static void print_range(const char* name, lms_range_t *range)
int info_list_find(lms_info_str_t* info_list, unsigned int count, const std::string &args)
{
unsigned int i, j;
- std::vector<string> filters;
+ std::vector<std::string> filters;
filters = comma_delimited_to_vector(args.c_str());
@@ -231,28 +198,7 @@ int info_list_find(lms_info_str_t* info_list, unsigned int count, const std::str
return -1;
}
-void LMSDevice::get_dev_band_desc(dev_band_desc& desc)
-{
- dev_band_map_it it;
- enum gsm_band req_band = band;
-
- if (req_band == 0) {
- LOGC(DDEV, ERROR) << "Nominal Tx Power requested before Tx Frequency was set! Providing band 900 by default... ";
- req_band = GSM_BAND_900;
- }
- it = dev_band_nom_power_param_map.find(dev_band_key(m_dev_type, req_band));
- if (it == dev_band_nom_power_param_map.end()) {
- dev_desc desc = dev_param_map.at(m_dev_type);
- LOGC(DDEV, ERROR) << "No Tx Power measurements exist for device "
- << desc.name_prefix << " on band " << gsm_band_name(req_band)
- << ", using LimeSDR-USB ones as fallback";
- it = dev_band_nom_power_param_map.find(dev_band_key(LMS_DEV_SDR_USB, req_band));
- }
- OSMO_ASSERT(it != dev_band_nom_power_param_map.end())
- desc = it->second;
-}
-
-int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
+int LMSDevice::open()
{
lms_info_str_t* info_list;
lms_range_t range_sr;
@@ -265,11 +211,12 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
LMS_RegisterLogHandler(&lms_log_callback);
- if ((n = LMS_GetDeviceList(NULL)) < 0)
+ if ((rc = LMS_GetDeviceList(NULL)) < 0)
LOGC(DDEV, ERROR) << "LMS_GetDeviceList(NULL) failed";
- LOGC(DDEV, INFO) << "Devices found: " << n;
- if (n < 1)
+ LOGC(DDEV, INFO) << "Devices found: " << rc;
+ if (rc < 1)
return -1;
+ n = rc;
info_list = new lms_info_str_t[n];
@@ -279,9 +226,9 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
for (i = 0; i < n; i++)
LOGC(DDEV, INFO) << "Device [" << i << "]: " << info_list[i];
- dev_id = info_list_find(info_list, n, args);
+ dev_id = info_list_find(info_list, n, cfg->dev_args);
if (dev_id == -1) {
- LOGC(DDEV, ERROR) << "No LMS device found with address '" << args << "'";
+ LOGC(DDEV, ERROR) << "No LMS device found with address '" << cfg->dev_args << "'";
delete[] info_list;
return -1;
}
@@ -298,14 +245,15 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
m_dev_type = parse_dev_type(m_lms_dev);
dev_desc = dev_param_map.at(m_dev_type);
+ update_band_dev(m_dev_type);
- if ((ref != REF_EXTERNAL) && (ref != REF_INTERNAL)){
+ if ((cfg->clock_ref != REF_EXTERNAL) && (cfg->clock_ref != REF_INTERNAL)) {
LOGC(DDEV, ERROR) << "Invalid reference type";
goto out_close;
}
/* if reference clock is external, setup must happen _before_ calling LMS_Init */
- if (ref == REF_EXTERNAL) {
+ if (cfg->clock_ref == REF_EXTERNAL) {
LOGC(DDEV, INFO) << "Setting External clock reference to 10MHz";
/* FIXME: Assume an external 10 MHz reference clock. make
external reference frequency configurable */
@@ -320,7 +268,7 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
}
/* if reference clock is internal, setup must happen _after_ calling LMS_Init */
- if (ref == REF_INTERNAL) {
+ if (cfg->clock_ref == REF_INTERNAL) {
LOGC(DDEV, INFO) << "Setting Internal clock reference";
/* Internal freq param is not used */
if (!do_clock_src_freq(REF_INTERNAL, 0))
@@ -456,6 +404,8 @@ bool LMSDevice::stop()
LMS_DestroyStream(m_lms_dev, &m_lms_stream_rx[i]);
}
+ band_reset();
+
started = false;
return true;
}
@@ -471,8 +421,8 @@ bool LMSDevice::do_clock_src_freq(enum ReferenceType ref, double freq)
break;
case REF_INTERNAL:
if (!dev_desc.clock_src_int_usable) {
- LOGC(DDEV, ERROR) << "Device type " << dev_desc.name_prefix
- << " doesn't support internal reference clock";
+ LOGC(DDEV, ERROR)
+ << "Device type " << dev_desc.desc_str << " doesn't support internal reference clock";
return false;
}
/* According to lms using LMS_CLOCK_EXTREF with a
@@ -490,8 +440,8 @@ bool LMSDevice::do_clock_src_freq(enum ReferenceType ref, double freq)
if (LMS_SetClockFreq(m_lms_dev, lms_clk_id, freq) < 0)
return false;
} else {
- LOGC(DDEV, INFO) << "Device type " << dev_desc.name_prefix
- << " doesn't support switching clock source through SW";
+ LOGC(DDEV, INFO)
+ << "Device type " << dev_desc.desc_str << " doesn't support switching clock source through SW";
}
return true;
@@ -561,6 +511,21 @@ double LMSDevice::setRxGain(double dB, size_t chan)
return rx_gains[chan];
}
+double LMSDevice::rssiOffset(size_t chan)
+{
+ double rssiOffset;
+ dev_band_desc desc;
+
+ if (chan >= rx_gains.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return 0.0f;
+ }
+
+ get_dev_band_desc(desc);
+ rssiOffset = rx_gains[chan] + desc.rxgain2rssioffset_rel;
+ return rssiOffset;
+}
+
double LMSDevice::setPowerAttenuation(int atten, size_t chan)
{
double tx_power, dB;
@@ -969,9 +934,6 @@ bool LMSDevice::updateAlignment(TIMESTAMP timestamp)
bool LMSDevice::setTxFreq(double wFreq, size_t chan)
{
- uint16_t req_arfcn;
- enum gsm_band req_band;
-
if (chan >= chans) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
return false;
@@ -979,29 +941,14 @@ bool LMSDevice::setTxFreq(double wFreq, size_t chan)
LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz";
- req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100 , 0);
- if (req_arfcn == 0xffff) {
- LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Tx Frequency " << wFreq / 1000 << " kHz";
+ if (!update_band_from_freq(wFreq, chan, true))
return false;
- }
- if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
- LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Tx Frequency " << wFreq
- << " Hz (ARFCN " << req_arfcn << " )";
- return false;
- }
-
- if (band != 0 && req_band != band) {
- LOGCHAN(chan, DDEV, ALERT) << "Requesting Tx Frequency " << wFreq
- << " Hz different from previous band " << gsm_band_name(band);
- return false;
- }
if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_TX, chan, wFreq) < 0) {
LOGCHAN(chan, DDEV, ERROR) << "Error setting Tx Freq to " << wFreq << " Hz";
return false;
}
- band = req_band;
return true;
}
@@ -1009,6 +956,9 @@ bool LMSDevice::setRxFreq(double wFreq, size_t chan)
{
LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz";
+ if (!update_band_from_freq(wFreq, chan, false))
+ return false;
+
if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_RX, chan, wFreq) < 0) {
LOGCHAN(chan, DDEV, ERROR) << "Error setting Rx Freq to " << wFreq << " Hz";
return false;
@@ -1017,18 +967,15 @@ bool LMSDevice::setRxFreq(double wFreq, size_t chan)
return true;
}
-RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
- InterfaceType iface, size_t chans, double lo_offset,
- const std::vector < std::string > &tx_paths,
- const std::vector < std::string > &rx_paths)
+RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg)
{
- if (tx_sps != rx_sps) {
- LOGC(DDEV, ERROR) << "LMS Requires tx_sps == rx_sps";
+ if (cfg->tx_sps != cfg->rx_sps) {
+ LOGC(DDEV, ERROR) << "LMS requires tx_sps == rx_sps";
return NULL;
}
- if (lo_offset != 0.0) {
+ if (cfg->offset != 0.0) {
LOGC(DDEV, ERROR) << "LMS doesn't support lo_offset";
return NULL;
}
- return new LMSDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
+ return new LMSDevice(type, cfg);
}
diff --git a/Transceiver52M/device/lms/LMSDevice.h b/Transceiver52M/device/lms/LMSDevice.h
index 7af09e2..2e5ca4c 100644
--- a/Transceiver52M/device/lms/LMSDevice.h
+++ b/Transceiver52M/device/lms/LMSDevice.h
@@ -18,11 +18,13 @@
#ifndef _LMS_DEVICE_H_
#define _LMS_DEVICE_H_
+#include <map>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "radioDevice.h"
+#include "bandmanager.h"
#include "smpl_buf.h"
#include <sys/time.h>
@@ -52,10 +54,52 @@ enum lms_dev_type {
LMS_DEV_UNKNOWN,
};
-struct dev_band_desc;
+struct dev_band_desc {
+ /* Maximum LimeSuite Tx Gain which can be set/used without distorting
+ the output * signal, and the resulting real output power measured
+ when that gain is used.
+ */
+ double nom_lms_tx_gain; /* dB */
+ double nom_out_tx_power; /* dBm */
+ /* Factor used to infer base real RSSI offset on the Rx path based on current
+ configured RxGain. The resulting rssiOffset is added to the per burst
+ calculated energy in upper layers. These values were empirically
+ found and may change based on multiple factors, see OS#4468.
+ Correct measured values only provided for LimeSDR-USB so far.
+ rssiOffset = rxGain + rxgain2rssioffset_rel;
+ */
+ double rxgain2rssioffset_rel; /* dB */
+};
+
+/* Device parameter descriptor */
+struct dev_desc {
+ /* Does LimeSuite allow switching the clock source for this device?
+ * LimeSDR-Mini does not have switches but needs soldering to select
+ * external/internal clock. Any call to LMS_SetClockFreq() will fail.
+ */
+ bool clock_src_switchable;
+ /* Does LimeSuite allow using REF_INTERNAL for this device?
+ * LimeNET-Micro does not like selecting internal clock
+ */
+ bool clock_src_int_usable;
+ /* Sample rate coef (without having TX/RX samples per symbol into account) */
+ double rate;
+ /* Sample rate coef (without having TX/RX samples per symbol into account), if multi-arfcn is enabled */
+ double rate_multiarfcn;
+ /* Coefficient multiplied by TX sample rate in order to shift Tx time */
+ double ts_offset_coef;
+ /* Coefficient multiplied by TX sample rate in order to shift Tx time, if multi-arfcn is enabled */
+ double ts_offset_coef_multiarfcn;
+ /* Device Name Prefix as presented by LimeSuite API LMS_GetDeviceInfo() */
+ std::string desc_str;
+};
+
+using dev_band_key_t = std::tuple<lms_dev_type, gsm_band>;
+using power_map_t = std::map<dev_band_key_t, dev_band_desc>;
+using dev_map_t = std::map<lms_dev_type, struct dev_desc>;
/** A class to handle a LimeSuite supported device */
-class LMSDevice:public RadioDevice {
+class LMSDevice:public RadioDevice, public band_manager<power_map_t, dev_map_t> {
private:
lms_device_t *m_lms_dev;
@@ -72,7 +116,6 @@ private:
TIMESTAMP ts_initial, ts_offset;
std::vector<double> tx_gains, rx_gains;
- enum gsm_band band;
enum lms_dev_type m_dev_type;
@@ -84,28 +127,25 @@ private:
void update_stream_stats_rx(size_t chan, bool *overrun);
void update_stream_stats_tx(size_t chan, bool *underrun);
bool do_clock_src_freq(enum ReferenceType ref, double freq);
- void get_dev_band_desc(dev_band_desc& desc);
-
public:
/** Object constructor */
- LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
- const std::vector<std::string>& tx_paths,
- const std::vector<std::string>& rx_paths);
- ~LMSDevice();
+ LMSDevice(InterfaceType iface, const struct trx_cfg *cfg);
+ ~LMSDevice();
- /** Instantiate the LMS */
- int open(const std::string &args, int ref, bool swap_channels);
+ /** Instantiate the LMS */
+ int open();
- /** Start the LMS */
- bool start();
+ /** Start the LMS */
+ bool start();
- /** Stop the LMS */
- bool stop();
+ /** Stop the LMS */
+ bool stop();
- enum TxWindowType getWindowType() {
- return TX_WINDOW_LMS1;
- }
+ enum TxWindowType getWindowType()
+ {
+ return TX_WINDOW_LMS1;
+ }
/**
Read samples from the LMS.
@@ -173,6 +213,7 @@ public:
/** return minimum Rx Gain **/
double minRxGain(void);
+ double rssiOffset(size_t chan);
double setPowerAttenuation(int atten, size_t chan);
double getPowerAttenuation(size_t chan = 0);
diff --git a/Transceiver52M/device/lms/Makefile.am b/Transceiver52M/device/lms/Makefile.am
index 77fd0e9..6665077 100644
--- a/Transceiver52M/device/lms/Makefile.am
+++ b/Transceiver52M/device/lms/Makefile.am
@@ -1,7 +1,7 @@
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
-AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LMS_CFLAGS)
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LMS_CFLAGS)
noinst_HEADERS = LMSDevice.h
diff --git a/Transceiver52M/device/uhd/Makefile.am b/Transceiver52M/device/uhd/Makefile.am
index ab63a4a..21df631 100644
--- a/Transceiver52M/device/uhd/Makefile.am
+++ b/Transceiver52M/device/uhd/Makefile.am
@@ -1,7 +1,7 @@
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
-AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(UHD_CFLAGS)
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(UHD_CFLAGS)
noinst_HEADERS = UHDDevice.h
diff --git a/Transceiver52M/device/uhd/UHDDevice.cpp b/Transceiver52M/device/uhd/UHDDevice.cpp
index 143a061..85e9e38 100644
--- a/Transceiver52M/device/uhd/UHDDevice.cpp
+++ b/Transceiver52M/device/uhd/UHDDevice.cpp
@@ -91,19 +91,7 @@ extern "C" {
* USRP1 with timestamps is not supported by UHD.
*/
-/* Device Type, Tx-SPS, Rx-SPS */
-typedef std::tuple<uhd_dev_type, int, int> dev_key;
-
-/* Device parameter descriptor */
-struct dev_desc {
- unsigned channels;
- double mcr;
- double rate;
- double offset;
- std::string str;
-};
-
-static const std::map<dev_key, dev_desc> dev_param_map {
+static const dev_map_t dev_param_map {
{ std::make_tuple(USRP2, 1, 1), { 1, 0.0, 390625, 1.2184e-4, "N2XX 1 SPS" } },
{ std::make_tuple(USRP2, 4, 1), { 1, 0.0, 390625, 7.6547e-5, "N2XX 4/1 Tx/Rx SPS" } },
{ std::make_tuple(USRP2, 4, 4), { 1, 0.0, 390625, 4.6080e-5, "N2XX 4 SPS" } },
@@ -129,24 +117,15 @@ static const std::map<dev_key, dev_desc> dev_param_map {
{ std::make_tuple(B2XX_MCBTS, 4, 4), { 1, 51.2e6, MCBTS_SPACING*4, B2XX_TIMING_MCBTS, "B200/B210 4 SPS Multi-ARFCN" } },
};
-typedef std::tuple<uhd_dev_type, enum gsm_band> dev_band_key;
-/* Maximum UHD Tx Gain which can be set/used without distorting the
- output signal, and the resulting real output power measured when that
- gain is used. Correct measured values only provided for B210 so far. */
-struct dev_band_desc {
- double nom_uhd_tx_gain; /* dB */
- double nom_out_tx_power; /* dBm */
-};
-typedef std::map<dev_band_key, dev_band_desc>::const_iterator dev_band_map_it;
-static const std::map<dev_band_key, dev_band_desc> dev_band_nom_power_param_map {
- { std::make_tuple(B200, GSM_BAND_850), { 89.75, 13.3 } },
- { std::make_tuple(B200, GSM_BAND_900), { 89.75, 13.3 } },
- { std::make_tuple(B200, GSM_BAND_1800), { 89.75, 7.5 } },
- { std::make_tuple(B200, GSM_BAND_1900), { 89.75, 7.7 } },
- { std::make_tuple(B210, GSM_BAND_850), { 89.75, 13.3 } },
- { std::make_tuple(B210, GSM_BAND_900), { 89.75, 13.3 } },
- { std::make_tuple(B210, GSM_BAND_1800), { 89.75, 7.5 } },
- { std::make_tuple(B210, GSM_BAND_1900), { 89.75, 7.7 } },
+static const power_map_t dev_band_nom_power_param_map {
+ { std::make_tuple(B200, GSM_BAND_850), { 89.75, 13.3, -7.5 } },
+ { std::make_tuple(B200, GSM_BAND_900), { 89.75, 13.3, -7.5 } },
+ { std::make_tuple(B200, GSM_BAND_1800), { 89.75, 7.5, -11.0 } },
+ { std::make_tuple(B200, GSM_BAND_1900), { 89.75, 7.7, -11.0 } },
+ { std::make_tuple(B210, GSM_BAND_850), { 89.75, 13.3, -7.5 } },
+ { std::make_tuple(B210, GSM_BAND_900), { 89.75, 13.3, -7.5 } },
+ { std::make_tuple(B210, GSM_BAND_1800), { 89.75, 7.5, -11.0 } },
+ { std::make_tuple(B210, GSM_BAND_1900), { 89.75, 7.7, -11.0 } },
};
void *async_event_loop(uhd_device *dev)
@@ -227,15 +206,10 @@ static double TxPower2TxGain(const dev_band_desc &desc, double tx_power_dbm)
return desc.nom_uhd_tx_gain - (desc.nom_out_tx_power - tx_power_dbm);
}
-uhd_device::uhd_device(size_t tx_sps, size_t rx_sps,
- InterfaceType iface, size_t chan_num, double lo_offset,
- const std::vector<std::string>& tx_paths,
- const std::vector<std::string>& rx_paths)
- : RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths),
- rx_gain_min(0.0), rx_gain_max(0.0),
- band((enum gsm_band)0), tx_spp(0), rx_spp(0),
- started(false), aligned(false), drop_cnt(0),
- prev_ts(0,0), ts_initial(0), ts_offset(0), async_event_thrd(NULL)
+uhd_device::uhd_device(InterfaceType iface, const struct trx_cfg *cfg)
+ : RadioDevice(iface, cfg), band_manager(dev_band_nom_power_param_map, dev_param_map), rx_gain_min(0.0),
+ rx_gain_max(0.0), tx_spp(0), rx_spp(0), started(false), aligned(false), drop_cnt(0), prev_ts(0, 0),
+ ts_initial(0), ts_offset(0), async_event_thrd(NULL)
{
}
@@ -247,27 +221,6 @@ uhd_device::~uhd_device()
delete rx_buffers[i];
}
-void uhd_device::get_dev_band_desc(dev_band_desc& desc)
-{
- dev_band_map_it it;
- enum gsm_band req_band = band;
-
- if (req_band == 0) {
- LOGC(DDEV, ERROR) << "Nominal Tx Power requested before Tx Frequency was set! Providing band 900 by default... ";
- req_band = GSM_BAND_900;
- }
- it = dev_band_nom_power_param_map.find(dev_band_key(dev_type, req_band));
- if (it == dev_band_nom_power_param_map.end()) {
- dev_desc desc = dev_param_map.at(dev_key(dev_type, tx_sps, rx_sps));
- LOGC(DDEV, ERROR) << "No Tx Power measurements exist for device "
- << desc.str << " on band " << gsm_band_name(req_band)
- << ", using B210 ones as fallback";
- it = dev_band_nom_power_param_map.find(dev_band_key(B210, req_band));
- }
- OSMO_ASSERT(it != dev_band_nom_power_param_map.end())
- desc = it->second;
-}
-
void uhd_device::init_gains()
{
double tx_gain_min, tx_gain_max;
@@ -332,7 +285,7 @@ void uhd_device::set_rates()
rx_rate = usrp_dev->get_rx_rate();
ts_offset = static_cast<TIMESTAMP>(desc.offset * rx_rate);
- LOGC(DDEV, INFO) << "Rates configured for " << desc.str;
+ LOGC(DDEV, INFO) << "Rates configured for " << desc.desc_str;
}
double uhd_device::setRxGain(double db, size_t chan)
@@ -342,6 +295,9 @@ double uhd_device::setRxGain(double db, size_t chan)
return 0.0f;
}
+ if (cfg->overrides.ul_gain_override)
+ return rx_gains[chan];
+
usrp_dev->set_rx_gain(db, chan);
rx_gains[chan] = usrp_dev->get_rx_gain(chan);
@@ -360,6 +316,21 @@ double uhd_device::getRxGain(size_t chan)
return rx_gains[chan];
}
+double uhd_device::rssiOffset(size_t chan)
+{
+ double rssiOffset;
+ dev_band_desc desc;
+
+ if (chan >= rx_gains.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return 0.0f;
+ }
+
+ get_dev_band_desc(desc);
+ rssiOffset = rx_gains[chan] + desc.rxgain2rssioffset_rel;
+ return rssiOffset;
+}
+
double uhd_device::setPowerAttenuation(int atten, size_t chan) {
double tx_power, db;
dev_band_desc desc;
@@ -369,6 +340,9 @@ double uhd_device::setPowerAttenuation(int atten, size_t chan) {
return 0.0f;
}
+ if (cfg->overrides.dl_gain_override)
+ return atten; // ensures caller does not apply digital attenuation
+
get_dev_band_desc(desc);
tx_power = desc.nom_out_tx_power - atten;
db = TxPower2TxGain(desc, tx_power);
@@ -520,9 +494,10 @@ void uhd_device::set_channels(bool swap)
}
}
-int uhd_device::open(const std::string &args, int ref, bool swap_channels)
+int uhd_device::open()
{
const char *refstr;
+ int clock_lock_attempts = 15;
/* Register msg handler. Different APIs depending on UHD version */
#ifdef USE_UHD_3_11
@@ -535,10 +510,10 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
#endif
// Find UHD devices
- uhd::device_addr_t addr(args);
+ uhd::device_addr_t addr(cfg->dev_args);
uhd::device_addrs_t dev_addrs = uhd::device::find(addr);
if (dev_addrs.size() == 0) {
- LOGC(DDEV, ALERT) << "No UHD devices found with address '" << args << "'";
+ LOGC(DDEV, ALERT) << "No UHD devices found with address '" << cfg->dev_args << "'";
return -1;
}
@@ -547,7 +522,7 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
try {
usrp_dev = uhd::usrp::multi_usrp::make(addr);
} catch(uhd::key_error::exception &e) {
- LOGC(DDEV, ALERT) << "UHD make failed, device " << args << ", exception:\n" << e.what();
+ LOGC(DDEV, ALERT) << "UHD make failed, device " << cfg->dev_args << ", exception:\n" << e.what();
return -1;
}
@@ -555,14 +530,16 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
if (!parse_dev_type())
return -1;
+ update_band_dev(dev_key(dev_type, tx_sps, rx_sps));
+
if ((dev_type == E3XX) && !uhd_e3xx_version_chk()) {
LOGC(DDEV, ALERT) << "E3XX requires UHD 003.009.000 or greater";
return -1;
}
try {
- set_channels(swap_channels);
- } catch (const std::exception &e) {
+ set_channels(cfg->swap_channels);
+ } catch (const std::exception &e) {
LOGC(DDEV, ALERT) << "Channel setting failed - " << e.what();
return -1;
}
@@ -578,7 +555,7 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
rx_gains.resize(chans);
rx_buffers.resize(chans);
- switch (ref) {
+ switch (cfg->clock_ref) {
case REF_INTERNAL:
refstr = "internal";
break;
@@ -595,6 +572,19 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
usrp_dev->set_clock_source(refstr);
+ std::vector<std::string> sensor_names = usrp_dev->get_mboard_sensor_names();
+ if (std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end()) {
+ LOGC(DDEV, INFO) << "Waiting for clock reference lock (max " << clock_lock_attempts << "s)..." << std::flush;
+ while (!usrp_dev->get_mboard_sensor("ref_locked", 0).to_bool() && clock_lock_attempts--)
+ sleep(1);
+
+ if (!clock_lock_attempts) {
+ LOGC(DDEV, ALERT) << "Locking to external 10Mhz failed!";
+ return -1;
+ }
+ }
+ LOGC(DDEV, INFO) << "Selected clock source is " << usrp_dev->get_clock_source(0);
+
try {
set_rates();
} catch (const std::exception &e) {
@@ -644,6 +634,32 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
// Print configuration
LOGC(DDEV, INFO) << "Device configuration: " << usrp_dev->get_pp_string();
+ if (cfg->overrides.dl_freq_override) {
+ uhd::tune_request_t treq_tx = uhd::tune_request_t(cfg->overrides.dl_freq, 0);
+ auto tres = usrp_dev->set_tx_freq(treq_tx, 0);
+ tx_freqs[0] = usrp_dev->get_tx_freq(0);
+ LOGCHAN(0, DDEV, INFO) << "OVERRIDE set_freq(" << tx_freqs[0] << ", TX): " << tres.to_pp_string() << std::endl;
+ }
+
+ if (cfg->overrides.ul_freq_override) {
+ uhd::tune_request_t treq_rx = uhd::tune_request_t(cfg->overrides.ul_freq, 0);
+ auto tres = usrp_dev->set_rx_freq(treq_rx, 0);
+ rx_freqs[0] = usrp_dev->get_rx_freq(0);
+ LOGCHAN(0, DDEV, INFO) << "OVERRIDE set_freq(" << rx_freqs[0] << ", RX): " << tres.to_pp_string() << std::endl;
+ }
+
+ if (cfg->overrides.ul_gain_override) {
+ usrp_dev->set_rx_gain(cfg->overrides.ul_gain, 0);
+ rx_gains[0] = usrp_dev->get_rx_gain(0);
+ LOGCHAN(0, DDEV, INFO) << " OVERRIDE RX gain:" << rx_gains[0] << std::endl;
+ }
+
+ if (cfg->overrides.dl_gain_override) {
+ usrp_dev->set_tx_gain(cfg->overrides.dl_gain, 0);
+ tx_gains[0] = usrp_dev->get_tx_gain(0);
+ LOGCHAN(0, DDEV, INFO) << " OVERRIDE TX gain:" << tx_gains[0] << std::endl;
+ }
+
if (iface == MULTI_ARFCN)
return MULTI_ARFCN;
@@ -754,6 +770,8 @@ bool uhd_device::stop()
for (size_t i = 0; i < rx_buffers.size(); i++)
rx_buffers[i]->reset();
+ band_reset();
+
started = false;
return true;
}
@@ -992,17 +1010,22 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx)
{
std::vector<double> freqs;
uhd::tune_result_t tres;
+ std::string str_dir = tx ? "Tx" : "Rx";
+
+ if (cfg->overrides.dl_freq_override || cfg->overrides.ul_freq_override)
+ return true;
+
+ if (!update_band_from_freq(freq, chan, tx))
+ return false;
+
uhd::tune_request_t treq = select_freq(freq, chan, tx);
- std::string str_dir;
if (tx) {
tres = usrp_dev->set_tx_freq(treq, chan);
tx_freqs[chan] = 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);
- str_dir = "Rx";
}
LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << str_dir << "): " << tres.to_pp_string() << std::endl;
@@ -1032,37 +1055,12 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx)
bool uhd_device::setTxFreq(double wFreq, size_t chan)
{
- uint16_t req_arfcn;
- enum gsm_band req_band;
-
if (chan >= tx_freqs.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
return false;
}
- ScopedLock lock(tune_lock);
-
- req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100 , 0);
- if (req_arfcn == 0xffff) {
- LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Tx Frequency " << wFreq / 1000 << " kHz";
- return false;
- }
- if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
- LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Tx Frequency " << wFreq
- << " Hz (ARFCN " << req_arfcn << " )";
- return false;
- }
- if (band != 0 && req_band != band) {
- LOGCHAN(chan, DDEV, ALERT) << "Requesting Tx Frequency " << wFreq
- << " Hz different from previous band " << gsm_band_name(band);
- return false;
- }
-
- if (!set_freq(wFreq, chan, true))
- return false;
-
- band = req_band;
- return true;
+ return set_freq(wFreq, chan, true);
}
bool uhd_device::setRxFreq(double wFreq, size_t chan)
@@ -1071,7 +1069,6 @@ bool uhd_device::setRxFreq(double wFreq, size_t chan)
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
return false;
}
- ScopedLock lock(tune_lock);
return set_freq(wFreq, chan, false);
}
@@ -1321,11 +1318,8 @@ std::string uhd_device::str_code(uhd::async_metadata_t metadata)
}
#ifndef IPCMAGIC
-RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
- InterfaceType iface, size_t chans, double lo_offset,
- const std::vector<std::string>& tx_paths,
- const std::vector<std::string>& rx_paths)
+RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg)
{
- return new uhd_device(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
+ return new uhd_device(type, cfg);
}
#endif
diff --git a/Transceiver52M/device/uhd/UHDDevice.h b/Transceiver52M/device/uhd/UHDDevice.h
index c159e63..f5e5232 100644
--- a/Transceiver52M/device/uhd/UHDDevice.h
+++ b/Transceiver52M/device/uhd/UHDDevice.h
@@ -30,6 +30,7 @@
#include "config.h"
#endif
+#include "bandmanager.h"
#include "radioDevice.h"
#include "smpl_buf.h"
@@ -56,7 +57,33 @@ enum uhd_dev_type {
LIMESDR,
};
-struct dev_band_desc;
+struct dev_band_desc {
+ /* Maximum UHD Tx Gain which can be set/used without distorting the
+ output signal, and the resulting real output power measured when that
+ gain is used. Correct measured values only provided for B210 so far. */
+ double nom_uhd_tx_gain; /* dB */
+ double nom_out_tx_power; /* dBm */
+ /* Factor used to infer base real RSSI offset on the Rx path based on current
+ configured RxGain. The resulting rssiOffset is added to the per burst
+ calculated energy in upper layers. These values were empirically
+ found and may change based on multiple factors, see OS#4468.
+ rssiOffset = rxGain + rxgain2rssioffset_rel;
+ */
+ double rxgain2rssioffset_rel; /* dB */
+};
+
+struct dev_desc {
+ unsigned channels;
+ double mcr;
+ double rate;
+ double offset;
+ std::string desc_str;
+};
+
+using dev_key = std::tuple<uhd_dev_type, int, int>;
+using dev_band_key = std::tuple<uhd_dev_type, enum gsm_band>;
+using power_map_t = std::map<dev_band_key, dev_band_desc>;
+using dev_map_t = std::map<dev_key, dev_desc>;
/*
uhd_device - UHD implementation of the Device interface. Timestamped samples
@@ -65,19 +92,19 @@ struct dev_band_desc;
Events and errors such as underruns are reported asynchronously
by the device and received in a separate thread.
*/
-class uhd_device : public RadioDevice {
+class uhd_device : public RadioDevice, public band_manager<power_map_t, dev_map_t> {
public:
- 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,
- const std::vector<std::string>& rx_paths);
- ~uhd_device();
-
- int open(const std::string &args, int ref, bool swap_channels);
- bool start();
- bool stop();
- bool restart();
- enum TxWindowType getWindowType() { return tx_window; }
+ uhd_device(InterfaceType iface, const struct trx_cfg *cfg);
+ ~uhd_device();
+
+ int open();
+ bool start();
+ bool stop();
+ bool restart();
+ enum TxWindowType getWindowType()
+ {
+ return tx_window;
+ }
int readSamples(std::vector<short *> &bufs, int len, bool *overrun,
TIMESTAMP timestamp, bool *underrun);
@@ -100,6 +127,7 @@ public:
double getRxGain(size_t chan);
double maxRxGain(void) { return rx_gain_max; }
double minRxGain(void) { return rx_gain_min; }
+ double rssiOffset(size_t chan);
double setPowerAttenuation(int atten, size_t chan);
double getPowerAttenuation(size_t chan = 0);
@@ -146,7 +174,6 @@ protected:
std::vector<double> tx_gains, rx_gains;
std::vector<double> tx_freqs, rx_freqs;
- enum gsm_band band;
size_t tx_spp, rx_spp;
bool started;
@@ -175,8 +202,6 @@ protected:
uhd::tune_request_t select_freq(double wFreq, size_t chan, bool tx);
bool set_freq(double freq, size_t chan, bool tx);
- void get_dev_band_desc(dev_band_desc& desc);
Thread *async_event_thrd;
- Mutex tune_lock;
};
diff --git a/Transceiver52M/device/usrp1/Makefile.am b/Transceiver52M/device/usrp1/Makefile.am
index 24760f7..ba93c7e 100644
--- a/Transceiver52M/device/usrp1/Makefile.am
+++ b/Transceiver52M/device/usrp1/Makefile.am
@@ -1,7 +1,7 @@
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
-AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(USRP_CFLAGS)
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(USRP_CFLAGS)
rev2dir = $(datadir)/usrp/rev2
rev4dir = $(datadir)/usrp/rev4
diff --git a/Transceiver52M/device/usrp1/USRPDevice.cpp b/Transceiver52M/device/usrp1/USRPDevice.cpp
index 5c40aa8..63a9bcc 100644
--- a/Transceiver52M/device/usrp1/USRPDevice.cpp
+++ b/Transceiver52M/device/usrp1/USRPDevice.cpp
@@ -60,11 +60,7 @@ const dboardConfigType dboardConfig = TXA_RXB;
const double USRPDevice::masterClockRate = 52.0e6;
-USRPDevice::USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface,
- size_t chan_num, double lo_offset,
- const std::vector<std::string>& tx_paths,
- const std::vector<std::string>& rx_paths):
- RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths)
+USRPDevice::USRPDevice(InterfaceType iface, const struct trx_cfg *cfg) : RadioDevice(iface, cfg)
{
LOGC(DDEV, INFO) << "creating USRP device...";
@@ -94,7 +90,7 @@ USRPDevice::USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface,
#endif
}
-int USRPDevice::open(const std::string &, int, bool)
+int USRPDevice::open()
{
writeLock.unlock();
@@ -587,8 +583,7 @@ bool USRPDevice::updateAlignment(TIMESTAMP timestamp)
{
#ifndef SWLOOPBACK
short data[] = {0x00,0x02,0x00,0x00};
- uint32_t *wordPtr = (uint32_t *) data;
- *wordPtr = host_to_usrp_u32(*wordPtr);
+ /* FIXME: big endian */
bool tmpUnderrun;
std::vector<short *> buf(1, data);
@@ -659,22 +654,19 @@ bool USRPDevice::setTxFreq(double wFreq) { return true;};
bool USRPDevice::setRxFreq(double wFreq) { return true;};
#endif
-RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
- InterfaceType iface, size_t chans, double lo_offset,
- const std::vector<std::string>& tx_paths,
- const std::vector<std::string>& rx_paths)
+RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg)
{
- if (tx_sps != rx_sps) {
- LOGC(DDEV, ERROR) << "USRP1 requires tx_sps == rx_sps";
- return NULL;
- }
- if (chans != 1) {
- LOGC(DDEV, ERROR) << "USRP1 supports only 1 channel";
- return NULL;
- }
- if (lo_offset != 0.0) {
- LOGC(DDEV, ERROR) << "USRP1 doesn't support lo_offset";
- return NULL;
- }
- return new USRPDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
+ if (cfg->tx_sps != cfg->rx_sps) {
+ LOGC(DDEV, ERROR) << "USRP1 requires tx_sps == rx_sps";
+ return NULL;
+ }
+ if (cfg->num_chans != 1) {
+ LOGC(DDEV, ERROR) << "USRP1 supports only 1 channel";
+ return NULL;
+ }
+ if (cfg->offset != 0.0) {
+ LOGC(DDEV, ERROR) << "USRP1 doesn't support lo_offset";
+ return NULL;
+ }
+ return new USRPDevice(type, cfg);
}
diff --git a/Transceiver52M/device/usrp1/USRPDevice.h b/Transceiver52M/device/usrp1/USRPDevice.h
index f761dc0..4957ee6 100644
--- a/Transceiver52M/device/usrp1/USRPDevice.h
+++ b/Transceiver52M/device/usrp1/USRPDevice.h
@@ -104,20 +104,21 @@ private:
public:
/** Object constructor */
- USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
- const std::vector<std::string>& tx_paths,
- const std::vector<std::string>& rx_paths);
+ USRPDevice(InterfaceType iface, const struct trx_cfg *cfg);
- /** Instantiate the USRP */
- int open(const std::string &, int, bool);
+ /** Instantiate the USRP */
+ int open();
- /** Start the USRP */
- bool start();
+ /** Start the USRP */
+ bool start();
- /** Stop the USRP */
- bool stop();
+ /** Stop the USRP */
+ bool stop();
- enum TxWindowType getWindowType() { return TX_WINDOW_USRP1; }
+ enum TxWindowType getWindowType()
+ {
+ return TX_WINDOW_USRP1;
+ }
/**
Read samples from the USRP.
@@ -174,6 +175,8 @@ private:
/** return minimum Rx Gain **/
double minRxGain(void);
+ double rssiOffset(size_t chan) { return 0.0f; } /* FIXME: not implemented */
+
double setPowerAttenuation(int atten, size_t chan);
double getPowerAttenuation(size_t chan=0);
diff --git a/Transceiver52M/grgsm_vitac/constants.h b/Transceiver52M/grgsm_vitac/constants.h
new file mode 100644
index 0000000..27bf6f4
--- /dev/null
+++ b/Transceiver52M/grgsm_vitac/constants.h
@@ -0,0 +1,149 @@
+#pragma once
+/* -*- c++ -*- */
+/*
+ * @file
+ * @author (C) 2009-2017 by Piotr Krysik <ptrkrysik@gmail.com>
+ * @section LICENSE
+ *
+ * Gr-gsm 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, or (at your option)
+ * any later version.
+ *
+ * Gr-gsm 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 gr-gsm; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <complex>
+
+#define gr_complex std::complex<float>
+
+
+#define GSM_SYMBOL_RATE (1625000.0/6.0) //symbols per second
+#define GSM_SYMBOL_PERIOD (1.0/GSM_SYMBOL_RATE) //seconds per symbol
+
+//Burst timing
+#define TAIL_BITS 3
+#define GUARD_BITS 8
+#define GUARD_FRACTIONAL 0.25 //fractional part of guard period
+#define GUARD_PERIOD GUARD_BITS + GUARD_FRACTIONAL
+#define DATA_BITS 57 //size of 1 data block in normal burst
+#define STEALING_BIT 1
+#define N_TRAIN_BITS 26
+#define N_SYNC_BITS 64
+#define N_ACCESS_BITS 41
+#define USEFUL_BITS 142 //(2*(DATA_BITS+STEALING_BIT) + N_TRAIN_BITS )
+#define FCCH_BITS USEFUL_BITS
+#define BURST_SIZE (USEFUL_BITS+2*TAIL_BITS)
+#define ACCESS_BURST_SIZE 88
+#define PROCESSED_CHUNK BURST_SIZE+2*GUARD_PERIOD
+
+#define SCH_DATA_LEN 39
+#define TS_BITS (TAIL_BITS+USEFUL_BITS+TAIL_BITS+GUARD_BITS) //a full TS (156 bits)
+#define TS_PER_FRAME 8
+#define FRAME_BITS (TS_PER_FRAME * TS_BITS + 2) // 156.25 * 8
+#define FCCH_POS TAIL_BITS
+#define SYNC_POS (TAIL_BITS + 39)
+#define TRAIN_POS ( TAIL_BITS + (DATA_BITS+STEALING_BIT) + 5) //first 5 bits of a training sequence
+ //aren't used for channel impulse response estimation
+#define TRAIN_BEGINNING 5
+#define SAFETY_MARGIN 6 //
+
+#define FCCH_HITS_NEEDED (USEFUL_BITS - 4)
+#define FCCH_MAX_MISSES 1
+#define FCCH_MAX_FREQ_OFFSET 100
+
+#define CHAN_IMP_RESP_LENGTH 5
+
+#define MAX_SCH_ERRORS 10 //maximum number of subsequent sch errors after which gsm receiver goes to find_next_fcch state
+
+typedef enum { empty, fcch_burst, sch_burst, normal_burst, rach_burst, dummy, dummy_or_normal, normal_or_noise } burst_type;
+typedef enum { unknown, multiframe_26, multiframe_51 } multiframe_type;
+
+static const unsigned char SYNC_BITS[] = {
+ 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
+ 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
+ 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1
+};
+
+static const unsigned char ACCESS_BITS [] = {
+ 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0,
+ 0, 1, 1, 1, 1, 0, 0, 0
+};
+
+const unsigned FCCH_FRAMES[] = { 0, 10, 20, 30, 40 };
+const unsigned SCH_FRAMES[] = { 1, 11, 21, 31, 41 };
+
+const unsigned BCCH_FRAMES[] = { 2, 3, 4, 5 }; //!!the receiver shouldn't care about logical
+ //!!channels so this will be removed from this header
+const unsigned TEST_CCH_FRAMES[] = { 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 16, 17, 18, 19, 22, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 44, 45, 46, 47, 48, 49 };
+const unsigned TRAFFIC_CHANNEL_F[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 };
+const unsigned TEST51[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 };
+
+
+#define TSC0 0
+#define TSC1 1
+#define TSC2 2
+#define TSC3 3
+#define TSC4 4
+#define TSC5 5
+#define TSC6 6
+#define TSC7 7
+#define TS_DUMMY 8
+
+#define TRAIN_SEQ_NUM 9
+
+#define TIMESLOT0 0
+#define TIMESLOT1 1
+#define TIMESLOT2 2
+#define TIMESLOT3 3
+#define TIMESLOT4 4
+#define TIMESLOT5 5
+#define TIMESLOT6 6
+#define TIMESLOT7 7
+
+
+static const unsigned char train_seq[TRAIN_SEQ_NUM][N_TRAIN_BITS] = {
+ {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, 0, 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},
+ {0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1} // DUMMY
+};
+
+
+//Dummy burst 0xFB 76 0A 4E 09 10 1F 1C 5C 5C 57 4A 33 39 E9 F1 2F A8
+static const unsigned char dummy_burst[] = {
+ 0, 0, 0,
+ 1, 1, 1, 1, 1, 0, 1, 1, 0, 1,
+ 1, 1, 0, 1, 1, 0, 0, 0, 0, 0,
+ 1, 0, 1, 0, 0, 1, 0, 0, 1, 1,
+ 1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 0, 0,
+
+ 0, 1, 1, 1, 0, 0, 0, 1, 0, 1,
+ 1, 1, 0, 0, 0, 1, 0, 1, 1, 1,
+ 0, 0, 0, 1, 0, 1,
+
+ 0, 1, 1, 1, 0, 1, 0, 0, 1, 0,
+ 1, 0, 0, 0, 1, 1, 0, 0, 1, 1,
+ 0, 0, 1, 1, 1, 0, 0, 1, 1, 1,
+ 1, 0, 1, 0, 0, 1, 1, 1, 1, 1,
+ 0, 0, 0, 1, 0, 0, 1, 0, 1, 1,
+ 1, 1, 1, 0, 1, 0, 1, 0,
+ 0, 0, 0
+};
diff --git a/Transceiver52M/grgsm_vitac/grgsm_vitac.cpp b/Transceiver52M/grgsm_vitac/grgsm_vitac.cpp
new file mode 100644
index 0000000..2016541
--- /dev/null
+++ b/Transceiver52M/grgsm_vitac/grgsm_vitac.cpp
@@ -0,0 +1,305 @@
+/* -*- c++ -*- */
+/*
+ * @file
+ * @author (C) 2009-2017 by Piotr Krysik <ptrkrysik@gmail.com>
+ * @author Contributions by sysmocom - s.f.m.c. GmbH / Eric Wild <ewild@sysmocom.de>
+ * @section LICENSE
+ *
+ * Gr-gsm 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, or (at your option)
+ * any later version.
+ *
+ * Gr-gsm 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 gr-gsm; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "constants.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <complex>
+
+
+#include <algorithm>
+#include <string.h>
+#include <iostream>
+#include <numeric>
+#include <vector>
+#include <fstream>
+
+#include "viterbi_detector.h"
+#include "grgsm_vitac.h"
+
+gr_complex d_acc_training_seq[N_ACCESS_BITS]; ///<encoded training sequence of a RACH burst
+gr_complex d_sch_training_seq[N_SYNC_BITS]; ///<encoded training sequence of a SCH burst
+gr_complex d_norm_training_seq[TRAIN_SEQ_NUM][N_TRAIN_BITS]; ///<encoded training sequences of a normal and dummy burst
+const int d_chan_imp_length = CHAN_IMP_RESP_LENGTH;
+
+void initvita()
+{
+ /**
+ * Prepare SCH sequence bits
+ *
+ * (TS_BITS + 2 * GUARD_PERIOD)
+ * Burst and two guard periods
+ * (one guard period is an arbitrary overlap)
+ */
+ gmsk_mapper(SYNC_BITS, N_SYNC_BITS, d_sch_training_seq, gr_complex(0.0, -1.0));
+ for (auto &i : d_sch_training_seq)
+ i = conj(i);
+
+ /* ab */
+ gmsk_mapper(ACCESS_BITS, N_ACCESS_BITS, d_acc_training_seq, gr_complex(0.0, -1.0));
+ for (auto &i : d_acc_training_seq)
+ i = conj(i);
+
+ /* Prepare bits of training sequences */
+ for (int i = 0; i < TRAIN_SEQ_NUM; i++) {
+ /**
+ * If first bit of the sequence is 0
+ * => first symbol is 1, else -1
+ */
+ gr_complex startpoint = train_seq[i][0] == 0 ? gr_complex(1.0, 0.0) : gr_complex(-1.0, 0.0);
+ gmsk_mapper(train_seq[i], N_TRAIN_BITS, d_norm_training_seq[i], startpoint);
+ for (auto &i : d_norm_training_seq[i])
+ i = conj(i);
+ }
+}
+
+template <unsigned int burst_size>
+NO_UBSAN static void detect_burst_generic(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start,
+ char *output_binary, int ss)
+{
+ std::vector<gr_complex> rhh_temp(CHAN_IMP_RESP_LENGTH * d_OSR);
+ unsigned int stop_states[2] = { 4, 12 };
+ gr_complex filtered_burst[burst_size];
+ gr_complex rhh[CHAN_IMP_RESP_LENGTH];
+ float output[burst_size];
+ int start_state = ss;
+
+ autocorrelation(chan_imp_resp, &rhh_temp[0], d_chan_imp_length * d_OSR);
+ for (int ii = 0; ii < d_chan_imp_length; ii++)
+ rhh[ii] = conj(rhh_temp[ii * d_OSR]);
+
+ mafi(&input[burst_start], burst_size, chan_imp_resp, d_chan_imp_length * d_OSR, filtered_burst);
+
+ viterbi_detector(filtered_burst, burst_size, rhh, start_state, stop_states, 2, output);
+
+ for (unsigned int i = 0; i < burst_size; i++)
+ output_binary[i] = output[i] > 0 ? -127 : 127; // pre flip bits!
+}
+
+NO_UBSAN void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary,
+ int ss)
+{
+ return detect_burst_generic<BURST_SIZE>(input, chan_imp_resp, burst_start, output_binary, ss);
+}
+NO_UBSAN void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary,
+ int ss)
+{
+ return detect_burst_generic<8 + 41 + 36 + 3>(input, chan_imp_resp, burst_start, output_binary, ss);
+}
+
+NO_UBSAN void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary)
+{
+ return detect_burst_nb(input, chan_imp_resp, burst_start, output_binary, 3);
+}
+NO_UBSAN void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary)
+{
+ return detect_burst_ab(input, chan_imp_resp, burst_start, output_binary, 3);
+}
+
+void gmsk_mapper(const unsigned char *input, int nitems, gr_complex *gmsk_output, gr_complex start_point)
+{
+ gr_complex j = gr_complex(0.0, 1.0);
+ gmsk_output[0] = start_point;
+
+ int previous_symbol = 2 * input[0] - 1;
+ int current_symbol;
+ int encoded_symbol;
+
+ for (int i = 1; i < nitems; i++) {
+ /* Change bits representation to NRZ */
+ current_symbol = 2 * input[i] - 1;
+
+ /* Differentially encode */
+ encoded_symbol = current_symbol * previous_symbol;
+
+ /* And do GMSK mapping */
+ gmsk_output[i] = j * gr_complex(encoded_symbol, 0.0) * gmsk_output[i - 1];
+
+ previous_symbol = current_symbol;
+ }
+}
+
+gr_complex correlate_sequence(const gr_complex *sequence, int length, const gr_complex *input)
+{
+ gr_complex result(0.0, 0.0);
+
+ for (int ii = 0; ii < length; ii++)
+ result += sequence[ii] * input[ii * d_OSR];
+
+ return conj(result) / gr_complex(length, 0);
+}
+
+/* Computes autocorrelation for positive arguments */
+inline void autocorrelation(const gr_complex *input, gr_complex *out, int nitems)
+{
+ for (int k = nitems - 1; k >= 0; k--) {
+ out[k] = gr_complex(0, 0);
+ for (int i = k; i < nitems; i++)
+ out[k] += input[i] * conj(input[i - k]);
+ }
+}
+
+inline void mafi(const gr_complex *input, int nitems, gr_complex *filter, int filter_length, gr_complex *output)
+{
+ for (int n = 0; n < nitems; n++) {
+ int a = n * d_OSR;
+ output[n] = 0;
+
+ for (int ii = 0; ii < filter_length; ii++) {
+ if ((a + ii) >= nitems * d_OSR)
+ break;
+
+ output[n] += input[a + ii] * filter[ii];
+ }
+ }
+}
+
+int get_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, int search_start_pos, int search_stop_pos,
+ gr_complex *tseq, int tseqlen, float *corr_max)
+{
+ const int num_search_windows = search_stop_pos - search_start_pos;
+ const int power_search_window_len = d_chan_imp_length * d_OSR;
+ std::vector<float> window_energy_buffer;
+ std::vector<float> power_buffer;
+ std::vector<gr_complex> correlation_buffer;
+
+ power_buffer.reserve(num_search_windows);
+ correlation_buffer.reserve(num_search_windows);
+ window_energy_buffer.reserve(num_search_windows);
+
+ for (int ii = 0; ii < num_search_windows; ii++) {
+ gr_complex correlation = correlate_sequence(tseq, tseqlen, &input[search_start_pos + ii]);
+ correlation_buffer.push_back(correlation);
+ power_buffer.push_back(std::pow(abs(correlation), 2));
+ }
+
+ /* Compute window energies */
+ float windowSum = 0;
+
+ // first window
+ for (int i = 0; i < power_search_window_len; i++) {
+ windowSum += power_buffer[i];
+ }
+ window_energy_buffer.push_back(windowSum);
+
+ // slide windows
+ for (int i = power_search_window_len; i < num_search_windows; i++) {
+ windowSum += power_buffer[i] - power_buffer[i - power_search_window_len];
+ window_energy_buffer.push_back(windowSum);
+ }
+
+ int strongest_window_nr = std::max_element(window_energy_buffer.begin(), window_energy_buffer.end()) -
+ window_energy_buffer.begin();
+
+ float max_correlation = 0;
+ for (int ii = 0; ii < power_search_window_len; ii++) {
+ gr_complex correlation = correlation_buffer[strongest_window_nr + ii];
+ if (abs(correlation) > max_correlation)
+ max_correlation = abs(correlation);
+ chan_imp_resp[ii] = correlation;
+ }
+
+ *corr_max = max_correlation;
+
+ /**
+ * Compute first sample position, which corresponds
+ * to the first sample of the impulse response
+ */
+ return search_start_pos + strongest_window_nr;
+}
+
+/*
+8 ext tail bits
+41 sync seq
+36 encrypted bits
+3 tail bits
+68.25 extended tail bits (!)
+
+center at 8+5 (actually known tb -> known isi, start at 8?) FIXME
+*/
+int get_access_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int max_delay)
+{
+ const int search_center = 8 + 5;
+ const int search_start_pos = (search_center - 5) * d_OSR + 1;
+ const int search_stop_pos = (search_center + 5 + d_chan_imp_length + max_delay) * d_OSR;
+ const auto tseq = &d_acc_training_seq[TRAIN_BEGINNING];
+ const auto tseqlen = N_ACCESS_BITS - (2 * TRAIN_BEGINNING);
+ return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, corr_max) -
+ search_center * d_OSR;
+}
+
+/*
+
+3 + 57 + 1 + 26 + 1 + 57 + 3 + 8.25
+
+search center = 3 + 57 + 1 + 5 (due to tsc 5+16+5 split)
+this is +-5 samples around (+5 beginning) of truncated t16 tsc
+
+*/
+int get_norm_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int bcc)
+{
+ const int search_center = TRAIN_POS;
+ const int search_start_pos = (search_center - 5) * d_OSR + 1;
+ const int search_stop_pos = (search_center + 5 + d_chan_imp_length) * d_OSR;
+ const auto tseq = &d_norm_training_seq[bcc][TRAIN_BEGINNING];
+ const auto tseqlen = N_TRAIN_BITS - (2 * TRAIN_BEGINNING);
+ return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, corr_max) -
+ search_center * d_OSR;
+}
+
+/*
+
+3 tail | 39 data | 64 tsc | 39 data | 3 tail | 8.25 guard
+start 3+39 - 10
+end 3+39 + SYNC_SEARCH_RANGE
+
+*/
+int get_sch_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp)
+{
+ const int search_center = SYNC_POS + TRAIN_BEGINNING;
+ const int search_start_pos = (search_center - 10) * d_OSR;
+ const int search_stop_pos = (search_center + SYNC_SEARCH_RANGE) * d_OSR;
+ const auto tseq = &d_sch_training_seq[TRAIN_BEGINNING];
+ const auto tseqlen = N_SYNC_BITS - (2 * TRAIN_BEGINNING);
+
+ // strongest_window_nr + chan_imp_resp_center + SYNC_POS *d_OSR - 48 * d_OSR - 2 * d_OSR + 2 ;
+ float corr_max;
+ return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, &corr_max) -
+ search_center * d_OSR;
+ ;
+}
+
+int get_sch_buffer_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, unsigned int len, float *corr_max)
+{
+ const auto tseqlen = N_SYNC_BITS - (2 * TRAIN_BEGINNING);
+ const int search_center = SYNC_POS + TRAIN_BEGINNING;
+ const int search_start_pos = 0;
+ // FIXME: proper end offset
+ const int search_stop_pos = len - (N_SYNC_BITS * 8);
+ auto tseq = &d_sch_training_seq[TRAIN_BEGINNING];
+
+ return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, corr_max) -
+ search_center * d_OSR;
+} \ No newline at end of file
diff --git a/Transceiver52M/grgsm_vitac/grgsm_vitac.h b/Transceiver52M/grgsm_vitac/grgsm_vitac.h
new file mode 100644
index 0000000..c1e470c
--- /dev/null
+++ b/Transceiver52M/grgsm_vitac/grgsm_vitac.h
@@ -0,0 +1,89 @@
+#pragma once
+/* -*- c++ -*- */
+/*
+ * @file
+ * @author (C) 2009-2017 by Piotr Krysik <ptrkrysik@gmail.com>
+ * @section LICENSE
+ *
+ * Gr-gsm 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, or (at your option)
+ * any later version.
+ *
+ * Gr-gsm 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 gr-gsm; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <vector>
+#include "constants.h"
+
+/* may only be used for for the DEFINITIONS!
+* see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91664
+*/
+#if defined(__has_attribute)
+#if __has_attribute(target_clones) && defined(__x86_64) && true
+#define MULTI_VER_TARGET_ATTR __attribute__((target_clones("avx", "sse4.2", "sse3", "sse2", "sse", "default")))
+#else
+#define MULTI_VER_TARGET_ATTR
+#endif
+#endif
+
+/* ... but apparently clang disagrees... */
+#if defined(__clang__)
+#define MULTI_VER_TARGET_ATTR_CLANGONLY MULTI_VER_TARGET_ATTR
+#else
+#define MULTI_VER_TARGET_ATTR_CLANGONLY
+#endif
+
+/* ancient gcc < 8 has no attribute, clang always pretends to be gcc 4 */
+#if !defined(__clang__) && __GNUC__ < 8
+#define NO_UBSAN __attribute__((no_sanitize_undefined))
+#else
+#if defined(__has_attribute)
+#if __has_attribute(no_sanitize)
+#define NO_UBSAN __attribute__((no_sanitize("undefined")))
+#endif
+#else
+#define NO_UBSAN
+#endif
+#endif
+
+#define SYNC_SEARCH_RANGE 30
+const int d_OSR(4);
+
+void initvita();
+
+int process_vita_burst(gr_complex *input, int tsc, unsigned char *output_binary);
+int process_vita_sc_burst(gr_complex *input, int tsc, unsigned char *output_binary, int *offset);
+
+void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, int ss);
+void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, int ss);
+void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary);
+void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary);
+
+void gmsk_mapper(const unsigned char *input, int nitems, gr_complex *gmsk_output, gr_complex start_point);
+gr_complex correlate_sequence(const gr_complex *sequence, int length, const gr_complex *input);
+inline void autocorrelation(const gr_complex *input, gr_complex *out, int nitems);
+inline void mafi(const gr_complex *input, int nitems, gr_complex *filter, int filter_length, gr_complex *output);
+int get_sch_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp);
+int get_norm_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int bcc);
+int get_sch_buffer_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, unsigned int len, float *corr_max);
+int get_access_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int max_delay);
+
+enum class btype { NB, SCH };
+struct fdata {
+ btype t;
+ unsigned int fn;
+ int tn;
+ int bcc;
+ std::string fpath;
+ std::vector<gr_complex> data;
+ unsigned int data_start_offset;
+}; \ No newline at end of file
diff --git a/Transceiver52M/grgsm_vitac/viterbi_detector.cc b/Transceiver52M/grgsm_vitac/viterbi_detector.cc
new file mode 100644
index 0000000..4ac4505
--- /dev/null
+++ b/Transceiver52M/grgsm_vitac/viterbi_detector.cc
@@ -0,0 +1,392 @@
+/* -*- c++ -*- */
+/*
+ * @file
+ * @author (C) 2009 by Piotr Krysik <ptrkrysik@gmail.com>
+ * @section LICENSE
+ *
+ * Gr-gsm 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, or (at your option)
+ * any later version.
+ *
+ * Gr-gsm 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 gr-gsm; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/*
+ * viterbi_detector:
+ * This part does the detection of received sequnece.
+ * Employed algorithm is viterbi Maximum Likehood Sequence Estimation.
+ * At this moment it gives hard decisions on the output, but
+ * it was designed with soft decisions in mind.
+ *
+ * SYNTAX: void viterbi_detector(
+ * const gr_complex * input,
+ * unsigned int samples_num,
+ * gr_complex * rhh,
+ * unsigned int start_state,
+ * const unsigned int * stop_states,
+ * unsigned int stops_num,
+ * float * output)
+ *
+ * INPUT: input: Complex received signal afted matched filtering.
+ * samples_num: Number of samples in the input table.
+ * rhh: The autocorrelation of the estimated channel
+ * impulse response.
+ * start_state: Number of the start point. In GSM each burst
+ * starts with sequence of three bits (0,0,0) which
+ * indicates start point of the algorithm.
+ * stop_states: Table with numbers of possible stop states.
+ * stops_num: Number of possible stop states
+ *
+ *
+ * OUTPUT: output: Differentially decoded hard output of the algorithm:
+ * -1 for logical "0" and 1 for logical "1"
+ *
+ * SUB_FUNC: none
+ *
+ * TEST(S): Tested with real world normal burst.
+ */
+
+#include "constants.h"
+#include <cmath>
+
+#define PATHS_NUM (1 << (CHAN_IMP_RESP_LENGTH-1))
+
+void viterbi_detector(const gr_complex * input, unsigned int samples_num, gr_complex * rhh, unsigned int start_state, const unsigned int * stop_states, unsigned int stops_num, float * output)
+{
+ float increment[8];
+ float path_metrics1[16];
+ float path_metrics2[16];
+ float paths_difference;
+ float * new_path_metrics;
+ float * old_path_metrics;
+ float * tmp;
+ float trans_table[BURST_SIZE][16];
+ float pm_candidate1, pm_candidate2;
+ bool real_imag;
+ float input_symbol_real, input_symbol_imag;
+ unsigned int i, sample_nr;
+
+/*
+* Setup first path metrics, so only state pointed by start_state is possible.
+* Start_state metric is equal to zero, the rest is written with some very low value,
+* which makes them practically impossible to occur.
+*/
+ for(i=0; i<PATHS_NUM; i++){
+ path_metrics1[i]=(-10e30);
+ }
+ path_metrics1[start_state]=0;
+
+/*
+* Compute Increment - a table of values which does not change for subsequent input samples.
+* Increment is table of reference levels for computation of branch metrics:
+* branch metric = (+/-)received_sample (+/-) reference_level
+*/
+ increment[0] = -rhh[1].imag() -rhh[2].real() -rhh[3].imag() +rhh[4].real();
+ increment[1] = rhh[1].imag() -rhh[2].real() -rhh[3].imag() +rhh[4].real();
+ increment[2] = -rhh[1].imag() +rhh[2].real() -rhh[3].imag() +rhh[4].real();
+ increment[3] = rhh[1].imag() +rhh[2].real() -rhh[3].imag() +rhh[4].real();
+ increment[4] = -rhh[1].imag() -rhh[2].real() +rhh[3].imag() +rhh[4].real();
+ increment[5] = rhh[1].imag() -rhh[2].real() +rhh[3].imag() +rhh[4].real();
+ increment[6] = -rhh[1].imag() +rhh[2].real() +rhh[3].imag() +rhh[4].real();
+ increment[7] = rhh[1].imag() +rhh[2].real() +rhh[3].imag() +rhh[4].real();
+
+
+/*
+* Computation of path metrics and decisions (Add-Compare-Select).
+* It's composed of two parts: one for odd input samples (imaginary numbers)
+* and one for even samples (real numbers).
+* Each part is composed of independent (parallelisable) statements like
+* this one:
+* pm_candidate1 = old_path_metrics[0] -input_symbol_imag +increment[2];
+* pm_candidate2 = old_path_metrics[8] -input_symbol_imag -increment[5];
+* paths_difference=pm_candidate2-pm_candidate1;
+* new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+* trans_table[sample_nr][1] = paths_difference;
+* This is very good point for optimisations (SIMD or OpenMP) as it's most time
+* consuming part of this function.
+*/
+ sample_nr=0;
+ old_path_metrics=path_metrics1;
+ new_path_metrics=path_metrics2;
+ while(sample_nr<samples_num){
+ //Processing imag states
+ real_imag=1;
+ input_symbol_imag = input[sample_nr].imag();
+
+ pm_candidate1 = old_path_metrics[0] +input_symbol_imag -increment[2];
+ pm_candidate2 = old_path_metrics[8] +input_symbol_imag +increment[5];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[0]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][0] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[0] -input_symbol_imag +increment[2];
+ pm_candidate2 = old_path_metrics[8] -input_symbol_imag -increment[5];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][1] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[1] +input_symbol_imag -increment[3];
+ pm_candidate2 = old_path_metrics[9] +input_symbol_imag +increment[4];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[2]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][2] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[1] -input_symbol_imag +increment[3];
+ pm_candidate2 = old_path_metrics[9] -input_symbol_imag -increment[4];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[3]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][3] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[2] +input_symbol_imag -increment[0];
+ pm_candidate2 = old_path_metrics[10] +input_symbol_imag +increment[7];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[4]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][4] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[2] -input_symbol_imag +increment[0];
+ pm_candidate2 = old_path_metrics[10] -input_symbol_imag -increment[7];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[5]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][5] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[3] +input_symbol_imag -increment[1];
+ pm_candidate2 = old_path_metrics[11] +input_symbol_imag +increment[6];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[6]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][6] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[3] -input_symbol_imag +increment[1];
+ pm_candidate2 = old_path_metrics[11] -input_symbol_imag -increment[6];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[7]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][7] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[4] +input_symbol_imag -increment[6];
+ pm_candidate2 = old_path_metrics[12] +input_symbol_imag +increment[1];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[8]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][8] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[4] -input_symbol_imag +increment[6];
+ pm_candidate2 = old_path_metrics[12] -input_symbol_imag -increment[1];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[9]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][9] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[5] +input_symbol_imag -increment[7];
+ pm_candidate2 = old_path_metrics[13] +input_symbol_imag +increment[0];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[10]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][10] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[5] -input_symbol_imag +increment[7];
+ pm_candidate2 = old_path_metrics[13] -input_symbol_imag -increment[0];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[11]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][11] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[6] +input_symbol_imag -increment[4];
+ pm_candidate2 = old_path_metrics[14] +input_symbol_imag +increment[3];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[12]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][12] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[6] -input_symbol_imag +increment[4];
+ pm_candidate2 = old_path_metrics[14] -input_symbol_imag -increment[3];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[13]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][13] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[7] +input_symbol_imag -increment[5];
+ pm_candidate2 = old_path_metrics[15] +input_symbol_imag +increment[2];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[14]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][14] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[7] -input_symbol_imag +increment[5];
+ pm_candidate2 = old_path_metrics[15] -input_symbol_imag -increment[2];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[15]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][15] = paths_difference;
+ tmp=old_path_metrics;
+ old_path_metrics=new_path_metrics;
+ new_path_metrics=tmp;
+
+ sample_nr++;
+ if(sample_nr==samples_num)
+ break;
+
+ //Processing real states
+ real_imag=0;
+ input_symbol_real = input[sample_nr].real();
+
+ pm_candidate1 = old_path_metrics[0] -input_symbol_real -increment[7];
+ pm_candidate2 = old_path_metrics[8] -input_symbol_real +increment[0];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[0]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][0] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[0] +input_symbol_real +increment[7];
+ pm_candidate2 = old_path_metrics[8] +input_symbol_real -increment[0];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][1] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[1] -input_symbol_real -increment[6];
+ pm_candidate2 = old_path_metrics[9] -input_symbol_real +increment[1];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[2]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][2] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[1] +input_symbol_real +increment[6];
+ pm_candidate2 = old_path_metrics[9] +input_symbol_real -increment[1];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[3]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][3] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[2] -input_symbol_real -increment[5];
+ pm_candidate2 = old_path_metrics[10] -input_symbol_real +increment[2];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[4]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][4] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[2] +input_symbol_real +increment[5];
+ pm_candidate2 = old_path_metrics[10] +input_symbol_real -increment[2];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[5]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][5] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[3] -input_symbol_real -increment[4];
+ pm_candidate2 = old_path_metrics[11] -input_symbol_real +increment[3];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[6]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][6] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[3] +input_symbol_real +increment[4];
+ pm_candidate2 = old_path_metrics[11] +input_symbol_real -increment[3];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[7]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][7] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[4] -input_symbol_real -increment[3];
+ pm_candidate2 = old_path_metrics[12] -input_symbol_real +increment[4];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[8]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][8] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[4] +input_symbol_real +increment[3];
+ pm_candidate2 = old_path_metrics[12] +input_symbol_real -increment[4];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[9]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][9] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[5] -input_symbol_real -increment[2];
+ pm_candidate2 = old_path_metrics[13] -input_symbol_real +increment[5];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[10]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][10] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[5] +input_symbol_real +increment[2];
+ pm_candidate2 = old_path_metrics[13] +input_symbol_real -increment[5];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[11]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][11] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[6] -input_symbol_real -increment[1];
+ pm_candidate2 = old_path_metrics[14] -input_symbol_real +increment[6];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[12]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][12] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[6] +input_symbol_real +increment[1];
+ pm_candidate2 = old_path_metrics[14] +input_symbol_real -increment[6];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[13]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][13] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[7] -input_symbol_real -increment[0];
+ pm_candidate2 = old_path_metrics[15] -input_symbol_real +increment[7];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[14]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][14] = paths_difference;
+
+ pm_candidate1 = old_path_metrics[7] +input_symbol_real +increment[0];
+ pm_candidate2 = old_path_metrics[15] +input_symbol_real -increment[7];
+ paths_difference=pm_candidate2-pm_candidate1;
+ new_path_metrics[15]=(paths_difference<0) ? pm_candidate1 : pm_candidate2;
+ trans_table[sample_nr][15] = paths_difference;
+
+ tmp=old_path_metrics;
+ old_path_metrics=new_path_metrics;
+ new_path_metrics=tmp;
+
+ sample_nr++;
+ }
+
+/*
+* Find the best from the stop states by comparing their path metrics.
+* Not every stop state is always possible, so we are searching in
+* a subset of them.
+*/
+ unsigned int best_stop_state;
+ float stop_state_metric, max_stop_state_metric;
+ best_stop_state = stop_states[0];
+ max_stop_state_metric = old_path_metrics[best_stop_state];
+ for(i=1; i< stops_num; i++){
+ stop_state_metric = old_path_metrics[stop_states[i]];
+ if(stop_state_metric > max_stop_state_metric){
+ max_stop_state_metric = stop_state_metric;
+ best_stop_state = stop_states[i];
+ }
+ }
+
+/*
+* This table was generated with hope that it gives a litle speedup during
+* traceback stage.
+* Received bit is related to the number of state in the trellis.
+* I've numbered states so their parity (number of ones) is related
+* to a received bit.
+*/
+ static const unsigned int parity_table[PATHS_NUM] = { 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, };
+
+/*
+* Table of previous states in the trellis diagram.
+* For GMSK modulation every state has two previous states.
+* Example:
+* previous_state_nr1 = prev_table[current_state_nr][0]
+* previous_state_nr2 = prev_table[current_state_nr][1]
+*/
+ static const unsigned int prev_table[PATHS_NUM][2] = { {0,8}, {0,8}, {1,9}, {1,9}, {2,10}, {2,10}, {3,11}, {3,11}, {4,12}, {4,12}, {5,13}, {5,13}, {6,14}, {6,14}, {7,15}, {7,15}, };
+
+/*
+* Traceback and differential decoding of received sequence.
+* Decisions stored in trans_table are used to restore best path in the trellis.
+*/
+ sample_nr=samples_num;
+ unsigned int state_nr=best_stop_state;
+ unsigned int decision;
+ bool out_bit=0;
+
+ while(sample_nr>0){
+ sample_nr--;
+ decision = (trans_table[sample_nr][state_nr]>0);
+
+ if(decision != out_bit)
+ output[sample_nr]=-trans_table[sample_nr][state_nr];
+ else
+ output[sample_nr]=trans_table[sample_nr][state_nr];
+
+ out_bit = out_bit ^ real_imag ^ parity_table[state_nr];
+ state_nr = prev_table[state_nr][decision];
+ real_imag = !real_imag;
+ }
+}
diff --git a/Transceiver52M/grgsm_vitac/viterbi_detector.h b/Transceiver52M/grgsm_vitac/viterbi_detector.h
new file mode 100644
index 0000000..92bc2a8
--- /dev/null
+++ b/Transceiver52M/grgsm_vitac/viterbi_detector.h
@@ -0,0 +1,64 @@
+/* -*- c++ -*- */
+/*
+ * @file
+ * @author (C) 2009 Piotr Krysik <ptrkrysik@gmail.com>
+ * @section LICENSE
+ *
+ * Gr-gsm 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, or (at your option)
+ * any later version.
+ *
+ * Gr-gsm 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 gr-gsm; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/*
+ * viterbi_detector:
+ * This part does the detection of received sequnece.
+ * Employed algorithm is viterbi Maximum Likehood Sequence Estimation.
+ * At this moment it gives hard decisions on the output, but
+ * it was designed with soft decisions in mind.
+ *
+ * SYNTAX: void viterbi_detector(
+ * const gr_complex * input,
+ * unsigned int samples_num,
+ * gr_complex * rhh,
+ * unsigned int start_state,
+ * const unsigned int * stop_states,
+ * unsigned int stops_num,
+ * float * output)
+ *
+ * INPUT: input: Complex received signal afted matched filtering.
+ * samples_num: Number of samples in the input table.
+ * rhh: The autocorrelation of the estimated channel
+ * impulse response.
+ * start_state: Number of the start point. In GSM each burst
+ * starts with sequence of three bits (0,0,0) which
+ * indicates start point of the algorithm.
+ * stop_states: Table with numbers of possible stop states.
+ * stops_num: Number of possible stop states
+ *
+ *
+ * OUTPUT: output: Differentially decoded hard output of the algorithm:
+ * -1 for logical "0" and 1 for logical "1"
+ *
+ * SUB_FUNC: none
+ *
+ * TEST(S): Tested with real world normal burst.
+ */
+
+#ifndef INCLUDED_VITERBI_DETECTOR_H
+#define INCLUDED_VITERBI_DETECTOR_H
+#include "constants.h"
+
+void viterbi_detector(const gr_complex * input, unsigned int samples_num, gr_complex * rhh, unsigned int start_state, const unsigned int * stop_states, unsigned int stops_num, float * output);
+
+#endif /* INCLUDED_VITERBI_DETECTOR_H */
diff --git a/Transceiver52M/ms/bladerf_specific.h b/Transceiver52M/ms/bladerf_specific.h
new file mode 100644
index 0000000..9db8bf0
--- /dev/null
+++ b/Transceiver52M/ms/bladerf_specific.h
@@ -0,0 +1,479 @@
+#pragma once
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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 "itrq.h"
+#include <atomic>
+#include <complex>
+#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <cassert>
+#include <cstring>
+
+#include <libbladeRF.h>
+#include <Timeval.h>
+#include <unistd.h>
+
+const size_t BLADE_BUFFER_SIZE = 1024 * 1;
+const size_t BLADE_NUM_BUFFERS = 32 * 1;
+const size_t NUM_TRANSFERS = 16 * 2;
+const int SAMPLE_SCALE_FACTOR = 15; // actually 16 but sigproc complains about clipping..
+
+// see https://en.cppreference.com/w/cpp/language/parameter_pack "Brace-enclosed initializers" example
+template <typename Arg, typename... Args>
+void expand_args(std::ostream &out, Arg &&arg, Args &&...args)
+{
+ out << '(' << std::forward<Arg>(arg);
+ (void)(int[]){ 0, (void((out << "," << std::forward<Args>(args))), 0)... };
+ out << ')' << std::endl;
+}
+
+template <class R, class... Args>
+using RvalFunc = R (*)(Args...);
+
+template <class R, class... Args>
+R exec_and_check(RvalFunc<R, Args...> func, const char *fname, const char *finame, const char *funcname, int line,
+ Args... args)
+{
+ R rval = func(std::forward<Args>(args)...);
+ if (rval != 0) {
+ std::cerr << ((rval >= 0) ? "OK:" : bladerf_strerror(rval)) << ':' << finame << ':' << line << ':'
+ << funcname << ':' << fname;
+ expand_args(std::cerr, args...);
+ }
+ return rval;
+}
+
+// only macros can pass a func name string
+#define blade_check(func, ...) exec_and_check(func, #func, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__)
+
+#pragma pack(push, 1)
+using blade_sample_type = std::complex<int16_t>;
+enum class blade_speed_buffer_type { HS, SS };
+template <blade_speed_buffer_type T>
+struct blade_usb_message {
+ uint32_t reserved;
+ uint64_t ts;
+ uint32_t meta_flags;
+ blade_sample_type d[(T == blade_speed_buffer_type::SS ? 512 : 256) - 4];
+};
+
+static_assert(sizeof(blade_usb_message<blade_speed_buffer_type::SS>) == 2048, "blade buffer mismatch!");
+static_assert(sizeof(blade_usb_message<blade_speed_buffer_type::HS>) == 1024, "blade buffer mismatch!");
+template <unsigned int SZ, blade_speed_buffer_type T>
+struct blade_otw_buffer {
+ static_assert((SZ >= 2 && !(SZ % 2)), "min size is 2x usb buffer!");
+ blade_usb_message<T> m[SZ];
+ int actual_samples_per_msg()
+ {
+ return sizeof(blade_usb_message<T>::d) / sizeof(typeof(blade_usb_message<T>::d[0]));
+ }
+ int actual_samples_per_buffer()
+ {
+ return SZ * actual_samples_per_msg();
+ }
+ int samples_per_buffer()
+ {
+ return SZ * sizeof(blade_usb_message<T>) / sizeof(typeof(blade_usb_message<T>::d[0]));
+ }
+ int num_msgs_per_buffer()
+ {
+ return SZ;
+ }
+ auto get_first_ts()
+ {
+ return m[0].ts;
+ }
+ constexpr auto *getsampleoffset(int ofs)
+ {
+ auto full = ofs / actual_samples_per_msg();
+ auto rem = ofs % actual_samples_per_msg();
+ return &m[full].d[rem];
+ }
+ int readall(blade_sample_type *outaddr)
+ {
+ blade_sample_type *addr = outaddr;
+ for (unsigned int i = 0; i < SZ; i++) {
+ memcpy(addr, &m[i].d[0], actual_samples_per_msg() * sizeof(blade_sample_type));
+ addr += actual_samples_per_msg();
+ }
+ return actual_samples_per_buffer();
+ }
+ int read_n(blade_sample_type *outaddr, int start, int num)
+ {
+ assert((start + num) <= actual_samples_per_buffer());
+ assert(start >= 0);
+
+ if (!num)
+ return 0;
+
+ // which buffer?
+ int start_buf_idx = (start > 0) ? start / actual_samples_per_msg() : 0;
+ // offset from actual buffer start
+ auto start_offset_in_buf = (start - (start_buf_idx * actual_samples_per_msg()));
+ auto samp_rem_in_first_buf = actual_samples_per_msg() - start_offset_in_buf;
+ auto remaining_first_buf = num > samp_rem_in_first_buf ? samp_rem_in_first_buf : num;
+
+ memcpy(outaddr, &m[start_buf_idx].d[start_offset_in_buf],
+ remaining_first_buf * sizeof(blade_sample_type));
+ outaddr += remaining_first_buf;
+
+ auto remaining = num - remaining_first_buf;
+
+ if (!remaining)
+ return num;
+
+ start_buf_idx++;
+
+ auto rem_full_bufs = remaining / actual_samples_per_msg();
+ remaining -= rem_full_bufs * actual_samples_per_msg();
+
+ for (int i = 0; i < rem_full_bufs; i++) {
+ memcpy(outaddr, &m[start_buf_idx++].d[0], actual_samples_per_msg() * sizeof(blade_sample_type));
+ outaddr += actual_samples_per_msg();
+ }
+
+ if (remaining)
+ memcpy(outaddr, &m[start_buf_idx].d[0], remaining * sizeof(blade_sample_type));
+ return num;
+ }
+ int write_n_burst(blade_sample_type *in, int num, uint64_t first_ts)
+ {
+ assert(num <= actual_samples_per_buffer());
+ int len_rem = num;
+ for (unsigned int i = 0; i < SZ; i++) {
+ m[i] = {};
+ m[i].ts = first_ts + i * actual_samples_per_msg();
+ if (len_rem) {
+ int max_to_copy =
+ len_rem > actual_samples_per_msg() ? actual_samples_per_msg() : len_rem;
+ memcpy(&m[i].d[0], in, max_to_copy * sizeof(blade_sample_type));
+ len_rem -= max_to_copy;
+ in += actual_samples_per_msg();
+ }
+ }
+ return num;
+ }
+};
+#pragma pack(pop)
+
+template <unsigned int SZ, blade_speed_buffer_type T>
+struct blade_otw_buffer_helper {
+ static_assert((SZ >= 1024 && ((SZ & (SZ - 1)) == 0)), "only buffer size multiples of 1024 allowed!");
+ static blade_otw_buffer<SZ / 512, T> x;
+};
+
+using dev_buf_t = typeof(blade_otw_buffer_helper<BLADE_BUFFER_SIZE, blade_speed_buffer_type::SS>::x);
+// using buf_in_use = blade_otw_buffer<2, blade_speed_buffer_type::SS>;
+using bh_fn_t = std::function<int(dev_buf_t *)>;
+
+template <typename T>
+struct blade_hw {
+ struct bladerf *dev;
+ struct bladerf_stream *rx_stream;
+ struct bladerf_stream *tx_stream;
+ // using pkt2buf = blade_otw_buffer<2, blade_speed_buffer_type::SS>;
+ using tx_buf_q_type = spsc_cond_timeout<BLADE_NUM_BUFFERS, dev_buf_t *, true, false>;
+ const unsigned int rxFullScale, txFullScale;
+ const int rxtxdelay;
+
+ float rxgain, txgain;
+ static std::atomic<bool> stop_lower_threads_flag;
+ double rxfreq_cache, txfreq_cache;
+
+ struct ms_trx_config {
+ int tx_freq;
+ int rx_freq;
+ int sample_rate;
+ int bandwidth;
+
+ public:
+ ms_trx_config() : tx_freq(881e6), rx_freq(926e6), sample_rate(((1625e3 / 6) * 4)), bandwidth(1e6)
+ {
+ }
+ } cfg;
+
+ struct buf_mgmt {
+ void **rx_samples;
+ void **tx_samples;
+ tx_buf_q_type bufptrqueue;
+
+ } buf_mgmt;
+
+ virtual ~blade_hw()
+ {
+ close_device();
+ }
+ blade_hw()
+ : rxFullScale(2047), txFullScale(2047), rxtxdelay(-60), rxgain(30), txgain(30), rxfreq_cache(0),
+ txfreq_cache(0)
+ {
+ }
+
+ void close_device()
+ {
+ if (dev) {
+ if (tx_stream) {
+ bladerf_deinit_stream(tx_stream);
+ }
+
+ if (rx_stream) {
+ bladerf_deinit_stream(rx_stream);
+ }
+
+ bladerf_enable_module(dev, BLADERF_MODULE_RX, false);
+ bladerf_enable_module(dev, BLADERF_MODULE_TX, false);
+
+ bladerf_close(dev);
+ dev = NULL;
+ }
+ }
+
+ int init_device(bh_fn_t rxh, bh_fn_t txh)
+ {
+ struct bladerf_rational_rate rate = { 0, static_cast<uint64_t>((1625e3 * 4)) * 64, 6 * 64 }, actual;
+
+ bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_DEBUG);
+ bladerf_set_usb_reset_on_open(true);
+
+ blade_check(bladerf_open, &dev, "");
+ if (!dev) {
+ std::cerr << "open failed, device missing?" << std::endl;
+ exit(0);
+ }
+ if (bladerf_device_speed(dev) != bladerf_dev_speed::BLADERF_DEVICE_SPEED_SUPER) {
+ std::cerr << "open failed, only superspeed (usb3) supported!" << std::endl;
+ return -1;
+ }
+
+ blade_check(bladerf_set_tuning_mode, dev, bladerf_tuning_mode::BLADERF_TUNING_MODE_FPGA);
+
+ bool is_locked;
+ blade_check(bladerf_set_pll_enable, dev, true);
+ uint64_t refclock = 10000000UL;
+ blade_check(bladerf_set_pll_refclk, dev, refclock);
+ for (int i = 0; i < 20; i++) {
+ usleep(50 * 1000);
+ bladerf_get_pll_lock_state(dev, &is_locked);
+
+ if (is_locked)
+ break;
+ }
+ if (!is_locked) {
+ std::cerr << "unable to lock refclk!" << std::endl;
+ return -1;
+ }
+
+ blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_RX(0), &rate, &actual);
+ blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_TX(0), &rate, &actual);
+
+ blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)cfg.rx_freq);
+ blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)cfg.tx_freq);
+
+ blade_check(bladerf_set_bandwidth, dev, BLADERF_CHANNEL_RX(0), (bladerf_bandwidth)cfg.bandwidth,
+ (bladerf_bandwidth *)NULL);
+ blade_check(bladerf_set_bandwidth, dev, BLADERF_CHANNEL_TX(0), (bladerf_bandwidth)cfg.bandwidth,
+ (bladerf_bandwidth *)NULL);
+
+ blade_check(bladerf_set_gain_mode, dev, BLADERF_CHANNEL_RX(0), BLADERF_GAIN_MGC);
+ setRxGain(rxgain, 0);
+ setTxGain(txgain, 0);
+ usleep(1000);
+
+ bladerf_set_stream_timeout(dev, BLADERF_TX, 10);
+ bladerf_set_stream_timeout(dev, BLADERF_RX, 10);
+
+ blade_check(bladerf_init_stream, &rx_stream, dev, getrxcb(rxh), &buf_mgmt.rx_samples, BLADE_NUM_BUFFERS,
+ BLADERF_FORMAT_SC16_Q11_META, BLADE_BUFFER_SIZE, NUM_TRANSFERS, (void *)this);
+
+ blade_check(bladerf_init_stream, &tx_stream, dev, gettxcb(txh), &buf_mgmt.tx_samples, BLADE_NUM_BUFFERS,
+ BLADERF_FORMAT_SC16_Q11_META, BLADE_BUFFER_SIZE, NUM_TRANSFERS, (void *)this);
+
+ for (unsigned int i = 0; i < BLADE_NUM_BUFFERS; i++) {
+ auto cur_buffer = reinterpret_cast<tx_buf_q_type::elem_t *>(buf_mgmt.tx_samples);
+ buf_mgmt.bufptrqueue.spsc_push(&cur_buffer[i]);
+ }
+
+ return 0;
+ }
+
+ void actually_enable_streams()
+ {
+ blade_check(bladerf_enable_module, dev, BLADERF_MODULE_RX, true);
+ usleep(1000);
+ blade_check(bladerf_enable_module, dev, BLADERF_MODULE_TX, true);
+ }
+
+ bool tuneTx(double freq, size_t chan = 0)
+ {
+ if (txfreq_cache == freq)
+ return true;
+ msleep(15);
+ blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)freq);
+ txfreq_cache = freq;
+ msleep(15);
+ return true;
+ };
+ bool tuneRx(double freq, size_t chan = 0)
+ {
+ if (rxfreq_cache == freq)
+ return true;
+ msleep(15);
+ blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)freq);
+ rxfreq_cache = freq;
+ msleep(15);
+ return true;
+ };
+ bool tuneRxOffset(double offset, size_t chan = 0)
+ {
+ return true;
+ };
+
+ double setRxGain(double dB, size_t chan = 0)
+ {
+ rxgain = dB;
+ msleep(15);
+ blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_RX(0), (bladerf_gain)dB);
+ msleep(15);
+ return dB;
+ };
+ double setTxGain(double dB, size_t chan = 0)
+ {
+ txgain = dB;
+ msleep(15);
+ blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_TX(0), (bladerf_gain)dB);
+ msleep(15);
+ return dB;
+ };
+ int setPowerAttenuation(int atten, size_t chan = 0)
+ {
+ return atten;
+ };
+
+ static void check_timestamp(dev_buf_t *rcd)
+ {
+ static bool first = true;
+ static uint64_t last_ts;
+ if (first) {
+ first = false;
+ last_ts = rcd->m[0].ts;
+ } else if (last_ts + rcd->actual_samples_per_buffer() != rcd->m[0].ts) {
+ std::cerr << "RX Overrun!" << last_ts << " " << rcd->actual_samples_per_buffer() << " "
+ << last_ts + rcd->actual_samples_per_buffer() << " " << rcd->m[0].ts << std::endl;
+ last_ts = rcd->m[0].ts;
+ } else {
+ last_ts = rcd->m[0].ts;
+ }
+ }
+
+ bladerf_stream_cb getrxcb(bh_fn_t rxbh)
+ {
+ // C cb -> no capture!
+ static auto rxbhfn = rxbh;
+ return [](struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta,
+ void *samples, size_t num_samples, void *user_data) -> void * {
+ // struct blade_hw *trx = (struct blade_hw *)user_data;
+ static int to_skip = 0;
+ dev_buf_t *rcd = (dev_buf_t *)samples;
+
+ if (stop_lower_threads_flag)
+ return BLADERF_STREAM_SHUTDOWN;
+
+ if (to_skip < 120) // prevents weird overflows on startup
+ to_skip++;
+ else {
+ check_timestamp(rcd);
+ rxbhfn(rcd);
+ }
+
+ return samples;
+ };
+ }
+ bladerf_stream_cb gettxcb(bh_fn_t txbh)
+ {
+ // C cb -> no capture!
+ static auto txbhfn = txbh;
+ return [](struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta,
+ void *samples, size_t num_samples, void *user_data) -> void * {
+ struct blade_hw *trx = (struct blade_hw *)user_data;
+ auto ptr = reinterpret_cast<tx_buf_q_type::elem_t>(samples);
+
+ if (samples) // put buffer address back into queue, ready to be reused
+ trx->buf_mgmt.bufptrqueue.spsc_push(&ptr);
+
+ if (stop_lower_threads_flag)
+ return BLADERF_STREAM_SHUTDOWN;
+
+ return BLADERF_STREAM_NO_DATA;
+ };
+ }
+
+ auto get_rx_burst_handler_fn(bh_fn_t burst_handler)
+ {
+ using thist = decltype(this);
+ auto fn = [](void *args) -> void * {
+ thist t = reinterpret_cast<thist>(args);
+ int status = 0;
+ if (!stop_lower_threads_flag)
+ status = bladerf_stream(t->rx_stream, BLADERF_RX_X1);
+ if (status < 0)
+ std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl;
+
+ return 0;
+ };
+ return fn;
+ }
+ auto get_tx_burst_handler_fn(bh_fn_t burst_handler)
+ {
+ using thist = decltype(this);
+ auto fn = [](void *args) -> void * {
+ thist t = reinterpret_cast<thist>(args);
+ int status = 0;
+ if (!stop_lower_threads_flag)
+ status = bladerf_stream(t->tx_stream, BLADERF_TX_X1);
+ if (status < 0)
+ std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl;
+
+ return 0;
+ };
+ return fn;
+ }
+
+ void submit_burst_ts(blade_sample_type *buffer, int len, uint64_t ts)
+ {
+ tx_buf_q_type::elem_t rcd;
+
+ // exit by submitting a dummy buffer to assure the libbladerf stream mutex is happy (thread!)
+ if (!buffer) {
+ bladerf_submit_stream_buffer(tx_stream, (void *)BLADERF_STREAM_SHUTDOWN, 1000);
+ return;
+ }
+
+ //get empty bufer from list
+ while (!buf_mgmt.bufptrqueue.spsc_pop(&rcd))
+ buf_mgmt.bufptrqueue.spsc_prep_pop();
+ assert(rcd != nullptr);
+
+ rcd->write_n_burst(buffer, len, ts + rxtxdelay); // blade xa4 specific delay!
+ blade_check(bladerf_submit_stream_buffer_nb, tx_stream, (void *)rcd);
+ }
+};
diff --git a/Transceiver52M/ms/itrq.h b/Transceiver52M/ms/itrq.h
new file mode 100644
index 0000000..69ff515
--- /dev/null
+++ b/Transceiver52M/ms/itrq.h
@@ -0,0 +1,249 @@
+#pragma once
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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 <atomic>
+
+#include <condition_variable>
+#include <mutex>
+#include <sys/eventfd.h>
+#include <unistd.h>
+
+namespace spsc_detail
+{
+template <bool block_read, bool block_write>
+class spsc_cond_timeout_detail {
+ std::condition_variable cond_r, cond_w;
+ std::mutex lr, lw;
+ std::atomic_int r_flag, w_flag;
+ const int timeout_ms = 200;
+
+ public:
+ explicit spsc_cond_timeout_detail() : r_flag(0), w_flag(0)
+ {
+ }
+
+ ~spsc_cond_timeout_detail()
+ {
+ }
+
+ ssize_t spsc_check_r()
+ {
+ std::unique_lock<std::mutex> lk(lr);
+ if (cond_r.wait_for(lk, std::chrono::milliseconds(timeout_ms), [&] { return r_flag != 0; })) {
+ r_flag--;
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ ssize_t spsc_check_w()
+ {
+ std::unique_lock<std::mutex> lk(lw);
+ if (cond_w.wait_for(lk, std::chrono::milliseconds(timeout_ms), [&] { return w_flag != 0; })) {
+ w_flag--;
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ void spsc_notify_r()
+ {
+ std::unique_lock<std::mutex> lk(lr);
+ r_flag++;
+ cond_r.notify_one();
+ }
+ void spsc_notify_w()
+ {
+ std::unique_lock<std::mutex> lk(lw);
+ w_flag++;
+ cond_w.notify_one();
+ }
+};
+
+template <bool block_read, bool block_write>
+class spsc_cond_detail {
+ std::condition_variable cond_r, cond_w;
+ std::mutex lr, lw;
+ std::atomic_int r_flag, w_flag;
+
+ public:
+ explicit spsc_cond_detail() : r_flag(0), w_flag(0)
+ {
+ }
+
+ ~spsc_cond_detail()
+ {
+ }
+
+ ssize_t spsc_check_r()
+ {
+ std::unique_lock<std::mutex> lk(lr);
+ while (r_flag == 0)
+ cond_r.wait(lk);
+ r_flag--;
+ return 1;
+ }
+ ssize_t spsc_check_w()
+ {
+ std::unique_lock<std::mutex> lk(lw);
+ while (w_flag == 0)
+ cond_w.wait(lk);
+ w_flag--;
+ return 1;
+ }
+ void spsc_notify_r()
+ {
+ std::unique_lock<std::mutex> lk(lr);
+ r_flag++;
+ cond_r.notify_one();
+ }
+ void spsc_notify_w()
+ {
+ std::unique_lock<std::mutex> lk(lw);
+ w_flag++;
+ cond_w.notify_one();
+ }
+};
+
+// originally designed for select loop integration
+template <bool block_read, bool block_write>
+class spsc_efd_detail {
+ int efd_r, efd_w; /* eventfds used to block/notify readers/writers */
+
+ public:
+ explicit spsc_efd_detail()
+ : efd_r(eventfd(0, block_read ? 0 : EFD_NONBLOCK)), efd_w(eventfd(1, block_write ? 0 : EFD_NONBLOCK))
+ {
+ }
+
+ ~spsc_efd_detail()
+ {
+ close(efd_r);
+ close(efd_w);
+ }
+
+ ssize_t spsc_check_r()
+ {
+ uint64_t efdr;
+ return read(efd_r, &efdr, sizeof(uint64_t));
+ }
+ ssize_t spsc_check_w()
+ {
+ uint64_t efdr;
+ return read(efd_w, &efdr, sizeof(uint64_t));
+ }
+ void spsc_notify_r()
+ {
+ uint64_t efdu = 1;
+ write(efd_r, &efdu, sizeof(uint64_t));
+ }
+ void spsc_notify_w()
+ {
+ uint64_t efdu = 1;
+ write(efd_w, &efdu, sizeof(uint64_t));
+ }
+ int get_r_efd()
+ {
+ return efd_r;
+ }
+ int get_w_efd()
+ {
+ return efd_w;
+ }
+};
+
+template <unsigned int SZ, typename ELEM, bool block_read, bool block_write, template <bool, bool> class T>
+class spsc : public T<block_read, block_write> {
+ static_assert(SZ > 0, "queues need a size...");
+ std::atomic<unsigned int> readptr;
+ std::atomic<unsigned int> writeptr;
+
+ ELEM buf[SZ];
+
+ public:
+ using base_t = T<block_read, block_write>;
+ using elem_t = ELEM;
+ explicit spsc() : readptr(0), writeptr(0)
+ {
+ }
+
+ ~spsc()
+ {
+ }
+
+ /*! Adds element to the queue by copying the data.
+ * \param[in] elem input buffer, must match the originally configured queue buffer size!.
+ * \returns true if queue was not full and element was successfully pushed */
+ bool spsc_push(const ELEM *elem)
+ {
+ size_t cur_wp, cur_rp;
+ cur_wp = writeptr.load(std::memory_order_relaxed);
+ cur_rp = readptr.load(std::memory_order_acquire);
+ if ((cur_wp + 1) % SZ == cur_rp) {
+ if (block_write)
+ base_t::spsc_check_w(); /* blocks, ensures next (!) call succeeds */
+ return false;
+ }
+ buf[cur_wp] = *elem;
+ writeptr.store((cur_wp + 1) % SZ, std::memory_order_release);
+ if (block_read)
+ base_t::spsc_notify_r(); /* fine after release */
+ return true;
+ }
+
+ /*! Removes element from the queue by copying the data.
+ * \param[in] elem output buffer, must match the originally configured queue buffer size!.
+ * \returns true if queue was not empty and element was successfully removed */
+ bool spsc_pop(ELEM *elem)
+ {
+ size_t cur_wp, cur_rp;
+ cur_wp = writeptr.load(std::memory_order_acquire);
+ cur_rp = readptr.load(std::memory_order_relaxed);
+
+ if (cur_wp == cur_rp) /* blocks via prep_pop */
+ return false;
+
+ *elem = buf[cur_rp];
+ readptr.store((cur_rp + 1) % SZ, std::memory_order_release);
+ if (block_write)
+ base_t::spsc_notify_w();
+ return true;
+ }
+
+ /*! Reads the read-fd of the queue, which, depending on settings passed on queue creation, blocks.
+ * This function can be used to deliberately wait for a non-empty queue on the read side.
+ * \returns result of reading the fd. */
+ ssize_t spsc_prep_pop()
+ {
+ return base_t::spsc_check_r();
+ }
+};
+
+} // namespace spsc_detail
+
+template <unsigned int SZ, typename ELEM, bool block_read, bool block_write>
+class spsc_evfd : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_efd_detail> {};
+template <unsigned int SZ, typename ELEM, bool block_read, bool block_write>
+class spsc_cond : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_cond_detail> {};
+template <unsigned int SZ, typename ELEM, bool block_read, bool block_write>
+class spsc_cond_timeout
+ : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_cond_timeout_detail> {}; \ No newline at end of file
diff --git a/Transceiver52M/ms/l1ctl_server.c b/Transceiver52M/ms/l1ctl_server.c
new file mode 100644
index 0000000..9e9083f
--- /dev/null
+++ b/Transceiver52M/ms/l1ctl_server.c
@@ -0,0 +1,275 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * UNIX socket server for L1CTL
+ *
+ * (C) 2013 by Sylvain Munaut <tnt@246tNt.com>
+ * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
+ * (C) 2022 by by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/write_queue.h>
+
+#include <osmocom/bb/trxcon/logging.h>
+#include <osmocom/bb/trxcon/l1ctl_server.h>
+
+#define LOGP_CLI(cli, cat, level, fmt, args...) LOGP(cat, level, "%s" fmt, (cli)->log_prefix, ##args)
+
+static int l1ctl_client_read_cb(struct osmo_fd *ofd)
+{
+ struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
+ struct msgb *msg;
+ uint16_t len;
+ int rc;
+
+ /* Attempt to read from socket */
+ rc = read(ofd->fd, &len, L1CTL_MSG_LEN_FIELD);
+ if (rc != L1CTL_MSG_LEN_FIELD) {
+ if (rc <= 0) {
+ LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: read() failed (rc=%d): %s\n", rc,
+ strerror(errno));
+ } else {
+ LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: short read\n");
+ rc = -EIO;
+ }
+ l1ctl_client_conn_close(client);
+ return rc;
+ }
+
+ /* Check message length */
+ len = ntohs(len);
+ if (len > L1CTL_LENGTH) {
+ LOGP_CLI(client, DL1D, LOGL_ERROR, "Length is too big: %u\n", len);
+ return -EINVAL;
+ }
+
+ /* Allocate a new msg */
+ msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM, L1CTL_HEADROOM, "l1ctl_rx_msg");
+ if (!msg) {
+ LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to allocate msg\n");
+ return -ENOMEM;
+ }
+
+ msg->l1h = msgb_put(msg, len);
+ rc = read(ofd->fd, msg->l1h, msgb_l1len(msg));
+ if (rc != len) {
+ LOGP_CLI(client, DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: %s\n", len, rc, strerror(errno));
+ msgb_free(msg);
+ return rc;
+ }
+
+ /* Debug print */
+ LOGP_CLI(client, DL1D, LOGL_DEBUG, "RX: '%s'\n", osmo_hexdump(msg->data, msg->len));
+
+ /* Call L1CTL handler */
+ client->server->cfg->conn_read_cb(client, msg);
+
+ return 0;
+}
+
+static int l1ctl_client_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
+ int len;
+
+ if (ofd->fd <= 0)
+ return -EINVAL;
+
+ len = write(ofd->fd, msg->data, msg->len);
+ if (len != msg->len) {
+ LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to write data: written (%d) < msg_len (%d)\n", len,
+ msg->len);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Connection handler */
+static int l1ctl_server_conn_cb(struct osmo_fd *sfd, unsigned int flags)
+{
+ struct l1ctl_server *server = (struct l1ctl_server *)sfd->data;
+ struct l1ctl_client *client;
+ int rc, client_fd;
+
+ client_fd = accept(sfd->fd, NULL, NULL);
+ if (client_fd < 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to accept() a new connection: "
+ "%s\n",
+ strerror(errno));
+ return client_fd;
+ }
+
+ if (server->cfg->num_clients_max > 0 /* 0 means unlimited */ &&
+ server->num_clients >= server->cfg->num_clients_max) {
+ LOGP(DL1C, LOGL_NOTICE,
+ "L1CTL server cannot accept more "
+ "than %u connection(s)\n",
+ server->cfg->num_clients_max);
+ close(client_fd);
+ return -ENOMEM;
+ }
+
+ client = talloc_zero(server, struct l1ctl_client);
+ if (client == NULL) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to allocate an L1CTL client\n");
+ close(client_fd);
+ return -ENOMEM;
+ }
+
+ /* Init the client's write queue */
+ osmo_wqueue_init(&client->wq, 100);
+ INIT_LLIST_HEAD(&client->wq.bfd.list);
+
+ client->wq.write_cb = &l1ctl_client_write_cb;
+ client->wq.read_cb = &l1ctl_client_read_cb;
+ osmo_fd_setup(&client->wq.bfd, client_fd, OSMO_FD_READ, &osmo_wqueue_bfd_cb, client, 0);
+
+ /* Register the client's write queue */
+ rc = osmo_fd_register(&client->wq.bfd);
+ if (rc != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to register a new connection fd\n");
+ close(client->wq.bfd.fd);
+ talloc_free(client);
+ return rc;
+ }
+
+ llist_add_tail(&client->list, &server->clients);
+ client->id = server->next_client_id++;
+ client->server = server;
+ server->num_clients++;
+
+ LOGP(DL1C, LOGL_NOTICE, "L1CTL server got a new connection (id=%u)\n", client->id);
+
+ if (client->server->cfg->conn_accept_cb != NULL)
+ client->server->cfg->conn_accept_cb(client);
+
+ return 0;
+}
+
+int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg)
+{
+ uint8_t *len;
+
+ /* Debug print */
+ LOGP_CLI(client, DL1D, LOGL_DEBUG, "TX: '%s'\n", osmo_hexdump(msg->data, msg->len));
+
+ if (msg->l1h != msg->data)
+ LOGP_CLI(client, DL1D, LOGL_INFO, "Message L1 header != Message Data\n");
+
+ /* Prepend 16-bit length before sending */
+ len = msgb_push(msg, L1CTL_MSG_LEN_FIELD);
+ osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len);
+
+ if (osmo_wqueue_enqueue(&client->wq, msg) != 0) {
+ LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to enqueue msg!\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+void l1ctl_client_conn_close(struct l1ctl_client *client)
+{
+ struct l1ctl_server *server = client->server;
+
+ LOGP_CLI(client, DL1C, LOGL_NOTICE, "Closing L1CTL connection\n");
+
+ if (server->cfg->conn_close_cb != NULL)
+ server->cfg->conn_close_cb(client);
+
+ /* Close connection socket */
+ osmo_fd_unregister(&client->wq.bfd);
+ close(client->wq.bfd.fd);
+ client->wq.bfd.fd = -1;
+
+ /* Clear pending messages */
+ osmo_wqueue_clear(&client->wq);
+
+ client->server->num_clients--;
+ llist_del(&client->list);
+ talloc_free(client);
+
+ /* If this was the last client, reset the client IDs generator to 0.
+ * This way avoid assigning huge unreadable client IDs like 26545. */
+ if (llist_empty(&server->clients))
+ server->next_client_id = 0;
+}
+
+struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg)
+{
+ struct l1ctl_server *server;
+ int rc;
+
+ LOGP(DL1C, LOGL_NOTICE, "Init L1CTL server (sock_path=%s)\n", cfg->sock_path);
+
+ server = talloc(ctx, struct l1ctl_server);
+ OSMO_ASSERT(server != NULL);
+
+ *server = (struct l1ctl_server){
+ .clients = LLIST_HEAD_INIT(server->clients),
+ .cfg = cfg,
+ };
+
+ /* conn_read_cb shall not be NULL */
+ OSMO_ASSERT(cfg->conn_read_cb != NULL);
+
+ /* Bind connection handler */
+ osmo_fd_setup(&server->ofd, -1, OSMO_FD_READ, &l1ctl_server_conn_cb, server, 0);
+
+ rc = osmo_sock_unix_init_ofd(&server->ofd, SOCK_STREAM, 0, cfg->sock_path, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n", strerror(errno));
+ talloc_free(server);
+ return NULL;
+ }
+
+ return server;
+}
+
+void l1ctl_server_free(struct l1ctl_server *server)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL server\n");
+
+ /* Close all client connections */
+ while (!llist_empty(&server->clients)) {
+ struct l1ctl_client *client = llist_entry(server->clients.next, struct l1ctl_client, list);
+ l1ctl_client_conn_close(client);
+ }
+
+ /* Unbind listening socket */
+ if (server->ofd.fd != -1) {
+ osmo_fd_unregister(&server->ofd);
+ close(server->ofd.fd);
+ server->ofd.fd = -1;
+ }
+
+ talloc_free(server);
+}
diff --git a/Transceiver52M/ms/l1ctl_server_cb.cpp b/Transceiver52M/ms/l1ctl_server_cb.cpp
new file mode 100644
index 0000000..42f64ac
--- /dev/null
+++ b/Transceiver52M/ms/l1ctl_server_cb.cpp
@@ -0,0 +1,71 @@
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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/>.
+ *
+ */
+
+extern "C" {
+#include <osmocom/bb/trxcon/trxcon.h>
+#include <osmocom/bb/trxcon/trxcon_fsm.h>
+#include <osmocom/bb/trxcon/l1ctl_server.h>
+}
+#include "ms_trxcon_if.h"
+
+static struct l1ctl_server_cfg server_cfg;
+static struct l1ctl_server *server = NULL;
+
+static int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg)
+{
+ struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv;
+
+ return trxcon_l1ctl_receive(trxcon, msg);
+}
+
+static void l1ctl_conn_accept_cb(struct l1ctl_client *l1c)
+{
+ l1c->log_prefix = talloc_strdup(l1c, g_trxcon->log_prefix);
+ l1c->priv = g_trxcon;
+ g_trxcon->l2if = l1c;
+}
+
+static void l1ctl_conn_close_cb(struct l1ctl_client *l1c)
+{
+ struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv;
+
+ if (trxcon == NULL || trxcon->fi == NULL)
+ return;
+
+ osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_L2IF_FAILURE, NULL);
+}
+
+bool trxc_l1ctl_init(void *tallctx)
+{
+ /* Start the L1CTL server */
+ server_cfg = (struct l1ctl_server_cfg){
+ /* TODO: make path configurable */
+ .sock_path = "/tmp/osmocom_l2", .num_clients_max = 1,
+ .conn_read_cb = &l1ctl_rx_cb, .conn_accept_cb = &l1ctl_conn_accept_cb,
+ .conn_close_cb = &l1ctl_conn_close_cb,
+ };
+
+ server = l1ctl_server_alloc(tallctx, &server_cfg);
+ if (server == NULL) {
+ return false;
+ }
+ return true;
+}
diff --git a/Transceiver52M/ms/logging.c b/Transceiver52M/ms/logging.c
new file mode 100644
index 0000000..af397e0
--- /dev/null
+++ b/Transceiver52M/ms/logging.c
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ *
+ */
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/bb/trxcon/trxcon.h>
+#include <osmocom/bb/trxcon/logging.h>
+
+static struct log_info_cat trxcon_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,
+ },
+ [DTRXC] = {
+ .name = "DTRXC",
+ .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,
+ },
+ [DGPRS] = {
+ .name = "DGPRS",
+ .description = "L1 GPRS (MAC layer)",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+};
+
+static const struct log_info trxcon_log_info = {
+ .cat = trxcon_log_info_cat,
+ .num_cat = ARRAY_SIZE(trxcon_log_info_cat),
+};
+
+static const int trxcon_log_cfg[] = {
+ [TRXCON_LOGC_FSM] = DAPP,
+ [TRXCON_LOGC_L1C] = DL1C,
+ [TRXCON_LOGC_L1D] = DL1D,
+ [TRXCON_LOGC_SCHC] = DSCH,
+ [TRXCON_LOGC_SCHD] = DSCHD,
+ [TRXCON_LOGC_GPRS] = DGPRS,
+};
+
+void trxc_log_init(void *tallctx)
+{
+ osmo_init_logging2(tallctx, &trxcon_log_info);
+ log_target_file_switch_to_wqueue(osmo_stderr_target);
+
+ trxcon_set_log_cfg(&trxcon_log_cfg[0], ARRAY_SIZE(trxcon_log_cfg));
+}
diff --git a/Transceiver52M/ms/ms.cpp b/Transceiver52M/ms/ms.cpp
new file mode 100644
index 0000000..4ae8668
--- /dev/null
+++ b/Transceiver52M/ms/ms.cpp
@@ -0,0 +1,160 @@
+
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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 "GSMCommon.h"
+#include <atomic>
+#include <cassert>
+#include <complex>
+#include <iostream>
+#include <cstdlib>
+#include <cstdio>
+#include <thread>
+#include <fstream>
+
+#include "ms.h"
+
+extern "C" {
+#include "sch.h"
+}
+
+#include "threadsched.h"
+
+dummylog ms_trx::dummy_log;
+
+#ifdef DBGXX
+const int offsetrange = 200;
+const int offset_start = -15;
+static int offset_ctr = 0;
+#endif
+
+template <>
+std::atomic<bool> ms_trx::base::stop_lower_threads_flag(false);
+
+int ms_trx::init_dev_and_streams()
+{
+ int status = 0;
+ status = init_device(rx_bh(), tx_bh());
+ if (status < 0) {
+ std::cerr << "failed to init dev!" << std::endl;
+ return -1;
+ }
+ return status;
+}
+
+bh_fn_t ms_trx::rx_bh()
+{
+ return [this](dev_buf_t *rcd) -> int {
+ if (this->search_for_sch(rcd) == SCH_STATE::FOUND)
+ this->grab_bursts(rcd);
+ return 0;
+ };
+}
+
+bh_fn_t ms_trx::tx_bh()
+{
+ return [this](dev_buf_t *rcd) -> int {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
+ auto y = this;
+#pragma GCC diagnostic pop
+ /* nothing to do here */
+ return 0;
+ };
+}
+
+void ms_trx::start_lower_ms()
+{
+ if (stop_lower_threads_flag)
+ return;
+ auto fn = get_rx_burst_handler_fn(rx_bh());
+ lower_rx_task = spawn_worker_thread(sched_params::thread_names::RXRUN, fn, this);
+
+ usleep(1000);
+ auto fn2 = get_tx_burst_handler_fn(tx_bh());
+ lower_tx_task = spawn_worker_thread(sched_params::thread_names::TXRUN, fn2, this);
+
+ actually_enable_streams();
+}
+
+void ms_trx::set_upper_ready(bool is_ready)
+{
+ upper_is_ready = is_ready;
+}
+
+void ms_trx::stop_threads()
+{
+ std::cerr << "killing threads..." << std::endl;
+ stop_lower_threads_flag = true;
+ close_device();
+ std::cerr << "dev closed..." << std::endl;
+ pthread_join(lower_rx_task, nullptr);
+ std::cerr << "L rx dead..." << std::endl;
+ pthread_join(lower_tx_task, nullptr);
+ std::cerr << "L tx dead..." << std::endl;
+}
+
+void ms_trx::submit_burst(blade_sample_type *buffer, int len, GSM::Time target)
+{
+ int64_t now_ts;
+ GSM::Time now_time;
+ target.incTN(3); // ul dl offset
+ int target_fn = target.FN();
+ int target_tn = target.TN();
+ timekeeper.get_both(&now_time, &now_ts);
+
+ auto diff_fn = GSM::FNDelta(target_fn, now_time.FN());
+ int diff_tn = (target_tn - (int)now_time.TN()) % 8;
+ auto tosend = GSM::Time(diff_fn, 0);
+
+ if (diff_tn > 0)
+ tosend.incTN(diff_tn);
+ else
+ tosend.decTN(-diff_tn);
+
+ // in theory fn equal and tn+3 equal is also a problem...
+ if (diff_fn < 0 || (diff_fn == 0 && (target_tn-now_time.TN() < 3))) {
+ std::cerr << "## TX too late?! fn DIFF:" << diff_fn << " tn LOCAL: " << now_time.TN()
+ << " tn OTHER: " << target_tn << std::endl;
+ return;
+ }
+
+ int64_t send_ts = now_ts + tosend.FN() * 8 * ONE_TS_BURST_LEN + tosend.TN() * ONE_TS_BURST_LEN - timing_advance;
+#ifdef DBGXX
+ auto check = now_time + tosend;
+ std::cerr << "## fn DIFF: " << diff_fn << " ## tn DIFF: " << diff_tn << " tn LOCAL/OTHER: " << now_time.TN()
+ << "/" << target_tn << " tndiff" << diff_tn << " tosend:" << tosend.FN() << ":" << tosend.TN()
+ << " check: " << check.FN() << ":" << check.TN() << " target: " << target.FN() << ":" << target.TN()
+ << " ts now: " << now_ts << " target ts:" << send_ts << std::endl;
+#endif
+#if 0
+ auto check = now_time + tosend;
+ unsigned int pad = 4 * 4;
+ blade_sample_type buf2[len + pad];
+ std::fill(buf2, buf2 + pad, 0);
+ memcpy(&buf2[pad], buffer, len * sizeof(blade_sample_type));
+
+ assert(target.FN() == check.FN());
+ assert(target.TN() == check.TN());
+ submit_burst_ts(buf2, len + pad, send_ts - pad);
+#else
+ submit_burst_ts(buffer, len, send_ts);
+#endif
+}
diff --git a/Transceiver52M/ms/ms.h b/Transceiver52M/ms/ms.h
new file mode 100644
index 0000000..2ad78de
--- /dev/null
+++ b/Transceiver52M/ms/ms.h
@@ -0,0 +1,301 @@
+#pragma once
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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 <atomic>
+#include <cassert>
+#include <complex>
+#include <cstdint>
+#include <mutex>
+#include <iostream>
+// #include <thread>
+
+#if defined(BUILDBLADE)
+#include "bladerf_specific.h"
+#define BASET blade_hw<ms_trx>
+#elif defined(BUILDUHD)
+#include "uhd_specific.h"
+#define BASET uhd_hw<ms_trx>
+#else
+#error wat? no device..
+#endif
+
+#include "Complex.h"
+#include "GSMCommon.h"
+#include "itrq.h"
+#include "threadpool.h"
+#include "threadsched.h"
+
+const unsigned int ONE_TS_BURST_LEN = (3 + 58 + 26 + 58 + 3 + 8.25) * 4 /*sps*/;
+const unsigned int SCH_LEN_SPS = (ONE_TS_BURST_LEN * 8 /*ts*/ * 12 /*frames*/);
+
+template <typename T>
+void clamp_array(T *start2, unsigned int len, T max)
+{
+ for (unsigned int i = 0; i < len; i++) {
+ const T t1 = start2[i] < -max ? -max : start2[i];
+ const T t2 = t1 > max ? max : t1;
+ start2[i] = t2;
+ }
+}
+
+namespace cvt_internal
+{
+
+template <typename SRC_T, typename ST>
+void convert_and_scale_i(float *dst, const SRC_T *src, unsigned int src_len, ST scale)
+{
+ for (unsigned int i = 0; i < src_len; i++)
+ dst[i] = static_cast<float>(src[i]) * scale;
+}
+
+template <typename DST_T, typename ST>
+void convert_and_scale_i(DST_T *dst, const float *src, unsigned int src_len, ST scale)
+{
+ for (unsigned int i = 0; i < src_len; i++)
+ dst[i] = static_cast<DST_T>(src[i] * scale);
+}
+
+template <typename ST>
+void convert_and_scale_i(float *dst, const float *src, unsigned int src_len, ST scale)
+{
+ for (unsigned int i = 0; i < src_len; i++)
+ dst[i] = src[i] * scale;
+}
+
+template <typename T>
+struct is_complex : std::false_type {
+ using baset = T;
+ static const unsigned int len_mul = 1;
+};
+
+template <typename T>
+struct is_complex<std::complex<T>> : std::true_type {
+ using baset = typename std::complex<T>::value_type;
+ static const unsigned int len_mul = 2;
+};
+
+template <typename T>
+struct is_complex<Complex<T>> : std::true_type {
+ using baset = typename Complex<T>::value_type;
+ static const unsigned int len_mul = 2;
+};
+
+} // namespace cvt_internal
+
+template <typename DST_T, typename SRC_T, typename ST>
+void convert_and_scale(DST_T *dst, const SRC_T *src, unsigned int src_len, ST scale)
+{
+ using vd = typename cvt_internal::is_complex<DST_T>::baset;
+ using vs = typename cvt_internal::is_complex<SRC_T>::baset;
+ return cvt_internal::convert_and_scale_i((vd *)dst, (vs *)src, src_len, scale);
+}
+
+template <typename array_t>
+float normed_abs_sum(array_t *src, int len)
+{
+ using vd = typename cvt_internal::is_complex<array_t>::baset;
+ auto len_mul = cvt_internal::is_complex<array_t>::len_mul;
+ auto ptr = reinterpret_cast<const vd *>(src);
+ float sum = 0;
+ for (unsigned int i = 0; i < len * len_mul; i++)
+ sum += std::abs(ptr[i]);
+ sum /= len * len_mul;
+ return sum;
+}
+
+struct one_burst {
+ one_burst()
+ {
+ }
+ GSM::Time gsmts;
+ union {
+ blade_sample_type burst[ONE_TS_BURST_LEN];
+ char sch_bits[148];
+ };
+};
+
+using rx_queue_t = spsc_cond_timeout<4, one_burst, true, false>;
+
+enum class SCH_STATE { SEARCHING, FOUND };
+
+class dummylog : private std::streambuf {
+ std::ostream null_stream;
+
+ public:
+ dummylog() : null_stream(this){};
+ ~dummylog() override{};
+ std::ostream &operator()()
+ {
+ return null_stream;
+ }
+ int overflow(int c) override
+ {
+ return c;
+ }
+};
+
+// keeps relationship between gsm time and (continuously adjusted) ts
+class time_keeper {
+ GSM::Time global_time_keeper;
+ int64_t global_ts_keeper;
+ std::mutex m;
+
+ public:
+ time_keeper() : global_time_keeper(0), global_ts_keeper(0)
+ {
+ }
+
+ void set(GSM::Time t, int64_t ts)
+ {
+ std::lock_guard<std::mutex> g(m);
+ global_time_keeper = t;
+ global_ts_keeper = ts;
+ }
+ void inc_both()
+ {
+ std::lock_guard<std::mutex> g(m);
+ global_time_keeper.incTN(1);
+ global_ts_keeper += ONE_TS_BURST_LEN;
+ }
+ void inc_and_update(int64_t new_ts)
+ {
+ std::lock_guard<std::mutex> g(m);
+ global_time_keeper.incTN(1);
+ global_ts_keeper = new_ts;
+ // std::cerr << "u " << new_ts << std::endl;
+ }
+ void inc_and_update_safe(int64_t new_ts)
+ {
+ std::lock_guard<std::mutex> g(m);
+ auto diff = new_ts - global_ts_keeper;
+ assert(diff < 1.5 * ONE_TS_BURST_LEN);
+ assert(diff > 0.5 * ONE_TS_BURST_LEN);
+ global_time_keeper.incTN(1);
+ global_ts_keeper = new_ts;
+ // std::cerr << "s " << new_ts << std::endl;
+ }
+ void dec_by_one()
+ {
+ std::lock_guard<std::mutex> g(m);
+ global_time_keeper.decTN(1);
+ global_ts_keeper -= ONE_TS_BURST_LEN;
+ }
+ auto get_ts()
+ {
+ std::lock_guard<std::mutex> g(m);
+ return global_ts_keeper;
+ }
+ auto gsmtime()
+ {
+ std::lock_guard<std::mutex> g(m);
+ return global_time_keeper;
+ }
+ void get_both(GSM::Time *t, int64_t *ts)
+ {
+ std::lock_guard<std::mutex> g(m);
+ *t = global_time_keeper;
+ *ts = global_ts_keeper;
+ }
+};
+
+using ts_hitter_q_t = spsc_cond<64, GSM::Time, true, false>;
+
+// used to globally initialize the sched/hw information
+struct sched_hw_info {
+ int hw_cpus;
+ sched_params::target hw_target;
+
+ sched_hw_info()
+ {
+ hw_cpus = std::thread::hardware_concurrency();
+ hw_target = hw_cpus > 4 ? sched_params::target::ODROID : sched_params::target::PI4;
+ set_sched_target(hw_target);
+ std::cerr << "scheduling for: " << (hw_cpus > 4 ? "odroid" : "pi4") << std::endl;
+ }
+};
+
+struct ms_trx : public BASET, public sched_hw_info {
+ using base = BASET;
+ static dummylog dummy_log;
+ unsigned int mTSC;
+ unsigned int mBSIC;
+ int timing_advance;
+ bool do_auto_gain;
+
+ pthread_t lower_rx_task;
+ pthread_t lower_tx_task;
+
+ // provides bursts to upper rx thread
+ rx_queue_t rxqueue;
+
+ blade_sample_type *first_sch_buf;
+ blade_sample_type *burst_copy_buffer;
+
+ uint64_t first_sch_buf_rcv_ts;
+ std::atomic<bool> rcv_done;
+ std::atomic<bool> sch_thread_done;
+
+ int64_t temp_ts_corr_offset = 0;
+ int64_t first_sch_ts_start = -1;
+
+ time_keeper timekeeper;
+ single_thread_pool worker_thread; // uses base class sched target hw info
+
+ void start_lower_ms();
+ std::atomic<bool> upper_is_ready;
+ void set_upper_ready(bool is_ready);
+
+ bool handle_sch_or_nb();
+ bool handle_sch(bool first = false);
+ bool decode_sch(char *bits, bool update_global_clock);
+ SCH_STATE search_for_sch(dev_buf_t *rcd);
+ void grab_bursts(dev_buf_t *rcd);
+
+ int init_dev_and_streams();
+ void stop_threads();
+ void *rx_cb(ms_trx *t);
+ void *tx_cb();
+ void maybe_update_gain(one_burst &brst);
+
+ ms_trx()
+ : mTSC(0), mBSIC(0), timing_advance(0), do_auto_gain(false), rxqueue(),
+ first_sch_buf(new blade_sample_type[SCH_LEN_SPS]),
+ burst_copy_buffer(new blade_sample_type[ONE_TS_BURST_LEN]), first_sch_buf_rcv_ts(0),
+ rcv_done{ false }, sch_thread_done{ false }, upper_is_ready(false)
+ {
+ }
+
+ virtual ~ms_trx()
+ {
+ delete[] burst_copy_buffer;
+ delete[] first_sch_buf;
+ }
+ bh_fn_t rx_bh();
+ bh_fn_t tx_bh();
+
+ void submit_burst(blade_sample_type *buffer, int len, GSM::Time);
+ void set_ta(int val)
+ {
+ assert(val > -127 && val < 128);
+ timing_advance = val * 4;
+ }
+};
diff --git a/Transceiver52M/ms/ms_rx_lower.cpp b/Transceiver52M/ms/ms_rx_lower.cpp
new file mode 100644
index 0000000..d894e96
--- /dev/null
+++ b/Transceiver52M/ms/ms_rx_lower.cpp
@@ -0,0 +1,420 @@
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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 "sigProcLib.h"
+#include "signalVector.h"
+#include <atomic>
+#include <cassert>
+#include <complex>
+#include <iostream>
+#include <future>
+
+#include "ms.h"
+#include "grgsm_vitac/grgsm_vitac.h"
+
+#include "threadpool.h"
+
+extern "C" {
+#include "sch.h"
+}
+
+#ifdef LOG
+#undef LOG
+#endif
+
+#if !defined(NODAMNLOG)
+#define DBGLG(...) ms_trx::dummy_log()
+#else
+#define DBGLG(...) std::cerr
+#endif
+
+#if !defined(NODAMNLOG)
+#define DBGLG2(...) ms_trx::dummy_log()
+#else
+#define DBGLG2(...) std::cerr
+#endif
+
+#define PRINT_Q_OVERFLOW
+
+bool ms_trx::decode_sch(char *bits, bool update_global_clock)
+{
+ int fn;
+ struct sch_info sch;
+ ubit_t info[GSM_SCH_INFO_LEN];
+ sbit_t data[GSM_SCH_CODED_LEN];
+
+ memcpy(&data[0], &bits[3], 39);
+ memcpy(&data[39], &bits[106], 39);
+
+ if (!gsm_sch_decode(info, data)) {
+ gsm_sch_parse(info, &sch);
+
+ if (update_global_clock) {
+ DBGLG() << "SCH : Decoded values" << std::endl;
+ DBGLG() << " BSIC: " << sch.bsic << std::endl;
+ DBGLG() << " TSC: " << (sch.bsic & 0x7) << std::endl;
+ DBGLG() << " T1 : " << sch.t1 << std::endl;
+ DBGLG() << " T2 : " << sch.t2 << std::endl;
+ DBGLG() << " T3p : " << sch.t3p << std::endl;
+ DBGLG() << " FN : " << gsm_sch_to_fn(&sch) << std::endl;
+ }
+
+ fn = gsm_sch_to_fn(&sch);
+ if (fn < 0) { // how? wh?
+ DBGLG() << "SCH : Failed to convert FN " << std::endl;
+ return false;
+ }
+
+ if (update_global_clock) {
+ mBSIC = sch.bsic;
+ mTSC = sch.bsic & 0x7;
+ timekeeper.set(fn, 0);
+ // global_time_keeper.FN(fn);
+ // global_time_keeper.TN(0);
+ }
+
+ return true;
+ }
+ return false;
+}
+
+void ms_trx::maybe_update_gain(one_burst &brst)
+{
+ static_assert((sizeof(brst.burst) / sizeof(brst.burst[0])) == ONE_TS_BURST_LEN, "wtf, buffer size mismatch?");
+ const int avgburst_num = 8 * 20; // ~ 50*4.5ms = 90ms?
+ static_assert(avgburst_num * 577 > (50 * 1000), "can't update faster then blade wait time?");
+ const unsigned int rx_max_cutoff = (rxFullScale * 2) / 3;
+ static int gain_check = 0;
+ static float runmean = 0;
+ float sum = normed_abs_sum(&brst.burst[0], ONE_TS_BURST_LEN);
+ runmean = gain_check ? (runmean * (gain_check + 2) - 1 + sum) / (gain_check + 2) : sum;
+
+ if (gain_check == avgburst_num - 1) {
+ DBGLG2() << "\x1B[32m #RXG \033[0m" << rxgain << " " << runmean << " " << sum << std::endl;
+ auto gainoffset = runmean < (rxFullScale / 4 ? 4 : 2);
+ gainoffset = runmean < (rxFullScale / 2 ? 2 : 1);
+ float newgain = runmean < rx_max_cutoff ? rxgain + gainoffset : rxgain - gainoffset;
+ // FIXME: gian cutoff
+ if (newgain != rxgain && newgain <= 60) {
+ auto gain_fun = [this, newgain] { setRxGain(newgain); };
+ worker_thread.add_task(gain_fun);
+ }
+
+ runmean = 0;
+ }
+ gain_check = (gain_check + 1) % avgburst_num;
+}
+
+static char sch_demod_bits[148];
+
+bool ms_trx::handle_sch_or_nb()
+{
+ one_burst brst;
+ const auto current_gsm_time = timekeeper.gsmtime();
+ const auto is_sch = gsm_sch_check_ts(current_gsm_time.TN(), current_gsm_time.FN());
+
+ //either pass burst to upper layer for demod, OR pass demodded SCH to upper layer so we don't waste time processing it twice
+ brst.gsmts = current_gsm_time;
+
+ if (!is_sch) {
+ memcpy(brst.burst, burst_copy_buffer, sizeof(blade_sample_type) * ONE_TS_BURST_LEN);
+ } else {
+ handle_sch(false);
+ memcpy(brst.sch_bits, sch_demod_bits, sizeof(sch_demod_bits));
+ }
+
+ while (upper_is_ready && !rxqueue.spsc_push(&brst))
+ ;
+
+ if (do_auto_gain)
+ maybe_update_gain(brst);
+
+ return false;
+}
+
+static float sch_acq_buffer[SCH_LEN_SPS * 2];
+
+bool ms_trx::handle_sch(bool is_first_sch_acq)
+{
+ auto current_gsm_time = timekeeper.gsmtime();
+ const auto buf_len = is_first_sch_acq ? SCH_LEN_SPS : ONE_TS_BURST_LEN;
+ const auto which_in_buffer = is_first_sch_acq ? first_sch_buf : burst_copy_buffer;
+ memset((void *)&sch_acq_buffer[0], 0, sizeof(sch_acq_buffer));
+#if 1
+ const auto which_out_buffer = is_first_sch_acq ? sch_acq_buffer : &sch_acq_buffer[40 * 2];
+ const auto ss = reinterpret_cast<std::complex<float> *>(which_out_buffer);
+ std::complex<float> channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
+ int start;
+ convert_and_scale(which_out_buffer, which_in_buffer, buf_len * 2, 1.f / float(rxFullScale));
+ if (is_first_sch_acq) {
+ float max_corr = 0;
+ start = get_sch_buffer_chan_imp_resp(ss, &channel_imp_resp[0], buf_len, &max_corr);
+ } else {
+ start = get_sch_chan_imp_resp(ss, &channel_imp_resp[0]);
+ start = start < 39 ? start : 39;
+ start = start > -39 ? start : -39;
+ }
+ detect_burst_nb(&ss[start], &channel_imp_resp[0], 0, sch_demod_bits);
+
+ auto sch_decode_success = decode_sch(sch_demod_bits, is_first_sch_acq);
+#if 0
+ auto burst = new signalVector(buf_len, 50);
+ const auto corr_type = is_first_sch_acq ? sch_detect_type::SCH_DETECT_BUFFER : sch_detect_type::SCH_DETECT_FULL;
+ struct estim_burst_params ebp;
+
+ // scale like uhd, +-2k -> +-32k
+ convert_and_scale(burst->begin(), which_in_buffer, buf_len * 2, SAMPLE_SCALE_FACTOR);
+
+ auto rv = detectSCHBurst(*burst, 4, 4, corr_type, &ebp);
+
+ int howmuchdelay = ebp.toa * 4;
+ std::cerr << "ooffs: " << howmuchdelay << " " << std::endl;
+ std::cerr << "voffs: " << start << " " << sch_decode_success << std::endl;
+#endif
+ if (sch_decode_success) {
+ const auto ts_offset_symb = 4;
+ if (is_first_sch_acq) {
+ // update ts to first sample in sch buffer, to allow delay calc for current ts
+ first_sch_ts_start = first_sch_buf_rcv_ts + start - (ts_offset_symb * 4) - 1;
+ } else if (abs(start) > 1) {
+ // continuous sch tracking, only update if off too much
+ temp_ts_corr_offset += -start;
+ std::cerr << "offs: " << start << " " << temp_ts_corr_offset << std::endl;
+ }
+
+ return true;
+ } else {
+ DBGLG2() << "L SCH : \x1B[31m decode fail \033[0m @ toa:" << start << " " << current_gsm_time.FN()
+ << ":" << current_gsm_time.TN() << std::endl;
+ }
+#else
+ const auto ts_offset_symb = 4;
+ auto burst = new signalVector(buf_len, 50);
+ const auto corr_type = is_first_sch_acq ? sch_detect_type::SCH_DETECT_BUFFER : sch_detect_type::SCH_DETECT_FULL;
+ struct estim_burst_params ebp;
+
+ // scale like uhd, +-2k -> +-32k
+ convert_and_scale(burst->begin(), which_in_buffer, buf_len * 2, SAMPLE_SCALE_FACTOR);
+
+ auto rv = detectSCHBurst(*burst, 4, 4, corr_type, &ebp);
+
+ int howmuchdelay = ebp.toa * 4;
+
+ if (!rv) {
+ delete burst;
+ DBGLG() << "SCH : \x1B[31m detect fail \033[0m NOOOOOOOOOOOOOOOOOO toa:" << ebp.toa << " "
+ << current_gsm_time.FN() << ":" << current_gsm_time.TN() << std::endl;
+ return false;
+ }
+
+ SoftVector *bits;
+ if (is_first_sch_acq) {
+ // can't be legit with a buf size spanning _at least_ one SCH but delay that implies partial sch burst
+ if (howmuchdelay < 0 || (buf_len - howmuchdelay) < ONE_TS_BURST_LEN) {
+ delete burst;
+ return false;
+ }
+
+ struct estim_burst_params ebp2;
+ // auto sch_chunk = new signalVector(ONE_TS_BURST_LEN, 50);
+ // auto sch_chunk_start = sch_chunk->begin();
+ // memcpy(sch_chunk_start, sch_buf_f.data() + howmuchdelay, sizeof(std::complex<float>) * ONE_TS_BURST_LEN);
+
+ auto delay = delayVector(burst, NULL, -howmuchdelay);
+
+ scaleVector(*delay, (complex)1.0 / ebp.amp);
+
+ auto rv2 = detectSCHBurst(*delay, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp2);
+ DBGLG() << "FIRST SCH : " << (rv2 ? "yes " : " ") << "Timing offset " << ebp2.toa << " symbols"
+ << std::endl;
+
+ bits = demodAnyBurst(*delay, SCH, 4, &ebp2);
+ delete delay;
+ } else {
+ bits = demodAnyBurst(*burst, SCH, 4, &ebp);
+ }
+
+ delete burst;
+
+ // clamp to +-1.5 because +-127 softbits scaled by 64 after -0.5 can be at most +-1.5
+ clamp_array(bits->begin(), 148, 1.5f);
+
+ float_to_sbit(&bits->begin()[0], (signed char *)&sch_demod_bits[0], 62, 148);
+ // float_to_sbit(&bits->begin()[106], &data[39], 62, 39);
+
+ if (decode_sch((char *)sch_demod_bits, is_first_sch_acq)) {
+ auto current_gsm_time_updated = timekeeper.gsmtime();
+ if (is_first_sch_acq) {
+ // update ts to first sample in sch buffer, to allow delay calc for current ts
+ first_sch_ts_start = first_sch_buf_rcv_ts + howmuchdelay - (ts_offset_symb * 4);
+ } else {
+ // continuous sch tracking, only update if off too much
+ auto diff = [](float x, float y) { return x > y ? x - y : y - x; };
+
+ auto d = diff(ebp.toa, ts_offset_symb);
+ if (abs(d) > 0.3) {
+ if (ebp.toa < ts_offset_symb)
+ ebp.toa = d;
+ else
+ ebp.toa = -d;
+ temp_ts_corr_offset += ebp.toa * 4;
+
+ DBGLG() << "offs: " << ebp.toa << " " << temp_ts_corr_offset << std::endl;
+ }
+ }
+
+ auto a = gsm_sch_check_fn(current_gsm_time_updated.FN() - 1);
+ auto b = gsm_sch_check_fn(current_gsm_time_updated.FN());
+ auto c = gsm_sch_check_fn(current_gsm_time_updated.FN() + 1);
+ DBGLG() << "L SCH : Timing offset " << rv << " " << ebp.toa << " " << a << b << c << "fn "
+ << current_gsm_time_updated.FN() << ":" << current_gsm_time_updated.TN() << std::endl;
+
+ delete bits;
+ return true;
+ } else {
+ DBGLG2() << "L SCH : \x1B[31m decode fail \033[0m @ toa:" << ebp.toa << " " << current_gsm_time.FN()
+ << ":" << current_gsm_time.TN() << std::endl;
+ }
+
+ delete bits;
+#endif
+ return false;
+}
+
+/*
+accumulates a full big buffer consisting of 8*12 timeslots, then:
+either
+1) adjusts gain if necessary and starts over
+2) searches and finds SCH and is done
+*/
+SCH_STATE ms_trx::search_for_sch(dev_buf_t *rcd)
+{
+ static unsigned int sch_pos = 0;
+ auto to_copy = SCH_LEN_SPS - sch_pos;
+
+ if (sch_thread_done)
+ return SCH_STATE::FOUND;
+
+ if (rcv_done)
+ return SCH_STATE::SEARCHING;
+
+ if (sch_pos == 0) // keep first ts for time delta calc
+ first_sch_buf_rcv_ts = rcd->get_first_ts();
+
+ if (to_copy) {
+ auto spsmax = rcd->actual_samples_per_buffer();
+ if (to_copy > (unsigned int)spsmax)
+ sch_pos += rcd->readall(first_sch_buf + sch_pos);
+ else
+ sch_pos += rcd->read_n(first_sch_buf + sch_pos, 0, to_copy);
+ } else { // (!to_copy)
+ sch_pos = 0;
+ rcv_done = true;
+ auto sch_search_fun = [this] {
+ const auto target_val = rxFullScale / 8;
+ float sum = normed_abs_sum(first_sch_buf, SCH_LEN_SPS);
+
+ //FIXME: arbitrary value, gain cutoff
+ if (sum > target_val || rxgain >= 60) // enough ?
+ sch_thread_done = this->handle_sch(true);
+ else {
+ std::cerr << "\x1B[32m #RXG \033[0m gain " << rxgain << " -> " << rxgain + 4
+ << " sample avg:" << sum << " target: >=" << target_val << std::endl;
+ setRxGain(rxgain + 4);
+ }
+
+ if (!sch_thread_done)
+ rcv_done = false; // retry!
+ };
+ worker_thread.add_task(sch_search_fun);
+ }
+ return SCH_STATE::SEARCHING;
+}
+
+void ms_trx::grab_bursts(dev_buf_t *rcd)
+{
+ // partial burst samples read from the last buffer
+ static int partial_rdofs = 0;
+ static bool first_call = true;
+ int to_skip = 0;
+
+ // round up to next burst by calculating the time between sch detection and now
+ if (first_call) {
+ const auto next_burst_start = rcd->get_first_ts() - first_sch_ts_start;
+ const auto fullts = next_burst_start / ONE_TS_BURST_LEN;
+ const auto fracts = next_burst_start % ONE_TS_BURST_LEN;
+ to_skip = ONE_TS_BURST_LEN - fracts;
+
+ for (unsigned int i = 0; i < fullts; i++)
+ timekeeper.inc_and_update(first_sch_ts_start + i * ONE_TS_BURST_LEN);
+
+ if (fracts)
+ timekeeper.inc_both();
+ // timekeeper.inc_and_update(first_sch_ts_start + 1 * ONE_TS_BURST_LEN);
+
+ timekeeper.dec_by_one(); // oops, off by one?
+
+ timekeeper.set(timekeeper.gsmtime(), rcd->get_first_ts() - ONE_TS_BURST_LEN + to_skip);
+
+ DBGLG() << "this ts: " << rcd->get_first_ts() << " diff full TN: " << fullts << " frac TN: " << fracts
+ << " GSM now: " << timekeeper.gsmtime().FN() << ":" << timekeeper.gsmtime().TN() << " is sch? "
+ << gsm_sch_check_fn(timekeeper.gsmtime().FN()) << std::endl;
+ first_call = false;
+ }
+
+ if (partial_rdofs) {
+ auto first_remaining = ONE_TS_BURST_LEN - partial_rdofs;
+ auto rd = rcd->read_n(burst_copy_buffer + partial_rdofs, 0, first_remaining);
+ if (rd != (int)first_remaining) {
+ partial_rdofs += rd;
+ return;
+ }
+
+ timekeeper.inc_and_update_safe(rcd->get_first_ts() - partial_rdofs);
+ handle_sch_or_nb();
+ to_skip = first_remaining;
+ }
+
+ // apply sample rate slippage compensation
+ to_skip -= temp_ts_corr_offset;
+
+ // FIXME: happens rarely, read_n start -1 blows up
+ // this is fine: will just be corrected one buffer later
+ if (to_skip < 0)
+ to_skip = 0;
+ else
+ temp_ts_corr_offset = 0;
+
+ const auto left_after_burst = rcd->actual_samples_per_buffer() - to_skip;
+
+ const int full = left_after_burst / ONE_TS_BURST_LEN;
+ const int frac = left_after_burst % ONE_TS_BURST_LEN;
+
+ for (int i = 0; i < full; i++) {
+ rcd->read_n(burst_copy_buffer, to_skip + i * ONE_TS_BURST_LEN, ONE_TS_BURST_LEN);
+ timekeeper.inc_and_update_safe(rcd->get_first_ts() + to_skip + i * ONE_TS_BURST_LEN);
+ handle_sch_or_nb();
+ }
+
+ if (frac)
+ rcd->read_n(burst_copy_buffer, to_skip + full * ONE_TS_BURST_LEN, frac);
+ partial_rdofs = frac;
+}
diff --git a/Transceiver52M/ms/ms_trxcon_if.cpp b/Transceiver52M/ms/ms_trxcon_if.cpp
new file mode 100644
index 0000000..7de9710
--- /dev/null
+++ b/Transceiver52M/ms/ms_trxcon_if.cpp
@@ -0,0 +1,78 @@
+/*
+ * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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 <atomic>
+#include "ms_trxcon_if.h"
+extern "C" {
+#include <osmocom/bb/trxcon/trxcon.h>
+#include <osmocom/bb/trxcon/l1ctl_server.h>
+#include <osmocom/core/panic.h>
+}
+
+extern tx_queue_t txq;
+extern cmd_queue_t cmdq_to_phy;
+extern cmdr_queue_t cmdq_from_phy;
+extern std::atomic<bool> g_exit_flag;
+// trxcon C call(back) if
+extern "C" {
+int trxcon_phyif_handle_burst_req(void *phyif, const struct trxcon_phyif_burst_req *br)
+{
+ if (br->burst_len == 0) // dummy/nope
+ return 0;
+ OSMO_ASSERT(br->burst != 0);
+
+ internal_q_tx_buf b(br);
+ if (!g_exit_flag)
+ txq.spsc_push(&b);
+ return 0;
+}
+
+int trxcon_phyif_handle_cmd(void *phyif, const struct trxcon_phyif_cmd *cmd)
+{
+#ifdef TXDEBUG
+ DBGLG() << "TOP C: " << cmd2str(cmd->type) << std::endl;
+#endif
+ if (!g_exit_flag)
+ cmdq_to_phy.spsc_push(cmd);
+ // q for resp polling happens in main loop
+ return 0;
+}
+
+void trxcon_phyif_close(void *phyif)
+{
+}
+
+void trxcon_l1ctl_close(struct trxcon_inst *trxcon)
+{
+ /* Avoid use-after-free: both *fi and *trxcon are children of
+ * the L2IF (L1CTL connection), so we need to re-parent *fi
+ * to NULL before calling l1ctl_client_conn_close(). */
+ talloc_steal(NULL, trxcon->fi);
+ l1ctl_client_conn_close((struct l1ctl_client *)trxcon->l2if);
+}
+
+int trxcon_l1ctl_send(struct trxcon_inst *trxcon, struct msgb *msg)
+{
+ struct l1ctl_client *l1c = (struct l1ctl_client *)trxcon->l2if;
+
+ return l1ctl_client_send(l1c, msg);
+}
+}
diff --git a/Transceiver52M/ms/ms_trxcon_if.h b/Transceiver52M/ms/ms_trxcon_if.h
new file mode 100644
index 0000000..0928d40
--- /dev/null
+++ b/Transceiver52M/ms/ms_trxcon_if.h
@@ -0,0 +1,42 @@
+#pragma once
+/*
+ * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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 "ms.h"
+extern "C" {
+#include <osmocom/bb/trxcon/phyif.h>
+}
+
+extern struct trxcon_inst *g_trxcon;
+struct internal_q_tx_buf {
+ trxcon_phyif_burst_req r;
+ uint8_t buf[148];
+ internal_q_tx_buf() = default;
+ internal_q_tx_buf(const internal_q_tx_buf &) = delete;
+ internal_q_tx_buf &operator=(const internal_q_tx_buf &) = default;
+ internal_q_tx_buf(const struct trxcon_phyif_burst_req *br) : r(*br)
+ {
+ memcpy(buf, (void *)br->burst, br->burst_len);
+ }
+};
+using tx_queue_t = spsc_cond<8 * 1, internal_q_tx_buf, true, false>;
+using cmd_queue_t = spsc_cond_timeout<8 * 1, trxcon_phyif_cmd, true, false>;
+using cmdr_queue_t = spsc_cond<8 * 1, trxcon_phyif_rsp, false, false>;
diff --git a/Transceiver52M/ms/ms_upper.cpp b/Transceiver52M/ms/ms_upper.cpp
new file mode 100644
index 0000000..2e8bc11
--- /dev/null
+++ b/Transceiver52M/ms/ms_upper.cpp
@@ -0,0 +1,512 @@
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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 <csignal>
+#include "sigProcLib.h"
+#include "ms.h"
+#include <signalVector.h>
+#include <radioVector.h>
+#include <radioInterface.h>
+#include <grgsm_vitac/grgsm_vitac.h>
+
+// #define TXDEBUG
+
+extern "C" {
+
+#include "sch.h"
+#include "convolve.h"
+#include "convert.h"
+
+#include <osmocom/core/application.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/bb/trxcon/trxcon.h>
+#include <osmocom/bb/trxcon/trxcon_fsm.h>
+#include <osmocom/bb/trxcon/l1ctl_server.h>
+
+extern void trxc_log_init(void *tallctx);
+#ifdef LSANDEBUG
+void __lsan_do_recoverable_leak_check();
+#endif
+}
+
+#include "ms_trxcon_if.h"
+#include "ms_upper.h"
+#include "threadsched.h"
+
+extern bool trxc_l1ctl_init(void *tallctx);
+struct trxcon_inst *g_trxcon;
+tx_queue_t txq;
+cmd_queue_t cmdq_to_phy;
+cmdr_queue_t cmdq_from_phy;
+
+#ifdef LOG
+#undef LOG
+#define LOG(...) upper_trx::dummy_log()
+#endif
+
+#define DBGLG(...) upper_trx::dummy_log()
+
+std::atomic<bool> g_exit_flag;
+
+void upper_trx::stop_upper_threads()
+{
+ g_exit_flag = true;
+
+ pthread_join(thr_control, NULL);
+ pthread_join(thr_tx, NULL);
+}
+
+void upper_trx::start_threads()
+{
+ DBGLG(...) << "spawning threads.." << std::endl;
+
+ thr_control = spawn_worker_thread(
+ sched_params::thread_names::U_CTL,
+ [](void *args) -> void * {
+ upper_trx *t = reinterpret_cast<upper_trx *>(args);
+#ifdef TXDEBUG
+ struct sched_param param;
+ int policy;
+ pthread_getschedparam(pthread_self(), &policy, &param);
+ printf("ID: %lu, CPU: %d policy = %d priority = %d\n", pthread_self(), sched_getcpu(), policy,
+ param.sched_priority);
+#endif
+ std::cerr << "started U control!" << std::endl;
+ while (!g_exit_flag) {
+ t->driveControl();
+ }
+ std::cerr << "exit U control!" << std::endl;
+
+ return 0;
+ },
+ this);
+ thr_tx = spawn_worker_thread(
+ sched_params::thread_names::U_TX,
+ [](void *args) -> void * {
+ upper_trx *t = reinterpret_cast<upper_trx *>(args);
+#ifdef TXDEBUG
+ struct sched_param param;
+ int policy;
+ pthread_getschedparam(pthread_self(), &policy, &param);
+ printf("ID: %lu, CPU: %d policy = %d priority = %d\n", pthread_self(), sched_getcpu(), policy,
+ param.sched_priority);
+#endif
+ std::cerr << "started U tx!" << std::endl;
+ while (!g_exit_flag) {
+ t->driveTx();
+ }
+ std::cerr << "exit U tx!" << std::endl;
+
+ return 0;
+ },
+ this);
+
+#ifdef LSANDEBUG
+ std::thread([this] {
+ set_name_aff_sched(sched_params::thread_names::LEAKCHECK);
+
+ while (1) {
+ std::this_thread::sleep_for(std::chrono::seconds{ 5 });
+ __lsan_do_recoverable_leak_check();
+ }
+ }).detach();
+#endif
+}
+
+void upper_trx::main_loop()
+{
+ set_name_aff_sched(sched_params::thread_names::U_RX);
+ set_upper_ready(true);
+ while (!g_exit_flag) {
+ driveReceiveFIFO();
+ osmo_select_main(1);
+
+ trxcon_phyif_rsp r;
+ if (cmdq_from_phy.spsc_pop(&r)) {
+ DBGLG() << "HAVE RESP:" << r.type << std::endl;
+ trxcon_phyif_handle_rsp(g_trxcon, &r);
+ }
+ }
+ set_upper_ready(false);
+ std::cerr << "exit U rx!" << std::endl;
+ mOn = false;
+}
+
+// signalvector is owning despite claiming not to, but we can pretend, too..
+static void static_free(void *wData){};
+static void *static_alloc(size_t newSize)
+{
+ return 0;
+};
+
+bool upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset)
+{
+ float pow, avg = 1.0;
+ const auto zero_pad_len = 40; // give the VA some runway for misaligned bursts
+ const auto workbuf_size = zero_pad_len + ONE_TS_BURST_LEN + zero_pad_len;
+ static complex workbuf[workbuf_size];
+
+ static signalVector sv(workbuf, zero_pad_len, ONE_TS_BURST_LEN, static_alloc, static_free);
+ one_burst e;
+ auto ss = reinterpret_cast<std::complex<float> *>(&workbuf[zero_pad_len]);
+ std::fill(workbuf, workbuf + workbuf_size, 0);
+ // assert(sv.begin() == &workbuf[40]);
+
+ while (!rxqueue.spsc_pop(&e)) {
+ rxqueue.spsc_prep_pop();
+ }
+
+ wTime = e.gsmts;
+
+ const auto is_sch = gsm_sch_check_ts(wTime.TN(), wTime.FN());
+ const auto is_fcch = gsm_fcch_check_ts(wTime.TN(), wTime.FN());
+
+ trxcon_phyif_rtr_ind i = { static_cast<uint32_t>(wTime.FN()), static_cast<uint8_t>(wTime.TN()) };
+ trxcon_phyif_rtr_rsp r = {};
+ trxcon_phyif_handle_rtr_ind(g_trxcon, &i, &r);
+ if (!(r.flags & TRXCON_PHYIF_RTR_F_ACTIVE))
+ return false;
+
+ if (is_fcch) {
+ // return trash
+ return true;
+ }
+
+ if (is_sch) {
+ for (int i = 0; i < 148; i++)
+ (demodded_softbits)[i] = (e.sch_bits[i]);
+ RSSI = 10;
+ timingOffset = 0;
+ return true;
+ }
+
+#if 1
+ convert_and_scale(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(rxFullScale));
+
+ pow = energyDetect(sv, 20 * 4 /*sps*/);
+ if (pow < -1) {
+ LOG(ALERT) << "Received empty burst";
+ return false;
+ }
+
+ avg = sqrt(pow);
+ {
+ float ncmax;
+ std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
+ auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, mTSC);
+#ifdef DBGXX
+ float dcmax;
+ std::complex<float> chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR];
+ auto dummy_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp2[0], &dcmax, TS_DUMMY);
+ auto is_nb = ncmax > dcmax;
+ // DBGLG() << " U " << (is_nb ? "NB" : "DB") << "@ o nb: " << normal_burst_start
+ // << " o db: " << dummy_burst_start << std::endl;
+#endif
+ normal_burst_start = normal_burst_start < 39 ? normal_burst_start : 39;
+ normal_burst_start = normal_burst_start > -39 ? normal_burst_start : -39;
+#ifdef DBGXX
+ // fprintf(stderr, "%s %d\n", (is_nb ? "N":"D"), burst_time.FN());
+ // if (is_nb)
+#endif
+ detect_burst_nb(ss, &chan_imp_resp[0], normal_burst_start, demodded_softbits);
+#ifdef DBGXX
+ // else
+ // detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin);
+#endif
+ }
+#else
+
+ // lower layer sch detection offset, easy to verify by just printing the detected value using both the va+sigproc code.
+ convert_and_scale(ss + 16, e.burst, ONE_TS_BURST_LEN * 2, 15);
+
+ pow = energyDetect(sv, 20 * 4 /*sps*/);
+ if (pow < -1) {
+ LOG(ALERT) << "Received empty burst";
+ return false;
+ }
+
+ avg = sqrt(pow);
+
+ /* Detect normal or RACH bursts */
+ CorrType type = CorrType::TSC;
+ struct estim_burst_params ebp;
+ auto rc = detectAnyBurst(sv, mTSC, 3, 4, type, 48, &ebp);
+ if (rc > 0) {
+ type = (CorrType)rc;
+ }
+
+ if (rc < 0) {
+ std::cerr << "UR : \x1B[31m rx fail \033[0m @ toa:" << ebp.toa << " " << e.gsmts.FN() << ":"
+ << e.gsmts.TN() << std::endl;
+ return false;
+ }
+ SoftVector *bits = demodAnyBurst(sv, type, 4, &ebp);
+
+ SoftVector::const_iterator burstItr = bits->begin();
+ // invert and fix to +-127 sbits
+ for (int ii = 0; ii < 148; ii++) {
+ demodded_softbits[ii] = *burstItr++ > 0.0f ? -127 : 127;
+ }
+ delete bits;
+
+#endif
+ RSSI = (int)floor(20.0 * log10(rxFullScale / avg));
+ // FIXME: properly handle offset, sch/nb alignment diff? handled by lower anyway...
+ timingOffset = (int)round(0);
+
+ return true;
+}
+
+void upper_trx::driveReceiveFIFO()
+{
+ int RSSI;
+ int TOA; // in 1/256 of a symbol
+ GSM::Time burstTime;
+
+ if (!mOn)
+ return;
+
+ if (pullRadioVector(burstTime, RSSI, TOA)) {
+ trxcon_phyif_burst_ind bi;
+ bi.fn = burstTime.FN();
+ bi.tn = burstTime.TN();
+ bi.rssi = RSSI;
+ bi.toa256 = TOA;
+ bi.burst = (sbit_t *)demodded_softbits;
+ bi.burst_len = sizeof(demodded_softbits);
+ trxcon_phyif_handle_burst_ind(g_trxcon, &bi);
+ }
+
+ burstTime.incTN(2);
+ struct trxcon_phyif_rts_ind rts {
+ static_cast<uint32_t>(burstTime.FN()), static_cast<uint8_t>(burstTime.TN())
+ };
+ trxcon_phyif_handle_rts_ind(g_trxcon, &rts);
+}
+
+void upper_trx::driveTx()
+{
+ internal_q_tx_buf e;
+ static BitVector newBurst(sizeof(e.buf));
+ while (!txq.spsc_pop(&e)) {
+ txq.spsc_prep_pop();
+ }
+
+ // ensure our tx cb is tickled and can exit
+ if (g_exit_flag) {
+ submit_burst_ts(0, 1337, 1);
+ return;
+ }
+
+ internal_q_tx_buf *burst = &e;
+
+#ifdef TXDEBUG2
+ DBGLG() << "got burst!" << burst->r.fn << ":" << burst->ts << " current: " << timekeeper.gsmtime().FN()
+ << " dff: " << (int64_t)((int64_t)timekeeper.gsmtime().FN() - (int64_t)burst->r.fn) << std::endl;
+#endif
+
+ auto currTime = GSM::Time(burst->r.fn, burst->r.tn);
+ int RSSI = (int)burst->r.pwr;
+
+ BitVector::iterator itr = newBurst.begin();
+ auto *bufferItr = burst->buf;
+ while (itr < newBurst.end())
+ *itr++ = *bufferItr++;
+
+ auto txburst = modulateBurst(newBurst, 8 + (currTime.TN() % 4 == 0), 4);
+ scaleVector(*txburst, txFullScale * pow(10, -RSSI / 10));
+
+ // float -> int16
+ blade_sample_type burst_buf[txburst->size()];
+ convert_and_scale(burst_buf, txburst->begin(), txburst->size() * 2, 1);
+#ifdef TXDEBUG2
+ auto check = signalVector(txburst->size(), 40);
+ convert_and_scale(check.begin(), burst_buf, txburst->size() * 2, 1);
+ estim_burst_params ebp;
+ auto d = detectAnyBurst(check, 2, 4, 4, CorrType::RACH, 40, &ebp);
+ if (d)
+ DBGLG() << "RACH D! " << ebp.toa << std::endl;
+ else
+ DBGLG() << "RACH NOOOOOOOOOO D! " << ebp.toa << std::endl;
+
+ // memory read --binary --outfile /tmp/mem.bin &burst_buf[0] --count 2500 --force
+#endif
+ submit_burst(burst_buf, txburst->size(), currTime);
+ delete txburst;
+}
+
+#ifdef TXDEBUG
+static const char *cmd2str(trxcon_phyif_cmd_type c)
+{
+ switch (c) {
+ case TRXCON_PHYIF_CMDT_RESET:
+ return "TRXCON_PHYIF_CMDT_RESET";
+ case TRXCON_PHYIF_CMDT_POWERON:
+ return "TRXCON_PHYIF_CMDT_POWERON";
+ case TRXCON_PHYIF_CMDT_POWEROFF:
+ return "TRXCON_PHYIF_CMDT_POWEROFF";
+ case TRXCON_PHYIF_CMDT_MEASURE:
+ return "TRXCON_PHYIF_CMDT_MEASURE";
+ case TRXCON_PHYIF_CMDT_SETFREQ_H0:
+ return "TRXCON_PHYIF_CMDT_SETFREQ_H0";
+ case TRXCON_PHYIF_CMDT_SETFREQ_H1:
+ return "TRXCON_PHYIF_CMDT_SETFREQ_H1";
+ case TRXCON_PHYIF_CMDT_SETSLOT:
+ return "TRXCON_PHYIF_CMDT_SETSLOT";
+ case TRXCON_PHYIF_CMDT_SETTA:
+ return "TRXCON_PHYIF_CMDT_SETTA";
+ default:
+ return "UNKNOWN COMMAND!";
+ }
+}
+
+static void print_cmd(trxcon_phyif_cmd_type c)
+{
+ DBGLG() << "handling " << cmd2str(c) << std::endl;
+}
+#endif
+
+bool upper_trx::driveControl()
+{
+ trxcon_phyif_rsp r;
+ trxcon_phyif_cmd cmd;
+ while (!cmdq_to_phy.spsc_pop(&cmd)) {
+ cmdq_to_phy.spsc_prep_pop();
+ if (g_exit_flag)
+ return false;
+ }
+
+ if (g_exit_flag)
+ return false;
+
+#ifdef TXDEBUG
+ print_cmd(cmd.type);
+#endif
+
+ switch (cmd.type) {
+ case TRXCON_PHYIF_CMDT_RESET:
+ set_ta(0);
+ break;
+ case TRXCON_PHYIF_CMDT_POWERON:
+ if (!mOn) {
+ mOn = true;
+ start_lower_ms();
+ }
+ break;
+ case TRXCON_PHYIF_CMDT_POWEROFF:
+ break;
+ case TRXCON_PHYIF_CMDT_MEASURE:
+ r.type = trxcon_phyif_cmd_type::TRXCON_PHYIF_CMDT_MEASURE;
+ r.param.measure.band_arfcn = cmd.param.measure.band_arfcn;
+ // FIXME: do we want to measure anything, considering the transceiver just syncs by.. syncing?
+ r.param.measure.dbm = -80;
+ tuneRx(gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 0) * 1000 * 100);
+ tuneTx(gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 1) * 1000 * 100);
+ cmdq_from_phy.spsc_push(&r);
+ break;
+ case TRXCON_PHYIF_CMDT_SETFREQ_H0:
+ tuneRx(gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 0) * 1000 * 100);
+ tuneTx(gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 1) * 1000 * 100);
+ break;
+ case TRXCON_PHYIF_CMDT_SETFREQ_H1:
+ break;
+ case TRXCON_PHYIF_CMDT_SETSLOT:
+ break;
+ case TRXCON_PHYIF_CMDT_SETTA:
+ set_ta(cmd.param.setta.ta);
+ break;
+ }
+ return false;
+}
+
+void sighandler(int sigset)
+{
+ // we might get a sigpipe in case the l1ctl ud socket disconnects because mobile quits
+ if (sigset == SIGPIPE || sigset == SIGINT) {
+ g_exit_flag = true;
+
+ // we know the flag is atomic and it prevents the trxcon cb handlers from writing
+ // to the queues, so submit some trash to unblock the threads & exit
+ trxcon_phyif_cmd cmd = {};
+ internal_q_tx_buf b = {};
+ txq.spsc_push(&b);
+ cmdq_to_phy.spsc_push(&cmd);
+ msleep(200);
+
+ return;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ auto tall_trxcon_ctx = talloc_init("trxcon context");
+ signal(SIGPIPE, sighandler);
+ signal(SIGINT, sighandler);
+
+ msgb_talloc_ctx_init(tall_trxcon_ctx, 0);
+ trxc_log_init(tall_trxcon_ctx);
+
+ /* Configure pretty logging */
+ log_set_print_extended_timestamp(osmo_stderr_target, 1);
+ log_set_print_category_hex(osmo_stderr_target, 0);
+ log_set_print_category(osmo_stderr_target, 1);
+ log_set_print_level(osmo_stderr_target, 1);
+
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
+ log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END);
+
+ osmo_fsm_log_timeouts(true);
+
+ g_trxcon = trxcon_inst_alloc(tall_trxcon_ctx, 0);
+ g_trxcon->gsmtap = nullptr;
+ g_trxcon->phyif = nullptr;
+ g_trxcon->phy_quirks.fbsb_extend_fns = 866; // 4 seconds, known to work.
+
+ convolve_init();
+ convert_init();
+ sigProcLibSetup();
+ initvita();
+
+ int status = 0;
+ auto trx = new upper_trx();
+ trx->do_auto_gain = true;
+
+ status = trx->init_dev_and_streams();
+ if (status < 0) {
+ std::cerr << "Error initializing hardware, quitting.." << std::endl;
+ return -1;
+ }
+ set_name_aff_sched(sched_params::thread_names::MAIN);
+
+ if (!trxc_l1ctl_init(tall_trxcon_ctx)) {
+ std::cerr << "Error initializing l1ctl, quitting.." << std::endl;
+ return -1;
+ }
+
+ // blocking, will return when global exit is requested
+ trx->start_threads();
+ trx->main_loop();
+ trx->stop_threads();
+ trx->stop_upper_threads();
+
+ return status;
+}
diff --git a/Transceiver52M/ms/ms_upper.h b/Transceiver52M/ms/ms_upper.h
new file mode 100644
index 0000000..06f7c54
--- /dev/null
+++ b/Transceiver52M/ms/ms_upper.h
@@ -0,0 +1,48 @@
+#pragma once
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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 <netdb.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include "GSMCommon.h"
+#include "ms.h"
+
+class upper_trx : public ms_trx {
+ volatile bool mOn;
+ char demodded_softbits[444];
+
+ // void driveControl();
+ bool pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset);
+
+ pthread_t thr_control, thr_tx;
+
+ public:
+ void start_threads();
+ void main_loop();
+ void stop_upper_threads();
+
+ bool driveControl();
+ void driveReceiveFIFO();
+ void driveTx();
+
+ upper_trx() : mOn(false){};
+};
diff --git a/Transceiver52M/ms/sch.c b/Transceiver52M/ms/sch.c
new file mode 100644
index 0000000..34809a2
--- /dev/null
+++ b/Transceiver52M/ms/sch.c
@@ -0,0 +1,329 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * (C) 2016 by Tom Tsou <tom.tsou@ettus.com>
+ * (C) 2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2022 by 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> / Eric Wild <ewild@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <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 <osmocom/coding/gsm0503_coding.h>
+#include <osmocom/coding/gsm0503_parity.h>
+
+#include "sch.h"
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
+
+/* 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,
+};
+
+#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;
+}
+
+int gsm_fcch_check_ts(int ts, int fn) {
+ return ts == 0 && gsm_fcch_check_fn(fn);
+}
+
+int gsm_sch_check_ts(int ts, int fn) {
+ return ts == 0 && gsm_sch_check_fn(fn);
+}
+
+/* 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;
+ }
+
+}
+
+#pragma GCC diagnostic pop
diff --git a/Transceiver52M/ms/sch.h b/Transceiver52M/ms/sch.h
new file mode 100644
index 0000000..ea8ba85
--- /dev/null
+++ b/Transceiver52M/ms/sch.h
@@ -0,0 +1,48 @@
+#pragma once
+/*
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * (C) 2016 by Tom Tsou <tom.tsou@ettus.com>
+ * (C) 2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> / Eric Wild <ewild@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <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);
+int gsm_fcch_check_ts(int ts, int fn);
+int gsm_sch_check_ts(int ts, int fn);
+
+double gsm_fcch_offset(float *burst, int len);
+
+int float_to_sbit(const float *in, sbit_t *out, float scale, int len);
+
diff --git a/Transceiver52M/ms/threadpool.h b/Transceiver52M/ms/threadpool.h
new file mode 100644
index 0000000..4b1eefd
--- /dev/null
+++ b/Transceiver52M/ms/threadpool.h
@@ -0,0 +1,95 @@
+#pragma once
+/*
+ * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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 <atomic>
+#include <vector>
+#include <future>
+#include <mutex>
+#include <queue>
+#include "threadsched.h"
+
+struct single_thread_pool {
+ std::mutex m;
+ std::condition_variable cv;
+ std::atomic<bool> stop_flag;
+ std::atomic<bool> is_ready;
+ std::deque<std::function<void()>> wq;
+ pthread_t worker_thread;
+
+ template <class F>
+ void add_task(F &&f)
+ {
+ std::unique_lock<std::mutex> l(m);
+ wq.emplace_back(std::forward<F>(f));
+ cv.notify_one();
+ return;
+ }
+
+ single_thread_pool() : stop_flag(false), is_ready(false)
+ {
+ worker_thread = spawn_worker_thread(
+ sched_params::thread_names::SCH_SEARCH,
+ [](void *args) -> void * {
+ using thist = decltype(this);
+ thist t = reinterpret_cast<thist>(args);
+ t->thread_loop();
+ return 0;
+ },
+ this);
+ }
+ ~single_thread_pool()
+ {
+ stop();
+ }
+
+ private:
+ void stop()
+ {
+ {
+ std::unique_lock<std::mutex> l(m);
+ wq.clear();
+ stop_flag = true;
+ cv.notify_one();
+ }
+ pthread_join(worker_thread, nullptr);
+ }
+
+ void thread_loop()
+ {
+ while (true) {
+ is_ready = true;
+ std::function<void()> f;
+ {
+ std::unique_lock<std::mutex> l(m);
+ if (wq.empty()) {
+ cv.wait(l, [&] { return !wq.empty() || stop_flag; });
+ }
+ if (stop_flag)
+ return;
+ is_ready = false;
+ f = std::move(wq.front());
+ wq.pop_front();
+ }
+ f();
+ }
+ }
+}; \ No newline at end of file
diff --git a/Transceiver52M/ms/threadsched.cpp b/Transceiver52M/ms/threadsched.cpp
new file mode 100644
index 0000000..ba5cae7
--- /dev/null
+++ b/Transceiver52M/ms/threadsched.cpp
@@ -0,0 +1,104 @@
+/*
+ * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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 <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+#include <thread>
+
+extern "C" {
+#include <pthread.h>
+}
+
+#include "threadsched.h"
+
+sched_params::target scheduling_target;
+
+void set_sched_target(sched_params::target t)
+{
+ scheduling_target = t;
+}
+
+void set_name_aff_sched(std::thread::native_handle_type h, const char *name, int cpunum, int schedtype, int prio)
+{
+ pthread_setname_np(h, name);
+
+ cpu_set_t cpuset;
+
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpunum, &cpuset);
+
+ if (pthread_setaffinity_np(h, sizeof(cpuset), &cpuset) < 0) {
+ std::cerr << name << " affinity: errreur! " << std::strerror(errno);
+ return exit(0);
+ }
+
+ sched_param sch_params;
+ sch_params.sched_priority = prio;
+ if (pthread_setschedparam(h, schedtype, &sch_params) < 0) {
+ std::cerr << name << " sched: errreur! " << std::strerror(errno);
+ return exit(0);
+ }
+}
+
+static pthread_t do_spawn_thr(const char *name, int cpunum, int schedtype, int prio, worker_func_sig fun, void *arg)
+{
+ pthread_t thread;
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+
+ sched_param sch_params;
+ sch_params.sched_priority = prio;
+ cpu_set_t cpuset;
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpunum, &cpuset);
+ auto a = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
+ a |= pthread_attr_setschedpolicy(&attr, schedtype);
+ a |= pthread_attr_setschedparam(&attr, &sch_params);
+ a |= pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
+ if (a)
+ std::cerr << "thread arg rc:" << a << std::endl;
+ pthread_create(&thread, &attr, fun, arg);
+ pthread_setname_np(thread, name);
+ pthread_attr_destroy(&attr);
+ return thread;
+}
+
+void set_name_aff_sched(std::thread::native_handle_type h, sched_params::thread_names name)
+{
+ auto tgt = schdp[scheduling_target][name];
+ // std::cerr << "scheduling for: " << tgt.name << ":" << tgt.core << std::endl;
+ set_name_aff_sched(h, tgt.name, tgt.core, tgt.schedtype, tgt.prio);
+}
+
+void set_name_aff_sched(sched_params::thread_names name)
+{
+ set_name_aff_sched(pthread_self(), name);
+}
+
+pthread_t spawn_worker_thread(sched_params::thread_names name, worker_func_sig fun, void *arg)
+{
+ auto tgt = schdp[scheduling_target][name];
+ // std::cerr << "scheduling for: " << tgt.name << ":" << tgt.core << " prio:" << tgt.prio << std::endl;
+ return do_spawn_thr(tgt.name, tgt.core, tgt.schedtype, tgt.prio, fun, arg);
+}
diff --git a/Transceiver52M/ms/threadsched.h b/Transceiver52M/ms/threadsched.h
new file mode 100644
index 0000000..7cc9176
--- /dev/null
+++ b/Transceiver52M/ms/threadsched.h
@@ -0,0 +1,68 @@
+#pragma once
+/*
+ * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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/>.
+ *
+ */
+
+extern "C" {
+#include <pthread.h>
+#include <sched.h>
+}
+
+static struct sched_params {
+ enum thread_names { U_CTL = 0, U_RX, U_TX, SCH_SEARCH, MAIN, LEAKCHECK, RXRUN, TXRUN, _THRD_NAME_COUNT };
+ enum target { ODROID = 0, PI4 };
+ const char *name;
+ int core;
+ int schedtype;
+ int prio;
+} schdp[][sched_params::_THRD_NAME_COUNT]{
+ {
+ { "upper_ctrl", 2, SCHED_RR, sched_get_priority_max(SCHED_RR) },
+ { "upper_rx", 2, SCHED_RR, sched_get_priority_max(SCHED_RR) - 5 },
+ { "upper_tx", 2, SCHED_RR, sched_get_priority_max(SCHED_RR) - 1 },
+
+ { "sch_search", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 },
+ { "main", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 },
+ { "leakcheck", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10 },
+
+ { "rxrun", 4, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2 },
+ { "txrun", 5, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1 },
+ },
+ {
+ { "upper_ctrl", 1, SCHED_RR, sched_get_priority_max(SCHED_RR) },
+ { "upper_rx", 1, SCHED_RR, sched_get_priority_max(SCHED_RR) - 5 },
+ { "upper_tx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1 },
+
+ { "sch_search", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 },
+ { "main", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 },
+ { "leakcheck", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10 },
+
+ { "rxrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2 },
+ { "txrun", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1 },
+ },
+};
+
+void set_sched_target(sched_params::target t);
+
+using worker_func_sig = void *(*)(void *);
+
+void set_name_aff_sched(sched_params::thread_names name);
+
+pthread_t spawn_worker_thread(sched_params::thread_names name, worker_func_sig fun, void *arg);
diff --git a/Transceiver52M/ms/uhd_specific.h b/Transceiver52M/ms/uhd_specific.h
new file mode 100644
index 0000000..151c002
--- /dev/null
+++ b/Transceiver52M/ms/uhd_specific.h
@@ -0,0 +1,279 @@
+#pragma once
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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 <uhd/version.hpp>
+#include <uhd/usrp/multi_usrp.hpp>
+#include <uhd/types/metadata.hpp>
+#include <complex>
+#include <cstring>
+#include <iostream>
+#include <thread>
+
+#include <Timeval.h>
+#include <vector>
+
+using blade_sample_type = std::complex<int16_t>;
+const int SAMPLE_SCALE_FACTOR = 1;
+
+struct uhd_buf_wrap {
+ double rxticks;
+ size_t num_samps;
+ uhd::rx_metadata_t *md;
+ blade_sample_type *buf;
+ auto actual_samples_per_buffer()
+ {
+ return num_samps;
+ }
+ long get_first_ts()
+ {
+ return md->time_spec.to_ticks(rxticks);
+ }
+ int readall(blade_sample_type *outaddr)
+ {
+ memcpy(outaddr, buf, num_samps * sizeof(blade_sample_type));
+ return num_samps;
+ }
+ int read_n(blade_sample_type *outaddr, int start, int num)
+ {
+ assert(start >= 0);
+ auto to_read = std::min((int)num_samps - start, num);
+ assert(to_read >= 0);
+ memcpy(outaddr, buf + start, to_read * sizeof(blade_sample_type));
+ return to_read;
+ }
+};
+
+using dev_buf_t = uhd_buf_wrap;
+using bh_fn_t = std::function<int(dev_buf_t *)>;
+
+template <typename T>
+struct uhd_hw {
+ uhd::usrp::multi_usrp::sptr dev;
+ uhd::rx_streamer::sptr rx_stream;
+ uhd::tx_streamer::sptr tx_stream;
+ blade_sample_type *one_pkt_buf;
+ std::vector<blade_sample_type *> pkt_ptrs;
+ size_t rx_spp;
+ double rxticks;
+ const unsigned int rxFullScale, txFullScale;
+ const int rxtxdelay;
+ float rxgain, txgain;
+ static std::atomic<bool> stop_lower_threads_flag;
+ double rxfreq_cache, txfreq_cache;
+
+ virtual ~uhd_hw()
+ {
+ delete[] one_pkt_buf;
+ }
+ uhd_hw() : rxFullScale(32767), txFullScale(32767 * 0.3), rxtxdelay(-67), rxfreq_cache(0), txfreq_cache(0)
+ {
+ }
+
+ void close_device()
+ {
+ }
+
+ bool tuneTx(double freq, size_t chan = 0)
+ {
+ if (txfreq_cache == freq)
+ return true;
+ msleep(25);
+ dev->set_tx_freq(freq, chan);
+ txfreq_cache = freq;
+ msleep(25);
+ return true;
+ };
+ bool tuneRx(double freq, size_t chan = 0)
+ {
+ if (rxfreq_cache == freq)
+ return true;
+ msleep(25);
+ dev->set_rx_freq(freq, chan);
+ rxfreq_cache = freq;
+ msleep(25);
+ return true;
+ };
+ bool tuneRxOffset(double offset, size_t chan = 0)
+ {
+ return true;
+ };
+
+ double setRxGain(double dB, size_t chan = 0)
+ {
+ rxgain = dB;
+ msleep(25);
+ dev->set_rx_gain(dB, chan);
+ msleep(25);
+ return dB;
+ };
+ double setTxGain(double dB, size_t chan = 0)
+ {
+ txgain = dB;
+ msleep(25);
+ dev->set_tx_gain(dB, chan);
+ msleep(25);
+ return dB;
+ };
+ int setPowerAttenuation(int atten, size_t chan = 0)
+ {
+ return atten;
+ };
+
+ int init_device(bh_fn_t rxh, bh_fn_t txh)
+ {
+ auto const lock_delay_ms = 500;
+ auto clock_lock_attempts = 15; // x lock_delay_ms
+ auto const mcr = 26e6;
+ auto const rate = (1625e3 / 6) * 4;
+ auto const ref = "external";
+ auto const gain = 35;
+ auto const freq = 931.4e6; // 936.8e6
+ auto bw = 0.5e6;
+ auto const channel = 0;
+ // aligned to blade: 1020 samples per transfer
+ std::string args = { "recv_frame_size=4092,send_frame_size=4092" };
+
+ dev = uhd::usrp::multi_usrp::make(args);
+ std::cout << "Using Device: " << dev->get_pp_string() << std::endl;
+ dev->set_clock_source(ref);
+ dev->set_master_clock_rate(mcr);
+ dev->set_rx_rate(rate, channel);
+ dev->set_tx_rate(rate, channel);
+ uhd::tune_request_t tune_request(freq, 0);
+ dev->set_rx_freq(tune_request, channel);
+ dev->set_rx_gain(gain, channel);
+ dev->set_tx_gain(60, channel);
+ dev->set_rx_bandwidth(bw, channel);
+ dev->set_tx_bandwidth(bw, channel);
+
+ while (!(dev->get_rx_sensor("lo_locked", channel).to_bool() &&
+ dev->get_mboard_sensor("ref_locked").to_bool()) &&
+ clock_lock_attempts > 0) {
+ std::cerr << "clock source lock attempts remaining: " << clock_lock_attempts << ".."
+ << std::endl;
+ std::this_thread::sleep_for(std::chrono::milliseconds(lock_delay_ms));
+ clock_lock_attempts--;
+ }
+
+ if (clock_lock_attempts <= 0) {
+ std::cerr << "Error locking clock, gpsdo missing? quitting.." << std::endl;
+ return -1;
+ }
+
+ uhd::stream_args_t stream_args("sc16", "sc16");
+ rx_stream = dev->get_rx_stream(stream_args);
+ uhd::stream_args_t stream_args2("sc16", "sc16");
+ tx_stream = dev->get_tx_stream(stream_args2);
+
+ rx_spp = rx_stream->get_max_num_samps();
+ rxticks = dev->get_rx_rate();
+ assert(rxticks == dev->get_tx_rate());
+ one_pkt_buf = new blade_sample_type[rx_spp];
+ pkt_ptrs = { 1, &one_pkt_buf[0] };
+ return 0;
+ }
+
+ void actually_enable_streams()
+ {
+ // nop: stream cmd in handler
+ }
+
+ void *rx_cb(bh_fn_t burst_handler)
+ {
+ void *ret = nullptr;
+ static int to_skip = 0;
+
+ uhd::rx_metadata_t md;
+ auto num_rx_samps = rx_stream->recv(pkt_ptrs.front(), rx_spp, md, 1.0, true);
+
+ if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) {
+ std::cerr << boost::format("Timeout while streaming") << std::endl;
+ exit(0);
+ }
+ if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW) {
+ std::cerr << boost::format("Got an overflow indication\n") << std::endl;
+ exit(0);
+ }
+ if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE) {
+ std::cerr << str(boost::format("Receiver error: %s") % md.strerror());
+ exit(0);
+ }
+
+ dev_buf_t rcd = { rxticks, num_rx_samps, &md, &one_pkt_buf[0] };
+
+ if (to_skip < 120) // prevents weird overflows on startup
+ to_skip++;
+ else {
+ burst_handler(&rcd);
+ }
+
+ return ret;
+ }
+
+ auto get_rx_burst_handler_fn(bh_fn_t burst_handler)
+ {
+ // C cb -> ghetto closure capture, which is fine, the args never change.
+ static auto rx_burst_cap_this = this;
+ static auto rx_burst_cap_bh = burst_handler;
+ auto fn = [](void *args) -> void * {
+ pthread_setname_np(pthread_self(), "rxrun");
+
+ uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS);
+ stream_cmd.stream_now = true;
+ stream_cmd.time_spec = uhd::time_spec_t();
+ rx_burst_cap_this->rx_stream->issue_stream_cmd(stream_cmd);
+
+ while (!rx_burst_cap_this->stop_lower_threads_flag) {
+ rx_burst_cap_this->rx_cb(rx_burst_cap_bh);
+ }
+ return 0;
+ };
+ return fn;
+ }
+ auto get_tx_burst_handler_fn(bh_fn_t burst_handler)
+ {
+ auto fn = [](void *args) -> void * {
+ // dummy
+ return 0;
+ };
+ return fn;
+ }
+ void submit_burst_ts(blade_sample_type *buffer, int len, uint64_t ts)
+ {
+ uhd::tx_metadata_t m = {};
+ m.end_of_burst = true;
+ m.start_of_burst = true;
+ m.has_time_spec = true;
+ m.time_spec = m.time_spec.from_ticks(ts + rxtxdelay, rxticks); // uhd specific b210 delay!
+ std::vector<void *> ptrs(1, buffer);
+
+ tx_stream->send(ptrs, len, m, 1.0);
+#ifdef DBGXX
+ uhd::async_metadata_t async_md;
+ bool tx_ack = false;
+ while (!tx_ack && tx_stream->recv_async_msg(async_md)) {
+ tx_ack = (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_BURST_ACK);
+ }
+ std::cout << (tx_ack ? "yay" : "nay") << " " << async_md.time_spec.to_ticks(rxticks) << std::endl;
+#endif
+ }
+}; \ No newline at end of file
diff --git a/Transceiver52M/osmo-trx.cpp b/Transceiver52M/osmo-trx.cpp
index 4a92447..8f5cc85 100644
--- a/Transceiver52M/osmo-trx.cpp
+++ b/Transceiver52M/osmo-trx.cpp
@@ -12,10 +12,6 @@
* 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
@@ -81,6 +77,24 @@ static struct ctrl_handle *g_ctrlh;
static RadioDevice *usrp;
static RadioInterface *radio;
+/* adjusts read timestamp offset to make the viterbi equalizer happy by including the start tail bits */
+template <typename B>
+class rif_va_wrapper : public B {
+ bool use_va;
+
+ public:
+ template <typename... Args>
+ rif_va_wrapper(bool use_va, Args &&...args) : B(std::forward<Args>(args)...), use_va(use_va)
+ {
+ }
+ bool start() override
+ {
+ auto rv = B::start();
+ B::readTimestamp -= use_va ? 20 : 0;
+ return rv;
+ };
+};
+
/* Create radio interface
* The interface consists of sample rate changes, frequency shifts,
* channel multiplexing, and other conversions. The transceiver core
@@ -95,17 +109,17 @@ RadioInterface *makeRadioInterface(struct trx_ctx *trx,
switch (type) {
case RadioDevice::NORMAL:
- radio = new RadioInterface(usrp, trx->cfg.tx_sps,
- trx->cfg.rx_sps, trx->cfg.num_chans);
+ radio = new rif_va_wrapper<RadioInterface>(trx->cfg.use_va, 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);
+ radio = new rif_va_wrapper<RadioInterfaceResamp>(trx->cfg.use_va, 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 rif_va_wrapper<RadioInterfaceMulti>(trx->cfg.use_va, usrp, trx->cfg.tx_sps, trx->cfg.rx_sps,
+ trx->cfg.num_chans);
break;
default:
LOG(ALERT) << "Unsupported radio interface configuration";
@@ -144,12 +158,8 @@ int makeTransceiver(struct trx_ctx *trx, RadioInterface *radio)
{
VectorFIFO *fifo;
- transceiver = new Transceiver(trx->cfg.base_port, trx->cfg.bind_addr,
- trx->cfg.remote_addr, trx->cfg.tx_sps,
- trx->cfg.rx_sps, trx->cfg.num_chans, GSM::Time(3,0),
- radio, trx->cfg.rssi_offset, trx->cfg.stack_size);
- if (!transceiver->init(trx->cfg.filler, trx->cfg.rtsc,
- trx->cfg.rach_delay, trx->cfg.egprs, trx->cfg.ext_rach)) {
+ transceiver = new Transceiver(&trx->cfg, GSM::Time(3,0), radio);
+ if (!transceiver->init()) {
LOG(ALERT) << "Failed to initialize transceiver";
return -1;
}
@@ -181,6 +191,17 @@ static void sig_handler(int signo)
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);
@@ -246,10 +267,13 @@ static void setup_signal_handlers()
static void print_help()
{
- fprintf(stdout, "Options:\n"
- " -h, --help This text\n"
- " -C, --config Filename The config file to use\n"
- " -V, --version Print the version of OsmoTRX\n"
+ 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"
);
}
@@ -260,16 +284,44 @@ static void print_deprecated(char opt)
<< " 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}
};
@@ -280,6 +332,9 @@ static void handle_options(int argc, char **argv, struct trx_ctx* trx)
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);
@@ -346,6 +401,7 @@ static void handle_options(int argc, char **argv, struct trx_ctx* trx)
case 'R':
print_deprecated(option);
trx->cfg.rssi_offset = atof(optarg);
+ trx->cfg.force_rssi_offset = true;
break;
case 'S':
print_deprecated(option);
@@ -427,6 +483,12 @@ int trx_validate_config(struct trx_ctx *trx)
return -1;
}
+ if (trx->cfg.use_va &&
+ (trx->cfg.egprs || trx->cfg.multi_arfcn || trx->cfg.tx_sps != 4 || trx->cfg.rx_sps != 4)) {
+ LOG(ERROR) << "Viterbi equalizer only works for gmsk with 4 tx/rx samples per symbol!";
+ return -1;
+ }
+
return 0;
}
@@ -447,6 +509,38 @@ static int set_sched_rr(unsigned int prio)
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;
@@ -468,8 +562,10 @@ static void print_config(struct trx_ctx *trx)
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 << " Tuning offset........... " << trx->cfg.offset << std::endl;
- ost << " RSSI to dBm offset...... " << trx->cfg.rssi_offset << 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++) {
@@ -499,24 +595,14 @@ static void trx_stop()
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);
+ usrp = RadioDevice::make(iface, &trx->cfg);
+ type = usrp->open();
if (type < 0) {
LOG(ALERT) << "Failed to create radio device" << std::endl;
goto shutdown;
@@ -554,35 +640,6 @@ int main(int argc, char *argv[])
g_trx_ctx = vty_trx_ctx_alloc(tall_trx_ctx);
-#ifdef HAVE_SSE3
- printf("Info: SSE3 support compiled in");
-#ifdef HAVE___BUILTIN_CPU_SUPPORTS
- if (__builtin_cpu_supports("sse3"))
- printf(" and supported by CPU\n");
- else
- printf(", but not supported by CPU\n");
-#else
- printf(", but runtime SIMD detection disabled\n");
-#endif
-#endif
-
-#ifdef HAVE_SSE4_1
- printf("Info: SSE4.1 support compiled in");
-#ifdef HAVE___BUILTIN_CPU_SUPPORTS
- if (__builtin_cpu_supports("sse4.1"))
- printf(" and supported by CPU\n");
- else
- printf(", but not supported by CPU\n");
-#else
- printf(", 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!")
- printf("Built without atomic operation support. Using Mutex, it may affect performance!\n");
-#endif
-
convolve_init();
convert_init();
@@ -608,11 +665,11 @@ int main(int argc, char *argv[])
exit(2);
}
- rc = telnet_init_dynif(tall_trx_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_TRX);
+ rc = telnet_init_default(tall_trx_ctx, NULL, 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);
+ g_ctrlh = ctrl_interface_setup(NULL, OSMO_CTRL_PORT_TRX, NULL);
if (!g_ctrlh) {
LOG(ERROR) << "Failed to create CTRL interface.\n";
exit(1);
@@ -629,6 +686,7 @@ int main(int argc, char *argv[])
" 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) {
diff --git a/Transceiver52M/proto_trxd.h b/Transceiver52M/proto_trxd.h
index d72cfdd..c250a74 100644
--- a/Transceiver52M/proto_trxd.h
+++ b/Transceiver52M/proto_trxd.h
@@ -48,9 +48,8 @@ struct trxd_hdr_common {
reserved:1,
version:4;
#elif OSMO_IS_BIG_ENDIAN
- uint8_t version:4,
- reserved:1,
- tn:3;
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t version:4, reserved:1, tn:3;
#endif
uint32_t fn; /* big endian */
} __attribute__ ((packed));
@@ -86,9 +85,8 @@ struct trxd_hdr_v1_specific {
modulation:4,
idle:1;
#elif OSMO_IS_BIG_ENDIAN
- uint8_t idle:1,
- modulation:4,
- tsc:3;
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t idle:1, modulation:4, tsc:3;
#endif
int16_t ci; /* big endian, in centiBels */
} __attribute__ ((packed));
diff --git a/Transceiver52M/radioBuffer.cpp b/Transceiver52M/radioBuffer.cpp
index 62f6553..ec868e5 100644
--- a/Transceiver52M/radioBuffer.cpp
+++ b/Transceiver52M/radioBuffer.cpp
@@ -28,7 +28,7 @@
RadioBuffer::RadioBuffer(size_t numSegments, size_t segmentLen,
size_t hLen, bool outDirection)
- : writeIndex(0), readIndex(0), availSamples(0)
+ : writeIndex(0), readIndex(0), availSamples(0), segments(numSegments)
{
if (!outDirection)
hLen = 0;
@@ -36,7 +36,6 @@ RadioBuffer::RadioBuffer(size_t numSegments, size_t segmentLen,
buffer = new float[2 * (hLen + numSegments * segmentLen)];
bufferLen = numSegments * segmentLen;
- segments.resize(numSegments);
for (size_t i = 0; i < numSegments; i++)
segments[i] = &buffer[2 * (hLen + i * segmentLen)];
diff --git a/Transceiver52M/radioInterface.cpp b/Transceiver52M/radioInterface.cpp
index 64a6f21..311af34 100644
--- a/Transceiver52M/radioInterface.cpp
+++ b/Transceiver52M/radioInterface.cpp
@@ -39,9 +39,10 @@ extern "C" {
RadioInterface::RadioInterface(RadioDevice *wDevice, size_t tx_sps,
size_t rx_sps, size_t chans,
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)
+ : mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mReceiveFIFO(mChans), mDevice(wDevice),
+ sendBuffer(mChans), recvBuffer(mChans), convertRecvBuffer(mChans),
+ convertSendBuffer(mChans), powerScaling(mChans), underrun(false), overrun(false),
+ writeTimestamp(0), readTimestamp(0), receiveOffset(wReceiveOffset), mOn(false)
{
mClock.set(wStartTime);
}
@@ -58,15 +59,6 @@ bool RadioInterface::init(int type)
return false;
}
- close();
-
- sendBuffer.resize(mChans);
- recvBuffer.resize(mChans);
- convertSendBuffer.resize(mChans);
- convertRecvBuffer.resize(mChans);
- mReceiveFIFO.resize(mChans);
- powerScaling.resize(mChans);
-
for (size_t i = 0; i < mChans; i++) {
sendBuffer[i] = new RadioBuffer(NUMCHUNKS, CHUNK * mSPSTx, 0, true);
recvBuffer[i] = new RadioBuffer(NUMCHUNKS, CHUNK * mSPSRx, 0, false);
@@ -322,6 +314,11 @@ double RadioInterface::setRxGain(double dB, size_t chan)
return mDevice->setRxGain(dB, chan);
}
+double RadioInterface::rssiOffset(size_t chan)
+{
+ return mDevice->rssiOffset(chan);
+}
+
/* Receive a timestamped chunk from the device */
int RadioInterface::pullBuffer()
{
diff --git a/Transceiver52M/radioInterface.h b/Transceiver52M/radioInterface.h
index 8e5f4c1..b05af78 100644
--- a/Transceiver52M/radioInterface.h
+++ b/Transceiver52M/radioInterface.h
@@ -31,6 +31,9 @@ static const unsigned gSlotLen = 148; ///< number of symbols per slot, not
class RadioInterface {
protected:
+ size_t mSPSTx;
+ size_t mSPSRx;
+ size_t mChans;
Thread mAlignRadioServiceLoopThread; ///< thread that synchronizes transmit and receive sections
@@ -38,10 +41,6 @@ protected:
RadioDevice *mDevice; ///< the USRP object
- size_t mSPSTx;
- size_t mSPSRx;
- size_t mChans;
-
std::vector<RadioBuffer *> sendBuffer;
std::vector<RadioBuffer *> recvBuffer;
@@ -76,7 +75,7 @@ private:
public:
/** start the interface */
- bool start();
+ virtual bool start();
bool stop();
/** initialization */
@@ -109,6 +108,9 @@ public:
/** set receive gain */
virtual double setRxGain(double dB, size_t chan = 0);
+ /** return base RSSI offset to apply for received samples **/
+ virtual double rssiOffset(size_t chan = 0);
+
/** drive transmission of GSM bursts */
void driveTransmitRadio(std::vector<signalVector *> &bursts,
std::vector<bool> &zeros);
@@ -149,7 +151,7 @@ private:
public:
RadioInterfaceResamp(RadioDevice* wDevice, size_t tx_sps, size_t rx_sps);
- ~RadioInterfaceResamp();
+ virtual ~RadioInterfaceResamp();
bool init(int type);
void close();
@@ -182,7 +184,7 @@ private:
public:
RadioInterfaceMulti(RadioDevice* radio, size_t tx_sps,
size_t rx_sps, size_t chans = 1);
- ~RadioInterfaceMulti();
+ virtual ~RadioInterfaceMulti();
bool init(int type);
void close();
@@ -190,4 +192,5 @@ public:
bool tuneTx(double freq, size_t chan);
bool tuneRx(double freq, size_t chan);
virtual double setRxGain(double dB, size_t chan);
+ virtual double rssiOffset(size_t chan = 0);
};
diff --git a/Transceiver52M/radioInterfaceMulti.cpp b/Transceiver52M/radioInterfaceMulti.cpp
index 1ca3fbb..a7195b4 100644
--- a/Transceiver52M/radioInterfaceMulti.cpp
+++ b/Transceiver52M/radioInterfaceMulti.cpp
@@ -44,8 +44,9 @@ extern "C" {
RadioInterfaceMulti::RadioInterfaceMulti(RadioDevice *radio, size_t tx_sps,
size_t rx_sps, size_t chans)
: RadioInterface(radio, tx_sps, rx_sps, chans),
- outerSendBuffer(NULL), outerRecvBuffer(NULL),
- dnsampler(NULL), upsampler(NULL), channelizer(NULL), synthesis(NULL)
+ outerSendBuffer(NULL), outerRecvBuffer(NULL), history(mChans), active(MCHANS, false),
+ rx_freq_state(mChans), tx_freq_state(mChans), dnsampler(NULL), upsampler(NULL), channelizer(NULL),
+ synthesis(NULL)
{
}
@@ -70,12 +71,16 @@ void RadioInterfaceMulti::close()
channelizer = NULL;
synthesis = NULL;
- mReceiveFIFO.resize(0);
- powerScaling.resize(0);
- history.resize(0);
- active.resize(0);
- rx_freq_state.resize(0);
- tx_freq_state.resize(0);
+
+ for (std::vector<signalVector*>::iterator it = history.begin(); it != history.end(); ++it)
+ delete *it;
+
+ mReceiveFIFO.clear();
+ powerScaling.clear();
+ history.clear();
+ active.clear();
+ rx_freq_state.clear();
+ tx_freq_state.clear();
RadioInterface::close();
}
@@ -148,20 +153,9 @@ bool RadioInterfaceMulti::init(int type)
return false;
}
- close();
-
- sendBuffer.resize(mChans);
- recvBuffer.resize(mChans);
convertSendBuffer.resize(1);
convertRecvBuffer.resize(1);
- mReceiveFIFO.resize(mChans);
- powerScaling.resize(mChans);
- history.resize(mChans);
- rx_freq_state.resize(mChans);
- tx_freq_state.resize(mChans);
- active.resize(MCHANS, false);
-
/* 4 == sps */
inchunk = RESAMP_INRATE * 4;
outchunk = RESAMP_OUTRATE * 4;
@@ -446,6 +440,11 @@ double RadioInterfaceMulti::setRxGain(double db, size_t chan)
return mDevice->getRxGain();
}
+double RadioInterfaceMulti::rssiOffset(size_t chan)
+{
+ return mDevice->rssiOffset(0);
+}
+
int RadioInterfaceMulti::setPowerAttenuation(int atten, size_t chan)
{
return RadioInterface::setPowerAttenuation(atten, 0);
diff --git a/Transceiver52M/radioInterfaceResamp.cpp b/Transceiver52M/radioInterfaceResamp.cpp
index b92432f..869acd0 100644
--- a/Transceiver52M/radioInterfaceResamp.cpp
+++ b/Transceiver52M/radioInterfaceResamp.cpp
@@ -98,15 +98,6 @@ bool RadioInterfaceResamp::init(int type)
{
float cutoff = 1.0f;
- close();
-
- sendBuffer.resize(1);
- recvBuffer.resize(1);
- convertSendBuffer.resize(1);
- convertRecvBuffer.resize(1);
- mReceiveFIFO.resize(1);
- powerScaling.resize(1);
-
switch (type) {
case RadioDevice::RESAMP_64M:
resamp_inrate = RESAMP_64M_INRATE;
diff --git a/Transceiver52M/radioVector.cpp b/Transceiver52M/radioVector.cpp
index 68e42c5..acefc97 100644
--- a/Transceiver52M/radioVector.cpp
+++ b/Transceiver52M/radioVector.cpp
@@ -76,22 +76,25 @@ bool radioVector::setVector(signalVector *vector, size_t chan)
return true;
}
-noiseVector::noiseVector(size_t size)
+avgVector::avgVector(size_t size)
: std::vector<float>(size), itr(0)
{
}
-float noiseVector::avg() const
+float avgVector::avg() const
{
float val = 0.0;
+ if (!size())
+ return 0.0f;
+
for (size_t i = 0; i < size(); i++)
val += (*this)[i];
return val / (float) size();
}
-bool noiseVector::insert(float val)
+bool avgVector::insert(float val)
{
if (!size())
return false;
diff --git a/Transceiver52M/radioVector.h b/Transceiver52M/radioVector.h
index 84e3987..90db626 100644
--- a/Transceiver52M/radioVector.h
+++ b/Transceiver52M/radioVector.h
@@ -48,9 +48,9 @@ private:
GSM::Time mTime;
};
-class noiseVector : std::vector<float> {
+class avgVector : std::vector<float> {
public:
- noiseVector(size_t size = 0);
+ avgVector(size_t size = 0);
bool insert(float val);
float avg() const;
diff --git a/Transceiver52M/sigProcLib.cpp b/Transceiver52M/sigProcLib.cpp
index 04f7e30..5fac365 100644
--- a/Transceiver52M/sigProcLib.cpp
+++ b/Transceiver52M/sigProcLib.cpp
@@ -34,6 +34,7 @@
#include "Resampler.h"
extern "C" {
+#include <osmocom/core/panic.h>
#include "convolve.h"
#include "scale.h"
#include "mult.h"
@@ -128,6 +129,8 @@ 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 CorrelationSequence *gDummySequence = NULL;
static PulseSequence *GSMPulse1 = NULL;
static PulseSequence *GSMPulse4 = NULL;
@@ -150,6 +153,12 @@ void sigProcLibDestroy()
gRACHSequences[i] = NULL;
}
+ delete gSCHSequence;
+ gSCHSequence = NULL;
+
+ delete gDummySequence;
+ gDummySequence = NULL;
+
delete GMSKRotation1;
delete GMSKReverseRotation1;
delete GMSKRotation4;
@@ -314,6 +323,7 @@ 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;
@@ -1288,6 +1298,77 @@ release:
return status;
}
+static bool generateDummyMidamble(int sps)
+{
+ bool status = true;
+ float toa;
+ complex *data = NULL;
+ signalVector *autocorr = NULL, *midamble = NULL;
+ signalVector *midMidamble = NULL, *_midMidamble = NULL;
+
+ delete gDummySequence;
+
+ /* Use middle 16 bits of each TSC. Correlation sequence is not pulse shaped */
+ midMidamble = modulateBurst(gDummyBurstTSC.segment(5,16), 0, sps, true);
+ if (!midMidamble)
+ return false;
+
+ /* Simulated receive sequence is pulse shaped */
+ midamble = modulateBurst(gDummyBurstTSC, 0, sps, false);
+ if (!midamble) {
+ status = false;
+ goto release;
+ }
+
+ // NOTE: Because ideal TSC 16-bit midamble is 66 symbols into burst,
+ // the ideal TSC has an + 180 degree phase shift,
+ // due to the pi/2 frequency shift, that
+ // needs to be accounted for.
+ // 26-midamble is 61 symbols into burst, has +90 degree phase shift.
+ scaleVector(*midMidamble, complex(-1.0, 0.0));
+ scaleVector(*midamble, complex(0.0, 1.0));
+
+ conjugateVector(*midMidamble);
+
+ /* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */
+ data = (complex *) convolve_h_alloc(midMidamble->size());
+ _midMidamble = new signalVector(data, 0, midMidamble->size(), convolve_h_alloc, free);
+ _midMidamble->setAligned(true);
+ midMidamble->copyTo(*_midMidamble);
+
+ autocorr = convolve(midamble, _midMidamble, NULL, NO_DELAY);
+ if (!autocorr) {
+ status = false;
+ goto release;
+ }
+
+ gDummySequence = new CorrelationSequence;
+ gDummySequence->sequence = _midMidamble;
+ gDummySequence->gain = peakDetect(*autocorr, &toa, NULL);
+
+ /* For 1 sps only
+ * (Half of correlation length - 1) + midpoint of pulse shape + remainder
+ * 13.5 = (16 / 2 - 1) + 1.5 + (26 - 10) / 2
+ */
+ if (sps == 1)
+ gDummySequence->toa = toa - 13.5;
+ else
+ gDummySequence->toa = 0;
+
+release:
+ delete autocorr;
+ delete midamble;
+ delete midMidamble;
+
+ if (!status) {
+ delete _midMidamble;
+ free(data);
+ gDummySequence = NULL;
+ }
+
+ return status;
+}
+
static CorrelationSequence *generateEdgeMidamble(int tsc)
{
complex *data = NULL;
@@ -1383,6 +1464,69 @@ 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(), convolve_h_alloc, free);
+ _seq1->setAligned(true);
+ seq1->copyTo(*_seq1);
+
+ 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);
+
+ /* 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
*/
@@ -1440,14 +1584,15 @@ float energyDetect(const signalVector &rxBurst, unsigned windowLength)
return energy/windowLength;
}
-static signalVector *downsampleBurst(const signalVector &burst)
+static signalVector *downsampleBurst(const signalVector &burst, int in_len = DOWNSAMPLE_IN_LEN,
+ int out_len = DOWNSAMPLE_OUT_LEN)
{
- signalVector in(DOWNSAMPLE_IN_LEN, dnsampler->len());
- signalVector *out = new signalVector(DOWNSAMPLE_OUT_LEN);
- burst.copyToSegment(in, 0, DOWNSAMPLE_IN_LEN);
+ signalVector in(in_len, dnsampler->len());
+ // gSCHSequence->sequence->size(), ensure next conv has no realloc
+ signalVector *out = new signalVector(out_len, 64);
+ burst.copyToSegment(in, 0, in_len);
- if (dnsampler->rotate((float *) in.begin(), DOWNSAMPLE_IN_LEN,
- (float *) out->begin(), DOWNSAMPLE_OUT_LEN) < 0) {
+ if (dnsampler->rotate((float *)in.begin(), in_len, (float *)out->begin(), out_len) < 0) {
delete out;
out = NULL;
}
@@ -1460,25 +1605,36 @@ static signalVector *downsampleBurst(const signalVector &burst)
* It is computed from the training sequence of each received burst,
* by comparing the "ideal" training sequence with the actual one.
*/
-static float computeCI(const signalVector *burst, CorrelationSequence *sync,
- float toa, int start, complex xcorr)
+static float computeCI(const signalVector *burst, const CorrelationSequence *sync,
+ float toa, int start, const complex &xcorr)
{
+ const int N = sync->sequence->size();
float S, C;
- int ps;
-
/* Integer position where the sequence starts */
- ps = start + 1 - sync->sequence->size() + (int)roundf(toa);
+ 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;
+
+ if (ps + N > (int)burst->size())
+ return 0;
/* Estimate Signal power */
S = 0.0f;
- for (int i=0, j=ps; i<(int)sync->sequence->size(); i++,j++)
+ for (int i=0, j=ps; i<(int)N; i++,j++)
S += (*burst)[j].norm2();
- S /= sync->sequence->size();
+ S /= N;
/* Esimate Carrier power */
- C = xcorr.norm2() / ((sync->sequence->size() - 1) * sync->gain.abs());
-
- /* Interference = Signal - Carrier, so C/I = C / (S - C) */
+ C = xcorr.norm2() / ((N - 1) * sync->gain.abs());
+
+ /* Interference = Signal - Carrier, so C/I = C / (S - C).
+ * Calculated in dB:
+ * C/I_dB = 10 * log10(C/I)
+ * C/I_dB = 10 * (1/log2(10)) * log2(C/I)
+ * C/I_dB = 10 * 0.30103 * log2(C/I)
+ * C/I_dB = 3.0103 * log2(C/I)
+ */
return 3.0103f * log2f(C / (S - C));
}
@@ -1491,7 +1647,7 @@ static float computeCI(const signalVector *burst, CorrelationSequence *sync,
* and we run full interpolating peak detection.
*/
static int detectBurst(const signalVector &burst,
- signalVector &corr, CorrelationSequence *sync,
+ signalVector &corr, const CorrelationSequence *sync,
float thresh, int sps, int start, int len,
struct estim_burst_params *ebp)
{
@@ -1500,12 +1656,18 @@ static int detectBurst(const signalVector &burst,
complex xcorr;
int rc = 1;
- if (sps == 4) {
- dec = downsampleBurst(burst);
- corr_in = dec;
- sps = 1;
- } else {
+ switch (sps) {
+ case 1:
corr_in = &burst;
+ break;
+ case 4:
+ dec = downsampleBurst(burst);
+ /* Running at the downsampled rate at this point: */
+ corr_in = dec;
+ sps = 1;
+ break;
+ default:
+ osmo_panic("%s:%d SPS %d not supported! Only 1 or 4 supported", __FILE__, __LINE__, sps);
}
/* Correlate */
@@ -1515,9 +1677,6 @@ static int detectBurst(const signalVector &burst,
goto del_ret;
}
- /* Running at the downsampled rate at this point */
- sps = 1;
-
/* Peak detection - place restrictions at correlation edges */
ebp->amp = fastPeakDetect(corr, &ebp->toa);
@@ -1586,7 +1745,7 @@ static int detectGeneralBurst(const signalVector &rxBurst, float thresh, int sps
// and only report clipping if we can't demod.
float maxAmpl = maxAmplitude(rxBurst);
if (maxAmpl > CLIP_THRESH) {
- LOG(DEBUG) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl;
+ LOG(INFO) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl;
clipping = true;
}
@@ -1643,6 +1802,80 @@ 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;
+ complex _amp;
+ 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_BUFFER:
+ target = 1;
+ head = 0;
+ tail = (12 * 8 * 625) / 4; // 12 frames, downsampled /4 to 1 sps
+ 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;
+ signalVector corr(len);
+
+ signalVector *dec = downsampleBurst(burst, len * 4, len);
+ rc = detectBurst(*dec, corr, sync, thresh, 1, start, len, ebp);
+ delete dec;
+
+ if (rc < 0) {
+ return -1;
+ } else if (!rc) {
+ ebp->amp = 0.0f;
+ ebp->toa = 0.0f;
+ return 0;
+ }
+
+ if (state == sch_detect_type::SCH_DETECT_BUFFER)
+ ebp->toa = ebp->toa - (3 + 39 + 64);
+ else {
+ /* Subtract forward search bits from delay */
+ ebp->toa = ebp->toa - head;
+ }
+
+ return rc;
+}
+
+static int detectDummyBurst(const signalVector &burst, float threshold,
+ int sps, unsigned max_toa, struct estim_burst_params *ebp)
+{
+ int rc, target, head, tail;
+ CorrelationSequence *sync;
+
+ target = 3 + 58 + 16 + 5;
+ head = 10;
+ tail = 6 + max_toa;
+ sync = gDummySequence;
+
+ ebp->tsc = 0;
+ rc = detectGeneralBurst(burst, threshold, sps, target, head, tail, sync, ebp);
+ return rc;
+}
+
/*
* Normal burst detection
*
@@ -1661,7 +1894,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];
@@ -1710,6 +1943,9 @@ int detectAnyBurst(const signalVector &burst, unsigned tsc, float threshold,
case RACH:
rc = detectRACHBurst(burst, threshold, sps, max_toa, type == EXT_RACH, ebp);
break;
+ case IDLE:
+ rc = detectDummyBurst(burst, threshold, sps, max_toa, ebp);
+ break;
default:
LOG(ERR) << "Invalid correlation type";
}
@@ -1792,15 +2028,15 @@ static SoftVector *signalToSoftVector(signalVector *dec)
* stages.
*/
static signalVector *demodCommon(const signalVector &burst, int sps,
- complex chan, float toa)
+ const struct estim_burst_params *ebp)
{
signalVector *delay, *dec;
if ((sps != 1) && (sps != 4))
return NULL;
- delay = delayVector(&burst, NULL, -toa * (float) sps);
- scaleVector(*delay, (complex) 1.0 / chan);
+ delay = delayVector(&burst, NULL, -ebp->toa * (float) sps);
+ scaleVector(*delay, (complex) 1.0 / ebp->amp);
if (sps == 1)
return delay;
@@ -1816,13 +2052,13 @@ static signalVector *demodCommon(const signalVector &burst, int sps,
* 4 SPS (if activated) to minimize distortion through the fractional
* delay filters. Symbol rotation and after always operates at 1 SPS.
*/
-static SoftVector *demodGmskBurst(const signalVector &rxBurst,
- int sps, complex channel, float TOA)
+static SoftVector *demodGmskBurst(const signalVector &rxBurst, int sps,
+ const struct estim_burst_params *ebp)
{
SoftVector *bits;
signalVector *dec;
- dec = demodCommon(rxBurst, sps, channel, TOA);
+ dec = demodCommon(rxBurst, sps, ebp);
if (!dec)
return NULL;
@@ -1835,29 +2071,51 @@ static SoftVector *demodGmskBurst(const signalVector &rxBurst,
return bits;
}
+static float computeEdgeCI(const signalVector *rot)
+{
+ float err_pwr = 0.0f;
+ float step = 2.0f * M_PI_F / 8.0f;
+
+ for (size_t i = 8; i < rot->size() - 8; i++) {
+ /* Compute the ideal symbol */
+ complex sym = (*rot)[i];
+ float phase = step * roundf(sym.arg() / step);
+ complex ideal = complex(cos(phase), sin(phase));
+
+ /* Compute the error vector */
+ complex err = ideal - sym;
+
+ /* Accumulate power */
+ err_pwr += err.norm2();
+ }
+
+ return 3.0103f * log2f(1.0f * (rot->size() - 16) / err_pwr);
+}
+
/*
* Demodulate an 8-PSK burst. Prior to symbol rotation, operate at
* 4 SPS (if activated) to minimize distortion through the fractional
* delay filters. Symbol rotation and after always operates at 1 SPS.
*
* Allow 1 SPS demodulation here, but note that other parts of the
- * transceiver restrict EDGE operatoin to 4 SPS - 8-PSK distortion
+ * transceiver restrict EDGE operation to 4 SPS - 8-PSK distortion
* through the fractional delay filters at 1 SPS renders signal
* nearly unrecoverable.
*/
-static SoftVector *demodEdgeBurst(const signalVector &burst,
- int sps, complex chan, float toa)
+static SoftVector *demodEdgeBurst(const signalVector &burst, int sps,
+ struct estim_burst_params *ebp)
{
SoftVector *bits;
signalVector *dec, *rot, *eq;
- dec = demodCommon(burst, sps, chan, toa);
+ dec = demodCommon(burst, sps, ebp);
if (!dec)
return NULL;
/* Equalize and derotate */
eq = convolve(dec, GSMPulse4->c0_inv, NULL, NO_DELAY);
rot = derotateEdgeBurst(*eq, 1);
+ ebp->ci = computeEdgeCI(rot);
/* Soft slice and normalize */
bits = softSliceEdgeBurst(*rot);
@@ -1869,13 +2127,13 @@ static SoftVector *demodEdgeBurst(const signalVector &burst,
return bits;
}
-SoftVector *demodAnyBurst(const signalVector &burst, int sps, complex amp,
- float toa, CorrType type)
+SoftVector *demodAnyBurst(const signalVector &burst, CorrType type,
+ int sps, struct estim_burst_params *ebp)
{
if (type == EDGE)
- return demodEdgeBurst(burst, sps, amp, toa);
+ return demodEdgeBurst(burst, sps, ebp);
else
- return demodGmskBurst(burst, sps, amp, toa);
+ return demodGmskBurst(burst, sps, ebp);
}
bool sigProcLibSetup()
@@ -1890,6 +2148,9 @@ bool sigProcLibSetup()
generateRACHSequence(&gRACHSequences[1], gRACHSynchSequenceTS1, 1);
generateRACHSequence(&gRACHSequences[2], gRACHSynchSequenceTS2, 1);
+ generateSCHSequence(1);
+ generateDummyMidamble(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 dd03190..39c8ddd 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
};
@@ -93,6 +94,8 @@ signalVector *generateDummyBurst(int sps, int tn);
void scaleVector(signalVector &x,
complex scale);
+signalVector *delayVector(const signalVector *in, signalVector *out, float delay);
+
/**
Rough energy estimator.
@param rxBurst A GSM burst.
@@ -133,8 +136,19 @@ int detectAnyBurst(const signalVector &burst,
unsigned max_toa,
struct estim_burst_params *ebp);
+enum class sch_detect_type {
+ SCH_DETECT_FULL,
+ SCH_DETECT_NARROW,
+ SCH_DETECT_BUFFER,
+};
+
+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, int sps,
- complex amp, float toa, CorrType type);
+SoftVector *demodAnyBurst(const signalVector &burst, CorrType type,
+ int sps, struct estim_burst_params *ebp);
#endif /* SIGPROCLIB_H */