Compare commits

...

8 Commits

Author SHA1 Message Date
Eric
2ecd9f698f trcon<->l1 data if without sockets
direct call of UL rx receive data handler + sched check if ts is active
tx dir call stub for for tx thread queue.

Change-Id: I5911004db58742cf39b968fcf87bc1243f7a374a
2022-10-31 15:01:26 +01:00
Eric
70bd9415a2 trxcon/upper loop thread merge hack
Change-Id: Ia42f9df3050d7e6cc558f4d60e08955a0fa70b8a
2022-10-31 14:02:30 +01:00
Eric
2c12b30ace new ms
Change-Id: I7c5abe57182e7ef508cac4068c0b41f905d39fd6
2022-10-31 14:02:11 +01:00
Eric
607141bf34 clean up mutex, scopedlock, and signal classes
This also uncovers very interesting design decisions like the copying of
mutexes and condition vars depending on recursive locks that were
previously hidden by shady c function calls..
We have perfectly good c++11 versions for all of that.

While we're at it, also use the initialization list for the other (still
copy constructable) vectors, which cleans up the radio interfaces.

Change-Id: Idc9e3b1144c5b93f5dad2f8e0e30f1058477aa52
2022-07-19 21:23:06 +02:00
Eric Wild
901f689086 add linked trxcon copy
Change-Id: Ic746c26e527f4505188729d6e5df47435daac96f
2022-07-19 21:22:34 +02:00
Eric
966af04ff1 rename noisevector class -> avgvector
The vectors feature is averaging, and not adding noise.

Change-Id: I05def8ab9ea7a2cece8db09c36c303e13ef40927
2022-07-19 21:22:34 +02:00
Eric
d5cafc2cc0 xray ignores
tiny functions, do not want.

Change-Id: Ie55458f31d16e76e84855ed2c634a9dd9a5e139b
2022-07-19 21:22:34 +02:00
Eric
b8ef806c25 ignore vscode dirs
Change-Id: Iad9fd20924b7cfc6dbbfb708aa9c692a3cab574c
2022-07-19 21:04:20 +02:00
93 changed files with 18340 additions and 269 deletions

12
.gitignore vendored
View File

@@ -6,6 +6,15 @@ Transceiver52M/osmo-trx-uhd
Transceiver52M/osmo-trx-usrp1
Transceiver52M/osmo-trx-lms
Transceiver52M/osmo-trx-ipc
Transceiver52M/osmo-trx-blade
Transceiver52M/osmo-trx-ipc2
Transceiver52M/osmo-trx-syncthing-blade
Transceiver52M/osmo-trx-syncthing-uhd
Transceiver52M/osmo-trx-syncthing-ipc
Transceiver52M/osmo-trx-ms-blade
Transceiver52M/osmo-trx-ms-uhd
Transceiver52M/osmo-trx-ms-ipc
.clang-format
@@ -74,3 +83,6 @@ contrib/osmo-trx.spec
!contrib/osmo-trx.spec.in
utils/osmo-prbs-tool
/.qtc_clangd/*
/.cache/*
/.vscode/*

View File

@@ -517,7 +517,7 @@ public:
@param timeout The blocking timeout in ms.
@return Pointer at key or NULL on timeout.
*/
D* read(const K &key, unsigned timeout) const
D* read(const K &key, unsigned timeout)
{
if (timeout==0) return readNoBlock(key);
ScopedLock lock(mLock);
@@ -537,7 +537,7 @@ public:
@param key The key to read from.
@return Pointer at key.
*/
D* read(const K &key) const
D* read(const K &key)
{
ScopedLock lock(mLock);
typename Map::const_iterator iter = mMap.find(key);

View File

@@ -43,71 +43,6 @@ using namespace std;
#endif
Mutex gStreamLock; ///< Global lock to control access to cout and cerr.
void lockCout()
{
gStreamLock.lock();
Timeval entryTime;
cout << entryTime << " " << osmo_gettid() << ": ";
}
void unlockCout()
{
cout << dec << endl << flush;
gStreamLock.unlock();
}
void lockCerr()
{
gStreamLock.lock();
Timeval entryTime;
cerr << entryTime << " " << osmo_gettid() << ": ";
}
void unlockCerr()
{
cerr << dec << endl << flush;
gStreamLock.unlock();
}
Mutex::Mutex()
{
bool res;
res = pthread_mutexattr_init(&mAttribs);
assert(!res);
res = pthread_mutexattr_settype(&mAttribs,PTHREAD_MUTEX_RECURSIVE);
assert(!res);
res = pthread_mutex_init(&mMutex,&mAttribs);
assert(!res);
}
Mutex::~Mutex()
{
pthread_mutex_destroy(&mMutex);
bool res = pthread_mutexattr_destroy(&mAttribs);
assert(!res);
}
/** Block for the signal up to the cancellation timeout. */
void Signal::wait(Mutex& wMutex, unsigned timeout) const
{
Timeval then(timeout);
struct timespec waitTime = then.timespec();
pthread_cond_timedwait(&mSignal,&wMutex.mMutex,&waitTime);
}
void set_selfthread_name(const char *name)
{

View File

@@ -28,143 +28,96 @@
#ifndef THREADS_H
#define THREADS_H
#include "config.h"
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <pthread.h>
#include <iostream>
#include <assert.h>
#include <cassert>
#include <unistd.h>
#include "config.h"
#include "Timeval.h"
class Mutex;
/**@name Multithreaded access for standard streams. */
//@{
/**@name Functions for gStreamLock. */
//@{
extern Mutex gStreamLock; ///< global lock for cout and cerr
void lockCerr(); ///< call prior to writing cerr
void unlockCerr(); ///< call after writing cerr
void lockCout(); ///< call prior to writing cout
void unlockCout(); ///< call after writing cout
//@}
/**@name Macros for standard messages. */
//@{
#define COUT(text) { lockCout(); std::cout << text; unlockCout(); }
#define CERR(text) { lockCerr(); std::cerr << __FILE__ << ":" << __LINE__ << ": " << text; unlockCerr(); }
#ifdef NDEBUG
#define DCOUT(text) {}
#define OBJDCOUT(text) {}
#else
#define DCOUT(text) { COUT(__FILE__ << ":" << __LINE__ << " " << text); }
#define OBJDCOUT(text) { DCOUT(this << " " << text); }
#endif
//@}
//@}
/**@defgroup C++ wrappers for pthread mechanisms. */
//@{
/** A class for recursive mutexes based on pthread_mutex. */
/** A class for recursive mutexes. */
class Mutex {
std::recursive_mutex m;
private:
public:
pthread_mutex_t mMutex;
pthread_mutexattr_t mAttribs;
void lock() {
m.lock();
}
public:
bool trylock() {
return m.try_lock();
}
Mutex();
~Mutex();
void lock() { pthread_mutex_lock(&mMutex); }
bool trylock() { return pthread_mutex_trylock(&mMutex)==0; }
void unlock() { pthread_mutex_unlock(&mMutex); }
void unlock() {
m.unlock();
}
friend class Signal;
};
class ScopedLock {
Mutex &mMutex;
private:
Mutex& mMutex;
public:
ScopedLock(Mutex& wMutex) :mMutex(wMutex) { mMutex.lock(); }
~ScopedLock() { mMutex.unlock(); }
public:
ScopedLock(Mutex &wMutex) : mMutex(wMutex) {
mMutex.lock();
}
~ScopedLock() {
mMutex.unlock();
}
};
/** A C++ interthread signal based on pthread condition variables. */
/** A C++ interthread signal. */
class Signal {
/* any, because for some reason our mutex is recursive... */
std::condition_variable_any mSignal;
private:
public:
mutable pthread_cond_t mSignal;
void wait(Mutex &wMutex, unsigned timeout) {
mSignal.wait_for(wMutex.m, std::chrono::milliseconds(timeout));
}
public:
void wait(Mutex &wMutex) {
mSignal.wait(wMutex.m);
}
Signal() { int s = pthread_cond_init(&mSignal,NULL); assert(!s); }
~Signal() { pthread_cond_destroy(&mSignal); }
/**
Block for the signal up to the cancellation timeout.
Under Linux, spurious returns are possible.
*/
void wait(Mutex& wMutex, unsigned timeout) const;
/**
Block for the signal.
Under Linux, spurious returns are possible.
*/
void wait(Mutex& wMutex) const
{ pthread_cond_wait(&mSignal,&wMutex.mMutex); }
void signal() { pthread_cond_signal(&mSignal); }
void broadcast() { pthread_cond_broadcast(&mSignal); }
void signal() {
mSignal.notify_one();
}
void broadcast() {
mSignal.notify_all();
}
};
#define START_THREAD(thread,function,argument) \
thread.start((void *(*)(void*))function, (void*)argument);
void set_selfthread_name(const char *name);
void thread_enable_cancel(bool cancel);
/** A C++ wrapper for pthread threads. */
class Thread {
private:
private:
pthread_t mThread;
pthread_attr_t mAttrib;
// FIXME -- Can this be reduced now?
size_t mStackSize;
public:
public:
/** Create a thread in a non-running state. */
Thread(size_t wStackSize = 0):mThread((pthread_t)0) {
pthread_attr_init(&mAttrib); // (pat) moved this here.
mStackSize=wStackSize;
Thread(size_t wStackSize = 0) : mThread((pthread_t)0)
{
pthread_attr_init(&mAttrib); // (pat) moved this here.
mStackSize = wStackSize;
}
/**
@@ -172,14 +125,17 @@ class Thread {
It should be stopped and joined.
*/
// (pat) If the Thread is destroyed without being started, then mAttrib is undefined. Oops.
~Thread() { pthread_attr_destroy(&mAttrib); }
~Thread()
{
pthread_attr_destroy(&mAttrib);
}
/** Start the thread on a task. */
void start(void *(*task)(void*), void *arg);
void start(void *(*task)(void *), void *arg);
/** Join a thread that will stop on its own. */
void join() {
void join()
{
if (mThread) {
int s = pthread_join(mThread, NULL);
assert(!s);
@@ -187,7 +143,10 @@ class Thread {
}
/** Send cancellation to thread */
void cancel() { pthread_cancel(mThread); }
void cancel()
{
pthread_cancel(mThread);
}
};
#ifdef HAVE_ATOMIC_OPS

View File

@@ -55,12 +55,15 @@ const BitVector GSM::gEdgeTrainingSequence[] = {
};
const BitVector GSM::gDummyBurst("0001111101101110110000010100100111000001001000100000001111100011100010111000101110001010111010010100011001100111001111010011111000100101111101010000");
const BitVector GSM::gDummyBurstTSC("01110001011100010111000101");
/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */
const BitVector GSM::gRACHSynchSequenceTS0("01001011011111111001100110101010001111000"); /* GSM, GMSK (default) */
const BitVector GSM::gRACHSynchSequenceTS1("01010100111110001000011000101111001001101"); /* EGPRS, 8-PSK */
const BitVector GSM::gRACHSynchSequenceTS2("11101111001001110101011000001101101110111"); /* EGPRS, GMSK */
const BitVector GSM::gSCHSynchSequence("1011100101100010000001000000111100101101010001010111011000011011");
// |-head-||---------midamble----------------------||--------------data----------------||t|
const BitVector GSM::gRACHBurst("0011101001001011011111111001100110101010001111000110111101111110000111001001010110011000");

View File

@@ -52,11 +52,16 @@ extern const BitVector gEdgeTrainingSequence[];
/** C0T0 filler burst, GSM 05.02, 5.2.6 */
extern const BitVector gDummyBurst;
extern const BitVector gDummyBurstTSC;
/** Random access burst synch. sequence */
extern const BitVector gRACHSynchSequenceTS0;
extern const BitVector gRACHSynchSequenceTS1;
extern const BitVector gRACHSynchSequenceTS2;
/** Synchronization burst sync sequence */
extern const BitVector gSCHSynchSequence;
/** Random access burst synch. sequence, GSM 05.02 5.2.7 */
extern const BitVector gRACHBurst;

View File

@@ -28,6 +28,7 @@ AM_CXXFLAGS = -Wall -pthread
# Order must be preserved
SUBDIRS = \
trxcon \
CommonLibs \
GSM \
Transceiver52M \

View File

@@ -28,6 +28,7 @@ STD_DEFINES_AND_INCLUDES = \
COMMON_LA = $(top_builddir)/CommonLibs/libcommon.la
GSM_LA = $(top_builddir)/GSM/libGSM.la
TRXCON_LA = $(top_builddir)/trxcon/libtrxcon.la
if ARCH_ARM
ARCH_LA = $(top_builddir)/Transceiver52M/arch/arm/libarch.la

View File

@@ -24,12 +24,13 @@ include $(top_srcdir)/Makefile.common
SUBDIRS = arch device
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/arch/common -I${srcdir}/device/common
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -mcpu=cortex-a72 -mfloat-abi=hard -mfpu=neon-fp-armv8
AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -mcpu=cortex-a72 -mfloat-abi=hard -mfpu=neon-fp-armv8
noinst_LTLIBRARIES = libtransceiver_common.la
COMMON_SOURCES = \
l1if.cpp \
radioInterface.cpp \
radioVector.cpp \
radioClock.cpp \
@@ -40,7 +41,10 @@ COMMON_SOURCES = \
ChannelizerBase.cpp \
Channelizer.cpp \
Synthesis.cpp \
proto_trxd.c
proto_trxd.c \
sch.c \
grgsm_vitac/grgsm_vitac.cpp \
grgsm_vitac/viterbi_detector.cc
libtransceiver_common_la_SOURCES = \
$(COMMON_SOURCES) \
@@ -61,7 +65,9 @@ noinst_HEADERS = \
ChannelizerBase.h \
Channelizer.h \
Synthesis.h \
proto_trxd.h
proto_trxd.h \
grgsm_vitac/viterbi_detector.h \
grgsm_vitac/constants.h
COMMON_LDADD = \
libtransceiver_common.la \
@@ -70,6 +76,7 @@ COMMON_LDADD = \
$(COMMON_LA) \
$(FFTWF_LIBS) \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOCODING_LIBS) \
$(LIBOSMOCTRL_LIBS) \
$(LIBOSMOVTY_LIBS)
@@ -83,6 +90,24 @@ osmo_trx_uhd_LDADD = \
$(COMMON_LDADD) \
$(UHD_LIBS)
osmo_trx_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS)
bin_PROGRAMS += osmo-trx-ms-uhd
osmo_trx_ms_uhd_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_upper.cpp ms/ms_commandhandler.cpp
osmo_trx_ms_uhd_LDADD = \
$(builddir)/device/bladerf/libdevice.la \
$(COMMON_LDADD) \
$(UHD_LIBS) \
$(TRXCON_LA)
osmo_trx_ms_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) -DBUILDUHD
bin_PROGRAMS += osmo-trx-syncthing-uhd
osmo_trx_syncthing_uhd_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_burst.cpp
osmo_trx_syncthing_uhd_LDADD = \
$(builddir)/device/bladerf/libdevice.la \
$(COMMON_LDADD) \
$(UHD_LIBS)
osmo_trx_syncthing_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) -DSYNCTHINGONLY -DBUILDUHD
#osmo_trx_syncthing_LDFLAGS = -fsanitize=address,undefined -shared-libsan
endif
if DEVICE_USRP1
@@ -105,6 +130,34 @@ 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)
bin_PROGRAMS += osmo-trx-ms-blade
osmo_trx_ms_blade_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_upper.cpp ms/ms_commandhandler.cpp
osmo_trx_ms_blade_LDADD = \
$(builddir)/device/bladerf/libdevice.la \
$(COMMON_LDADD) \
$(BLADE_LIBS) \
$(TRXCON_LA)
osmo_trx_ms_blade_CPPFLAGS = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DBUILDBLADE
bin_PROGRAMS += osmo-trx-syncthing-blade
osmo_trx_syncthing_blade_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_burst.cpp
osmo_trx_syncthing_blade_LDADD = \
$(builddir)/device/bladerf/libdevice.la \
$(COMMON_LDADD) \
$(BLADE_LIBS)
osmo_trx_syncthing_blade_CPPFLAGS = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DSYNCTHINGONLY -DBUILDBLADE -mcpu=cortex-a72 -mfloat-abi=hard -mfpu=neon-fp-armv8 -I../device/ipc
#osmo_trx_syncthing_LDFLAGS = -fsanitize=address,undefined -shared-libsan
endif
if DEVICE_IPC
bin_PROGRAMS += osmo-trx-ipc
osmo_trx_ipc_SOURCES = osmo-trx.cpp
@@ -112,5 +165,32 @@ osmo_trx_ipc_LDADD = \
$(builddir)/device/ipc/libdevice.la \
$(COMMON_LDADD)
osmo_trx_ipc_CPPFLAGS = $(AM_CPPFLAGS)
bin_PROGRAMS += osmo-trx-ipc2
osmo_trx_ipc2_SOURCES = osmo-trx.cpp
osmo_trx_ipc2_LDADD = \
$(builddir)/device/ipc2/libdevice.la \
$(COMMON_LDADD)
osmo_trx_ipc2_CPPFLAGS = $(AM_CPPFLAGS)
bin_PROGRAMS += osmo-trx-ms-ipc
osmo_trx_ms_ipc_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_upper.cpp ms/ms_commandhandler.cpp
osmo_trx_ms_ipc_LDADD = \
$(COMMON_LDADD) \
$(TRXCON_LA)
osmo_trx_ms_ipc_CPPFLAGS = $(AM_CPPFLAGS) -DBUILDIPC -I./device/ipc2 -I../device/ipc2
bin_PROGRAMS += osmo-trx-syncthing-ipc
osmo_trx_syncthing_ipc_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_burst.cpp
osmo_trx_syncthing_ipc_LDADD = $(COMMON_LDADD)
osmo_trx_syncthing_ipc_CPPFLAGS = $(AM_CPPFLAGS) -DSYNCTHINGONLY -DBUILDIPC -I./device/ipc2 -I../device/ipc2
endif
noinst_HEADERS += \
ms/syncthing.h \
ms/bladerf_specific.h \
ms/uhd_specific.h \
ms/ms_rx_upper.h \
itrq.h
# -fsanitize=address,undefined -shared-libsan -O0
#

View File

@@ -32,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;

View File

@@ -135,12 +135,13 @@ bool TransceiverState::init(FillerType filler, size_t sps, float scale, size_t r
Transceiver::Transceiver(const struct trx_cfg *cfg,
GSM::Time wTransmitLatency,
RadioInterface *wRadioInterface)
: cfg(cfg), mClockSocket(-1),
mRxLowerLoopThread(nullptr), mTxLowerLoopThread(nullptr),
mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface),
mChans(cfg->num_chans), mOn(false), mForceClockInterface(false),
mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0),
mMaxExpectedDelayNB(0), mWriteBurstToDiskMask(0)
: 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();
@@ -208,14 +209,7 @@ bool Transceiver::init()
}
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 (cfg->filler == FILLER_DUMMY)

View File

@@ -80,7 +80,7 @@ struct TransceiverState {
/* Received noise energy levels */
float mNoiseLev;
noiseVector mNoises;
avgVector mNoises;
/* Shadowed downlink attenuation */
int mPower;
@@ -148,6 +148,7 @@ public:
} ChannelCombination;
private:
size_t mChans;
struct ctrl_msg {
char data[101];
ctrl_msg() {};
@@ -218,7 +219,7 @@ struct ctrl_sock_state {
/** drive handling of control messages from GSM core */
int ctrl_sock_handle_rx(int chan);
size_t mChans;
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

View File

@@ -17,6 +17,7 @@
#include "convert.h"
__attribute__((xray_never_instrument))
void base_convert_float_short(short *out, const float *in,
float scale, int len)
{
@@ -24,6 +25,7 @@ void base_convert_float_short(short *out, const float *in,
out[i] = in[i] * scale;
}
__attribute__((xray_never_instrument))
void base_convert_short_float(float *out, const short *in, int len)
{
for (int i = 0; i < len; i++)

View File

@@ -24,6 +24,7 @@
#endif
/* Base multiply and accumulate complex-real */
__attribute__((xray_never_instrument))
static void mac_real(const float *x, const float *h, float *y)
{
y[0] += x[0] * h[0];
@@ -31,6 +32,7 @@ static void mac_real(const float *x, const float *h, float *y)
}
/* Base multiply and accumulate complex-complex */
__attribute__((xray_never_instrument))
static void mac_cmplx(const float *x, const float *h, float *y)
{
y[0] += x[0] * h[0] - x[1] * h[1];
@@ -38,6 +40,7 @@ static void mac_cmplx(const float *x, const float *h, float *y)
}
/* Base vector complex-complex multiply and accumulate */
__attribute__((xray_never_instrument))
static void mac_real_vec_n(const float *x, const float *h, float *y,
int len)
{
@@ -46,6 +49,7 @@ static void mac_real_vec_n(const float *x, const float *h, float *y,
}
/* Base vector complex-complex multiply and accumulate */
__attribute__((xray_never_instrument))
static void mac_cmplx_vec_n(const float *x, const float *h, float *y,
int len)
{
@@ -54,6 +58,7 @@ static void mac_cmplx_vec_n(const float *x, const float *h, float *y,
}
/* Base complex-real convolution */
__attribute__((xray_never_instrument))
int _base_convolve_real(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
@@ -69,6 +74,7 @@ int _base_convolve_real(const float *x, int x_len,
}
/* Base complex-complex convolution */
__attribute__((xray_never_instrument))
int _base_convolve_complex(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
@@ -85,6 +91,7 @@ int _base_convolve_complex(const float *x, int x_len,
}
/* Buffer validity checks */
__attribute__((xray_never_instrument))
int bounds_check(int x_len, int h_len, int y_len,
int start, int len)
{
@@ -105,6 +112,7 @@ int bounds_check(int x_len, int h_len, int y_len,
}
/* API: Non-aligned (no SSE) complex-real */
__attribute__((xray_never_instrument))
int base_convolve_real(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
@@ -122,6 +130,7 @@ int base_convolve_real(const float *x, int x_len,
}
/* API: Non-aligned (no SSE) complex-complex */
__attribute__((xray_never_instrument))
int base_convolve_complex(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
@@ -139,6 +148,7 @@ int base_convolve_complex(const float *x, int x_len,
}
/* Aligned filter tap allocation */
__attribute__((xray_never_instrument))
void *convolve_h_alloc(size_t len)
{
#ifdef HAVE_SSE3

View File

@@ -60,6 +60,7 @@ void convert_init(void)
#endif
}
__attribute__((xray_never_instrument))
void convert_float_short(short *out, const float *in, float scale, int len)
{
if (!(len % 16))
@@ -70,6 +71,7 @@ void convert_float_short(short *out, const float *in, float scale, int len)
c.convert_scale_ps_si16(out, in, scale, len);
}
__attribute__((xray_never_instrument))
void convert_short_float(float *out, const short *in, int len)
{
if (!(len % 16))

View File

@@ -26,6 +26,7 @@
#include <emmintrin.h>
/* 8*N single precision floats scaled and converted to 16-bit signed integer */
__attribute__((xray_never_instrument))
void _sse_convert_scale_ps_si16_8n(short *restrict out,
const float *restrict in,
float scale, int len)
@@ -54,6 +55,7 @@ void _sse_convert_scale_ps_si16_8n(short *restrict out,
}
/* 8*N single precision floats scaled and converted with remainder */
__attribute__((xray_never_instrument))
void _sse_convert_scale_ps_si16(short *restrict out,
const float *restrict in, float scale, int len)
{
@@ -66,6 +68,7 @@ void _sse_convert_scale_ps_si16(short *restrict out,
}
/* 16*N single precision floats scaled and converted to 16-bit signed integer */
__attribute__((xray_never_instrument))
void _sse_convert_scale_ps_si16_16n(short *restrict out,
const float *restrict in,
float scale, int len)

View File

@@ -25,6 +25,7 @@
#include <smmintrin.h>
/* 16*N 16-bit signed integer converted to single precision floats */
__attribute__((xray_never_instrument))
void _sse_convert_si16_ps_16n(float *restrict out,
const short *restrict in, int len)
{
@@ -59,6 +60,7 @@ void _sse_convert_si16_ps_16n(float *restrict out,
}
/* 16*N 16-bit signed integer conversion with remainder */
__attribute__((xray_never_instrument))
void _sse_convert_si16_ps(float *restrict out,
const short *restrict in, int len)
{

View File

@@ -91,6 +91,7 @@ void convolve_init(void)
}
/* API: Aligned complex-real */
__attribute__((xray_never_instrument))
int convolve_real(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len, int start, int len)
@@ -130,6 +131,7 @@ int convolve_real(const float *x, int x_len,
}
/* API: Aligned complex-complex */
__attribute__((xray_never_instrument))
int convolve_complex(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,

View File

@@ -27,6 +27,7 @@
#include <pmmintrin.h>
/* 4-tap SSE complex-real convolution */
__attribute__((xray_never_instrument))
void sse_conv_real4(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
@@ -68,6 +69,7 @@ void sse_conv_real4(const float *x, int x_len,
}
/* 8-tap SSE complex-real convolution */
__attribute__((xray_never_instrument))
void sse_conv_real8(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
@@ -119,6 +121,7 @@ void sse_conv_real8(const float *x, int x_len,
}
/* 12-tap SSE complex-real convolution */
__attribute__((xray_never_instrument))
void sse_conv_real12(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
@@ -185,6 +188,7 @@ void sse_conv_real12(const float *x, int x_len,
}
/* 16-tap SSE complex-real convolution */
__attribute__((xray_never_instrument))
void sse_conv_real16(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
@@ -264,6 +268,7 @@ void sse_conv_real16(const float *x, int x_len,
}
/* 20-tap SSE complex-real convolution */
__attribute__((xray_never_instrument))
void sse_conv_real20(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
@@ -354,6 +359,7 @@ void sse_conv_real20(const float *x, int x_len,
}
/* 4*N-tap SSE complex-real convolution */
__attribute__((xray_never_instrument))
void sse_conv_real4n(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
@@ -401,6 +407,7 @@ void sse_conv_real4n(const float *x, int x_len,
}
/* 4*N-tap SSE complex-complex convolution */
__attribute__((xray_never_instrument))
void sse_conv_cmplx_4n(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
@@ -459,6 +466,7 @@ void sse_conv_cmplx_4n(const float *x, int x_len,
}
/* 8*N-tap SSE complex-complex convolution */
__attribute__((xray_never_instrument))
void sse_conv_cmplx_8n(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,

View File

@@ -3,7 +3,7 @@ include $(top_srcdir)/Makefile.common
SUBDIRS = common
if DEVICE_IPC
SUBDIRS += ipc
SUBDIRS += ipc ipc2
endif
if DEVICE_USRP1
@@ -17,3 +17,7 @@ endif
if DEVICE_LMS
SUBDIRS += lms
endif
if DEVICE_BLADE
SUBDIRS += bladerf
endif

View File

@@ -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

View File

@@ -0,0 +1,777 @@
/*
* 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 USRP_TX_AMPL 0.3
#define UMTRX_TX_AMPL 0.7
#define LIMESDR_TX_AMPL 0.3
#define SAMPLE_BUF_SZ (1 << 20)
/*
* UHD timeout value on streaming (re)start
*
* Allow some time for streaming to commence after the start command is issued,
* but consider a wait beyond one second to be a definite error condition.
*/
#define UHD_RESTART_TIMEOUT 1.0
/*
* UmTRX specific settings
*/
#define UMTRX_VGA1_DEF -18
/*
* USRP version dependent device timings
*/
#define B2XX_TIMING_1SPS 1.7153e-4
#define B2XX_TIMING_4SPS 1.1696e-4
#define B2XX_TIMING_4_4SPS 6.18462e-5
#define B2XX_TIMING_MCBTS 7e-5
#define CHKRET() { \
if(status !=0) \
fprintf(stderr, "%s:%s:%d %s\n", __FILE__,__FUNCTION__, __LINE__, bladerf_strerror(status)); \
}
/*
* Tx / Rx sample offset values. In a perfect world, there is no group delay
* though analog components, and behaviour through digital filters exactly
* matches calculated values. In reality, there are unaccounted factors,
* which are captured in these empirically measured (using a loopback test)
* timing correction values.
*
* Notes:
* USRP1 with timestamps is not supported by UHD.
*/
/* Device Type, Tx-SPS, Rx-SPS */
typedef std::tuple<blade_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 {
{ std::make_tuple(blade_dev_type::BLADE2, 1, 1), { 1, 26e6, GSMRATE, B2XX_TIMING_1SPS, "B200 1 SPS" } },
{ std::make_tuple(blade_dev_type::BLADE2, 4, 1), { 1, 26e6, GSMRATE, B2XX_TIMING_4SPS, "B200 4/1 Tx/Rx SPS" } },
{ std::make_tuple(blade_dev_type::BLADE2, 4, 4), { 1, 26e6, GSMRATE, B2XX_TIMING_4_4SPS, "B200 4 SPS" } },
};
typedef std::tuple<blade_dev_type, enum gsm_band> dev_band_key;
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(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(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),
dev(nullptr), rx_gain_min(0.0), rx_gain_max(0.0),
band_ass_curr_sess(false), band((enum gsm_band)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::assign_band_desc(enum gsm_band req_band)
{
dev_band_map_it it;
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 Power parameters 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(blade_dev_type::BLADE2, req_band));
}
OSMO_ASSERT(it != dev_band_nom_power_param_map.end())
band_desc = it->second;
}
bool blade_device::set_band(enum gsm_band req_band)
{
if (band_ass_curr_sess && req_band != band) {
LOGC(DDEV, ALERT) << "Requesting band " << gsm_band_name(req_band)
<< " different from previous band " << gsm_band_name(band);
return false;
}
if (req_band != band) {
band = req_band;
assign_band_desc(band);
}
band_ass_curr_sess = true;
return true;
}
void blade_device::get_dev_band_desc(dev_band_desc& desc)
{
if (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 = band_desc;
}
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);//gain);
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);//gain);
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);//gain);
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()
{
//dev_desc desc = dev_param_map.at(dev_key(dev_type, tx_sps, rx_sps));
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;//static_cast<TIMESTAMP>(desc.offset * rx_rate);
//LOGC(DDEV, INFO) << "Rates configured for " << desc.str;
}
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);//db);
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(const std::string &args, int ref, bool swap_channels)
{
bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_VERBOSE);
bladerf_set_usb_reset_on_open(true);
auto success = bladerf_open(&dev, args.c_str());
if(success != 0) {
struct bladerf_devinfo* info;
auto num_devs = bladerf_get_device_list(&info);
LOGC(DDEV, ALERT) << "No bladerf devices found with identifier '" << 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;
struct bladerf_devinfo info;
bladerf_get_devinfo(dev, &info);
// Use the first found device
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 (ref) {
case REF_INTERNAL:
case REF_EXTERNAL:
break;
default:
LOGC(DDEV, ALERT) << "Invalid reference type";
return -1;
}
if(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 " << ((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;
// Create receive buffer
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);
// Create vector buffer
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());
// Initialize and shadow gain values
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;
}
// Start streaming
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_ass_curr_sess = false;
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;
//static bool first_rx = true;
// meta.timestamp = (first_rx) ? ts : 0;
// meta.flags = (!first_rx) ? 0:BLADERF_META_FLAG_RX_NOW;
// if(first_rx)
// first_rx = false;
// Receive samples from the usrp until we have enough
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)
std::cerr << "RX fucked: " << bladerf_strerror(status);
if(meta.flags & BLADERF_META_STATUS_OVERRUN )
std::cerr << "RX fucked OVER: " << 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;
}
// We have enough samples
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);
//size_t num_smpls = tx_stream->send(bufs, len, metadata);
thread_enable_cancel(true);
if(status != 0)
std::cerr << "TX fucked: " << bladerf_strerror(status);
// LOGCHAN(0, DDEV, INFO) << "tx " << timestamp << " " << len << " t+l: "<< timestamp+len << std::endl;
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)
{
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 (!set_band(req_band))
return false;
if (!set_freq(wFreq, chan, true))
return false;
return true;
}
bool blade_device::setRxFreq(double wFreq, size_t chan)
{
uint16_t req_arfcn;
enum gsm_band req_band;
if (chan >= rx_freqs.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
return false;
}
ScopedLock lock(tune_lock);
req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 1);
if (req_arfcn == 0xffff) {
LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Rx Frequency " << wFreq / 1000 << " kHz";
return false;
}
if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Rx Frequency " << wFreq
<< " Hz (ARFCN " << req_arfcn << " )";
return false;
}
if (!set_band(req_band))
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() {
/* Empirical data from a handful of
relatively recent machines shows that the B100 will underrun when
the transmit threshold is reduced to a time of 6 and a half frames,
so we set a minimum 7 frame threshold. */
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;
}
#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)
{
return new blade_device(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
}
#endif

View File

@@ -0,0 +1,171 @@
/*
* 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
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "radioDevice.h"
#include "smpl_buf.h"
extern "C" {
#include <osmocom/gsm/gsm_utils.h>
}
#include <bladerf.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 */
};
class blade_device : public RadioDevice {
public:
blade_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);
~blade_device();
int open(const std::string &args, int ref, bool swap_channels);
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;
bool band_ass_curr_sess; /* true if "band" was set after last POWEROFF */
enum gsm_band band;
struct dev_band_desc band_desc;
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);
void get_dev_band_desc(dev_band_desc& desc);
bool set_band(enum gsm_band req_band);
void assign_band_desc(enum gsm_band req_band);
Thread *async_event_thrd;
Mutex tune_lock;
};

View File

@@ -169,14 +169,13 @@ class RadioDevice {
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)
tx_paths(tx_paths), rx_paths(rx_paths), m_ctr(chans)
{
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;

View File

@@ -58,18 +58,12 @@ 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)
: RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths), 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));

View File

@@ -0,0 +1,314 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@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 <sys/time.h>
#include <osmocom/core/timer_compat.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "Logger.h"
#include "Threads.h"
#include "IPCDevice.h"
#include "smpl_buf.h"
#define SAMPLE_BUF_SZ (1 << 20)
static const auto ONE_BIT_DURATION ((12./5200.)/(156.25*4.));
static const auto ONE_SAMPLE_DURATION_US ((ONE_BIT_DURATION/4.)*1000*1000);
using namespace std;
IPCDevice2::IPCDevice2(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_buffers(chans),
started(false), tx_gains(chans), rx_gains(chans)
{
LOGC(DDEV, INFO) << "creating IPC device...";
if (!(tx_sps == 4) || !(rx_sps == 4)) {
LOGC(DDEV, FATAL) << "IPC shm if create failed!";
exit(0);
}
/* 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));
if (!m.create()) {
LOGC(DDEV, FATAL) << "IPC shm if create failed!";
exit(0);
}
}
IPCDevice2::~IPCDevice2()
{
LOGC(DDEV, INFO) << "Closing IPC device";
/* disable all channels */
for (size_t i = 0; i < rx_buffers.size(); i++)
delete rx_buffers[i];
}
int IPCDevice2::open(const std::string &args, int ref, bool swap_channels)
{
std::string k, v;
/* configure antennas */
if (!set_antennas()) {
LOGC(DDEV, FATAL) << "IPC antenna setting failed";
goto out_close;
}
return iface == MULTI_ARFCN ? MULTI_ARFCN : NORMAL;
out_close:
LOGC(DDEV, FATAL) << "Error in IPC open, closing";
return -1;
}
bool IPCDevice2::start()
{
LOGC(DDEV, INFO) << "starting IPC...";
if (started) {
LOGC(DDEV, ERR) << "Device already started";
return true;
}
int max_bufs_to_flush = 120;
flush_recv(max_bufs_to_flush);
started = true;
return true;
}
bool IPCDevice2::stop()
{
if (!started)
return true;
LOGC(DDEV, NOTICE) << "All channels stopped, terminating...";
/* reset internal buffer timestamps */
for (size_t i = 0; i < rx_buffers.size(); i++)
rx_buffers[i]->reset();
started = false;
return true;
}
double IPCDevice2::maxRxGain()
{
return 70;
}
double IPCDevice2::minRxGain()
{
return 0;
}
int IPCDevice2::getNominalTxPower(size_t chan)
{
return 10;
}
double IPCDevice2::setPowerAttenuation(int atten, size_t chan)
{
return atten;
}
double IPCDevice2::getPowerAttenuation(size_t chan)
{
return 0;
}
double IPCDevice2::setRxGain(double dB, size_t chan)
{
if (dB > maxRxGain())
dB = maxRxGain();
if (dB < minRxGain())
dB = minRxGain();
LOGCHAN(chan, DDEV, NOTICE) << "Setting RX gain to " << dB << " dB";
return dB;
}
bool IPCDevice2::flush_recv(size_t num_pkts)
{
ts_initial = 10000;
LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl;
return true;
}
bool IPCDevice2::setRxAntenna(const std::string &ant, size_t chan)
{
return true;
}
std::string IPCDevice2::getRxAntenna(size_t chan)
{
return "";
}
bool IPCDevice2::setTxAntenna(const std::string &ant, size_t chan)
{
return true;
}
std::string IPCDevice2::getTxAntenna(size_t chan)
{
return "";
}
bool IPCDevice2::requiresRadioAlign()
{
return false;
}
GSM::Time IPCDevice2::minLatency()
{
/* UNUSED */
return GSM::Time(0, 0);
}
/** Returns the starting write Timestamp*/
TIMESTAMP IPCDevice2::initialWriteTimestamp(void)
{
return ts_initial;
}
/** Returns the starting read Timestamp*/
TIMESTAMP IPCDevice2::initialReadTimestamp(void)
{
return ts_initial;
}
static timespec readtime, writetime;
static void wait_for_sample_time(timespec* last, unsigned int len) {
timespec ts, diff;
clock_gettime(CLOCK_MONOTONIC, &ts);
timespecsub(&ts, last, &diff);
auto elapsed_us = (diff.tv_sec * 1000000) + (diff.tv_nsec / 1000);
auto max_wait_us = ONE_SAMPLE_DURATION_US * len;
if(elapsed_us < max_wait_us)
usleep(max_wait_us-elapsed_us);
*last = ts;
}
// NOTE: Assumes sequential reads
int IPCDevice2::readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun)
{
int rc, num_smpls; //, expect_smpls;
ssize_t avail_smpls;
unsigned int i = 0;
*overrun = false;
*underrun = false;
timestamp += 0;
/* 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;
}
/* Receive samples from HW until we have enough */
while ((avail_smpls = rx_buffers[i]->avail_smpls(timestamp)) < len) {
uint64_t recv_timestamp = timestamp;
m.read_ul(len - avail_smpls, &recv_timestamp, reinterpret_cast<sample_t *>(bufs[0]));
num_smpls = len - avail_smpls;
wait_for_sample_time(&readtime, num_smpls);
if (num_smpls == -ETIMEDOUT)
continue;
LOGCHAN(i, DDEV, DEBUG)
"Received timestamp = " << (TIMESTAMP)recv_timestamp << " (" << num_smpls << ")";
rc = rx_buffers[i]->write(bufs[i], num_smpls, (TIMESTAMP)recv_timestamp);
if (rc < 0) {
LOGCHAN(i, DDEV, ERROR)
<< rx_buffers[i]->str_code(rc) << " num smpls: " << num_smpls << " chan: " << i;
LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_status(timestamp);
if (rc != smpl_buf::ERROR_OVERFLOW)
return 0;
}
}
/* We have enough samples */
rc = rx_buffers[i]->read(bufs[i], len, timestamp);
if ((rc < 0) || (rc != len)) {
LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_code(rc) << ". " << rx_buffers[i]->str_status(timestamp)
<< ", (len=" << len << ")";
return 0;
}
return len;
}
int IPCDevice2::writeSamples(std::vector<short *> &bufs, int len, bool *underrun, unsigned long long timestamp)
{
*underrun = false;
LOGCHAN(0, DDEV, DEBUG) << "send buffer of len " << len << " timestamp " << std::hex << timestamp;
// rc = ipc_shm_enqueue(shm_io_tx_streams[i], timestamp, len, (uint16_t *)bufs[i]);
m.write_dl(len, timestamp, reinterpret_cast<sample_t *>(bufs[0]));
wait_for_sample_time(&writetime, len);
return 0;
}
bool IPCDevice2::updateAlignment(TIMESTAMP timestamp)
{
return true;
}
bool IPCDevice2::setTxFreq(double wFreq, size_t chan)
{
return true;
}
bool IPCDevice2::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)
{
if (tx_sps != rx_sps) {
LOGC(DDEV, ERROR) << "IPC Requires tx_sps == rx_sps";
return NULL;
}
if (lo_offset != 0.0) {
LOGC(DDEV, ERROR) << "IPC doesn't support lo_offset";
return NULL;
}
return new IPCDevice2(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@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.
*/
#ifndef _IPC_DEVICE_H_
#define _IPC_DEVICE_H_
#include <climits>
#include <string>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "radioDevice.h"
#include "ipcif.h"
class smpl_buf;
class IPCDevice2 : public RadioDevice {
trxmsif m;
protected:
std::vector<smpl_buf *> rx_buffers;
double actualSampleRate;
bool started;
TIMESTAMP ts_initial;
std::vector<double> tx_gains, rx_gains;
bool flush_recv(size_t num_pkts);
void update_stream_stats_rx(size_t chan, bool *overrun);
void update_stream_stats_tx(size_t chan, bool *underrun);
bool send_chan_wait_rsp(uint32_t chan, struct msgb *msg_to_send, uint32_t expected_rsp_msg_id);
bool send_all_chan_wait_rsp(uint32_t msgid_to_send, uint32_t msgid_to_expect);
public:
/** Object constructor */
IPCDevice2(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);
virtual ~IPCDevice2() override;
/** Instantiate the IPC */
virtual int open(const std::string &args, int ref, bool swap_channels) override;
/** Start the IPC */
virtual bool start() override;
/** Stop the IPC */
virtual bool stop() override;
/* FIXME: any != USRP1 will do for now... */
enum TxWindowType getWindowType() override
{
return TX_WINDOW_FIXED;
}
/**
Read samples from the IPC.
@param buf preallocated buf to contain read result
@param len number of samples desired
@param overrun Set if read buffer has been overrun, e.g. data not being read fast enough
@param timestamp The timestamp of the first samples to be read
@param underrun Set if IPC does not have data to transmit, e.g. data not being sent fast enough
@return The number of samples actually read
*/
virtual int readSamples(std::vector<short *> &buf, int len, bool *overrun, TIMESTAMP timestamp = 0xffffffff,
bool *underrun = NULL) override;
/**
Write samples to the IPC.
@param buf Contains the data to be written.
@param len number of samples to write.
@param underrun Set if IPC does not have data to transmit, e.g. data not being sent fast enough
@param timestamp The timestamp of the first sample of the data buffer.
@return The number of samples actually written
*/
virtual int writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
TIMESTAMP timestamp = 0xffffffff) override;
/** Update the alignment between the read and write timestamps */
virtual bool updateAlignment(TIMESTAMP timestamp) override;
/** Set the transmitter frequency */
virtual bool setTxFreq(double wFreq, size_t chan = 0) override;
/** Set the receiver frequency */
virtual bool setRxFreq(double wFreq, size_t chan = 0) override;
/** Returns the starting write Timestamp*/
virtual TIMESTAMP initialWriteTimestamp(void) override;
/** Returns the starting read Timestamp*/
virtual TIMESTAMP initialReadTimestamp(void) override;
/** returns the full-scale transmit amplitude **/
virtual double fullScaleInputValue() override
{
return (double)SHRT_MAX * 1;
}
/** returns the full-scale receive amplitude **/
virtual double fullScaleOutputValue() override
{
return (double)SHRT_MAX * 1;
}
/** sets the receive chan gain, returns the gain setting **/
virtual double setRxGain(double dB, size_t chan = 0) override;
/** get the current receive gain */
virtual double getRxGain(size_t chan = 0) override
{
return rx_gains[chan];
}
/** return maximum Rx Gain **/
virtual double maxRxGain(void) override;
/** 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;
virtual int getNominalTxPower(size_t chan = 0) override;
/** sets the RX path to use, returns true if successful and false otherwise */
virtual bool setRxAntenna(const std::string &ant, size_t chan = 0) override;
/* return the used RX path */
virtual std::string getRxAntenna(size_t chan = 0) override;
/** sets the RX path to use, returns true if successful and false otherwise */
virtual bool setTxAntenna(const std::string &ant, size_t chan = 0) override;
/* return the used RX path */
virtual std::string getTxAntenna(size_t chan = 0) override;
/** return whether user drives synchronization of Tx/Rx of USRP */
virtual bool requiresRadioAlign() override;
/** return whether user drives synchronization of Tx/Rx of USRP */
virtual GSM::Time minLatency() override;
/** Return internal status values */
virtual inline double getTxFreq(size_t chan = 0) override
{
return 0;
}
virtual inline double getRxFreq(size_t chan = 0) override
{
return 0;
}
virtual inline double getSampleRate() override
{
return actualSampleRate;
}
};
#endif // _IPC_DEVICE_H_

View File

@@ -0,0 +1,14 @@
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS)
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS)
AM_LDFLAGS = -lpthread -lrt
noinst_HEADERS = IPCDevice.h
noinst_LTLIBRARIES = libdevice.la
libdevice_la_SOURCES = IPCDevice.cpp
libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la
libdevice_la_CXXFLAGS = $(AM_CXXFLAGS) -DIPCMAGIC

View File

@@ -0,0 +1,161 @@
/*
* (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/>.
*
*/
#pragma once
#include <atomic>
#include <complex>
#include <cassert>
#include "shmif.h"
const int max_ul_rdlen = 1024 * 10;
const int max_dl_rdlen = 1024 * 10;
using sample_t = std::complex<int16_t>;
struct shm_if {
std::atomic<bool> ms_connected;
struct {
shm::shmmutex m;
shm::shmcond c;
std::atomic<uint64_t> ts;
std::atomic<size_t> len_req; // <-
std::atomic<size_t> len_written; // ->
sample_t buffer[max_ul_rdlen];
} ul;
struct {
shm::shmmutex writemutex;
shm::shmcond rdy2write;
shm::shmmutex readmutex;
shm::shmcond rdy2read;
std::atomic<uint64_t> ts;
std::atomic<size_t> len_req;
std::atomic<size_t> len_written;
sample_t buffer[max_dl_rdlen];
} dl;
};
// unique up to signed_type/2 diff
template <typename A> auto unsigned_diff(A a, A b) -> typename std::make_signed<A>::type
{
using stype = typename std::make_signed<A>::type;
return (a > b) ? static_cast<stype>(a - b) : -static_cast<stype>(b - a);
};
class trxmsif {
shm::shm<shm_if> m;
shm_if *ptr;
int dl_readoffset;
int samp2byte(int v)
{
return v * sizeof(sample_t);
}
public:
trxmsif() : m("trx-ms-if"), dl_readoffset(0)
{
}
bool create()
{
m.create();
ptr = m.p();
return m.isgood();
}
bool connect()
{
m.open();
ptr = m.p();
ptr->ms_connected = true;
return m.isgood();
}
bool good()
{
return m.isgood();
}
void write_dl(size_t howmany, uint64_t write_ts, sample_t *inbuf)
{
auto &dl = ptr->dl;
auto buf = &dl.buffer[0];
// if (ptr->ms_connected != true)
// return;
assert(sizeof(dl.buffer) >= samp2byte(howmany));
{
shm::signal_guard g(dl.writemutex, dl.rdy2write, dl.rdy2read);
memcpy(buf, inbuf, samp2byte(howmany));
dl.ts = write_ts;
dl.len_written = howmany;
}
}
void read_dl(size_t howmany, uint64_t* read_ts, sample_t *outbuf)
{
auto &dl = ptr->dl;
auto buf = &dl.buffer[0];
size_t len_avail = dl.len_written;
uint64_t ts = dl.ts;
auto left_to_read = len_avail - dl_readoffset;
// no data, wait for new buffer, maybe some data left afterwards
if (!left_to_read) {
shm::signal_guard g(dl.readmutex, dl.rdy2read, dl.rdy2write);
*read_ts = dl.ts;
len_avail = dl.len_written;
dl_readoffset += howmany;
assert(len_avail >= howmany);
memcpy(outbuf, buf, samp2byte(howmany));
return;
}
*read_ts = dl.ts + dl_readoffset;
left_to_read = len_avail - dl_readoffset;
// data left from prev read
if (left_to_read >= howmany) {
memcpy(outbuf, buf, samp2byte(howmany));
dl_readoffset += howmany;
return;
} else {
memcpy(outbuf, buf, samp2byte(left_to_read));
dl_readoffset = 0;
auto still_left_to_read = howmany - left_to_read;
{
shm::signal_guard g(dl.readmutex, dl.rdy2read, dl.rdy2write);
len_avail = dl.len_written;
dl_readoffset += still_left_to_read;
assert(len_avail >= still_left_to_read);
memcpy(outbuf + left_to_read, buf, samp2byte(still_left_to_read));
}
}
}
void read_ul(size_t howmany, uint64_t* read_ts, sample_t *outbuf)
{
// if (ptr->ms_connected != true) {
memset(outbuf, 0, samp2byte(howmany));
return;
// }
}
};

View File

@@ -0,0 +1,219 @@
/*
* (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/>.
*
*/
#pragma once
#include <cstring>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <cerrno>
namespace shm
{
class shmmutex {
pthread_mutex_t mutex;
public:
shmmutex()
{
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
pthread_mutex_init(&mutex, &attr);
pthread_mutexattr_destroy(&attr);
}
~shmmutex()
{
pthread_mutex_destroy(&mutex);
}
void lock()
{
pthread_mutex_lock(&mutex);
}
bool try_lock()
{
return pthread_mutex_trylock(&mutex);
}
void unlock()
{
pthread_mutex_unlock(&mutex);
}
pthread_mutex_t *p()
{
return &mutex;
}
};
class shmcond {
pthread_cond_t cond;
public:
shmcond()
{
pthread_condattr_t attr;
pthread_condattr_init(&attr);
pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_cond_init(&cond, &attr);
pthread_condattr_destroy(&attr);
}
~shmcond()
{
pthread_cond_destroy(&cond);
}
void wait(shmmutex *lock)
{
pthread_cond_wait(&cond, lock->p());
}
void signal()
{
pthread_cond_signal(&cond);
}
void signal_all()
{
pthread_cond_broadcast(&cond);
}
};
template <typename IFT> class shm {
char shmname[512];
size_t IFT_sz = sizeof(IFT);
IFT *shmptr;
bool good;
int ipc_shm_setup(const char *shm_name)
{
int fd;
int rc;
void *ptr;
if ((fd = shm_open(shm_name, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) {
rc = -errno;
return rc;
}
if (ftruncate(fd, IFT_sz) < 0) {
rc = -errno;
shm_unlink(shm_name);
::close(fd);
}
if ((ptr = mmap(NULL, IFT_sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
rc = -errno;
shm_unlink(shm_name);
::close(fd);
}
shmptr = new (ptr) IFT(); //static_cast<IFT *>(ptr);
::close(fd);
return 0;
}
int ipc_shm_connect(const char *shm_name)
{
int fd;
int rc;
void *ptr;
if ((fd = shm_open(shm_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)) < 0) {
rc = -errno;
return rc;
}
struct stat shm_stat;
if (fstat(fd, &shm_stat) < 0) {
rc = -errno;
shm_unlink(shm_name);
::close(fd);
}
if ((ptr = mmap(NULL, shm_stat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
rc = -errno;
shm_unlink(shm_name);
::close(fd);
}
shmptr = static_cast<IFT *>(ptr);
::close(fd);
return 0;
}
public:
using IFT_t = IFT;
explicit shm(const char *name) : good(false)
{
strncpy((char *)shmname, name, 512);
}
void create()
{
if (ipc_shm_setup(shmname) == 0)
good = true;
}
void open()
{
if (ipc_shm_connect(shmname) == 0)
good = true;
}
bool isgood() const
{
return good;
}
void close()
{
if (isgood())
shm_unlink(shmname);
}
IFT *p()
{
return shmptr;
}
};
class signal_guard {
shmmutex &m;
shmcond &s;
public:
explicit signal_guard(shmmutex &m, shmcond &wait_for, shmcond &to_signal) : m(m), s(to_signal)
{
m.lock();
wait_for.wait(&m);
}
~signal_guard()
{
s.signal();
m.unlock();
}
};
} // namespace shm

View File

@@ -0,0 +1 @@
#include "../uhd/UHDDevice.cpp"

View File

@@ -0,0 +1,255 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Eric Wild <ewild@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
extern "C" {
#include <osmocom/core/application.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include "shm.h"
#include "ipc_shm.h"
#include "ipc-driver-test.h"
}
#include "../uhd/UHDDevice.h"
#include "uhdwrap.h"
#include "Logger.h"
#include "Threads.h"
#include "Utils.h"
int uhd_wrap::open(const std::string &args, int ref, bool swap_channels)
{
int rv = uhd_device::open(args, ref, swap_channels);
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();
wrap_rx_buffs = std::vector<std::vector<short> >(channel_count, std::vector<short>(2 * samps_per_buff_rx));
for (size_t i = 0; i < wrap_rx_buffs.size(); i++)
wrap_rx_buf_ptrs.push_back(&wrap_rx_buffs[i].front());
wrap_tx_buffs = std::vector<std::vector<short> >(channel_count, std::vector<short>(2 * 5000));
for (size_t i = 0; i < wrap_tx_buffs.size(); i++)
wrap_tx_buf_ptrs.push_back(&wrap_tx_buffs[i].front());
return rv;
}
uhd_wrap::~uhd_wrap()
{
// drvtest::gshutdown = 1;
//t->join();
}
size_t uhd_wrap::bufsizerx()
{
return samps_per_buff_rx;
}
size_t uhd_wrap::bufsizetx()
{
return samps_per_buff_tx;
}
int uhd_wrap::chancount()
{
return channel_count;
}
int uhd_wrap::wrap_read(TIMESTAMP *timestamp)
{
uhd::rx_metadata_t md;
size_t num_rx_samps = rx_stream->recv(wrap_rx_buf_ptrs, samps_per_buff_rx, md, 0.1, true);
*timestamp = md.time_spec.to_ticks(rx_rate);
return num_rx_samps; //uhd_device::readSamples(bufs, len, overrun, timestamp, underrun);
}
extern "C" void *uhdwrap_open(struct ipc_sk_if_open_req *open_req)
{
unsigned int rx_sps, tx_sps;
/* 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;
break;
case FEATURE_MASK_CLOCKREF_INTERNAL:
default:
cref = 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);
}
/* 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);
return uhd_wrap_dev;
}
extern "C" int32_t uhdwrap_get_bufsizerx(void *dev)
{
uhd_wrap *d = (uhd_wrap *)dev;
return d->bufsizerx();
}
extern "C" int32_t uhdwrap_get_timingoffset(void *dev)
{
uhd_wrap *d = (uhd_wrap *)dev;
return d->getTimingOffset();
}
extern "C" int32_t uhdwrap_read(void *dev, uint32_t num_chans)
{
TIMESTAMP t;
uhd_wrap *d = (uhd_wrap *)dev;
if (num_chans != d->wrap_rx_buf_ptrs.size()) {
perror("omg chans?!");
}
int32_t read = d->wrap_read(&t);
/* multi channel rx on b210 will return 0 due to alignment adventures, do not put 0 samples into a ipc buffer... */
if (read <= 0)
return read;
for (uint32_t i = 0; i < num_chans; i++) {
ipc_shm_enqueue(ios_rx_from_device[i], t, read, (uint16_t *)&d->wrap_rx_buffs[i].front());
}
return read;
}
extern "C" int32_t uhdwrap_write(void *dev, uint32_t num_chans, bool *underrun)
{
uhd_wrap *d = (uhd_wrap *)dev;
uint64_t timestamp;
int32_t len = -1;
for (uint32_t i = 0; i < num_chans; i++) {
len = ipc_shm_read(ios_tx_to_device[i], (uint16_t *)&d->wrap_tx_buffs[i].front(), 5000, &timestamp, 1);
if (len < 0)
return 0;
}
return d->writeSamples(d->wrap_tx_buf_ptrs, len, underrun, timestamp);
}
extern "C" double uhdwrap_set_freq(void *dev, double f, size_t chan, bool for_tx)
{
uhd_wrap *d = (uhd_wrap *)dev;
if (for_tx)
return d->setTxFreq(f, chan);
else
return d->setRxFreq(f, chan);
}
extern "C" double uhdwrap_set_gain(void *dev, double f, size_t chan, bool for_tx)
{
uhd_wrap *d = (uhd_wrap *)dev;
// if (for_tx)
// return d->setTxGain(f, chan);
// else
return d->setRxGain(f, chan);
}
extern "C" double uhdwrap_set_txatt(void *dev, double a, size_t chan)
{
uhd_wrap *d = (uhd_wrap *)dev;
return d->setPowerAttenuation(a, chan);
}
extern "C" int32_t uhdwrap_start(void *dev, int chan)
{
uhd_wrap *d = (uhd_wrap *)dev;
return d->start();
}
extern "C" int32_t uhdwrap_stop(void *dev, int chan)
{
uhd_wrap *d = (uhd_wrap *)dev;
return d->stop();
}
extern "C" void uhdwrap_fill_info_cnf(struct ipc_sk_if *ipc_prim)
{
struct ipc_sk_if_info_chan *chan_info;
uhd::device_addr_t args("");
uhd::device_addrs_t devs_found = uhd::device::find(args);
if (devs_found.size() < 1) {
std::cout << "\n No device found!";
exit(0);
}
uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(devs_found[0]);
auto rxchans = usrp->get_rx_num_channels();
auto txchans = usrp->get_tx_num_channels();
auto rx_range = usrp->get_rx_gain_range();
auto tx_range = usrp->get_tx_gain_range();
//auto nboards = usrp->get_num_mboards();
auto refs = usrp->get_clock_sources(0);
auto devname = usrp->get_mboard_name(0);
ipc_prim->u.info_cnf.feature_mask = 0;
if (std::find(refs.begin(), refs.end(), "internal") != refs.end())
ipc_prim->u.info_cnf.feature_mask |= FEATURE_MASK_CLOCKREF_INTERNAL;
if (std::find(refs.begin(), refs.end(), "external") != refs.end())
ipc_prim->u.info_cnf.feature_mask |= FEATURE_MASK_CLOCKREF_EXTERNAL;
// at least one duplex channel
auto num_chans = rxchans == txchans ? txchans : 1;
ipc_prim->u.info_cnf.iq_scaling_val_rx = 0.3;
ipc_prim->u.info_cnf.iq_scaling_val_tx = 1;
ipc_prim->u.info_cnf.max_num_chans = num_chans;
OSMO_STRLCPY_ARRAY(ipc_prim->u.info_cnf.dev_desc, devname.c_str());
chan_info = ipc_prim->u.info_cnf.chan_info;
for (unsigned int i = 0; i < ipc_prim->u.info_cnf.max_num_chans; i++) {
auto rxant = usrp->get_rx_antennas(i);
auto txant = usrp->get_tx_antennas(i);
for (unsigned int j = 0; j < txant.size(); j++) {
OSMO_STRLCPY_ARRAY(chan_info->tx_path[j], txant[j].c_str());
}
for (unsigned int j = 0; j < rxant.size(); j++) {
OSMO_STRLCPY_ARRAY(chan_info->rx_path[j], rxant[j].c_str());
}
chan_info->min_rx_gain = rx_range.start();
chan_info->max_rx_gain = rx_range.stop();
chan_info->min_tx_gain = tx_range.start();
chan_info->max_tx_gain = tx_range.stop();
chan_info->nominal_tx_power = 7.5; // FIXME: would require uhd dev + freq info
chan_info++;
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Eric Wild <ewild@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef IPC_UHDWRAP_H
#define IPC_UHDWRAP_H
#ifdef __cplusplus
#include "../uhd/UHDDevice.h"
class uhd_wrap : public uhd_device {
public:
// std::thread *t;
size_t samps_per_buff_rx;
size_t samps_per_buff_tx;
int channel_count;
std::vector<std::vector<short> > wrap_rx_buffs;
std::vector<std::vector<short> > wrap_tx_buffs;
std::vector<short *> wrap_rx_buf_ptrs;
std::vector<short *> wrap_tx_buf_ptrs;
template <typename... Args> uhd_wrap(Args... args) : uhd_device(args...)
{
// t = new std::thread(magicthread);
// give the thread some time to start and set up
// std::this_thread::sleep_for(std::chrono::seconds(1));
}
virtual ~uhd_wrap();
// void ipc_sock_close() override {};
int wrap_read(TIMESTAMP *timestamp);
virtual int open(const std::string &args, int ref, bool swap_channels) override;
// bool start() override;
// bool stop() override;
// virtual TIMESTAMP initialWriteTimestamp() override;
// virtual TIMESTAMP initialReadTimestamp() override;
int getTimingOffset()
{
return ts_offset;
}
size_t bufsizerx();
size_t bufsizetx();
int chancount();
};
#else
void *uhdwrap_open(struct ipc_sk_if_open_req *open_req);
int32_t uhdwrap_get_bufsizerx(void *dev);
int32_t uhdwrap_get_timingoffset(void *dev);
int32_t uhdwrap_read(void *dev, uint32_t num_chans);
int32_t uhdwrap_write(void *dev, uint32_t num_chans, bool *underrun);
double uhdwrap_set_freq(void *dev, double f, size_t chan, bool for_tx);
double uhdwrap_set_gain(void *dev, double f, size_t chan, bool for_tx);
int32_t uhdwrap_start(void *dev, int chan);
int32_t uhdwrap_stop(void *dev, int chan);
void uhdwrap_fill_info_cnf(struct ipc_sk_if *ipc_prim);
double uhdwrap_set_txatt(void *dev, double a, size_t chan);
#endif
#endif // IPC_B210_H

View File

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

View File

@@ -0,0 +1,338 @@
/* -*- 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 "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"
//signalVector mChanResp;
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);
/* 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);
}
}
MULTI_VER_TARGET_ATTR
void
detect_burst(const gr_complex* input,
gr_complex* chan_imp_resp, int burst_start,
unsigned char* output_binary)
{
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 = 3;
// if(burst_start < 0 ||burst_start > 10)
// fprintf(stderr, "bo %d\n", burst_start);
// burst_start = burst_start >= 0 ? burst_start : 0;
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 (int i = 0; i < BURST_SIZE; i++)
output_binary[i] = output[i] > 0;
}
int process_vita_burst(gr_complex* input, int tsc, unsigned char* output_binary) {
gr_complex channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
int normal_burst_start, dummy_burst_start;
float dummy_corr_max, normal_corr_max;
dummy_burst_start = get_norm_chan_imp_resp(input,
&channel_imp_resp[0], &dummy_corr_max, TS_DUMMY);
normal_burst_start = get_norm_chan_imp_resp(input,
&channel_imp_resp[0], &normal_corr_max, tsc);
if (normal_corr_max > dummy_corr_max) {
/* Perform MLSE detection */
detect_burst(input, &channel_imp_resp[0],
normal_burst_start, output_binary);
return 0;
} else {
memcpy(output_binary, dummy_burst, 148);
//std::cerr << std::endl << "#NOPE#" << dd.fpath << std::endl << std::endl;
return -1;
}
}
int process_vita_sc_burst(gr_complex* input, int tsc, unsigned char* output_binary, int* offset) {
gr_complex channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
/* Get channel impulse response */
int d_c0_burst_start = get_sch_chan_imp_resp(input, &channel_imp_resp[0]);
// *offset = d_c0_burst_start;
/* Perform MLSE detection */
detect_burst(input, &channel_imp_resp[0],
d_c0_burst_start, output_binary);
return 0;
}
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_center, int search_start_pos,
int search_stop_pos, gr_complex *tseq, int tseqlen, float *corr_max)
{
std::vector<gr_complex> correlation_buffer;
std::vector<float> window_energy_buffer;
std::vector<float> power_buffer;
for (int ii = search_start_pos; ii < search_stop_pos; ii++) {
gr_complex correlation = correlate_sequence(tseq, tseqlen, &input[ii]);
correlation_buffer.push_back(correlation);
power_buffer.push_back(std::pow(abs(correlation), 2));
}
int strongest_corr_nr = max_element(power_buffer.begin(), power_buffer.end()) - power_buffer.begin();
/* Compute window energies */
auto window_energy_start_offset = strongest_corr_nr - 6 * d_OSR;
window_energy_start_offset = window_energy_start_offset < 0 ? 0 : window_energy_start_offset; //can end up out of range..
auto window_energy_end_offset = strongest_corr_nr + 6 * d_OSR + d_chan_imp_length * d_OSR;
auto iter = power_buffer.begin() + window_energy_start_offset;
auto iter_end = power_buffer.begin() + window_energy_end_offset;
while (iter != iter_end) {
std::vector<float>::iterator iter_ii = iter;
bool loop_end = false;
float energy = 0;
int len = d_chan_imp_length * d_OSR;
for (int ii = 0; ii < len; ii++, iter_ii++) {
if (iter_ii == power_buffer.end()) {
loop_end = true;
break;
}
energy += (*iter_ii);
}
if (loop_end)
break;
window_energy_buffer.push_back(energy);
iter++;
}
/* Calculate the strongest window number */
int strongest_window_nr = window_energy_start_offset +
max_element(window_energy_buffer.begin(), window_energy_buffer.end()) -
window_energy_buffer.begin();
// auto window_search_start = window_energy_buffer.begin() + strongest_corr_nr - 5* d_OSR;
// auto window_search_end = window_energy_buffer.begin() + strongest_corr_nr + 10* d_OSR;
// window_search_end = window_search_end >= window_energy_buffer.end() ? window_energy_buffer.end() : window_search_end;
// /* Calculate the strongest window number */
// int strongest_window_nr = max_element(window_search_start, window_search_end /* - d_chan_imp_length * d_OSR*/) - window_energy_buffer.begin();
// if (strongest_window_nr < 0)
// strongest_window_nr = 0;
float max_correlation = 0;
for (int ii = 0; ii < d_chan_imp_length * d_OSR; 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 - 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_center, search_start_pos, search_stop_pos, tseq, tseqlen,
corr_max);
}
/*
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_center, search_start_pos, search_stop_pos, tseq, tseqlen,
&corr_max);
}
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_center, search_start_pos, search_stop_pos, tseq, tseqlen,
corr_max);
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <vector>
#include "constants.h"
#if defined(__has_attribute)
#if __has_attribute(target_clones) && defined(__x86_64) && false
#define MULTI_VER_TARGET_ATTR __attribute__((target_clones("avx", "sse4.2", "sse3", "sse2", "sse", "default")))
#else
#define MULTI_VER_TARGET_ATTR
#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);
MULTI_VER_TARGET_ATTR
void detect_burst(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, unsigned 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);
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;
};

View File

@@ -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;
}
}

View File

@@ -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 */

204
Transceiver52M/itrq.h Normal file
View File

@@ -0,0 +1,204 @@
#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>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdlib.h>
/*
classic lamport circular lockfree spsc queue:
every "side" only writes its own ptr, but may read the other sides ptr
notify reader using eventfd as soon as element is added, reader then reads until
read fails
-> reader pops in a loop until FALSE and might get spurious events because it
read before it was notified, which is fine
-> writing pushes *the same data* in a loop until TRUE, blocks
shutting this down requires
1) to stop reading and pushing
2) ONE side to take care of the eventfds
*/
namespace spsc_detail
{
template <bool block_read, bool block_write> class spsc_cond_detail {
std::condition_variable cond_r, cond_w;
std::mutex l;
public:
explicit spsc_cond_detail()
{
}
~spsc_cond_detail()
{
}
ssize_t spsc_check_r()
{
std::unique_lock<std::mutex> lk(l);
cond_r.wait(lk);
return 1;
}
ssize_t spsc_check_w()
{
std::unique_lock<std::mutex> lk(l);
cond_w.wait(lk);
return 1;
}
void spsc_notify_r()
{
cond_r.notify_one();
}
void spsc_notify_w()
{
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(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> {};

124
Transceiver52M/l1if.cpp Normal file
View File

@@ -0,0 +1,124 @@
#include <mutex>
#include <queue>
#include <deque>
#include <condition_variable>
#include <iostream>
extern "C" {
#include <unistd.h>
#include <sys/eventfd.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/select.h>
}
#include "l1if.h"
using namespace std;
using namespace std::chrono_literals;
template<typename Data>
class spsc_q{
std::queue<Data> m_q;
std::mutex m_mtx;
std::condition_variable m_cond;
bool killme;
public:
spsc_q() : killme{ false } { }
void push(Data i){
std::unique_lock<std::mutex> lock(m_mtx);
m_q.push(i);
m_cond.notify_one();
}
Data pop(){
std::unique_lock<std::mutex> lock(m_mtx);
m_cond.wait_for(lock, 100ms, [&](){ return !m_q.empty() || killme; });
if (killme || m_q.empty()){
return {};
}
Data x = m_q.front();
m_q.pop();
return x;
}
void stop(){
killme = true;
m_cond.notify_all();
}
auto sz() { return m_q.size(); }
};
/*
* trxif_from_trx_c <-> push_c
* trxif_to_trx_c <-> pop_c
* trxif_from_trx_d <-> push_d
* trxif_to_trx_d <-> pop_d
* ...
*
*
*/
class trxl1if {
public:
spsc_q<TRX_C*> c_to_trx;
spsc_q<TRX_C*> c_from_trx;
spsc_q<trxd_to_trx*> d_to_trx;
spsc_q<trxd_from_trx*> d_from_trx;
struct osmo_fd g_event_ofd_C;
struct osmo_fd g_event_ofd_D;
};
trxl1if trxif;
void push_c(TRX_C* i) {
uint64_t one = 1;
int rc;
trxif.c_from_trx.push(i);
// std::clog << trxif.c_from_trx.sz() << std::endl;
rc = ::write(trxif.g_event_ofd_C.fd, &one, sizeof(one));
return;
};
TRX_C* pop_c() {
return trxif.c_to_trx.pop();
};
void push_d(trxd_from_trx* i) {
uint64_t one = 1;
int rc;
trxif.d_from_trx.push(i);
rc = ::write(trxif.g_event_ofd_D.fd, &one, sizeof(one));
return;
};
trxd_to_trx* pop_d() {
return trxif.d_to_trx.pop();
};
extern "C" {
char* trxif_from_trx_c() {
uint64_t one = 1;
::read(trxif.g_event_ofd_C.fd, &one, sizeof(one));
return (char*)trxif.c_from_trx.pop();
}
void trxif_to_trx_c(char* msg) {
trxif.c_to_trx.push((TRX_C*)msg);
}
trxd_from_trx* trxif_from_trx_d() {
uint64_t one = 1;
::read(trxif.g_event_ofd_D.fd, &one, sizeof(one));
return trxif.d_from_trx.pop();
}
void trxif_to_trx_d(trxd_to_trx* msg) {
trxif.d_to_trx.push(msg);
}
struct osmo_fd* get_c_fd() { return &trxif.g_event_ofd_C;}
struct osmo_fd* get_d_fd() { return &trxif.g_event_ofd_D;}
}

74
Transceiver52M/l1if.h Normal file
View File

@@ -0,0 +1,74 @@
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#ifdef __cplusplus
}
#endif
/* ------------------------------------------------------------------------ */
/* Data interface handlers */
/* ------------------------------------------------------------------------ */
/* DATA interface */
/* */
/* Messages on the data interface carry one radio burst per UDP message. */
/* */
/* Received Data Burst: */
/* 1 byte timeslot index */
/* 4 bytes GSM frame number, BE */
/* 1 byte RSSI in -dBm */
/* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE */
/* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1" */
/* 2 bytes are not used, but being sent by OsmoTRX */
/* */
/* Transmit Data Burst: */
/* 1 byte timeslot index */
/* 4 bytes GSM frame number, BE */
/* 1 byte transmit level wrt ARFCN max, -dB (attenuation) */
/* 148 bytes output symbol values, 0 & 1 */
/* ------------------------------------------------------------------------ */
struct __attribute__((packed)) trxd_to_trx {
uint8_t ts;
uint32_t fn;
uint8_t txlev;
uint8_t symbols[148];
};
struct __attribute__((packed)) trxd_from_trx {
uint8_t ts;
uint32_t fn;
uint8_t rssi;
uint16_t toa;
uint8_t symbols[148];
uint8_t pad[2];
};
#define TRXC_BUF_SIZE 1024
struct TRX_C {
char cmd[TRXC_BUF_SIZE];
};
#ifdef __cplusplus
void push_c(TRX_C* i);
TRX_C* pop_c();
void push_d(trxd_from_trx* i);
trxd_to_trx* pop_d();
#else
char* trxif_from_trx_c();
void trxif_to_trx_c(char* msg);
struct trxd_from_trx* trxif_from_trx_d();
void trxif_to_trx_d(struct trxd_to_trx* msg);
struct osmo_fd* get_c_fd();
struct osmo_fd* get_d_fd();
#endif

View File

@@ -0,0 +1,482 @@
#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..
template <typename Arg, typename... Args> void doPrint(std::ostream &out, Arg &&arg, Args &&...args)
{
out << '(' << std::forward<Arg>(arg);
using expander = int[];
(void)expander{ 0, (void(out << ',' << std::forward<Args>(args)), 0)... };
out << ')' << std::endl;
}
template <class R, class... Args> using RvalFunc = R (*)(Args...);
// specialisation for funcs which return a value
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;
doPrint(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 (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 (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<BLADE_NUM_BUFFERS, dev_buf_t *, true, false>;
const unsigned int rxFullScale, txFullScale;
const int rxtxdelay;
float rxgain, txgain;
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)
{
}
void close_device()
{
if (dev) {
if (rx_stream) {
bladerf_deinit_stream(rx_stream);
}
if (tx_stream) {
bladerf_deinit_stream(tx_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);
blade_check(bladerf_set_pll_refclk, dev, 10000000UL);
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;
}
// bladerf_sample_rate r = (1625e3 * 4)/6, act;
// blade_check(bladerf_set_sample_rate,dev, BLADERF_CHANNEL_RX(0), r, &act);
// blade_check(bladerf_set_sample_rate,dev, BLADERF_CHANNEL_TX(0), r, &act);
// auto ratrate = (1625e3 * 4) / 6;
// rate.integer = (uint32_t)ratrate;
// rate.den = 10000;
// rate.num = (ratrate - rate.integer) * rate.den;
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);
// blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_RX(0), (bladerf_gain)30);
// blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_TX(0), (bladerf_gain)50);
usleep(1000);
blade_check(bladerf_enable_module, dev, BLADERF_MODULE_RX, true);
usleep(1000);
blade_check(bladerf_enable_module, dev, BLADERF_MODULE_TX, true);
usleep(1000);
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 (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]);
}
setRxGain(20);
setTxGain(30);
usleep(1000);
// bladerf_set_stream_timeout(dev, BLADERF_TX, 4);
// bladerf_set_stream_timeout(dev, BLADERF_RX, 4);
return 0;
}
bool tuneTx(double freq, size_t chan = 0)
{
msleep(15);
blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)freq);
msleep(15);
return true;
};
bool tuneRx(double freq, size_t chan = 0)
{
msleep(15);
blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)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 (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);
return BLADERF_STREAM_NO_DATA;
};
}
auto get_rx_burst_handler_fn(bh_fn_t burst_handler)
{
auto fn = [this] {
int status;
set_name_aff_sched("rxrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2);
status = bladerf_stream(rx_stream, BLADERF_RX_X1);
if (status < 0)
std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl;
return NULL;
};
return fn;
}
auto get_tx_burst_handler_fn(bh_fn_t burst_handler)
{
auto fn = [this] {
int status;
set_name_aff_sched("txrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1);
status = bladerf_stream(tx_stream, BLADERF_TX_X1);
if (status < 0)
std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl;
return NULL;
};
return fn;
}
void submit_burst_ts(blade_sample_type *buffer, int len, uint64_t ts)
{
//get empty bufer from list
tx_buf_q_type::elem_t rcd;
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, 100U);
blade_check(bladerf_submit_stream_buffer_nb, tx_stream, (void *)rcd);
}
void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio)
{
pthread_setname_np(pthread_self(), name);
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpunum, &cpuset);
auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
if (rv < 0) {
std::cerr << name << " affinity: errreur! " << std::strerror(errno);
return exit(0);
}
sched_param sch_params;
sch_params.sched_priority = prio;
rv = pthread_setschedparam(pthread_self(), schedtype, &sch_params);
if (rv < 0) {
std::cerr << name << " sched: errreur! " << std::strerror(errno);
return exit(0);
}
}
};

View File

@@ -0,0 +1,259 @@
#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 <complex>
#include <cstring>
#include <functional>
#include <iostream>
#include <thread>
#include <Timeval.h>
#include <vector>
#include <ipcif.h>
// typedef unsigned long long TIMESTAMP;
using blade_sample_type = std::complex<int16_t>;
const int SAMPLE_SCALE_FACTOR = 1;
struct uhd_buf_wrap {
uint64_t ts;
uint32_t num_samps;
blade_sample_type *buf;
auto actual_samples_per_buffer()
{
return num_samps;
}
long get_first_ts()
{
return ts; //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 ipc_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;
trxmsif m;
virtual ~ipc_hw()
{
delete[] one_pkt_buf;
}
ipc_hw() : rxFullScale(32767), txFullScale(32767), rxtxdelay(-67)
{
}
bool tuneTx(double freq, size_t chan = 0)
{
msleep(25);
// dev->set_tx_freq(freq, chan);
msleep(25);
return true;
};
bool tuneRx(double freq, size_t chan = 0)
{
msleep(25);
// dev->set_rx_freq(freq, chan);
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)
{
// std::thread([] {
// osmo_ctx_init("bernd");
// osmo_select_init();
// main_ipc();
// while (true)
// osmo_select_main(0);
// }).detach();
return m.connect() ? 0: -1;
}
void *rx_cb(bh_fn_t burst_handler)
{
void *ret;
static int to_skip = 0;
blade_sample_type pbuf[508 * 2];
uint64_t t;
int len = 508 * 2;
m.read_dl(508 * 2, &t, pbuf);
// auto len = ipc_shm_read(ios_tx_to_device[0], (uint16_t *)&pbuf, 508 * 2, &t, 1);
// if(len < 0) {
// std::cerr << "fuck, rx fail!" << std::endl;
// exit(0);
// }
// uhd::rx_metadata_t md;
// auto num_rx_samps = rx_stream->recv(pkt_ptrs.front(), rx_spp, md, 3.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. Please consider the following:\n"
// " Your write medium must sustain a rate of %fMB/s.\n"
// " Dropped samples will not be written to the file.\n"
// " Please modify this example for your purposes.\n"
// " This message will not appear again.\n") %
// 1.f;
// 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 = { t, static_cast<uint32_t>(len), pbuf };
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)
{
auto fn = [this, burst_handler] {
pthread_setname_np(pthread_self(), "rxrun");
// wait_for_shm_open();
// 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_stream->issue_stream_cmd(stream_cmd);
while (1) {
rx_cb(burst_handler);
}
};
return fn;
}
auto get_tx_burst_handler_fn(bh_fn_t burst_handler)
{
auto fn = [] {
// wait_for_shm_open();
// dummy
};
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);
// 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;
}
void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio)
{
pthread_setname_np(pthread_self(), name);
// cpu_set_t cpuset;
// CPU_ZERO(&cpuset);
// CPU_SET(cpunum, &cpuset);
// auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
// if (rv < 0) {
// std::cerr << name << " affinity: errreur! " << std::strerror(errno);
// return exit(0);
// }
// sched_param sch_params;
// sch_params.sched_priority = prio;
// rv = pthread_setschedparam(pthread_self(), schedtype, &sch_params);
// if (rv < 0) {
// std::cerr << name << " sched: errreur! " << std::strerror(errno);
// return exit(0);
// }
}
};

473
Transceiver52M/ms/ipcif.c Normal file
View File

@@ -0,0 +1,473 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#define _GNU_SOURCE
#include <pthread.h>
#include <debug.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
#include <osmocom/core/application.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <shm.h>
#include <ipc_shm.h>
#include <ipc_chan.h>
#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;
/* 8 channels are plenty */
struct ipc_sock_state *global_ctrl_socks[8];
struct ipc_shm_io *ios_tx_to_device[8];
struct ipc_shm_io *ios_rx_from_device[8];
void *shm;
// void *global_dev;
static pthread_mutex_t wait_open_lock;
static pthread_cond_t wait_open_cond;
static struct ipc_shm_region *decoded_region;
static struct {
int msocknum;
char *ud_prefix_dir;
} cmdline_cfg = { 1, IPC_SOCK_PATH_PREFIX };
static const struct log_info_cat default_categories[] = {
[DMAIN] = {
.name = "DMAIN",
.color = NULL,
.description = "Main generic category",
.loglevel = LOGL_DEBUG,.enabled = 1,
},
[DDEV] = {
.name = "DDEV",
.description = "Device/Driver specific code",
.color = NULL,
.enabled = 1, .loglevel = LOGL_DEBUG,
},
};
const struct log_info log_infox = {
.cat = default_categories,
.num_cat = ARRAY_SIZE(default_categories),
};
volatile int ipc_exit_requested = 0;
static int ipc_shm_setup(const char *shm_name, size_t shm_len)
{
int fd;
int rc;
LOGP(DMAIN, LOGL_NOTICE, "Opening shm path %s\n", shm_name);
if ((fd = shm_open(shm_name, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) {
LOGP(DMAIN, LOGL_ERROR, "shm_open %d: %s\n", errno, strerror(errno));
rc = -errno;
goto err_shm_open;
}
LOGP(DMAIN, LOGL_NOTICE, "Truncating %d to size %zu\n", fd, shm_len);
if (ftruncate(fd, shm_len) < 0) {
LOGP(DMAIN, LOGL_ERROR, "ftruncate %d: %s\n", errno, strerror(errno));
rc = -errno;
goto err_mmap;
}
LOGP(DMAIN, LOGL_NOTICE, "mmaping shared memory fd %d\n", fd);
if ((shm = mmap(NULL, shm_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
LOGP(DMAIN, LOGL_ERROR, "mmap %d: %s\n", errno, strerror(errno));
rc = -errno;
goto err_mmap;
}
LOGP(DMAIN, LOGL_NOTICE, "mmap'ed shared memory at addr %p\n", shm);
/* After a call to mmap(2) the file descriptor may be closed without affecting the memory mapping. */
close(fd);
return 0;
err_mmap:
shm_unlink(shm_name);
close(fd);
err_shm_open:
return rc;
}
struct msgb *ipc_msgb_alloc(uint8_t msg_type)
{
struct msgb *msg;
struct ipc_sk_if *ipc_prim;
msg = msgb_alloc(sizeof(struct ipc_sk_if) + 1000, "ipc_sock_tx");
if (!msg)
return NULL;
msgb_put(msg, sizeof(struct ipc_sk_if) + 1000);
ipc_prim = (struct ipc_sk_if *)msg->data;
ipc_prim->msg_type = msg_type;
return msg;
}
static int ipc_tx_info_cnf()
{
struct msgb *msg;
struct ipc_sk_if *ipc_prim;
msg = ipc_msgb_alloc(IPC_IF_MSG_INFO_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_if *)msg->data;
ipc_prim->u.info_cnf.feature_mask = FEATURE_MASK_CLOCKREF_EXTERNAL;
ipc_prim->u.info_cnf.iq_scaling_val_rx = 1;
ipc_prim->u.info_cnf.iq_scaling_val_tx = 1;
ipc_prim->u.info_cnf.max_num_chans = 1;
OSMO_STRLCPY_ARRAY(ipc_prim->u.info_cnf.dev_desc, "bernd");
struct ipc_sk_if_info_chan *chan_info = ipc_prim->u.info_cnf.chan_info;
OSMO_STRLCPY_ARRAY(chan_info->tx_path[0], "TX/RX");
OSMO_STRLCPY_ARRAY(chan_info->rx_path[0], "RX2");
chan_info->min_rx_gain = 0;
chan_info->max_rx_gain = 60;
chan_info->min_tx_gain = 0;
chan_info->max_tx_gain = 60;
chan_info->nominal_tx_power = 10;
return ipc_sock_send(msg);
}
static int ipc_tx_open_cnf(int rc, uint32_t num_chans, int32_t timingoffset)
{
struct msgb *msg;
struct ipc_sk_if *ipc_prim;
struct ipc_sk_if_open_cnf_chan *chan_info;
unsigned int i;
msg = ipc_msgb_alloc(IPC_IF_MSG_OPEN_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_if *)msg->data;
ipc_prim->u.open_cnf.return_code = rc;
ipc_prim->u.open_cnf.path_delay = timingoffset; // 6.18462e-5 * 1625e3 / 6;
OSMO_STRLCPY_ARRAY(ipc_prim->u.open_cnf.shm_name, DEFAULT_SHM_NAME);
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/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);
chan_info++;
}
return ipc_sock_send(msg);
}
int ipc_rx_greeting_req(struct ipc_sk_if_greeting *greeting_req)
{
struct msgb *msg;
struct ipc_sk_if *ipc_prim;
msg = ipc_msgb_alloc(IPC_IF_MSG_GREETING_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_if *)msg->data;
ipc_prim->u.greeting_cnf.req_version =
greeting_req->req_version == IPC_SOCK_API_VERSION ? IPC_SOCK_API_VERSION : 0;
ipc_sock_send(msg);
return 0;
}
int ipc_rx_info_req(struct ipc_sk_if_info_req *info_req)
{
ipc_tx_info_cnf();
return 0;
}
int ipc_rx_open_req(struct ipc_sk_if_open_req *open_req)
{
/* calculate size needed */
unsigned int len;
unsigned int i;
// global_dev = uhdwrap_open(open_req);
/* b210 packet size is 2040, but our tx size is 2500, so just do *2 */
int shmbuflen = 5000 * 2;
len = ipc_shm_encode_region(NULL, open_req->num_chans, 4, shmbuflen);
/* Here we verify num_chans, rx_path, tx_path, clockref, etc. */
int rc = ipc_shm_setup(DEFAULT_SHM_NAME, len);
len = ipc_shm_encode_region((struct ipc_shm_raw_region *)shm, open_req->num_chans, 4, shmbuflen);
// LOGP(DMAIN, LOGL_NOTICE, "%s\n", osmo_hexdump((const unsigned char *)shm, 80));
/* set up our own copy of the decoded area, we have to do it here,
* since the uhd wrapper does not allow starting single channels
* additionally go for the producer init for both, so only we are responsible for the init, instead
* of splitting it with the client and causing potential races if one side uses it too early */
decoded_region = ipc_shm_decode_region(0, (struct ipc_shm_raw_region *)shm);
for (i = 0; i < open_req->num_chans; i++) {
// ios_tx_to_device[i] = ipc_shm_init_consumer(decoded_region->channels[i]->dl_stream);
ios_tx_to_device[i] = ipc_shm_init_producer(decoded_region->channels[i]->dl_stream);
ios_rx_from_device[i] = ipc_shm_init_producer(decoded_region->channels[i]->ul_stream);
}
ipc_tx_open_cnf(-rc, open_req->num_chans, 0);
return 0;
}
volatile bool ul_running = false;
volatile bool dl_running = false;
void *uplink_thread(void *x_void_ptr)
{
uint32_t chann = decoded_region->num_chans;
ul_running = true;
pthread_setname_np(pthread_self(), "uplink_rx");
while (!ipc_exit_requested) {
// int32_t read = uhdwrap_read(global_dev, chann);
// ipc_shm_enqueue(ios_rx_from_device[i], tstamp, num_rx_samps, (uint16_t *)&d->wrap_rx_buffs[i].front());
if (read < 0)
return 0;
}
return 0;
}
void *downlink_thread(void *x_void_ptr)
{
int chann = decoded_region->num_chans;
dl_running = true;
pthread_setname_np(pthread_self(), "downlink_tx");
while (!ipc_exit_requested) {
bool underrun;
// uhdwrap_write(global_dev, chann, &underrun);
// len = ipc_shm_read(ios_tx_to_device[i], (uint16_t *)&d->wrap_tx_buffs[i].front(), 5000, &timestamp, 1);
// return d->writeSamples(d->wrap_tx_buf_ptrs, len, underrun, timestamp);
}
return 0;
}
int ipc_rx_chan_start_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr)
{
struct msgb *msg;
struct ipc_sk_chan_if *ipc_prim;
int rc = 0;
// rc = uhdwrap_start(global_dev, chan_nr);
pthread_cond_broadcast(&wait_open_cond);
// /* no per-chan start/stop */
// if (!dl_running || !ul_running) {
// /* chan != first chan start will "fail", which is fine, usrp can't start/stop chans independently */
// if (rc) {
// LOGP(DMAIN, LOGL_INFO, "starting rx/tx threads.. req for chan:%d\n", chan_nr);
// pthread_t rx, tx;
// pthread_create(&rx, NULL, uplink_thread, 0);
// pthread_create(&tx, NULL, downlink_thread, 0);
// }
// } else
// LOGP(DMAIN, LOGL_INFO, "starting rx/tx threads request ignored.. req for chan:%d\n", chan_nr);
msg = ipc_msgb_alloc(IPC_IF_MSG_START_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_chan_if *)msg->data;
ipc_prim->u.start_cnf.return_code = rc ? 0 : -1;
return ipc_chan_sock_send(msg, chan_nr);
}
int ipc_rx_chan_stop_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr)
{
struct msgb *msg;
struct ipc_sk_chan_if *ipc_prim;
int rc = true;
/* no per-chan start/stop */
// rc = uhdwrap_stop(global_dev, chan_nr);
msg = ipc_msgb_alloc(IPC_IF_MSG_STOP_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_chan_if *)msg->data;
ipc_prim->u.stop_cnf.return_code = rc ? 0 : -1;
return ipc_chan_sock_send(msg, chan_nr);
}
int ipc_rx_chan_setgain_req(struct ipc_sk_chan_if_gain *req, uint8_t chan_nr)
{
struct msgb *msg;
struct ipc_sk_chan_if *ipc_prim;
double rv = req->gain;
// rv = uhdwrap_set_gain(global_dev, req->gain, chan_nr, req->is_tx);
msg = ipc_msgb_alloc(IPC_IF_MSG_SETGAIN_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_chan_if *)msg->data;
ipc_prim->u.set_gain_cnf.is_tx = req->is_tx;
ipc_prim->u.set_gain_cnf.gain = rv;
return ipc_chan_sock_send(msg, chan_nr);
}
int ipc_rx_chan_setfreq_req(struct ipc_sk_chan_if_freq_req *req, uint8_t chan_nr)
{
struct msgb *msg;
struct ipc_sk_chan_if *ipc_prim;
bool rv = true;
// rv = uhdwrap_set_freq(global_dev, req->freq, chan_nr, req->is_tx);
msg = ipc_msgb_alloc(IPC_IF_MSG_SETFREQ_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_chan_if *)msg->data;
ipc_prim->u.set_freq_cnf.return_code = rv ? 0 : 1;
return ipc_chan_sock_send(msg, chan_nr);
}
int ipc_rx_chan_settxatten_req(struct ipc_sk_chan_if_tx_attenuation *req, uint8_t chan_nr)
{
struct msgb *msg;
struct ipc_sk_chan_if *ipc_prim;
double rv = req->attenuation;
// rv = uhdwrap_set_txatt(global_dev, req->attenuation, chan_nr);
msg = ipc_msgb_alloc(IPC_IF_MSG_SETTXATTN_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_chan_if *)msg->data;
ipc_prim->u.txatten_cnf.attenuation = rv;
return ipc_chan_sock_send(msg, chan_nr);
}
int ipc_sock_init(const char *path, struct ipc_sock_state **global_state_var,
int (*sock_callback_fn)(struct osmo_fd *fd, unsigned int what), int n)
{
struct ipc_sock_state *state;
struct osmo_fd *bfd;
int rc;
state = talloc_zero(NULL, struct ipc_sock_state);
if (!state)
return -ENOMEM;
*global_state_var = state;
INIT_LLIST_HEAD(&state->upqueue);
state->conn_bfd.fd = -1;
bfd = &state->listen_bfd;
bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, OSMO_SOCK_F_BIND);
if (bfd->fd < 0) {
LOGP(DMAIN, LOGL_ERROR, "Could not create %s unix socket: %s\n", path, strerror(errno));
talloc_free(state);
return -1;
}
osmo_fd_setup(bfd, bfd->fd, OSMO_FD_READ, sock_callback_fn, state, n);
rc = osmo_fd_register(bfd);
if (rc < 0) {
LOGP(DMAIN, LOGL_ERROR, "Could not register listen fd: %d\n", rc);
close(bfd->fd);
talloc_free(state);
return rc;
}
LOGP(DMAIN, LOGL_INFO, "Started listening on IPC socket: %s\n", path);
return 0;
}
int main_ipc()
{
char *ipc_msock_path = "/tmp/ipc_sock1";
tall_ctx = talloc_named_const(NULL, 0, "trx-ipc-ms");
msgb_talloc_ctx_init(tall_ctx, 0);
osmo_init_logging2(tall_ctx, &log_infox);
log_enable_multithread();
LOGP(DMAIN, LOGL_INFO, "Starting %s\n", "bernd");
ipc_sock_init(ipc_msock_path, &global_ipc_sock_state, ipc_sock_accept, 0);
// while (!ipc_exit_requested)
// osmo_select_main(0);
// if (global_dev) {
// unsigned int i;
// for (i = 0; i < decoded_region->num_chans; i++)
// uhdwrap_stop(global_dev, i);
// }
// ipc_sock_close(global_ipc_sock_state);
int rv;
pthread_condattr_t t2;
rv = pthread_condattr_setpshared(&t2, PTHREAD_PROCESS_SHARED);
rv = pthread_cond_init(&wait_open_cond, &t2);
return 0;
}
int wait_for_shm_open()
{
struct timespec tv;
int rv;
clock_gettime(CLOCK_REALTIME, &tv);
tv.tv_sec += 15;
rv = pthread_mutex_timedlock(&wait_open_lock, &tv);
if (rv != 0)
return -rv;
rv = pthread_cond_timedwait(&wait_open_cond, &wait_open_lock, &tv);
return rv;
}

View File

@@ -0,0 +1,227 @@
/*
* (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 <radioInterface.h>
#include "ms_rx_upper.h"
#include "syncthing.h"
void upper_trx::driveControl()
{
#ifdef IPCIF
auto m = pop_c();
if (!m)
return;
#else
TRX_C cmd;
socklen_t addr_len = sizeof(ctrlsrc);
int rdln = recvfrom(mCtrlSockets, (void *)cmd.cmd, sizeof(cmd) - 1, 0, &ctrlsrc, &addr_len);
if (rdln < 0 && errno == EAGAIN) {
std::cerr << "fuck, send ctrl?" << std::endl;
exit(0);
}
TRX_C *m = &cmd;
#endif
auto response = (TRX_C *)malloc(sizeof(TRX_C));
response->cmd[0] = '\0';
commandhandler(m->cmd, response->cmd);
#ifdef IPCIF
free(m);
#endif
std::clog << "response is " << response->cmd << std::endl;
#ifdef IPCIF
push_c(response);
#else
int rv = sendto(mCtrlSockets, response, strlen(response->cmd) + 1, 0, &ctrlsrc, sizeof(struct sockaddr_in));
if (rv < 0) {
std::cerr << "fuck, rcv ctrl?" << std::endl;
exit(0);
}
free(response);
#endif
}
void upper_trx::commandhandler(char *buffer, char *response)
{
int MAX_PACKET_LENGTH = TRXC_BUF_SIZE;
char cmdcheck[4];
char command[MAX_PACKET_LENGTH];
sscanf(buffer, "%3s %s", cmdcheck, command);
if (strcmp(cmdcheck, "CMD") != 0) {
LOG(WARNING) << "bogus message on control interface";
return;
}
std::clog << "command is " << buffer << std::endl << std::flush;
if (strcmp(command, "MEASURE") == 0) {
msleep(100);
int freq;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &freq);
sprintf(response, "RSP MEASURE 0 %d -80", freq);
} else if (strcmp(command, "ECHO") == 0) {
msleep(100);
sprintf(response, "RSP ECHO 0");
} else if (strcmp(command, "POWEROFF") == 0) {
set_ta(0);
// turn off transmitter/demod
// set_upper_ready(false);
sprintf(response, "RSP POWEROFF 0");
} else if (strcmp(command, "POWERON") == 0) {
// turn on transmitter/demod
if (!mTxFreq || !mRxFreq)
sprintf(response, "RSP POWERON 1");
else {
sprintf(response, "RSP POWERON 0");
if (!mOn) {
// Prepare for thread start
mPower = -20;
// start_ms();
set_upper_ready(true);
writeClockInterface();
mOn = true;
}
}
} else if (strcmp(command, "SETMAXDLY") == 0) {
//set expected maximum time-of-arrival
int maxDelay;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &maxDelay);
mMaxExpectedDelay = maxDelay; // 1 GSM symbol is approx. 1 km
sprintf(response, "RSP SETMAXDLY 0 %d", maxDelay);
} else if (strcmp(command, "SETRXGAIN") == 0) {
//set expected maximum time-of-arrival
int newGain;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &newGain);
newGain = setRxGain(newGain);
sprintf(response, "RSP SETRXGAIN 0 %d", newGain);
} else if (strcmp(command, "NOISELEV") == 0) {
if (mOn) {
float lev = 0; //mStates[chan].mNoiseLev;
sprintf(response, "RSP NOISELEV 0 %d", (int)round(20.0 * log10(rxFullScale / lev)));
} else {
sprintf(response, "RSP NOISELEV 1 0");
}
} else if (!strcmp(command, "SETPOWER")) {
// set output power in dB
int dbPwr;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &dbPwr);
if (!mOn)
sprintf(response, "RSP SETPOWER 1 %d", dbPwr);
else {
mPower = dbPwr;
setPowerAttenuation(mPower);
sprintf(response, "RSP SETPOWER 0 %d", dbPwr);
}
} else if (!strcmp(command, "ADJPOWER")) {
// adjust power in dB steps
int dbStep;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &dbStep);
if (!mOn)
sprintf(response, "RSP ADJPOWER 1 %d", mPower);
else {
mPower += dbStep;
setPowerAttenuation(mPower);
sprintf(response, "RSP ADJPOWER 0 %d", mPower);
}
} else if (strcmp(command, "RXTUNE") == 0) {
// tune receiver
int freqKhz;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &freqKhz);
mRxFreq = freqKhz * 1e3;
if (!tuneRx(mRxFreq)) {
LOG(ALERT) << "RX failed to tune";
sprintf(response, "RSP RXTUNE 1 %d", freqKhz);
} else
sprintf(response, "RSP RXTUNE 0 %d", freqKhz);
} else if (strcmp(command, "TXTUNE") == 0) {
// tune txmtr
int freqKhz;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &freqKhz);
mTxFreq = freqKhz * 1e3;
if (!tuneTx(mTxFreq)) {
LOG(ALERT) << "TX failed to tune";
sprintf(response, "RSP TXTUNE 1 %d", freqKhz);
} else
sprintf(response, "RSP TXTUNE 0 %d", freqKhz);
} else if (!strcmp(command, "SETTSC")) {
// set TSC
unsigned TSC;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &TSC);
if (mOn)
sprintf(response, "RSP SETTSC 1 %d", TSC);
// else if (chan && (TSC != mTSC))
// sprintf(response, "RSP SETTSC 1 %d", TSC);
else {
mTSC = TSC;
//generateMidamble(rx_sps, TSC);
sprintf(response, "RSP SETTSC 0 %d", TSC);
}
} else if (!strcmp(command, "GETBSIC")) {
if (mBSIC < 0)
sprintf(response, "RSP GETBSIC 1");
else
sprintf(response, "RSP GETBSIC 0 %d", mBSIC);
} else if (strcmp(command, "SETSLOT") == 0) {
// set TSC
int corrCode;
int timeslot;
sscanf(buffer, "%3s %s %d %d", cmdcheck, command, &timeslot, &corrCode);
if ((timeslot < 0) || (timeslot > 7)) {
LOG(WARNING) << "bogus message on control interface";
sprintf(response, "RSP SETSLOT 1 %d %d", timeslot, corrCode);
return;
}
sprintf(response, "RSP SETSLOT 0 %d %d", timeslot, corrCode);
} else if (!strcmp(command, "SETRXMASK")) {
int slot;
unsigned long long mask;
sscanf(buffer, "%3s %s %d 0x%llx", cmdcheck, command, &slot, &mask);
if ((slot < 0) || (slot > 7)) {
sprintf(response, "RSP SETRXMASK 1");
} else {
mRxSlotMask[slot] = mask;
sprintf(response, "RSP SETRXMASK 0 %d 0x%llx", slot, mask);
}
} else if (!strcmp(command, "SYNC")) {
// msleep(10);
sprintf(response, "RSP SYNC 0");
mMaxExpectedDelay = 48;
// setRxGain(30);
// msleep(10);
} else if (!strcmp(command, "SETTA")) {
int ta;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &ta);
set_ta(ta);
sprintf(response, "RSP SETTA 0 %d", ta);
} else {
LOG(WARNING) << "bogus command " << command << " on control interface.";
}
//mCtrlSockets[chan]->write(response, strlen(response) + 1);
}

View File

@@ -0,0 +1,211 @@
/*
* (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 "syncthing.h"
#include "sigProcLib.h"
#include "signalVector.h"
#include "grgsm_vitac/grgsm_vitac.h"
extern "C" {
#include "sch.h"
}
#if !defined(SYNCTHINGONLY) || !defined(NODAMNLOG)
#define DBGLG(...) ms_trx::dummy_log()
#else
#define DBGLG(...) std::cerr
#endif
#if !defined(SYNCTHINGONLY)
#define DBGLG2(...) ms_trx::dummy_log()
#else
#define DBGLG2(...) std::cerr
#endif
__attribute__((xray_always_instrument)) __attribute__((noinline)) static bool decode_sch(float *bits,
bool update_global_clock)
{
struct sch_info sch;
ubit_t info[GSM_SCH_INFO_LEN];
sbit_t data[GSM_SCH_CODED_LEN];
float_to_sbit(&bits[3], &data[0], 62, 39);
float_to_sbit(&bits[106], &data[39], 62, 39);
if (!gsm_sch_decode(info, data)) {
gsm_sch_parse(info, &sch);
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;
return true;
}
return false;
}
static void check_rcv_fn(GSM::Time t, bool first, unsigned int &lastfn, unsigned int &fnbm)
{
if (first && t.TN() == 0) {
lastfn = t.FN();
fnbm = 1 << 0;
first = false;
}
if (!first && t.FN() != lastfn) {
if (fnbm != 255)
std::cerr << "rx " << lastfn << ":" << fnbm << " " << __builtin_popcount(fnbm) << std::endl;
lastfn = t.FN();
fnbm = 1 << t.TN();
}
fnbm |= 1 << t.TN();
}
__attribute__((xray_always_instrument)) __attribute__((noinline)) static void
handle_it(one_burst &e, signalVector &burst, unsigned int tsc, int scale)
{
memset(burst.begin(), 0, burst.size() * sizeof(std::complex<float>));
auto is_sch = gsm_sch_check_fn(e.gsmts.FN()) && e.gsmts.TN() == 0;
auto is_fcch = gsm_fcch_check_fn(e.gsmts.FN()) && e.gsmts.TN() == 0;
// if (is_sch)
// return;
if (is_fcch)
return;
if (is_sch) {
unsigned char outbin[148];
convert_and_scale_default<float, int16_t>(burst.begin(), e.burst, ONE_TS_BURST_LEN * 2);
std::stringstream dbgout;
#if 0
{
struct estim_burst_params ebp;
auto rv2 = detectSCHBurst(burst, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp);
auto bits = demodAnyBurst(burst, SCH, 4, &ebp);
// clamp_array(bits->begin(), 148, 1.5f);
for (auto &i : *bits)
i = (i > 0 ? 1 : -1);
auto rv = decode_sch(bits->begin(), false);
dbgout << "U DET@" << (rv2 ? "yes " : " ") << "Timing offset " << ebp.toa
<< " symbols, DECODE: " << (rv ? "yes" : "---") << " ";
delete bits;
}
#endif
{
convert_and_scale<float, float>(burst.begin(), burst.begin(), ONE_TS_BURST_LEN * 2,
1.f / float(scale));
std::complex<float> channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
auto ss = reinterpret_cast<std::complex<float> *>(burst.begin());
int d_c0_burst_start = get_sch_chan_imp_resp(ss, &channel_imp_resp[0]);
detect_burst(ss, &channel_imp_resp[0], d_c0_burst_start, outbin);
SoftVector bits;
bits.resize(148);
for (int i = 0; i < 148; i++) {
bits[i] = (!outbin[i]) < 1 ? -1 : 1;
}
auto rv = decode_sch(bits.begin(), false);
dbgout << "U SCH@"
<< " " << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << d_c0_burst_start
<< " DECODE:" << (rv ? "yes" : "---") << std::endl;
}
DBGLG() << dbgout.str();
return;
}
#if 1
convert_and_scale<float, int16_t>(burst.begin(), e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(scale));
// std::cerr << "@" << tsc << " " << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << ebp.toa << " "
// << std::endl;
unsigned char outbin[148];
auto ss = reinterpret_cast<std::complex<float> *>(burst.begin());
float ncmax, dcmax;
std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR], chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR];
auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, tsc);
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;
if (is_nb)
detect_burst(ss, &chan_imp_resp[0], normal_burst_start, outbin);
else
detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin);
;
auto bits = SoftVector(148);
for (int i = 0; i < 148; i++)
(bits)[i] = outbin[i] < 1 ? -1 : 1;
#endif
}
__attribute__((xray_always_instrument)) __attribute__((noinline)) void rcv_bursts_test(rx_queue_t *q, unsigned int *tsc, int scale)
{
static bool first = true;
unsigned int lastfn = 0;
unsigned int fnbm = 0;
signalVector burst(ONE_TS_BURST_LEN, 100, 100);
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(1, &cpuset);
auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
if (rv < 0) {
std::cerr << "affinity: errreur! " << std::strerror(errno);
exit(0);
}
int prio = sched_get_priority_max(SCHED_RR);
struct sched_param param;
param.sched_priority = prio;
rv = sched_setscheduler(0, SCHED_RR, &param);
if (rv < 0) {
std::cerr << "scheduler: errreur! " << std::strerror(errno);
exit(0);
}
while (1) {
one_burst e;
while (!q->spsc_pop(&e)) {
q->spsc_prep_pop();
}
check_rcv_fn(e.gsmts, first, lastfn, fnbm);
handle_it(e, burst, *tsc, scale);
// rv = detectSCHBurst(*burst, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp);
// if (rv > 0)
// std::cerr << "#" << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << ebp.toa << std::endl;
// sched_yield();
}
}

View File

@@ -0,0 +1,25 @@
#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 "syncthing.h"
void rcv_bursts_test(rx_queue_t *q, unsigned int *tsc, int scale);

View File

@@ -0,0 +1,350 @@
/*
* (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 "syncthing.h"
#include "grgsm_vitac/grgsm_vitac.h"
extern "C" {
#include "sch.h"
}
#ifdef LOG
#undef LOG
#endif
#if !defined(SYNCTHINGONLY) //|| !defined(NODAMNLOG)
#define DBGLG(...) ms_trx::dummy_log()
#else
#define DBGLG(...) std::cerr
#endif
#if !defined(SYNCTHINGONLY) || !defined(NODAMNLOG)
#define DBGLG2(...) ms_trx::dummy_log()
#else
#define DBGLG2(...) std::cerr
#endif
#define PRINT_Q_OVERFLOW
__attribute__((xray_always_instrument)) __attribute__((noinline)) bool ms_trx::decode_sch(float *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];
float_to_sbit(&bits[3], &data[0], 62, 39);
float_to_sbit(&bits[106], &data[39], 62, 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);
}
#ifdef SYNCTHINGONLY
else {
int t3 = sch.t3p * 10 + 1;
if (t3 == 11) {
// timeslot hitter attempt @ fn 21 in mf
DBGLG2() << "sch @ " << t3 << std::endl;
auto e = GSM::Time(fn, 0);
e += 10;
ts_hitter_q.spsc_push(&e);
}
}
#endif
// auto sch11 = gsm_sch_check_fn(fn + 11);
// DBGLG() << "next sch: "<< (sch11 ? "11":"10")<<" first ts " << first_sch_buf_rcv_ts << std::endl;
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 = 0;
for (auto i : brst.burst)
sum += abs(i.real()) + abs(i.imag());
sum /= ONE_TS_BURST_LEN * 2;
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)
std::thread([this, newgain] { setRxGain(newgain); }).detach();
runmean = 0;
}
gain_check = (gain_check + 1) % avgburst_num;
}
static unsigned char sch_demod_bits[148];
bool ms_trx::handle_sch_or_nb()
{
one_burst brst;
auto current_gsm_time = timekeeper.gsmtime();
auto is_sch = gsm_sch_check_fn(current_gsm_time.FN()) && current_gsm_time.TN() == 0;
auto is_fcch = gsm_fcch_check_fn(current_gsm_time.FN()) && current_gsm_time.TN() == 0;
#pragma unused(is_fcch)
//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));
}
// auto pushok = rxqueue.spsc_push(&brst);
#ifndef SYNCTHINGONLY
if (upper_is_ready) { // this is blocking, so only submit if there is a reader - only if upper exists!
#endif
while (!rxqueue.spsc_push(&brst))
;
#ifndef SYNCTHINGONLY
}
#endif
// #ifdef PRINT_Q_OVERFLOW
// if (!pushok)
// std::cout << "F" << std::endl;
// #endif
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;
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];
float max_corr = 0;
int start;
memset((void *)&sch_acq_buffer[0], 0, sizeof(sch_acq_buffer));
if (is_first_sch_acq) {
convert_and_scale<float, int16_t>(which_out_buffer, which_in_buffer, buf_len * 2,
1.f / float(rxFullScale));
start = get_sch_buffer_chan_imp_resp(ss, &channel_imp_resp[0], buf_len, &max_corr);
detect_burst(&ss[start], &channel_imp_resp[0], 0, sch_demod_bits);
} else {
convert_and_scale<float, int16_t>(which_out_buffer, which_in_buffer, buf_len * 2,
1.f / float(rxFullScale));
start = get_sch_chan_imp_resp(ss, &channel_imp_resp[0]);
start = start < 39 ? start : 39;
start = start > -39 ? start : -39;
detect_burst(&ss[start], &channel_imp_resp[0], 0, sch_demod_bits);
}
SoftVector bitss(148);
for (int i = 0; i < 148; i++) {
bitss[i] = (!sch_demod_bits[i]) < 1 ? -1 : 1;
}
auto sch_decode_success = decode_sch(bitss.begin(), is_first_sch_acq);
if (sch_decode_success) {
const auto ts_offset_symb = 0;
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;
}
return false;
}
__attribute__((xray_never_instrument)) SCH_STATE ms_trx::search_for_sch(dev_buf_t *rcd)
{
static unsigned int sch_pos = 0;
if (sch_thread_done)
return SCH_STATE::FOUND;
if (rcv_done)
return SCH_STATE::SEARCHING;
auto to_copy = SCH_LEN_SPS - sch_pos;
if (SCH_LEN_SPS == to_copy) // first time
first_sch_buf_rcv_ts = rcd->get_first_ts();
if (!to_copy) {
sch_pos = 0;
rcv_done = true;
std::thread([this] {
set_name_aff_sched("sch_search", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5);
auto ptr = reinterpret_cast<const int16_t *>(first_sch_buf);
const auto target_val = rxFullScale / 8;
float sum = 0;
for (int i = 0; i < SCH_LEN_SPS * 2; i++)
sum += std::abs(ptr[i]);
sum /= SCH_LEN_SPS * 2;
//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!
return (bool)sch_thread_done;
}).detach();
}
auto spsmax = rcd->actual_samples_per_buffer();
if (to_copy > spsmax)
sch_pos += rcd->readall(first_sch_buf + sch_pos);
else
sch_pos += rcd->read_n(first_sch_buf + sch_pos, 0, to_copy);
return SCH_STATE::SEARCHING;
}
__attribute__((optnone)) 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 (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;
// memcpy(burst_copy_buffer, partial_buf, partial_rdofs * sizeof(blade_sample_type));
auto rd = rcd->read_n(burst_copy_buffer + partial_rdofs, 0, first_remaining);
if (rd != 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;
}

View File

@@ -0,0 +1,301 @@
/*
* (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 "syncthing.h"
#include <signalVector.h>
#include <radioVector.h>
#include <radioInterface.h>
#include "grgsm_vitac/grgsm_vitac.h"
#include "ms_rx_upper.h"
extern "C" {
#include <osmocom/core/select.h>
#include "sch.h"
#include "convolve.h"
#include "convert.h"
#include "proto_trxd.h"
void __lsan_do_recoverable_leak_check();
}
namespace trxcon
{
extern "C" {
#include <trxcon/trx_if.h>
}
trx_instance *trxcon_instance; // local handle
static tx_queue_t txq;
} // namespace trxcon
#ifdef LOG
#undef LOG
#define LOG(...) upper_trx::dummy_log()
#endif
void upper_trx::start_threads()
{
thr_control = std::thread([this] {
set_name_aff_sched("upper_ctrl", 1, SCHED_RR, sched_get_priority_max(SCHED_RR));
while (1) {
driveControl();
pthread_testcancel();
}
});
msleep(1);
thr_tx = std::thread([this] {
set_name_aff_sched("upper_tx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1);
while (1) {
driveTx();
pthread_testcancel();
}
});
// atomic ensures data is not written to q until loop reads
start_ms();
set_name_aff_sched("upper_rx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_RR) - 5);
while (1) {
// set_upper_ready(true);
driveReceiveFIFO();
pthread_testcancel();
osmo_select_main(1);
}
// std::thread([this] {
// set_name_aff_sched("leakcheck", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10);
// while (1) {
// std::this_thread::sleep_for(std::chrono::seconds{ 5 });
// __lsan_do_recoverable_leak_check();
// }
// }).detach();
}
void upper_trx::start_ms()
{
ms_trx::start();
}
SoftVector *upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset) __attribute__((optnone))
{
float pow, avg = 1.0;
static SoftVector bits(148);
static complex workbuf[40 + 625 + 40];
static signalVector sv(workbuf, 40, 625);
GSM::Time burst_time;
auto ss = reinterpret_cast<std::complex<float> *>(&workbuf[40]);
memset((void *)&workbuf[0], 0, sizeof(workbuf));
// assert(sv.begin() == &workbuf[40]);
one_burst e;
unsigned char outbin[148];
std::stringstream dbgout;
while (!rxqueue.spsc_pop(&e)) {
rxqueue.spsc_prep_pop();
}
burst_time = e.gsmts;
wTime = burst_time;
auto is_sch = (burst_time.TN() == 0 && gsm_sch_check_fn(burst_time.FN()));
auto is_fcch = (burst_time.TN() == 0 && gsm_fcch_check_fn(burst_time.FN()));
if (is_fcch) {
// return trash
// fprintf(stderr, "c %d\n",burst_time.FN());
return &bits;
}
if (is_sch) {
for (int i = 0; i < 148; i++)
(bits)[i] = (!e.sch_bits[i]) < 1 ? -1 : 1;
RSSI = 10;
timingOffset = 0;
// fprintf(stderr, "s %d\n", burst_time.FN());
return &bits;
}
auto ts = trxcon::trxcon_instance->ts_list[burst_time.TN()];
if (ts == NULL || ts->mf_layout == NULL)
return 0;
convert_and_scale<float, int16_t>(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(rxFullScale));
pow = energyDetect(sv, 20 * rx_sps);
if (pow < -1) {
LOG(ALERT) << "Received empty burst";
return NULL;
}
avg = sqrt(pow);
{
float ncmax, dcmax;
std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
std::complex<float> chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR];
auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, mTSC);
auto dummy_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp2[0], &dcmax, TS_DUMMY);
auto is_nb = ncmax > dcmax;
// std::cerr << " U " << (is_nb ? "NB" : "DB") << "@ o nb: " << normal_burst_start
// << " o db: " << dummy_burst_start << std::endl;
normal_burst_start = normal_burst_start < 39 ? normal_burst_start : 39;
normal_burst_start = normal_burst_start > -39 ? normal_burst_start : -39;
// fprintf(stderr, "%s %d\n", (is_nb ? "N":"D"), burst_time.FN());
// if (is_nb)
detect_burst(ss, &chan_imp_resp[0], normal_burst_start, outbin);
// else
// detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin);
for (int i = 0; i < 148; i++)
(bits)[i] = (outbin[i]) < 1 ? -1 : 1;
}
RSSI = (int)floor(20.0 * log10(rxFullScale / avg));
timingOffset = (int)round(0);
return &bits;
}
void upper_trx::driveReceiveFIFO()
{
int RSSI;
int TOA; // in 1/256 of a symbol
GSM::Time burstTime;
if (!mOn)
return;
SoftVector *rxBurst = pullRadioVector(burstTime, RSSI, TOA);
if (rxBurst) {
trxd_from_trx response;
response.ts = burstTime.TN();
response.fn = htonl(burstTime.FN());
response.rssi = RSSI;
response.toa = htons(TOA);
SoftVector::const_iterator burstItr = rxBurst->begin();
if (burstTime.TN() == 0 && gsm_sch_check_fn(burstTime.FN())) {
clamp_array(rxBurst->begin(), 148, 1.5f);
for (unsigned int i = 0; i < gSlotLen; i++) {
auto val = *burstItr++;
auto vval = isnan(val) ? 0 : val;
((int8_t *)response.symbols)[i] = round((vval - 0.5) * 64.0);
}
} else {
// invert and fix to +-127 sbits
for (int i = 0; i < 148; i++)
((int8_t *)response.symbols)[i] = *burstItr++ > 0.0f ? -127 : 127;
}
trxcon::trx_data_rx_handler(trxcon::trxcon_instance, (uint8_t *)&response);
}
}
void upper_trx::driveTx()
{
trxd_to_trx e;
while (!trxcon::txq.spsc_pop(&e)) {
trxcon::txq.spsc_prep_pop();
}
trxd_to_trx *burst = &e;
auto proper_fn = ntohl(burst->fn);
// std::cerr << "got burst!" << proper_fn << ":" << burst->ts
// << " current: " << timekeeper.gsmtime().FN()
// << " dff: " << (int64_t)((int64_t)timekeeper.gsmtime().FN() - (int64_t)proper_fn)
// << std::endl;
auto currTime = GSM::Time(proper_fn, burst->ts);
int RSSI = (int)burst->txlev;
static BitVector newBurst(gSlotLen);
BitVector::iterator itr = newBurst.begin();
auto *bufferItr = burst->symbols;
while (itr < newBurst.end())
*itr++ = *bufferItr++;
auto txburst = modulateBurst(newBurst, 8 + (currTime.TN() % 4 == 0), 4);
scaleVector(*txburst, txFullScale * 0.7 /* * pow(10, -RSSI / 10)*/);
// float -> int16
blade_sample_type burst_buf[txburst->size()];
convert_and_scale<int16_t, float>(burst_buf, txburst->begin(), txburst->size() * 2, 1);
// auto check = signalVector(txburst->size(), 40);
// convert_and_scale<float, int16_t, 1>(check.begin(), burst_buf, txburst->size() * 2);
// estim_burst_params ebp;
// auto d = detectAnyBurst(check, 2, 4, 4, CorrType::RACH, 40, &ebp);
// if(d)
// std::cerr << "RACH D! " << ebp.toa << std::endl;
// else
// std::cerr << "RACH NOOOOOOOOOO D! " << ebp.toa << std::endl;
// memory read --binary --outfile /tmp/mem.bin &burst_buf[0] --count 2500 --force
submit_burst(burst_buf, txburst->size(), currTime);
delete txburst;
}
int trxc_main(int argc, char *argv[])
{
pthread_setname_np(pthread_self(), "main_trxc");
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(0, 0);
trx->start_threads();
return status;
}
extern "C" {
void init_external_transceiver(struct trx_instance *trx, int argc, char **argv)
{
trxcon::trxcon_instance = (trxcon::trx_instance *)trx;
std::cout << "init?" << std::endl;
trxc_main(argc, argv);
}
void close_external_transceiver(int argc, char **argv)
{
std::cout << "Shutting down transceiver..." << std::endl;
}
void tx_external_transceiver(uint8_t *burst)
{
trxcon::txq.spsc_push((trxd_to_trx *)burst);
}
}

View File

@@ -0,0 +1,111 @@
#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 "radioClock.h"
#include "syncthing.h"
#include "l1if.h"
using tx_queue_t = spsc_cond<8 * 1, trxd_to_trx, true, false>;
class upper_trx : public ms_trx {
int rx_sps, tx_sps;
bool mOn; ///< flag to indicate that transceiver is powered on
double mTxFreq; ///< the transmit frequency
double mRxFreq; ///< the receive frequency
int mPower; ///< the transmit power in dB
unsigned mMaxExpectedDelay; ///< maximum TOA offset in GSM symbols
unsigned long long mRxSlotMask[8]; ///< MS - enabled multiframe slot mask
int mCtrlSockets;
sockaddr_in ctrldest;
sockaddr ctrlsrc;
void openudp(int *mSocketFD, unsigned short localPort, const char *wlocalIP)
{
*mSocketFD = socket(AF_INET, SOCK_DGRAM, 0);
int on = 1;
setsockopt(*mSocketFD, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
struct sockaddr_in address;
size_t length = sizeof(address);
bzero(&address, length);
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr(wlocalIP);
address.sin_port = htons(localPort);
if (bind(*mSocketFD, (struct sockaddr *)&address, length) < 0) {
std::cerr << "bind fail!" << std::endl;
exit(0);
}
}
bool resolveAddress(struct sockaddr_in *address, const char *host, unsigned short port)
{
struct hostent *hp;
int h_errno_local;
struct hostent hostData;
char tmpBuffer[2048];
auto rc = gethostbyname2_r(host, AF_INET, &hostData, tmpBuffer, sizeof(tmpBuffer), &hp, &h_errno_local);
if (hp == NULL || hp->h_addrtype != AF_INET || rc != 0) {
std::cerr << "WARNING -- gethostbyname() failed for " << host << ", "
<< hstrerror(h_errno_local);
exit(0);
return false;
}
address->sin_family = hp->h_addrtype;
assert(sizeof(address->sin_addr) == hp->h_length);
memcpy(&(address->sin_addr), hp->h_addr_list[0], hp->h_length);
address->sin_port = htons(port);
return true;
}
void driveControl();
void driveReceiveFIFO();
void driveTx();
void commandhandler(char *buffer, char *response);
void writeClockInterface(){};
SoftVector *pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset);
std::thread thr_control, thr_rx, thr_tx;
public:
void start_threads();
void start_ms();
upper_trx() : rx_sps(4), tx_sps(4)
{
auto c_srcport = 6700 + 2 * 0 + 1;
auto c_dstport = 6700 + 2 * 0 + 101;
openudp(&mCtrlSockets, c_srcport, "127.0.0.1");
resolveAddress(&ctrldest, "127.0.0.1", c_dstport);
};
};

View File

@@ -0,0 +1,337 @@
/*
* (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 "sigProcLib.h"
#include "syncthing.h"
#include "ms_rx_burst.h"
#include "grgsm_vitac/grgsm_vitac.h"
extern "C" {
#include "sch.h"
#include "convolve.h"
#include "convert.h"
}
dummylog ms_trx::dummy_log;
const int offset_start = -15;
int offsetrange = 200;
static int offset_ctr = 0;
void tx_test(ms_trx *t, ts_hitter_q_t *q, unsigned int *tsc)
{
sched_param sch_params;
sch_params.sched_priority = sched_get_priority_max(SCHED_FIFO);
pthread_setschedparam(pthread_self(), SCHED_FIFO, &sch_params);
auto burst = genRandAccessBurst(0, 4, 0);
scaleVector(*burst, t->txFullScale * 0.7);
// float -> int16
blade_sample_type burst_buf[burst->size()];
convert_and_scale<int16_t, float>(burst_buf, burst->begin(), burst->size() * 2, 1);
while (1) {
GSM::Time target;
while (!q->spsc_pop(&target)) {
q->spsc_prep_pop();
}
std::cerr << std::endl << "\x1B[32m hitting " << target.FN() << "\033[0m" << std::endl;
int timing_advance = 0;
int64_t now_ts;
GSM::Time now_time;
target.incTN(3); // ul dl offset
int target_fn = target.FN();
int target_tn = target.TN();
t->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 if (diff_tn < 0)
tosend.decTN(-diff_tn);
// in thory fn equal and tn+3 equal is also a problem...
if (diff_fn < 0 || (diff_fn == 0 && (now_time.TN() - target_tn < 1))) {
std::cerr << "## TX too late?! fn DIFF:" << diff_fn << " tn LOCAL: " << now_time.TN()
<< " tn OTHER: " << target_tn << std::endl;
return;
}
auto check = now_time + tosend;
int64_t send_ts =
now_ts + tosend.FN() * 8 * ONE_TS_BURST_LEN + tosend.TN() * ONE_TS_BURST_LEN - timing_advance;
// std::cerr << "## fn DIFF: " << diff_fn << " ## tn DIFF: " << diff_tn
// << " tn LOCAL: " << now_time.TN() << " tn OTHER: " << target_tn
// << " tndiff" << diff_tn << " tosend:" << tosend.FN() << ":" << tosend.TN()
// << " calc: " << check.FN() << ":" <<check.TN()
// << " target: " << target.FN() << ":" <<target.TN()
// << " ts now: " << now_ts << " target ts:" << send_ts << std::endl;
unsigned int pad = 4 * 25;
blade_sample_type buf2[burst->size() + pad];
memset(buf2, 0, pad * sizeof(blade_sample_type));
memcpy(&buf2[pad], burst_buf, burst->size() * sizeof(blade_sample_type));
assert(target.FN() == check.FN());
assert(target.TN() == check.TN());
assert(target.FN() % 51 == 21);
// auto this_offset = offset_start + (offset_ctr++ % offsetrange);
// std::cerr << "-- O " << this_offset << std::endl;
// send_ts = now_ts + ((target.FN() * 8 + (int)target.TN()) - (now_time.FN() * 8 + (int)now_time.TN())) * ONE_TS_BURST_LEN - timing_advance;
t->submit_burst_ts(buf2, burst->size() + pad, send_ts - pad);
// signalVector test(burst->size() + pad);
// convert_and_scale<float, int16_t>(test.begin(), buf2, burst->size() * 2 + pad, 1.f / float(scale));
// estim_burst_params ebp;
// auto det = detectAnyBurst(test, 0, 4, 4, CorrType::RACH, 40, &ebp);
// if (det > 0)
// std::cerr << "## Y " << ebp.toa << std::endl;
// else
// std::cerr << "## NOOOOOOOOO " << ebp.toa << std::endl;
}
}
#ifdef SYNCTHINGONLY
template <typename A> auto parsec(std::vector<std::string> &v, A &itr, std::string arg, bool *rv)
{
if (*itr == arg) {
*rv = true;
return true;
}
return false;
}
template <typename A, typename B, typename C>
bool parsec(std::vector<std::string> &v, A &itr, std::string arg, B f, C *rv)
{
if (*itr == arg) {
itr++;
if (itr != v.end()) {
*rv = f(itr->c_str());
return true;
}
}
return false;
}
template <typename A> bool parsec(std::vector<std::string> &v, A &itr, std::string arg, int scale, int *rv)
{
return parsec(
v, itr, arg, [scale](const char *v) -> auto{ return atoi(v) * scale; }, rv);
}
template <typename A> bool parsec(std::vector<std::string> &v, A &itr, std::string arg, int scale, unsigned int *rv)
{
return parsec(
v, itr, arg, [scale](const char *v) -> auto{ return atoi(v) * scale; }, rv);
}
int main(int argc, char *argv[])
{
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(2, &cpuset);
auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
if (rv < 0) {
std::cerr << "affinity: errreur! " << std::strerror(errno);
return 0;
}
unsigned int default_tx_freq(881000 * 1000), default_rx_freq(926000 * 1000);
unsigned int grx = 20, gtx = 20;
bool tx_flag = false;
pthread_setname_np(pthread_self(), "main");
convolve_init();
convert_init();
sigProcLibSetup();
initvita();
int status = 0;
auto trx = new ms_trx();
trx->do_auto_gain = true;
std::vector<std::string> args(argv + 1, argv + argc);
for (auto i = args.begin(); i != args.end(); ++i) {
parsec(args, i, "-r", 1000, &default_rx_freq);
parsec(args, i, "-t", 1000, &default_tx_freq);
parsec(args, i, "-gr", 1, &grx);
parsec(args, i, "-gt", 1, &gtx);
parsec(args, i, "-tx", &tx_flag);
}
std::cerr << "usage: " << argv[0] << " <rxfreq in khz, i.e. 926000> [txfreq in khz, i.e. 881000] [TX]"
<< std::endl
<< "rx" << (argc == 1 ? " (default) " : " ") << default_rx_freq << "hz, tx " << default_tx_freq
<< "hz" << std::endl
<< "gain rx " << grx << " gain tx " << gtx << std::endl
<< (tx_flag ? "##!!## RACH TX ACTIVE ##!!##" : "-- no rach tx --") << std::endl;
status = trx->init_dev_and_streams(0, 0);
if (status < 0)
return status;
trx->tuneRx(default_rx_freq);
trx->tuneTx(default_tx_freq);
trx->setRxGain(grx);
trx->setTxGain(gtx);
if (status == 0) {
// FIXME: hacks! needs exit flag for detached threads!
std::thread(rcv_bursts_test, &trx->rxqueue, &trx->mTSC, trx->rxFullScale).detach();
if (tx_flag)
std::thread(tx_test, trx, &trx->ts_hitter_q, &trx->mTSC).detach();
trx->start();
do {
sleep(1);
} while (1);
trx->stop_threads();
}
delete trx;
return status;
}
#endif
int ms_trx::init_streams(void *rx_cb, void *tx_cb)
{
return 0;
}
int ms_trx::init_dev_and_streams(void *rx_cb, void *tx_cb)
{
int status = 0;
status = base::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 unused(rcd)
auto y = this;
#pragma unused(y)
/* nothing to do here */
return 0;
};
}
void ms_trx::start()
{
auto fn = get_rx_burst_handler_fn(rx_bh());
rx_task = std::thread(fn);
usleep(1000);
auto fn2 = get_tx_burst_handler_fn(tx_bh());
tx_task = std::thread(fn2);
}
void ms_trx::set_upper_ready(bool is_ready)
{
upper_is_ready = is_ready;
}
void ms_trx::stop_threads()
{
std::cerr << "killing threads...\r\n" << std::endl;
rx_task.join();
}
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 thory fn equal and tn+3 equal is also a problem...
if (diff_fn < 0 || (diff_fn == 0 && (now_time.TN() - target_tn < 1))) {
std::cerr << "## TX too late?! fn DIFF:" << diff_fn << " tn LOCAL: " << now_time.TN()
<< " tn OTHER: " << target_tn << std::endl;
return;
}
auto check = now_time + tosend;
int64_t send_ts = now_ts + tosend.FN() * 8 * ONE_TS_BURST_LEN + tosend.TN() * ONE_TS_BURST_LEN - timing_advance;
// 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;
#if 1
unsigned int pad = 4 * 4;
blade_sample_type buf2[len + pad];
memset(buf2, 0, pad * sizeof(blade_sample_type));
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
}

View File

@@ -0,0 +1,235 @@
#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>
#elif defined(BUILDIPC)
#include "ipc_specific.h"
#define BASET ipc_hw<ms_trx>
#else
#error wat? no device..
#endif
#include "GSMCommon.h"
#include "itrq.h"
const unsigned int ONE_TS_BURST_LEN = (3 + 58 + 26 + 58 + 3 + 8.25) * 4 /*sps*/;
const unsigned int NUM_RXQ_FRAMES = 1; // rx thread <-> upper rx queue
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 (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;
}
}
template <typename DST_T, typename SRC_T, typename ST>
void convert_and_scale(void *dst, void *src, unsigned int src_len, ST scale)
{
for (unsigned int i = 0; i < src_len; i++)
reinterpret_cast<DST_T *>(dst)[i] = static_cast<DST_T>((reinterpret_cast<SRC_T *>(src)[i])) * scale;
}
template <typename DST_T, typename SRC_T> void convert_and_scale_default(void *dst, void *src, unsigned int src_len)
{
return convert_and_scale<DST_T, SRC_T>(dst, src, src_len, SAMPLE_SCALE_FACTOR);
}
struct one_burst {
one_burst()
{
}
GSM::Time gsmts;
union {
blade_sample_type burst[ONE_TS_BURST_LEN];
unsigned char sch_bits[148];
};
};
using rx_queue_t = spsc_cond<8 * NUM_RXQ_FRAMES, one_burst, true, true>;
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>;
struct ms_trx : public BASET {
using base = BASET;
static dummylog dummy_log;
unsigned int mTSC;
unsigned int mBSIC;
int timing_advance;
bool do_auto_gain;
std::thread rx_task;
std::thread tx_task;
std::thread *calcrval_task;
// provides bursts to upper rx thread
rx_queue_t rxqueue;
#ifdef SYNCTHINGONLY
ts_hitter_q_t ts_hitter_q;
#endif
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;
void start();
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(float *bits, bool update_global_clock);
SCH_STATE search_for_sch(dev_buf_t *rcd);
void grab_bursts(dev_buf_t *rcd);
int init_device();
int init_streams(void *rx_cb, void *tx_cb);
int init_dev_and_streams(void *rx_cb, void *tx_cb);
void stop_threads();
void *rx_cb(ms_trx *t);
void *tx_cb();
void maybe_update_gain(one_burst &brst);
ms_trx()
: 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]), rcv_done{ false }, sch_thread_done{ 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;
}
};

View File

@@ -0,0 +1,274 @@
#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;
virtual ~uhd_hw()
{
delete[] one_pkt_buf;
}
uhd_hw() : rxFullScale(32767), txFullScale(32767), rxtxdelay(-67)
{
}
bool tuneTx(double freq, size_t chan = 0)
{
msleep(25);
dev->set_tx_freq(freq, chan);
msleep(25);
return true;
};
bool tuneRx(double freq, size_t chan = 0)
{
msleep(25);
dev->set_rx_freq(freq, chan);
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 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;
std::string args = {};
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()))
std::this_thread::sleep_for(std::chrono::milliseconds(lock_delay_ms));
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 *rx_cb(bh_fn_t burst_handler)
{
void *ret;
static int to_skip = 0;
uhd::rx_metadata_t md;
auto num_rx_samps = rx_stream->recv(pkt_ptrs.front(), rx_spp, md, 3.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. Please consider the following:\n"
" Your write medium must sustain a rate of %fMB/s.\n"
" Dropped samples will not be written to the file.\n"
" Please modify this example for your purposes.\n"
" This message will not appear again.\n") %
1.f;
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)
{
auto fn = [this, burst_handler] {
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_stream->issue_stream_cmd(stream_cmd);
while (1) {
rx_cb(burst_handler);
}
};
return fn;
}
auto get_tx_burst_handler_fn(bh_fn_t burst_handler)
{
auto fn = [] {
// dummy
};
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);
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;
}
void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio)
{
pthread_setname_np(pthread_self(), name);
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpunum, &cpuset);
auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
if (rv < 0) {
std::cerr << name << " affinity: errreur! " << std::strerror(errno);
return exit(0);
}
sched_param sch_params;
sch_params.sched_priority = prio;
rv = pthread_setschedparam(pthread_self(), schedtype, &sch_params);
if (rv < 0) {
std::cerr << name << " sched: errreur! " << std::strerror(errno);
return exit(0);
}
}
};

View File

@@ -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)];

View File

@@ -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);

View File

@@ -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;

View File

@@ -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)
{
}
@@ -74,12 +75,12 @@ void RadioInterfaceMulti::close()
for (std::vector<signalVector*>::iterator it = history.begin(); it != history.end(); ++it)
delete *it;
mReceiveFIFO.resize(0);
powerScaling.resize(0);
history.resize(0);
active.resize(0);
rx_freq_state.resize(0);
tx_freq_state.resize(0);
mReceiveFIFO.clear();
powerScaling.clear();
history.clear();
active.clear();
rx_freq_state.clear();
tx_freq_state.clear();
RadioInterface::close();
}
@@ -154,18 +155,9 @@ bool RadioInterfaceMulti::init(int type)
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;

View File

@@ -100,13 +100,6 @@ bool RadioInterfaceResamp::init(int type)
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;

View File

@@ -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;

View File

@@ -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;

294
Transceiver52M/sch.c Normal file
View File

@@ -0,0 +1,294 @@
#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"
/* 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;
}
/* 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 */
__attribute__((xray_always_instrument)) __attribute__((noinline)) 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;
}
}

27
Transceiver52M/sch.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef _SCH_H_
#define _SCH_H_
#include <osmocom/core/bits.h>
struct sch_info {
int bsic;
int t1;
int t2;
int t3p;
};
#define GSM_SCH_INFO_LEN 25
#define GSM_SCH_UNCODED_LEN 35
#define GSM_SCH_CODED_LEN 78
int gsm_sch_decode(uint8_t *sb_info, sbit_t *burst);
int gsm_sch_parse(const uint8_t *sb_info, struct sch_info *desc);
int gsm_sch_to_fn(struct sch_info *sch);
int gsm_sch_check_fn(int fn);
int gsm_fcch_check_fn(int fn);
double gsm_fcch_offset(float *burst, int len);
int float_to_sbit(const float *in, sbit_t *out, float scale, int len);
#endif /* _SCH_H_ */

View File

@@ -87,17 +87,19 @@ static Resampler *dnsampler = NULL;
* perform 16-byte memory alignment required by many SSE instructions.
*/
struct CorrelationSequence {
CorrelationSequence() : sequence(NULL), buffer(NULL), toa(0.0)
CorrelationSequence() : sequence(NULL), buffer(NULL), toa(0.0), history(nullptr)
{
}
~CorrelationSequence()
{
delete sequence;
delete[] history;
}
signalVector *sequence;
void *buffer;
complex *history;
float toa;
complex gain;
};
@@ -129,6 +131,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;
@@ -151,6 +155,12 @@ void sigProcLibDestroy()
gRACHSequences[i] = NULL;
}
delete gSCHSequence;
gSCHSequence = NULL;
delete gDummySequence;
gDummySequence = NULL;
delete GMSKRotation1;
delete GMSKReverseRotation1;
delete GMSKRotation4;
@@ -315,6 +325,8 @@ static signalVector *convolve(const signalVector *x, const signalVector *h,
append = true;
break;
case CUSTOM:
// FIXME: x->getstart?
if (start < h->size() - 1) {
head = h->size() - start;
append = true;
@@ -1088,6 +1100,7 @@ signalVector *delayVector(const signalVector *in, signalVector *out, float delay
return out;
}
__attribute__((xray_never_instrument))
static complex interpolatePoint(const signalVector &inSig, float ix)
{
int start = (int) (floor(ix) - 10);
@@ -1289,6 +1302,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;
@@ -1384,6 +1468,70 @@ release:
return status;
}
bool generateSCHSequence(int sps)
{
bool status = true;
float toa;
complex *data = NULL;
signalVector *autocorr = NULL;
signalVector *seq0 = NULL, *seq1 = NULL, *_seq1 = NULL;
delete gSCHSequence;
seq0 = modulateBurst(gSCHSynchSequence, 0, sps, false);
if (!seq0)
return false;
seq1 = modulateBurst(gSCHSynchSequence, 0, sps, true);
if (!seq1) {
status = false;
goto release;
}
conjugateVector(*seq1);
/* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */
data = (complex *) convolve_h_alloc(seq1->size());
_seq1 = new signalVector(data, 0, seq1->size(), convolve_h_alloc, free);
_seq1->setAligned(true);
memcpy(_seq1->begin(), seq1->begin(), seq1->size() * sizeof(complex));
autocorr = convolve(seq0, _seq1, autocorr, NO_DELAY);
if (!autocorr) {
status = false;
goto release;
}
gSCHSequence = new CorrelationSequence;
gSCHSequence->sequence = _seq1;
gSCHSequence->buffer = data;
gSCHSequence->gain = peakDetect(*autocorr, &toa, NULL);
gSCHSequence->history = new complex[_seq1->size()];
/* For 1 sps only
* (Half of correlation length - 1) + midpoint of pulse shaping filer
* 20.5 = (64 / 2 - 1) + 1.5
*/
if (sps == 1)
gSCHSequence->toa = toa - 32.5;
else
gSCHSequence->toa = 0.0;
release:
delete autocorr;
delete seq0;
delete seq1;
if (!status) {
delete _seq1;
free(data);
gSCHSequence = NULL;
}
return status;
}
/*
* Peak-to-average computation +/- range from peak in symbols
*/
@@ -1441,17 +1589,18 @@ 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) {
delete out;
out = NULL;
}
if (dnsampler->rotate((float *)in.begin(), in_len, (float *)out->begin(), out_len) < 0) {
delete out;
out = NULL;
}
return out;
};
@@ -1469,6 +1618,12 @@ static float computeCI(const signalVector *burst, const CorrelationSequence *syn
/* Integer position where the sequence starts */
const int ps = start + 1 - N + (int)roundf(toa);
if(ps < 0) // might be -22 for toa 40 with N=64, if off by a lot during sch ms sync
return 0;
if (ps + N > burst->size())
return 0;
/* Estimate Signal power */
S = 0.0f;
for (int i=0, j=ps; i<(int)N; i++,j++)
@@ -1511,11 +1666,11 @@ static int detectBurst(const signalVector &burst,
corr_in = &burst;
break;
case 4:
dec = downsampleBurst(burst);
/* Running at the downsampled rate at this point: */
corr_in = dec;
sps = 1;
break;
dec = downsampleBurst(burst, len * 4, len);
/* 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);
}
@@ -1593,11 +1748,11 @@ static int detectGeneralBurst(const signalVector &rxBurst, float thresh, int sps
// Detect potential clipping
// We still may be able to demod the burst, so we'll give it a try
// and only report clipping if we can't demod.
float maxAmpl = maxAmplitude(rxBurst);
if (maxAmpl > CLIP_THRESH) {
LOG(INFO) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl;
clipping = true;
}
// float maxAmpl = maxAmplitude(rxBurst);
// if (maxAmpl > CLIP_THRESH) {
// LOG(INFO) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl;
// clipping = true;
// }
start = target - head - 1;
len = head + tail;
@@ -1652,6 +1807,86 @@ static int detectRACHBurst(const signalVector &burst, float threshold, int sps,
return rc;
}
int detectSCHBurst(signalVector &burst,
float thresh,
int sps,
sch_detect_type state, struct estim_burst_params *ebp)
{
int rc, start, target, head, tail, len;
float _toa;
complex _amp;
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 _burst(burst, sync->sequence->size(), 5);
memcpy(_burst.begin() - sync->sequence->size(), sync->history, sync->sequence->size() * sizeof(complex));
memcpy(sync->history, &burst.begin()[burst.size() - sync->sequence->size()],
sync->sequence->size() * sizeof(complex));
rc = detectBurst(_burst, corr, sync, thresh, sps, start, len, ebp);
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
*
@@ -1670,7 +1905,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];
@@ -1719,6 +1954,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";
}
@@ -1921,6 +2159,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);

View File

@@ -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,6 +136,17 @@ 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, CorrType type,
int sps, struct estim_burst_params *ebp);

View File

@@ -138,6 +138,11 @@ AC_ARG_WITH(ipc, [
[enable IPC])
])
AC_ARG_WITH(bladerf, [
AS_HELP_STRING([--with-bladerf],
[enable bladeRF])
])
AC_ARG_WITH(singledb, [
AS_HELP_STRING([--with-singledb],
[enable single daughterboard use on USRP1])
@@ -187,6 +192,10 @@ AS_IF([test "x$with_uhd" = "xyes"],[
)
])
AS_IF([test "x$with_bladerf" = "xyes"], [
PKG_CHECK_MODULES(BLADE, libbladeRF >= 2.0)
])
AS_IF([test "x$with_singledb" = "xyes"], [
AC_DEFINE(SINGLEDB, 1, Define to 1 for single daughterboard)
])
@@ -240,6 +249,7 @@ AM_CONDITIONAL(DEVICE_UHD, [test "x$with_uhd" = "xyes"])
AM_CONDITIONAL(DEVICE_USRP1, [test "x$with_usrp1" = "xyes"])
AM_CONDITIONAL(DEVICE_LMS, [test "x$with_lms" = "xyes"])
AM_CONDITIONAL(DEVICE_IPC, [test "x$with_ipc" = "xyes"])
AM_CONDITIONAL(DEVICE_BLADE, [test "x$with_bladerf" = "xyes"])
AM_CONDITIONAL(ARCH_ARM, [test "x$with_neon" = "xyes" || test "x$with_neon_vfpv4" = "xyes"])
AM_CONDITIONAL(ARCH_ARM_A15, [test "x$with_neon_vfpv4" = "xyes"])
@@ -312,6 +322,7 @@ AC_MSG_RESULT([LDFLAGS="$LDFLAGS"])
dnl Output files
AC_CONFIG_FILES([\
Makefile \
trxcon/Makefile \
CommonLibs/Makefile \
GSM/Makefile \
Transceiver52M/Makefile \
@@ -325,6 +336,8 @@ AC_CONFIG_FILES([\
Transceiver52M/device/usrp1/Makefile \
Transceiver52M/device/lms/Makefile \
Transceiver52M/device/ipc/Makefile \
Transceiver52M/device/ipc2/Makefile \
Transceiver52M/device/bladerf/Makefile \
tests/Makefile \
tests/CommonLibs/Makefile \
tests/Transceiver52M/Makefile \

27
trxcon/.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# autoreconf by-products
*.in
aclocal.m4
autom4te.cache/
configure
depcomp
install-sh
missing
compile
# configure by-products
.deps/
Makefile
config.status
version.h
# build by-products
*.o
*.a
trxcon
# various
.version
.tarball-version

58
trxcon/Makefile.am Normal file
View File

@@ -0,0 +1,58 @@
#AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
AM_CXXFLAGS = -Wall -O3 -g -ldl -lpthread
# versioning magic
BUILT_SOURCES = $(top_srcdir)/.version
$(top_srcdir)/.version:
echo $(VERSION) > $@-t && mv $@-t $@
dist-hook:
echo $(VERSION) > $(distdir)/.tarball-version
AM_CPPFLAGS = \
$(all_includes) \
-I$(top_srcdir)/include \
$(NULL)
AM_CFLAGS = \
-Wall \
$(LIBOSMOCORE_CFLAGS) \
$(LIBOSMOCODING_CFLAGS) \
$(LIBOSMOGSM_CFLAGS) \
$(NULL)
noinst_LTLIBRARIES = libtrxcon.la
libtrxcon_la_SOURCES = \
l1ctl_link.c \
l1ctl.c \
trx_if.c \
logging.c \
trxcon.c \
$(NULL)
# Scheduler
libtrxcon_la_SOURCES += \
sched_lchan_common.c \
sched_lchan_pdtch.c \
sched_lchan_desc.c \
sched_lchan_xcch.c \
sched_lchan_tchf.c \
sched_lchan_tchh.c \
sched_lchan_rach.c \
sched_lchan_sch.c \
sched_mframe.c \
sched_clck.c \
sched_prim.c \
sched_trx.c \
$(NULL)
libtrxcon_la_LIBADD = \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOCODING_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(NULL)

917
trxcon/l1ctl.c Normal file
View File

@@ -0,0 +1,917 @@
/*
* OsmocomBB <-> SDR connection bridge
* GSM L1 control interface handlers
*
* (C) 2014 by Sylvain Munaut <tnt@246tNt.com>
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <arpa/inet.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include "logging.h"
#include "l1ctl_link.h"
#include "l1ctl_proto.h"
#include "trx_if.h"
#include "sched_trx.h"
static const char *arfcn2band_name(uint16_t arfcn)
{
enum gsm_band band;
if (gsm_arfcn2band_rc(arfcn, &band) < 0)
return "(invalid)";
return gsm_band_name(band);
}
static struct msgb *l1ctl_alloc_msg(uint8_t msg_type)
{
struct l1ctl_hdr *l1h;
struct msgb *msg;
/**
* Each L1CTL message gets its own length pushed in front
* before sending. This is why we need this small headroom.
*/
msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_MSG_LEN_FIELD,
L1CTL_MSG_LEN_FIELD, "l1ctl_tx_msg");
if (!msg) {
LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n");
return NULL;
}
msg->l1h = msgb_put(msg, sizeof(*l1h));
l1h = (struct l1ctl_hdr *) msg->l1h;
l1h->msg_type = msg_type;
return msg;
}
int l1ctl_tx_pm_conf(struct l1ctl_link *l1l, uint16_t band_arfcn,
int dbm, int last)
{
struct l1ctl_pm_conf *pmc;
struct msgb *msg;
msg = l1ctl_alloc_msg(L1CTL_PM_CONF);
if (!msg)
return -ENOMEM;
LOGP(DL1C, LOGL_DEBUG, "Send PM Conf (%s %d = %d dBm)\n",
arfcn2band_name(band_arfcn),
band_arfcn &~ ARFCN_FLAG_MASK, dbm);
pmc = (struct l1ctl_pm_conf *) msgb_put(msg, sizeof(*pmc));
pmc->band_arfcn = htons(band_arfcn);
pmc->pm[0] = dbm2rxlev(dbm);
pmc->pm[1] = 0;
if (last) {
struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->l1h;
l1h->flags |= L1CTL_F_DONE;
}
return l1ctl_link_send(l1l, msg);
}
int l1ctl_tx_reset_ind(struct l1ctl_link *l1l, uint8_t type)
{
struct msgb *msg;
struct l1ctl_reset *res;
msg = l1ctl_alloc_msg(L1CTL_RESET_IND);
if (!msg)
return -ENOMEM;
LOGP(DL1C, LOGL_DEBUG, "Send Reset Ind (%u)\n", type);
res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
res->type = type;
return l1ctl_link_send(l1l, msg);
}
int l1ctl_tx_reset_conf(struct l1ctl_link *l1l, uint8_t type)
{
struct msgb *msg;
struct l1ctl_reset *res;
msg = l1ctl_alloc_msg(L1CTL_RESET_CONF);
if (!msg)
return -ENOMEM;
LOGP(DL1C, LOGL_DEBUG, "Send Reset Conf (%u)\n", type);
res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
res->type = type;
return l1ctl_link_send(l1l, msg);
}
static struct l1ctl_info_dl *put_dl_info_hdr(struct msgb *msg, struct l1ctl_info_dl *dl_info)
{
size_t len = sizeof(struct l1ctl_info_dl);
struct l1ctl_info_dl *dl = (struct l1ctl_info_dl *) msgb_put(msg, len);
if (dl_info) /* Copy DL info provided by handler */
memcpy(dl, dl_info, len);
else /* Init DL info header */
memset(dl, 0x00, len);
return dl;
}
/* Fill in FBSB payload: BSIC and sync result */
static struct l1ctl_fbsb_conf *fbsb_conf_make(struct msgb *msg, uint8_t result, uint8_t bsic)
{
struct l1ctl_fbsb_conf *conf = (struct l1ctl_fbsb_conf *) msgb_put(msg, sizeof(*conf));
LOGP(DL1C, LOGL_DEBUG, "Send FBSB Conf (result=%u, bsic=%u)\n", result, bsic);
conf->result = result;
conf->bsic = bsic;
return conf;
}
int l1ctl_tx_fbsb_conf(struct l1ctl_link *l1l, uint8_t result,
struct l1ctl_info_dl *dl_info, uint8_t bsic)
{
struct l1ctl_fbsb_conf *conf;
struct msgb *msg;
msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
if (msg == NULL)
return -ENOMEM;
put_dl_info_hdr(msg, dl_info);
talloc_free(dl_info);
conf = fbsb_conf_make(msg, result, bsic);
/* FIXME: set proper value */
conf->initial_freq_err = 0;
/* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */
l1l->fbsb_conf_sent = true;
/* Abort FBSB expire timer */
if (osmo_timer_pending(&l1l->fbsb_timer))
osmo_timer_del(&l1l->fbsb_timer);
return l1ctl_link_send(l1l, msg);
}
int l1ctl_tx_ccch_mode_conf(struct l1ctl_link *l1l, uint8_t mode)
{
struct l1ctl_ccch_mode_conf *conf;
struct msgb *msg;
msg = l1ctl_alloc_msg(L1CTL_CCCH_MODE_CONF);
if (msg == NULL)
return -ENOMEM;
conf = (struct l1ctl_ccch_mode_conf *) msgb_put(msg, sizeof(*conf));
conf->ccch_mode = mode;
return l1ctl_link_send(l1l, msg);
}
/**
* Handles both L1CTL_DATA_IND and L1CTL_TRAFFIC_IND.
*/
int l1ctl_tx_dt_ind(struct l1ctl_link *l1l, struct l1ctl_info_dl *data,
uint8_t *l2, size_t l2_len, bool traffic)
{
struct msgb *msg;
uint8_t *msg_l2;
msg = l1ctl_alloc_msg(traffic ?
L1CTL_TRAFFIC_IND : L1CTL_DATA_IND);
if (msg == NULL)
return -ENOMEM;
put_dl_info_hdr(msg, data);
/* Copy the L2 payload if preset */
if (l2 && l2_len > 0) {
msg_l2 = (uint8_t *) msgb_put(msg, l2_len);
memcpy(msg_l2, l2, l2_len);
}
/* Put message to upper layers */
return l1ctl_link_send(l1l, msg);
}
int l1ctl_tx_rach_conf(struct l1ctl_link *l1l,
uint16_t band_arfcn, uint32_t fn)
{
struct l1ctl_info_dl *dl;
struct msgb *msg;
msg = l1ctl_alloc_msg(L1CTL_RACH_CONF);
if (msg == NULL)
return -ENOMEM;
dl = put_dl_info_hdr(msg, NULL);
memset(dl, 0x00, sizeof(*dl));
dl->band_arfcn = htons(band_arfcn);
dl->frame_nr = htonl(fn);
return l1ctl_link_send(l1l, msg);
}
/**
* Handles both L1CTL_DATA_CONF and L1CTL_TRAFFIC_CONF.
*/
int l1ctl_tx_dt_conf(struct l1ctl_link *l1l,
struct l1ctl_info_dl *data, bool traffic)
{
struct msgb *msg;
msg = l1ctl_alloc_msg(traffic ?
L1CTL_TRAFFIC_CONF : L1CTL_DATA_CONF);
if (msg == NULL)
return -ENOMEM;
/* Copy DL frame header from source message */
put_dl_info_hdr(msg, data);
return l1ctl_link_send(l1l, msg);
}
static enum gsm_phys_chan_config l1ctl_ccch_mode2pchan_config(enum ccch_mode mode)
{
switch (mode) {
/* TODO: distinguish extended BCCH */
case CCCH_MODE_NON_COMBINED:
case CCCH_MODE_NONE:
return GSM_PCHAN_CCCH;
case CCCH_MODE_COMBINED:
return GSM_PCHAN_CCCH_SDCCH4;
case CCCH_MODE_COMBINED_CBCH:
return GSM_PCHAN_CCCH_SDCCH4_CBCH;
default:
LOGP(DL1C, LOGL_NOTICE, "Undandled CCCH mode (%u), "
"assuming non-combined configuration\n", mode);
return GSM_PCHAN_CCCH;
}
}
/* FBSB expire timer */
static void fbsb_timer_cb(void *data)
{
struct l1ctl_link *l1l = (struct l1ctl_link *) data;
struct l1ctl_info_dl *dl;
struct msgb *msg;
msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
if (msg == NULL)
return;
LOGP(DL1C, LOGL_NOTICE, "FBSB timer fired for ARFCN %u\n", l1l->trx->band_arfcn &~ ARFCN_FLAG_MASK);
dl = put_dl_info_hdr(msg, NULL);
/* Fill in current ARFCN */
dl->band_arfcn = htons(l1l->trx->band_arfcn);
fbsb_conf_make(msg, 255, 0);
/* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */
l1l->fbsb_conf_sent = true;
l1ctl_link_send(l1l, msg);
}
static int l1ctl_rx_fbsb_req(struct l1ctl_link *l1l, struct msgb *msg)
{
enum gsm_phys_chan_config ch_config;
struct l1ctl_fbsb_req *fbsb;
uint16_t band_arfcn;
uint16_t timeout;
int rc = 0;
fbsb = (struct l1ctl_fbsb_req *) msg->l1h;
if (msgb_l1len(msg) < sizeof(*fbsb)) {
LOGP(DL1C, LOGL_ERROR, "MSG too short FBSB Req: %u\n",
msgb_l1len(msg));
rc = -EINVAL;
goto exit;
}
ch_config = l1ctl_ccch_mode2pchan_config(fbsb->ccch_mode);
band_arfcn = ntohs(fbsb->band_arfcn);
timeout = ntohs(fbsb->timeout);
LOGP(DL1C, LOGL_NOTICE, "Received FBSB request (%s %d)\n",
arfcn2band_name(band_arfcn),
band_arfcn &~ ARFCN_FLAG_MASK);
/* Reset scheduler and clock counter */
sched_trx_reset(l1l->trx, true);
/* Configure a single timeslot */
sched_trx_configure_ts(l1l->trx, 0, ch_config);
/* Ask SCH handler to send L1CTL_FBSB_CONF */
l1l->fbsb_conf_sent = false;
/* Only if current ARFCN differs */
if (l1l->trx->band_arfcn != band_arfcn) {
/* Update current ARFCN */
l1l->trx->band_arfcn = band_arfcn;
/* Tune transceiver to required ARFCN */
trx_if_cmd_rxtune(l1l->trx, band_arfcn);
trx_if_cmd_txtune(l1l->trx, band_arfcn);
}
/* Transceiver might have been powered on before, e.g.
* in case of sending L1CTL_FBSB_REQ due to signal loss. */
if (!l1l->trx->powered_up)
trx_if_cmd_poweron(l1l->trx);
trx_if_cmd_sync(l1l->trx);
/* Start FBSB expire timer */
l1l->fbsb_timer.data = l1l;
l1l->fbsb_timer.cb = fbsb_timer_cb;
LOGP(DL1C, LOGL_INFO, "Starting FBSB timer %u ms\n", timeout * GSM_TDMA_FN_DURATION_uS / 1000);
osmo_timer_schedule(&l1l->fbsb_timer, 2, timeout * GSM_TDMA_FN_DURATION_uS);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_pm_req(struct l1ctl_link *l1l, struct msgb *msg)
{
uint16_t band_arfcn_start, band_arfcn_stop;
struct l1ctl_pm_req *pmr;
int rc = 0;
pmr = (struct l1ctl_pm_req *) msg->l1h;
if (msgb_l1len(msg) < sizeof(*pmr)) {
LOGP(DL1C, LOGL_ERROR, "MSG too short PM Req: %u\n",
msgb_l1len(msg));
rc = -EINVAL;
goto exit;
}
band_arfcn_start = ntohs(pmr->range.band_arfcn_from);
band_arfcn_stop = ntohs(pmr->range.band_arfcn_to);
LOGP(DL1C, LOGL_NOTICE, "Received power measurement "
"request (%s: %d -> %d)\n",
arfcn2band_name(band_arfcn_start),
band_arfcn_start &~ ARFCN_FLAG_MASK,
band_arfcn_stop &~ ARFCN_FLAG_MASK);
/* Send measurement request to transceiver */
rc = trx_if_cmd_measure(l1l->trx, band_arfcn_start, band_arfcn_stop);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_reset_req(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_reset *res;
int rc = 0;
res = (struct l1ctl_reset *) msg->l1h;
if (msgb_l1len(msg) < sizeof(*res)) {
LOGP(DL1C, LOGL_ERROR, "MSG too short Reset Req: %u\n",
msgb_l1len(msg));
rc = -EINVAL;
goto exit;
}
LOGP(DL1C, LOGL_NOTICE, "Received reset request (%u)\n",
res->type);
switch (res->type) {
case L1CTL_RES_T_FULL:
/* TODO: implement trx_if_reset() */
trx_if_cmd_poweroff(l1l->trx);
trx_if_cmd_echo(l1l->trx);
/* Fall through */
case L1CTL_RES_T_SCHED:
sched_trx_reset(l1l->trx, true);
break;
default:
LOGP(DL1C, LOGL_ERROR, "Unknown L1CTL_RESET_REQ type\n");
goto exit;
}
/* Confirm */
rc = l1ctl_tx_reset_conf(l1l, res->type);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_echo_req(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_hdr *l1h;
LOGP(DL1C, LOGL_NOTICE, "Recv Echo Req\n");
LOGP(DL1C, LOGL_NOTICE, "Send Echo Conf\n");
/* Nothing to do, just send it back */
l1h = (struct l1ctl_hdr *) msg->l1h;
l1h->msg_type = L1CTL_ECHO_CONF;
msg->data = msg->l1h;
return l1ctl_link_send(l1l, msg);
}
static int l1ctl_rx_ccch_mode_req(struct l1ctl_link *l1l, struct msgb *msg)
{
enum gsm_phys_chan_config ch_config;
struct l1ctl_ccch_mode_req *req;
struct trx_ts *ts;
int rc = 0;
req = (struct l1ctl_ccch_mode_req *) msg->l1h;
if (msgb_l1len(msg) < sizeof(*req)) {
LOGP(DL1C, LOGL_ERROR, "MSG too short Reset Req: %u\n",
msgb_l1len(msg));
rc = -EINVAL;
goto exit;
}
LOGP(DL1C, LOGL_NOTICE, "Received CCCH mode request (%u)\n",
req->ccch_mode); /* TODO: add value-string for ccch_mode */
/* Make sure that TS0 is allocated and configured */
ts = l1l->trx->ts_list[0];
if (ts == NULL || ts->mf_layout == NULL) {
LOGP(DL1C, LOGL_ERROR, "TS0 is not configured");
rc = -EINVAL;
goto exit;
}
/* Choose corresponding channel combination */
ch_config = l1ctl_ccch_mode2pchan_config(req->ccch_mode);
/* Do nothing if the current mode matches required */
if (ts->mf_layout->chan_config != ch_config)
rc = sched_trx_configure_ts(l1l->trx, 0, ch_config);
/* Confirm reconfiguration */
if (!rc)
rc = l1ctl_tx_ccch_mode_conf(l1l, req->ccch_mode);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_rach_req(struct l1ctl_link *l1l, struct msgb *msg, bool ext)
{
struct l1ctl_ext_rach_req *ext_req;
struct l1ctl_rach_req *req;
struct l1ctl_info_ul *ul;
struct trx_ts_prim *prim;
size_t len;
int rc;
ul = (struct l1ctl_info_ul *) msg->l1h;
/* Is it extended (11-bit) RACH or not? */
if (ext) {
ext_req = (struct l1ctl_ext_rach_req *) ul->payload;
ext_req->offset = ntohs(ext_req->offset);
ext_req->ra11 = ntohs(ext_req->ra11);
len = sizeof(*ext_req);
LOGP(DL1C, LOGL_NOTICE, "Received extended (11-bit) RACH request "
"(offset=%u, synch_seq=%u, ra11=0x%02hx)\n",
ext_req->offset, ext_req->synch_seq, ext_req->ra11);
} else {
req = (struct l1ctl_rach_req *) ul->payload;
req->offset = ntohs(req->offset);
len = sizeof(*req);
LOGP(DL1C, LOGL_NOTICE, "Received regular (8-bit) RACH request "
"(offset=%u, ra=0x%02x)\n", req->offset, req->ra);
}
/* The controlling L1CTL side always does include the UL info header,
* but may leave it empty. We assume RACH is on TS0 in this case. */
if (ul->chan_nr == 0x00) {
LOGP(DL1C, LOGL_NOTICE, "The UL info header is empty, "
"assuming RACH is on TS0\n");
ul->chan_nr = RSL_CHAN_RACH;
}
/* Init a new primitive */
rc = sched_prim_init(l1l->trx, &prim, len, ul->chan_nr, ul->link_id);
if (rc)
goto exit;
/**
* Push this primitive to the transmit queue.
* Indicated timeslot needs to be configured.
*/
rc = sched_prim_push(l1l->trx, prim, ul->chan_nr);
if (rc) {
talloc_free(prim);
goto exit;
}
/* Fill in the payload */
memcpy(prim->payload, ul->payload, len);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_proc_est_req_h0(struct trx_instance *trx, struct l1ctl_h0 *h)
{
uint16_t band_arfcn;
int rc = 0;
band_arfcn = ntohs(h->band_arfcn);
LOGP(DL1C, LOGL_NOTICE, "L1CTL_DM_EST_REQ indicates a single "
"ARFCN=%u channel\n", band_arfcn &~ ARFCN_FLAG_MASK);
/* Do we need to retune? */
if (trx->band_arfcn == band_arfcn)
return 0;
/* Tune transceiver to required ARFCN */
rc |= trx_if_cmd_rxtune(trx, band_arfcn);
rc |= trx_if_cmd_txtune(trx, band_arfcn);
if (rc)
return rc;
/* Update current ARFCN */
trx->band_arfcn = band_arfcn;
return 0;
}
static int l1ctl_proc_est_req_h1(struct trx_instance *trx, struct l1ctl_h1 *h)
{
uint16_t ma[64];
int i, rc;
LOGP(DL1C, LOGL_NOTICE, "L1CTL_DM_EST_REQ indicates a Frequency "
"Hopping (hsn=%u, maio=%u, chans=%u) channel\n",
h->hsn, h->maio, h->n);
/* No channels?!? */
if (!h->n) {
LOGP(DL1C, LOGL_ERROR, "No channels in mobile allocation?!?\n");
return -EINVAL;
} else if (h->n > ARRAY_SIZE(ma)) {
LOGP(DL1C, LOGL_ERROR, "More than 64 channels in mobile allocation?!?\n");
return -EINVAL;
}
/* Convert from network to host byte order */
for (i = 0; i < h->n; i++)
ma[i] = ntohs(h->ma[i]);
/* Forward hopping parameters to TRX */
rc = trx_if_cmd_setfh(trx, h->hsn, h->maio, ma, h->n);
if (rc)
return rc;
/**
* TODO: update the state of trx_instance somehow
* in order to indicate that it is in hopping mode...
*/
return 0;
}
static int l1ctl_rx_dm_est_req(struct l1ctl_link *l1l, struct msgb *msg)
{
enum gsm_phys_chan_config config;
struct l1ctl_dm_est_req *est_req;
struct l1ctl_info_ul *ul;
struct trx_ts *ts;
uint8_t chan_nr, tn;
int rc;
ul = (struct l1ctl_info_ul *) msg->l1h;
est_req = (struct l1ctl_dm_est_req *) ul->payload;
chan_nr = ul->chan_nr;
tn = chan_nr & 0x07;
LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_DM_EST_REQ "
"(tn=%u, chan_nr=0x%02x, tsc=%u, tch_mode=0x%02x)\n",
tn, chan_nr, est_req->tsc, est_req->tch_mode);
/* Determine channel config */
config = sched_trx_chan_nr2pchan_config(chan_nr);
if (config == GSM_PCHAN_NONE) {
LOGP(DL1C, LOGL_ERROR, "Couldn't determine channel config\n");
rc = -EINVAL;
goto exit;
}
/* Frequency hopping? */
if (est_req->h)
rc = l1ctl_proc_est_req_h1(l1l->trx, &est_req->h1);
else /* Single ARFCN */
rc = l1ctl_proc_est_req_h0(l1l->trx, &est_req->h0);
if (rc)
goto exit;
/* Update TSC (Training Sequence Code) */
l1l->trx->tsc = est_req->tsc;
/* Configure requested TS */
rc = sched_trx_configure_ts(l1l->trx, tn, config);
ts = l1l->trx->ts_list[tn];
if (rc) {
rc = -EINVAL;
goto exit;
}
/* Deactivate all lchans */
sched_trx_deactivate_all_lchans(ts);
/* Activate only requested lchans */
rc = sched_trx_set_lchans(ts, chan_nr, 1, est_req->tch_mode);
if (rc) {
LOGP(DL1C, LOGL_ERROR, "Couldn't activate requested lchans\n");
rc = -EINVAL;
goto exit;
}
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_dm_rel_req(struct l1ctl_link *l1l, struct msgb *msg)
{
LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_DM_REL_REQ, "
"switching back to CCCH\n");
/* Reset scheduler */
sched_trx_reset(l1l->trx, false);
msgb_free(msg);
return 0;
}
/**
* Handles both L1CTL_DATA_REQ and L1CTL_TRAFFIC_REQ.
*/
static int l1ctl_rx_dt_req(struct l1ctl_link *l1l,
struct msgb *msg, bool traffic)
{
struct l1ctl_info_ul *ul;
struct trx_ts_prim *prim;
uint8_t chan_nr, link_id;
size_t payload_len;
int rc;
/* Extract UL frame header */
ul = (struct l1ctl_info_ul *) msg->l1h;
/* Calculate the payload len */
msg->l2h = ul->payload;
payload_len = msgb_l2len(msg);
/* Obtain channel description */
chan_nr = ul->chan_nr;
link_id = ul->link_id & 0x40;
LOGP(DL1D, LOGL_DEBUG, "Recv %s Req (chan_nr=0x%02x, "
"link_id=0x%02x, len=%zu)\n", traffic ? "TRAFFIC" : "DATA",
chan_nr, link_id, payload_len);
/* Init a new primitive */
rc = sched_prim_init(l1l->trx, &prim, payload_len,
chan_nr, link_id);
if (rc)
goto exit;
/* Push this primitive to transmit queue */
rc = sched_prim_push(l1l->trx, prim, chan_nr);
if (rc) {
talloc_free(prim);
goto exit;
}
/* Fill in the payload */
memcpy(prim->payload, ul->payload, payload_len);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_param_req(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_par_req *par_req;
struct l1ctl_info_ul *ul;
ul = (struct l1ctl_info_ul *) msg->l1h;
par_req = (struct l1ctl_par_req *) ul->payload;
LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_PARAM_REQ "
"(ta=%d, tx_power=%u)\n", par_req->ta, par_req->tx_power);
/* Instruct TRX to use new TA value */
if (l1l->trx->ta != par_req->ta) {
trx_if_cmd_setta(l1l->trx, par_req->ta);
l1l->trx->ta = par_req->ta;
}
l1l->trx->tx_power = par_req->tx_power;
msgb_free(msg);
return 0;
}
static int l1ctl_rx_tch_mode_req(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_tch_mode_req *req;
struct trx_lchan_state *lchan;
struct trx_ts *ts;
int i;
req = (struct l1ctl_tch_mode_req *) msg->l1h;
LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_TCH_MODE_REQ "
"(tch_mode=%u, audio_mode=%u)\n", req->tch_mode, req->audio_mode);
/* Iterate over timeslot list */
for (i = 0; i < TRX_TS_COUNT; i++) {
/* Timeslot is not allocated */
ts = l1l->trx->ts_list[i];
if (ts == NULL)
continue;
/* Timeslot is not configured */
if (ts->mf_layout == NULL)
continue;
/* Iterate over all allocated lchans */
llist_for_each_entry(lchan, &ts->lchans, list) {
/* Omit inactive channels */
if (!lchan->active)
continue;
/* Set TCH mode */
lchan->tch_mode = req->tch_mode;
}
}
/* TODO: do we need to care about audio_mode? */
/* Re-use the original message as confirmation */
struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
l1h->msg_type = L1CTL_TCH_MODE_CONF;
return l1ctl_link_send(l1l, msg);
}
static int l1ctl_rx_crypto_req(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_crypto_req *req;
struct l1ctl_info_ul *ul;
struct trx_ts *ts;
uint8_t tn;
int rc = 0;
ul = (struct l1ctl_info_ul *) msg->l1h;
req = (struct l1ctl_crypto_req *) ul->payload;
LOGP(DL1C, LOGL_NOTICE, "L1CTL_CRYPTO_REQ (algo=A5/%u, key_len=%u)\n",
req->algo, req->key_len);
/* Determine TS index */
tn = ul->chan_nr & 0x7;
/* Make sure that required TS is allocated and configured */
ts = l1l->trx->ts_list[tn];
if (ts == NULL || ts->mf_layout == NULL) {
LOGP(DL1C, LOGL_ERROR, "TS %u is not configured\n", tn);
rc = -EINVAL;
goto exit;
}
/* Poke scheduler */
rc = sched_trx_start_ciphering(ts, req->algo, req->key, req->key_len);
if (rc) {
LOGP(DL1C, LOGL_ERROR, "Couldn't configure ciphering\n");
rc = -EINVAL;
goto exit;
}
exit:
msgb_free(msg);
return rc;
}
int l1ctl_rx_cb(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_hdr *l1h;
l1h = (struct l1ctl_hdr *) msg->l1h;
msg->l1h = l1h->data;
switch (l1h->msg_type) {
case L1CTL_FBSB_REQ:
return l1ctl_rx_fbsb_req(l1l, msg);
case L1CTL_PM_REQ:
return l1ctl_rx_pm_req(l1l, msg);
case L1CTL_RESET_REQ:
return l1ctl_rx_reset_req(l1l, msg);
case L1CTL_ECHO_REQ:
return l1ctl_rx_echo_req(l1l, msg);
case L1CTL_CCCH_MODE_REQ:
return l1ctl_rx_ccch_mode_req(l1l, msg);
case L1CTL_RACH_REQ:
return l1ctl_rx_rach_req(l1l, msg, false);
case L1CTL_EXT_RACH_REQ:
return l1ctl_rx_rach_req(l1l, msg, true);
case L1CTL_DM_EST_REQ:
return l1ctl_rx_dm_est_req(l1l, msg);
case L1CTL_DM_REL_REQ:
return l1ctl_rx_dm_rel_req(l1l, msg);
case L1CTL_DATA_REQ:
return l1ctl_rx_dt_req(l1l, msg, false);
case L1CTL_TRAFFIC_REQ:
return l1ctl_rx_dt_req(l1l, msg, true);
case L1CTL_PARAM_REQ:
return l1ctl_rx_param_req(l1l, msg);
case L1CTL_TCH_MODE_REQ:
return l1ctl_rx_tch_mode_req(l1l, msg);
case L1CTL_CRYPTO_REQ:
return l1ctl_rx_crypto_req(l1l, msg);
/* Not (yet) handled messages */
case L1CTL_NEIGH_PM_REQ:
case L1CTL_DATA_TBF_REQ:
case L1CTL_TBF_CFG_REQ:
case L1CTL_DM_FREQ_REQ:
case L1CTL_SIM_REQ:
LOGP(DL1C, LOGL_NOTICE, "Ignoring unsupported message "
"(type=%u)\n", l1h->msg_type);
msgb_free(msg);
return -ENOTSUP;
default:
LOGP(DL1C, LOGL_ERROR, "Unknown MSG type %u: %s\n", l1h->msg_type,
osmo_hexdump(msgb_data(msg), msgb_length(msg)));
msgb_free(msg);
return -EINVAL;
}
}
void l1ctl_shutdown_cb(struct l1ctl_link *l1l)
{
/* Abort FBSB expire timer */
if (osmo_timer_pending(&l1l->fbsb_timer))
osmo_timer_del(&l1l->fbsb_timer);
}

26
trxcon/l1ctl.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <stdint.h>
#include <osmocom/core/msgb.h>
#include "l1ctl_link.h"
#include "l1ctl_proto.h"
/* Event handlers */
int l1ctl_rx_cb(struct l1ctl_link *l1l, struct msgb *msg);
void l1ctl_shutdown_cb(struct l1ctl_link *l1l);
int l1ctl_tx_fbsb_conf(struct l1ctl_link *l1l, uint8_t result,
struct l1ctl_info_dl *dl_info, uint8_t bsic);
int l1ctl_tx_ccch_mode_conf(struct l1ctl_link *l1l, uint8_t mode);
int l1ctl_tx_pm_conf(struct l1ctl_link *l1l, uint16_t band_arfcn,
int dbm, int last);
int l1ctl_tx_reset_conf(struct l1ctl_link *l1l, uint8_t type);
int l1ctl_tx_reset_ind(struct l1ctl_link *l1l, uint8_t type);
int l1ctl_tx_dt_ind(struct l1ctl_link *l1l, struct l1ctl_info_dl *data,
uint8_t *l2, size_t l2_len, bool traffic);
int l1ctl_tx_dt_conf(struct l1ctl_link *l1l,
struct l1ctl_info_dl *data, bool traffic);
int l1ctl_tx_rach_conf(struct l1ctl_link *l1l,
uint16_t band_arfcn, uint32_t fn);

316
trxcon/l1ctl_link.c Normal file
View File

@@ -0,0 +1,316 @@
/*
* OsmocomBB <-> SDR connection bridge
* GSM L1 control socket (/tmp/osmocom_l2) handlers
*
* (C) 2013 by Sylvain Munaut <tnt@246tNt.com>
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/write_queue.h>
#include "trxcon.h"
#include "logging.h"
#include "l1ctl_link.h"
#include "l1ctl.h"
static struct value_string l1ctl_evt_names[] = {
{ 0, NULL } /* no events? */
};
static struct osmo_fsm_state l1ctl_fsm_states[] = {
[L1CTL_STATE_IDLE] = {
.out_state_mask = GEN_MASK(L1CTL_STATE_CONNECTED),
.name = "IDLE",
},
[L1CTL_STATE_CONNECTED] = {
.out_state_mask = GEN_MASK(L1CTL_STATE_IDLE),
.name = "CONNECTED",
},
};
static struct osmo_fsm l1ctl_fsm = {
.name = "l1ctl_link_fsm",
.states = l1ctl_fsm_states,
.num_states = ARRAY_SIZE(l1ctl_fsm_states),
.log_subsys = DL1C,
.event_names = l1ctl_evt_names,
};
static int l1ctl_link_read_cb(struct osmo_fd *bfd)
{
struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data;
struct msgb *msg;
uint16_t len;
int rc;
/* Attempt to read from socket */
rc = read(bfd->fd, &len, L1CTL_MSG_LEN_FIELD);
if (rc < L1CTL_MSG_LEN_FIELD) {
LOGP(DL1D, LOGL_NOTICE, "L1CTL has lost connection\n");
if (rc >= 0)
rc = -EIO;
l1ctl_link_close_conn(l1l);
return rc;
}
/* Check message length */
len = ntohs(len);
if (len > L1CTL_LENGTH) {
LOGP(DL1D, LOGL_ERROR, "Length is too big: %u\n", len);
return -EINVAL;
}
/* Allocate a new msg */
msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM,
L1CTL_HEADROOM, "l1ctl_rx_msg");
if (!msg) {
LOGP(DL1D, LOGL_ERROR, "Failed to allocate msg\n");
return -ENOMEM;
}
msg->l1h = msgb_put(msg, len);
rc = read(bfd->fd, msg->l1h, msgb_l1len(msg));
if (rc != len) {
LOGP(DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: "
"%s\n", len, rc, strerror(errno));
msgb_free(msg);
return rc;
}
/* Debug print */
LOGP(DL1D, LOGL_DEBUG, "RX: '%s'\n",
osmo_hexdump(msg->data, msg->len));
/* Call L1CTL handler */
l1ctl_rx_cb(l1l, msg);
return 0;
}
static int l1ctl_link_write_cb(struct osmo_fd *bfd, struct msgb *msg)
{
int len;
if (bfd->fd <= 0)
return -EINVAL;
len = write(bfd->fd, msg->data, msg->len);
if (len != msg->len) {
LOGP(DL1D, LOGL_ERROR, "Failed to write data: "
"written (%d) < msg_len (%d)\n", len, msg->len);
return -1;
}
return 0;
}
/* Connection handler */
static int l1ctl_link_accept(struct osmo_fd *bfd, unsigned int flags)
{
struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data;
struct osmo_fd *conn_bfd = &l1l->wq.bfd;
struct sockaddr_un un_addr;
socklen_t len;
int cfd;
len = sizeof(un_addr);
cfd = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
if (cfd < 0) {
LOGP(DL1C, LOGL_ERROR, "Failed to accept a new connection\n");
return -1;
}
/* Check if we already have an active connection */
if (conn_bfd->fd != -1) {
LOGP(DL1C, LOGL_NOTICE, "A new connection rejected: "
"we already have another active\n");
close(cfd);
return 0;
}
osmo_wqueue_init(&l1l->wq, 100);
INIT_LLIST_HEAD(&conn_bfd->list);
l1l->wq.write_cb = l1ctl_link_write_cb;
l1l->wq.read_cb = l1ctl_link_read_cb;
osmo_fd_setup(conn_bfd, cfd, OSMO_FD_READ, osmo_wqueue_bfd_cb, l1l, 0);
if (osmo_fd_register(conn_bfd) != 0) {
LOGP(DL1C, LOGL_ERROR, "Failed to register new connection fd\n");
close(conn_bfd->fd);
conn_bfd->fd = -1;
return -1;
}
osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_CONNECT, l1l);
osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_CONNECTED, 0, 0);
LOGP(DL1C, LOGL_NOTICE, "L1CTL has a new connection\n");
return 0;
}
int l1ctl_link_send(struct l1ctl_link *l1l, struct msgb *msg)
{
uint8_t *len;
/* Debug print */
LOGP(DL1D, LOGL_DEBUG, "TX: '%s'\n",
osmo_hexdump(msg->data, msg->len));
if (msg->l1h != msg->data)
LOGP(DL1D, LOGL_INFO, "Message L1 header != Message Data\n");
/* Prepend 16-bit length before sending */
len = msgb_push(msg, L1CTL_MSG_LEN_FIELD);
osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len);
if (osmo_wqueue_enqueue(&l1l->wq, msg) != 0) {
LOGP(DL1D, LOGL_ERROR, "Failed to enqueue msg!\n");
msgb_free(msg);
return -EIO;
}
return 0;
}
int l1ctl_link_close_conn(struct l1ctl_link *l1l)
{
struct osmo_fd *conn_bfd = &l1l->wq.bfd;
if (conn_bfd->fd <= 0)
return -EINVAL;
/* Close connection socket */
osmo_fd_unregister(conn_bfd);
close(conn_bfd->fd);
conn_bfd->fd = -1;
/* Clear pending messages */
osmo_wqueue_clear(&l1l->wq);
osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_DISCONNECT, l1l);
osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_IDLE, 0, 0);
return 0;
}
struct l1ctl_link *l1ctl_link_init(void *tall_ctx, const char *sock_path)
{
struct l1ctl_link *l1l;
struct osmo_fd *bfd;
int rc;
LOGP(DL1C, LOGL_NOTICE, "Init L1CTL link (%s)\n", sock_path);
l1l = talloc_zero(tall_ctx, struct l1ctl_link);
if (!l1l) {
LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n");
return NULL;
}
/* Allocate a new dedicated state machine */
l1l->fsm = osmo_fsm_inst_alloc(&l1ctl_fsm, l1l,
NULL, LOGL_DEBUG, "l1ctl_link");
if (l1l->fsm == NULL) {
LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance "
"of FSM '%s'\n", l1ctl_fsm.name);
talloc_free(l1l);
return NULL;
}
/* Create a socket and bind handlers */
bfd = &l1l->listen_bfd;
/* Bind connection handler */
osmo_fd_setup(bfd, -1, OSMO_FD_READ, l1ctl_link_accept, l1l, 0);
rc = osmo_sock_unix_init_ofd(bfd, SOCK_STREAM, 0, sock_path,
OSMO_SOCK_F_BIND);
if (rc < 0) {
LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n",
strerror(errno));
osmo_fsm_inst_free(l1l->fsm);
talloc_free(l1l);
return NULL;
}
/* Bind shutdown handler */
l1l->shutdown_cb = l1ctl_shutdown_cb;
/**
* To be able to accept first connection and
* drop others, it should be set to -1
*/
l1l->wq.bfd.fd = -1;
return l1l;
}
void l1ctl_link_shutdown(struct l1ctl_link *l1l)
{
struct osmo_fd *listen_bfd;
/* May be unallocated due to init error */
if (!l1l)
return;
LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL link\n");
/* Call shutdown callback */
if (l1l->shutdown_cb != NULL)
l1l->shutdown_cb(l1l);
listen_bfd = &l1l->listen_bfd;
/* Check if we have an established connection */
if (l1l->wq.bfd.fd != -1)
l1ctl_link_close_conn(l1l);
/* Unbind listening socket */
if (listen_bfd->fd != -1) {
osmo_fd_unregister(listen_bfd);
close(listen_bfd->fd);
listen_bfd->fd = -1;
}
osmo_fsm_inst_free(l1l->fsm);
talloc_free(l1l);
}
static __attribute__((constructor)) void on_dso_load(void)
{
OSMO_ASSERT(osmo_fsm_register(&l1ctl_fsm) == 0);
}

48
trxcon/l1ctl_link.h Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include <stdint.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/fsm.h>
#define L1CTL_LENGTH 256
#define L1CTL_HEADROOM 32
/**
* Each L1CTL message gets its own length pushed
* as two bytes in front before sending.
*/
#define L1CTL_MSG_LEN_FIELD 2
/* Forward declaration to avoid mutual include */
struct trx_instance;
enum l1ctl_fsm_states {
L1CTL_STATE_IDLE = 0,
L1CTL_STATE_CONNECTED,
};
struct l1ctl_link {
struct osmo_fsm_inst *fsm;
struct osmo_fd listen_bfd;
struct osmo_wqueue wq;
/* Bind TRX instance */
struct trx_instance *trx;
/* L1CTL handlers specific */
struct osmo_timer_list fbsb_timer;
bool fbsb_conf_sent;
/* Shutdown callback */
void (*shutdown_cb)(struct l1ctl_link *l1l);
};
struct l1ctl_link *l1ctl_link_init(void *tall_ctx, const char *sock_path);
void l1ctl_link_shutdown(struct l1ctl_link *l1l);
int l1ctl_link_send(struct l1ctl_link *l1l, struct msgb *msg);
int l1ctl_link_close_conn(struct l1ctl_link *l1l);

387
trxcon/l1ctl_proto.h Normal file
View File

@@ -0,0 +1,387 @@
/* Messages to be sent between the different layers */
/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
* (C) 2010 by Holger Hans Peter Freyther
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#ifndef __L1CTL_PROTO_H__
#define __L1CTL_PROTO_H__
enum {
_L1CTL_NONE = 0,
L1CTL_FBSB_REQ,
L1CTL_FBSB_CONF,
L1CTL_DATA_IND,
L1CTL_RACH_REQ,
L1CTL_DM_EST_REQ,
L1CTL_DATA_REQ,
L1CTL_RESET_IND,
L1CTL_PM_REQ, /* power measurement */
L1CTL_PM_CONF, /* power measurement */
L1CTL_ECHO_REQ,
L1CTL_ECHO_CONF,
L1CTL_RACH_CONF,
L1CTL_RESET_REQ,
L1CTL_RESET_CONF,
L1CTL_DATA_CONF,
L1CTL_CCCH_MODE_REQ,
L1CTL_CCCH_MODE_CONF,
L1CTL_DM_REL_REQ,
L1CTL_PARAM_REQ,
L1CTL_DM_FREQ_REQ,
L1CTL_CRYPTO_REQ,
L1CTL_SIM_REQ,
L1CTL_SIM_CONF,
L1CTL_TCH_MODE_REQ,
L1CTL_TCH_MODE_CONF,
L1CTL_NEIGH_PM_REQ,
L1CTL_NEIGH_PM_IND,
L1CTL_TRAFFIC_REQ,
L1CTL_TRAFFIC_CONF,
L1CTL_TRAFFIC_IND,
L1CTL_BURST_IND,
/* configure TBF for uplink/downlink */
L1CTL_TBF_CFG_REQ,
L1CTL_TBF_CFG_CONF,
L1CTL_DATA_TBF_REQ,
L1CTL_DATA_TBF_CONF,
/* Extended (11-bit) RACH (see 3GPP TS 05.02, section 5.2.7) */
L1CTL_EXT_RACH_REQ,
};
enum ccch_mode {
CCCH_MODE_NONE = 0,
CCCH_MODE_NON_COMBINED,
CCCH_MODE_COMBINED,
CCCH_MODE_COMBINED_CBCH,
};
enum neigh_mode {
NEIGH_MODE_NONE = 0,
NEIGH_MODE_PM,
NEIGH_MODE_SB,
};
enum l1ctl_coding_scheme {
L1CTL_CS_NONE,
L1CTL_CS1,
L1CTL_CS2,
L1CTL_CS3,
L1CTL_CS4,
L1CTL_MCS1,
L1CTL_MCS2,
L1CTL_MCS3,
L1CTL_MCS4,
L1CTL_MCS5,
L1CTL_MCS6,
L1CTL_MCS7,
L1CTL_MCS8,
L1CTL_MCS9,
};
/*
* NOTE: struct size. We do add manual padding out of the believe
* that it will avoid some unaligned access.
*/
/* there are no more messages in a sequence */
#define L1CTL_F_DONE 0x01
struct l1ctl_hdr {
uint8_t msg_type;
uint8_t flags;
uint8_t padding[2];
uint8_t data[0];
} __attribute__((packed));
/*
* downlink info ... down from the BTS..
*/
struct l1ctl_info_dl {
/* GSM 08.58 channel number (9.3.1) */
uint8_t chan_nr;
/* GSM 08.58 link identifier (9.3.2) */
uint8_t link_id;
/* the ARFCN and the band. FIXME: what about MAIO? */
uint16_t band_arfcn;
uint32_t frame_nr;
uint8_t rx_level; /* 0 .. 63 in typical GSM notation (dBm+110) */
uint8_t snr; /* Signal/Noise Ration (dB) */
uint8_t num_biterr;
uint8_t fire_crc;
uint8_t payload[0];
} __attribute__((packed));
/* new CCCH was found. This is following the header */
struct l1ctl_fbsb_conf {
int16_t initial_freq_err;
uint8_t result;
uint8_t bsic;
/* FIXME: contents of cell_info ? */
} __attribute__((packed));
/* CCCH mode was changed */
struct l1ctl_ccch_mode_conf {
uint8_t ccch_mode; /* enum ccch_mode */
uint8_t padding[3];
} __attribute__((packed));
/* 3GPP TS 44.014, section 5.1 (Calypso specific numbers) */
enum l1ctl_tch_loop_mode {
L1CTL_TCH_LOOP_OPEN = 0x00,
L1CTL_TCH_LOOP_A = 0x01,
L1CTL_TCH_LOOP_B = 0x02,
L1CTL_TCH_LOOP_C = 0x03,
L1CTL_TCH_LOOP_D = 0x04,
L1CTL_TCH_LOOP_E = 0x05,
L1CTL_TCH_LOOP_F = 0x06,
L1CTL_TCH_LOOP_I = 0x07,
};
/* TCH mode was changed */
struct l1ctl_tch_mode_conf {
uint8_t tch_mode; /* enum tch_mode */
uint8_t audio_mode;
uint8_t tch_loop_mode; /* enum l1ctl_tch_loop_mode */
uint8_t padding[1];
} __attribute__((packed));
/* data on the CCCH was found. This is following the header */
struct l1ctl_data_ind {
uint8_t data[23];
} __attribute__((packed));
/* traffic from the network */
struct l1ctl_traffic_ind {
uint8_t data[0];
} __attribute__((packed));
/*
* uplink info
*/
struct l1ctl_info_ul {
/* GSM 08.58 channel number (9.3.1) */
uint8_t chan_nr;
/* GSM 08.58 link identifier (9.3.2) */
uint8_t link_id;
uint8_t padding[2];
uint8_t payload[0];
} __attribute__((packed));
struct l1ctl_info_ul_tbf {
/* references l1ctl_tbf_cfg_req.tbf_nr */
uint8_t tbf_nr;
uint8_t coding_scheme;
uint8_t padding[2];
/* RLC/MAC block, size determines CS */
uint8_t payload[0];
} __attribute__((packed));
/*
* msg for FBSB_REQ
* the l1_info_ul header is in front
*/
struct l1ctl_fbsb_req {
uint16_t band_arfcn;
uint16_t timeout; /* in TDMA frames */
uint16_t freq_err_thresh1;
uint16_t freq_err_thresh2;
uint8_t num_freqerr_avg;
uint8_t flags; /* L1CTL_FBSB_F_* */
uint8_t sync_info_idx;
uint8_t ccch_mode; /* enum ccch_mode */
uint8_t rxlev_exp; /* expected signal level */
} __attribute__((packed));
#define L1CTL_FBSB_F_FB0 (1 << 0)
#define L1CTL_FBSB_F_FB1 (1 << 1)
#define L1CTL_FBSB_F_SB (1 << 2)
#define L1CTL_FBSB_F_FB01SB (L1CTL_FBSB_F_FB0|L1CTL_FBSB_F_FB1|L1CTL_FBSB_F_SB)
/*
* msg for CCCH_MODE_REQ
* the l1_info_ul header is in front
*/
struct l1ctl_ccch_mode_req {
uint8_t ccch_mode; /* enum ccch_mode */
uint8_t padding[3];
} __attribute__((packed));
/*
* msg for TCH_MODE_REQ
* the l1_info_ul header is in front
*/
struct l1ctl_tch_mode_req {
uint8_t tch_mode; /* enum gsm48_chan_mode */
#define AUDIO_TX_MICROPHONE (1<<0)
#define AUDIO_TX_TRAFFIC_REQ (1<<1)
#define AUDIO_RX_SPEAKER (1<<2)
#define AUDIO_RX_TRAFFIC_IND (1<<3)
uint8_t audio_mode;
uint8_t tch_loop_mode; /* enum l1ctl_tch_loop_mode */
uint8_t padding[1];
} __attribute__((packed));
/* the l1_info_ul header is in front */
struct l1ctl_rach_req {
uint8_t ra;
uint8_t combined;
uint16_t offset;
} __attribute__((packed));
/* the l1_info_ul header is in front */
struct l1ctl_ext_rach_req {
uint16_t ra11;
uint8_t synch_seq;
uint8_t combined;
uint16_t offset;
} __attribute__((packed));
/* the l1_info_ul header is in front */
struct l1ctl_par_req {
int8_t ta;
uint8_t tx_power;
uint8_t padding[2];
} __attribute__((packed));
struct l1ctl_h0 {
uint16_t band_arfcn;
} __attribute__((packed));
struct l1ctl_h1 {
uint8_t hsn;
uint8_t maio;
uint8_t n;
uint8_t _padding[1];
uint16_t ma[64];
} __attribute__((packed));
struct l1ctl_dm_est_req {
uint8_t tsc;
uint8_t h;
union {
struct l1ctl_h0 h0;
struct l1ctl_h1 h1;
};
uint8_t tch_mode;
uint8_t audio_mode;
} __attribute__((packed));
struct l1ctl_dm_freq_req {
uint16_t fn;
uint8_t tsc;
uint8_t h;
union {
struct l1ctl_h0 h0;
struct l1ctl_h1 h1;
};
} __attribute__((packed));
struct l1ctl_crypto_req {
uint8_t algo;
uint8_t key_len;
uint8_t key[0];
} __attribute__((packed));
struct l1ctl_pm_req {
uint8_t type;
uint8_t padding[3];
union {
struct {
uint16_t band_arfcn_from;
uint16_t band_arfcn_to;
} range;
};
} __attribute__((packed));
#define BI_FLG_DUMMY (1 << 4)
#define BI_FLG_SACCH (1 << 5)
struct l1ctl_burst_ind {
uint32_t frame_nr;
uint16_t band_arfcn; /* ARFCN + band + ul indicator */
uint8_t chan_nr; /* GSM 08.58 channel number (9.3.1) */
uint8_t flags; /* BI_FLG_xxx + burst_id = 2LSBs */
uint8_t rx_level; /* 0 .. 63 in typical GSM notation (dBm+110) */
uint8_t snr; /* Reported SNR >> 8 (0-255) */
uint8_t bits[15]; /* 114 bits + 2 steal bits. Filled MSB first */
} __attribute__((packed));
/* a single L1CTL_PM response */
struct l1ctl_pm_conf {
uint16_t band_arfcn;
uint8_t pm[2];
} __attribute__((packed));
enum l1ctl_reset_type {
L1CTL_RES_T_BOOT, /* only _IND */
L1CTL_RES_T_FULL,
L1CTL_RES_T_SCHED,
};
/* argument to L1CTL_RESET_REQ and L1CTL_RESET_IND */
struct l1ctl_reset {
uint8_t type;
uint8_t pad[3];
} __attribute__((packed));
struct l1ctl_neigh_pm_req {
uint8_t n;
uint8_t padding[1];
uint16_t band_arfcn[64];
uint8_t tn[64];
} __attribute__((packed));
/* neighbour cell measurement results */
struct l1ctl_neigh_pm_ind {
uint16_t band_arfcn;
uint8_t pm[2];
uint8_t tn;
uint8_t padding;
} __attribute__((packed));
/* traffic data to network */
struct l1ctl_traffic_req {
uint8_t data[0];
} __attribute__((packed));
struct l1ctl_tbf_cfg_req {
/* future support for multiple concurrent TBFs. 0 for now */
uint8_t tbf_nr;
/* is this about an UL TBF (1) or DL (0) */
uint8_t is_uplink;
uint8_t padding[2];
/* one USF for each TN, or 255 for invalid/unused */
uint8_t usf[8];
} __attribute__((packed));
#endif /* __L1CTL_PROTO_H__ */

88
trxcon/logging.c Normal file
View File

@@ -0,0 +1,88 @@
/*
* OsmocomBB <-> SDR connection bridge
*
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <osmocom/core/application.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include "logging.h"
static struct log_info_cat trx_log_info_cat[] = {
[DAPP] = {
.name = "DAPP",
.description = "Application",
.color = "\033[1;35m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DL1C] = {
.name = "DL1C",
.description = "Layer 1 control interface",
.color = "\033[1;31m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DL1D] = {
.name = "DL1D",
.description = "Layer 1 data",
.color = "\033[1;31m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DTRX] = {
.name = "DTRX",
.description = "Transceiver control interface",
.color = "\033[1;33m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DTRXD] = {
.name = "DTRXD",
.description = "Transceiver data interface",
.color = "\033[1;33m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DSCH] = {
.name = "DSCH",
.description = "Scheduler management",
.color = "\033[1;36m",
.enabled = 0, .loglevel = LOGL_NOTICE,
},
[DSCHD] = {
.name = "DSCHD",
.description = "Scheduler data",
.color = "\033[1;36m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
};
static const struct log_info trx_log_info = {
.cat = trx_log_info_cat,
.num_cat = ARRAY_SIZE(trx_log_info_cat),
};
int trx_log_init(void *tall_ctx, const char *category_mask)
{
osmo_init_logging2(tall_ctx, &trx_log_info);
if (category_mask)
log_parse_category_mask(osmo_stderr_target, category_mask);
return 0;
}

17
trxcon/logging.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include <osmocom/core/logging.h>
#define DEBUG_DEFAULT "DAPP:DL1C:DL1D:DTRX:DTRXD:DSCH:DSCHD"
enum {
DAPP,
DL1C,
DL1D,
DTRX,
DTRXD,
DSCH,
DSCHD,
};
int trx_log_init(void *tall_ctx, const char *category_mask);

206
trxcon/sched_clck.c Normal file
View File

@@ -0,0 +1,206 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: clock synchronization
*
* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
* (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
* (C) 2015 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/timer_compat.h>
#include <osmocom/gsm/a5.h>
#include "scheduler.h"
#include "logging.h"
#include "trx_if.h"
#define MAX_FN_SKEW 50
#define TRX_LOSS_FRAMES 400
static void sched_clck_tick(void *data)
{
struct trx_sched *sched = (struct trx_sched *) data;
struct timespec tv_now, *tv_clock, elapsed;
int64_t elapsed_us;
const struct timespec frame_duration = { .tv_sec = 0, .tv_nsec = GSM_TDMA_FN_DURATION_nS };
/* Check if transceiver is still alive */
if (sched->fn_counter_lost++ == TRX_LOSS_FRAMES) {
LOGP(DSCH, LOGL_DEBUG, "No more clock from transceiver\n");
sched->state = SCH_CLCK_STATE_WAIT;
return;
}
/* Get actual / previous frame time */
osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now);
tv_clock = &sched->clock;
timespecsub(&tv_now, tv_clock, &elapsed);
elapsed_us = (elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000);
/* If someone played with clock, or if the process stalled */
if (elapsed_us > GSM_TDMA_FN_DURATION_uS * MAX_FN_SKEW || elapsed_us < 0) {
LOGP(DSCH, LOGL_NOTICE, "PC clock skew: "
"elapsed uS %" PRId64 "\n", elapsed_us);
sched->state = SCH_CLCK_STATE_WAIT;
return;
}
/* Schedule next FN clock */
while (elapsed_us > GSM_TDMA_FN_DURATION_uS / 2) {
timespecadd(tv_clock, &frame_duration, tv_clock);
elapsed_us -= GSM_TDMA_FN_DURATION_uS;
GSM_TDMA_FN_INC(sched->fn_counter_proc);
/* Call frame callback */
if (sched->clock_cb)
sched->clock_cb(sched);
}
osmo_timer_schedule(&sched->clock_timer, 0,
GSM_TDMA_FN_DURATION_uS - elapsed_us);
}
static void sched_clck_correct(struct trx_sched *sched,
struct timespec *tv_now, uint32_t fn)
{
sched->fn_counter_proc = fn;
/* Call frame callback */
if (sched->clock_cb)
sched->clock_cb(sched);
/* Schedule first FN clock */
sched->clock = *tv_now;
memset(&sched->clock_timer, 0, sizeof(sched->clock_timer));
sched->clock_timer.cb = sched_clck_tick;
sched->clock_timer.data = sched;
osmo_timer_schedule(&sched->clock_timer, 0, GSM_TDMA_FN_DURATION_uS);
}
int sched_clck_handle(struct trx_sched *sched, uint32_t fn)
{
struct timespec tv_now, *tv_clock, elapsed;
int64_t elapsed_us, elapsed_fn;
/* Reset lost counter */
sched->fn_counter_lost = 0;
/* Get actual / previous frame time */
osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now);
tv_clock = &sched->clock;
/* If this is the first CLCK IND */
if (sched->state == SCH_CLCK_STATE_WAIT) {
sched_clck_correct(sched, &tv_now, fn);
LOGP(DSCH, LOGL_DEBUG, "Initial clock received: fn=%u\n", fn);
sched->state = SCH_CLCK_STATE_OK;
return 0;
}
LOGP(DSCH, LOGL_NOTICE, "Clock indication: fn=%u\n", fn);
osmo_timer_del(&sched->clock_timer);
/* Calculate elapsed time / frames since last processed fn */
timespecsub(&tv_now, tv_clock, &elapsed);
elapsed_us = (elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000);
elapsed_fn = GSM_TDMA_FN_SUB(fn, sched->fn_counter_proc);
if (elapsed_fn >= 135774)
elapsed_fn -= GSM_TDMA_HYPERFRAME;
/* Check for max clock skew */
if (elapsed_fn > MAX_FN_SKEW || elapsed_fn < -MAX_FN_SKEW) {
LOGP(DSCH, LOGL_NOTICE, "GSM clock skew: old fn=%u, "
"new fn=%u\n", sched->fn_counter_proc, fn);
sched_clck_correct(sched, &tv_now, fn);
return 0;
}
LOGP(DSCH, LOGL_INFO, "GSM clock jitter: %" PRId64 "\n",
elapsed_fn * GSM_TDMA_FN_DURATION_uS - elapsed_us);
/* Too many frames have been processed already */
if (elapsed_fn < 0) {
struct timespec duration;
/**
* Set clock to the time or last FN should
* have been transmitted
*/
duration.tv_nsec = (0 - elapsed_fn) * GSM_TDMA_FN_DURATION_nS;
duration.tv_sec = duration.tv_nsec / 1000000000;
duration.tv_nsec = duration.tv_nsec % 1000000000;
timespecadd(&tv_now, &duration, tv_clock);
/* Set time to the time our next FN has to be transmitted */
osmo_timer_schedule(&sched->clock_timer, 0,
GSM_TDMA_FN_DURATION_uS * (1 - elapsed_fn));
return 0;
}
/* Transmit what we still need to transmit */
while (fn != sched->fn_counter_proc) {
GSM_TDMA_FN_INC(sched->fn_counter_proc);
/* Call frame callback */
if (sched->clock_cb)
sched->clock_cb(sched);
}
/* Schedule next FN to be transmitted */
*tv_clock = tv_now;
osmo_timer_schedule(&sched->clock_timer, 0, GSM_TDMA_FN_DURATION_uS);
return 0;
}
void sched_clck_reset(struct trx_sched *sched)
{
/* Reset internal state */
sched->state = SCH_CLCK_STATE_WAIT;
/* Stop clock timer */
osmo_timer_del(&sched->clock_timer);
/* Flush counters */
sched->fn_counter_proc = 0;
sched->fn_counter_lost = 0;
}

232
trxcon/sched_lchan_common.c Normal file
View File

@@ -0,0 +1,232 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: common routines for lchan handlers
*
* (C) 2017-2020 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <string.h>
#include <talloc.h>
#include <stdint.h>
#include <stdbool.h>
#include <arpa/inet.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/gsmtap.h>
#include <osmocom/codec/codec.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#include "logging.h"
#include "trxcon.h"
#include "trx_if.h"
#include "l1ctl.h"
/* GSM 05.02 Chapter 5.2.3 Normal Burst (NB) */
const uint8_t sched_nb_training_bits[8][26] = {
{
0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1,
},
{
0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1,
1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1,
},
{
0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1,
0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0,
},
{
0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0,
},
{
0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0,
1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1,
},
{
0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0,
0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0,
},
{
1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1,
0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1,
},
{
1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0,
0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0,
},
};
/* Get a string representation of the burst buffer's completeness.
* Examples: " ****.." (incomplete, 4/6 bursts)
* " ****" (complete, all 4 bursts)
* "**.***.." (incomplete, 5/8 bursts) */
const char *burst_mask2str(const uint8_t *mask, int bits)
{
/* TODO: CSD is interleaved over 22 bursts, so the mask needs to be extended */
static char buf[8 + 1];
char *ptr = buf;
OSMO_ASSERT(bits <= 8 && bits > 0);
while (--bits >= 0)
*(ptr++) = (*mask & (1 << bits)) ? '*' : '.';
*ptr = '\0';
return buf;
}
int sched_gsmtap_send(enum trx_lchan_type lchan_type, uint32_t fn, uint8_t tn,
uint16_t band_arfcn, int8_t signal_dbm, uint8_t snr,
const uint8_t *data, size_t data_len)
{
const struct trx_lchan_desc *lchan_desc = &trx_lchan_desc[lchan_type];
/* GSMTAP logging may not be enabled */
if (gsmtap == NULL)
return 0;
/* Omit frames with unknown channel type */
if (lchan_desc->gsmtap_chan_type == GSMTAP_CHANNEL_UNKNOWN)
return 0;
/* TODO: distinguish GSMTAP_CHANNEL_PCH and GSMTAP_CHANNEL_AGCH */
return gsmtap_send(gsmtap, band_arfcn, tn, lchan_desc->gsmtap_chan_type,
lchan_desc->ss_nr, fn, signal_dbm, snr, data, data_len);
}
int sched_send_dt_ind(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint8_t *l2, size_t l2_len,
int bit_error_count, bool dec_failed, bool traffic)
{
const struct trx_meas_set *meas = &lchan->meas_avg;
const struct trx_lchan_desc *lchan_desc;
struct l1ctl_info_dl dl_hdr;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
/* Fill in known downlink info */
dl_hdr.chan_nr = lchan_desc->chan_nr | ts->index;
dl_hdr.link_id = lchan_desc->link_id;
dl_hdr.band_arfcn = htons(trx->band_arfcn);
dl_hdr.num_biterr = bit_error_count;
/* sched_trx_meas_avg() gives us TDMA frame number of the first burst */
dl_hdr.frame_nr = htonl(meas->fn);
/* RX level: 0 .. 63 in typical GSM notation (dBm + 110) */
dl_hdr.rx_level = dbm2rxlev(meas->rssi);
/* FIXME: set proper values */
dl_hdr.snr = 0;
/* Mark frame as broken if so */
dl_hdr.fire_crc = dec_failed ? 2 : 0;
/* Put a packet to higher layers */
l1ctl_tx_dt_ind(trx->l1l, &dl_hdr, l2, l2_len, traffic);
/* Optional GSMTAP logging */
if (l2_len > 0 && (!traffic || lchan_desc->chan_nr == RSL_CHAN_OSMO_PDCH)) {
sched_gsmtap_send(lchan->type, meas->fn, ts->index,
trx->band_arfcn, meas->rssi, 0, l2, l2_len);
}
return 0;
}
int sched_send_dt_conf(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, bool traffic)
{
const struct trx_lchan_desc *lchan_desc;
struct l1ctl_info_dl dl_hdr;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
/* Zero-initialize DL header, because we don't set all fields */
memset(&dl_hdr, 0x00, sizeof(struct l1ctl_info_dl));
/* Fill in known downlink info */
dl_hdr.chan_nr = lchan_desc->chan_nr | ts->index;
dl_hdr.link_id = lchan_desc->link_id;
dl_hdr.band_arfcn = htons(trx->band_arfcn);
dl_hdr.frame_nr = htonl(fn);
l1ctl_tx_dt_conf(trx->l1l, &dl_hdr, traffic);
/* Optional GSMTAP logging */
if (!traffic || lchan_desc->chan_nr == RSL_CHAN_OSMO_PDCH) {
sched_gsmtap_send(lchan->type, fn, ts->index,
trx->band_arfcn | ARFCN_UPLINK,
0, 0, lchan->prim->payload,
lchan->prim->payload_len);
}
return 0;
}
/**
* Composes a bad frame indication message
* according to the current tch_mode.
*
* @param l2 Caller-allocated byte array
* @param lchan Logical channel to generate BFI for
* @return How much bytes were written
*/
size_t sched_bad_frame_ind(uint8_t *l2, struct trx_lchan_state *lchan)
{
switch (lchan->tch_mode) {
case GSM48_CMODE_SPEECH_V1:
if (lchan->type == TRXC_TCHF) { /* Full Rate */
memset(l2, 0x00, GSM_FR_BYTES);
l2[0] = 0xd0;
return GSM_FR_BYTES;
} else { /* Half Rate */
memset(l2 + 1, 0x00, GSM_HR_BYTES);
l2[0] = 0x70; /* F = 0, FT = 111 */
return GSM_HR_BYTES + 1;
}
case GSM48_CMODE_SPEECH_EFR: /* Enhanced Full Rate */
memset(l2, 0x00, GSM_EFR_BYTES);
l2[0] = 0xc0;
return GSM_EFR_BYTES;
case GSM48_CMODE_SPEECH_AMR: /* Adaptive Multi Rate */
/* FIXME: AMR is not implemented yet */
return 0;
case GSM48_CMODE_SIGN:
LOGP(DSCH, LOGL_ERROR, "BFI is not allowed in signalling mode\n");
return 0;
default:
LOGP(DSCH, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
return 0;
}
}

622
trxcon/sched_lchan_desc.c Normal file
View File

@@ -0,0 +1,622 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: logical channels, RX / TX handlers
*
* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
* (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
* (C) 2015 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include <osmocom/core/gsmtap.h>
#include "sched_trx.h"
/* Forward declaration of handlers */
int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
const sbit_t *bits, const struct trx_meas_set *meas);
int tx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
const sbit_t *bits, const struct trx_meas_set *meas);
int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
int rx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
const sbit_t *bits, const struct trx_meas_set *meas);
int tx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
int rx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
const sbit_t *bits, const struct trx_meas_set *meas);
int tx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
const sbit_t *bits, const struct trx_meas_set *meas);
int tx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = {
[TRXC_IDLE] = {
.name = "IDLE",
.desc = "Idle channel",
/* The MS needs to perform neighbour measurements during
* IDLE slots, however this is not implemented (yet). */
},
[TRXC_FCCH] = {
.name = "FCCH", /* 3GPP TS 05.02, section 3.3.2.1 */
.desc = "Frequency correction channel",
/* Handled by transceiver, nothing to do. */
},
[TRXC_SCH] = {
.name = "SCH", /* 3GPP TS 05.02, section 3.3.2.2 */
.desc = "Synchronization channel",
/* 3GPP TS 05.03, section 4.7. Handled by transceiver,
* however we still need to parse BSIC (BCC / NCC). */
.flags = TRX_CH_FLAG_AUTO,
.rx_fn = rx_sch_fn,
},
[TRXC_BCCH] = {
.name = "BCCH", /* 3GPP TS 05.02, section 3.3.2.3 */
.desc = "Broadcast control channel",
.gsmtap_chan_type = GSMTAP_CHANNEL_BCCH,
.chan_nr = RSL_CHAN_BCCH,
/* Rx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4),
* regular interleaving (3GPP TS 05.02, clause 7, table 3):
* a L2 frame is interleaved over 4 consecutive bursts. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_AUTO,
.rx_fn = rx_data_fn,
},
[TRXC_RACH] = {
.name = "RACH", /* 3GPP TS 05.02, section 3.3.3.1 */
.desc = "Random access channel",
.gsmtap_chan_type = GSMTAP_CHANNEL_RACH,
.chan_nr = RSL_CHAN_RACH,
/* Tx only, RACH convolutional coding (3GPP TS 05.03, section 4.6). */
.flags = TRX_CH_FLAG_AUTO,
.tx_fn = tx_rach_fn,
},
[TRXC_CCCH] = {
.name = "CCCH", /* 3GPP TS 05.02, section 3.3.3.1 */
.desc = "Common control channel",
.gsmtap_chan_type = GSMTAP_CHANNEL_CCCH,
.chan_nr = RSL_CHAN_PCH_AGCH,
/* Rx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4),
* regular interleaving (3GPP TS 05.02, clause 7, table 3):
* a L2 frame is interleaved over 4 consecutive bursts. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_AUTO,
.rx_fn = rx_data_fn,
},
[TRXC_TCHF] = {
.name = "TCH/F", /* 3GPP TS 05.02, section 3.2 */
.desc = "Full Rate traffic channel",
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_F,
.chan_nr = RSL_CHAN_Bm_ACCHs,
.link_id = TRX_CH_LID_DEDIC,
/* Rx and Tx, multiple convolutional coding types (3GPP TS 05.03,
* chapter 3), block diagonal interleaving (3GPP TS 05.02, clause 7):
*
* - a traffic frame is interleaved over 8 consecutive bursts
* using the even numbered bits of the first 4 bursts
* and odd numbered bits of the last 4 bursts;
* - a FACCH/F frame 'steals' (replaces) one traffic frame,
* interleaving is done in the same way.
*
* The MS shall continuously transmit bursts, even if there is nothing
* to send, unless DTX (Discontinuous Transmission) is used. */
.burst_buf_size = 8 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_tchf_fn,
.tx_fn = tx_tchf_fn,
},
[TRXC_TCHH_0] = {
.name = "TCH/H(0)", /* 3GPP TS 05.02, section 3.2 */
.desc = "Half Rate traffic channel (sub-channel 0)",
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H,
.chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 0,
/* Rx and Tx, multiple convolutional coding types (3GPP TS 05.03,
* chapter 3), block diagonal interleaving (3GPP TS 05.02, clause 7):
*
* - a traffic frame is interleaved over 4 non-consecutive bursts
* using the even numbered bits of the first 2 bursts,
* and odd numbered bits of the last 2 bursts;
* - a FACCH/H frame is interleaved over 6 non-consecutive bursts
* using the even numbered bits of the first 2 bursts,
* all bits of the middle two 2 bursts,
* and odd numbered bits of the last 2 bursts;
* - a FACCH/H frame 'steals' (replaces) two traffic frames,
* interleaving is done over 4 consecutive bursts,
* the same as given for a TCH/FS.
*
* The MS shall continuously transmit bursts, even if there is nothing
* to send, unless DTX (Discontinuous Transmission) is used. */
.burst_buf_size = 6 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_tchh_fn,
.tx_fn = tx_tchh_fn,
},
[TRXC_TCHH_1] = {
.name = "TCH/H(1)", /* 3GPP TS 05.02, section 3.2 */
.desc = "Half Rate traffic channel (sub-channel 1)",
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H,
.chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 1,
/* Same as for TRXC_TCHH_0, see above. */
.burst_buf_size = 6 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_tchh_fn,
.tx_fn = tx_tchh_fn,
},
[TRXC_SDCCH4_0] = {
.name = "SDCCH/4(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Stand-alone dedicated control channel (sub-channel 0)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 0,
/* Same as for TRXC_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH4_1] = {
.name = "SDCCH/4(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Stand-alone dedicated control channel (sub-channel 1)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 1,
/* Same as for TRXC_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH4_2] = {
.name = "SDCCH/4(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Stand-alone dedicated control channel (sub-channel 2)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 2,
/* Same as for TRXC_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH4_3] = {
.name = "SDCCH/4(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Stand-alone dedicated control channel (sub-channel 3)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 3,
/* Same as for TRXC_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_0] = {
.name = "SDCCH/8(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Stand-alone dedicated control channel (sub-channel 0)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 0,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_1] = {
.name = "SDCCH/8(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Stand-alone dedicated control channel (sub-channel 1)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 1,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_2] = {
.name = "SDCCH/8(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Stand-alone dedicated control channel (sub-channel 2)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 2,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_3] = {
.name = "SDCCH/8(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Stand-alone dedicated control channel (sub-channel 3)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 3,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_4] = {
.name = "SDCCH/8(4)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Stand-alone dedicated control channel (sub-channel 4)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 4,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_5] = {
.name = "SDCCH/8(5)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Stand-alone dedicated control channel (sub-channel 5)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 5,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_6] = {
.name = "SDCCH/8(6)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Stand-alone dedicated control channel (sub-channel 6)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 6,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SDCCH8_7] = {
.name = "SDCCH/8(7)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Stand-alone dedicated control channel (sub-channel 7)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3),
.link_id = TRX_CH_LID_DEDIC,
.ss_nr = 7,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCHTF] = {
.name = "SACCH/TF", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow TCH/F associated control channel",
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_F | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_Bm_ACCHs,
.link_id = TRX_CH_LID_SACCH,
/* Same as for TRXC_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCHTH_0] = {
.name = "SACCH/TH(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow TCH/H associated control channel (sub-channel 0)",
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 0,
/* Same as for TRXC_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCHTH_1] = {
.name = "SACCH/TH(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow TCH/H associated control channel (sub-channel 1)",
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 1,
/* Same as for TRXC_BCCH (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH4_0] = {
.name = "SACCH/4(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow SDCCH/4 associated control channel (sub-channel 0)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 0,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH4_1] = {
.name = "SACCH/4(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow SDCCH/4 associated control channel (sub-channel 1)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 1,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH4_2] = {
.name = "SACCH/4(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow SDCCH/4 associated control channel (sub-channel 2)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 2,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH4_3] = {
.name = "SACCH/4(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow SDCCH/4 associated control channel (sub-channel 3)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 3,
/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_0] = {
.name = "SACCH/8(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow SDCCH/8 associated control channel (sub-channel 0)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 0,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_1] = {
.name = "SACCH/8(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow SDCCH/8 associated control channel (sub-channel 1)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 1,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_2] = {
.name = "SACCH/8(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow SDCCH/8 associated control channel (sub-channel 2)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 2,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_3] = {
.name = "SACCH/8(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow SDCCH/8 associated control channel (sub-channel 3)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 3,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_4] = {
.name = "SACCH/8(4)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow SDCCH/8 associated control channel (sub-channel 4)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 4,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_5] = {
.name = "SACCH/8(5)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow SDCCH/8 associated control channel (sub-channel 5)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 5,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_6] = {
.name = "SACCH/8(6)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow SDCCH/8 associated control channel (sub-channel 6)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 6,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_SACCH8_7] = {
.name = "SACCH/8(7)", /* 3GPP TS 05.02, section 3.3.4.1 */
.desc = "Slow SDCCH/8 associated control channel (sub-channel 7)",
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3),
.link_id = TRX_CH_LID_SACCH,
.ss_nr = 7,
/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_CBTX,
.rx_fn = rx_data_fn,
.tx_fn = tx_data_fn,
},
[TRXC_PDTCH] = {
.name = "PDTCH", /* 3GPP TS 05.02, sections 3.2.4, 3.3.2.4 */
.desc = "Packet data traffic & control channel",
.gsmtap_chan_type = GSMTAP_CHANNEL_PDTCH,
.chan_nr = RSL_CHAN_OSMO_PDCH,
/* Rx and Tx, multiple coding schemes: CS-1..4 and MCS-1..9 (3GPP TS
* 05.03, chapter 5), regular interleaving as specified for xCCH.
* NOTE: the burst buffer is three times bigger because the
* payload of EDGE bursts is three times longer. */
.burst_buf_size = 3 * 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_PDCH,
.rx_fn = rx_pdtch_fn,
.tx_fn = tx_pdtch_fn,
},
[TRXC_PTCCH] = {
.name = "PTCCH", /* 3GPP TS 05.02, section 3.3.4.2 */
.desc = "Packet Timing advance control channel",
.gsmtap_chan_type = GSMTAP_CHANNEL_PTCCH,
.chan_nr = RSL_CHAN_OSMO_PDCH,
.link_id = TRX_CH_LID_PTCCH,
/* On the Uplink, mobile stations transmit random Access Bursts
* to allow estimation of the timing advance for one MS in packet
* transfer mode. On Downlink, the network sends timing advance
* updates for several mobile stations. The coding scheme used
* for PTCCH/D messages is the same as for PDTCH CS-1. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_PDCH,
.rx_fn = rx_pdtch_fn,
.tx_fn = tx_rach_fn,
},
[TRXC_SDCCH4_CBCH] = {
.name = "SDCCH/4(CBCH)", /* 3GPP TS 05.02, section 3.3.5 */
.desc = "Cell Broadcast channel on SDCCH/4",
.gsmtap_chan_type = GSMTAP_CHANNEL_CBCH51,
.chan_nr = RSL_CHAN_OSMO_CBCH4,
.ss_nr = 2,
/* Same as for TRXC_BCCH (xCCH), but Rx only. See above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.flags = TRX_CH_FLAG_AUTO,
.rx_fn = rx_data_fn,
},
[TRXC_SDCCH8_CBCH] = {
.name = "SDCCH/8(CBCH)", /* 3GPP TS 05.02, section 3.3.5 */
.desc = "Cell Broadcast channel on SDCCH/8",
.gsmtap_chan_type = GSMTAP_CHANNEL_CBCH52,
.chan_nr = RSL_CHAN_OSMO_CBCH8,
.ss_nr = 2,
/* Same as for TRXC_BCCH (xCCH), but Rx only. See above. */
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
.rx_fn = rx_data_fn,
},
};

201
trxcon/sched_lchan_pdtch.c Normal file
View File

@@ -0,0 +1,201 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2018-2020 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/coding/gsm0503_coding.h>
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#include "logging.h"
#include "trx_if.h"
#include "l1ctl.h"
int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
const sbit_t *bits, const struct trx_meas_set *meas)
{
const struct trx_lchan_desc *lchan_desc;
uint8_t l2[GPRS_L2_MAX_LEN], *mask;
int n_errors, n_bits_total, rc;
sbit_t *buffer, *offset;
size_t l2_len;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
mask = &lchan->rx_burst_mask;
buffer = lchan->rx_bursts;
LOGP(DSCHD, LOGL_DEBUG, "Packet data received on %s: "
"fn=%u ts=%u bid=%u\n", lchan_desc->name, fn, ts->index, bid);
/* Align to the first burst of a block */
if (*mask == 0x00 && bid != 0)
return 0;
/* Update mask */
*mask |= (1 << bid);
/* Store the measurements */
sched_trx_meas_push(lchan, meas);
/* Copy burst to buffer of 4 bursts */
offset = buffer + bid * 116;
memcpy(offset, bits + 3, 58);
memcpy(offset + 58, bits + 87, 58);
/* Wait until complete set of bursts */
if (bid != 3)
return 0;
/* Calculate AVG of the measurements */
sched_trx_meas_avg(lchan, 4);
/* Check for complete set of bursts */
if ((*mask & 0xf) != 0xf) {
LOGP(DSCHD, LOGL_ERROR, "Received incomplete (%s) data frame at "
"fn=%u (%u/%u) for %s\n",
burst_mask2str(mask, 4), lchan->meas_avg.fn,
lchan->meas_avg.fn % ts->mf_layout->period,
ts->mf_layout->period,
lchan_desc->name);
/* NOTE: do not abort here, give it a try. Maybe we're lucky ;) */
}
/* Keep the mask updated */
*mask = *mask << 4;
/* Attempt to decode */
rc = gsm0503_pdtch_decode(l2, buffer,
NULL, &n_errors, &n_bits_total);
if (rc < 0) {
LOGP(DSCHD, LOGL_ERROR, "Received bad packet data frame "
"at fn=%u (%u/%u) for %s\n", lchan->meas_avg.fn,
lchan->meas_avg.fn % ts->mf_layout->period,
ts->mf_layout->period,
lchan_desc->name);
}
/* Determine L2 length */
l2_len = rc > 0 ? rc : 0;
/* Send a L2 frame to the higher layers */
sched_send_dt_ind(trx, ts, lchan,
l2, l2_len, n_errors, rc < 0, true);
return 0;
}
int tx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
{
const struct trx_lchan_desc *lchan_desc;
ubit_t burst[GSM_BURST_LEN];
ubit_t *buffer, *offset;
const uint8_t *tsc;
uint8_t *mask;
int rc;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
mask = &lchan->tx_burst_mask;
buffer = lchan->tx_bursts;
if (bid > 0) {
/* If we have encoded bursts */
if (*mask)
goto send_burst;
else
return 0;
}
/* Encode payload */
rc = gsm0503_pdtch_encode(buffer, lchan->prim->payload,
lchan->prim->payload_len);
if (rc < 0) {
LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
lchan->prim->payload_len));
/* Forget this primitive */
sched_prim_drop(lchan);
return -EINVAL;
}
send_burst:
/* Determine which burst should be sent */
offset = buffer + bid * 116;
/* Update mask */
*mask |= (1 << bid);
/* Choose proper TSC */
tsc = sched_nb_training_bits[trx->tsc];
/* Compose a new burst */
memset(burst, 0, 3); /* TB */
memcpy(burst + 3, offset, 58); /* Payload 1/2 */
memcpy(burst + 61, tsc, 26); /* TSC */
memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
memset(burst + 145, 0, 3); /* TB */
LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
lchan_desc->name, fn, ts->index, bid);
/* Forward burst to scheduler */
rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
if (rc) {
/* Forget this primitive */
sched_prim_drop(lchan);
/* Reset mask */
*mask = 0x00;
return rc;
}
/* If we have sent the last (4/4) burst */
if ((*mask & 0x0f) == 0x0f) {
/* Confirm data / traffic sending */
sched_send_dt_conf(trx, ts, lchan, fn, true);
/* Forget processed primitive */
sched_prim_drop(lchan);
/* Reset mask */
*mask = 0x00;
}
return 0;
}

187
trxcon/sched_lchan_rach.c Normal file
View File

@@ -0,0 +1,187 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2017-2019 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/coding/gsm0503_coding.h>
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#include "logging.h"
#include "trx_if.h"
#include "l1ctl.h"
/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)" */
#define RACH_EXT_TAIL_BITS_LEN 8
#define RACH_SYNCH_SEQ_LEN 41
#define RACH_PAYLOAD_LEN 36
/* Extended tail bits (BN0..BN7) */
static const ubit_t rach_ext_tail_bits[] = {
0, 0, 1, 1, 1, 0, 1, 0,
};
/* Synchronization (training) sequence types */
enum rach_synch_seq_t {
RACH_SYNCH_SEQ_UNKNOWN = -1,
RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */
RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */
RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */
RACH_SYNCH_SEQ_NUM
};
/* Synchronization (training) sequence bits */
static const char rach_synch_seq_bits[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = {
[RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000",
[RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101",
[RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111",
};
/* Synchronization (training) sequence names */
static struct value_string rach_synch_seq_names[] = {
{ RACH_SYNCH_SEQ_UNKNOWN, "UNKNOWN" },
{ RACH_SYNCH_SEQ_TS0, "TS0: GSM, GMSK" },
{ RACH_SYNCH_SEQ_TS1, "TS1: EGPRS, 8-PSK" },
{ RACH_SYNCH_SEQ_TS2, "TS2: EGPRS, GMSK" },
{ 0, NULL },
};
/* Obtain a to-be-transmitted RACH burst */
int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
{
struct l1ctl_ext_rach_req *ext_req = NULL;
struct l1ctl_rach_req *req = NULL;
enum rach_synch_seq_t synch_seq;
uint8_t burst[GSM_BURST_LEN];
uint8_t *burst_ptr = burst;
uint8_t payload[36];
int i, rc;
/* Is it extended (11-bit) RACH or not? */
if (PRIM_IS_RACH11(lchan->prim)) {
ext_req = (struct l1ctl_ext_rach_req *) lchan->prim->payload;
synch_seq = ext_req->synch_seq;
/* Check requested synch. sequence */
if (synch_seq >= RACH_SYNCH_SEQ_NUM) {
LOGP(DSCHD, LOGL_ERROR, "Unknown RACH synch. sequence=0x%02x\n", synch_seq);
/* Forget this primitive */
sched_prim_drop(lchan);
return -ENOTSUP;
}
/* Delay sending according to offset value */
if (ext_req->offset-- > 0)
return 0;
/* Encode extended (11-bit) payload */
rc = gsm0503_rach_ext_encode(payload, ext_req->ra11, trx->bsic, true);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Could not encode extended RACH burst "
"(ra=%u bsic=%u)\n", ext_req->ra11, trx->bsic);
/* Forget this primitive */
sched_prim_drop(lchan);
return rc;
}
} else if (PRIM_IS_RACH8(lchan->prim)) {
req = (struct l1ctl_rach_req *) lchan->prim->payload;
synch_seq = RACH_SYNCH_SEQ_TS0;
/* Delay sending according to offset value */
if (req->offset-- > 0)
return 0;
/* Encode regular (8-bit) payload */
rc = gsm0503_rach_ext_encode(payload, req->ra, trx->bsic, false);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Could not encode RACH burst "
"(ra=%u bsic=%u)\n", req->ra, trx->bsic);
/* Forget this primitive */
sched_prim_drop(lchan);
return rc;
}
} else {
LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu (expected %zu or %zu), "
"so dropping...\n", lchan->prim->payload_len,
sizeof(*req), sizeof(*ext_req));
sched_prim_drop(lchan);
return -EINVAL;
}
/* BN0-7: extended tail bits */
memcpy(burst_ptr, rach_ext_tail_bits, RACH_EXT_TAIL_BITS_LEN);
burst_ptr += RACH_EXT_TAIL_BITS_LEN;
/* BN8-48: chosen synch. (training) sequence */
for (i = 0; i < RACH_SYNCH_SEQ_LEN; i++)
*(burst_ptr++) = rach_synch_seq_bits[synch_seq][i] == '1';
/* BN49-84: encrypted bits (the payload) */
memcpy(burst_ptr, payload, RACH_PAYLOAD_LEN);
burst_ptr += RACH_PAYLOAD_LEN;
/* BN85-156: tail bits & extended guard period */
memset(burst_ptr, 0, burst + GSM_BURST_LEN - burst_ptr);
LOGP(DSCHD, LOGL_NOTICE, "Transmitting %s RACH (%s) on fn=%u, tn=%u, lchan=%s\n",
PRIM_IS_RACH11(lchan->prim) ? "extended (11-bit)" : "regular (8-bit)",
get_value_string(rach_synch_seq_names, synch_seq), fn,
ts->index, trx_lchan_desc[lchan->type].name);
/* Forward burst to scheduler */
rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
if (rc) {
/* Forget this primitive */
sched_prim_drop(lchan);
return rc;
}
/* Confirm RACH request */
l1ctl_tx_rach_conf(trx->l1l, trx->band_arfcn, fn);
/* Optional GSMTAP logging */
sched_gsmtap_send(lchan->type, fn, ts->index,
trx->band_arfcn | ARFCN_UPLINK, 0, 0,
PRIM_IS_RACH11(lchan->prim) ? (uint8_t *) &ext_req->ra11 : &req->ra,
PRIM_IS_RACH11(lchan->prim) ? 2 : 1);
/* Forget processed primitive */
sched_prim_drop(lchan);
return 0;
}

139
trxcon/sched_lchan_sch.c Normal file
View File

@@ -0,0 +1,139 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <string.h>
#include <talloc.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/coding/gsm0503_coding.h>
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#include "logging.h"
#include "trx_if.h"
#include "l1ctl.h"
__attribute__((xray_always_instrument)) __attribute__((noinline))
static int gsm0503_sch_decode_xray(uint8_t *sb_info, const sbit_t *burst)
{
return gsm0503_sch_decode(sb_info, burst);
}
static void decode_sb(struct gsm_time *time, uint8_t *bsic, uint8_t *sb_info)
{
uint8_t t3p;
uint32_t sb;
sb = ((uint32_t)sb_info[3] << 24)
| (sb_info[2] << 16)
| (sb_info[1] << 8)
| sb_info[0];
*bsic = (sb >> 2) & 0x3f;
/* TS 05.02 Chapter 3.3.2.2.1 SCH Frame Numbers */
time->t1 = ((sb >> 23) & 0x01)
| ((sb >> 7) & 0x1fe)
| ((sb << 9) & 0x600);
time->t2 = (sb >> 18) & 0x1f;
t3p = ((sb >> 24) & 0x01) | ((sb >> 15) & 0x06);
time->t3 = t3p * 10 + 1;
/* TS 05.02 Chapter 4.3.3 TDMA frame number */
time->fn = gsm_gsmtime2fn(time);
}
int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
const sbit_t *bits, const struct trx_meas_set *meas)
{
sbit_t payload[2 * 39];
struct gsm_time time;
uint8_t sb_info[4];
uint8_t bsic;
int rc;
/* Obtain payload from burst */
memcpy(payload, bits + 3, 39);
memcpy(payload + 39, bits + 3 + 39 + 64, 39);
/* Attempt to decode */
rc = gsm0503_sch_decode_xray(sb_info, payload);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Received bad SCH burst at fn=%u\n", fn);
return rc;
}
/* Decode BSIC and TDMA frame number */
decode_sb(&time, &bsic, sb_info);
LOGP(DSCHD, LOGL_DEBUG, "Received SCH: bsic=%u, fn=%u, sched_fn=%u\n",
bsic, time.fn, trx->sched.fn_counter_proc);
/* Check if decoded frame number matches */
if (time.fn != fn) {
LOGP(DSCHD, LOGL_ERROR, "Decoded fn=%u does not match "
"fn=%u provided by scheduler\n", time.fn, fn);
return -EINVAL;
}
/* We don't need to send L1CTL_FBSB_CONF */
if (trx->l1l->fbsb_conf_sent)
return 0;
/* Send L1CTL_FBSB_CONF to higher layers */
struct l1ctl_info_dl *data;
data = talloc_zero_size(ts, sizeof(struct l1ctl_info_dl));
if (data == NULL)
return -ENOMEM;
/* Fill in some downlink info */
data->chan_nr = trx_lchan_desc[lchan->type].chan_nr | ts->index;
data->link_id = trx_lchan_desc[lchan->type].link_id;
data->band_arfcn = htons(trx->band_arfcn);
data->frame_nr = htonl(fn);
data->rx_level = -(meas->rssi);
/* FIXME: set proper values */
data->num_biterr = 0;
data->fire_crc = 0;
data->snr = 0;
l1ctl_tx_fbsb_conf(trx->l1l, 0, data, bsic);
/* Update BSIC value of trx_instance */
trx->bsic = bsic;
return 0;
}

303
trxcon/sched_lchan_tchf.c Normal file
View File

@@ -0,0 +1,303 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2017-2020 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/coding/gsm0503_coding.h>
#include <osmocom/codec/codec.h>
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#include "logging.h"
#include "trx_if.h"
#include "l1ctl.h"
int rx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
const sbit_t *bits, const struct trx_meas_set *meas)
{
const struct trx_lchan_desc *lchan_desc;
int n_errors = -1, n_bits_total, rc;
sbit_t *buffer, *offset;
uint8_t l2[128], *mask;
size_t l2_len;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
mask = &lchan->rx_burst_mask;
buffer = lchan->rx_bursts;
LOGP(DSCHD, LOGL_DEBUG, "Traffic received on %s: fn=%u ts=%u bid=%u\n",
lchan_desc->name, fn, ts->index, bid);
/* Align to the first burst of a block */
if (*mask == 0x00 && bid != 0)
return 0;
/* Update mask */
*mask |= (1 << bid);
/* Store the measurements */
sched_trx_meas_push(lchan, meas);
/* Copy burst to end of buffer of 8 bursts */
offset = buffer + bid * 116 + 464;
memcpy(offset, bits + 3, 58);
memcpy(offset + 58, bits + 87, 58);
/* Wait until complete set of bursts */
if (bid != 3)
return 0;
/* Calculate AVG of the measurements */
sched_trx_meas_avg(lchan, 8);
/* Check for complete set of bursts */
if ((*mask & 0xff) != 0xff) {
LOGP(DSCHD, LOGL_ERROR, "Received incomplete (%s) traffic frame at "
"fn=%u (%u/%u) for %s\n",
burst_mask2str(mask, 8), lchan->meas_avg.fn,
lchan->meas_avg.fn % ts->mf_layout->period,
ts->mf_layout->period,
lchan_desc->name);
/* NOTE: do not abort here, give it a try. Maybe we're lucky ;) */
}
/* Keep the mask updated */
*mask = *mask << 4;
switch (lchan->tch_mode) {
case GSM48_CMODE_SIGN:
case GSM48_CMODE_SPEECH_V1: /* FR */
rc = gsm0503_tch_fr_decode(l2, buffer,
1, 0, &n_errors, &n_bits_total);
break;
case GSM48_CMODE_SPEECH_EFR: /* EFR */
rc = gsm0503_tch_fr_decode(l2, buffer,
1, 1, &n_errors, &n_bits_total);
break;
case GSM48_CMODE_SPEECH_AMR: /* AMR */
/**
* TODO: AMR requires a dedicated loop,
* which will be implemented later...
*/
LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet\n");
return -ENOTSUP;
default:
LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
return -EINVAL;
}
/* Shift buffer by 4 bursts for interleaving */
memcpy(buffer, buffer + 464, 464);
/* Check decoding result */
if (rc < 4) {
LOGP(DSCHD, LOGL_ERROR, "Received bad TCH frame ending at "
"fn=%u for %s\n", fn, lchan_desc->name);
/* Send BFI */
goto bfi;
} else if (rc == GSM_MACBLOCK_LEN) {
/* FACCH received, forward it to the higher layers */
sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
n_errors, false, false);
/* Send BFI substituting a stolen TCH frame */
n_errors = -1; /* ensure fake measurements */
goto bfi;
} else {
/* A good TCH frame received */
l2_len = rc;
}
/* Send a traffic frame to the higher layers */
return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
n_errors, false, true);
bfi:
/* Didn't try to decode, fake measurements */
if (n_errors < 0) {
lchan->meas_avg = (struct trx_meas_set) {
.fn = lchan->meas_avg.fn,
.toa256 = 0,
.rssi = -110,
};
/* No bursts => no errors */
n_errors = 0;
}
/* BFI is not applicable in signalling mode */
if (lchan->tch_mode == GSM48_CMODE_SIGN)
return sched_send_dt_ind(trx, ts, lchan, NULL, 0,
n_errors, true, false);
/* Bad frame indication */
l2_len = sched_bad_frame_ind(l2, lchan);
/* Send a BFI frame to the higher layers */
return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
n_errors, true, true);
}
int tx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
{
const struct trx_lchan_desc *lchan_desc;
ubit_t burst[GSM_BURST_LEN];
ubit_t *buffer, *offset;
const uint8_t *tsc;
uint8_t *mask;
size_t l2_len;
int rc;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
mask = &lchan->tx_burst_mask;
buffer = lchan->tx_bursts;
/* If we have encoded bursts */
if (*mask)
goto send_burst;
/* Wait until a first burst in period */
if (bid > 0)
return 0;
/* Check the current TCH mode */
switch (lchan->tch_mode) {
case GSM48_CMODE_SIGN:
case GSM48_CMODE_SPEECH_V1: /* FR */
l2_len = GSM_FR_BYTES;
break;
case GSM48_CMODE_SPEECH_EFR: /* EFR */
l2_len = GSM_EFR_BYTES;
break;
case GSM48_CMODE_SPEECH_AMR: /* AMR */
/**
* TODO: AMR requires a dedicated loop,
* which will be implemented later...
*/
LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet, "
"dropping frame...\n");
/* Forget this primitive */
sched_prim_drop(lchan);
return -ENOTSUP;
default:
LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u, "
"dropping frame...\n", lchan->tch_mode);
/* Forget this primitive */
sched_prim_drop(lchan);
return -EINVAL;
}
/* Determine and check the payload length */
if (lchan->prim->payload_len == GSM_MACBLOCK_LEN) {
l2_len = GSM_MACBLOCK_LEN; /* FACCH */
} else if (lchan->prim->payload_len != l2_len) {
LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu "
"(expected %zu for TCH or %u for FACCH), so dropping...\n",
lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
sched_prim_drop(lchan);
return -EINVAL;
}
/* Shift buffer by 4 bursts back for interleaving */
memcpy(buffer, buffer + 464, 464);
/* Encode payload */
rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, l2_len, 1);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
lchan->prim->payload_len));
/* Forget this primitive */
sched_prim_drop(lchan);
return -EINVAL;
}
send_burst:
/* Determine which burst should be sent */
offset = buffer + bid * 116;
/* Update mask */
*mask |= (1 << bid);
/* Choose proper TSC */
tsc = sched_nb_training_bits[trx->tsc];
/* Compose a new burst */
memset(burst, 0, 3); /* TB */
memcpy(burst + 3, offset, 58); /* Payload 1/2 */
memcpy(burst + 61, tsc, 26); /* TSC */
memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
memset(burst + 145, 0, 3); /* TB */
LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
lchan_desc->name, fn, ts->index, bid);
/* Forward burst to scheduler */
rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
if (rc) {
/* Forget this primitive */
sched_prim_drop(lchan);
/* Reset mask */
*mask = 0x00;
return rc;
}
/* If we have sent the last (4/4) burst */
if (*mask == 0x0f) {
/* Confirm data / traffic sending */
sched_send_dt_conf(trx, ts, lchan, fn, PRIM_IS_TCH(lchan->prim));
/* Forget processed primitive */
sched_prim_drop(lchan);
/* Reset mask */
*mask = 0x00;
}
return 0;
}

501
trxcon/sched_lchan_tchh.c Normal file
View File

@@ -0,0 +1,501 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2018-2020 by Vadim Yanitskiy <axilirator@gmail.com>
* (C) 2018 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/bits.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/coding/gsm0503_coding.h>
#include <osmocom/codec/codec.h>
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#include "logging.h"
#include "trx_if.h"
#include "l1ctl.h"
static const uint8_t tch_h0_traffic_block_map[3][4] = {
/* B0(0,2,4,6), B1(4,6,8,10), B2(8,10,0,2) */
{ 0, 2, 4, 6 },
{ 4, 6, 8, 10 },
{ 8, 10, 0, 2 },
};
static const uint8_t tch_h1_traffic_block_map[3][4] = {
/* B0(1,3,5,7), B1(5,7,9,11), B2(9,11,1,3) */
{ 1, 3, 5, 7 },
{ 5, 7, 9, 11 },
{ 9, 11, 1, 3 },
};
static const uint8_t tch_h0_dl_facch_block_map[3][6] = {
/* B0(4,6,8,10,13,15), B1(13,15,17,19,21,23), B2(21,23,0,2,4,6) */
{ 4, 6, 8, 10, 13, 15 },
{ 13, 15, 17, 19, 21, 23 },
{ 21, 23, 0, 2, 4, 6 },
};
static const uint8_t tch_h0_ul_facch_block_map[3][6] = {
/* B0(0,2,4,6,8,10), B1(8,10,13,15,17,19), B2(17,19,21,23,0,2) */
{ 0, 2, 4, 6, 8, 10 },
{ 8, 10, 13, 15, 17, 19 },
{ 17, 19, 21, 23, 0, 2 },
};
static const uint8_t tch_h1_dl_facch_block_map[3][6] = {
/* B0(5,7,9,11,14,16), B1(14,16,18,20,22,24), B2(22,24,1,3,5,7) */
{ 5, 7, 9, 11, 14, 16 },
{ 14, 16, 18, 20, 22, 24 },
{ 22, 24, 1, 3, 5, 7 },
};
const uint8_t tch_h1_ul_facch_block_map[3][6] = {
/* B0(1,3,5,7,9,11), B1(9,11,14,16,18,20), B2(18,20,22,24,1,3) */
{ 1, 3, 5, 7, 9, 11 },
{ 9, 11, 14, 16, 18, 20 },
{ 18, 20, 22, 24, 1, 3 },
};
/**
* Can a TCH/H block transmission be initiated / finished
* on a given frame number and a given channel type?
*
* See GSM 05.02, clause 7, table 1
*
* @param chan channel type (TRXC_TCHH_0 or TRXC_TCHH_1)
* @param fn the current frame number
* @param ul Uplink or Downlink?
* @param facch FACCH/H or traffic?
* @param start init or end of transmission?
* @return true (yes) or false (no)
*/
bool sched_tchh_block_map_fn(enum trx_lchan_type chan,
uint32_t fn, bool ul, bool facch, bool start)
{
uint8_t fn_mf;
int i = 0;
/* Just to be sure */
OSMO_ASSERT(chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1);
/* Calculate a modulo */
fn_mf = facch ? (fn % 26) : (fn % 13);
#define MAP_GET_POS(map) \
(start ? 0 : ARRAY_SIZE(map[i]) - 1)
#define BLOCK_MAP_FN(map) \
do { \
if (map[i][MAP_GET_POS(map)] == fn_mf) \
return true; \
} while (++i < ARRAY_SIZE(map))
/* Choose a proper block map */
if (facch) {
if (ul) {
if (chan == TRXC_TCHH_0)
BLOCK_MAP_FN(tch_h0_ul_facch_block_map);
else
BLOCK_MAP_FN(tch_h1_ul_facch_block_map);
} else {
if (chan == TRXC_TCHH_0)
BLOCK_MAP_FN(tch_h0_dl_facch_block_map);
else
BLOCK_MAP_FN(tch_h1_dl_facch_block_map);
}
} else {
if (chan == TRXC_TCHH_0)
BLOCK_MAP_FN(tch_h0_traffic_block_map);
else
BLOCK_MAP_FN(tch_h1_traffic_block_map);
}
return false;
}
/**
* Calculates a frame number of the first burst
* using given frame number of the last burst.
*
* See GSM 05.02, clause 7, table 1
*
* @param chan channel type (TRXC_TCHH_0 or TRXC_TCHH_1)
* @param last_fn frame number of the last burst
* @param facch FACCH/H or traffic?
* @return either frame number of the first burst,
* or fn=last_fn if calculation failed
*/
uint32_t sched_tchh_block_dl_first_fn(enum trx_lchan_type chan,
uint32_t last_fn, bool facch)
{
uint8_t fn_mf, fn_diff;
int i = 0;
/* Just to be sure */
OSMO_ASSERT(chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1);
/* Calculate a modulo */
fn_mf = facch ? (last_fn % 26) : (last_fn % 13);
#define BLOCK_FIRST_FN(map) \
do { \
if (map[i][ARRAY_SIZE(map[i]) - 1] == fn_mf) { \
fn_diff = GSM_TDMA_FN_DIFF(fn_mf, map[i][0]); \
return GSM_TDMA_FN_SUB(last_fn, fn_diff); \
} \
} while (++i < ARRAY_SIZE(map))
/* Choose a proper block map */
if (facch) {
if (chan == TRXC_TCHH_0)
BLOCK_FIRST_FN(tch_h0_dl_facch_block_map);
else
BLOCK_FIRST_FN(tch_h1_dl_facch_block_map);
} else {
if (chan == TRXC_TCHH_0)
BLOCK_FIRST_FN(tch_h0_traffic_block_map);
else
BLOCK_FIRST_FN(tch_h1_traffic_block_map);
}
LOGP(DSCHD, LOGL_ERROR, "Failed to calculate TDMA "
"frame number of the first burst of %s block, "
"using the current fn=%u\n", facch ?
"FACCH/H" : "TCH/H", last_fn);
/* Couldn't calculate the first fn, return the last */
return last_fn;
}
int rx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
const sbit_t *bits, const struct trx_meas_set *meas)
{
const struct trx_lchan_desc *lchan_desc;
int n_errors = -1, n_bits_total, rc;
sbit_t *buffer, *offset;
uint8_t l2[128], *mask;
size_t l2_len;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
mask = &lchan->rx_burst_mask;
buffer = lchan->rx_bursts;
LOGP(DSCHD, LOGL_DEBUG, "Traffic received on %s: fn=%u ts=%u bid=%u\n",
lchan_desc->name, fn, ts->index, bid);
if (*mask == 0x00) {
/* Align to the first burst */
if (bid > 0)
return 0;
/* Align reception of the first FACCH/H frame */
if (lchan->tch_mode == GSM48_CMODE_SIGN) {
if (!sched_tchh_facch_start(lchan->type, fn, 0))
return 0;
} else { /* or TCH/H traffic frame */
if (!sched_tchh_traffic_start(lchan->type, fn, 0))
return 0;
}
}
/* Update mask */
*mask |= (1 << bid);
/* Store the measurements */
sched_trx_meas_push(lchan, meas);
/* Copy burst to the end of buffer of 6 bursts */
offset = buffer + bid * 116 + 464;
memcpy(offset, bits + 3, 58);
memcpy(offset + 58, bits + 87, 58);
/* Wait until the second burst */
if (bid != 1)
return 0;
/* Wait for complete set of bursts */
if (lchan->tch_mode == GSM48_CMODE_SIGN) {
/* FACCH/H is interleaved over 6 bursts */
if ((*mask & 0x3f) != 0x3f)
goto bfi_shift;
} else {
/* Traffic is interleaved over 4 bursts */
if ((*mask & 0x0f) != 0x0f)
goto bfi_shift;
}
/* Skip decoding attempt in case of FACCH/H */
if (lchan->dl_ongoing_facch) {
lchan->dl_ongoing_facch = false;
goto bfi_shift; /* 2/2 BFI */
}
switch (lchan->tch_mode) {
case GSM48_CMODE_SIGN:
case GSM48_CMODE_SPEECH_V1: /* HR */
rc = gsm0503_tch_hr_decode(l2, buffer,
!sched_tchh_facch_end(lchan->type, fn, 0),
&n_errors, &n_bits_total);
break;
case GSM48_CMODE_SPEECH_AMR: /* AMR */
/**
* TODO: AMR requires a dedicated loop,
* which will be implemented later...
*/
LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet\n");
return -ENOTSUP;
default:
LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
return -EINVAL;
}
/* Shift buffer by 4 bursts for interleaving */
memcpy(buffer, buffer + 232, 232);
memcpy(buffer + 232, buffer + 464, 232);
/* Shift burst mask */
*mask = *mask << 2;
/* Check decoding result */
if (rc < 4) {
/* Calculate AVG of the measurements (assuming 4 bursts) */
sched_trx_meas_avg(lchan, 4);
LOGP(DSCHD, LOGL_ERROR, "Received bad TCH frame (%s) "
"at fn=%u on %s (rc=%d)\n", burst_mask2str(mask, 6),
lchan->meas_avg.fn, lchan_desc->name, rc);
/* Send BFI */
goto bfi;
} else if (rc == GSM_MACBLOCK_LEN) {
/* Skip decoding of the next 2 stolen bursts */
lchan->dl_ongoing_facch = true;
/* Calculate AVG of the measurements (FACCH/H takes 6 bursts) */
sched_trx_meas_avg(lchan, 6);
/* FACCH/H received, forward to the higher layers */
sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
n_errors, false, false);
/* Send BFI substituting 1/2 stolen TCH frames */
n_errors = -1; /* ensure fake measurements */
goto bfi;
} else {
/* A good TCH frame received */
l2_len = rc;
/* Calculate AVG of the measurements (traffic takes 4 bursts) */
sched_trx_meas_avg(lchan, 4);
}
/* Send a traffic frame to the higher layers */
return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
n_errors, false, true);
bfi_shift:
/* Shift buffer */
memcpy(buffer, buffer + 232, 232);
memcpy(buffer + 232, buffer + 464, 232);
/* Shift burst mask */
*mask = *mask << 2;
bfi:
/* Didn't try to decode, fake measurements */
if (n_errors < 0) {
lchan->meas_avg = (struct trx_meas_set) {
.fn = sched_tchh_block_dl_first_fn(lchan->type, fn, false),
.toa256 = 0,
.rssi = -110,
};
/* No bursts => no errors */
n_errors = 0;
}
/* BFI is not applicable in signalling mode */
if (lchan->tch_mode == GSM48_CMODE_SIGN)
return sched_send_dt_ind(trx, ts, lchan, NULL, 0,
n_errors, true, false);
/* Bad frame indication */
l2_len = sched_bad_frame_ind(l2, lchan);
/* Send a BFI frame to the higher layers */
return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
n_errors, true, true);
}
int tx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
{
const struct trx_lchan_desc *lchan_desc;
ubit_t burst[GSM_BURST_LEN];
ubit_t *buffer, *offset;
const uint8_t *tsc;
uint8_t *mask;
size_t l2_len;
int rc;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
mask = &lchan->tx_burst_mask;
buffer = lchan->tx_bursts;
if (bid > 0) {
/* Align to the first burst */
if (*mask == 0x00)
return 0;
goto send_burst;
}
if (*mask == 0x00) {
/* Align transmission of the first FACCH/H frame */
if (lchan->tch_mode == GSM48_CMODE_SIGN)
if (!sched_tchh_facch_start(lchan->type, fn, 1))
return 0;
}
/* Shift buffer by 2 bursts back for interleaving */
memcpy(buffer, buffer + 232, 232);
/* Also shift TX burst mask */
*mask = *mask << 2;
/* If FACCH/H blocks are still pending */
if (lchan->ul_facch_blocks > 2) {
memcpy(buffer + 232, buffer + 464, 232);
goto send_burst;
}
/* Check the current TCH mode */
switch (lchan->tch_mode) {
case GSM48_CMODE_SIGN:
case GSM48_CMODE_SPEECH_V1: /* HR */
l2_len = GSM_HR_BYTES + 1;
break;
case GSM48_CMODE_SPEECH_AMR: /* AMR */
/**
* TODO: AMR requires a dedicated loop,
* which will be implemented later...
*/
LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet, "
"dropping frame...\n");
/* Forget this primitive */
sched_prim_drop(lchan);
return -ENOTSUP;
default:
LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u, "
"dropping frame...\n", lchan->tch_mode);
/* Forget this primitive */
sched_prim_drop(lchan);
return -EINVAL;
}
/* Determine payload length */
if (PRIM_IS_FACCH(lchan->prim)) {
l2_len = GSM_MACBLOCK_LEN; /* FACCH */
} else if (lchan->prim->payload_len != l2_len) {
LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu "
"(expected %zu for TCH or %u for FACCH), so dropping...\n",
lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
/* Forget this primitive */
sched_prim_drop(lchan);
return -EINVAL;
}
/* Encode the payload */
rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, l2_len);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
lchan->prim->payload_len));
/* Forget this primitive */
sched_prim_drop(lchan);
return -EINVAL;
}
/* A FACCH/H frame occupies 6 bursts */
if (PRIM_IS_FACCH(lchan->prim))
lchan->ul_facch_blocks = 6;
send_burst:
/* Determine which burst should be sent */
offset = buffer + bid * 116;
/* Update mask */
*mask |= (1 << bid);
/* Choose proper TSC */
tsc = sched_nb_training_bits[trx->tsc];
/* Compose a new burst */
memset(burst, 0, 3); /* TB */
memcpy(burst + 3, offset, 58); /* Payload 1/2 */
memcpy(burst + 61, tsc, 26); /* TSC */
memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
memset(burst + 145, 0, 3); /* TB */
LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
lchan_desc->name, fn, ts->index, bid);
/* Forward burst to transceiver */
sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
/* In case of a FACCH/H frame, one block less */
if (lchan->ul_facch_blocks)
lchan->ul_facch_blocks--;
if ((*mask & 0x0f) == 0x0f) {
/**
* If no more FACCH/H blocks pending,
* confirm data / traffic sending
*/
if (!lchan->ul_facch_blocks)
sched_send_dt_conf(trx, ts, lchan, fn,
PRIM_IS_TCH(lchan->prim));
/* Forget processed primitive */
sched_prim_drop(lchan);
}
return 0;
}

217
trxcon/sched_lchan_xcch.c Normal file
View File

@@ -0,0 +1,217 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2017-2020 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/coding/gsm0503_coding.h>
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#include "logging.h"
#include "trx_if.h"
#include "l1ctl.h"
__attribute__((xray_always_instrument)) __attribute__((noinline))
static int gsm0503_xcch_decode_xray(uint8_t *l2_data, const sbit_t *bursts,
int *n_errors, int *n_bits_total) {
return gsm0503_xcch_decode(l2_data, bursts, n_errors, n_bits_total);
}
__attribute__((xray_always_instrument)) __attribute__((noinline)) int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
const sbit_t *bits, const struct trx_meas_set *meas)
{
const struct trx_lchan_desc *lchan_desc;
uint8_t l2[GSM_MACBLOCK_LEN], *mask;
int n_errors, n_bits_total, rc;
sbit_t *buffer, *offset;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
mask = &lchan->rx_burst_mask;
buffer = lchan->rx_bursts;
LOGP(DSCHD, LOGL_DEBUG, "Data received on %s: fn=%u ts=%u bid=%u\n",
lchan_desc->name, fn, ts->index, bid);
/* Align to the first burst of a block */
if (*mask == 0x00 && bid != 0)
return 0;
/* Update mask */
*mask |= (1 << bid);
/* Store the measurements */
sched_trx_meas_push(lchan, meas);
/* Copy burst to buffer of 4 bursts */
offset = buffer + bid * 116;
memcpy(offset, bits + 3, 58);
memcpy(offset + 58, bits + 87, 58);
/* Wait until complete set of bursts */
if (bid != 3)
return 0;
/* Calculate AVG of the measurements */
sched_trx_meas_avg(lchan, 4);
/* Check for complete set of bursts */
if ((*mask & 0xf) != 0xf) {
LOGP(DSCHD, LOGL_ERROR, "Received incomplete (%s) data frame at "
"fn=%u (%u/%u) for %s\n",
burst_mask2str(mask, 4), lchan->meas_avg.fn,
lchan->meas_avg.fn % ts->mf_layout->period,
ts->mf_layout->period,
lchan_desc->name);
/* NOTE: xCCH has an insane amount of redundancy for error
* correction, so even just 2 valid bursts might be enough
* to reconstruct some L2 frames. This is why we do not
* abort here. */
}
/* Keep the mask updated */
*mask = *mask << 4;
/* Attempt to decode */
rc = gsm0503_xcch_decode_xray(l2, buffer, &n_errors, &n_bits_total);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Received bad data frame with %d errors at fn=%u "
"(%u/%u) for %s\n", n_errors, lchan->meas_avg.fn,
lchan->meas_avg.fn % ts->mf_layout->period,
ts->mf_layout->period,
lchan_desc->name);
/**
* We should anyway send dummy frame for
* proper measurement reporting...
*/
return sched_send_dt_ind(trx, ts, lchan, NULL, 0,
n_errors, true, false);
}
/* Send a L2 frame to the higher layers */
return sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
n_errors, false, false);
}
int tx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
{
const struct trx_lchan_desc *lchan_desc;
ubit_t burst[GSM_BURST_LEN];
ubit_t *buffer, *offset;
const uint8_t *tsc;
uint8_t *mask;
int rc;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
mask = &lchan->tx_burst_mask;
buffer = lchan->tx_bursts;
if (bid > 0) {
/* If we have encoded bursts */
if (*mask)
goto send_burst;
else
return 0;
}
/* Check the prim payload length */
if (lchan->prim->payload_len != GSM_MACBLOCK_LEN) {
LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu (expected %u), "
"so dropping...\n", lchan->prim->payload_len, GSM_MACBLOCK_LEN);
sched_prim_drop(lchan);
return -EINVAL;
}
/* Encode payload */
rc = gsm0503_xcch_encode(buffer, lchan->prim->payload);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
lchan->prim->payload_len));
/* Forget this primitive */
sched_prim_drop(lchan);
return -EINVAL;
}
send_burst:
/* Determine which burst should be sent */
offset = buffer + bid * 116;
/* Update mask */
*mask |= (1 << bid);
/* Choose proper TSC */
tsc = sched_nb_training_bits[trx->tsc];
/* Compose a new burst */
memset(burst, 0, 3); /* TB */
memcpy(burst + 3, offset, 58); /* Payload 1/2 */
memcpy(burst + 61, tsc, 26); /* TSC */
memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
memset(burst + 145, 0, 3); /* TB */
LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
lchan_desc->name, fn, ts->index, bid);
/* Forward burst to scheduler */
rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
if (rc) {
/* Forget this primitive */
sched_prim_drop(lchan);
/* Reset mask */
*mask = 0x00;
return rc;
}
/* If we have sent the last (4/4) burst */
if ((*mask & 0x0f) == 0x0f) {
/* Confirm data sending */
sched_send_dt_conf(trx, ts, lchan, fn, false);
/* Forget processed primitive */
sched_prim_drop(lchan);
/* Reset mask */
*mask = 0x00;
}
return 0;
}

2101
trxcon/sched_mframe.c Normal file

File diff suppressed because it is too large Load Diff

617
trxcon/sched_prim.c Normal file
View File

@@ -0,0 +1,617 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: primitive management
*
* (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <talloc.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include "scheduler.h"
#include "sched_trx.h"
#include "trx_if.h"
#include "logging.h"
/**
* Initializes a new primitive by allocating memory
* and filling some meta-information (e.g. lchan type).
*
* @param ctx parent talloc context
* @param prim external prim pointer (will point to the allocated prim)
* @param pl_len prim payload length
* @param chan_nr RSL channel description (used to set a proper chan)
* @param link_id RSL link description (used to set a proper chan)
* @return zero in case of success, otherwise a error number
*/
int sched_prim_init(void *ctx, struct trx_ts_prim **prim,
size_t pl_len, uint8_t chan_nr, uint8_t link_id)
{
enum trx_lchan_type lchan_type;
struct trx_ts_prim *new_prim;
uint8_t len;
/* Determine lchan type */
lchan_type = sched_trx_chan_nr2lchan_type(chan_nr, link_id);
if (!lchan_type) {
LOGP(DSCH, LOGL_ERROR, "Couldn't determine lchan type "
"for chan_nr=%02x and link_id=%02x\n", chan_nr, link_id);
return -EINVAL;
}
/* How much memory do we need? */
len = sizeof(struct trx_ts_prim); /* Primitive header */
len += pl_len; /* Requested payload size */
/* Allocate a new primitive */
new_prim = talloc_zero_size(ctx, len);
if (new_prim == NULL) {
LOGP(DSCH, LOGL_ERROR, "Failed to allocate memory\n");
return -ENOMEM;
}
/* Init primitive header */
new_prim->payload_len = pl_len;
new_prim->chan = lchan_type;
/* Set external pointer */
*prim = new_prim;
return 0;
}
/**
* Adds a primitive to the end of transmit queue of a particular
* timeslot, whose index is parsed from chan_nr.
*
* @param trx TRX instance
* @param prim to be enqueued primitive
* @param chan_nr RSL channel description
* @return zero in case of success, otherwise a error number
*/
int sched_prim_push(struct trx_instance *trx,
struct trx_ts_prim *prim, uint8_t chan_nr)
{
struct trx_ts *ts;
uint8_t tn;
/* Determine TS index */
tn = chan_nr & 0x7;
/* Check whether required timeslot is allocated and configured */
ts = trx->ts_list[tn];
if (ts == NULL || ts->mf_layout == NULL) {
LOGP(DSCH, LOGL_ERROR, "Timeslot %u isn't configured\n", tn);
return -EINVAL;
}
/**
* Change talloc context of primitive
* from trx to the parent ts
*/
talloc_steal(ts, prim);
/* Add primitive to TS transmit queue */
llist_add_tail(&prim->list, &ts->tx_prims);
return 0;
}
/**
* Composes a new primitive using either cached (if populated),
* or "dummy" Measurement Report message.
*
* @param lchan lchan to assign a primitive
* @return SACCH primitive to be transmitted
*/
static struct trx_ts_prim *prim_compose_mr(struct trx_lchan_state *lchan)
{
struct trx_ts_prim *prim;
uint8_t *mr_src_ptr;
bool cached;
int rc;
/* "Dummy" Measurement Report */
static const uint8_t meas_rep_dummy[] = {
/* L1 SACCH pseudo-header */
0x0f, 0x00,
/* LAPDm header */
0x01, 0x03, 0x49,
/* RR Management messages, Measurement Report */
0x06, 0x15,
/* Measurement results (see 3GPP TS 44.018, section 10.5.2.20):
* 0... .... = BA-USED: 0
* .0.. .... = DTX-USED: DTX was not used
* ..11 0110 = RXLEV-FULL-SERVING-CELL: -57 <= x < -56 dBm (54)
* 0... .... = 3G-BA-USED: 0
* .1.. .... = MEAS-VALID: The measurement results are not valid
* ..11 0110 = RXLEV-SUB-SERVING-CELL: -57 <= x < -56 dBm (54)
* 0... .... = SI23_BA_USED: 0
* .000 .... = RXQUAL-FULL-SERVING-CELL: BER < 0.2%, Mean value 0.14% (0)
* .... 000. = RXQUAL-SUB-SERVING-CELL: BER < 0.2%, Mean value 0.14% (0)
* .... ...1 11.. .... = NO-NCELL-M: Neighbour cell information not available */
0x36, 0x76, 0x01, 0xc0,
/* 0** -- Padding with zeroes */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
/* Allocate a new primitive */
rc = sched_prim_init(lchan, &prim, GSM_MACBLOCK_LEN,
trx_lchan_desc[lchan->type].chan_nr, TRX_CH_LID_SACCH);
OSMO_ASSERT(rc == 0);
/* Check if the MR cache is populated (verify LAPDm header) */
cached = (lchan->sacch.mr_cache[2] != 0x00
&& lchan->sacch.mr_cache[3] != 0x00
&& lchan->sacch.mr_cache[4] != 0x00);
if (cached) { /* Use the cached one */
mr_src_ptr = lchan->sacch.mr_cache;
lchan->sacch.mr_cache_usage++;
} else { /* Use "dummy" one */
mr_src_ptr = (uint8_t *) meas_rep_dummy;
}
/* Compose a new Measurement Report primitive */
memcpy(prim->payload, mr_src_ptr, GSM_MACBLOCK_LEN);
/**
* Update the L1 SACCH pseudo-header (only for cached MRs)
*
* TODO: filling of the actual values into cached Measurement
* Reports would break the distance spoofing feature. If it
* were known whether the spoofing is enabled or not, we could
* decide whether to update the cached L1 SACCH header here.
*/
if (!cached) {
prim->payload[0] = lchan->ts->trx->tx_power;
prim->payload[1] = lchan->ts->trx->ta;
}
/* Inform about the cache usage count */
if (cached && lchan->sacch.mr_cache_usage > 5) {
LOGP(DSCHD, LOGL_NOTICE, "SACCH MR cache usage count=%u > 5 "
"on lchan=%s => ancient measurements, please fix!\n",
lchan->sacch.mr_cache_usage,
trx_lchan_desc[lchan->type].name);
}
LOGP(DSCHD, LOGL_NOTICE, "Using a %s Measurement Report "
"on lchan=%s\n", (cached ? "cached" : "dummy"),
trx_lchan_desc[lchan->type].name);
return prim;
}
/**
* Dequeues a SACCH primitive from transmit queue, if present.
* Otherwise dequeues a cached Measurement Report (the last
* received one). Finally, if the cache is empty, a "dummy"
* measurement report is used.
*
* According to 3GPP TS 04.08, section 3.4.1, SACCH channel
* accompanies either a traffic or a signaling channel. It
* has the particularity that continuous transmission must
* occur in both directions, so on the Uplink direction
* measurement result messages are sent at each possible
* occasion when nothing else has to be sent. The LAPDm
* fill frames (0x01, 0x03, 0x01, 0x2b, ...) are not
* applicable on SACCH channels!
*
* Unfortunately, 3GPP TS 04.08 doesn't clearly state
* which "else messages" besides Measurement Reports
* can be send by the MS on SACCH channels. However,
* in sub-clause 3.4.1 it's stated that the interval
* between two successive measurement result messages
* shall not exceed one L2 frame.
*
* @param queue transmit queue to take a prim from
* @param lchan lchan to assign a primitive
* @return SACCH primitive to be transmitted
*/
static struct trx_ts_prim *prim_dequeue_sacch(struct llist_head *queue,
struct trx_lchan_state *lchan)
{
struct trx_ts_prim *prim_nmr = NULL;
struct trx_ts_prim *prim_mr = NULL;
struct trx_ts_prim *prim;
bool mr_now;
/* Shall we transmit MR now? */
mr_now = !lchan->sacch.mr_tx_last;
#define PRIM_IS_MR(prim) \
(prim->payload[5] == GSM48_PDISC_RR \
&& prim->payload[6] == GSM48_MT_RR_MEAS_REP)
/* Iterate over all primitives in the queue */
llist_for_each_entry(prim, queue, list) {
/* We are looking for particular channel */
if (prim->chan != lchan->type)
continue;
/* Look for a Measurement Report */
if (!prim_mr && PRIM_IS_MR(prim))
prim_mr = prim;
/* Look for anything else */
if (!prim_nmr && !PRIM_IS_MR(prim))
prim_nmr = prim;
/* Should we look further? */
if (mr_now && prim_mr)
break; /* MR was found */
else if (!mr_now && prim_nmr)
break; /* something else was found */
}
LOGP(DSCHD, LOGL_DEBUG, "SACCH MR selection on lchan=%s: "
"mr_tx_last=%d prim_mr=%p prim_nmr=%p\n",
trx_lchan_desc[lchan->type].name,
lchan->sacch.mr_tx_last,
prim_mr, prim_nmr);
/* Prioritize non-MR prim if possible */
if (mr_now && prim_mr)
prim = prim_mr;
else if (!mr_now && prim_nmr)
prim = prim_nmr;
else if (!mr_now && prim_mr)
prim = prim_mr;
else /* Nothing was found */
prim = NULL;
/* Have we found what we were looking for? */
if (prim) /* Dequeue if so */
llist_del(&prim->list);
else /* Otherwise compose a new MR */
prim = prim_compose_mr(lchan);
/* Update the cached report */
if (prim == prim_mr) {
memcpy(lchan->sacch.mr_cache,
prim->payload, GSM_MACBLOCK_LEN);
lchan->sacch.mr_cache_usage = 0;
LOGP(DSCHD, LOGL_DEBUG, "SACCH MR cache has been updated "
"for lchan=%s\n", trx_lchan_desc[lchan->type].name);
}
/* Update the MR transmission state */
lchan->sacch.mr_tx_last = PRIM_IS_MR(prim);
LOGP(DSCHD, LOGL_DEBUG, "SACCH decision on lchan=%s: %s\n",
trx_lchan_desc[lchan->type].name, PRIM_IS_MR(prim) ?
"Measurement Report" : "data frame");
return prim;
}
/* Dequeues a primitive of a given channel type */
static struct trx_ts_prim *prim_dequeue_one(struct llist_head *queue,
enum trx_lchan_type lchan_type)
{
struct trx_ts_prim *prim;
/**
* There is no need to use the 'safe' list iteration here
* as an item removal is immediately followed by return.
*/
llist_for_each_entry(prim, queue, list) {
if (prim->chan == lchan_type) {
llist_del(&prim->list);
return prim;
}
}
return NULL;
}
/**
* Dequeues either a FACCH, or a speech TCH primitive
* of a given channel type (Lm or Bm).
*
* Note: we could avoid 'lchan_type' parameter and just
* check the prim's channel type using CHAN_IS_TCH(),
* but the current approach is a bit more flexible,
* and allows one to have both sub-slots of TCH/H
* enabled on same timeslot e.g. for testing...
*
* @param queue transmit queue to take a prim from
* @param lchan_type required channel type of a primitive,
* e.g. TRXC_TCHF, TRXC_TCHH_0, or TRXC_TCHH_1
* @param facch FACCH (true) or speech (false) prim?
* @return either a FACCH, or a TCH primitive if found,
* otherwise NULL
*/
static struct trx_ts_prim *prim_dequeue_tch(struct llist_head *queue,
enum trx_lchan_type lchan_type, bool facch)
{
struct trx_ts_prim *prim;
/**
* There is no need to use the 'safe' list iteration here
* as an item removal is immediately followed by return.
*/
llist_for_each_entry(prim, queue, list) {
if (prim->chan != lchan_type)
continue;
/* Either FACCH, or not FACCH */
if (PRIM_IS_FACCH(prim) != facch)
continue;
llist_del(&prim->list);
return prim;
}
return NULL;
}
/**
* Dequeues either a TCH/F, or a FACCH/F prim (preferred).
* If a FACCH/F prim is found, one TCH/F prim is being
* dropped (i.e. replaced).
*
* @param queue a transmit queue to take a prim from
* @return either a FACCH/F, or a TCH/F primitive,
* otherwise NULL
*/
static struct trx_ts_prim *prim_dequeue_tch_f(struct llist_head *queue)
{
struct trx_ts_prim *facch;
struct trx_ts_prim *tch;
/* Attempt to find a pair of both FACCH/F and TCH/F frames */
facch = prim_dequeue_tch(queue, TRXC_TCHF, true);
tch = prim_dequeue_tch(queue, TRXC_TCHF, false);
/* Prioritize FACCH/F, if found */
if (facch) {
/* One TCH/F prim is replaced */
if (tch)
talloc_free(tch);
return facch;
} else if (tch) {
/* Only TCH/F prim was found */
return tch;
} else {
/* Nothing was found, e.g. when only SACCH frames are in queue */
return NULL;
}
}
/**
* Dequeues either a TCH/H, or a FACCH/H prim (preferred).
* If a FACCH/H prim is found, two TCH/H prims are being
* dropped (i.e. replaced).
*
* According to GSM 05.02, the following blocks can be used
* to carry FACCH/H data (see clause 7, table 1 of 9):
*
* UL FACCH/H0:
* B0(0,2,4,6,8,10), B1(8,10,13,15,17,19), B2(17,19,21,23,0,2)
*
* UL FACCH/H1:
* B0(1,3,5,7,9,11), B1(9,11,14,16,18,20), B2(18,20,22,24,1,3)
*
* where the numbers within brackets are fn % 26.
*
* @param queue transmit queue to take a prim from
* @param fn the current frame number
* @param lchan_type required channel type of a primitive,
* @return either a FACCH/H, or a TCH/H primitive,
* otherwise NULL
*/
static struct trx_ts_prim *prim_dequeue_tch_h(struct llist_head *queue,
uint32_t fn, enum trx_lchan_type lchan_type)
{
struct trx_ts_prim *facch;
struct trx_ts_prim *tch;
bool facch_now;
/* May we initiate an UL FACCH/H frame transmission now? */
facch_now = sched_tchh_facch_start(lchan_type, fn, true);
if (!facch_now) /* Just dequeue a TCH/H prim */
goto no_facch;
/* If there are no FACCH/H prims in the queue */
facch = prim_dequeue_tch(queue, lchan_type, true);
if (!facch) /* Just dequeue a TCH/H prim */
goto no_facch;
/* FACCH/H prim replaces two TCH/F prims */
tch = prim_dequeue_tch(queue, lchan_type, false);
if (tch) {
/* At least one TCH/H prim is dropped */
talloc_free(tch);
/* Attempt to find another */
tch = prim_dequeue_tch(queue, lchan_type, false);
if (tch) /* Drop the second TCH/H prim */
talloc_free(tch);
}
return facch;
no_facch:
return prim_dequeue_tch(queue, lchan_type, false);
}
/**
* Dequeues a single primitive of required type
* from a specified transmit queue.
*
* @param queue a transmit queue to take a prim from
* @param fn the current frame number (used for FACCH/H)
* @param lchan logical channel state
* @return a primitive or NULL if not found
*/
struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue,
uint32_t fn, struct trx_lchan_state *lchan)
{
/* SACCH is unorthodox, see 3GPP TS 04.08, section 3.4.1 */
if (CHAN_IS_SACCH(lchan->type))
return prim_dequeue_sacch(queue, lchan);
/* There is nothing to dequeue */
if (llist_empty(queue))
return NULL;
switch (lchan->type) {
/* TCH/F requires FACCH/F prioritization */
case TRXC_TCHF:
return prim_dequeue_tch_f(queue);
/* FACCH/H prioritization is a bit more complex */
case TRXC_TCHH_0:
case TRXC_TCHH_1:
return prim_dequeue_tch_h(queue, fn, lchan->type);
/* Other kinds of logical channels */
default:
return prim_dequeue_one(queue, lchan->type);
}
}
/**
* Drops the current primitive of specified logical channel
*
* @param lchan a logical channel to drop prim from
*/
void sched_prim_drop(struct trx_lchan_state *lchan)
{
/* Forget this primitive */
talloc_free(lchan->prim);
lchan->prim = NULL;
}
/**
* Assigns a dummy primitive to a lchan depending on its type.
* Could be used when there is nothing to transmit, but
* CBTX (Continuous Burst Transmission) is assumed.
*
* @param lchan lchan to assign a primitive
* @return zero in case of success, otherwise a error code
*/
int sched_prim_dummy(struct trx_lchan_state *lchan)
{
enum trx_lchan_type chan = lchan->type;
uint8_t tch_mode = lchan->tch_mode;
struct trx_ts_prim *prim;
uint8_t prim_buffer[40];
size_t prim_len = 0;
int i;
/**
* TS 144.006, section 8.4.2.3 "Fill frames"
* A fill frame is a UI command frame for SAPI 0, P=0
* and with an information field of 0 octet length.
*/
static const uint8_t lapdm_fill_frame[] = {
0x01, 0x03, 0x01, 0x2b,
/* Pending part is to be randomized */
};
/* Make sure that there is no existing primitive */
OSMO_ASSERT(lchan->prim == NULL);
/* Not applicable for SACCH! */
OSMO_ASSERT(!CHAN_IS_SACCH(lchan->type));
/**
* Determine what actually should be generated:
* TCH in GSM48_CMODE_SIGN: LAPDm fill frame;
* TCH in other modes: silence frame;
* other channels: LAPDm fill frame.
*/
if (CHAN_IS_TCH(chan) && TCH_MODE_IS_SPEECH(tch_mode)) {
/* Bad frame indication */
prim_len = sched_bad_frame_ind(prim_buffer, lchan);
} else if (CHAN_IS_TCH(chan) && TCH_MODE_IS_DATA(tch_mode)) {
/* FIXME: should we do anything for CSD? */
return 0;
} else {
/* Copy LAPDm fill frame's header */
memcpy(prim_buffer, lapdm_fill_frame, sizeof(lapdm_fill_frame));
/**
* TS 144.006, section 5.2 "Frame delimitation and fill bits"
* Except for the first octet containing fill bits which shall
* be set to the binary value "00101011", each fill bit should
* be set to a random value when sent by the network.
*/
for (i = sizeof(lapdm_fill_frame); i < GSM_MACBLOCK_LEN; i++)
prim_buffer[i] = (uint8_t) rand();
/* Define a prim length */
prim_len = GSM_MACBLOCK_LEN;
}
/* Nothing to allocate / assign */
if (!prim_len)
return 0;
/* Allocate a new primitive */
prim = talloc_zero_size(lchan, sizeof(struct trx_ts_prim) + prim_len);
if (prim == NULL)
return -ENOMEM;
/* Init primitive header */
prim->payload_len = prim_len;
prim->chan = lchan->type;
/* Fill in the payload */
memcpy(prim->payload, prim_buffer, prim_len);
/* Assign the current prim */
lchan->prim = prim;
LOGP(DSCHD, LOGL_DEBUG, "Transmitting a dummy / silence frame "
"on lchan=%s\n", trx_lchan_desc[chan].name);
return 0;
}
/**
* Flushes a queue of primitives
*
* @param list list of prims going to be flushed
*/
void sched_prim_flush_queue(struct llist_head *list)
{
struct trx_ts_prim *prim, *prim_next;
llist_for_each_entry_safe(prim, prim_next, list, list) {
llist_del(&prim->list);
talloc_free(prim);
}
}

841
trxcon/sched_trx.c Normal file
View File

@@ -0,0 +1,841 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: GSM PHY routines
*
* (C) 2017-2019 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <error.h>
#include <errno.h>
#include <string.h>
#include <talloc.h>
#include <stdbool.h>
#include <osmocom/gsm/a5.h>
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/linuxlist.h>
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#include "trx_if.h"
#include "logging.h"
static void sched_frame_clck_cb(struct trx_sched *sched)
{
struct trx_instance *trx = (struct trx_instance *) sched->data;
const struct trx_frame *frame;
struct trx_lchan_state *lchan;
trx_lchan_tx_func *handler;
enum trx_lchan_type chan;
uint8_t offset, bid;
struct trx_ts *ts;
uint32_t fn;
int i;
/* Iterate over timeslot list */
for (i = 0; i < TRX_TS_COUNT; i++) {
/* Timeslot is not allocated */
ts = trx->ts_list[i];
if (ts == NULL)
continue;
/* Timeslot is not configured */
if (ts->mf_layout == NULL)
continue;
/**
* Advance frame number, giving the transceiver more
* time until a burst must be transmitted...
*/
fn = GSM_TDMA_FN_SUM(sched->fn_counter_proc, sched->fn_counter_advance);
/* Get frame from multiframe */
offset = fn % ts->mf_layout->period;
frame = ts->mf_layout->frames + offset;
/* Get required info from frame */
bid = frame->ul_bid;
chan = frame->ul_chan;
handler = trx_lchan_desc[chan].tx_fn;
/* Omit lchans without handler */
if (!handler)
continue;
/* Make sure that lchan was allocated and activated */
lchan = sched_trx_find_lchan(ts, chan);
if (lchan == NULL)
continue;
/* Omit inactive lchans */
if (!lchan->active)
continue;
/**
* If we aren't processing any primitive yet,
* attempt to obtain a new one from queue
*/
if (lchan->prim == NULL)
lchan->prim = sched_prim_dequeue(&ts->tx_prims, fn, lchan);
/* TODO: report TX buffers health to the higher layers */
/* If CBTX (Continuous Burst Transmission) is assumed */
if (trx_lchan_desc[chan].flags & TRX_CH_FLAG_CBTX) {
/**
* Probably, a TX buffer is empty. Nevertheless,
* we shall continuously transmit anything on
* CBTX channels.
*/
if (lchan->prim == NULL)
sched_prim_dummy(lchan);
}
/* If there is no primitive, do nothing */
if (lchan->prim == NULL)
continue;
/* Handover RACH needs to be handled regardless of the
* current channel type and the associated handler. */
if (PRIM_IS_RACH(lchan->prim) && lchan->prim->chan != TRXC_RACH)
handler = trx_lchan_desc[TRXC_RACH].tx_fn;
/* Poke lchan handler */
handler(trx, ts, lchan, fn, bid);
}
}
int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance)
{
struct trx_sched *sched;
if (!trx)
return -EINVAL;
LOGP(DSCH, LOGL_NOTICE, "Init scheduler\n");
/* Obtain a scheduler instance from TRX */
sched = &trx->sched;
/* Register frame clock callback */
sched->clock_cb = sched_frame_clck_cb;
/* Set pointers */
sched = &trx->sched;
sched->data = trx;
/* Set frame counter advance */
sched->fn_counter_advance = fn_advance;
return 0;
}
int sched_trx_shutdown(struct trx_instance *trx)
{
int i;
if (!trx)
return -EINVAL;
LOGP(DSCH, LOGL_NOTICE, "Shutdown scheduler\n");
/* Free all potentially allocated timeslots */
for (i = 0; i < TRX_TS_COUNT; i++)
sched_trx_del_ts(trx, i);
return 0;
}
int sched_trx_reset(struct trx_instance *trx, bool reset_clock)
{
int i;
if (!trx)
return -EINVAL;
LOGP(DSCH, LOGL_NOTICE, "Reset scheduler %s\n",
reset_clock ? "and clock counter" : "");
/* Free all potentially allocated timeslots */
for (i = 0; i < TRX_TS_COUNT; i++)
sched_trx_del_ts(trx, i);
/* Stop and reset clock counter if required */
if (reset_clock)
sched_clck_reset(&trx->sched);
return 0;
}
struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn)
{
/* Make sure that ts isn't allocated yet */
if (trx->ts_list[tn] != NULL) {
LOGP(DSCH, LOGL_ERROR, "Timeslot #%u already allocated\n", tn);
return NULL;
}
LOGP(DSCH, LOGL_NOTICE, "Add a new TDMA timeslot #%u\n", tn);
/* Allocate a new one */
trx->ts_list[tn] = talloc_zero(trx, struct trx_ts);
/* Add backpointer */
trx->ts_list[tn]->trx = trx;
/* Assign TS index */
trx->ts_list[tn]->index = tn;
return trx->ts_list[tn];
}
void sched_trx_del_ts(struct trx_instance *trx, int tn)
{
struct trx_lchan_state *lchan, *lchan_next;
struct trx_ts *ts;
/* Find ts in list */
ts = trx->ts_list[tn];
if (ts == NULL)
return;
LOGP(DSCH, LOGL_NOTICE, "Delete TDMA timeslot #%u\n", tn);
/* Deactivate all logical channels */
sched_trx_deactivate_all_lchans(ts);
/* Free channel states */
llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
llist_del(&lchan->list);
talloc_free(lchan);
}
/* Flush queue primitives for TX */
sched_prim_flush_queue(&ts->tx_prims);
/* Remove ts from list and free memory */
trx->ts_list[tn] = NULL;
talloc_free(ts);
/* Notify transceiver about that */
trx_if_cmd_setslot(trx, tn, 0);
}
#define LAYOUT_HAS_LCHAN(layout, lchan) \
(layout->lchan_mask & ((uint64_t) 0x01 << lchan))
int sched_trx_configure_ts(struct trx_instance *trx, int tn,
enum gsm_phys_chan_config config)
{
struct trx_lchan_state *lchan;
enum trx_lchan_type type;
struct trx_ts *ts;
/* Try to find specified ts */
ts = trx->ts_list[tn];
if (ts != NULL) {
/* Reconfiguration of existing one */
sched_trx_reset_ts(trx, tn);
} else {
/* Allocate a new one if doesn't exist */
ts = sched_trx_add_ts(trx, tn);
if (ts == NULL)
return -ENOMEM;
}
/* Choose proper multiframe layout */
ts->mf_layout = sched_mframe_layout(config, tn);
if (!ts->mf_layout)
return -EINVAL;
if (ts->mf_layout->chan_config != config)
return -EINVAL;
LOGP(DSCH, LOGL_NOTICE, "(Re)configure TDMA timeslot #%u as %s\n",
tn, ts->mf_layout->name);
/* Init queue primitives for TX */
INIT_LLIST_HEAD(&ts->tx_prims);
/* Init logical channels list */
INIT_LLIST_HEAD(&ts->lchans);
/* Allocate channel states */
for (type = 0; type < _TRX_CHAN_MAX; type++) {
if (!LAYOUT_HAS_LCHAN(ts->mf_layout, type))
continue;
/* Allocate a channel state */
lchan = talloc_zero(ts, struct trx_lchan_state);
if (!lchan)
return -ENOMEM;
/* set backpointer */
lchan->ts = ts;
/* Set channel type */
lchan->type = type;
/* Add to the list of channel states */
llist_add_tail(&lchan->list, &ts->lchans);
/* Enable channel automatically if required */
if (trx_lchan_desc[type].flags & TRX_CH_FLAG_AUTO)
sched_trx_activate_lchan(ts, type);
}
/* Notify transceiver about TS activation */
/* FIXME: set proper channel type */
trx_if_cmd_setslot(trx, tn, 1);
return 0;
}
int sched_trx_reset_ts(struct trx_instance *trx, int tn)
{
struct trx_lchan_state *lchan, *lchan_next;
struct trx_ts *ts;
/* Try to find specified ts */
ts = trx->ts_list[tn];
if (ts == NULL)
return -EINVAL;
/* Undefine multiframe layout */
ts->mf_layout = NULL;
/* Flush queue primitives for TX */
sched_prim_flush_queue(&ts->tx_prims);
/* Deactivate all logical channels */
sched_trx_deactivate_all_lchans(ts);
/* Free channel states */
llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
llist_del(&lchan->list);
talloc_free(lchan);
}
/* Notify transceiver about that */
trx_if_cmd_setslot(trx, tn, 0);
return 0;
}
int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo,
uint8_t *key, uint8_t key_len)
{
struct trx_lchan_state *lchan;
/* Prevent NULL-pointer deference */
if (!ts)
return -EINVAL;
/* Make sure we can store this key */
if (key_len > MAX_A5_KEY_LEN)
return -ERANGE;
/* Iterate over all allocated logical channels */
llist_for_each_entry(lchan, &ts->lchans, list) {
/* Omit inactive channels */
if (!lchan->active)
continue;
/* Set key length and algorithm */
lchan->a5.key_len = key_len;
lchan->a5.algo = algo;
/* Copy requested key */
if (key_len)
memcpy(lchan->a5.key, key, key_len);
}
return 0;
}
struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts,
enum trx_lchan_type chan)
{
struct trx_lchan_state *lchan;
llist_for_each_entry(lchan, &ts->lchans, list)
if (lchan->type == chan)
return lchan;
return NULL;
}
int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode)
{
const struct trx_lchan_desc *lchan_desc;
struct trx_lchan_state *lchan;
int rc = 0;
/* Prevent NULL-pointer deference */
if (ts == NULL) {
LOGP(DSCH, LOGL_ERROR, "Timeslot isn't configured\n");
return -EINVAL;
}
/* Iterate over all allocated lchans */
llist_for_each_entry(lchan, &ts->lchans, list) {
lchan_desc = &trx_lchan_desc[lchan->type];
if (lchan_desc->chan_nr == (chan_nr & 0xf8)) {
if (active) {
rc |= sched_trx_activate_lchan(ts, lchan->type);
lchan->tch_mode = tch_mode;
} else
rc |= sched_trx_deactivate_lchan(ts, lchan->type);
}
}
return rc;
}
int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
{
const struct trx_lchan_desc *lchan_desc = &trx_lchan_desc[chan];
struct trx_lchan_state *lchan;
/* Try to find requested logical channel */
lchan = sched_trx_find_lchan(ts, chan);
if (lchan == NULL)
return -EINVAL;
if (lchan->active) {
LOGP(DSCH, LOGL_ERROR, "Logical channel %s already activated "
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
return -EINVAL;
}
LOGP(DSCH, LOGL_NOTICE, "Activating lchan=%s "
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
/* Conditionally allocate memory for bursts */
if (lchan_desc->rx_fn && lchan_desc->burst_buf_size > 0) {
lchan->rx_bursts = talloc_zero_size(lchan,
lchan_desc->burst_buf_size);
if (lchan->rx_bursts == NULL)
return -ENOMEM;
}
if (lchan_desc->tx_fn && lchan_desc->burst_buf_size > 0) {
lchan->tx_bursts = talloc_zero_size(lchan,
lchan_desc->burst_buf_size);
if (lchan->tx_bursts == NULL)
return -ENOMEM;
}
/* Finally, update channel status */
lchan->active = 1;
return 0;
}
static void sched_trx_reset_lchan(struct trx_lchan_state *lchan)
{
/* Prevent NULL-pointer deference */
OSMO_ASSERT(lchan != NULL);
/* Print some TDMA statistics for Downlink */
if (trx_lchan_desc[lchan->type].rx_fn && lchan->active) {
LOGP(DSCH, LOGL_DEBUG, "TDMA statistics for lchan=%s on ts=%u: "
"%lu DL frames have been processed, "
"%lu lost (compensated), last fn=%u\n",
trx_lchan_desc[lchan->type].name, lchan->ts->index,
lchan->tdma.num_proc, lchan->tdma.num_lost,
lchan->tdma.last_proc);
}
/* Reset internal state variables */
lchan->rx_burst_mask = 0x00;
lchan->tx_burst_mask = 0x00;
/* Free burst memory */
talloc_free(lchan->rx_bursts);
talloc_free(lchan->tx_bursts);
lchan->rx_bursts = NULL;
lchan->tx_bursts = NULL;
/* Forget the current prim */
sched_prim_drop(lchan);
/* Channel specific stuff */
if (CHAN_IS_TCH(lchan->type)) {
lchan->dl_ongoing_facch = 0;
lchan->ul_facch_blocks = 0;
lchan->tch_mode = GSM48_CMODE_SIGN;
/* Reset AMR state */
memset(&lchan->amr, 0x00, sizeof(lchan->amr));
} else if (CHAN_IS_SACCH(lchan->type)) {
/* Reset SACCH state */
memset(&lchan->sacch, 0x00, sizeof(lchan->sacch));
}
/* Reset ciphering state */
memset(&lchan->a5, 0x00, sizeof(lchan->a5));
/* Reset TDMA frame statistics */
memset(&lchan->tdma, 0x00, sizeof(lchan->tdma));
}
int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
{
struct trx_lchan_state *lchan;
/* Try to find requested logical channel */
lchan = sched_trx_find_lchan(ts, chan);
if (lchan == NULL)
return -EINVAL;
if (!lchan->active) {
LOGP(DSCH, LOGL_ERROR, "Logical channel %s already deactivated "
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
return -EINVAL;
}
LOGP(DSCH, LOGL_DEBUG, "Deactivating lchan=%s "
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
/* Reset internal state, free memory */
sched_trx_reset_lchan(lchan);
/* Update activation flag */
lchan->active = 0;
return 0;
}
void sched_trx_deactivate_all_lchans(struct trx_ts *ts)
{
struct trx_lchan_state *lchan;
LOGP(DSCH, LOGL_DEBUG, "Deactivating all logical channels "
"on ts=%d\n", ts->index);
llist_for_each_entry(lchan, &ts->lchans, list) {
/* Omit inactive channels */
if (!lchan->active)
continue;
/* Reset internal state, free memory */
sched_trx_reset_lchan(lchan);
/* Update activation flag */
lchan->active = 0;
}
}
enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr)
{
uint8_t cbits = chan_nr >> 3;
if (cbits == ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs)
return GSM_PCHAN_TCH_F;
else if ((cbits & 0x1e) == ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0))
return GSM_PCHAN_TCH_H;
else if ((cbits & 0x1c) == ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0))
return GSM_PCHAN_CCCH_SDCCH4;
else if ((cbits & 0x18) == ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0))
return GSM_PCHAN_SDCCH8_SACCH8C;
else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH4)
return GSM_PCHAN_CCCH_SDCCH4_CBCH;
else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH8)
return GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH)
return GSM_PCHAN_PDCH;
return GSM_PCHAN_NONE;
}
enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr,
uint8_t link_id)
{
int i;
/* Iterate over all known lchan types */
for (i = 0; i < _TRX_CHAN_MAX; i++)
if (trx_lchan_desc[i].chan_nr == (chan_nr & 0xf8))
if (trx_lchan_desc[i].link_id == link_id)
return i;
return TRXC_IDLE;
}
static void sched_trx_a5_burst_dec(struct trx_lchan_state *lchan,
uint32_t fn, sbit_t *burst)
{
ubit_t ks[114];
int i;
/* Generate keystream for a DL burst */
osmo_a5(lchan->a5.algo, lchan->a5.key, fn, ks, NULL);
/* Apply keystream over ciphertext */
for (i = 0; i < 57; i++) {
if (ks[i])
burst[i + 3] *= -1;
if (ks[i + 57])
burst[i + 88] *= -1;
}
}
static void sched_trx_a5_burst_enc(struct trx_lchan_state *lchan,
uint32_t fn, ubit_t *burst)
{
ubit_t ks[114];
int i;
/* Generate keystream for an UL burst */
osmo_a5(lchan->a5.algo, lchan->a5.key, fn, NULL, ks);
/* Apply keystream over plaintext */
for (i = 0; i < 57; i++) {
burst[i + 3] ^= ks[i];
burst[i + 88] ^= ks[i + 57];
}
}
static int subst_frame_loss(struct trx_lchan_state *lchan,
trx_lchan_rx_func *handler,
uint32_t fn)
{
const struct trx_multiframe *mf;
const struct trx_frame *fp;
int elapsed, i;
/* Wait until at least one TDMA frame is processed */
if (lchan->tdma.num_proc == 0)
return -EAGAIN;
/* Short alias for the current multiframe */
mf = lchan->ts->mf_layout;
/* Calculate how many frames elapsed since the last received one.
* The algorithm is based on GSM::FNDelta() from osmo-trx. */
elapsed = fn - lchan->tdma.last_proc;
if (elapsed >= GSM_TDMA_HYPERFRAME / 2)
elapsed -= GSM_TDMA_HYPERFRAME;
else if (elapsed < -GSM_TDMA_HYPERFRAME / 2)
elapsed += GSM_TDMA_HYPERFRAME;
/* Check TDMA frame order (wrong order is possible with fake_trx.py, see OS#4658) */
if (elapsed < 0) {
/* This burst has already been substituted by a dummy burst (all bits set to zero),
* so better drop it. Otherwise we risk to get undefined behavior in handler(). */
LOGP(DSCHD, LOGL_ERROR, "(%s) Rx burst with fn=%u older than the last "
"processed fn=%u (see OS#4658) => dropping\n",
trx_lchan_desc[lchan->type].name,
fn, lchan->tdma.last_proc);
return -EALREADY;
}
/* Check how many frames we (potentially) need to compensate */
if (elapsed > mf->period) {
LOGP(DSCHD, LOGL_NOTICE, "Too many (>%u) contiguous TDMA frames elapsed (%d) "
"since the last processed fn=%u (current %u)\n",
mf->period, elapsed, lchan->tdma.last_proc, fn);
return -EIO;
} else if (elapsed == 0) {
LOGP(DSCHD, LOGL_ERROR, "No TDMA frames elapsed since the last processed "
"fn=%u, must be a bug?\n", lchan->tdma.last_proc);
return -EIO;
}
static const sbit_t bits[148] = { 0 };
struct trx_meas_set fake_meas = {
.fn = lchan->tdma.last_proc,
.rssi = -120,
.toa256 = 0,
};
/* Traverse from fp till the current frame */
for (i = 0; i < elapsed - 1; i++) {
fp = &mf->frames[GSM_TDMA_FN_INC(fake_meas.fn) % mf->period];
if (fp->dl_chan != lchan->type)
continue;
LOGP(DSCHD, LOGL_NOTICE, "Substituting lost TDMA frame %u on %s\n",
fake_meas.fn, trx_lchan_desc[lchan->type].name);
handler(lchan->ts->trx, lchan->ts, lchan,
fake_meas.fn, fp->dl_bid,
bits, &fake_meas);
/* Update TDMA frame statistics */
lchan->tdma.last_proc = fake_meas.fn;
lchan->tdma.num_proc++;
lchan->tdma.num_lost++;
}
return 0;
}
int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
uint32_t fn, sbit_t *bits, uint16_t nbits,
const struct trx_meas_set *meas)
{
struct trx_lchan_state *lchan;
const struct trx_frame *frame;
struct trx_ts *ts;
trx_lchan_rx_func *handler;
enum trx_lchan_type chan;
uint8_t offset, bid;
int rc;
/* Check whether required timeslot is allocated and configured */
ts = trx->ts_list[tn];
if (ts == NULL || ts->mf_layout == NULL) {
LOGP(DSCHD, LOGL_DEBUG, "TDMA timeslot #%u isn't configured, "
"ignoring burst...\n", tn);
return -EINVAL;
}
/* Get frame from multiframe */
offset = fn % ts->mf_layout->period;
frame = ts->mf_layout->frames + offset;
/* Get required info from frame */
bid = frame->dl_bid;
chan = frame->dl_chan;
handler = trx_lchan_desc[chan].rx_fn;
/* Omit bursts which have no handler, like IDLE bursts.
* TODO: handle noise indications during IDLE frames. */
if (!handler)
return -ENODEV;
/* Find required channel state */
lchan = sched_trx_find_lchan(ts, chan);
if (lchan == NULL)
return -ENODEV;
/* Ensure that channel is active */
if (!lchan->active)
return 0;
/* Compensate lost TDMA frames (if any) */
rc = subst_frame_loss(lchan, handler, fn);
if (rc == -EALREADY)
return rc;
/* Perform A5/X decryption if required */
if (lchan->a5.algo)
sched_trx_a5_burst_dec(lchan, fn, bits);
/* Put burst to handler */
handler(trx, ts, lchan, fn, bid, bits, meas);
/* Update TDMA frame statistics */
lchan->tdma.last_proc = fn;
if (++lchan->tdma.num_proc == 0) {
/* Theoretically, we may have an integer overflow of num_proc counter.
* As a consequence, subst_frame_loss() will be unable to compensate
* one (potentionally lost) Downlink burst. On practice, it would
* happen once in 4615 * 10e-6 * (2 ^ 32 - 1) seconds or ~6 years. */
LOGP(DSCHD, LOGL_NOTICE, "Too many TDMA frames have been processed. "
"Are you running trxcon for more than 6 years?!?\n");
lchan->tdma.num_proc = 1;
}
return 0;
}
int sched_trx_handle_tx_burst(struct trx_instance *trx,
struct trx_ts *ts, struct trx_lchan_state *lchan,
uint32_t fn, ubit_t *bits)
{
int rc;
/* Perform A5/X burst encryption if required */
if (lchan->a5.algo)
sched_trx_a5_burst_enc(lchan, fn, bits);
/* Forward burst to transceiver */
rc = trx_if_tx_burst(trx, ts->index, fn, trx->tx_power, bits);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Could not send burst to transceiver\n");
return rc;
}
return 0;
}
#define MEAS_HIST_FIRST(hist) \
(&hist->buf[0])
#define MEAS_HIST_LAST(hist) \
(MEAS_HIST_FIRST(hist) + ARRAY_SIZE(hist->buf) - 1)
/* Add a new set of measurements to the history */
void sched_trx_meas_push(struct trx_lchan_state *lchan, const struct trx_meas_set *meas)
{
struct trx_lchan_meas_hist *hist = &lchan->meas_hist;
/* Find a new position where to store the measurements */
if (hist->head == MEAS_HIST_LAST(hist) || hist->head == NULL)
hist->head = MEAS_HIST_FIRST(hist);
else
hist->head++;
*hist->head = *meas;
}
/* Calculate the AVG of n measurements from the history */
void sched_trx_meas_avg(struct trx_lchan_state *lchan, unsigned int n)
{
struct trx_lchan_meas_hist *hist = &lchan->meas_hist;
struct trx_meas_set *meas = hist->head;
int toa256_sum = 0;
int rssi_sum = 0;
int i;
OSMO_ASSERT(n > 0 && n <= ARRAY_SIZE(hist->buf));
OSMO_ASSERT(meas != NULL);
/* Traverse backwards up to n entries, calculate the sum */
for (i = 0; i < n; i++) {
toa256_sum += meas->toa256;
rssi_sum += meas->rssi;
/* Do not go below the first burst */
if (i + 1 == n)
break;
if (meas == MEAS_HIST_FIRST(hist))
meas = MEAS_HIST_LAST(hist);
else
meas--;
}
/* Calculate the AVG */
lchan->meas_avg.toa256 = toa256_sum / n;
lchan->meas_avg.rssi = rssi_sum / n;
/* As a bonus, store TDMA frame number of the first burst */
lchan->meas_avg.fn = meas->fn;
}

405
trxcon/sched_trx.h Normal file
View File

@@ -0,0 +1,405 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/core/linuxlist.h>
#include "logging.h"
#include "scheduler.h"
#define GSM_BURST_LEN 148
#define GSM_BURST_PL_LEN 116
#define GPRS_BURST_LEN GSM_BURST_LEN
#define EDGE_BURST_LEN 444
#define GPRS_L2_MAX_LEN 54
#define EDGE_L2_MAX_LEN 155
#define TRX_CH_LID_DEDIC 0x00
#define TRX_CH_LID_SACCH 0x40
/* Osmocom-specific extension for PTCCH (see 3GPP TS 45.002, section 3.3.4.2).
* Shall be used to distinguish PTCCH and PDTCH channels on a PDCH time-slot. */
#define TRX_CH_LID_PTCCH 0x80
/* Is a channel related to PDCH (GPRS) */
#define TRX_CH_FLAG_PDCH (1 << 0)
/* Should a channel be activated automatically */
#define TRX_CH_FLAG_AUTO (1 << 1)
/* Is continuous burst transmission assumed */
#define TRX_CH_FLAG_CBTX (1 << 2)
#define MAX_A5_KEY_LEN (128 / 8)
#define TRX_TS_COUNT 8
/* Forward declaration to avoid mutual include */
struct trx_lchan_state;
struct trx_meas_set;
struct trx_instance;
struct trx_ts;
enum trx_burst_type {
TRX_BURST_GMSK,
TRX_BURST_8PSK,
};
/**
* These types define the different channels on a multiframe.
* Each channel has queues and can be activated individually.
*/
enum trx_lchan_type {
TRXC_IDLE = 0,
TRXC_FCCH,
TRXC_SCH,
TRXC_BCCH,
TRXC_RACH,
TRXC_CCCH,
TRXC_TCHF,
TRXC_TCHH_0,
TRXC_TCHH_1,
TRXC_SDCCH4_0,
TRXC_SDCCH4_1,
TRXC_SDCCH4_2,
TRXC_SDCCH4_3,
TRXC_SDCCH8_0,
TRXC_SDCCH8_1,
TRXC_SDCCH8_2,
TRXC_SDCCH8_3,
TRXC_SDCCH8_4,
TRXC_SDCCH8_5,
TRXC_SDCCH8_6,
TRXC_SDCCH8_7,
TRXC_SACCHTF,
TRXC_SACCHTH_0,
TRXC_SACCHTH_1,
TRXC_SACCH4_0,
TRXC_SACCH4_1,
TRXC_SACCH4_2,
TRXC_SACCH4_3,
TRXC_SACCH8_0,
TRXC_SACCH8_1,
TRXC_SACCH8_2,
TRXC_SACCH8_3,
TRXC_SACCH8_4,
TRXC_SACCH8_5,
TRXC_SACCH8_6,
TRXC_SACCH8_7,
TRXC_PDTCH,
TRXC_PTCCH,
TRXC_SDCCH4_CBCH,
TRXC_SDCCH8_CBCH,
_TRX_CHAN_MAX
};
typedef int trx_lchan_rx_func(struct trx_instance *trx,
struct trx_ts *ts, struct trx_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
const struct trx_meas_set *meas);
typedef int trx_lchan_tx_func(struct trx_instance *trx,
struct trx_ts *ts, struct trx_lchan_state *lchan,
uint32_t fn, uint8_t bid);
struct trx_lchan_desc {
/*! \brief Human-readable name */
const char *name;
/*! \brief Human-readable description */
const char *desc;
/*! \brief Channel Number (like in RSL) */
uint8_t chan_nr;
/*! \brief Link ID (like in RSL) */
uint8_t link_id;
/*! \brief Sub-slot number (for SDCCH and TCH/H) */
uint8_t ss_nr;
/*! \brief GSMTAP channel type (see GSMTAP_CHANNEL_*) */
uint8_t gsmtap_chan_type;
/*! \brief How much memory do we need to store bursts */
size_t burst_buf_size;
/*! \brief Channel specific flags */
uint8_t flags;
/*! \brief Function to call when burst received from PHY */
trx_lchan_rx_func *rx_fn;
/*! \brief Function to call when data received from L2 */
trx_lchan_tx_func *tx_fn;
};
struct trx_frame {
/*! \brief Downlink TRX channel type */
enum trx_lchan_type dl_chan;
/*! \brief Downlink block ID */
uint8_t dl_bid;
/*! \brief Uplink TRX channel type */
enum trx_lchan_type ul_chan;
/*! \brief Uplink block ID */
uint8_t ul_bid;
};
struct trx_multiframe {
/*! \brief Channel combination */
enum gsm_phys_chan_config chan_config;
/*! \brief Human-readable name */
const char *name;
/*! \brief Repeats how many frames */
uint8_t period;
/*! \brief Applies to which timeslots */
uint8_t slotmask;
/*! \brief Contains which lchans */
uint64_t lchan_mask;
/*! \brief Pointer to scheduling structure */
const struct trx_frame *frames;
};
struct trx_meas_set {
/*! \brief TDMA frame number of the first burst this set belongs to */
uint32_t fn;
/*! \brief ToA256 (Timing of Arrival, 1/256 of a symbol) */
int16_t toa256;
/*! \brief RSSI (Received Signal Strength Indication) */
int8_t rssi;
};
/* Simple ring buffer (up to 8 unique measurements) */
struct trx_lchan_meas_hist {
struct trx_meas_set buf[8];
struct trx_meas_set *head;
};
/* States each channel on a multiframe */
struct trx_lchan_state {
/*! \brief Channel type */
enum trx_lchan_type type;
/*! \brief Channel status */
uint8_t active;
/*! \brief Link to a list of channels */
struct llist_head list;
/*! \brief Burst type: GMSK or 8PSK */
enum trx_burst_type burst_type;
/*! \brief Mask of received bursts */
uint8_t rx_burst_mask;
/*! \brief Mask of transmitted bursts */
uint8_t tx_burst_mask;
/*! \brief Burst buffer for RX */
sbit_t *rx_bursts;
/*! \brief Burst buffer for TX */
ubit_t *tx_bursts;
/*! \brief A primitive being sent */
struct trx_ts_prim *prim;
/*! \brief Mode for TCH channels (see GSM48_CMODE_*) */
uint8_t tch_mode;
/*! \brief FACCH/H on downlink */
bool dl_ongoing_facch;
/*! \brief pending FACCH/H blocks on Uplink */
uint8_t ul_facch_blocks;
/*! \brief Downlink measurements history */
struct trx_lchan_meas_hist meas_hist;
/*! \brief AVG measurements of the last received block */
struct trx_meas_set meas_avg;
/*! \brief TDMA loss detection state */
struct {
/*! \brief Last processed TDMA frame number */
uint32_t last_proc;
/*! \brief Number of processed TDMA frames */
unsigned long num_proc;
/*! \brief Number of lost TDMA frames */
unsigned long num_lost;
} tdma;
/*! \brief SACCH state */
struct {
/*! \brief Cached measurement report (last received) */
uint8_t mr_cache[GSM_MACBLOCK_LEN];
/*! \brief Cache usage counter */
uint8_t mr_cache_usage;
/*! \brief Was a MR transmitted last time? */
bool mr_tx_last;
} sacch;
/* AMR specific */
struct {
/*! \brief 4 possible codecs for AMR */
uint8_t codec[4];
/*! \brief Number of possible codecs */
uint8_t codecs;
/*! \brief Current uplink FT index */
uint8_t ul_ft;
/*! \brief Current downlink FT index */
uint8_t dl_ft;
/*! \brief Current uplink CMR index */
uint8_t ul_cmr;
/*! \brief Current downlink CMR index */
uint8_t dl_cmr;
/*! \brief If AMR loop is enabled */
uint8_t amr_loop;
/*! \brief Number of bit error rates */
uint8_t ber_num;
/*! \brief Sum of bit error rates */
float ber_sum;
} amr;
/*! \brief A5/X encryption state */
struct {
uint8_t key[MAX_A5_KEY_LEN];
uint8_t key_len;
uint8_t algo;
} a5;
/* TS that this lchan belongs to */
struct trx_ts *ts;
};
struct trx_ts {
/*! \brief Timeslot index within a frame (0..7) */
uint8_t index;
/*! \brief Pointer to multiframe layout */
const struct trx_multiframe *mf_layout;
/*! \brief Channel states for logical channels */
struct llist_head lchans;
/*! \brief Queue primitives for TX */
struct llist_head tx_prims;
/* backpointer to its TRX */
struct trx_instance *trx;
};
/* Represents one TX primitive in the queue of trx_ts */
struct trx_ts_prim {
/*! \brief Link to queue of TS */
struct llist_head list;
/*! \brief Logical channel type */
enum trx_lchan_type chan;
/*! \brief Payload length */
size_t payload_len;
/*! \brief Payload */
uint8_t payload[0];
};
extern const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX];
const struct trx_multiframe *sched_mframe_layout(
enum gsm_phys_chan_config config, int tn);
/* Scheduler management functions */
int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance);
int sched_trx_reset(struct trx_instance *trx, bool reset_clock);
int sched_trx_shutdown(struct trx_instance *trx);
/* Timeslot management functions */
struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn);
void sched_trx_del_ts(struct trx_instance *trx, int tn);
int sched_trx_reset_ts(struct trx_instance *trx, int tn);
int sched_trx_configure_ts(struct trx_instance *trx, int tn,
enum gsm_phys_chan_config config);
int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo,
uint8_t *key, uint8_t key_len);
/* Logical channel management functions */
enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr);
enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr,
uint8_t link_id);
void sched_trx_deactivate_all_lchans(struct trx_ts *ts);
int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode);
int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan);
int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan);
struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts,
enum trx_lchan_type chan);
/* Primitive management functions */
int sched_prim_init(void *ctx, struct trx_ts_prim **prim,
size_t pl_len, uint8_t chan_nr, uint8_t link_id);
int sched_prim_push(struct trx_instance *trx,
struct trx_ts_prim *prim, uint8_t chan_nr);
#define TCH_MODE_IS_SPEECH(mode) \
(mode == GSM48_CMODE_SPEECH_V1 \
|| mode == GSM48_CMODE_SPEECH_EFR \
|| mode == GSM48_CMODE_SPEECH_AMR)
#define TCH_MODE_IS_DATA(mode) \
(mode == GSM48_CMODE_DATA_14k5 \
|| mode == GSM48_CMODE_DATA_12k0 \
|| mode == GSM48_CMODE_DATA_6k0 \
|| mode == GSM48_CMODE_DATA_3k6)
#define CHAN_IS_TCH(chan) \
(chan == TRXC_TCHF || chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1)
#define CHAN_IS_SACCH(chan) \
(trx_lchan_desc[chan].link_id & TRX_CH_LID_SACCH)
/* FIXME: we need a better way to identify / distinguish primitives */
#define PRIM_IS_RACH11(prim) \
(prim->payload_len == sizeof(struct l1ctl_ext_rach_req))
#define PRIM_IS_RACH8(prim) \
(prim->payload_len == sizeof(struct l1ctl_rach_req))
#define PRIM_IS_RACH(prim) \
(PRIM_IS_RACH8(prim) || PRIM_IS_RACH11(prim))
#define PRIM_IS_TCH(prim) \
(CHAN_IS_TCH(prim->chan) && prim->payload_len != GSM_MACBLOCK_LEN)
#define PRIM_IS_FACCH(prim) \
(CHAN_IS_TCH(prim->chan) && prim->payload_len == GSM_MACBLOCK_LEN)
struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue,
uint32_t fn, struct trx_lchan_state *lchan);
int sched_prim_dummy(struct trx_lchan_state *lchan);
void sched_prim_drop(struct trx_lchan_state *lchan);
void sched_prim_flush_queue(struct llist_head *list);
int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
uint32_t fn, sbit_t *bits, uint16_t nbits,
const struct trx_meas_set *meas);
int sched_trx_handle_tx_burst(struct trx_instance *trx,
struct trx_ts *ts, struct trx_lchan_state *lchan,
uint32_t fn, ubit_t *bits);
/* Shared declarations for lchan handlers */
extern const uint8_t sched_nb_training_bits[8][26];
const char *burst_mask2str(const uint8_t *mask, int bits);
size_t sched_bad_frame_ind(uint8_t *l2, struct trx_lchan_state *lchan);
int sched_send_dt_ind(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint8_t *l2, size_t l2_len,
int bit_error_count, bool dec_failed, bool traffic);
int sched_send_dt_conf(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, bool traffic);
int sched_gsmtap_send(enum trx_lchan_type lchan_type, uint32_t fn, uint8_t tn,
uint16_t band_arfcn, int8_t signal_dbm, uint8_t snr,
const uint8_t *data, size_t data_len);
/* Interleaved TCH/H block TDMA frame mapping */
uint32_t sched_tchh_block_dl_first_fn(enum trx_lchan_type chan,
uint32_t last_fn, bool facch);
bool sched_tchh_block_map_fn(enum trx_lchan_type chan,
uint32_t fn, bool ul, bool facch, bool start);
#define sched_tchh_traffic_start(chan, fn, ul) \
sched_tchh_block_map_fn(chan, fn, ul, 0, 1)
#define sched_tchh_traffic_end(chan, fn, ul) \
sched_tchh_block_map_fn(chan, fn, ul, 0, 0)
#define sched_tchh_facch_start(chan, fn, ul) \
sched_tchh_block_map_fn(chan, fn, ul, 1, 1)
#define sched_tchh_facch_end(chan, fn, ul) \
sched_tchh_block_map_fn(chan, fn, ul, 1, 0)
/* Measurement history */
void sched_trx_meas_push(struct trx_lchan_state *lchan, const struct trx_meas_set *meas);
void sched_trx_meas_avg(struct trx_lchan_state *lchan, unsigned int n);

38
trxcon/scheduler.h Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
#include <stdint.h>
#include <time.h>
#include <osmocom/core/timer.h>
#include <osmocom/gsm/gsm0502.h>
enum tdma_sched_clck_state {
SCH_CLCK_STATE_WAIT,
SCH_CLCK_STATE_OK,
};
/* Forward structure declaration */
struct trx_sched;
/*! \brief One scheduler instance */
struct trx_sched {
/*! \brief Clock state */
enum tdma_sched_clck_state state;
/*! \brief Local clock source */
struct timespec clock;
/*! \brief Count of processed frames */
uint32_t fn_counter_proc;
/*! \brief Local frame counter advance */
uint32_t fn_counter_advance;
/*! \brief Count of lost frames */
uint32_t fn_counter_lost;
/*! \brief Frame callback timer */
struct osmo_timer_list clock_timer;
/*! \brief Frame callback */
void (*clock_cb)(struct trx_sched *sched);
/*! \brief Private data (e.g. pointer to trx instance) */
void *data;
};
int sched_clck_handle(struct trx_sched *sched, uint32_t fn);
void sched_clck_reset(struct trx_sched *sched);

826
trxcon/trx_if.c Normal file
View File

@@ -0,0 +1,826 @@
/*
* OsmocomBB <-> SDR connection bridge
* Transceiver interface handlers
*
* Copyright (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
* Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <errno.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/eventfd.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/fsm.h>
#include <osmocom/gsm/gsm_utils.h>
#include "l1ctl.h"
#include "trxcon.h"
#include "trx_if.h"
#include "logging.h"
#include "scheduler.h"
#ifdef IPCIF
#include "../Transceiver52M/l1if.h"
#endif
static struct value_string trx_evt_names[] = {
{ 0, NULL } /* no events? */
};
static struct osmo_fsm_state trx_fsm_states[] = {
[TRX_STATE_OFFLINE] = {
.out_state_mask = (
GEN_MASK(TRX_STATE_IDLE) |
GEN_MASK(TRX_STATE_RSP_WAIT)),
.name = "OFFLINE",
},
[TRX_STATE_IDLE] = {
.out_state_mask = UINT32_MAX,
.name = "IDLE",
},
[TRX_STATE_ACTIVE] = {
.out_state_mask = (
GEN_MASK(TRX_STATE_IDLE) |
GEN_MASK(TRX_STATE_RSP_WAIT)),
.name = "ACTIVE",
},
[TRX_STATE_RSP_WAIT] = {
.out_state_mask = (
GEN_MASK(TRX_STATE_IDLE) |
GEN_MASK(TRX_STATE_ACTIVE) |
GEN_MASK(TRX_STATE_OFFLINE)),
.name = "RSP_WAIT",
},
};
static struct osmo_fsm trx_fsm = {
.name = "trx_interface_fsm",
.states = trx_fsm_states,
.num_states = ARRAY_SIZE(trx_fsm_states),
.log_subsys = DTRX,
.event_names = trx_evt_names,
};
static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local,
uint16_t port_local, const char *host_remote, uint16_t port_remote,
int (*cb)(struct osmo_fd *fd, unsigned int what))
{
int rc;
ofd->data = priv;
ofd->fd = -1;
ofd->cb = cb;
/* Init UDP Connection */
rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, host_local, port_local,
host_remote, port_remote,
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
return rc;
}
static void trx_udp_close(struct osmo_fd *ofd)
{
if (ofd->fd > 0) {
osmo_fd_unregister(ofd);
close(ofd->fd);
ofd->fd = -1;
}
}
/* ------------------------------------------------------------------------ */
/* Control (CTRL) interface handlers */
/* ------------------------------------------------------------------------ */
/* Commands on the Per-ARFCN Control Interface */
/* */
/* The per-ARFCN control interface uses a command-response protocol. */
/* Commands are NULL-terminated ASCII strings, one per UDP socket. */
/* Each command has a corresponding response. */
/* Every command is of the form: */
/* */
/* CMD <cmdtype> [params] */
/* */
/* The <cmdtype> is the actual command. */
/* Parameters are optional depending on the commands type. */
/* Every response is of the form: */
/* */
/* RSP <cmdtype> <status> [result] */
/* */
/* The <status> is 0 for success and a non-zero error code for failure. */
/* Successful responses may include results, depending on the command type. */
/* ------------------------------------------------------------------------ */
static void trx_ctrl_timer_cb(void *data);
/* Send first CTRL message and start timer */
static void trx_ctrl_send(struct trx_instance *trx)
{
struct trx_ctrl_msg *tcm;
if (llist_empty(&trx->trx_ctrl_list))
return;
tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
#ifdef IPCIF
char* cmd = malloc(TRXC_BUF_SIZE);
memcpy(cmd, tcm->cmd, TRXC_BUF_SIZE);
/* Send command */
LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
trxif_to_trx_c(cmd);
#else
/* Send command */
LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0);
#endif
/* Trigger state machine */
if (trx->fsm->state != TRX_STATE_RSP_WAIT) {
trx->prev_state = trx->fsm->state;
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_RSP_WAIT, 0, 0);
}
/* Start expire timer */
trx->trx_ctrl_timer.data = trx;
trx->trx_ctrl_timer.cb = trx_ctrl_timer_cb;
osmo_timer_schedule(&trx->trx_ctrl_timer, 2, 0);
}
static void trx_ctrl_timer_cb(void *data)
{
struct trx_instance *trx = (struct trx_instance *) data;
struct trx_ctrl_msg *tcm;
/* Queue may be cleaned at this moment */
if (llist_empty(&trx->trx_ctrl_list))
return;
LOGP(DTRX, LOGL_NOTICE, "No response from transceiver...\n");
tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
if (++tcm->retry_cnt > 3) {
LOGP(DTRX, LOGL_NOTICE, "Transceiver offline\n");
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_OFFLINE, 0, 0);
osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_OFFLINE, trx);
return;
}
/* Attempt to send a command again */
trx_ctrl_send(trx);
}
/* Add a new CTRL command to the trx_ctrl_list */
static int trx_ctrl_cmd(struct trx_instance *trx, int critical,
const char *cmd, const char *fmt, ...)
{
struct trx_ctrl_msg *tcm;
int len, pending = 0;
va_list ap;
/* TODO: make sure that transceiver online */
if (!llist_empty(&trx->trx_ctrl_list))
pending = 1;
/* Allocate a message */
tcm = talloc_zero(trx, struct trx_ctrl_msg);
if (!tcm)
return -ENOMEM;
/* Fill in command arguments */
if (fmt && fmt[0]) {
len = snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s ", cmd);
va_start(ap, fmt);
vsnprintf(tcm->cmd + len, sizeof(tcm->cmd) - len - 1, fmt, ap);
va_end(ap);
} else {
snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s", cmd);
}
tcm->cmd_len = strlen(cmd);
tcm->critical = critical;
llist_add_tail(&tcm->list, &trx->trx_ctrl_list);
LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd);
/* Send message, if no pending messages */
if (!pending)
trx_ctrl_send(trx);
return 0;
}
/*
* Power Control
*
* ECHO is used to check transceiver availability.
* CMD ECHO
* RSP ECHO <status>
*
* POWEROFF shuts off transmitter power and stops the demodulator.
* CMD POWEROFF
* RSP POWEROFF <status>
*
* POWERON starts the transmitter and starts the demodulator.
* Initial power level is very low.
* This command fails if the transmitter and receiver are not yet tuned.
* This command fails if the transmit or receive frequency creates a conflict
* with another ARFCN that is already running.
* If the transceiver is already on, it response with success to this command.
* CMD POWERON
* RSP POWERON <status>
*/
int trx_if_cmd_sync(struct trx_instance *trx)
{
return trx_ctrl_cmd(trx, 1, "SYNC", "");
}
int trx_if_cmd_echo(struct trx_instance *trx)
{
return trx_ctrl_cmd(trx, 1, "ECHO", "");
}
int trx_if_cmd_poweroff(struct trx_instance *trx)
{
return trx_ctrl_cmd(trx, 1, "POWEROFF", "");
}
int trx_if_cmd_poweron(struct trx_instance *trx)
{
if (trx->powered_up) {
/* FIXME: this should be handled by the FSM, not here! */
LOGP(DTRX, LOGL_ERROR, "Suppressing POWERON as we're already powered up\n");
return -EAGAIN;
}
return trx_ctrl_cmd(trx, 1, "POWERON", "");
}
/*
* Timeslot Control
*
* SETSLOT sets the format of the uplink timeslots in the ARFCN.
* The <timeslot> indicates the timeslot of interest.
* The <chantype> indicates the type of channel that occupies the timeslot.
* A chantype of zero indicates the timeslot is off.
* CMD SETSLOT <timeslot> <chantype>
* RSP SETSLOT <status> <timeslot> <chantype>
*/
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type)
{
return trx_ctrl_cmd(trx, 1, "SETSLOT", "%u %u", tn, type);
}
/*
* Tuning Control
*
* (RX/TX)TUNE tunes the receiver to a given frequency in kHz.
* This command fails if the receiver is already running.
* (To re-tune you stop the radio, re-tune, and restart.)
* This command fails if the transmit or receive frequency
* creates a conflict with another ARFCN that is already running.
* CMD (RX/TX)TUNE <kHz>
* RSP (RX/TX)TUNE <status> <kHz>
*/
int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn)
{
uint16_t freq10;
/* RX is downlink on MS side */
freq10 = gsm_arfcn2freq10(band_arfcn, 0);
if (freq10 == 0xffff) {
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
return -ENOTSUP;
}
return trx_ctrl_cmd(trx, 1, "RXTUNE", "%u", freq10 * 100);
}
int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn)
{
uint16_t freq10;
/* TX is uplink on MS side */
freq10 = gsm_arfcn2freq10(band_arfcn, 1);
if (freq10 == 0xffff) {
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
return -ENOTSUP;
}
return trx_ctrl_cmd(trx, 1, "TXTUNE", "%u", freq10 * 100);
}
/*
* Power measurement
*
* MEASURE instructs the transceiver to perform a power
* measurement on specified frequency. After receiving this
* request, transceiver should quickly re-tune to requested
* frequency, measure power level and re-tune back to the
* previous frequency.
* CMD MEASURE <kHz>
* RSP MEASURE <status> <kHz> <dB>
*/
int trx_if_cmd_measure(struct trx_instance *trx,
uint16_t band_arfcn_start, uint16_t band_arfcn_stop)
{
uint16_t freq10;
/* Update ARFCN range for measurement */
trx->pm_band_arfcn_start = band_arfcn_start;
trx->pm_band_arfcn_stop = band_arfcn_stop;
/* Calculate a frequency for current ARFCN (DL) */
freq10 = gsm_arfcn2freq10(band_arfcn_start, 0);
if (freq10 == 0xffff) {
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn_start);
return -ENOTSUP;
}
return trx_ctrl_cmd(trx, 1, "MEASURE", "%u", freq10 * 100);
}
static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp)
{
unsigned int freq10;
uint16_t band_arfcn;
int dbm;
/* Parse freq. and power level */
sscanf(resp, "%u %d", &freq10, &dbm);
freq10 /= 100;
/* Check received ARFCN against expected */
band_arfcn = gsm_freq102arfcn((uint16_t) freq10, 0);
if (band_arfcn != trx->pm_band_arfcn_start) {
LOGP(DTRX, LOGL_ERROR, "Power measurement error: "
"response ARFCN=%u doesn't match expected ARFCN=%u\n",
band_arfcn &~ ARFCN_FLAG_MASK,
trx->pm_band_arfcn_start &~ ARFCN_FLAG_MASK);
return;
}
/* Send L1CTL_PM_CONF */
l1ctl_tx_pm_conf(trx->l1l, band_arfcn, dbm,
band_arfcn == trx->pm_band_arfcn_stop);
/* Schedule a next measurement */
if (band_arfcn != trx->pm_band_arfcn_stop)
trx_if_cmd_measure(trx, ++band_arfcn, trx->pm_band_arfcn_stop);
}
/*
* Timing Advance control
*
* SETTA instructs the transceiver to transmit bursts in
* advance calculated from requested TA value. This value is
* normally between 0 and 63, with each step representing
* an advance of one bit period (about 3.69 microseconds).
* Since OsmocomBB has a special feature, which allows one
* to spoof the distance from BTS, the range is extended.
* CMD SETTA <-128..127>
* RSP SETTA <status> <TA>
*/
int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta)
{
return trx_ctrl_cmd(trx, 0, "SETTA", "%d", ta);
}
/*
* Frequency Hopping parameters indication.
*
* SETFH instructs transceiver to enable frequency hopping mode
* using the given HSN, MAIO, and Mobile Allocation parameters.
*
* CMD SETFH <HSN> <MAIO> <RXF1> <TXF1> [... <RXFN> <TXFN>]
*
* where <RXFN> and <TXFN> is a pair of Rx/Tx frequencies (in kHz)
* corresponding to one ARFCN the Mobile Allocation. Note that the
* channel list is expected to be sorted in ascending order.
*/
int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
uint8_t maio, uint16_t *ma, size_t ma_len)
{
/* Reserve some room for CMD SETFH <HSN> <MAIO> */
char ma_buf[TRXC_BUF_SIZE - 24];
size_t ma_buf_len = sizeof(ma_buf) - 1;
uint16_t rx_freq, tx_freq;
char *ptr;
int i, rc;
/* Make sure that Mobile Allocation has at least one ARFCN */
if (!ma_len || ma == NULL) {
LOGP(DTRX, LOGL_ERROR, "Mobile Allocation is empty?!?\n");
return -EINVAL;
}
/* Compose a sequence of Rx/Tx frequencies (mobile allocation) */
for (i = 0, ptr = ma_buf; i < ma_len; i++) {
/* Convert ARFCN to a pair of Rx/Tx frequencies (Hz * 10) */
rx_freq = gsm_arfcn2freq10(ma[i], 0); /* Rx: Downlink */
tx_freq = gsm_arfcn2freq10(ma[i], 1); /* Tx: Uplink */
if (rx_freq == 0xffff || tx_freq == 0xffff) {
LOGP(DTRX, LOGL_ERROR, "Failed to convert ARFCN %u "
"to a pair of Rx/Tx frequencies\n",
ma[i] & ~ARFCN_FLAG_MASK);
return -EINVAL;
}
/* Append a pair of Rx/Tx frequencies (in kHz) to the buffer */
rc = snprintf(ptr, ma_buf_len, "%u %u ", rx_freq * 100, tx_freq * 100);
if (rc < 0 || rc > ma_buf_len) { /* Prevent buffer overflow */
LOGP(DTRX, LOGL_ERROR, "Not enough room to encode "
"Mobile Allocation (N=%zu)\n", ma_len);
return -ENOSPC;
}
/* Move pointer */
ma_buf_len -= rc;
ptr += rc;
}
/* Overwrite the last space */
*(ptr - 1) = '\0';
return trx_ctrl_cmd(trx, 1, "SETFH", "%u %u %s", hsn, maio, ma_buf);
}
/* Get response from CTRL socket */
static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
{
struct trx_instance *trx = ofd->data;
struct trx_ctrl_msg *tcm;
int resp, rsp_len;
char buf[TRXC_BUF_SIZE], *p;
ssize_t read_len;
#ifdef IPCIF
char* response = trxif_from_trx_c();
if (!response) {
LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", response);
goto rsp_error;
}
memcpy(buf, response, TRXC_BUF_SIZE);
free(response);
#else
read_len = read(ofd->fd, buf, sizeof(buf) - 1);
if (read_len <= 0) {
LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
return read_len;
}
buf[read_len] = '\0';
#endif
if (!!strncmp(buf, "RSP ", 4)) {
LOGP(DTRX, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf);
return 0;
}
/* Calculate the length of response item */
p = strchr(buf + 4, ' ');
rsp_len = p ? p - buf - 4 : strlen(buf) - 4;
LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf);
/* Abort expire timer */
if (osmo_timer_pending(&trx->trx_ctrl_timer))
osmo_timer_del(&trx->trx_ctrl_timer);
/* Get command for response message */
if (llist_empty(&trx->trx_ctrl_list)) {
LOGP(DTRX, LOGL_NOTICE, "Response message without command\n");
return -EINVAL;
}
tcm = llist_entry(trx->trx_ctrl_list.next,
struct trx_ctrl_msg, list);
/* Check if response matches command */
if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) {
LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
"Response message '%s' does not match command "
"message '%s'\n", buf, tcm->cmd);
goto rsp_error;
}
/* Check for response code */
sscanf(p + 1, "%d", &resp);
if (resp) {
LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
"Transceiver rejected TRX command with "
"response: '%s'\n", buf);
if (tcm->critical)
goto rsp_error;
}
/* Trigger state machine */
if (!strncmp(tcm->cmd + 4, "POWERON", 7)) {
trx->powered_up = true;
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_ACTIVE, 0, 0);
}
else if (!strncmp(tcm->cmd + 4, "POWEROFF", 8)) {
trx->powered_up = false;
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
}
else if (!strncmp(tcm->cmd + 4, "MEASURE", 7))
trx_if_measure_rsp_cb(trx, buf + 14);
else if (!strncmp(tcm->cmd + 4, "ECHO", 4))
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
else
osmo_fsm_inst_state_chg(trx->fsm, trx->prev_state, 0, 0);
/* Remove command from list */
llist_del(&tcm->list);
talloc_free(tcm);
/* Send next message, if any */
trx_ctrl_send(trx);
return 0;
rsp_error:
/* Notify higher layers about the problem */
osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_RSP_ERROR, trx);
return -EIO;
}
/* ------------------------------------------------------------------------ */
/* Data interface handlers */
/* ------------------------------------------------------------------------ */
/* DATA interface */
/* */
/* Messages on the data interface carry one radio burst per UDP message. */
/* */
/* Received Data Burst: */
/* 1 byte timeslot index */
/* 4 bytes GSM frame number, BE */
/* 1 byte RSSI in -dBm */
/* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE */
/* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1" */
/* 2 bytes are not used, but being sent by OsmoTRX */
/* */
/* Transmit Data Burst: */
/* 1 byte timeslot index */
/* 4 bytes GSM frame number, BE */
/* 1 byte transmit level wrt ARFCN max, -dB (attenuation) */
/* 148 bytes output symbol values, 0 & 1 */
/* ------------------------------------------------------------------------ */
static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
{
struct trx_instance *trx = ofd->data;
uint8_t buf[TRXD_BUF_SIZE];
ssize_t read_len;
read_len = read(ofd->fd, buf, sizeof(buf));
if (read_len <= 0) {
LOGP(DTRXD, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
return read_len;
}
if (read_len != 158) {
LOGP(DTRXD, LOGL_ERROR,
"Got data message with invalid "
"length '%zd'\n",
read_len);
return -EINVAL;
}
return trx_data_rx_handler(trx, buf);
}
int trx_data_rx_handler(struct trx_instance *trx, uint8_t *buf)
{
struct trx_meas_set meas;
sbit_t bits[148];
int8_t rssi, tn;
int16_t toa256;
uint32_t fn;
tn = buf[0];
fn = osmo_load32be(buf + 1);
rssi = -(int8_t)buf[5];
toa256 = ((int16_t)(buf[6] << 8) | buf[7]);
/* Copy and convert bits {254..0} to sbits {-127..127} */
//osmo_ubit2sbit(bits, buf + 8, 148);
memcpy(bits, buf + 8, 148);
if (tn >= 8) {
LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn);
return -EINVAL;
}
if (fn >= 2715648) {
LOGP(DTRXD, LOGL_ERROR, "Illegal FN %u\n", fn);
return -EINVAL;
}
LOGP(DTRXD, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%d\n",
tn, fn, rssi, toa256);
/* Group the measurements together */
meas = (struct trx_meas_set) {
.toa256 = toa256,
.rssi = rssi,
.fn = fn,
};
/* Poke scheduler */
sched_trx_handle_rx_burst(trx, tn, fn, bits, 148, &meas);
/* Correct local clock counter */
if (fn % 51 == 0)
sched_clck_handle(&trx->sched, fn);
return 0;
}
extern void tx_external_transceiver(uint8_t *burst) __attribute__((weak));
int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
uint8_t pwr, const ubit_t *bits)
{
#ifdef IPCIF
struct trxd_to_trx* t = malloc(sizeof(struct trxd_to_trx));
t->ts = tn;
t->fn = fn;
t->txlev = pwr;
memcpy(t->symbols, bits, 148);
trxif_to_trx_d(t);
#else
uint8_t buf[TRXD_BUF_SIZE];
/**
* We must be sure that we have clock,
* and we have sent all control data
*
* TODO: introduce proper state machines for both
* transceiver and its TRXC interface.
*/
#if 0
if (trx->fsm->state != TRX_STATE_ACTIVE) {
LOGP(DTRXD, LOGL_ERROR, "Ignoring TX data, "
"transceiver isn't ready\n");
return -EAGAIN;
}
#endif
LOGP(DTRXD, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr);
buf[0] = tn;
osmo_store32be(fn, buf + 1);
buf[5] = pwr;
/* Copy ubits {0,1} */
memcpy(buf + 6, bits, 148);
/* Send data to transceiver */
if (tx_external_transceiver)
tx_external_transceiver(buf);
else
send(trx->trx_ofd_data.fd, buf, 154, 0);
#endif
return 0;
}
/* Init TRX interface (TRXC, TRXD sockets and FSM) */
struct trx_instance *trx_if_open(void *tall_ctx,
const char *local_host, const char *remote_host,
uint16_t base_port)
{
struct trx_instance *trx;
int rc;
LOGP(DTRX, LOGL_NOTICE, "Init transceiver interface "
"(%s:%u)\n", remote_host, base_port);
/* Try to allocate memory */
trx = talloc_zero(tall_ctx, struct trx_instance);
if (!trx) {
LOGP(DTRX, LOGL_ERROR, "Failed to allocate memory\n");
return NULL;
}
/* Allocate a new dedicated state machine */
trx->fsm = osmo_fsm_inst_alloc(&trx_fsm, trx,
NULL, LOGL_DEBUG, "trx_interface");
if (trx->fsm == NULL) {
LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance "
"of FSM '%s'\n", trx_fsm.name);
talloc_free(trx);
return NULL;
}
/* Initialize CTRL queue */
INIT_LLIST_HEAD(&trx->trx_ctrl_list);
#ifdef IPCIF
rc = eventfd(0, 0);
osmo_fd_setup(get_c_fd(), rc, OSMO_FD_READ, trx_ctrl_read_cb, trx, 0);
osmo_fd_register(get_c_fd());
rc = eventfd(0, 0);
osmo_fd_setup(get_d_fd(), rc, OSMO_FD_READ, trx_data_rx_cb, trx, 0);
osmo_fd_register(get_d_fd());
#else
/* Open sockets */
rc = trx_udp_open(trx, &trx->trx_ofd_ctrl, local_host, base_port + 101, remote_host, base_port + 1,
trx_ctrl_read_cb);
if (rc < 0)
goto udp_error;
rc = trx_udp_open(trx, &trx->trx_ofd_data, local_host, base_port + 102, remote_host, base_port + 2,
trx_data_rx_cb);
if (rc < 0)
goto udp_error;
#endif
return trx;
udp_error:
LOGP(DTRX, LOGL_ERROR, "Couldn't establish UDP connection\n");
osmo_fsm_inst_free(trx->fsm);
talloc_free(trx);
return NULL;
}
/* Flush pending control messages */
void trx_if_flush_ctrl(struct trx_instance *trx)
{
struct trx_ctrl_msg *tcm;
/* Reset state machine */
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
/* Clear command queue */
while (!llist_empty(&trx->trx_ctrl_list)) {
tcm = llist_entry(trx->trx_ctrl_list.next,
struct trx_ctrl_msg, list);
llist_del(&tcm->list);
talloc_free(tcm);
}
}
void trx_if_close(struct trx_instance *trx)
{
/* May be unallocated due to init error */
if (!trx)
return;
LOGP(DTRX, LOGL_NOTICE, "Shutdown transceiver interface\n");
/* Flush CTRL message list */
trx_if_flush_ctrl(trx);
/* Close sockets */
#ifdef IPCIF
close(get_c_fd()->fd);
close(get_d_fd()->fd);
#else
trx_udp_close(&trx->trx_ofd_ctrl);
trx_udp_close(&trx->trx_ofd_data);
#endif
/* Free memory */
osmo_fsm_inst_free(trx->fsm);
talloc_free(trx);
}
static __attribute__((constructor)) void on_dso_load(void)
{
OSMO_ASSERT(osmo_fsm_register(&trx_fsm) == 0);
}

816
trxcon/trx_if.c.backup.c Normal file
View File

@@ -0,0 +1,816 @@
/*
* OsmocomBB <-> SDR connection bridge
* Transceiver interface handlers
*
* Copyright (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
* Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <errno.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/eventfd.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/fsm.h>
#include <osmocom/gsm/gsm_utils.h>
#include "l1ctl.h"
#include "trxcon.h"
#include "trx_if.h"
#include "logging.h"
#include "scheduler.h"
#include "../Transceiver52M/l1if.h"
static struct value_string trx_evt_names[] = {
{ 0, NULL } /* no events? */
};
static struct osmo_fsm_state trx_fsm_states[] = {
[TRX_STATE_OFFLINE] = {
.out_state_mask = (
GEN_MASK(TRX_STATE_IDLE) |
GEN_MASK(TRX_STATE_RSP_WAIT)),
.name = "OFFLINE",
},
[TRX_STATE_IDLE] = {
.out_state_mask = UINT32_MAX,
.name = "IDLE",
},
[TRX_STATE_ACTIVE] = {
.out_state_mask = (
GEN_MASK(TRX_STATE_IDLE) |
GEN_MASK(TRX_STATE_RSP_WAIT)),
.name = "ACTIVE",
},
[TRX_STATE_RSP_WAIT] = {
.out_state_mask = (
GEN_MASK(TRX_STATE_IDLE) |
GEN_MASK(TRX_STATE_ACTIVE) |
GEN_MASK(TRX_STATE_OFFLINE)),
.name = "RSP_WAIT",
},
};
static struct osmo_fsm trx_fsm = {
.name = "trx_interface_fsm",
.states = trx_fsm_states,
.num_states = ARRAY_SIZE(trx_fsm_states),
.log_subsys = DTRX,
.event_names = trx_evt_names,
};
static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local,
uint16_t port_local, const char *host_remote, uint16_t port_remote,
int (*cb)(struct osmo_fd *fd, unsigned int what))
{
int rc;
ofd->data = priv;
ofd->fd = -1;
ofd->cb = cb;
/* Init UDP Connection */
rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, host_local, port_local,
host_remote, port_remote,
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
return rc;
}
static void trx_udp_close(struct osmo_fd *ofd)
{
if (ofd->fd > 0) {
osmo_fd_unregister(ofd);
close(ofd->fd);
ofd->fd = -1;
}
}
/* ------------------------------------------------------------------------ */
/* Control (CTRL) interface handlers */
/* ------------------------------------------------------------------------ */
/* Commands on the Per-ARFCN Control Interface */
/* */
/* The per-ARFCN control interface uses a command-response protocol. */
/* Commands are NULL-terminated ASCII strings, one per UDP socket. */
/* Each command has a corresponding response. */
/* Every command is of the form: */
/* */
/* CMD <cmdtype> [params] */
/* */
/* The <cmdtype> is the actual command. */
/* Parameters are optional depending on the commands type. */
/* Every response is of the form: */
/* */
/* RSP <cmdtype> <status> [result] */
/* */
/* The <status> is 0 for success and a non-zero error code for failure. */
/* Successful responses may include results, depending on the command type. */
/* ------------------------------------------------------------------------ */
static void trx_ctrl_timer_cb(void *data);
/* Send first CTRL message and start timer */
static void trx_ctrl_send(struct trx_instance *trx)
{
struct trx_ctrl_msg *tcm;
if (llist_empty(&trx->trx_ctrl_list))
return;
tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
char* cmd = malloc(TRXC_BUF_SIZE);
memcpy(cmd, tcm->cmd, TRXC_BUF_SIZE);
/* Send command */
LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
trxif_to_trx_c(cmd);
// send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0);
/* Trigger state machine */
if (trx->fsm->state != TRX_STATE_RSP_WAIT) {
trx->prev_state = trx->fsm->state;
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_RSP_WAIT, 0, 0);
}
/* Start expire timer */
trx->trx_ctrl_timer.data = trx;
trx->trx_ctrl_timer.cb = trx_ctrl_timer_cb;
osmo_timer_schedule(&trx->trx_ctrl_timer, 2, 0);
}
static void trx_ctrl_timer_cb(void *data)
{
struct trx_instance *trx = (struct trx_instance *) data;
struct trx_ctrl_msg *tcm;
/* Queue may be cleaned at this moment */
if (llist_empty(&trx->trx_ctrl_list))
return;
LOGP(DTRX, LOGL_NOTICE, "No response from transceiver...\n");
tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
if (++tcm->retry_cnt > 3) {
LOGP(DTRX, LOGL_NOTICE, "Transceiver offline\n");
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_OFFLINE, 0, 0);
osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_OFFLINE, trx);
return;
}
/* Attempt to send a command again */
trx_ctrl_send(trx);
}
/* Add a new CTRL command to the trx_ctrl_list */
static int trx_ctrl_cmd(struct trx_instance *trx, int critical,
const char *cmd, const char *fmt, ...)
{
struct trx_ctrl_msg *tcm;
int len, pending = 0;
va_list ap;
/* TODO: make sure that transceiver online */
if (!llist_empty(&trx->trx_ctrl_list))
pending = 1;
/* Allocate a message */
tcm = talloc_zero(trx, struct trx_ctrl_msg);
if (!tcm)
return -ENOMEM;
/* Fill in command arguments */
if (fmt && fmt[0]) {
len = snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s ", cmd);
va_start(ap, fmt);
vsnprintf(tcm->cmd + len, sizeof(tcm->cmd) - len - 1, fmt, ap);
va_end(ap);
} else {
snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s", cmd);
}
tcm->cmd_len = strlen(cmd);
tcm->critical = critical;
llist_add_tail(&tcm->list, &trx->trx_ctrl_list);
LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd);
/* Send message, if no pending messages */
if (!pending)
trx_ctrl_send(trx);
return 0;
}
/*
* Power Control
*
* ECHO is used to check transceiver availability.
* CMD ECHO
* RSP ECHO <status>
*
* POWEROFF shuts off transmitter power and stops the demodulator.
* CMD POWEROFF
* RSP POWEROFF <status>
*
* POWERON starts the transmitter and starts the demodulator.
* Initial power level is very low.
* This command fails if the transmitter and receiver are not yet tuned.
* This command fails if the transmit or receive frequency creates a conflict
* with another ARFCN that is already running.
* If the transceiver is already on, it response with success to this command.
* CMD POWERON
* RSP POWERON <status>
*/
int trx_if_cmd_sync(struct trx_instance *trx)
{
return trx_ctrl_cmd(trx, 1, "SYNC", "");
}
int trx_if_cmd_echo(struct trx_instance *trx)
{
return trx_ctrl_cmd(trx, 1, "ECHO", "");
}
int trx_if_cmd_poweroff(struct trx_instance *trx)
{
return trx_ctrl_cmd(trx, 1, "POWEROFF", "");
}
int trx_if_cmd_poweron(struct trx_instance *trx)
{
if (trx->powered_up) {
/* FIXME: this should be handled by the FSM, not here! */
LOGP(DTRX, LOGL_ERROR, "Suppressing POWERON as we're already powered up\n");
return -EAGAIN;
}
return trx_ctrl_cmd(trx, 1, "POWERON", "");
}
/*
* Timeslot Control
*
* SETSLOT sets the format of the uplink timeslots in the ARFCN.
* The <timeslot> indicates the timeslot of interest.
* The <chantype> indicates the type of channel that occupies the timeslot.
* A chantype of zero indicates the timeslot is off.
* CMD SETSLOT <timeslot> <chantype>
* RSP SETSLOT <status> <timeslot> <chantype>
*/
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type)
{
return trx_ctrl_cmd(trx, 1, "SETSLOT", "%u %u", tn, type);
}
/*
* Tuning Control
*
* (RX/TX)TUNE tunes the receiver to a given frequency in kHz.
* This command fails if the receiver is already running.
* (To re-tune you stop the radio, re-tune, and restart.)
* This command fails if the transmit or receive frequency
* creates a conflict with another ARFCN that is already running.
* CMD (RX/TX)TUNE <kHz>
* RSP (RX/TX)TUNE <status> <kHz>
*/
int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn)
{
uint16_t freq10;
/* RX is downlink on MS side */
freq10 = gsm_arfcn2freq10(band_arfcn, 0);
if (freq10 == 0xffff) {
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
return -ENOTSUP;
}
return trx_ctrl_cmd(trx, 1, "RXTUNE", "%u", freq10 * 100);
}
int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn)
{
uint16_t freq10;
/* TX is uplink on MS side */
freq10 = gsm_arfcn2freq10(band_arfcn, 1);
if (freq10 == 0xffff) {
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
return -ENOTSUP;
}
return trx_ctrl_cmd(trx, 1, "TXTUNE", "%u", freq10 * 100);
}
/*
* Power measurement
*
* MEASURE instructs the transceiver to perform a power
* measurement on specified frequency. After receiving this
* request, transceiver should quickly re-tune to requested
* frequency, measure power level and re-tune back to the
* previous frequency.
* CMD MEASURE <kHz>
* RSP MEASURE <status> <kHz> <dB>
*/
int trx_if_cmd_measure(struct trx_instance *trx,
uint16_t band_arfcn_start, uint16_t band_arfcn_stop)
{
uint16_t freq10;
/* Update ARFCN range for measurement */
trx->pm_band_arfcn_start = band_arfcn_start;
trx->pm_band_arfcn_stop = band_arfcn_stop;
/* Calculate a frequency for current ARFCN (DL) */
freq10 = gsm_arfcn2freq10(band_arfcn_start, 0);
if (freq10 == 0xffff) {
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn_start);
return -ENOTSUP;
}
return trx_ctrl_cmd(trx, 1, "MEASURE", "%u", freq10 * 100);
}
static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp)
{
unsigned int freq10;
uint16_t band_arfcn;
int dbm;
/* Parse freq. and power level */
sscanf(resp, "%u %d", &freq10, &dbm);
freq10 /= 100;
/* Check received ARFCN against expected */
band_arfcn = gsm_freq102arfcn((uint16_t) freq10, 0);
if (band_arfcn != trx->pm_band_arfcn_start) {
LOGP(DTRX, LOGL_ERROR, "Power measurement error: "
"response ARFCN=%u doesn't match expected ARFCN=%u\n",
band_arfcn &~ ARFCN_FLAG_MASK,
trx->pm_band_arfcn_start &~ ARFCN_FLAG_MASK);
return;
}
/* Send L1CTL_PM_CONF */
l1ctl_tx_pm_conf(trx->l1l, band_arfcn, dbm,
band_arfcn == trx->pm_band_arfcn_stop);
/* Schedule a next measurement */
if (band_arfcn != trx->pm_band_arfcn_stop)
trx_if_cmd_measure(trx, ++band_arfcn, trx->pm_band_arfcn_stop);
}
/*
* Timing Advance control
*
* SETTA instructs the transceiver to transmit bursts in
* advance calculated from requested TA value. This value is
* normally between 0 and 63, with each step representing
* an advance of one bit period (about 3.69 microseconds).
* Since OsmocomBB has a special feature, which allows one
* to spoof the distance from BTS, the range is extended.
* CMD SETTA <-128..127>
* RSP SETTA <status> <TA>
*/
int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta)
{
return trx_ctrl_cmd(trx, 0, "SETTA", "%d", ta);
}
/*
* Frequency Hopping parameters indication.
*
* SETFH instructs transceiver to enable frequency hopping mode
* using the given HSN, MAIO, and Mobile Allocation parameters.
*
* CMD SETFH <HSN> <MAIO> <RXF1> <TXF1> [... <RXFN> <TXFN>]
*
* where <RXFN> and <TXFN> is a pair of Rx/Tx frequencies (in kHz)
* corresponding to one ARFCN the Mobile Allocation. Note that the
* channel list is expected to be sorted in ascending order.
*/
int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
uint8_t maio, uint16_t *ma, size_t ma_len)
{
/* Reserve some room for CMD SETFH <HSN> <MAIO> */
char ma_buf[TRXC_BUF_SIZE - 24];
size_t ma_buf_len = sizeof(ma_buf) - 1;
uint16_t rx_freq, tx_freq;
char *ptr;
int i, rc;
/* Make sure that Mobile Allocation has at least one ARFCN */
if (!ma_len || ma == NULL) {
LOGP(DTRX, LOGL_ERROR, "Mobile Allocation is empty?!?\n");
return -EINVAL;
}
/* Compose a sequence of Rx/Tx frequencies (mobile allocation) */
for (i = 0, ptr = ma_buf; i < ma_len; i++) {
/* Convert ARFCN to a pair of Rx/Tx frequencies (Hz * 10) */
rx_freq = gsm_arfcn2freq10(ma[i], 0); /* Rx: Downlink */
tx_freq = gsm_arfcn2freq10(ma[i], 1); /* Tx: Uplink */
if (rx_freq == 0xffff || tx_freq == 0xffff) {
LOGP(DTRX, LOGL_ERROR, "Failed to convert ARFCN %u "
"to a pair of Rx/Tx frequencies\n",
ma[i] & ~ARFCN_FLAG_MASK);
return -EINVAL;
}
/* Append a pair of Rx/Tx frequencies (in kHz) to the buffer */
rc = snprintf(ptr, ma_buf_len, "%u %u ", rx_freq * 100, tx_freq * 100);
if (rc < 0 || rc > ma_buf_len) { /* Prevent buffer overflow */
LOGP(DTRX, LOGL_ERROR, "Not enough room to encode "
"Mobile Allocation (N=%zu)\n", ma_len);
return -ENOSPC;
}
/* Move pointer */
ma_buf_len -= rc;
ptr += rc;
}
/* Overwrite the last space */
*(ptr - 1) = '\0';
return trx_ctrl_cmd(trx, 1, "SETFH", "%u %u %s", hsn, maio, ma_buf);
}
/* Get response from CTRL socket */
static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
{
struct trx_instance *trx = ofd->data;
struct trx_ctrl_msg *tcm;
int resp, rsp_len;
char buf[TRXC_BUF_SIZE], *p;
ssize_t read_len;
// read_len = read(ofd->fd, buf, sizeof(buf) - 1);
// if (read_len <= 0) {
// LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
// return read_len;
// }
// buf[read_len] = '\0';
LOGP(DTRX, LOGL_NOTICE, "C wat: %d\n", what);
char* response = trxif_from_trx_c();
if (!response) {
LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", response);
return response;
}
memcpy(buf, response, TRXC_BUF_SIZE);
free(response);
if (!!strncmp(buf, "RSP ", 4)) {
LOGP(DTRX, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf);
return 0;
}
/* Calculate the length of response item */
p = strchr(buf + 4, ' ');
rsp_len = p ? p - buf - 4 : strlen(buf) - 4;
LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf);
/* Abort expire timer */
if (osmo_timer_pending(&trx->trx_ctrl_timer))
osmo_timer_del(&trx->trx_ctrl_timer);
/* Get command for response message */
if (llist_empty(&trx->trx_ctrl_list)) {
LOGP(DTRX, LOGL_NOTICE, "Response message without command\n");
return -EINVAL;
}
tcm = llist_entry(trx->trx_ctrl_list.next,
struct trx_ctrl_msg, list);
/* Check if response matches command */
if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) {
LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
"Response message '%s' does not match command "
"message '%s'\n", buf, tcm->cmd);
goto rsp_error;
}
/* Check for response code */
sscanf(p + 1, "%d", &resp);
if (resp) {
LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
"Transceiver rejected TRX command with "
"response: '%s'\n", buf);
if (tcm->critical)
goto rsp_error;
}
/* Trigger state machine */
if (!strncmp(tcm->cmd + 4, "POWERON", 7)) {
trx->powered_up = true;
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_ACTIVE, 0, 0);
}
else if (!strncmp(tcm->cmd + 4, "POWEROFF", 8)) {
trx->powered_up = false;
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
}
else if (!strncmp(tcm->cmd + 4, "MEASURE", 7))
trx_if_measure_rsp_cb(trx, buf + 14);
else if (!strncmp(tcm->cmd + 4, "ECHO", 4))
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
else
osmo_fsm_inst_state_chg(trx->fsm, trx->prev_state, 0, 0);
/* Remove command from list */
llist_del(&tcm->list);
talloc_free(tcm);
/* Send next message, if any */
trx_ctrl_send(trx);
return 0;
rsp_error:
/* Notify higher layers about the problem */
osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_RSP_ERROR, trx);
return -EIO;
}
/* ------------------------------------------------------------------------ */
/* Data interface handlers */
/* ------------------------------------------------------------------------ */
/* DATA interface */
/* */
/* Messages on the data interface carry one radio burst per UDP message. */
/* */
/* Received Data Burst: */
/* 1 byte timeslot index */
/* 4 bytes GSM frame number, BE */
/* 1 byte RSSI in -dBm */
/* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE */
/* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1" */
/* 2 bytes are not used, but being sent by OsmoTRX */
/* */
/* Transmit Data Burst: */
/* 1 byte timeslot index */
/* 4 bytes GSM frame number, BE */
/* 1 byte transmit level wrt ARFCN max, -dB (attenuation) */
/* 148 bytes output symbol values, 0 & 1 */
/* ------------------------------------------------------------------------ */
static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
{
struct trx_instance *trx = ofd->data;
struct trx_meas_set meas;
uint8_t buf[TRXD_BUF_SIZE];
sbit_t bits[148];
int8_t rssi, tn;
int16_t toa256;
uint32_t fn;
ssize_t read_len;
// read_len = read(ofd->fd, buf, sizeof(buf));
// if (read_len <= 0) {
// LOGP(DTRXD, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
// return read_len;
// }
// if (read_len != 158) {
// LOGP(DTRXD, LOGL_ERROR, "Got data message with invalid "
// "length '%zd'\n", read_len);
// return -EINVAL;
// }
// tn = buf[0];
// fn = osmo_load32be(buf + 1);
// rssi = -(int8_t) buf[5];
// toa256 = ((int16_t) (buf[6] << 8) | buf[7]);
// /* Copy and convert bits {254..0} to sbits {-127..127} */
// osmo_ubit2sbit(bits, buf + 8, 148);
struct trxd_from_trx* rcvd = trxif_from_trx_d();
if (!rcvd) {
LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", rcvd);
return rcvd;
}
tn = rcvd->ts;
fn = rcvd->fn;
rssi = -(int8_t) rcvd->rssi;
toa256 = (int16_t) rcvd->toa;
/* Copy and convert bits {254..0} to sbits {-127..127} */
//osmo_ubit2sbit(bits, rcvd->symbols, 148);
memcpy(bits, rcvd->symbols, 148);
free(rcvd);
if (tn >= 8) {
LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn);
return -EINVAL;
}
if (fn >= 2715648) {
LOGP(DTRXD, LOGL_ERROR, "Illegal FN %u\n", fn);
return -EINVAL;
}
LOGP(DTRXD, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%d\n",
tn, fn, rssi, toa256);
/* Group the measurements together */
meas = (struct trx_meas_set) {
.toa256 = toa256,
.rssi = rssi,
.fn = fn,
};
/* Poke scheduler */
sched_trx_handle_rx_burst(trx, tn, fn, bits, 148, &meas);
/* Correct local clock counter */
if (fn % 51 == 0)
sched_clck_handle(&trx->sched, fn);
return 0;
}
int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
uint8_t pwr, const ubit_t *bits)
{
// uint8_t buf[TRXD_BUF_SIZE];
// /**
// * We must be sure that we have clock,
// * and we have sent all control data
// *
// * TODO: introduce proper state machines for both
// * transceiver and its TRXC interface.
// */
//#if 0
// if (trx->fsm->state != TRX_STATE_ACTIVE) {
// LOGP(DTRXD, LOGL_ERROR, "Ignoring TX data, "
// "transceiver isn't ready\n");
// return -EAGAIN;
// }
//#endif
// LOGP(DTRXD, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr);
// buf[0] = tn;
// osmo_store32be(fn, buf + 1);
// buf[5] = pwr;
// /* Copy ubits {0,1} */
// memcpy(buf + 6, bits, 148);
// /* Send data to transceiver */
// send(trx->trx_ofd_data.fd, buf, 154, 0);
struct trxd_to_trx* t = malloc(sizeof(struct trxd_to_trx));
t->ts = tn;
t->fn = fn;
t->txlev = pwr;
memcpy(t->symbols, bits, 148);
trxif_to_trx_d(t);
return 0;
}
/* Init TRX interface (TRXC, TRXD sockets and FSM) */
struct trx_instance *trx_if_open(void *tall_ctx,
const char *local_host, const char *remote_host,
uint16_t base_port)
{
struct trx_instance *trx;
int rc;
LOGP(DTRX, LOGL_NOTICE, "Init transceiver interface "
"(%s:%u)\n", remote_host, base_port);
/* Try to allocate memory */
trx = talloc_zero(tall_ctx, struct trx_instance);
if (!trx) {
LOGP(DTRX, LOGL_ERROR, "Failed to allocate memory\n");
return NULL;
}
/* Allocate a new dedicated state machine */
trx->fsm = osmo_fsm_inst_alloc(&trx_fsm, trx,
NULL, LOGL_DEBUG, "trx_interface");
if (trx->fsm == NULL) {
LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance "
"of FSM '%s'\n", trx_fsm.name);
talloc_free(trx);
return NULL;
}
/* Initialize CTRL queue */
INIT_LLIST_HEAD(&trx->trx_ctrl_list);
rc = eventfd(0, 0);
osmo_fd_setup(get_c_fd(), rc, OSMO_FD_READ, trx_ctrl_read_cb, trx, 0);
osmo_fd_register(get_c_fd());
rc = eventfd(0, 0);
osmo_fd_setup(get_d_fd(), rc, OSMO_FD_READ, trx_data_rx_cb, trx, 0);
osmo_fd_register(get_d_fd());
// /* Open sockets */
// rc = trx_udp_open(trx, &trx->trx_ofd_ctrl, local_host,
// base_port + 101, remote_host, base_port + 1, trx_ctrl_read_cb);
// if (rc < 0)
// goto udp_error;
// rc = trx_udp_open(trx, &trx->trx_ofd_data, local_host,
// base_port + 102, remote_host, base_port + 2, trx_data_rx_cb);
// if (rc < 0)
// goto udp_error;
return trx;
//udp_error:
// LOGP(DTRX, LOGL_ERROR, "Couldn't establish UDP connection\n");
// osmo_fsm_inst_free(trx->fsm);
// talloc_free(trx);
// return NULL;
}
/* Flush pending control messages */
void trx_if_flush_ctrl(struct trx_instance *trx)
{
struct trx_ctrl_msg *tcm;
/* Reset state machine */
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
/* Clear command queue */
while (!llist_empty(&trx->trx_ctrl_list)) {
tcm = llist_entry(trx->trx_ctrl_list.next,
struct trx_ctrl_msg, list);
llist_del(&tcm->list);
talloc_free(tcm);
}
}
void trx_if_close(struct trx_instance *trx)
{
/* May be unallocated due to init error */
if (!trx)
return;
LOGP(DTRX, LOGL_NOTICE, "Shutdown transceiver interface\n");
/* Flush CTRL message list */
trx_if_flush_ctrl(trx);
/* Close sockets */
close(get_c_fd()->fd);
close(get_d_fd()->fd);
// trx_udp_close(&trx->trx_ofd_ctrl);
// trx_udp_close(&trx->trx_ofd_data);
/* Free memory */
osmo_fsm_inst_free(trx->fsm);
talloc_free(trx);
}
static __attribute__((constructor)) void on_dso_load(void)
{
OSMO_ASSERT(osmo_fsm_register(&trx_fsm) == 0);
}

87
trxcon/trx_if.h Normal file
View File

@@ -0,0 +1,87 @@
#pragma once
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/fsm.h>
#include "scheduler.h"
#include "sched_trx.h"
#define TRXC_BUF_SIZE 1024
#define TRXD_BUF_SIZE 512
/* Forward declaration to avoid mutual include */
struct l1ctl_link;
enum trx_fsm_states {
TRX_STATE_OFFLINE = 0,
TRX_STATE_IDLE,
TRX_STATE_ACTIVE,
TRX_STATE_RSP_WAIT,
};
struct trx_instance {
#ifndef IPCIF
struct osmo_fd trx_ofd_ctrl;
struct osmo_fd trx_ofd_data;
#endif
struct osmo_timer_list trx_ctrl_timer;
struct llist_head trx_ctrl_list;
struct osmo_fsm_inst *fsm;
/* HACK: we need proper state machines */
uint32_t prev_state;
bool powered_up;
/* GSM L1 specific */
uint16_t pm_band_arfcn_start;
uint16_t pm_band_arfcn_stop;
uint16_t band_arfcn;
uint8_t tx_power;
uint8_t bsic;
uint8_t tsc;
int8_t ta;
/* Scheduler stuff */
struct trx_sched sched;
struct trx_ts *ts_list[TRX_TS_COUNT];
/* Bind L1CTL link */
struct l1ctl_link *l1l;
};
struct trx_ctrl_msg {
struct llist_head list;
char cmd[TRXC_BUF_SIZE];
int retry_cnt;
int critical;
int cmd_len;
};
struct trx_instance *trx_if_open(void *tall_ctx,
const char *local_host, const char *remote_host, uint16_t port);
void trx_if_flush_ctrl(struct trx_instance *trx);
void trx_if_close(struct trx_instance *trx);
int trx_if_cmd_poweron(struct trx_instance *trx);
int trx_if_cmd_poweroff(struct trx_instance *trx);
int trx_if_cmd_echo(struct trx_instance *trx);
int trx_if_cmd_sync(struct trx_instance *trx);
int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta);
int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn);
int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn);
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type);
int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
uint8_t maio, uint16_t *ma, size_t ma_len);
int trx_if_cmd_measure(struct trx_instance *trx,
uint16_t band_arfcn_start, uint16_t band_arfcn_stop);
int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
uint8_t pwr, const ubit_t *bits);
int trx_data_rx_handler(struct trx_instance *trx, uint8_t *buf);

407
trxcon/trxcon.c Normal file
View File

@@ -0,0 +1,407 @@
/*
* OsmocomBB <-> SDR connection bridge
*
* (C) 2016-2020 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/signal.h>
#include <osmocom/core/select.h>
#include <osmocom/core/application.h>
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/gsmtap.h>
#include <osmocom/gsm/gsm_utils.h>
#include "trxcon.h"
#include "trx_if.h"
#include "logging.h"
#include "l1ctl.h"
#include "l1ctl_link.h"
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#define COPYRIGHT \
"Copyright (C) 2016-2020 by Vadim Yanitskiy <axilirator@gmail.com>\n" \
"License GPLv2+: GNU GPL version 2 or later " \
"<http://gnu.org/licenses/gpl.html>\n" \
"This is free software: you are free to change and redistribute it.\n" \
"There is NO WARRANTY, to the extent permitted by law.\n\n"
static struct {
const char *debug_mask;
int daemonize;
int quit;
/* L1CTL specific */
struct l1ctl_link *l1l;
const char *bind_socket;
/* TRX specific */
struct trx_instance *trx;
const char *trx_bind_ip;
const char *trx_remote_ip;
uint16_t trx_base_port;
uint32_t trx_fn_advance;
const char *gsmtap_ip;
} app_data;
static void *tall_trxcon_ctx = NULL;
struct gsmtap_inst *gsmtap = NULL;
struct osmo_fsm_inst *trxcon_fsm;
static void trxcon_fsm_idle_action(struct osmo_fsm_inst *fi,
uint32_t event, void *data)
{
if (event == L1CTL_EVENT_CONNECT)
osmo_fsm_inst_state_chg(trxcon_fsm, TRXCON_STATE_MANAGED, 0, 0);
}
static void trxcon_fsm_managed_action(struct osmo_fsm_inst *fi,
uint32_t event, void *data)
{
switch (event) {
case L1CTL_EVENT_DISCONNECT:
osmo_fsm_inst_state_chg(trxcon_fsm, TRXCON_STATE_IDLE, 0, 0);
if (app_data.trx->fsm->state != TRX_STATE_OFFLINE) {
/* Reset scheduler and clock counter */
sched_trx_reset(app_data.trx, true);
/* TODO: implement trx_if_reset() */
trx_if_cmd_poweroff(app_data.trx);
trx_if_cmd_echo(app_data.trx);
}
break;
case TRX_EVENT_RSP_ERROR:
case TRX_EVENT_OFFLINE:
/* TODO: notify L2 & L3 about that */
break;
default:
LOGPFSML(fi, LOGL_ERROR, "Unhandled event %u\n", event);
}
}
static struct osmo_fsm_state trxcon_fsm_states[] = {
[TRXCON_STATE_IDLE] = {
.in_event_mask = GEN_MASK(L1CTL_EVENT_CONNECT),
.out_state_mask = GEN_MASK(TRXCON_STATE_MANAGED),
.name = "IDLE",
.action = trxcon_fsm_idle_action,
},
[TRXCON_STATE_MANAGED] = {
.in_event_mask = (
GEN_MASK(L1CTL_EVENT_DISCONNECT) |
GEN_MASK(TRX_EVENT_RSP_ERROR) |
GEN_MASK(TRX_EVENT_OFFLINE)),
.out_state_mask = GEN_MASK(TRXCON_STATE_IDLE),
.name = "MANAGED",
.action = trxcon_fsm_managed_action,
},
};
static const struct value_string app_evt_names[] = {
OSMO_VALUE_STRING(L1CTL_EVENT_CONNECT),
OSMO_VALUE_STRING(L1CTL_EVENT_DISCONNECT),
OSMO_VALUE_STRING(TRX_EVENT_OFFLINE),
OSMO_VALUE_STRING(TRX_EVENT_RSP_ERROR),
{ 0, NULL }
};
static struct osmo_fsm trxcon_fsm_def = {
.name = "trxcon_app_fsm",
.states = trxcon_fsm_states,
.num_states = ARRAY_SIZE(trxcon_fsm_states),
.log_subsys = DAPP,
.event_names = app_evt_names,
};
static void print_usage(const char *app)
{
printf("Usage: %s\n", app);
}
static void print_help(void)
{
printf(" Some help...\n");
printf(" -h --help this text\n");
printf(" -d --debug Change debug flags. Default: %s\n", DEBUG_DEFAULT);
printf(" -b --trx-bind TRX bind IP address (default 0.0.0.0)\n");
printf(" -i --trx-remote TRX remote IP address (default 127.0.0.1)\n");
printf(" -p --trx-port Base port of TRX instance (default 6700)\n");
printf(" -f --trx-advance Uplink burst scheduling advance (default 3)\n");
printf(" -s --socket Listening socket for layer23 (default /tmp/osmocom_l2)\n");
printf(" -g --gsmtap-ip The destination IP used for GSMTAP (disabled by default)\n");
printf(" -D --daemonize Run as daemon\n");
}
static void handle_options(int argc, char **argv)
{
while (1) {
int option_index = 0, c;
static struct option long_options[] = {
{"help", 0, 0, 'h'},
{"debug", 1, 0, 'd'},
{"socket", 1, 0, 's'},
{"trx-bind", 1, 0, 'b'},
/* NOTE: 'trx-ip' is now an alias for 'trx-remote'
* due to backward compatibility reasons! */
{"trx-ip", 1, 0, 'i'},
{"trx-remote", 1, 0, 'i'},
{"trx-port", 1, 0, 'p'},
{"trx-advance", 1, 0, 'f'},
{"gsmtap-ip", 1, 0, 'g'},
{"daemonize", 0, 0, 'D'},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, "d:b:i:p:f:s:g:Dh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'h':
print_usage(argv[0]);
print_help();
exit(0);
break;
case 'd':
app_data.debug_mask = optarg;
break;
case 'b':
app_data.trx_bind_ip = optarg;
break;
case 'i':
app_data.trx_remote_ip = optarg;
break;
case 'p':
app_data.trx_base_port = atoi(optarg);
break;
case 'f':
app_data.trx_fn_advance = atoi(optarg);
break;
case 's':
app_data.bind_socket = optarg;
break;
case 'g':
app_data.gsmtap_ip = optarg;
break;
case 'D':
app_data.daemonize = 1;
break;
default:
break;
}
}
}
static void init_defaults(void)
{
app_data.bind_socket = "/tmp/osmocom_l2";
app_data.trx_remote_ip = "127.0.0.1";
app_data.trx_bind_ip = "0.0.0.0";
app_data.trx_base_port = 6700;
app_data.trx_fn_advance = 3;
app_data.debug_mask = NULL;
app_data.gsmtap_ip = NULL;
app_data.daemonize = 0;
app_data.quit = 0;
}
static void signal_handler(int signum)
{
fprintf(stderr, "signal %u received\n", signum);
switch (signum) {
case SIGINT:
app_data.quit++;
break;
case SIGABRT:
/* in case of abort, we want to obtain a talloc report and
* then run default SIGABRT handler, who will generate coredump
* and abort the process. abort() should do this for us after we
* return, but program wouldn't exit if an external SIGABRT is
* received.
*/
talloc_report_full(tall_trxcon_ctx, stderr);
signal(SIGABRT, SIG_DFL);
raise(SIGABRT);
break;
case SIGUSR1:
case SIGUSR2:
talloc_report_full(tall_trxcon_ctx, stderr);
break;
default:
break;
}
}
extern void init_external_transceiver(struct trx_instance *trx, int argc, char **argv) __attribute__((weak));
extern void close_external_transceiver(int argc, char **argv) __attribute__((weak));
int main(int argc, char **argv)
{
int rc = 0;
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(3, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
int prio = sched_get_priority_max(SCHED_RR) - 5;
struct sched_param param;
param.sched_priority = prio;
int rv = sched_setscheduler(0, SCHED_RR, &param);
if (rv < 0) {
LOGP(DAPP, LOGL_ERROR, "Failed to set sched!\n");
exit(0);
}
printf("%s", COPYRIGHT);
init_defaults();
handle_options(argc, argv);
/* Track the use of talloc NULL memory contexts */
talloc_enable_null_tracking();
/* Init talloc memory management system */
tall_trxcon_ctx = talloc_init("trxcon context");
msgb_talloc_ctx_init(tall_trxcon_ctx, 0);
/* Setup signal handlers */
// signal(SIGINT, &signal_handler);
// signal(SIGABRT, &signal_handler);
// signal(SIGUSR1, &signal_handler);
// signal(SIGUSR2, &signal_handler);
osmo_init_ignore_signals();
/* Init logging system */
trx_log_init(tall_trxcon_ctx, app_data.debug_mask);
/* Configure pretty logging */
log_set_print_extended_timestamp(osmo_stderr_target, 1);
log_set_print_category_hex(osmo_stderr_target, 0);
log_set_print_category(osmo_stderr_target, 1);
log_set_print_level(osmo_stderr_target, 1);
/* Optional GSMTAP */
if (app_data.gsmtap_ip != NULL) {
gsmtap = gsmtap_source_init(app_data.gsmtap_ip, GSMTAP_UDP_PORT, 1);
if (!gsmtap) {
LOGP(DAPP, LOGL_ERROR, "Failed to init GSMTAP\n");
goto exit;
}
/* Suppress ICMP "destination unreachable" errors */
gsmtap_source_add_sink(gsmtap);
}
/* Allocate the application state machine */
OSMO_ASSERT(osmo_fsm_register(&trxcon_fsm_def) == 0);
trxcon_fsm = osmo_fsm_inst_alloc(&trxcon_fsm_def, tall_trxcon_ctx,
NULL, LOGL_DEBUG, "main");
/* Init L1CTL server */
app_data.l1l = l1ctl_link_init(tall_trxcon_ctx,
app_data.bind_socket);
if (app_data.l1l == NULL)
goto exit;
/* Init transceiver interface */
app_data.trx = trx_if_open(tall_trxcon_ctx,
app_data.trx_bind_ip, app_data.trx_remote_ip,
app_data.trx_base_port);
if (!app_data.trx)
goto exit;
/* Bind L1CTL with TRX and vice versa */
app_data.l1l->trx = app_data.trx;
app_data.trx->l1l = app_data.l1l;
/* Init scheduler */
rc = sched_trx_init(app_data.trx, app_data.trx_fn_advance);
if (rc)
goto exit;
LOGP(DAPP, LOGL_NOTICE, "Init complete\n");
if (app_data.daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
perror("Error during daemonize");
goto exit;
}
}
/* Initialize pseudo-random generator */
srand(time(NULL));
if (init_external_transceiver)
init_external_transceiver(app_data.trx, argc, argv);
else
while (!app_data.quit)
osmo_select_main(0);
if (close_external_transceiver)
close_external_transceiver(argc, argv);
exit:
/* Close active connections */
l1ctl_link_shutdown(app_data.l1l);
sched_trx_shutdown(app_data.trx);
trx_if_close(app_data.trx);
/* Shutdown main state machine */
osmo_fsm_inst_free(trxcon_fsm);
/* Deinitialize logging */
log_fini();
/**
* Print report for the root talloc context in order
* to be able to find and fix potential memory leaks.
*/
talloc_report_full(tall_trxcon_ctx, stderr);
talloc_free(tall_trxcon_ctx);
/* Make both Valgrind and ASAN happy */
talloc_report_full(NULL, stderr);
talloc_disable_null_tracking();
return rc;
}

21
trxcon/trxcon.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#define GEN_MASK(state) (0x01 << state)
extern struct osmo_fsm_inst *trxcon_fsm;
extern struct gsmtap_inst *gsmtap;
enum trxcon_fsm_states {
TRXCON_STATE_IDLE = 0,
TRXCON_STATE_MANAGED,
};
enum trxcon_fsm_events {
/* L1CTL specific events */
L1CTL_EVENT_CONNECT,
L1CTL_EVENT_DISCONNECT,
/* TRX specific events */
TRX_EVENT_RSP_ERROR,
TRX_EVENT_OFFLINE,
};