mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-trx.git
synced 2025-11-02 13:13:17 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
847de6d780 | ||
|
|
e29b3c8151 | ||
|
|
0ded5a07ac | ||
|
|
a378a1dea0 | ||
|
|
607141bf34 | ||
|
|
901f689086 | ||
|
|
966af04ff1 | ||
|
|
d5cafc2cc0 | ||
|
|
b8ef806c25 | ||
|
|
1a19caf002 |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -6,6 +6,12 @@ Transceiver52M/osmo-trx-uhd
|
||||
Transceiver52M/osmo-trx-usrp1
|
||||
Transceiver52M/osmo-trx-lms
|
||||
Transceiver52M/osmo-trx-ipc
|
||||
Transceiver52M/osmo-trx-blade
|
||||
Transceiver52M/osmo-trx-syncthing-blade
|
||||
Transceiver52M/osmo-trx-syncthing-uhd
|
||||
Transceiver52M/osmo-trx-ms-blade
|
||||
Transceiver52M/osmo-trx-ms-uhd
|
||||
|
||||
|
||||
.clang-format
|
||||
|
||||
@@ -74,3 +80,6 @@ contrib/osmo-trx.spec
|
||||
!contrib/osmo-trx.spec.in
|
||||
|
||||
utils/osmo-prbs-tool
|
||||
/.qtc_clangd/*
|
||||
/.cache/*
|
||||
/.vscode/*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ AM_CXXFLAGS = -Wall -pthread
|
||||
|
||||
# Order must be preserved
|
||||
SUBDIRS = \
|
||||
trxcon \
|
||||
CommonLibs \
|
||||
GSM \
|
||||
Transceiver52M \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,13 +65,16 @@ 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 \
|
||||
$(ARCH_LA) \
|
||||
$(GSM_LA) \
|
||||
$(COMMON_LA) \
|
||||
$(TRXCON_LA) \
|
||||
$(FFTWF_LIBS) \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOCTRL_LIBS) \
|
||||
@@ -83,6 +90,21 @@ 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 = $(osmo_trx_ms_uhd_SOURCES) ms/ms_rx_burst.cpp
|
||||
osmo_trx_syncthing_uhd_LDADD = $(osmo_trx_ms_uhd_LDADD)
|
||||
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 +127,31 @@ 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 = $(osmo_trx_ms_blade_SOURCES) ms/ms_rx_burst.cpp
|
||||
osmo_trx_syncthing_blade_LDADD = $(osmo_trx_ms_blade_LDADD)
|
||||
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 +159,33 @@ 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 = $(osmo_trx_ms_ipc_SOURCES) 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 \
|
||||
ms/ms_state.h \
|
||||
itrq.h
|
||||
# -fsanitize=address,undefined -shared-libsan -O0
|
||||
#
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
11
Transceiver52M/device/bladerf/Makefile.am
Normal file
11
Transceiver52M/device/bladerf/Makefile.am
Normal 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
|
||||
777
Transceiver52M/device/bladerf/bladerf.cpp
Normal file
777
Transceiver52M/device/bladerf/bladerf.cpp
Normal 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
|
||||
171
Transceiver52M/device/bladerf/bladerf.h
Normal file
171
Transceiver52M/device/bladerf/bladerf.h
Normal 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;
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
314
Transceiver52M/device/ipc2/IPCDevice.cpp
Normal file
314
Transceiver52M/device/ipc2/IPCDevice.cpp
Normal 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);
|
||||
}
|
||||
186
Transceiver52M/device/ipc2/IPCDevice.h
Normal file
186
Transceiver52M/device/ipc2/IPCDevice.h
Normal 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_
|
||||
14
Transceiver52M/device/ipc2/Makefile.am
Normal file
14
Transceiver52M/device/ipc2/Makefile.am
Normal 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
|
||||
161
Transceiver52M/device/ipc2/ipcif.h
Normal file
161
Transceiver52M/device/ipc2/ipcif.h
Normal 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;
|
||||
// }
|
||||
}
|
||||
};
|
||||
219
Transceiver52M/device/ipc2/shmif.h
Normal file
219
Transceiver52M/device/ipc2/shmif.h
Normal 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
|
||||
1
Transceiver52M/device/ipc2/uhddev_ipc.cpp
Normal file
1
Transceiver52M/device/ipc2/uhddev_ipc.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "../uhd/UHDDevice.cpp"
|
||||
255
Transceiver52M/device/ipc2/uhdwrap.cpp
Normal file
255
Transceiver52M/device/ipc2/uhdwrap.cpp
Normal 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, ×tamp, 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++;
|
||||
}
|
||||
}
|
||||
83
Transceiver52M/device/ipc2/uhdwrap.h
Normal file
83
Transceiver52M/device/ipc2/uhdwrap.h
Normal 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
|
||||
121
Transceiver52M/grgsm_vitac/constants.h
Normal file
121
Transceiver52M/grgsm_vitac/constants.h
Normal 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
|
||||
};
|
||||
338
Transceiver52M/grgsm_vitac/grgsm_vitac.cpp
Normal file
338
Transceiver52M/grgsm_vitac/grgsm_vitac.cpp
Normal 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);
|
||||
}
|
||||
42
Transceiver52M/grgsm_vitac/grgsm_vitac.h
Normal file
42
Transceiver52M/grgsm_vitac/grgsm_vitac.h
Normal 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;
|
||||
};
|
||||
392
Transceiver52M/grgsm_vitac/viterbi_detector.cc
Normal file
392
Transceiver52M/grgsm_vitac/viterbi_detector.cc
Normal 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;
|
||||
}
|
||||
}
|
||||
64
Transceiver52M/grgsm_vitac/viterbi_detector.h
Normal file
64
Transceiver52M/grgsm_vitac/viterbi_detector.h
Normal 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
204
Transceiver52M/itrq.h
Normal 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
124
Transceiver52M/l1if.cpp
Normal 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
74
Transceiver52M/l1if.h
Normal 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
|
||||
|
||||
482
Transceiver52M/ms/bladerf_specific.h
Normal file
482
Transceiver52M/ms/bladerf_specific.h
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
259
Transceiver52M/ms/ipc_specific.h
Normal file
259
Transceiver52M/ms/ipc_specific.h
Normal 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
473
Transceiver52M/ms/ipcif.c
Normal 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, ×tamp, 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;
|
||||
}
|
||||
232
Transceiver52M/ms/ms_commandhandler.cpp
Normal file
232
Transceiver52M/ms/ms_commandhandler.cpp
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* (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 "l1if.h"
|
||||
#include "ms_rx_upper.h"
|
||||
#include "syncthing.h"
|
||||
#include "ms_state.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, ×lot, &corrCode);
|
||||
if ((timeslot < 0) || (timeslot > 7)) {
|
||||
LOG(WARNING) << "bogus message on control interface";
|
||||
sprintf(response, "RSP SETSLOT 1 %d %d", timeslot, corrCode);
|
||||
return;
|
||||
}
|
||||
mStates.chanType[timeslot] = (ChannelCombination)corrCode;
|
||||
mStates.setModulus(timeslot);
|
||||
sprintf(response, "RSP SETSLOT 0 %d %d", timeslot, corrCode);
|
||||
} else if (!strcmp(command, "SETRXMASK")) {
|
||||
int slot;
|
||||
unsigned long long mask;
|
||||
sscanf(buffer, "%3s %s %d 0x%llx", cmdcheck, command, &slot, &mask);
|
||||
if ((slot < 0) || (slot > 7)) {
|
||||
sprintf(response, "RSP SETRXMASK 1");
|
||||
} else {
|
||||
mRxSlotMask[slot] = mask;
|
||||
sprintf(response, "RSP SETRXMASK 0 %d 0x%llx", slot, mask);
|
||||
}
|
||||
} else if (!strcmp(command, "SYNC")) {
|
||||
// msleep(10);
|
||||
mStates.mode = trx_mode::TRX_MODE_MS_TRACK;
|
||||
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);
|
||||
}
|
||||
211
Transceiver52M/ms/ms_rx_burst.cpp
Normal file
211
Transceiver52M/ms/ms_rx_burst.cpp
Normal 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)
|
||||
{
|
||||
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 / 32767.f);
|
||||
|
||||
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 / 2047.f);
|
||||
// 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)
|
||||
{
|
||||
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, ¶m);
|
||||
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);
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
25
Transceiver52M/ms/ms_rx_burst.h
Normal file
25
Transceiver52M/ms/ms_rx_burst.h
Normal 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);
|
||||
350
Transceiver52M/ms/ms_rx_lower.cpp
Normal file
350
Transceiver52M/ms/ms_rx_lower.cpp
Normal 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;
|
||||
}
|
||||
397
Transceiver52M/ms/ms_rx_upper.cpp
Normal file
397
Transceiver52M/ms/ms_rx_upper.cpp
Normal file
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
* (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 "l1if.h"
|
||||
#include <signalVector.h>
|
||||
#include <radioVector.h>
|
||||
#include <radioInterface.h>
|
||||
#include "grgsm_vitac/grgsm_vitac.h"
|
||||
#include "ms_state.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();
|
||||
}
|
||||
|
||||
#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();
|
||||
}
|
||||
|
||||
/* Detect SCH synchronization sequence within a burst */
|
||||
bool upper_trx::detectSCH(ms_TransceiverState *state, signalVector &burst, struct estim_burst_params *ebp)
|
||||
{
|
||||
int shift;
|
||||
sch_detect_type full;
|
||||
float mag, threshold = 4.0;
|
||||
|
||||
full = (state->mode == trx_mode::TRX_MODE_MS_TRACK) ? sch_detect_type::SCH_DETECT_NARROW :
|
||||
sch_detect_type::SCH_DETECT_FULL;
|
||||
|
||||
if (!detectSCHBurst(burst, threshold, rx_sps, full, ebp))
|
||||
return false;
|
||||
|
||||
std::clog << "SCH : Timing offset " << ebp->toa << " symbols" << std::endl;
|
||||
|
||||
mag = fabsf(ebp->toa);
|
||||
if (mag < 1.0f)
|
||||
return true;
|
||||
|
||||
shift = (int)(mag / 2.0f);
|
||||
if (!shift)
|
||||
shift++;
|
||||
|
||||
shift = ebp->toa > 0 ? shift : -shift;
|
||||
std::clog << "SCH : shift -> " << shift << " symbols" << std::endl;
|
||||
// mRadioInterface->applyOffset(shift);
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
CorrType type = TSC;
|
||||
|
||||
// tickle UL by returning null bursts if demod is skipped due to unused TS
|
||||
switch (mStates.mode) {
|
||||
case trx_mode::TRX_MODE_MS_TRACK:
|
||||
if (mStates.chanType[burst_time.TN()] == ChannelCombination::NONE_INACTIVE) {
|
||||
type = OFF;
|
||||
goto release;
|
||||
} else if (is_sch)
|
||||
type = SCH;
|
||||
else if (!is_fcch) // all ts0, but not fcch or sch..
|
||||
type = TSC;
|
||||
break;
|
||||
|
||||
case trx_mode::TRX_MODE_OFF:
|
||||
default:
|
||||
goto release;
|
||||
}
|
||||
|
||||
convert_and_scale<float, int16_t>(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / 2047.f);
|
||||
|
||||
pow = energyDetect(sv, 20 * rx_sps);
|
||||
if (pow < -1) {
|
||||
LOG(ALERT) << "Received empty burst";
|
||||
goto release;
|
||||
}
|
||||
|
||||
avg = sqrt(pow);
|
||||
|
||||
if (type == SCH) {
|
||||
std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
|
||||
int d_c0_burst_start = get_sch_chan_imp_resp(ss, &chan_imp_resp[0]);
|
||||
detect_burst(ss, &chan_imp_resp[0], d_c0_burst_start, outbin);
|
||||
|
||||
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;
|
||||
|
||||
// std::cerr << dbgout.str();
|
||||
} else {
|
||||
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;
|
||||
|
||||
release:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
trxd_from_trx response;
|
||||
response.ts = burstTime.TN();
|
||||
response.fn = htonl(burstTime.FN());
|
||||
response.rssi = RSSI;
|
||||
response.toa = htons(TOA);
|
||||
if (rxBurst) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef IPCIF
|
||||
push_d(response);
|
||||
#else
|
||||
int rv = sendto(mDataSockets, &response, sizeof(trxd_from_trx), 0, (struct sockaddr *)&datadest,
|
||||
sizeof(struct sockaddr_in));
|
||||
if (rv < 0) {
|
||||
std::cerr << "fuck, send?" << std::endl;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void upper_trx::driveTx()
|
||||
{
|
||||
#ifdef IPCIF
|
||||
auto burst = pop_d();
|
||||
if (!burst) {
|
||||
// std::cerr << "wtf no tx burst?" << std::endl;
|
||||
// exit(0);
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
trxd_to_trx buffer;
|
||||
|
||||
socklen_t addr_len = sizeof(datasrc);
|
||||
int rdln = recvfrom(mDataSockets, (void *)&buffer, sizeof(trxd_to_trx), 0, &datasrc, &addr_len);
|
||||
if (rdln < 0 && errno == EAGAIN) {
|
||||
std::cerr << "fuck, rcv?" << std::endl;
|
||||
exit(0);
|
||||
}
|
||||
if(rdln < sizeof(buffer)) // nope ind has len 6 or something like that
|
||||
return;
|
||||
|
||||
|
||||
trxd_to_trx *burst = &buffer;
|
||||
#endif
|
||||
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;
|
||||
|
||||
#ifdef IPCIF
|
||||
free(burst);
|
||||
#endif
|
||||
}
|
||||
|
||||
// __attribute__((xray_always_instrument)) static void *rx_stream_callback(struct bladerf *dev,
|
||||
// struct bladerf_stream *stream,
|
||||
// struct bladerf_metadata *meta, void *samples,
|
||||
// size_t num_samples, void *user_data)
|
||||
// {
|
||||
// struct ms_trx *trx = (struct ms_trx *)user_data;
|
||||
// return trx->rx_cb(dev, stream, meta, samples, num_samples, user_data);
|
||||
// }
|
||||
|
||||
// __attribute__((xray_always_instrument)) static void *tx_stream_callback(struct bladerf *dev,
|
||||
// struct bladerf_stream *stream,
|
||||
// struct bladerf_metadata *meta, void *samples,
|
||||
// size_t num_samples, void *user_data)
|
||||
// {
|
||||
// struct ms_trx *trx = (struct ms_trx *)user_data;
|
||||
// return BLADERF_STREAM_NO_DATA;
|
||||
// }
|
||||
|
||||
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" volatile bool gshutdown = false;
|
||||
extern "C" void init_external_transceiver(int argc, char **argv)
|
||||
{
|
||||
std::cout << "init?" << std::endl;
|
||||
trxc_main(argc, argv);
|
||||
}
|
||||
|
||||
extern "C" void stop_trx()
|
||||
{
|
||||
std::cout << "Shutting down transceiver..." << std::endl;
|
||||
}
|
||||
121
Transceiver52M/ms/ms_rx_upper.h
Normal file
121
Transceiver52M/ms/ms_rx_upper.h
Normal file
@@ -0,0 +1,121 @@
|
||||
|
||||
#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 "ms_state.h"
|
||||
|
||||
class upper_trx : public ms_trx {
|
||||
int rx_sps, tx_sps;
|
||||
|
||||
ms_TransceiverState mStates;
|
||||
|
||||
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 mDataSockets;
|
||||
sockaddr_in datadest;
|
||||
sockaddr datasrc;
|
||||
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);
|
||||
|
||||
bool detectSCH(ms_TransceiverState *state, signalVector &burst, struct estim_burst_params *ebp);
|
||||
|
||||
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;
|
||||
auto d_srcport = 6700 + 2 * 0 + 2;
|
||||
auto d_dstport = 6700 + 2 * 0 + 102;
|
||||
|
||||
openudp(&mCtrlSockets, c_srcport, "127.0.0.1");
|
||||
openudp(&mDataSockets, d_srcport, "127.0.0.1");
|
||||
resolveAddress(&ctrldest, "127.0.0.1", c_dstport);
|
||||
resolveAddress(&datadest, "127.0.0.1", d_dstport);
|
||||
};
|
||||
};
|
||||
175
Transceiver52M/ms/ms_state.h
Normal file
175
Transceiver52M/ms/ms_state.h
Normal file
@@ -0,0 +1,175 @@
|
||||
#pragma once
|
||||
|
||||
#include <radioVector.h>
|
||||
#include <signalVector.h>
|
||||
|
||||
enum class trx_mode {
|
||||
TRX_MODE_OFF,
|
||||
TRX_MODE_BTS,
|
||||
TRX_MODE_MS_ACQUIRE,
|
||||
TRX_MODE_MS_TRACK,
|
||||
};
|
||||
|
||||
enum class ChannelCombination {
|
||||
FILL, ///< Channel is transmitted, but unused
|
||||
I, ///< TCH/FS
|
||||
II, ///< TCH/HS, idle every other slot
|
||||
III, ///< TCH/HS
|
||||
IV, ///< FCCH+SCH+CCCH+BCCH, uplink RACH
|
||||
V, ///< FCCH+SCH+CCCH+BCCH+SDCCH/4+SACCH/4, uplink RACH+SDCCH/4
|
||||
VI, ///< CCCH+BCCH, uplink RACH
|
||||
VII, ///< SDCCH/8 + SACCH/8
|
||||
VIII, ///< TCH/F + FACCH/F + SACCH/M
|
||||
IX, ///< TCH/F + SACCH/M
|
||||
X, ///< TCH/FD + SACCH/MD
|
||||
XI, ///< PBCCH+PCCCH+PDTCH+PACCH+PTCCH
|
||||
XII, ///< PCCCH+PDTCH+PACCH+PTCCH
|
||||
XIII, ///< PDTCH+PACCH+PTCCH
|
||||
NONE_INACTIVE, ///< Channel is inactive, default
|
||||
LOOPBACK ///< similar go VII, used in loopback testing
|
||||
};
|
||||
|
||||
struct ms_TransceiverState {
|
||||
ms_TransceiverState() : mFreqOffsets(10), mode(trx_mode::TRX_MODE_OFF)
|
||||
{
|
||||
for (int i = 0; i < 8; i++) {
|
||||
chanType[i] = ChannelCombination::NONE_INACTIVE;
|
||||
fillerModulus[i] = 26;
|
||||
|
||||
for (int n = 0; n < 102; n++)
|
||||
fillerTable[n][i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
~ms_TransceiverState()
|
||||
{
|
||||
for (int i = 0; i < 8; i++) {
|
||||
for (int n = 0; n < 102; n++)
|
||||
delete fillerTable[n][i];
|
||||
}
|
||||
}
|
||||
|
||||
void setModulus(size_t timeslot)
|
||||
{
|
||||
switch (chanType[timeslot]) {
|
||||
case ChannelCombination::NONE_INACTIVE:
|
||||
case ChannelCombination::I:
|
||||
case ChannelCombination::II:
|
||||
case ChannelCombination::III:
|
||||
case ChannelCombination::FILL:
|
||||
fillerModulus[timeslot] = 26;
|
||||
break;
|
||||
case ChannelCombination::IV:
|
||||
case ChannelCombination::VI:
|
||||
case ChannelCombination::V:
|
||||
fillerModulus[timeslot] = 51;
|
||||
break;
|
||||
//case V:
|
||||
case ChannelCombination::VII:
|
||||
fillerModulus[timeslot] = 102;
|
||||
break;
|
||||
case ChannelCombination::XIII:
|
||||
fillerModulus[timeslot] = 52;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CorrType expectedCorrType(GSM::Time currTime, unsigned long long *mRxSlotMask)
|
||||
{
|
||||
unsigned burstTN = currTime.TN();
|
||||
unsigned burstFN = currTime.FN();
|
||||
|
||||
if (mode == trx_mode::TRX_MODE_MS_TRACK) {
|
||||
/* 102 modulus case currently unhandled */
|
||||
if (fillerModulus[burstTN] > 52)
|
||||
return OFF;
|
||||
|
||||
int modFN = burstFN % fillerModulus[burstTN];
|
||||
unsigned long long reg = (unsigned long long)1 << modFN;
|
||||
if (reg & mRxSlotMask[burstTN])
|
||||
return TSC;
|
||||
else
|
||||
return OFF;
|
||||
}
|
||||
|
||||
switch (chanType[burstTN]) {
|
||||
case ChannelCombination::NONE_INACTIVE:
|
||||
return OFF;
|
||||
break;
|
||||
case ChannelCombination::FILL:
|
||||
return IDLE;
|
||||
break;
|
||||
case ChannelCombination::I:
|
||||
return TSC;
|
||||
/*if (burstFN % 26 == 25)
|
||||
return IDLE;
|
||||
else
|
||||
return TSC;*/
|
||||
break;
|
||||
case ChannelCombination::II:
|
||||
return TSC;
|
||||
break;
|
||||
case ChannelCombination::III:
|
||||
return TSC;
|
||||
break;
|
||||
case ChannelCombination::IV:
|
||||
case ChannelCombination::VI:
|
||||
return RACH;
|
||||
break;
|
||||
case ChannelCombination::V: {
|
||||
int mod51 = burstFN % 51;
|
||||
if ((mod51 <= 36) && (mod51 >= 14))
|
||||
return RACH;
|
||||
else if ((mod51 == 4) || (mod51 == 5))
|
||||
return RACH;
|
||||
else if ((mod51 == 45) || (mod51 == 46))
|
||||
return RACH;
|
||||
else
|
||||
return TSC;
|
||||
break;
|
||||
}
|
||||
case ChannelCombination::VII:
|
||||
if ((burstFN % 51 <= 14) && (burstFN % 51 >= 12))
|
||||
return IDLE;
|
||||
else
|
||||
return TSC;
|
||||
break;
|
||||
case ChannelCombination::XIII: {
|
||||
int mod52 = burstFN % 52;
|
||||
if ((mod52 == 12) || (mod52 == 38))
|
||||
return RACH;
|
||||
else if ((mod52 == 25) || (mod52 == 51))
|
||||
return IDLE;
|
||||
else
|
||||
return TSC;
|
||||
break;
|
||||
}
|
||||
case ChannelCombination::LOOPBACK:
|
||||
if ((burstFN % 51 <= 50) && (burstFN % 51 >= 48))
|
||||
return IDLE;
|
||||
else
|
||||
return TSC;
|
||||
break;
|
||||
default:
|
||||
return OFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize a multiframe slot in the filler table */
|
||||
void init(size_t slot, signalVector *burst, bool fill);
|
||||
|
||||
ChannelCombination chanType[8];
|
||||
|
||||
/* The filler table */
|
||||
signalVector *fillerTable[102][8];
|
||||
int fillerModulus[8];
|
||||
|
||||
/* Received noise energy levels */
|
||||
avgVector mFreqOffsets;
|
||||
|
||||
/* Transceiver mode */
|
||||
trx_mode mode;
|
||||
};
|
||||
337
Transceiver52M/ms/syncthing.cpp
Normal file
337
Transceiver52M/ms/syncthing.cpp
Normal 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 / 2047.f);
|
||||
// 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, >x);
|
||||
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).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
|
||||
}
|
||||
235
Transceiver52M/ms/syncthing.h
Normal file
235
Transceiver52M/ms/syncthing.h
Normal 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;
|
||||
}
|
||||
};
|
||||
274
Transceiver52M/ms/uhd_specific.h
Normal file
274
Transceiver52M/ms/uhd_specific.h
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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)];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
294
Transceiver52M/sch.c
Normal 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
27
Transceiver52M/sch.h
Normal 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_ */
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
13
configure.ac
13
configure.ac
@@ -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 \
|
||||
|
||||
@@ -11,7 +11,7 @@ EXTRA_DIST = BitVectorTest.ok \
|
||||
LogTest.ok \
|
||||
LogTest.err
|
||||
|
||||
noinst_PROGRAMS = \
|
||||
check_PROGRAMS = \
|
||||
BitVectorTest \
|
||||
PRBSTest \
|
||||
InterthreadTest \
|
||||
|
||||
@@ -4,7 +4,7 @@ AM_CFLAGS = -Wall -I$(top_srcdir)/Transceiver52M -I$(top_srcdir)/Transceiver52M/
|
||||
|
||||
EXTRA_DIST = convolve_test.ok convolve_test_golden.h
|
||||
|
||||
noinst_PROGRAMS = \
|
||||
check_PROGRAMS = \
|
||||
convolve_test
|
||||
|
||||
convolve_test_SOURCES = convolve_test.c
|
||||
@@ -18,7 +18,7 @@ convolve_test_CFLAGS += $(SIMD_FLAGS)
|
||||
endif
|
||||
|
||||
if DEVICE_LMS
|
||||
noinst_PROGRAMS += LMSDeviceTest
|
||||
check_PROGRAMS += LMSDeviceTest
|
||||
LMSDeviceTest_SOURCES = LMSDeviceTest.cpp
|
||||
LMSDeviceTest_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LMS_LIBS)
|
||||
LMSDeviceTest_LDADD = \
|
||||
|
||||
51
trxcon/Makefile.am
Normal file
51
trxcon/Makefile.am
Normal file
@@ -0,0 +1,51 @@
|
||||
include $(top_srcdir)/Makefile.common
|
||||
|
||||
AM_CPPFLAGS = \
|
||||
$(all_includes) \
|
||||
-I$(top_srcdir)/include \
|
||||
-Iinclude \
|
||||
$(LIBOSMOCORE_CFLAGS) \
|
||||
$(LIBOSMOCODING_CFLAGS) \
|
||||
$(LIBOSMOGSM_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
AM_CFLAGS = \
|
||||
-Wall \
|
||||
-Iinclude \
|
||||
$(LIBOSMOCORE_CFLAGS) \
|
||||
$(LIBOSMOCODING_CFLAGS) \
|
||||
$(LIBOSMOGSM_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
|
||||
AM_CPPFLAGS += $(STD_DEFINES_AND_INCLUDES)
|
||||
AM_CXXFLAGS = -Wall -O3 -g -ldl -lpthread -Iinclude
|
||||
|
||||
noinst_LTLIBRARIES = libtrxcon.la
|
||||
|
||||
libtrxcon_la_SOURCES = \
|
||||
src/sched_lchan_common.c \
|
||||
src/sched_lchan_pdtch.c \
|
||||
src/sched_lchan_desc.c \
|
||||
src/sched_lchan_xcch.c \
|
||||
src/sched_lchan_tchf.c \
|
||||
src/sched_lchan_tchh.c \
|
||||
src/sched_lchan_rach.c \
|
||||
src/sched_lchan_sch.c \
|
||||
src/sched_mframe.c \
|
||||
src/sched_clck.c \
|
||||
src/sched_prim.c \
|
||||
src/sched_trx.c \
|
||||
src/l1ctl_server.c \
|
||||
src/l1ctl.c \
|
||||
src/trx_if.c \
|
||||
src/logging.c \
|
||||
src/trxcon_fsm.c \
|
||||
src/trxcon.c \
|
||||
$(NULL)
|
||||
|
||||
libtrxcon_la_LIBADD = \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOCODING_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(NULL)
|
||||
45
trxcon/configure.ac
Normal file
45
trxcon/configure.ac
Normal file
@@ -0,0 +1,45 @@
|
||||
dnl Process this file with autoconf to produce a configure script
|
||||
AC_INIT([trxcon], [0.0.0])
|
||||
AM_INIT_AUTOMAKE
|
||||
LT_INIT
|
||||
|
||||
CFLAGS="$CFLAGS -std=gnu11"
|
||||
|
||||
dnl kernel style compile messages
|
||||
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
|
||||
|
||||
dnl checks for programs
|
||||
AC_PROG_MAKE_SET
|
||||
AC_PROG_CC
|
||||
AC_PROG_INSTALL
|
||||
|
||||
dnl checks for libraries
|
||||
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore)
|
||||
PKG_CHECK_MODULES(LIBOSMOCODING, libosmocoding)
|
||||
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm)
|
||||
|
||||
dnl checks for header files
|
||||
AC_HEADER_STDC
|
||||
|
||||
AC_ARG_ENABLE(sanitize,
|
||||
[AS_HELP_STRING(
|
||||
[--enable-sanitize],
|
||||
[Compile with address sanitizer enabled],
|
||||
)], [sanitize=$enableval], [sanitize="no"])
|
||||
if test x"$sanitize" = x"yes"
|
||||
then
|
||||
CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined"
|
||||
CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
|
||||
fi
|
||||
|
||||
dnl Checks for typedefs, structures and compiler characteristics
|
||||
|
||||
AC_CONFIG_MACRO_DIRS([m4])
|
||||
AC_CONFIG_FILES([include/Makefile
|
||||
include/osmocom/Makefile
|
||||
include/osmocom/bb/Makefile
|
||||
include/osmocom/bb/l1sched/Makefile
|
||||
include/osmocom/bb/trxcon/Makefile
|
||||
src/Makefile
|
||||
Makefile])
|
||||
AC_OUTPUT
|
||||
3
trxcon/include/Makefile.am
Normal file
3
trxcon/include/Makefile.am
Normal file
@@ -0,0 +1,3 @@
|
||||
SUBDIRS = \
|
||||
osmocom \
|
||||
$(NULL)
|
||||
3
trxcon/include/osmocom/Makefile.am
Normal file
3
trxcon/include/osmocom/Makefile.am
Normal file
@@ -0,0 +1,3 @@
|
||||
SUBDIRS = \
|
||||
bb \
|
||||
$(NULL)
|
||||
4
trxcon/include/osmocom/bb/Makefile.am
Normal file
4
trxcon/include/osmocom/bb/Makefile.am
Normal file
@@ -0,0 +1,4 @@
|
||||
SUBDIRS = \
|
||||
l1sched \
|
||||
trxcon \
|
||||
$(NULL)
|
||||
4
trxcon/include/osmocom/bb/l1sched/Makefile.am
Normal file
4
trxcon/include/osmocom/bb/l1sched/Makefile.am
Normal file
@@ -0,0 +1,4 @@
|
||||
noinst_HEADERS = \
|
||||
l1sched.h \
|
||||
logging.h \
|
||||
$(NULL)
|
||||
508
trxcon/include/osmocom/bb/l1sched/l1sched.h
Normal file
508
trxcon/include/osmocom/bb/l1sched/l1sched.h
Normal file
@@ -0,0 +1,508 @@
|
||||
#pragma once
|
||||
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
#include <osmocom/gsm/gsm0502.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
|
||||
#define GSM_BURST_LEN 148
|
||||
#define GSM_BURST_PL_LEN 116
|
||||
|
||||
#define GPRS_BURST_LEN GSM_BURST_LEN
|
||||
#define EDGE_BURST_LEN 444
|
||||
|
||||
#define GPRS_L2_MAX_LEN 54
|
||||
#define EDGE_L2_MAX_LEN 155
|
||||
|
||||
#define L1SCHED_CH_LID_DEDIC 0x00
|
||||
#define L1SCHED_CH_LID_SACCH 0x40
|
||||
|
||||
/* Osmocom-specific extension for PTCCH (see 3GPP TS 45.002, section 3.3.4.2).
|
||||
* Shall be used to distinguish PTCCH and PDTCH channels on a PDCH time-slot. */
|
||||
#define L1SCHED_CH_LID_PTCCH 0x80
|
||||
|
||||
/* Is a channel related to PDCH (GPRS) */
|
||||
#define L1SCHED_CH_FLAG_PDCH (1 << 0)
|
||||
/* Should a channel be activated automatically */
|
||||
#define L1SCHED_CH_FLAG_AUTO (1 << 1)
|
||||
/* Is continuous burst transmission assumed */
|
||||
#define L1SCHED_CH_FLAG_CBTX (1 << 2)
|
||||
|
||||
#define MAX_A5_KEY_LEN (128 / 8)
|
||||
#define TRX_TS_COUNT 8
|
||||
|
||||
struct l1sched_lchan_state;
|
||||
struct l1sched_meas_set;
|
||||
struct l1sched_state;
|
||||
struct l1sched_ts;
|
||||
|
||||
enum l1sched_clck_state {
|
||||
L1SCHED_CLCK_ST_WAIT,
|
||||
L1SCHED_CLCK_ST_OK,
|
||||
};
|
||||
|
||||
enum l1sched_burst_type {
|
||||
L1SCHED_BURST_GMSK,
|
||||
L1SCHED_BURST_8PSK,
|
||||
};
|
||||
|
||||
enum l1sched_ts_prim_type {
|
||||
L1SCHED_PRIM_DATA,
|
||||
L1SCHED_PRIM_RACH8,
|
||||
L1SCHED_PRIM_RACH11,
|
||||
};
|
||||
|
||||
/**
|
||||
* These types define the different channels on a multiframe.
|
||||
* Each channel has queues and can be activated individually.
|
||||
*/
|
||||
enum l1sched_lchan_type {
|
||||
L1SCHED_IDLE = 0,
|
||||
L1SCHED_FCCH,
|
||||
L1SCHED_SCH,
|
||||
L1SCHED_BCCH,
|
||||
L1SCHED_RACH,
|
||||
L1SCHED_CCCH,
|
||||
L1SCHED_TCHF,
|
||||
L1SCHED_TCHH_0,
|
||||
L1SCHED_TCHH_1,
|
||||
L1SCHED_SDCCH4_0,
|
||||
L1SCHED_SDCCH4_1,
|
||||
L1SCHED_SDCCH4_2,
|
||||
L1SCHED_SDCCH4_3,
|
||||
L1SCHED_SDCCH8_0,
|
||||
L1SCHED_SDCCH8_1,
|
||||
L1SCHED_SDCCH8_2,
|
||||
L1SCHED_SDCCH8_3,
|
||||
L1SCHED_SDCCH8_4,
|
||||
L1SCHED_SDCCH8_5,
|
||||
L1SCHED_SDCCH8_6,
|
||||
L1SCHED_SDCCH8_7,
|
||||
L1SCHED_SACCHTF,
|
||||
L1SCHED_SACCHTH_0,
|
||||
L1SCHED_SACCHTH_1,
|
||||
L1SCHED_SACCH4_0,
|
||||
L1SCHED_SACCH4_1,
|
||||
L1SCHED_SACCH4_2,
|
||||
L1SCHED_SACCH4_3,
|
||||
L1SCHED_SACCH8_0,
|
||||
L1SCHED_SACCH8_1,
|
||||
L1SCHED_SACCH8_2,
|
||||
L1SCHED_SACCH8_3,
|
||||
L1SCHED_SACCH8_4,
|
||||
L1SCHED_SACCH8_5,
|
||||
L1SCHED_SACCH8_6,
|
||||
L1SCHED_SACCH8_7,
|
||||
L1SCHED_PDTCH,
|
||||
L1SCHED_PTCCH,
|
||||
L1SCHED_SDCCH4_CBCH,
|
||||
L1SCHED_SDCCH8_CBCH,
|
||||
_L1SCHED_CHAN_MAX
|
||||
};
|
||||
|
||||
enum l1sched_data_type {
|
||||
L1SCHED_DT_PACKET_DATA,
|
||||
L1SCHED_DT_SIGNALING,
|
||||
L1SCHED_DT_TRAFFIC,
|
||||
L1SCHED_DT_OTHER, /* SCH and RACH */
|
||||
};
|
||||
|
||||
enum l1sched_config_type {
|
||||
/*! Channel combination for a timeslot */
|
||||
L1SCHED_CFG_PCHAN_COMB,
|
||||
};
|
||||
|
||||
/* Represents a (re)configuration request */
|
||||
struct l1sched_config_req {
|
||||
enum l1sched_config_type type;
|
||||
union {
|
||||
struct {
|
||||
uint8_t tn;
|
||||
enum gsm_phys_chan_config pchan;
|
||||
} pchan_comb;
|
||||
};
|
||||
};
|
||||
|
||||
/* Represents a burst to be transmitted */
|
||||
struct l1sched_burst_req {
|
||||
uint32_t fn;
|
||||
uint8_t tn;
|
||||
uint8_t pwr;
|
||||
|
||||
/* Internally used by the scheduler */
|
||||
uint8_t bid;
|
||||
|
||||
ubit_t burst[EDGE_BURST_LEN];
|
||||
size_t burst_len;
|
||||
};
|
||||
|
||||
typedef int l1sched_lchan_rx_func(struct l1sched_lchan_state *lchan,
|
||||
uint32_t fn, uint8_t bid, const sbit_t *bits,
|
||||
const struct l1sched_meas_set *meas);
|
||||
|
||||
typedef int l1sched_lchan_tx_func(struct l1sched_lchan_state *lchan,
|
||||
struct l1sched_burst_req *br);
|
||||
|
||||
struct l1sched_lchan_desc {
|
||||
/*! Human-readable name */
|
||||
const char *name;
|
||||
/*! Human-readable description */
|
||||
const char *desc;
|
||||
|
||||
/*! Channel Number (like in RSL) */
|
||||
uint8_t chan_nr;
|
||||
/*! Link ID (like in RSL) */
|
||||
uint8_t link_id;
|
||||
/*! Sub-slot number (for SDCCH and TCH/H) */
|
||||
uint8_t ss_nr;
|
||||
/*! GSMTAP channel type (see GSMTAP_CHANNEL_*) */
|
||||
uint8_t gsmtap_chan_type;
|
||||
|
||||
/*! How much memory do we need to store bursts */
|
||||
size_t burst_buf_size;
|
||||
/*! Channel specific flags */
|
||||
uint8_t flags;
|
||||
|
||||
/*! Function to call when burst received from PHY */
|
||||
l1sched_lchan_rx_func *rx_fn;
|
||||
/*! Function to call when data received from L2 */
|
||||
l1sched_lchan_tx_func *tx_fn;
|
||||
};
|
||||
|
||||
struct l1sched_tdma_frame {
|
||||
/*! Downlink channel (slot) type */
|
||||
enum l1sched_lchan_type dl_chan;
|
||||
/*! Downlink block ID */
|
||||
uint8_t dl_bid;
|
||||
/*! Uplink channel (slot) type */
|
||||
enum l1sched_lchan_type ul_chan;
|
||||
/*! Uplink block ID */
|
||||
uint8_t ul_bid;
|
||||
};
|
||||
|
||||
struct l1sched_tdma_multiframe {
|
||||
/*! Channel combination */
|
||||
enum gsm_phys_chan_config chan_config;
|
||||
/*! Human-readable name */
|
||||
const char *name;
|
||||
/*! Repeats how many frames */
|
||||
uint8_t period;
|
||||
/*! Applies to which timeslots */
|
||||
uint8_t slotmask;
|
||||
/*! Contains which lchans */
|
||||
uint64_t lchan_mask;
|
||||
/*! Pointer to scheduling structure */
|
||||
const struct l1sched_tdma_frame *frames;
|
||||
};
|
||||
|
||||
struct l1sched_meas_set {
|
||||
/*! TDMA frame number of the first burst this set belongs to */
|
||||
uint32_t fn;
|
||||
/*! ToA256 (Timing of Arrival, 1/256 of a symbol) */
|
||||
int16_t toa256;
|
||||
/*! RSSI (Received Signal Strength Indication) */
|
||||
int8_t rssi;
|
||||
};
|
||||
|
||||
/* Simple ring buffer (up to 8 unique measurements) */
|
||||
struct l1sched_lchan_meas_hist {
|
||||
struct l1sched_meas_set buf[8];
|
||||
struct l1sched_meas_set *head;
|
||||
};
|
||||
|
||||
/* States each channel on a multiframe */
|
||||
struct l1sched_lchan_state {
|
||||
/*! Channel type */
|
||||
enum l1sched_lchan_type type;
|
||||
/*! Channel status */
|
||||
uint8_t active;
|
||||
/*! Link to a list of channels */
|
||||
struct llist_head list;
|
||||
|
||||
/*! Burst type: GMSK or 8PSK */
|
||||
enum l1sched_burst_type burst_type;
|
||||
/*! Mask of received bursts */
|
||||
uint8_t rx_burst_mask;
|
||||
/*! Mask of transmitted bursts */
|
||||
uint8_t tx_burst_mask;
|
||||
/*! Burst buffer for RX */
|
||||
sbit_t *rx_bursts;
|
||||
/*! Burst buffer for TX */
|
||||
ubit_t *tx_bursts;
|
||||
|
||||
/*! A primitive being sent */
|
||||
struct l1sched_ts_prim *prim;
|
||||
|
||||
/*! Mode for TCH channels (see GSM48_CMODE_*) */
|
||||
uint8_t tch_mode;
|
||||
/*! Training Sequence Code */
|
||||
uint8_t tsc;
|
||||
|
||||
/*! FACCH/H on downlink */
|
||||
bool dl_ongoing_facch;
|
||||
/*! pending FACCH/H blocks on Uplink */
|
||||
uint8_t ul_facch_blocks;
|
||||
|
||||
/*! Downlink measurements history */
|
||||
struct l1sched_lchan_meas_hist meas_hist;
|
||||
/*! AVG measurements of the last received block */
|
||||
struct l1sched_meas_set meas_avg;
|
||||
|
||||
/*! TDMA loss detection state */
|
||||
struct {
|
||||
/*! Last processed TDMA frame number */
|
||||
uint32_t last_proc;
|
||||
/*! Number of processed TDMA frames */
|
||||
unsigned long num_proc;
|
||||
/*! Number of lost TDMA frames */
|
||||
unsigned long num_lost;
|
||||
} tdma;
|
||||
|
||||
/*! SACCH state */
|
||||
struct {
|
||||
/*! Cached measurement report (last received) */
|
||||
uint8_t mr_cache[GSM_MACBLOCK_LEN];
|
||||
/*! Cache usage counter */
|
||||
uint8_t mr_cache_usage;
|
||||
/*! Was a MR transmitted last time? */
|
||||
bool mr_tx_last;
|
||||
} sacch;
|
||||
|
||||
/* AMR specific */
|
||||
struct {
|
||||
/*! 4 possible codecs for AMR */
|
||||
uint8_t codec[4];
|
||||
/*! Number of possible codecs */
|
||||
uint8_t codecs;
|
||||
/*! Current uplink FT index */
|
||||
uint8_t ul_ft;
|
||||
/*! Current downlink FT index */
|
||||
uint8_t dl_ft;
|
||||
/*! Current uplink CMR index */
|
||||
uint8_t ul_cmr;
|
||||
/*! Current downlink CMR index */
|
||||
uint8_t dl_cmr;
|
||||
/*! If AMR loop is enabled */
|
||||
uint8_t amr_loop;
|
||||
/*! Number of bit error rates */
|
||||
uint8_t ber_num;
|
||||
/*! Sum of bit error rates */
|
||||
float ber_sum;
|
||||
/* last received dtx frame type */
|
||||
uint8_t last_dtx;
|
||||
} amr;
|
||||
|
||||
/*! A5/X encryption state */
|
||||
struct {
|
||||
uint8_t key[MAX_A5_KEY_LEN];
|
||||
uint8_t key_len;
|
||||
uint8_t algo;
|
||||
} a5;
|
||||
|
||||
/* TS that this lchan belongs to */
|
||||
struct l1sched_ts *ts;
|
||||
};
|
||||
|
||||
struct l1sched_ts {
|
||||
/*! Timeslot index within a frame (0..7) */
|
||||
uint8_t index;
|
||||
|
||||
/*! Pointer to multiframe layout */
|
||||
const struct l1sched_tdma_multiframe *mf_layout;
|
||||
/*! Channel states for logical channels */
|
||||
struct llist_head lchans;
|
||||
/*! Queue primitives for TX */
|
||||
struct llist_head tx_prims;
|
||||
/*! Backpointer to the scheduler */
|
||||
struct l1sched_state *sched;
|
||||
};
|
||||
|
||||
/* Represents one TX primitive in the queue of l1sched_ts */
|
||||
struct l1sched_ts_prim {
|
||||
/*! Link to queue of TS */
|
||||
struct llist_head list;
|
||||
/*! Type of primitive */
|
||||
enum l1sched_ts_prim_type type;
|
||||
/*! Logical channel type */
|
||||
enum l1sched_lchan_type chan;
|
||||
/*! Payload length */
|
||||
size_t payload_len;
|
||||
/*! Payload */
|
||||
uint8_t payload[0];
|
||||
};
|
||||
|
||||
/*! Represents a RACH (8-bit or 11-bit) primitive */
|
||||
struct l1sched_ts_prim_rach {
|
||||
/*! RA value */
|
||||
uint16_t ra;
|
||||
/*! Training Sequence (only for 11-bit RA) */
|
||||
uint8_t synch_seq;
|
||||
/*! Transmission offset (how many frames to skip) */
|
||||
uint8_t offset;
|
||||
};
|
||||
|
||||
/*! Scheduler configuration */
|
||||
struct l1sched_cfg {
|
||||
/*! Logging context (used as prefix for messages) */
|
||||
const char *log_prefix;
|
||||
/*! TDMA frame-number advance */
|
||||
uint32_t fn_advance;
|
||||
};
|
||||
|
||||
/*! One scheduler instance */
|
||||
struct l1sched_state {
|
||||
/*! Clock state */
|
||||
enum l1sched_clck_state clck_state;
|
||||
/*! Local clock source */
|
||||
struct timespec clock;
|
||||
/*! Count of processed frames */
|
||||
uint32_t fn_counter_proc;
|
||||
/*! Local frame counter advance */
|
||||
uint32_t fn_counter_advance;
|
||||
/*! Count of lost frames */
|
||||
uint32_t fn_counter_lost;
|
||||
/*! Frame callback timer */
|
||||
struct osmo_timer_list clock_timer;
|
||||
/*! Frame callback */
|
||||
void (*clock_cb)(struct l1sched_state *sched);
|
||||
/*! List of timeslots maintained by this scheduler */
|
||||
struct l1sched_ts *ts[TRX_TS_COUNT];
|
||||
/*! SACCH cache (common for all lchans) */
|
||||
uint8_t sacch_cache[GSM_MACBLOCK_LEN];
|
||||
/*! BSIC value learned from SCH bursts */
|
||||
uint8_t bsic;
|
||||
/*! Logging context (used as prefix for messages) */
|
||||
const char *log_prefix;
|
||||
/*! Some private data */
|
||||
void *priv;
|
||||
};
|
||||
|
||||
extern const struct l1sched_lchan_desc l1sched_lchan_desc[_L1SCHED_CHAN_MAX];
|
||||
const struct l1sched_tdma_multiframe *l1sched_mframe_layout(
|
||||
enum gsm_phys_chan_config config, int tn);
|
||||
|
||||
/* Scheduler management functions */
|
||||
void l1sched_logging_init(int log_cat_common, int log_cat_data);
|
||||
struct l1sched_state *l1sched_alloc(void *ctx, const struct l1sched_cfg *cfg, void *priv);
|
||||
void l1sched_reset(struct l1sched_state *sched, bool reset_clock);
|
||||
void l1sched_free(struct l1sched_state *sched);
|
||||
|
||||
/* Timeslot management functions */
|
||||
struct l1sched_ts *l1sched_add_ts(struct l1sched_state *sched, int tn);
|
||||
void l1sched_del_ts(struct l1sched_state *sched, int tn);
|
||||
int l1sched_reset_ts(struct l1sched_state *sched, int tn);
|
||||
int l1sched_configure_ts(struct l1sched_state *sched, int tn,
|
||||
enum gsm_phys_chan_config config);
|
||||
int l1sched_start_ciphering(struct l1sched_ts *ts, uint8_t algo,
|
||||
const uint8_t *key, uint8_t key_len);
|
||||
|
||||
/* Logical channel management functions */
|
||||
enum gsm_phys_chan_config l1sched_chan_nr2pchan_config(uint8_t chan_nr);
|
||||
enum l1sched_lchan_type l1sched_chan_nr2lchan_type(uint8_t chan_nr,
|
||||
uint8_t link_id);
|
||||
|
||||
void l1sched_deactivate_all_lchans(struct l1sched_ts *ts);
|
||||
int l1sched_set_lchans(struct l1sched_ts *ts, uint8_t chan_nr,
|
||||
int active, uint8_t tch_mode, uint8_t tsc);
|
||||
int l1sched_activate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan);
|
||||
int l1sched_deactivate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan);
|
||||
struct l1sched_lchan_state *l1sched_find_lchan(struct l1sched_ts *ts,
|
||||
enum l1sched_lchan_type chan);
|
||||
|
||||
/* Primitive management functions */
|
||||
struct l1sched_ts_prim *l1sched_prim_push(struct l1sched_state *sched,
|
||||
enum l1sched_ts_prim_type type,
|
||||
uint8_t chan_nr, uint8_t link_id,
|
||||
const uint8_t *pl, size_t pl_len);
|
||||
|
||||
#define L1SCHED_TCH_MODE_IS_SPEECH(mode) \
|
||||
(mode == GSM48_CMODE_SPEECH_V1 \
|
||||
|| mode == GSM48_CMODE_SPEECH_EFR \
|
||||
|| mode == GSM48_CMODE_SPEECH_AMR)
|
||||
|
||||
#define L1SCHED_TCH_MODE_IS_DATA(mode) \
|
||||
(mode == GSM48_CMODE_DATA_14k5 \
|
||||
|| mode == GSM48_CMODE_DATA_12k0 \
|
||||
|| mode == GSM48_CMODE_DATA_6k0 \
|
||||
|| mode == GSM48_CMODE_DATA_3k6)
|
||||
|
||||
#define L1SCHED_CHAN_IS_TCH(chan) \
|
||||
(chan == L1SCHED_TCHF || chan == L1SCHED_TCHH_0 || chan == L1SCHED_TCHH_1)
|
||||
|
||||
#define L1SCHED_CHAN_IS_SACCH(chan) \
|
||||
(l1sched_lchan_desc[chan].link_id & L1SCHED_CH_LID_SACCH)
|
||||
|
||||
#define L1SCHED_PRIM_IS_RACH11(prim) \
|
||||
(prim->type == L1SCHED_PRIM_RACH11)
|
||||
|
||||
#define L1SCHED_PRIM_IS_RACH8(prim) \
|
||||
(prim->type == L1SCHED_PRIM_RACH8)
|
||||
|
||||
#define L1SCHED_PRIM_IS_RACH(prim) \
|
||||
(L1SCHED_PRIM_IS_RACH8(prim) || L1SCHED_PRIM_IS_RACH11(prim))
|
||||
|
||||
#define L1SCHED_PRIM_IS_TCH(prim) \
|
||||
(L1SCHED_CHAN_IS_TCH(prim->chan) && prim->payload_len != GSM_MACBLOCK_LEN)
|
||||
|
||||
#define L1SCHED_PRIM_IS_FACCH(prim) \
|
||||
(L1SCHED_CHAN_IS_TCH(prim->chan) && prim->payload_len == GSM_MACBLOCK_LEN)
|
||||
|
||||
struct l1sched_ts_prim *l1sched_prim_dequeue(struct llist_head *queue,
|
||||
uint32_t fn, struct l1sched_lchan_state *lchan);
|
||||
int l1sched_prim_dummy(struct l1sched_lchan_state *lchan);
|
||||
void l1sched_prim_drop(struct l1sched_lchan_state *lchan);
|
||||
void l1sched_prim_flush_queue(struct llist_head *list);
|
||||
|
||||
int l1sched_handle_rx_burst(struct l1sched_state *sched, uint8_t tn,
|
||||
uint32_t fn, sbit_t *bits, uint16_t nbits,
|
||||
const struct l1sched_meas_set *meas);
|
||||
|
||||
/* Shared declarations for lchan handlers */
|
||||
extern const uint8_t l1sched_nb_training_bits[8][26];
|
||||
|
||||
const char *l1sched_burst_mask2str(const uint8_t *mask, int bits);
|
||||
size_t l1sched_bad_frame_ind(uint8_t *l2, struct l1sched_lchan_state *lchan);
|
||||
|
||||
/* Interleaved TCH/H block TDMA frame mapping */
|
||||
bool l1sched_tchh_block_map_fn(enum l1sched_lchan_type chan,
|
||||
uint32_t fn, bool ul, bool facch, bool start);
|
||||
|
||||
#define l1sched_tchh_traffic_start(chan, fn, ul) \
|
||||
l1sched_tchh_block_map_fn(chan, fn, ul, 0, 1)
|
||||
#define l1sched_tchh_traffic_end(chan, fn, ul) \
|
||||
l1sched_tchh_block_map_fn(chan, fn, ul, 0, 0)
|
||||
|
||||
#define l1sched_tchh_facch_start(chan, fn, ul) \
|
||||
l1sched_tchh_block_map_fn(chan, fn, ul, 1, 1)
|
||||
#define l1sched_tchh_facch_end(chan, fn, ul) \
|
||||
l1sched_tchh_block_map_fn(chan, fn, ul, 1, 0)
|
||||
|
||||
/* Measurement history */
|
||||
void l1sched_lchan_meas_push(struct l1sched_lchan_state *lchan, const struct l1sched_meas_set *meas);
|
||||
void l1sched_lchan_meas_avg(struct l1sched_lchan_state *lchan, unsigned int n);
|
||||
|
||||
int l1sched_clck_handle(struct l1sched_state *sched, uint32_t fn);
|
||||
void l1sched_clck_reset(struct l1sched_state *sched);
|
||||
|
||||
/* External L1 API, must be implemented by the API user */
|
||||
int l1sched_handle_config_req(struct l1sched_state *sched,
|
||||
const struct l1sched_config_req *cr);
|
||||
int l1sched_handle_burst_req(struct l1sched_state *sched,
|
||||
const struct l1sched_burst_req *br);
|
||||
|
||||
/* External L2 API, must be implemented by the API user */
|
||||
int l1sched_handle_data_ind(struct l1sched_lchan_state *lchan,
|
||||
const uint8_t *data, size_t data_len,
|
||||
int n_errors, int n_bits_total,
|
||||
enum l1sched_data_type dt);
|
||||
int l1sched_handle_data_cnf(struct l1sched_lchan_state *lchan,
|
||||
uint32_t fn, enum l1sched_data_type dt);
|
||||
35
trxcon/include/osmocom/bb/l1sched/logging.h
Normal file
35
trxcon/include/osmocom/bb/l1sched/logging.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
extern int l1sched_log_cat_common;
|
||||
extern int l1sched_log_cat_data;
|
||||
|
||||
/* Messages using l1sched_state as the context */
|
||||
#define LOGP_SCHED_CAT(sched, cat, level, fmt, args...) \
|
||||
LOGP(l1sched_log_cat_##cat, level, "%s" fmt, \
|
||||
(sched)->log_prefix, ## args)
|
||||
|
||||
/* Common messages using l1sched_state as the context */
|
||||
#define LOGP_SCHEDC(sched, level, fmt, args...) \
|
||||
LOGP_SCHED_CAT(sched, common, level, fmt, ## args)
|
||||
|
||||
/* Data messages using l1sched_state as the context */
|
||||
#define LOGP_SCHEDD(sched, level, fmt, args...) \
|
||||
LOGP_SCHED_CAT(sched, common, level, fmt, ## args)
|
||||
|
||||
|
||||
#define LOGP_LCHAN_NAME_FMT "TS%u-%s"
|
||||
#define LOGP_LCHAN_NAME_ARGS(lchan) \
|
||||
(lchan)->ts->index, l1sched_lchan_desc[(lchan)->type].name
|
||||
|
||||
/* Messages using l1sched_lchan_state as the context */
|
||||
#define LOGP_LCHAN_CAT(lchan, cat, level, fmt, args...) \
|
||||
LOGP_SCHED_CAT((lchan)->ts->sched, cat, level, LOGP_LCHAN_NAME_FMT " " fmt, \
|
||||
LOGP_LCHAN_NAME_ARGS(lchan), ## args)
|
||||
|
||||
/* Common messages using l1sched_lchan_state as the context */
|
||||
#define LOGP_LCHANC(lchan, level, fmt, args...) \
|
||||
LOGP_LCHAN_CAT(lchan, common, level, fmt, ## args)
|
||||
|
||||
/* Data messages using l1sched_lchan_state as the context */
|
||||
#define LOGP_LCHAND(lchan, level, fmt, args...) \
|
||||
LOGP_LCHAN_CAT(lchan, data, level, fmt, ## args)
|
||||
9
trxcon/include/osmocom/bb/trxcon/Makefile.am
Normal file
9
trxcon/include/osmocom/bb/trxcon/Makefile.am
Normal file
@@ -0,0 +1,9 @@
|
||||
noinst_HEADERS = \
|
||||
l1ctl_proto.h \
|
||||
l1ctl_server.h \
|
||||
l1ctl.h \
|
||||
sched_utils.h \
|
||||
trx_if.h \
|
||||
logging.h \
|
||||
trxcon.h \
|
||||
$(NULL)
|
||||
27
trxcon/include/osmocom/bb/trxcon/l1ctl.h
Normal file
27
trxcon/include/osmocom/bb/trxcon/l1ctl.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
|
||||
#include <osmocom/bb/trxcon/l1ctl_server.h>
|
||||
#include <osmocom/bb/trxcon/l1ctl_proto.h>
|
||||
|
||||
/* Event handlers */
|
||||
int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg);
|
||||
|
||||
int l1ctl_tx_fbsb_conf(struct l1ctl_client *l1c, uint16_t band_arfcn, uint8_t bsic);
|
||||
int l1ctl_tx_fbsb_fail(struct l1ctl_client *l1c, uint16_t band_arfcn);
|
||||
int l1ctl_tx_ccch_mode_conf(struct l1ctl_client *l1c, uint8_t mode);
|
||||
int l1ctl_tx_pm_conf(struct l1ctl_client *l1c, uint16_t band_arfcn,
|
||||
int dbm, int last);
|
||||
int l1ctl_tx_reset_conf(struct l1ctl_client *l1c, uint8_t type);
|
||||
int l1ctl_tx_reset_ind(struct l1ctl_client *l1c, uint8_t type);
|
||||
|
||||
int l1ctl_tx_dt_ind(struct l1ctl_client *l1c,
|
||||
const struct l1ctl_info_dl *dl_info,
|
||||
const uint8_t *l2, size_t l2_len,
|
||||
bool traffic);
|
||||
int l1ctl_tx_dt_conf(struct l1ctl_client *l1c,
|
||||
struct l1ctl_info_dl *data, bool traffic);
|
||||
int l1ctl_tx_rach_conf(struct l1ctl_client *l1c,
|
||||
uint16_t band_arfcn, uint32_t fn);
|
||||
389
trxcon/include/osmocom/bb/trxcon/l1ctl_proto.h
Normal file
389
trxcon/include/osmocom/bb/trxcon/l1ctl_proto.h
Normal file
@@ -0,0 +1,389 @@
|
||||
/* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#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 */
|
||||
struct { /* 3GPP TS 08.58 9.3.52, 3GPP TS 44.018 10.5.2.21aa */
|
||||
uint8_t start_codec;
|
||||
uint8_t codecs_bitmask;
|
||||
} amr;
|
||||
} __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 */
|
||||
struct { /* 3GPP TS 08.58 9.3.52, 3GPP TS 44.018 10.5.2.21aa */
|
||||
uint8_t start_codec;
|
||||
uint8_t codecs_bitmask;
|
||||
} amr;
|
||||
} __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__ */
|
||||
67
trxcon/include/osmocom/bb/trxcon/l1ctl_server.h
Normal file
67
trxcon/include/osmocom/bb/trxcon/l1ctl_server.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <osmocom/core/write_queue.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
|
||||
#define L1CTL_LENGTH 256
|
||||
#define L1CTL_HEADROOM 32
|
||||
|
||||
/**
|
||||
* Each L1CTL message gets its own length pushed
|
||||
* as two bytes in front before sending.
|
||||
*/
|
||||
#define L1CTL_MSG_LEN_FIELD 2
|
||||
|
||||
struct l1ctl_client;
|
||||
|
||||
typedef int l1ctl_conn_data_func(struct l1ctl_client *, struct msgb *);
|
||||
typedef void l1ctl_conn_state_func(struct l1ctl_client *);
|
||||
|
||||
struct l1ctl_server_cfg {
|
||||
/* UNIX socket path to listen on */
|
||||
const char *sock_path;
|
||||
/* maximum number of connected clients */
|
||||
unsigned int num_clients_max;
|
||||
/* functions to be called on various events */
|
||||
l1ctl_conn_data_func *conn_read_cb; /* mandatory */
|
||||
l1ctl_conn_state_func *conn_accept_cb; /* optional */
|
||||
l1ctl_conn_state_func *conn_close_cb; /* optional */
|
||||
};
|
||||
|
||||
struct l1ctl_server {
|
||||
/* list of connected clients */
|
||||
struct llist_head clients;
|
||||
/* number of connected clients */
|
||||
unsigned int num_clients;
|
||||
/* used for client ID generation */
|
||||
unsigned int next_client_id;
|
||||
/* socket on which we listen for connections */
|
||||
struct osmo_fd ofd;
|
||||
/* server configuration */
|
||||
const struct l1ctl_server_cfg *cfg;
|
||||
};
|
||||
|
||||
struct l1ctl_client {
|
||||
/* list head in l1ctl_server.clients */
|
||||
struct llist_head list;
|
||||
/* struct l1ctl_server we belong to */
|
||||
struct l1ctl_server *server;
|
||||
/* client's write queue */
|
||||
struct osmo_wqueue wq;
|
||||
/* logging context (used as prefix for messages) */
|
||||
const char *log_prefix;
|
||||
/* unique client ID */
|
||||
unsigned int id;
|
||||
/* some private data */
|
||||
void *priv;
|
||||
};
|
||||
|
||||
struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg);
|
||||
void l1ctl_server_free(struct l1ctl_server *server);
|
||||
|
||||
int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg);
|
||||
void l1ctl_client_conn_close(struct l1ctl_client *client);
|
||||
15
trxcon/include/osmocom/bb/trxcon/logging.h
Normal file
15
trxcon/include/osmocom/bb/trxcon/logging.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
enum {
|
||||
DAPP,
|
||||
DL1C,
|
||||
DL1D,
|
||||
DTRXC,
|
||||
DTRXD,
|
||||
DSCH,
|
||||
DSCHD,
|
||||
};
|
||||
|
||||
int trx_log_init(void *tall_ctx, const char *category_mask);
|
||||
62
trxcon/include/osmocom/bb/trxcon/sched_utils.h
Normal file
62
trxcon/include/osmocom/bb/trxcon/sched_utils.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/* Auxiliary scheduler utilities.
|
||||
*
|
||||
* (C) 2017 by Harald Welte <laforge@gnumonks.org>
|
||||
* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/*! determine whether an uplink AMR block is CMI according to 3GPP TS 45.009.
|
||||
* \param[in] fn_begin frame number of the beginning of the block.
|
||||
* \returns true in case of CMI; false otherwise. */
|
||||
static inline bool ul_amr_fn_is_cmi(uint32_t fn_begin)
|
||||
{
|
||||
switch (fn_begin % 26) {
|
||||
/*! See also: 3GPP TS 45.009, section 3.2.1.3 Transmitter/Receiver Synchronisation */
|
||||
/* valid for AHS subslot 0 and AFS: */
|
||||
case 0:
|
||||
case 8:
|
||||
case 17:
|
||||
/* valid for AHS subslot 1: */
|
||||
case 1:
|
||||
case 9:
|
||||
case 18:
|
||||
return true;
|
||||
break;
|
||||
/* Complementary values for sanity check */
|
||||
/* valid for AHS subslot 0 and AFS: */
|
||||
case 4:
|
||||
case 13:
|
||||
case 21:
|
||||
/* valid for AHS subslot 1: */
|
||||
case 5:
|
||||
case 14:
|
||||
case 22:
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
76
trxcon/include/osmocom/bb/trxcon/trx_if.h
Normal file
76
trxcon/include/osmocom/bb/trxcon/trx_if.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
|
||||
#define TRXC_BUF_SIZE 1024
|
||||
#define TRXD_BUF_SIZE 512
|
||||
|
||||
/* Forward declaration to avoid mutual include */
|
||||
struct l1sched_burst_req;
|
||||
struct trxcon_inst;
|
||||
|
||||
enum trx_fsm_states {
|
||||
TRX_STATE_OFFLINE = 0,
|
||||
TRX_STATE_IDLE,
|
||||
TRX_STATE_ACTIVE,
|
||||
TRX_STATE_RSP_WAIT,
|
||||
};
|
||||
|
||||
struct trx_instance {
|
||||
/* trxcon instance we belong to */
|
||||
struct trxcon_inst *trxcon;
|
||||
|
||||
struct osmo_fd trx_ofd_ctrl;
|
||||
struct osmo_fd trx_ofd_data;
|
||||
|
||||
struct osmo_timer_list trx_ctrl_timer;
|
||||
struct llist_head trx_ctrl_list;
|
||||
struct osmo_fsm_inst *fi;
|
||||
|
||||
/* 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;
|
||||
};
|
||||
|
||||
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(struct trxcon_inst *trxcon,
|
||||
const char *local_host, const char *remote_host, uint16_t port);
|
||||
void trx_if_flush_ctrl(struct trx_instance *trx);
|
||||
void trx_if_close(struct trx_instance *trx);
|
||||
|
||||
int trx_if_cmd_poweron(struct trx_instance *trx);
|
||||
int trx_if_cmd_poweroff(struct trx_instance *trx);
|
||||
int trx_if_cmd_echo(struct trx_instance *trx);
|
||||
|
||||
int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta);
|
||||
|
||||
int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn);
|
||||
int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn);
|
||||
|
||||
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn,
|
||||
enum gsm_phys_chan_config pchan);
|
||||
int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn, uint8_t maio,
|
||||
const uint16_t *ma, size_t ma_len);
|
||||
|
||||
int trx_if_cmd_measure(struct trx_instance *trx,
|
||||
uint16_t band_arfcn_start, uint16_t band_arfcn_stop);
|
||||
|
||||
int trx_if_tx_burst(struct trx_instance *trx,
|
||||
const struct l1sched_burst_req *br);
|
||||
int trx_if_cmd_sync(struct trx_instance *trx);
|
||||
169
trxcon/include/osmocom/bb/trxcon/trxcon.h
Normal file
169
trxcon/include/osmocom/bb/trxcon/trxcon.h
Normal file
@@ -0,0 +1,169 @@
|
||||
#pragma once
|
||||
|
||||
struct l1sched_state;
|
||||
|
||||
extern struct osmo_fsm trxcon_fsm_def;
|
||||
|
||||
enum trxcon_fsm_states {
|
||||
TRXCON_ST_RESET,
|
||||
TRXCON_ST_FULL_POWER_SCAN,
|
||||
TRXCON_ST_FBSB_SEARCH,
|
||||
TRXCON_ST_BCCH_CCCH,
|
||||
TRXCON_ST_DEDICATED,
|
||||
TRXCON_ST_PACKET_DATA,
|
||||
};
|
||||
|
||||
enum trxcon_fsm_events {
|
||||
TRXCON_EV_PHYIF_FAILURE,
|
||||
TRXCON_EV_L2IF_FAILURE,
|
||||
TRXCON_EV_RESET_FULL_REQ,
|
||||
TRXCON_EV_RESET_SCHED_REQ,
|
||||
TRXCON_EV_FULL_POWER_SCAN_REQ,
|
||||
TRXCON_EV_FULL_POWER_SCAN_RES,
|
||||
TRXCON_EV_FBSB_SEARCH_REQ,
|
||||
TRXCON_EV_FBSB_SEARCH_RES,
|
||||
TRXCON_EV_SET_CCCH_MODE_REQ,
|
||||
TRXCON_EV_SET_TCH_MODE_REQ,
|
||||
TRXCON_EV_SET_PHY_CONFIG_REQ,
|
||||
TRXCON_EV_TX_ACCESS_BURST_REQ,
|
||||
TRXCON_EV_UPDATE_SACCH_CACHE_REQ,
|
||||
TRXCON_EV_DEDICATED_ESTABLISH_REQ,
|
||||
TRXCON_EV_DEDICATED_RELEASE_REQ,
|
||||
TRXCON_EV_TX_TRAFFIC_REQ,
|
||||
TRXCON_EV_RX_TRAFFIC_IND,
|
||||
TRXCON_EV_TX_DATA_REQ,
|
||||
TRXCON_EV_RX_DATA_IND,
|
||||
TRXCON_EV_CRYPTO_REQ,
|
||||
};
|
||||
|
||||
/* param of TRXCON_EV_FULL_POWER_SCAN_REQ */
|
||||
struct trxcon_param_full_power_scan_req {
|
||||
uint16_t band_arfcn_start;
|
||||
uint16_t band_arfcn_stop;
|
||||
};
|
||||
|
||||
/* param of TRXCON_EV_FULL_POWER_SCAN_RES */
|
||||
struct trxcon_param_full_power_scan_res {
|
||||
bool last_result;
|
||||
uint16_t band_arfcn;
|
||||
int dbm;
|
||||
};
|
||||
|
||||
/* param of TRXCON_EV_FBSB_SEARCH_REQ */
|
||||
struct trxcon_param_fbsb_search_req {
|
||||
uint16_t band_arfcn;
|
||||
uint16_t timeout_ms;
|
||||
uint8_t pchan_config;
|
||||
};
|
||||
|
||||
/* param of TRXCON_EV_SET_{CCCH,TCH}_MODE_REQ */
|
||||
struct trxcon_param_set_ccch_tch_mode_req {
|
||||
uint8_t mode;
|
||||
struct {
|
||||
uint8_t start_codec;
|
||||
uint8_t codecs_bitmask;
|
||||
} amr;
|
||||
bool applied;
|
||||
};
|
||||
|
||||
/* param of TRXCON_EV_SET_PHY_CONFIG_REQ */
|
||||
struct trxcon_param_set_phy_config_req {
|
||||
enum {
|
||||
TRXCON_PHY_CFGT_PCHAN_COMB,
|
||||
TRXCON_PHY_CFGT_TX_PARAMS,
|
||||
} type;
|
||||
union {
|
||||
struct {
|
||||
uint8_t tn;
|
||||
uint8_t pchan;
|
||||
} pchan_comb;
|
||||
struct {
|
||||
uint8_t timing_advance;
|
||||
uint8_t tx_power;
|
||||
} tx_params;
|
||||
};
|
||||
};
|
||||
|
||||
/* param of TRXCON_EV_TX_{TRAFFIC,DATA}_REQ */
|
||||
struct trxcon_param_tx_traffic_data_req {
|
||||
uint8_t chan_nr;
|
||||
uint8_t link_id;
|
||||
size_t data_len;
|
||||
const uint8_t *data;
|
||||
};
|
||||
|
||||
/* param of TRXCON_EV_RX_{TRAFFIC,DATA}_IND */
|
||||
struct trxcon_param_rx_traffic_data_ind {
|
||||
uint8_t chan_nr;
|
||||
uint8_t link_id;
|
||||
uint32_t frame_nr;
|
||||
int16_t toa256;
|
||||
int8_t rssi;
|
||||
int n_errors;
|
||||
int n_bits_total;
|
||||
size_t data_len;
|
||||
const uint8_t *data;
|
||||
};
|
||||
|
||||
/* param of TRXCON_EV_TX_ACCESS_BURST_REQ */
|
||||
struct trxcon_param_tx_access_burst_req {
|
||||
uint8_t chan_nr;
|
||||
uint8_t link_id;
|
||||
uint8_t offset;
|
||||
uint8_t synch_seq;
|
||||
uint16_t ra;
|
||||
bool is_11bit;
|
||||
};
|
||||
|
||||
/* param of TRXCON_EV_DEDICATED_ESTABLISH_REQ */
|
||||
struct trxcon_param_dedicated_establish_req {
|
||||
uint8_t chan_nr;
|
||||
uint8_t tch_mode;
|
||||
uint8_t tsc;
|
||||
|
||||
bool hopping;
|
||||
union {
|
||||
struct { /* hopping=false */
|
||||
uint16_t band_arfcn;
|
||||
} h0;
|
||||
struct { /* hopping=true */
|
||||
uint8_t hsn;
|
||||
uint8_t maio;
|
||||
uint8_t n;
|
||||
uint16_t ma[64];
|
||||
} h1;
|
||||
};
|
||||
};
|
||||
|
||||
/* param of TRXCON_EV_CRYPTO_REQ */
|
||||
struct trxcon_param_crypto_req {
|
||||
uint8_t chan_nr;
|
||||
uint8_t a5_algo; /* 0 is A5/0 */
|
||||
uint8_t key_len;
|
||||
const uint8_t *key;
|
||||
};
|
||||
|
||||
struct trxcon_inst {
|
||||
struct osmo_fsm_inst *fi;
|
||||
unsigned int id;
|
||||
|
||||
/* Logging context for sched and l1c */
|
||||
const char *log_prefix;
|
||||
|
||||
/* The L1 scheduler */
|
||||
struct l1sched_state *sched;
|
||||
/* PHY interface (e.g. TRXC/TRXD) */
|
||||
void *phyif;
|
||||
/* L2 interface (e.g. L1CTL) */
|
||||
void *l2if;
|
||||
|
||||
/* L1 parameters */
|
||||
struct {
|
||||
uint16_t band_arfcn;
|
||||
uint8_t tx_power;
|
||||
int8_t ta;
|
||||
} l1p;
|
||||
};
|
||||
|
||||
struct trxcon_inst *trxcon_inst_alloc(void *ctx, unsigned int id);
|
||||
void trxcon_inst_free(struct trxcon_inst *trxcon);
|
||||
58
trxcon/src/Makefile.am
Normal file
58
trxcon/src/Makefile.am
Normal file
@@ -0,0 +1,58 @@
|
||||
include $(top_srcdir)/Makefile.common
|
||||
|
||||
AM_CPPFLAGS = \
|
||||
$(all_includes) \
|
||||
-I$(top_srcdir)/include \
|
||||
$(NULL)
|
||||
|
||||
AM_CFLAGS = \
|
||||
-Wall \
|
||||
$(LIBOSMOCORE_CFLAGS) \
|
||||
$(LIBOSMOCODING_CFLAGS) \
|
||||
$(LIBOSMOGSM_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
|
||||
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
|
||||
AM_CXXFLAGS = -Wall -O3 -g -ldl -lpthread
|
||||
|
||||
noinst_LTLIBRARIES = libl1sched.la
|
||||
|
||||
libl1sched_la_SOURCES = \
|
||||
sched_lchan_common.c \
|
||||
sched_lchan_pdtch.c \
|
||||
sched_lchan_desc.c \
|
||||
sched_lchan_xcch.c \
|
||||
sched_lchan_tchf.c \
|
||||
sched_lchan_tchh.c \
|
||||
sched_lchan_rach.c \
|
||||
sched_lchan_sch.c \
|
||||
sched_mframe.c \
|
||||
sched_clck.c \
|
||||
sched_prim.c \
|
||||
sched_trx.c \
|
||||
$(NULL)
|
||||
|
||||
libl1sched_la_LIBADD = \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOCODING_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(NULL)
|
||||
|
||||
|
||||
noinst_LTLIBRARIES = libtrxcon.la
|
||||
|
||||
libtrxcon_SOURCES = \
|
||||
l1ctl_server.c \
|
||||
l1ctl.c \
|
||||
trx_if.c \
|
||||
logging.c \
|
||||
trxcon_fsm.c \
|
||||
trxcon.c \
|
||||
$(NULL)
|
||||
|
||||
libtrxcon_LDADD = \
|
||||
libl1sched.la \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(NULL)
|
||||
807
trxcon/src/l1ctl.c
Normal file
807
trxcon/src/l1ctl.c
Normal file
@@ -0,0 +1,807 @@
|
||||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* GSM L1 control interface handlers
|
||||
*
|
||||
* (C) 2014 by Sylvain Munaut <tnt@246tNt.com>
|
||||
* (C) 2016-2022 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
* Contributions by sysmocom - s.f.m.c. GmbH
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/select.h>
|
||||
|
||||
#include <osmocom/gsm/gsm0502.h>
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
#include <osmocom/gsm/protocol/gsm_08_58.h>
|
||||
|
||||
#include <osmocom/bb/trxcon/logging.h>
|
||||
#include <osmocom/bb/trxcon/l1ctl_server.h>
|
||||
#include <osmocom/bb/trxcon/l1ctl_proto.h>
|
||||
#include <osmocom/bb/trxcon/trxcon.h>
|
||||
|
||||
static const char *arfcn2band_name(uint16_t arfcn)
|
||||
{
|
||||
enum gsm_band band;
|
||||
|
||||
if (gsm_arfcn2band_rc(arfcn, &band) < 0)
|
||||
return "(invalid)";
|
||||
|
||||
return gsm_band_name(band);
|
||||
}
|
||||
|
||||
static struct msgb *l1ctl_alloc_msg(uint8_t msg_type)
|
||||
{
|
||||
struct l1ctl_hdr *l1h;
|
||||
struct msgb *msg;
|
||||
|
||||
/**
|
||||
* Each L1CTL message gets its own length pushed in front
|
||||
* before sending. This is why we need this small headroom.
|
||||
*/
|
||||
msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_MSG_LEN_FIELD,
|
||||
L1CTL_MSG_LEN_FIELD, "l1ctl_tx_msg");
|
||||
if (!msg) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
msg->l1h = msgb_put(msg, sizeof(*l1h));
|
||||
l1h = (struct l1ctl_hdr *) msg->l1h;
|
||||
l1h->msg_type = msg_type;
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
int l1ctl_tx_pm_conf(struct l1ctl_client *l1c, uint16_t band_arfcn,
|
||||
int dbm, int last)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_pm_conf *pmc;
|
||||
struct msgb *msg;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_PM_CONF);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_DEBUG,
|
||||
"Send PM Conf (%s %d = %d dBm)\n",
|
||||
arfcn2band_name(band_arfcn),
|
||||
band_arfcn & ~ARFCN_FLAG_MASK, dbm);
|
||||
|
||||
pmc = (struct l1ctl_pm_conf *) msgb_put(msg, sizeof(*pmc));
|
||||
pmc->band_arfcn = htons(band_arfcn);
|
||||
pmc->pm[0] = dbm2rxlev(dbm);
|
||||
pmc->pm[1] = 0;
|
||||
|
||||
if (last) {
|
||||
struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->l1h;
|
||||
l1h->flags |= L1CTL_F_DONE;
|
||||
}
|
||||
|
||||
return l1ctl_client_send(l1c, msg);
|
||||
}
|
||||
|
||||
int l1ctl_tx_reset_ind(struct l1ctl_client *l1c, uint8_t type)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct msgb *msg;
|
||||
struct l1ctl_reset *res;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_RESET_IND);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_DEBUG, "Send Reset Ind (%u)\n", type);
|
||||
|
||||
res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
|
||||
res->type = type;
|
||||
|
||||
return l1ctl_client_send(l1c, msg);
|
||||
}
|
||||
|
||||
int l1ctl_tx_reset_conf(struct l1ctl_client *l1c, uint8_t type)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct msgb *msg;
|
||||
struct l1ctl_reset *res;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_RESET_CONF);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_DEBUG, "Send Reset Conf (%u)\n", type);
|
||||
res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
|
||||
res->type = type;
|
||||
|
||||
return l1ctl_client_send(l1c, msg);
|
||||
}
|
||||
|
||||
static struct l1ctl_info_dl *put_dl_info_hdr(struct msgb *msg,
|
||||
const struct l1ctl_info_dl *dl_info)
|
||||
{
|
||||
size_t len = sizeof(struct l1ctl_info_dl);
|
||||
struct l1ctl_info_dl *dl = (struct l1ctl_info_dl *) msgb_put(msg, len);
|
||||
|
||||
if (dl_info) /* Copy DL info provided by handler */
|
||||
memcpy(dl, dl_info, len);
|
||||
else /* Init DL info header */
|
||||
memset(dl, 0x00, len);
|
||||
|
||||
return dl;
|
||||
}
|
||||
|
||||
/* Fill in FBSB payload: BSIC and sync result */
|
||||
static struct l1ctl_fbsb_conf *fbsb_conf_make(struct msgb *msg, uint8_t result, uint8_t bsic)
|
||||
{
|
||||
struct l1ctl_fbsb_conf *conf = (struct l1ctl_fbsb_conf *) msgb_put(msg, sizeof(*conf));
|
||||
|
||||
conf->result = result;
|
||||
conf->bsic = bsic;
|
||||
|
||||
return conf;
|
||||
}
|
||||
|
||||
int l1ctl_tx_fbsb_fail(struct l1ctl_client *l1c, uint16_t band_arfcn)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_info_dl *dl;
|
||||
struct msgb *msg;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
|
||||
if (msg == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
dl = put_dl_info_hdr(msg, NULL);
|
||||
|
||||
/* Fill in current ARFCN */
|
||||
dl->band_arfcn = htons(band_arfcn);
|
||||
|
||||
fbsb_conf_make(msg, 255, 0);
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_DEBUG, "Send FBSB Conf (timeout)\n");
|
||||
|
||||
return l1ctl_client_send(l1c, msg);
|
||||
}
|
||||
|
||||
int l1ctl_tx_fbsb_conf(struct l1ctl_client *l1c, uint16_t band_arfcn, uint8_t bsic)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_fbsb_conf *conf;
|
||||
struct l1ctl_info_dl *dl;
|
||||
struct msgb *msg;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
|
||||
if (msg == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
dl = put_dl_info_hdr(msg, NULL);
|
||||
|
||||
/* Fill in current ARFCN */
|
||||
dl->band_arfcn = htons(band_arfcn);
|
||||
|
||||
conf = fbsb_conf_make(msg, 0, bsic);
|
||||
|
||||
/* FIXME: set proper value */
|
||||
conf->initial_freq_err = 0;
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_DEBUG,
|
||||
"Send FBSB Conf (result=%u, bsic=%u)\n",
|
||||
conf->result, conf->bsic);
|
||||
|
||||
return l1ctl_client_send(l1c, msg);
|
||||
}
|
||||
|
||||
int l1ctl_tx_ccch_mode_conf(struct l1ctl_client *l1c, uint8_t mode)
|
||||
{
|
||||
struct l1ctl_ccch_mode_conf *conf;
|
||||
struct msgb *msg;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_CCCH_MODE_CONF);
|
||||
if (msg == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
conf = (struct l1ctl_ccch_mode_conf *) msgb_put(msg, sizeof(*conf));
|
||||
conf->ccch_mode = mode;
|
||||
|
||||
return l1ctl_client_send(l1c, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles both L1CTL_DATA_IND and L1CTL_TRAFFIC_IND.
|
||||
*/
|
||||
int l1ctl_tx_dt_ind(struct l1ctl_client *l1c,
|
||||
const struct l1ctl_info_dl *dl_info,
|
||||
const uint8_t *l2, size_t l2_len,
|
||||
bool traffic)
|
||||
{
|
||||
struct msgb *msg;
|
||||
uint8_t *msg_l2;
|
||||
|
||||
msg = l1ctl_alloc_msg(traffic ?
|
||||
L1CTL_TRAFFIC_IND : L1CTL_DATA_IND);
|
||||
if (msg == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
put_dl_info_hdr(msg, dl_info);
|
||||
|
||||
/* Copy the L2 payload if preset */
|
||||
if (l2 && l2_len > 0) {
|
||||
msg_l2 = (uint8_t *) msgb_put(msg, l2_len);
|
||||
memcpy(msg_l2, l2, l2_len);
|
||||
}
|
||||
|
||||
/* Put message to upper layers */
|
||||
return l1ctl_client_send(l1c, msg);
|
||||
}
|
||||
|
||||
int l1ctl_tx_rach_conf(struct l1ctl_client *l1c,
|
||||
uint16_t band_arfcn, uint32_t fn)
|
||||
{
|
||||
struct l1ctl_info_dl *dl;
|
||||
struct msgb *msg;
|
||||
|
||||
msg = l1ctl_alloc_msg(L1CTL_RACH_CONF);
|
||||
if (msg == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
dl = put_dl_info_hdr(msg, NULL);
|
||||
memset(dl, 0x00, sizeof(*dl));
|
||||
|
||||
dl->band_arfcn = htons(band_arfcn);
|
||||
dl->frame_nr = htonl(fn);
|
||||
|
||||
return l1ctl_client_send(l1c, msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles both L1CTL_DATA_CONF and L1CTL_TRAFFIC_CONF.
|
||||
*/
|
||||
int l1ctl_tx_dt_conf(struct l1ctl_client *l1c,
|
||||
struct l1ctl_info_dl *data, bool traffic)
|
||||
{
|
||||
struct msgb *msg;
|
||||
|
||||
msg = l1ctl_alloc_msg(traffic ?
|
||||
L1CTL_TRAFFIC_CONF : L1CTL_DATA_CONF);
|
||||
if (msg == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Copy DL frame header from source message */
|
||||
put_dl_info_hdr(msg, data);
|
||||
|
||||
return l1ctl_client_send(l1c, msg);
|
||||
}
|
||||
|
||||
static enum gsm_phys_chan_config l1ctl_ccch_mode2pchan_config(enum ccch_mode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
/* TODO: distinguish extended BCCH */
|
||||
case CCCH_MODE_NON_COMBINED:
|
||||
case CCCH_MODE_NONE:
|
||||
return GSM_PCHAN_CCCH;
|
||||
|
||||
case CCCH_MODE_COMBINED:
|
||||
return GSM_PCHAN_CCCH_SDCCH4;
|
||||
case CCCH_MODE_COMBINED_CBCH:
|
||||
return GSM_PCHAN_CCCH_SDCCH4_CBCH;
|
||||
|
||||
default:
|
||||
LOGP(DL1C, LOGL_NOTICE, "Undandled CCCH mode (%u), "
|
||||
"assuming non-combined configuration\n", mode);
|
||||
return GSM_PCHAN_CCCH;
|
||||
}
|
||||
}
|
||||
|
||||
static int l1ctl_rx_fbsb_req(struct l1ctl_client *l1c, struct msgb *msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_fbsb_req *fbsb;
|
||||
int rc = 0;
|
||||
|
||||
fbsb = (struct l1ctl_fbsb_req *) msg->l1h;
|
||||
if (msgb_l1len(msg) < sizeof(*fbsb)) {
|
||||
LOGPFSMSL(fi, DL1C, LOGL_ERROR,
|
||||
"MSG too short FBSB Req: %u\n",
|
||||
msgb_l1len(msg));
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
struct trxcon_param_fbsb_search_req req = {
|
||||
.pchan_config = l1ctl_ccch_mode2pchan_config(fbsb->ccch_mode),
|
||||
.timeout_ms = 2000,
|
||||
.band_arfcn = ntohs(fbsb->band_arfcn),
|
||||
};
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
|
||||
"Received FBSB request (%s %d, timeout %u ms)\n",
|
||||
arfcn2band_name(req.band_arfcn),
|
||||
req.band_arfcn & ~ARFCN_FLAG_MASK,
|
||||
req.timeout_ms);
|
||||
|
||||
osmo_fsm_inst_dispatch(fi, TRXCON_EV_FBSB_SEARCH_REQ, &req);
|
||||
|
||||
exit:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_pm_req(struct l1ctl_client *l1c, struct msgb *msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_pm_req *pmr;
|
||||
int rc = 0;
|
||||
|
||||
pmr = (struct l1ctl_pm_req *) msg->l1h;
|
||||
if (msgb_l1len(msg) < sizeof(*pmr)) {
|
||||
LOGPFSMSL(fi, DL1C, LOGL_ERROR,
|
||||
"MSG too short PM Req: %u\n",
|
||||
msgb_l1len(msg));
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
struct trxcon_param_full_power_scan_req req = {
|
||||
.band_arfcn_start = ntohs(pmr->range.band_arfcn_from),
|
||||
.band_arfcn_stop = ntohs(pmr->range.band_arfcn_to),
|
||||
};
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
|
||||
"Received power measurement request (%s: %d -> %d)\n",
|
||||
arfcn2band_name(req.band_arfcn_start),
|
||||
req.band_arfcn_start & ~ARFCN_FLAG_MASK,
|
||||
req.band_arfcn_stop & ~ARFCN_FLAG_MASK);
|
||||
|
||||
osmo_fsm_inst_dispatch(fi, TRXCON_EV_FULL_POWER_SCAN_REQ, &req);
|
||||
|
||||
exit:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_reset_req(struct l1ctl_client *l1c, struct msgb *msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_reset *res;
|
||||
int rc = 0;
|
||||
|
||||
res = (struct l1ctl_reset *) msg->l1h;
|
||||
if (msgb_l1len(msg) < sizeof(*res)) {
|
||||
LOGPFSMSL(fi, DL1C, LOGL_ERROR,
|
||||
"MSG too short Reset Req: %u\n",
|
||||
msgb_l1len(msg));
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
|
||||
"Received reset request (%u)\n", res->type);
|
||||
|
||||
switch (res->type) {
|
||||
case L1CTL_RES_T_FULL:
|
||||
osmo_fsm_inst_dispatch(fi, TRXCON_EV_RESET_FULL_REQ, NULL);
|
||||
break;
|
||||
case L1CTL_RES_T_SCHED:
|
||||
osmo_fsm_inst_dispatch(fi, TRXCON_EV_RESET_SCHED_REQ, NULL);
|
||||
break;
|
||||
default:
|
||||
LOGPFSMSL(fi, DL1C, LOGL_ERROR,
|
||||
"Unknown L1CTL_RESET_REQ type\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Confirm */
|
||||
rc = l1ctl_tx_reset_conf(l1c, res->type);
|
||||
|
||||
exit:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_echo_req(struct l1ctl_client *l1c, struct msgb *msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_hdr *l1h;
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE, "Recv Echo Req\n");
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE, "Send Echo Conf\n");
|
||||
|
||||
/* Nothing to do, just send it back */
|
||||
l1h = (struct l1ctl_hdr *) msg->l1h;
|
||||
l1h->msg_type = L1CTL_ECHO_CONF;
|
||||
msg->data = msg->l1h;
|
||||
|
||||
return l1ctl_client_send(l1c, msg);
|
||||
}
|
||||
|
||||
static int l1ctl_rx_ccch_mode_req(struct l1ctl_client *l1c, struct msgb *msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_ccch_mode_req *mode_req;
|
||||
int rc;
|
||||
|
||||
mode_req = (struct l1ctl_ccch_mode_req *)msg->l1h;
|
||||
if (msgb_l1len(msg) < sizeof(*mode_req)) {
|
||||
LOGPFSMSL(fi, DL1C, LOGL_ERROR,
|
||||
"MSG too short Reset Req: %u\n",
|
||||
msgb_l1len(msg));
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE, "Received CCCH mode request (%u)\n",
|
||||
mode_req->ccch_mode); /* TODO: add value-string for ccch_mode */
|
||||
|
||||
struct trxcon_param_set_ccch_tch_mode_req req = {
|
||||
/* Choose corresponding channel combination */
|
||||
.mode = l1ctl_ccch_mode2pchan_config(mode_req->ccch_mode),
|
||||
};
|
||||
|
||||
rc = osmo_fsm_inst_dispatch(fi, TRXCON_EV_SET_CCCH_MODE_REQ, &req);
|
||||
if (rc == 0 && req.applied)
|
||||
l1ctl_tx_ccch_mode_conf(l1c, mode_req->ccch_mode);
|
||||
|
||||
exit:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_rach_req(struct l1ctl_client *l1c, struct msgb *msg, bool is_11bit)
|
||||
{
|
||||
struct trxcon_param_tx_access_burst_req req;
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_info_ul *ul;
|
||||
|
||||
ul = (struct l1ctl_info_ul *) msg->l1h;
|
||||
|
||||
if (is_11bit) {
|
||||
const struct l1ctl_ext_rach_req *rr = (void *)ul->payload;
|
||||
|
||||
req = (struct trxcon_param_tx_access_burst_req) {
|
||||
.offset = ntohs(rr->offset),
|
||||
.synch_seq = rr->synch_seq,
|
||||
.ra = ntohs(rr->ra11),
|
||||
.is_11bit = true,
|
||||
};
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
|
||||
"Received 11-bit RACH request "
|
||||
"(offset=%u, synch_seq=%u, ra11=0x%02hx)\n",
|
||||
req.offset, req.synch_seq, req.ra);
|
||||
} else {
|
||||
const struct l1ctl_rach_req *rr = (void *)ul->payload;
|
||||
|
||||
req = (struct trxcon_param_tx_access_burst_req) {
|
||||
.offset = ntohs(rr->offset),
|
||||
.ra = rr->ra,
|
||||
};
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
|
||||
"Received 8-bit RACH request "
|
||||
"(offset=%u, ra=0x%02x)\n", req.offset, req.ra);
|
||||
}
|
||||
|
||||
/* The controlling L1CTL side always does include the UL info header,
|
||||
* but may leave it empty. We assume RACH is on TS0 in this case. */
|
||||
if (ul->chan_nr == 0x00) {
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
|
||||
"The UL info header is empty, assuming RACH is on TS0\n");
|
||||
req.chan_nr = RSL_CHAN_RACH;
|
||||
req.link_id = 0x00;
|
||||
} else {
|
||||
req.chan_nr = ul->chan_nr;
|
||||
req.link_id = ul->link_id;
|
||||
}
|
||||
|
||||
osmo_fsm_inst_dispatch(fi, TRXCON_EV_TX_ACCESS_BURST_REQ, &req);
|
||||
|
||||
msgb_free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l1ctl_proc_est_req_h0(struct osmo_fsm_inst *fi,
|
||||
struct trxcon_param_dedicated_establish_req *req,
|
||||
const struct l1ctl_h0 *h)
|
||||
{
|
||||
req->h0.band_arfcn = ntohs(h->band_arfcn);
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
|
||||
"L1CTL_DM_EST_REQ indicates single ARFCN %s %u\n",
|
||||
arfcn2band_name(req->h0.band_arfcn),
|
||||
req->h0.band_arfcn & ~ARFCN_FLAG_MASK);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l1ctl_proc_est_req_h1(struct osmo_fsm_inst *fi,
|
||||
struct trxcon_param_dedicated_establish_req *req,
|
||||
const struct l1ctl_h1 *h)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
|
||||
"L1CTL_DM_EST_REQ indicates a Frequency "
|
||||
"Hopping (hsn=%u, maio=%u, chans=%u) channel\n",
|
||||
h->hsn, h->maio, h->n);
|
||||
|
||||
/* No channels?!? */
|
||||
if (!h->n) {
|
||||
LOGPFSMSL(fi, DL1C, LOGL_ERROR,
|
||||
"No channels in mobile allocation?!?\n");
|
||||
return -EINVAL;
|
||||
} else if (h->n > ARRAY_SIZE(h->ma)) {
|
||||
LOGPFSMSL(fi, DL1C, LOGL_ERROR,
|
||||
"More than 64 channels in mobile allocation?!?\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Convert from network to host byte order */
|
||||
for (i = 0; i < h->n; i++)
|
||||
req->h1.ma[i] = ntohs(h->ma[i]);
|
||||
req->h1.n = h->n;
|
||||
req->h1.hsn = h->hsn;
|
||||
req->h1.maio = h->maio;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_dm_est_req(struct l1ctl_client *l1c, struct msgb *msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_dm_est_req *est_req;
|
||||
struct l1ctl_info_ul *ul;
|
||||
int rc;
|
||||
|
||||
ul = (struct l1ctl_info_ul *) msg->l1h;
|
||||
est_req = (struct l1ctl_dm_est_req *) ul->payload;
|
||||
|
||||
struct trxcon_param_dedicated_establish_req req = {
|
||||
.chan_nr = ul->chan_nr,
|
||||
.tch_mode = est_req->tch_mode,
|
||||
.tsc = est_req->tsc,
|
||||
.hopping = est_req->h,
|
||||
};
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
|
||||
"Received L1CTL_DM_EST_REQ "
|
||||
"(tn=%u, chan_nr=0x%02x, tsc=%u, tch_mode=0x%02x)\n",
|
||||
req.chan_nr & 0x07, req.chan_nr, req.tsc, req.tch_mode);
|
||||
|
||||
/* Frequency hopping? */
|
||||
if (est_req->h)
|
||||
rc = l1ctl_proc_est_req_h1(fi, &req, &est_req->h1);
|
||||
else /* Single ARFCN */
|
||||
rc = l1ctl_proc_est_req_h0(fi, &req, &est_req->h0);
|
||||
if (rc)
|
||||
goto exit;
|
||||
|
||||
osmo_fsm_inst_dispatch(fi, TRXCON_EV_DEDICATED_ESTABLISH_REQ, &req);
|
||||
|
||||
exit:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_dm_rel_req(struct l1ctl_client *l1c, struct msgb *msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE, "Received L1CTL_DM_REL_REQ\n");
|
||||
|
||||
osmo_fsm_inst_dispatch(fi, TRXCON_EV_DEDICATED_RELEASE_REQ, NULL);
|
||||
|
||||
msgb_free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles both L1CTL_DATA_REQ and L1CTL_TRAFFIC_REQ.
|
||||
*/
|
||||
static int l1ctl_rx_dt_req(struct l1ctl_client *l1c,
|
||||
struct msgb *msg, bool traffic)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_info_ul *ul;
|
||||
|
||||
/* Extract UL frame header */
|
||||
ul = (struct l1ctl_info_ul *) msg->l1h;
|
||||
msg->l2h = ul->payload;
|
||||
|
||||
struct trxcon_param_tx_traffic_data_req req = {
|
||||
.chan_nr = ul->chan_nr,
|
||||
.link_id = ul->link_id & 0x40,
|
||||
.data_len = msgb_l2len(msg),
|
||||
.data = ul->payload,
|
||||
};
|
||||
|
||||
LOGPFSMSL(fi, DL1D, LOGL_DEBUG,
|
||||
"Recv %s Req (chan_nr=0x%02x, link_id=0x%02x, len=%zu)\n",
|
||||
traffic ? "TRAFFIC" : "DATA", req.chan_nr, req.link_id, req.data_len);
|
||||
|
||||
switch (fi->state) {
|
||||
case TRXCON_ST_DEDICATED:
|
||||
if (traffic)
|
||||
osmo_fsm_inst_dispatch(fi, TRXCON_EV_TX_TRAFFIC_REQ, &req);
|
||||
else
|
||||
osmo_fsm_inst_dispatch(fi, TRXCON_EV_TX_DATA_REQ, &req);
|
||||
break;
|
||||
default:
|
||||
if (!traffic && req.link_id == 0x40) /* only for SACCH */
|
||||
osmo_fsm_inst_dispatch(fi, TRXCON_EV_UPDATE_SACCH_CACHE_REQ, &req);
|
||||
/* TODO: log an error about uhnandled DATA.req / TRAFFIC.req */
|
||||
}
|
||||
|
||||
msgb_free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_param_req(struct l1ctl_client *l1c, struct msgb *msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_par_req *par_req;
|
||||
struct l1ctl_info_ul *ul;
|
||||
|
||||
ul = (struct l1ctl_info_ul *) msg->l1h;
|
||||
par_req = (struct l1ctl_par_req *) ul->payload;
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
|
||||
"Received L1CTL_PARAM_REQ (ta=%d, tx_power=%u)\n",
|
||||
par_req->ta, par_req->tx_power);
|
||||
|
||||
struct trxcon_param_set_phy_config_req req = {
|
||||
.type = TRXCON_PHY_CFGT_TX_PARAMS,
|
||||
.tx_params = {
|
||||
.timing_advance = par_req->ta,
|
||||
.tx_power = par_req->tx_power,
|
||||
}
|
||||
};
|
||||
|
||||
osmo_fsm_inst_dispatch(fi, TRXCON_EV_SET_PHY_CONFIG_REQ, &req);
|
||||
|
||||
msgb_free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_tch_mode_req(struct l1ctl_client *l1c, struct msgb *msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_tch_mode_req *mode_req;
|
||||
int rc;
|
||||
|
||||
mode_req = (struct l1ctl_tch_mode_req *)msg->l1h;
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
|
||||
"Received L1CTL_TCH_MODE_REQ (tch_mode=%u, audio_mode=%u)\n",
|
||||
mode_req->tch_mode, mode_req->audio_mode);
|
||||
|
||||
/* TODO: do we need to care about audio_mode? */
|
||||
|
||||
struct trxcon_param_set_ccch_tch_mode_req req = {
|
||||
.mode = mode_req->tch_mode,
|
||||
};
|
||||
if (mode_req->tch_mode == GSM48_CMODE_SPEECH_AMR) {
|
||||
req.amr.start_codec = mode_req->amr.start_codec;
|
||||
req.amr.codecs_bitmask = mode_req->amr.codecs_bitmask;
|
||||
}
|
||||
|
||||
rc = osmo_fsm_inst_dispatch(fi, TRXCON_EV_SET_TCH_MODE_REQ, &req);
|
||||
if (rc != 0 || !req.applied) {
|
||||
talloc_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Re-use the original message as confirmation */
|
||||
struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
|
||||
l1h->msg_type = L1CTL_TCH_MODE_CONF;
|
||||
|
||||
return l1ctl_client_send(l1c, msg);
|
||||
}
|
||||
|
||||
static int l1ctl_rx_crypto_req(struct l1ctl_client *l1c, struct msgb *msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_crypto_req *cr;
|
||||
struct l1ctl_info_ul *ul;
|
||||
|
||||
ul = (struct l1ctl_info_ul *) msg->l1h;
|
||||
cr = (struct l1ctl_crypto_req *) ul->payload;
|
||||
|
||||
struct trxcon_param_crypto_req req = {
|
||||
.chan_nr = ul->chan_nr,
|
||||
.a5_algo = cr->algo,
|
||||
.key_len = cr->key_len,
|
||||
.key = cr->key,
|
||||
};
|
||||
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
|
||||
"L1CTL_CRYPTO_REQ (algo=A5/%u, key_len=%u)\n",
|
||||
req.a5_algo, req.key_len);
|
||||
|
||||
osmo_fsm_inst_dispatch(fi, TRXCON_EV_CRYPTO_REQ, &req);
|
||||
|
||||
msgb_free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = l1c->priv;
|
||||
struct l1ctl_hdr *l1h;
|
||||
|
||||
l1h = (struct l1ctl_hdr *) msg->l1h;
|
||||
msg->l1h = l1h->data;
|
||||
|
||||
switch (l1h->msg_type) {
|
||||
case L1CTL_FBSB_REQ:
|
||||
return l1ctl_rx_fbsb_req(l1c, msg);
|
||||
case L1CTL_PM_REQ:
|
||||
return l1ctl_rx_pm_req(l1c, msg);
|
||||
case L1CTL_RESET_REQ:
|
||||
return l1ctl_rx_reset_req(l1c, msg);
|
||||
case L1CTL_ECHO_REQ:
|
||||
return l1ctl_rx_echo_req(l1c, msg);
|
||||
case L1CTL_CCCH_MODE_REQ:
|
||||
return l1ctl_rx_ccch_mode_req(l1c, msg);
|
||||
case L1CTL_RACH_REQ:
|
||||
return l1ctl_rx_rach_req(l1c, msg, false);
|
||||
case L1CTL_EXT_RACH_REQ:
|
||||
return l1ctl_rx_rach_req(l1c, msg, true);
|
||||
case L1CTL_DM_EST_REQ:
|
||||
return l1ctl_rx_dm_est_req(l1c, msg);
|
||||
case L1CTL_DM_REL_REQ:
|
||||
return l1ctl_rx_dm_rel_req(l1c, msg);
|
||||
case L1CTL_DATA_REQ:
|
||||
return l1ctl_rx_dt_req(l1c, msg, false);
|
||||
case L1CTL_TRAFFIC_REQ:
|
||||
return l1ctl_rx_dt_req(l1c, msg, true);
|
||||
case L1CTL_PARAM_REQ:
|
||||
return l1ctl_rx_param_req(l1c, msg);
|
||||
case L1CTL_TCH_MODE_REQ:
|
||||
return l1ctl_rx_tch_mode_req(l1c, msg);
|
||||
case L1CTL_CRYPTO_REQ:
|
||||
return l1ctl_rx_crypto_req(l1c, msg);
|
||||
|
||||
/* Not (yet) handled messages */
|
||||
case L1CTL_NEIGH_PM_REQ:
|
||||
case L1CTL_DATA_TBF_REQ:
|
||||
case L1CTL_TBF_CFG_REQ:
|
||||
case L1CTL_DM_FREQ_REQ:
|
||||
case L1CTL_SIM_REQ:
|
||||
LOGPFSMSL(fi, DL1C, LOGL_NOTICE,
|
||||
"Ignoring unsupported message (type=%u)\n",
|
||||
l1h->msg_type);
|
||||
msgb_free(msg);
|
||||
return -ENOTSUP;
|
||||
default:
|
||||
LOGPFSMSL(fi, DL1C, LOGL_ERROR, "Unknown MSG type %u: %s\n",
|
||||
l1h->msg_type, osmo_hexdump(msgb_data(msg), msgb_length(msg)));
|
||||
msgb_free(msg);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
282
trxcon/src/l1ctl_server.c
Normal file
282
trxcon/src/l1ctl_server.c
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* UNIX socket server for L1CTL
|
||||
*
|
||||
* (C) 2013 by Sylvain Munaut <tnt@246tNt.com>
|
||||
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
* (C) 2022 by by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/un.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/write_queue.h>
|
||||
|
||||
#include <osmocom/bb/trxcon/logging.h>
|
||||
#include <osmocom/bb/trxcon/l1ctl_server.h>
|
||||
|
||||
#define LOGP_CLI(cli, cat, level, fmt, args...) \
|
||||
LOGP(cat, level, "%s" fmt, (cli)->log_prefix, ## args)
|
||||
|
||||
static int l1ctl_client_read_cb(struct osmo_fd *ofd)
|
||||
{
|
||||
struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
|
||||
struct msgb *msg;
|
||||
uint16_t len;
|
||||
int rc;
|
||||
|
||||
/* Attempt to read from socket */
|
||||
rc = read(ofd->fd, &len, L1CTL_MSG_LEN_FIELD);
|
||||
if (rc != L1CTL_MSG_LEN_FIELD) {
|
||||
if (rc <= 0) {
|
||||
LOGP_CLI(client, DL1D, LOGL_NOTICE,
|
||||
"L1CTL connection error: read() failed (rc=%d): %s\n",
|
||||
rc, strerror(errno));
|
||||
} else {
|
||||
LOGP_CLI(client, DL1D, LOGL_NOTICE,
|
||||
"L1CTL connection error: short read\n");
|
||||
rc = -EIO;
|
||||
}
|
||||
l1ctl_client_conn_close(client);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Check message length */
|
||||
len = ntohs(len);
|
||||
if (len > L1CTL_LENGTH) {
|
||||
LOGP_CLI(client, DL1D, LOGL_ERROR, "Length is too big: %u\n", len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Allocate a new msg */
|
||||
msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM,
|
||||
L1CTL_HEADROOM, "l1ctl_rx_msg");
|
||||
if (!msg) {
|
||||
LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to allocate msg\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
msg->l1h = msgb_put(msg, len);
|
||||
rc = read(ofd->fd, msg->l1h, msgb_l1len(msg));
|
||||
if (rc != len) {
|
||||
LOGP_CLI(client, DL1D, LOGL_ERROR,
|
||||
"Can not read data: len=%d < rc=%d: %s\n",
|
||||
len, rc, strerror(errno));
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Debug print */
|
||||
LOGP_CLI(client, DL1D, LOGL_DEBUG, "RX: '%s'\n", osmo_hexdump(msg->data, msg->len));
|
||||
|
||||
/* Call L1CTL handler */
|
||||
client->server->cfg->conn_read_cb(client, msg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l1ctl_client_write_cb(struct osmo_fd *ofd, struct msgb *msg)
|
||||
{
|
||||
struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
|
||||
int len;
|
||||
|
||||
if (ofd->fd <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
len = write(ofd->fd, msg->data, msg->len);
|
||||
if (len != msg->len) {
|
||||
LOGP_CLI(client, DL1D, LOGL_ERROR,
|
||||
"Failed to write data: written (%d) < msg_len (%d)\n",
|
||||
len, msg->len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Connection handler */
|
||||
static int l1ctl_server_conn_cb(struct osmo_fd *sfd, unsigned int flags)
|
||||
{
|
||||
struct l1ctl_server *server = (struct l1ctl_server *)sfd->data;
|
||||
struct l1ctl_client *client;
|
||||
int rc, client_fd;
|
||||
|
||||
client_fd = accept(sfd->fd, NULL, NULL);
|
||||
if (client_fd < 0) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Failed to accept() a new connection: "
|
||||
"%s\n", strerror(errno));
|
||||
return client_fd;
|
||||
}
|
||||
|
||||
if (server->cfg->num_clients_max > 0 /* 0 means unlimited */ &&
|
||||
server->num_clients >= server->cfg->num_clients_max) {
|
||||
LOGP(DL1C, LOGL_NOTICE, "L1CTL server cannot accept more "
|
||||
"than %u connection(s)\n", server->cfg->num_clients_max);
|
||||
close(client_fd);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
client = talloc_zero(server, struct l1ctl_client);
|
||||
if (client == NULL) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Failed to allocate an L1CTL client\n");
|
||||
close(client_fd);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Init the client's write queue */
|
||||
osmo_wqueue_init(&client->wq, 100);
|
||||
INIT_LLIST_HEAD(&client->wq.bfd.list);
|
||||
|
||||
client->wq.write_cb = &l1ctl_client_write_cb;
|
||||
client->wq.read_cb = &l1ctl_client_read_cb;
|
||||
osmo_fd_setup(&client->wq.bfd, client_fd, OSMO_FD_READ, &osmo_wqueue_bfd_cb, client, 0);
|
||||
|
||||
/* Register the client's write queue */
|
||||
rc = osmo_fd_register(&client->wq.bfd);
|
||||
if (rc != 0) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Failed to register a new connection fd\n");
|
||||
close(client->wq.bfd.fd);
|
||||
talloc_free(client);
|
||||
return rc;
|
||||
}
|
||||
|
||||
llist_add_tail(&client->list, &server->clients);
|
||||
client->id = server->next_client_id++;
|
||||
client->server = server;
|
||||
server->num_clients++;
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "L1CTL server got a new connection (id=%u)\n", client->id);
|
||||
|
||||
if (client->server->cfg->conn_accept_cb != NULL)
|
||||
client->server->cfg->conn_accept_cb(client);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg)
|
||||
{
|
||||
uint8_t *len;
|
||||
|
||||
/* Debug print */
|
||||
LOGP_CLI(client, DL1D, LOGL_DEBUG, "TX: '%s'\n", osmo_hexdump(msg->data, msg->len));
|
||||
|
||||
if (msg->l1h != msg->data)
|
||||
LOGP_CLI(client, DL1D, LOGL_INFO, "Message L1 header != Message Data\n");
|
||||
|
||||
/* Prepend 16-bit length before sending */
|
||||
len = msgb_push(msg, L1CTL_MSG_LEN_FIELD);
|
||||
osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len);
|
||||
|
||||
if (osmo_wqueue_enqueue(&client->wq, msg) != 0) {
|
||||
LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to enqueue msg!\n");
|
||||
msgb_free(msg);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void l1ctl_client_conn_close(struct l1ctl_client *client)
|
||||
{
|
||||
struct l1ctl_server *server = client->server;
|
||||
|
||||
LOGP_CLI(client, DL1C, LOGL_NOTICE, "Closing L1CTL connection\n");
|
||||
|
||||
if (server->cfg->conn_close_cb != NULL)
|
||||
server->cfg->conn_close_cb(client);
|
||||
|
||||
/* Close connection socket */
|
||||
osmo_fd_unregister(&client->wq.bfd);
|
||||
close(client->wq.bfd.fd);
|
||||
client->wq.bfd.fd = -1;
|
||||
|
||||
/* Clear pending messages */
|
||||
osmo_wqueue_clear(&client->wq);
|
||||
|
||||
client->server->num_clients--;
|
||||
llist_del(&client->list);
|
||||
talloc_free(client);
|
||||
|
||||
/* If this was the last client, reset the client IDs generator to 0.
|
||||
* This way avoid assigning huge unreadable client IDs like 26545. */
|
||||
if (llist_empty(&server->clients))
|
||||
server->next_client_id = 0;
|
||||
}
|
||||
|
||||
struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg)
|
||||
{
|
||||
struct l1ctl_server *server;
|
||||
int rc;
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Init L1CTL server (sock_path=%s)\n", cfg->sock_path);
|
||||
|
||||
server = talloc(ctx, struct l1ctl_server);
|
||||
OSMO_ASSERT(server != NULL);
|
||||
|
||||
*server = (struct l1ctl_server) {
|
||||
.clients = LLIST_HEAD_INIT(server->clients),
|
||||
.cfg = cfg,
|
||||
};
|
||||
|
||||
/* conn_read_cb shall not be NULL */
|
||||
OSMO_ASSERT(cfg->conn_read_cb != NULL);
|
||||
|
||||
/* Bind connection handler */
|
||||
osmo_fd_setup(&server->ofd, -1, OSMO_FD_READ, &l1ctl_server_conn_cb, server, 0);
|
||||
|
||||
rc = osmo_sock_unix_init_ofd(&server->ofd, SOCK_STREAM, 0,
|
||||
cfg->sock_path, OSMO_SOCK_F_BIND);
|
||||
if (rc < 0) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n",
|
||||
strerror(errno));
|
||||
talloc_free(server);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
void l1ctl_server_free(struct l1ctl_server *server)
|
||||
{
|
||||
LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL server\n");
|
||||
|
||||
/* Close all client connections */
|
||||
while (!llist_empty(&server->clients)) {
|
||||
struct l1ctl_client *client = llist_entry(server->clients.next,
|
||||
struct l1ctl_client,
|
||||
list);
|
||||
l1ctl_client_conn_close(client);
|
||||
}
|
||||
|
||||
/* Unbind listening socket */
|
||||
if (server->ofd.fd != -1) {
|
||||
osmo_fd_unregister(&server->ofd);
|
||||
close(server->ofd.fd);
|
||||
server->ofd.fd = -1;
|
||||
}
|
||||
|
||||
talloc_free(server);
|
||||
}
|
||||
84
trxcon/src/logging.c
Normal file
84
trxcon/src/logging.c
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
*
|
||||
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <osmocom/core/application.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <osmocom/bb/trxcon/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,
|
||||
},
|
||||
[DTRXC] = {
|
||||
.name = "DTRXC",
|
||||
.description = "Transceiver control interface",
|
||||
.color = "\033[1;33m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DTRXD] = {
|
||||
.name = "DTRXD",
|
||||
.description = "Transceiver data interface",
|
||||
.color = "\033[1;33m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DSCH] = {
|
||||
.name = "DSCH",
|
||||
.description = "Scheduler management",
|
||||
.color = "\033[1;36m",
|
||||
.enabled = 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;
|
||||
}
|
||||
203
trxcon/src/sched_clck.c
Normal file
203
trxcon/src/sched_clck.c
Normal file
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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/logging.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/core/timer_compat.h>
|
||||
|
||||
#include <osmocom/bb/l1sched/l1sched.h>
|
||||
#include <osmocom/bb/l1sched/logging.h>
|
||||
|
||||
#define MAX_FN_SKEW 50
|
||||
#define TRX_LOSS_FRAMES 400
|
||||
|
||||
static void l1sched_clck_tick(void *data)
|
||||
{
|
||||
struct l1sched_state *sched = (struct l1sched_state *) data;
|
||||
struct timespec tv_now, *tv_clock, elapsed;
|
||||
int64_t elapsed_us;
|
||||
const struct timespec frame_duration = { .tv_sec = 0, .tv_nsec = GSM_TDMA_FN_DURATION_nS };
|
||||
|
||||
/* Check if transceiver is still alive */
|
||||
if (sched->fn_counter_lost++ == TRX_LOSS_FRAMES) {
|
||||
LOGP_SCHEDC(sched, LOGL_DEBUG, "No more clock from transceiver\n");
|
||||
sched->clck_state = L1SCHED_CLCK_ST_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_SCHEDC(sched, LOGL_NOTICE, "PC clock skew: "
|
||||
"elapsed uS %" PRId64 "\n", elapsed_us);
|
||||
|
||||
sched->clck_state = L1SCHED_CLCK_ST_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 l1sched_clck_correct(struct l1sched_state *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 = l1sched_clck_tick;
|
||||
sched->clock_timer.data = sched;
|
||||
osmo_timer_schedule(&sched->clock_timer, 0, GSM_TDMA_FN_DURATION_uS);
|
||||
}
|
||||
|
||||
int l1sched_clck_handle(struct l1sched_state *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->clck_state == L1SCHED_CLCK_ST_WAIT) {
|
||||
l1sched_clck_correct(sched, &tv_now, fn);
|
||||
|
||||
LOGP_SCHEDC(sched, LOGL_DEBUG, "Initial clock received: fn=%u\n", fn);
|
||||
sched->clck_state = L1SCHED_CLCK_ST_OK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOGP_SCHEDC(sched, 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_SCHEDC(sched, LOGL_NOTICE, "GSM clock skew: old fn=%u, "
|
||||
"new fn=%u\n", sched->fn_counter_proc, fn);
|
||||
|
||||
l1sched_clck_correct(sched, &tv_now, fn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOGP_SCHEDC(sched, LOGL_INFO, "GSM clock jitter: %" PRId64 "\n",
|
||||
elapsed_fn * GSM_TDMA_FN_DURATION_uS - elapsed_us);
|
||||
|
||||
/* Too many frames have been processed already */
|
||||
if (elapsed_fn < 0) {
|
||||
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 l1sched_clck_reset(struct l1sched_state *sched)
|
||||
{
|
||||
/* Reset internal state */
|
||||
sched->clck_state = L1SCHED_CLCK_ST_WAIT;
|
||||
|
||||
/* Stop clock timer */
|
||||
osmo_timer_del(&sched->clock_timer);
|
||||
|
||||
/* Flush counters */
|
||||
sched->fn_counter_proc = 0;
|
||||
sched->fn_counter_lost = 0;
|
||||
}
|
||||
143
trxcon/src/sched_lchan_common.c
Normal file
143
trxcon/src/sched_lchan_common.c
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: common routines for lchan handlers
|
||||
*
|
||||
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
* Contributions by sysmocom - s.f.m.c. GmbH
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <talloc.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
|
||||
#include <osmocom/codec/codec.h>
|
||||
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
#include <osmocom/gsm/protocol/gsm_08_58.h>
|
||||
|
||||
#include <osmocom/bb/l1sched/l1sched.h>
|
||||
#include <osmocom/bb/l1sched/logging.h>
|
||||
|
||||
/* GSM 05.02 Chapter 5.2.3 Normal Burst (NB) */
|
||||
const uint8_t l1sched_nb_training_bits[8][26] = {
|
||||
{
|
||||
0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0,
|
||||
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1,
|
||||
},
|
||||
{
|
||||
0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1,
|
||||
},
|
||||
{
|
||||
0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||
},
|
||||
{
|
||||
0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0,
|
||||
},
|
||||
{
|
||||
0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0,
|
||||
1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1,
|
||||
},
|
||||
{
|
||||
0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0,
|
||||
},
|
||||
{
|
||||
1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1,
|
||||
0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1,
|
||||
},
|
||||
{
|
||||
1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0,
|
||||
0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0,
|
||||
},
|
||||
};
|
||||
|
||||
/* Get a string representation of the burst buffer's completeness.
|
||||
* Examples: " ****.." (incomplete, 4/6 bursts)
|
||||
* " ****" (complete, all 4 bursts)
|
||||
* "**.***.." (incomplete, 5/8 bursts) */
|
||||
const char *l1sched_burst_mask2str(const uint8_t *mask, int bits)
|
||||
{
|
||||
/* TODO: CSD is interleaved over 22 bursts, so the mask needs to be extended */
|
||||
static char buf[8 + 1];
|
||||
char *ptr = buf;
|
||||
|
||||
OSMO_ASSERT(bits <= 8 && bits > 0);
|
||||
|
||||
while (--bits >= 0)
|
||||
*(ptr++) = (*mask & (1 << bits)) ? '*' : '.';
|
||||
*ptr = '\0';
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes a bad frame indication message
|
||||
* according to the current tch_mode.
|
||||
*
|
||||
* @param l2 Caller-allocated byte array
|
||||
* @param lchan Logical channel to generate BFI for
|
||||
* @return How much bytes were written
|
||||
*/
|
||||
size_t l1sched_bad_frame_ind(uint8_t *l2, struct l1sched_lchan_state *lchan)
|
||||
{
|
||||
int rc;
|
||||
|
||||
switch (lchan->tch_mode) {
|
||||
case GSM48_CMODE_SPEECH_V1:
|
||||
if (lchan->type == L1SCHED_TCHF) { /* Full Rate */
|
||||
memset(l2, 0x00, GSM_FR_BYTES);
|
||||
l2[0] = 0xd0;
|
||||
return GSM_FR_BYTES;
|
||||
} else { /* Half Rate */
|
||||
memset(l2 + 1, 0x00, GSM_HR_BYTES);
|
||||
l2[0] = 0x70; /* F = 0, FT = 111 */
|
||||
return GSM_HR_BYTES + 1;
|
||||
}
|
||||
case GSM48_CMODE_SPEECH_EFR: /* Enhanced Full Rate */
|
||||
memset(l2, 0x00, GSM_EFR_BYTES);
|
||||
l2[0] = 0xc0;
|
||||
return GSM_EFR_BYTES;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* Adaptive Multi Rate */
|
||||
rc = osmo_amr_rtp_enc(l2,
|
||||
lchan->amr.codec[lchan->amr.dl_cmr],
|
||||
lchan->amr.codec[lchan->amr.dl_ft],
|
||||
AMR_BAD);
|
||||
if (rc < 2) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Failed to encode AMR_BAD frame (rc=%d), "
|
||||
"not sending BFI\n", rc);
|
||||
return 0;
|
||||
}
|
||||
memset(l2 + 2, 0, rc - 2);
|
||||
return rc;
|
||||
case GSM48_CMODE_SIGN:
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "BFI is not allowed in signalling mode\n");
|
||||
return 0;
|
||||
default:
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
623
trxcon/src/sched_lchan_desc.c
Normal file
623
trxcon/src/sched_lchan_desc.c
Normal file
@@ -0,0 +1,623 @@
|
||||
/*
|
||||
* 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>
|
||||
* Contributions by sysmocom - s.f.m.c. GmbH
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 <osmocom/bb/l1sched/l1sched.h>
|
||||
|
||||
/* Forward declaration of handlers */
|
||||
int rx_data_fn(struct l1sched_lchan_state *lchan,
|
||||
uint32_t fn, uint8_t bid, const sbit_t *bits,
|
||||
const struct l1sched_meas_set *meas);
|
||||
|
||||
int tx_data_fn(struct l1sched_lchan_state *lchan,
|
||||
struct l1sched_burst_req *br);
|
||||
|
||||
int rx_sch_fn(struct l1sched_lchan_state *lchan,
|
||||
uint32_t fn, uint8_t bid, const sbit_t *bits,
|
||||
const struct l1sched_meas_set *meas);
|
||||
|
||||
int tx_rach_fn(struct l1sched_lchan_state *lchan,
|
||||
struct l1sched_burst_req *br);
|
||||
|
||||
int rx_tchf_fn(struct l1sched_lchan_state *lchan,
|
||||
uint32_t fn, uint8_t bid, const sbit_t *bits,
|
||||
const struct l1sched_meas_set *meas);
|
||||
|
||||
int tx_tchf_fn(struct l1sched_lchan_state *lchan,
|
||||
struct l1sched_burst_req *br);
|
||||
|
||||
int rx_tchh_fn(struct l1sched_lchan_state *lchan,
|
||||
uint32_t fn, uint8_t bid, const sbit_t *bits,
|
||||
const struct l1sched_meas_set *meas);
|
||||
|
||||
int tx_tchh_fn(struct l1sched_lchan_state *lchan,
|
||||
struct l1sched_burst_req *br);
|
||||
|
||||
int rx_pdtch_fn(struct l1sched_lchan_state *lchan,
|
||||
uint32_t fn, uint8_t bid, const sbit_t *bits,
|
||||
const struct l1sched_meas_set *meas);
|
||||
|
||||
int tx_pdtch_fn(struct l1sched_lchan_state *lchan,
|
||||
struct l1sched_burst_req *br);
|
||||
|
||||
const struct l1sched_lchan_desc l1sched_lchan_desc[_L1SCHED_CHAN_MAX] = {
|
||||
[L1SCHED_IDLE] = {
|
||||
.name = "IDLE",
|
||||
.desc = "Idle channel",
|
||||
/* The MS needs to perform neighbour measurements during
|
||||
* IDLE slots, however this is not implemented (yet). */
|
||||
},
|
||||
[L1SCHED_FCCH] = {
|
||||
.name = "FCCH", /* 3GPP TS 05.02, section 3.3.2.1 */
|
||||
.desc = "Frequency correction channel",
|
||||
/* Handled by transceiver, nothing to do. */
|
||||
},
|
||||
[L1SCHED_SCH] = {
|
||||
.name = "SCH", /* 3GPP TS 05.02, section 3.3.2.2 */
|
||||
.desc = "Synchronization channel",
|
||||
|
||||
/* 3GPP TS 05.03, section 4.7. Handled by transceiver,
|
||||
* however we still need to parse BSIC (BCC / NCC). */
|
||||
.flags = L1SCHED_CH_FLAG_AUTO,
|
||||
.rx_fn = rx_sch_fn,
|
||||
},
|
||||
[L1SCHED_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 = L1SCHED_CH_FLAG_AUTO,
|
||||
.rx_fn = rx_data_fn,
|
||||
},
|
||||
[L1SCHED_RACH] = {
|
||||
.name = "RACH", /* 3GPP TS 05.02, section 3.3.3.1 */
|
||||
.desc = "Random access channel",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_RACH,
|
||||
.chan_nr = RSL_CHAN_RACH,
|
||||
|
||||
/* Tx only, RACH convolutional coding (3GPP TS 05.03, section 4.6). */
|
||||
.flags = L1SCHED_CH_FLAG_AUTO,
|
||||
.tx_fn = tx_rach_fn,
|
||||
},
|
||||
[L1SCHED_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 = L1SCHED_CH_FLAG_AUTO,
|
||||
.rx_fn = rx_data_fn,
|
||||
},
|
||||
[L1SCHED_TCHF] = {
|
||||
.name = "TCH/F", /* 3GPP TS 05.02, section 3.2 */
|
||||
.desc = "Full Rate traffic channel",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_F,
|
||||
.chan_nr = RSL_CHAN_Bm_ACCHs,
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
|
||||
/* Rx and Tx, multiple convolutional coding types (3GPP TS 05.03,
|
||||
* chapter 3), block diagonal interleaving (3GPP TS 05.02, clause 7):
|
||||
*
|
||||
* - a traffic frame is interleaved over 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 = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_tchf_fn,
|
||||
.tx_fn = tx_tchf_fn,
|
||||
},
|
||||
[L1SCHED_TCHH_0] = {
|
||||
.name = "TCH/H(0)", /* 3GPP TS 05.02, section 3.2 */
|
||||
.desc = "Half Rate traffic channel (sub-channel 0)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H,
|
||||
.chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3),
|
||||
.link_id = L1SCHED_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 = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_tchh_fn,
|
||||
.tx_fn = tx_tchh_fn,
|
||||
},
|
||||
[L1SCHED_TCHH_1] = {
|
||||
.name = "TCH/H(1)", /* 3GPP TS 05.02, section 3.2 */
|
||||
.desc = "Half Rate traffic channel (sub-channel 1)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H,
|
||||
.chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3),
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
.ss_nr = 1,
|
||||
|
||||
/* Same as for L1SCHED_TCHH_0, see above. */
|
||||
.burst_buf_size = 6 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_tchh_fn,
|
||||
.tx_fn = tx_tchh_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH4_0] = {
|
||||
.name = "SDCCH/4(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 0)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3),
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
.ss_nr = 0,
|
||||
|
||||
/* Same as for L1SCHED_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH4_1] = {
|
||||
.name = "SDCCH/4(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 1)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3),
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
.ss_nr = 1,
|
||||
|
||||
/* Same as for L1SCHED_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH4_2] = {
|
||||
.name = "SDCCH/4(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 2)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3),
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
.ss_nr = 2,
|
||||
|
||||
/* Same as for L1SCHED_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH4_3] = {
|
||||
.name = "SDCCH/4(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 3)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3),
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
.ss_nr = 3,
|
||||
|
||||
/* Same as for L1SCHED_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH8_0] = {
|
||||
.name = "SDCCH/8(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 0)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3),
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
.ss_nr = 0,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH8_1] = {
|
||||
.name = "SDCCH/8(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 1)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3),
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
.ss_nr = 1,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH8_2] = {
|
||||
.name = "SDCCH/8(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 2)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3),
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
.ss_nr = 2,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH8_3] = {
|
||||
.name = "SDCCH/8(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 3)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3),
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
.ss_nr = 3,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH8_4] = {
|
||||
.name = "SDCCH/8(4)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 4)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3),
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
.ss_nr = 4,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH8_5] = {
|
||||
.name = "SDCCH/8(5)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 5)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3),
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
.ss_nr = 5,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH8_6] = {
|
||||
.name = "SDCCH/8(6)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 6)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3),
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
.ss_nr = 6,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH8_7] = {
|
||||
.name = "SDCCH/8(7)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Stand-alone dedicated control channel (sub-channel 7)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3),
|
||||
.link_id = L1SCHED_CH_LID_DEDIC,
|
||||
.ss_nr = 7,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCHTF] = {
|
||||
.name = "SACCH/TF", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow TCH/F associated control channel",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_F | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_Bm_ACCHs,
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
|
||||
/* Same as for L1SCHED_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCHTH_0] = {
|
||||
.name = "SACCH/TH(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow TCH/H associated control channel (sub-channel 0)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 0,
|
||||
|
||||
/* Same as for L1SCHED_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCHTH_1] = {
|
||||
.name = "SACCH/TH(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow TCH/H associated control channel (sub-channel 1)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 1,
|
||||
|
||||
/* Same as for L1SCHED_BCCH (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCH4_0] = {
|
||||
.name = "SACCH/4(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/4 associated control channel (sub-channel 0)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 0,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCH4_1] = {
|
||||
.name = "SACCH/4(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/4 associated control channel (sub-channel 1)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 1,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCH4_2] = {
|
||||
.name = "SACCH/4(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/4 associated control channel (sub-channel 2)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 2,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCH4_3] = {
|
||||
.name = "SACCH/4(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/4 associated control channel (sub-channel 3)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 3,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH4_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCH8_0] = {
|
||||
.name = "SACCH/8(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 0)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 0,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCH8_1] = {
|
||||
.name = "SACCH/8(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 1)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 1,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCH8_2] = {
|
||||
.name = "SACCH/8(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 2)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 2,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCH8_3] = {
|
||||
.name = "SACCH/8(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 3)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 3,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCH8_4] = {
|
||||
.name = "SACCH/8(4)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 4)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 4,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCH8_5] = {
|
||||
.name = "SACCH/8(5)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 5)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 5,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCH8_6] = {
|
||||
.name = "SACCH/8(6)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 6)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 6,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_SACCH8_7] = {
|
||||
.name = "SACCH/8(7)", /* 3GPP TS 05.02, section 3.3.4.1 */
|
||||
.desc = "Slow SDCCH/8 associated control channel (sub-channel 7)",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
|
||||
.chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3),
|
||||
.link_id = L1SCHED_CH_LID_SACCH,
|
||||
.ss_nr = 7,
|
||||
|
||||
/* Same as for L1SCHED_BCCH and L1SCHED_SDCCH8_* (xCCH), see above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_CBTX,
|
||||
.rx_fn = rx_data_fn,
|
||||
.tx_fn = tx_data_fn,
|
||||
},
|
||||
[L1SCHED_PDTCH] = {
|
||||
.name = "PDTCH", /* 3GPP TS 05.02, sections 3.2.4, 3.3.2.4 */
|
||||
.desc = "Packet data traffic & control channel",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_PDTCH,
|
||||
.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 = L1SCHED_CH_FLAG_PDCH,
|
||||
.rx_fn = rx_pdtch_fn,
|
||||
.tx_fn = tx_pdtch_fn,
|
||||
},
|
||||
[L1SCHED_PTCCH] = {
|
||||
.name = "PTCCH", /* 3GPP TS 05.02, section 3.3.4.2 */
|
||||
.desc = "Packet Timing advance control channel",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_PTCCH,
|
||||
.chan_nr = RSL_CHAN_OSMO_PDCH,
|
||||
.link_id = L1SCHED_CH_LID_PTCCH,
|
||||
|
||||
/* On the Uplink, mobile stations transmit random Access Bursts
|
||||
* to allow estimation of the timing advance for one MS in packet
|
||||
* transfer mode. On Downlink, the network sends timing advance
|
||||
* updates for several mobile stations. The coding scheme used
|
||||
* for PTCCH/D messages is the same as for PDTCH CS-1. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_PDCH,
|
||||
.rx_fn = rx_pdtch_fn,
|
||||
.tx_fn = tx_rach_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH4_CBCH] = {
|
||||
.name = "SDCCH/4(CBCH)", /* 3GPP TS 05.02, section 3.3.5 */
|
||||
.desc = "Cell Broadcast channel on SDCCH/4",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_CBCH51,
|
||||
.chan_nr = RSL_CHAN_OSMO_CBCH4,
|
||||
.ss_nr = 2,
|
||||
|
||||
/* Same as for L1SCHED_BCCH (xCCH), but Rx only. See above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.flags = L1SCHED_CH_FLAG_AUTO,
|
||||
.rx_fn = rx_data_fn,
|
||||
},
|
||||
[L1SCHED_SDCCH8_CBCH] = {
|
||||
.name = "SDCCH/8(CBCH)", /* 3GPP TS 05.02, section 3.3.5 */
|
||||
.desc = "Cell Broadcast channel on SDCCH/8",
|
||||
.gsmtap_chan_type = GSMTAP_CHANNEL_CBCH52,
|
||||
.chan_nr = RSL_CHAN_OSMO_CBCH8,
|
||||
.ss_nr = 2,
|
||||
|
||||
/* Same as for L1SCHED_BCCH (xCCH), but Rx only. See above. */
|
||||
.burst_buf_size = 4 * GSM_BURST_PL_LEN,
|
||||
.rx_fn = rx_data_fn,
|
||||
},
|
||||
};
|
||||
172
trxcon/src/sched_lchan_pdtch.c
Normal file
172
trxcon/src/sched_lchan_pdtch.c
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: handlers for DL / UL bursts on logical channels
|
||||
*
|
||||
* (C) 2018-2022 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
* Contributions by sysmocom - s.f.m.c. GmbH
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
|
||||
#include <osmocom/bb/l1sched/l1sched.h>
|
||||
#include <osmocom/bb/l1sched/logging.h>
|
||||
|
||||
int rx_pdtch_fn(struct l1sched_lchan_state *lchan,
|
||||
uint32_t fn, uint8_t bid, const sbit_t *bits,
|
||||
const struct l1sched_meas_set *meas)
|
||||
{
|
||||
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 */
|
||||
mask = &lchan->rx_burst_mask;
|
||||
buffer = lchan->rx_bursts;
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_DEBUG, "Packet data received: fn=%u bid=%u\n", fn, bid);
|
||||
|
||||
/* Align to the first burst of a block */
|
||||
if (*mask == 0x00 && bid != 0)
|
||||
return 0;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* Store the measurements */
|
||||
l1sched_lchan_meas_push(lchan, meas);
|
||||
|
||||
/* Copy burst to buffer of 4 bursts */
|
||||
offset = buffer + bid * 116;
|
||||
memcpy(offset, bits + 3, 58);
|
||||
memcpy(offset + 58, bits + 87, 58);
|
||||
|
||||
/* Wait until complete set of bursts */
|
||||
if (bid != 3)
|
||||
return 0;
|
||||
|
||||
/* Calculate AVG of the measurements */
|
||||
l1sched_lchan_meas_avg(lchan, 4);
|
||||
|
||||
/* Check for complete set of bursts */
|
||||
if ((*mask & 0xf) != 0xf) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Received incomplete (%s) packet data at fn=%u (%u/%u)\n",
|
||||
l1sched_burst_mask2str(mask, 4), lchan->meas_avg.fn,
|
||||
lchan->meas_avg.fn % lchan->ts->mf_layout->period,
|
||||
lchan->ts->mf_layout->period);
|
||||
/* NOTE: do not abort here, give it a try. Maybe we're lucky ;) */
|
||||
}
|
||||
|
||||
/* Keep the mask updated */
|
||||
*mask = *mask << 4;
|
||||
|
||||
/* Attempt to decode */
|
||||
rc = gsm0503_pdtch_decode(l2, buffer,
|
||||
NULL, &n_errors, &n_bits_total);
|
||||
if (rc < 0) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Received bad frame (rc=%d, ber=%d/%d) at fn=%u\n",
|
||||
rc, n_errors, n_bits_total, lchan->meas_avg.fn);
|
||||
}
|
||||
|
||||
/* Determine L2 length */
|
||||
l2_len = rc > 0 ? rc : 0;
|
||||
|
||||
/* Send a L2 frame to the higher layers */
|
||||
l1sched_handle_data_ind(lchan, l2, l2_len, n_errors, n_bits_total, L1SCHED_DT_PACKET_DATA);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int tx_pdtch_fn(struct l1sched_lchan_state *lchan,
|
||||
struct l1sched_burst_req *br)
|
||||
{
|
||||
ubit_t *buffer, *offset;
|
||||
const uint8_t *tsc;
|
||||
uint8_t *mask;
|
||||
int rc;
|
||||
|
||||
/* Set up pointers */
|
||||
mask = &lchan->tx_burst_mask;
|
||||
buffer = lchan->tx_bursts;
|
||||
|
||||
if (br->bid > 0) {
|
||||
/* If we have encoded bursts */
|
||||
if (*mask)
|
||||
goto send_burst;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Encode payload */
|
||||
rc = gsm0503_pdtch_encode(buffer, lchan->prim->payload,
|
||||
lchan->prim->payload_len);
|
||||
if (rc < 0) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
|
||||
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
|
||||
lchan->prim->payload_len));
|
||||
|
||||
/* Forget this primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
send_burst:
|
||||
/* Determine which burst should be sent */
|
||||
offset = buffer + br->bid * 116;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << br->bid);
|
||||
|
||||
/* Choose proper TSC */
|
||||
tsc = l1sched_nb_training_bits[lchan->tsc];
|
||||
|
||||
/* Compose a new burst */
|
||||
memset(br->burst, 0, 3); /* TB */
|
||||
memcpy(br->burst + 3, offset, 58); /* Payload 1/2 */
|
||||
memcpy(br->burst + 61, tsc, 26); /* TSC */
|
||||
memcpy(br->burst + 87, offset + 58, 58); /* Payload 2/2 */
|
||||
memset(br->burst + 145, 0, 3); /* TB */
|
||||
br->burst_len = GSM_BURST_LEN;
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled at fn=%u burst=%u\n", br->fn, br->bid);
|
||||
|
||||
/* If we have sent the last (4/4) burst */
|
||||
if ((*mask & 0x0f) == 0x0f) {
|
||||
/* Confirm data / traffic sending */
|
||||
l1sched_handle_data_cnf(lchan, br->fn, L1SCHED_DT_PACKET_DATA);
|
||||
|
||||
/* Forget processed primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
|
||||
/* Reset mask */
|
||||
*mask = 0x00;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
160
trxcon/src/sched_lchan_rach.c
Normal file
160
trxcon/src/sched_lchan_rach.c
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: handlers for DL / UL bursts on logical channels
|
||||
*
|
||||
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
* Contributions by sysmocom - s.f.m.c. GmbH
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
|
||||
#include <osmocom/bb/l1sched/l1sched.h>
|
||||
#include <osmocom/bb/l1sched/logging.h>
|
||||
|
||||
/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)" */
|
||||
#define RACH_EXT_TAIL_BITS_LEN 8
|
||||
#define RACH_SYNCH_SEQ_LEN 41
|
||||
#define RACH_PAYLOAD_LEN 36
|
||||
|
||||
/* Extended tail bits (BN0..BN7) */
|
||||
static const ubit_t rach_ext_tail_bits[] = {
|
||||
0, 0, 1, 1, 1, 0, 1, 0,
|
||||
};
|
||||
|
||||
/* Synchronization (training) sequence types */
|
||||
enum rach_synch_seq_t {
|
||||
RACH_SYNCH_SEQ_UNKNOWN = -1,
|
||||
RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */
|
||||
RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */
|
||||
RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */
|
||||
RACH_SYNCH_SEQ_NUM
|
||||
};
|
||||
|
||||
/* Synchronization (training) sequence bits */
|
||||
static const char rach_synch_seq_bits[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = {
|
||||
[RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000",
|
||||
[RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101",
|
||||
[RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111",
|
||||
};
|
||||
|
||||
/* Synchronization (training) sequence names */
|
||||
static struct value_string rach_synch_seq_names[] = {
|
||||
{ RACH_SYNCH_SEQ_UNKNOWN, "UNKNOWN" },
|
||||
{ RACH_SYNCH_SEQ_TS0, "TS0: GSM, GMSK" },
|
||||
{ RACH_SYNCH_SEQ_TS1, "TS1: EGPRS, 8-PSK" },
|
||||
{ RACH_SYNCH_SEQ_TS2, "TS2: EGPRS, GMSK" },
|
||||
{ 0, NULL },
|
||||
};
|
||||
|
||||
/* Obtain a to-be-transmitted RACH burst */
|
||||
int tx_rach_fn(struct l1sched_lchan_state *lchan,
|
||||
struct l1sched_burst_req *br)
|
||||
{
|
||||
const uint8_t bsic = lchan->ts->sched->bsic;
|
||||
struct l1sched_ts_prim_rach *rach;
|
||||
uint8_t *burst_ptr = br->burst;
|
||||
uint8_t payload[36];
|
||||
int i, rc;
|
||||
|
||||
rach = (struct l1sched_ts_prim_rach *)lchan->prim->payload;
|
||||
|
||||
/* Delay sending according to offset value */
|
||||
if (rach->offset-- > 0)
|
||||
return 0;
|
||||
|
||||
if (L1SCHED_PRIM_IS_RACH11(lchan->prim)) {
|
||||
/* Check requested synch. sequence */
|
||||
if (rach->synch_seq >= RACH_SYNCH_SEQ_NUM) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Unknown RACH synch. sequence=0x%02x\n",
|
||||
rach->synch_seq);
|
||||
|
||||
/* Forget this primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/* Encode 11-bit payload */
|
||||
rc = gsm0503_rach_ext_encode(payload, rach->ra, bsic, true);
|
||||
if (rc) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Could not encode 11-bit RACH burst (ra=%u bsic=%u)\n",
|
||||
rach->ra, bsic);
|
||||
|
||||
/* Forget this primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
return rc;
|
||||
}
|
||||
} else if (L1SCHED_PRIM_IS_RACH8(lchan->prim)) {
|
||||
rach->synch_seq = RACH_SYNCH_SEQ_TS0;
|
||||
|
||||
/* Encode 8-bit payload */
|
||||
rc = gsm0503_rach_ext_encode(payload, rach->ra, bsic, false);
|
||||
if (rc) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Could not encode RACH burst (ra=%u bsic=%u)\n",
|
||||
rach->ra, bsic);
|
||||
|
||||
/* Forget this primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
return rc;
|
||||
}
|
||||
} else {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Primitive has unexpected type=0x%02x\n",
|
||||
lchan->prim->type);
|
||||
l1sched_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[rach->synch_seq][i] == '1';
|
||||
|
||||
/* BN49-84: encrypted bits (the payload) */
|
||||
memcpy(burst_ptr, payload, RACH_PAYLOAD_LEN);
|
||||
burst_ptr += RACH_PAYLOAD_LEN;
|
||||
|
||||
/* BN85-156: tail bits & extended guard period */
|
||||
memset(burst_ptr, 0, br->burst + GSM_BURST_LEN - burst_ptr);
|
||||
br->burst_len = GSM_BURST_LEN;
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_NOTICE, "Scheduled %s-bit RACH (%s) at fn=%u\n",
|
||||
L1SCHED_PRIM_IS_RACH11(lchan->prim) ? "11" : "8",
|
||||
get_value_string(rach_synch_seq_names, rach->synch_seq), br->fn);
|
||||
|
||||
/* Confirm RACH request */
|
||||
l1sched_handle_data_cnf(lchan, br->fn, L1SCHED_DT_OTHER);
|
||||
|
||||
/* Forget processed primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
|
||||
return 0;
|
||||
}
|
||||
106
trxcon/src/sched_lchan_sch.c
Normal file
106
trxcon/src/sched_lchan_sch.c
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: handlers for DL / UL bursts on logical channels
|
||||
*
|
||||
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
* Contributions by sysmocom - s.f.m.c. GmbH
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <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 <osmocom/bb/l1sched/l1sched.h>
|
||||
#include <osmocom/bb/l1sched/logging.h>
|
||||
|
||||
static void decode_sb(struct gsm_time *time, uint8_t *bsic, uint8_t *sb_info)
|
||||
{
|
||||
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 l1sched_lchan_state *lchan,
|
||||
uint32_t fn, uint8_t bid, const sbit_t *bits,
|
||||
const struct l1sched_meas_set *meas)
|
||||
{
|
||||
sbit_t payload[2 * 39];
|
||||
struct gsm_time time;
|
||||
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(sb_info, payload);
|
||||
if (rc) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "Received bad SCH burst at fn=%u\n", fn);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Decode BSIC and TDMA frame number */
|
||||
decode_sb(&time, &bsic, sb_info);
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_DEBUG, "Received SCH: bsic=%u, fn=%u, sched_fn=%u\n",
|
||||
bsic, time.fn, lchan->ts->sched->fn_counter_proc);
|
||||
|
||||
/* Check if decoded frame number matches */
|
||||
if (time.fn != fn) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Decoded fn=%u does not match fn=%u provided by scheduler\n",
|
||||
time.fn, fn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Update BSIC value in the scheduler state */
|
||||
lchan->ts->sched->bsic = bsic;
|
||||
|
||||
l1sched_handle_data_ind(lchan, (const uint8_t *)&time, sizeof(time),
|
||||
0, 39 * 2, L1SCHED_DT_OTHER);
|
||||
|
||||
return 0;
|
||||
}
|
||||
366
trxcon/src/sched_lchan_tchf.c
Normal file
366
trxcon/src/sched_lchan_tchf.c
Normal file
@@ -0,0 +1,366 @@
|
||||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: handlers for DL / UL bursts on logical channels
|
||||
*
|
||||
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
* Contributions by sysmocom - s.f.m.c. GmbH
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
#include <osmocom/coding/gsm0503_amr_dtx.h>
|
||||
#include <osmocom/codec/codec.h>
|
||||
|
||||
#include <osmocom/bb/l1sched/l1sched.h>
|
||||
#include <osmocom/bb/l1sched/logging.h>
|
||||
#include <osmocom/bb/trxcon/sched_utils.h>
|
||||
|
||||
/* 3GPP TS 45.009, table 3.2.1.3-{1,3}: AMR on Downlink TCH/F.
|
||||
*
|
||||
* +---+---+---+---+---+---+---+---+
|
||||
* | a | b | c | d | e | f | g | h | Burst 'a' received first
|
||||
* +---+---+---+---+---+---+---+---+
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Speech/FACCH frame (bursts 'a' .. 'h')
|
||||
*
|
||||
* TDMA frame number of burst 'h' is always used as the table index. */
|
||||
static const uint8_t sched_tchf_dl_amr_cmi_map[26] = {
|
||||
[11] = 1, /* TCH/F: a=4 / h=11 */
|
||||
[20] = 1, /* TCH/F: a=13 / h=20 */
|
||||
[3] = 1, /* TCH/F: a=21 / h=3 (21+7=28, 25 is idle -> 29. 29%26=3) */
|
||||
};
|
||||
|
||||
int rx_tchf_fn(struct l1sched_lchan_state *lchan,
|
||||
uint32_t fn, uint8_t bid, const sbit_t *bits,
|
||||
const struct l1sched_meas_set *meas)
|
||||
{
|
||||
int n_errors = -1, n_bits_total = 0, rc;
|
||||
sbit_t *buffer, *offset;
|
||||
uint8_t l2[128], *mask;
|
||||
size_t l2_len;
|
||||
int amr = 0;
|
||||
uint8_t ft;
|
||||
bool amr_is_cmr;
|
||||
|
||||
/* Set up pointers */
|
||||
mask = &lchan->rx_burst_mask;
|
||||
buffer = lchan->rx_bursts;
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_DEBUG, "Traffic received: fn=%u bid=%u\n", fn, bid);
|
||||
|
||||
/* Align to the first burst of a block */
|
||||
if (*mask == 0x00 && bid != 0)
|
||||
return 0;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* Store the measurements */
|
||||
l1sched_lchan_meas_push(lchan, meas);
|
||||
|
||||
/* Copy burst to end of buffer of 8 bursts */
|
||||
offset = buffer + bid * 116 + 464;
|
||||
memcpy(offset, bits + 3, 58);
|
||||
memcpy(offset + 58, bits + 87, 58);
|
||||
|
||||
/* Wait until complete set of bursts */
|
||||
if (bid != 3)
|
||||
return 0;
|
||||
|
||||
/* Calculate AVG of the measurements */
|
||||
l1sched_lchan_meas_avg(lchan, 8);
|
||||
|
||||
/* Check for complete set of bursts */
|
||||
if ((*mask & 0xff) != 0xff) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Received incomplete (%s) traffic frame at fn=%u (%u/%u)\n",
|
||||
l1sched_burst_mask2str(mask, 8), lchan->meas_avg.fn,
|
||||
lchan->meas_avg.fn % lchan->ts->mf_layout->period,
|
||||
lchan->ts->mf_layout->period);
|
||||
/* NOTE: do not abort here, give it a try. Maybe we're lucky ;) */
|
||||
|
||||
}
|
||||
|
||||
/* Keep the mask updated */
|
||||
*mask = *mask << 4;
|
||||
|
||||
switch (lchan->tch_mode) {
|
||||
case GSM48_CMODE_SIGN:
|
||||
case GSM48_CMODE_SPEECH_V1: /* FR */
|
||||
rc = gsm0503_tch_fr_decode(l2, buffer,
|
||||
1, 0, &n_errors, &n_bits_total);
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_EFR: /* EFR */
|
||||
rc = gsm0503_tch_fr_decode(l2, buffer,
|
||||
1, 1, &n_errors, &n_bits_total);
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* AMR */
|
||||
/* the first FN 4,13,21 defines that CMI is included in frame,
|
||||
* the first FN 0,8,17 defines that CMR/CMC is included in frame.
|
||||
* NOTE: A frame ends 7 FN after start.
|
||||
*/
|
||||
amr_is_cmr = !sched_tchf_dl_amr_cmi_map[fn % 26];
|
||||
|
||||
/* we store tch_data + 2 header bytes, the amr variable set to
|
||||
* 2 will allow us to skip the first 2 bytes in case we did
|
||||
* receive an FACCH frame instead of a voice frame (we do not
|
||||
* know this before we actually decode the frame) */
|
||||
amr = 2;
|
||||
rc = gsm0503_tch_afs_decode_dtx(l2 + amr, buffer,
|
||||
amr_is_cmr, lchan->amr.codec, lchan->amr.codecs, &lchan->amr.dl_ft,
|
||||
&lchan->amr.dl_cmr, &n_errors, &n_bits_total, &lchan->amr.last_dtx);
|
||||
|
||||
/* only good speech frames get rtp header */
|
||||
if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
|
||||
if (lchan->amr.last_dtx == AMR_OTHER) {
|
||||
ft = lchan->amr.codec[lchan->amr.dl_ft];
|
||||
} else {
|
||||
/* SID frames will always get Frame Type Index 8 (AMR_SID) */
|
||||
ft = AMR_SID;
|
||||
}
|
||||
rc = osmo_amr_rtp_enc(l2,
|
||||
lchan->amr.codec[lchan->amr.dl_cmr],
|
||||
ft, AMR_GOOD);
|
||||
if (rc < 0)
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"osmo_amr_rtp_enc() returned rc=%d\n", rc);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Shift buffer by 4 bursts for interleaving */
|
||||
memcpy(buffer, buffer + 464, 464);
|
||||
|
||||
/* Check decoding result */
|
||||
if (rc < 4) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Received bad frame (rc=%d, ber=%d/%d) at fn=%u\n",
|
||||
rc, n_errors, n_bits_total, lchan->meas_avg.fn);
|
||||
|
||||
/* Send BFI */
|
||||
goto bfi;
|
||||
} else if (rc == GSM_MACBLOCK_LEN) {
|
||||
/* FACCH received, forward it to the higher layers */
|
||||
l1sched_handle_data_ind(lchan, l2 + amr, GSM_MACBLOCK_LEN,
|
||||
n_errors, n_bits_total,
|
||||
L1SCHED_DT_SIGNALING);
|
||||
|
||||
/* Send BFI substituting a stolen TCH frame */
|
||||
n_errors = -1; /* ensure fake measurements */
|
||||
goto bfi;
|
||||
} else {
|
||||
/* A good TCH frame received */
|
||||
l2_len = rc;
|
||||
}
|
||||
|
||||
/* Send a traffic frame to the higher layers */
|
||||
return l1sched_handle_data_ind(lchan, l2, l2_len, n_errors, n_bits_total, L1SCHED_DT_TRAFFIC);
|
||||
|
||||
bfi:
|
||||
/* Didn't try to decode, fake measurements */
|
||||
if (n_errors < 0) {
|
||||
lchan->meas_avg = (struct l1sched_meas_set) {
|
||||
.fn = lchan->meas_avg.fn,
|
||||
.toa256 = 0,
|
||||
.rssi = -110,
|
||||
};
|
||||
|
||||
/* No bursts => no errors */
|
||||
n_errors = 0;
|
||||
}
|
||||
|
||||
/* BFI is not applicable in signalling mode */
|
||||
if (lchan->tch_mode == GSM48_CMODE_SIGN) {
|
||||
return l1sched_handle_data_ind(lchan, NULL, 0,
|
||||
n_errors, n_bits_total,
|
||||
L1SCHED_DT_TRAFFIC);
|
||||
}
|
||||
|
||||
/* Bad frame indication */
|
||||
l2_len = l1sched_bad_frame_ind(l2, lchan);
|
||||
|
||||
/* Send a BFI frame to the higher layers */
|
||||
return l1sched_handle_data_ind(lchan, l2, l2_len,
|
||||
n_errors, n_bits_total,
|
||||
L1SCHED_DT_TRAFFIC);
|
||||
}
|
||||
|
||||
int tx_tchf_fn(struct l1sched_lchan_state *lchan,
|
||||
struct l1sched_burst_req *br)
|
||||
{
|
||||
ubit_t *buffer, *offset;
|
||||
const uint8_t *tsc;
|
||||
uint8_t *mask;
|
||||
size_t l2_len;
|
||||
int rc;
|
||||
|
||||
/* Set up pointers */
|
||||
mask = &lchan->tx_burst_mask;
|
||||
buffer = lchan->tx_bursts;
|
||||
|
||||
/* If we have encoded bursts */
|
||||
if (*mask)
|
||||
goto send_burst;
|
||||
|
||||
/* Wait until a first burst in period */
|
||||
if (br->bid > 0)
|
||||
return 0;
|
||||
|
||||
/* Shift buffer by 4 bursts back for interleaving */
|
||||
memcpy(buffer, buffer + 464, 464);
|
||||
|
||||
/* populate the buffer with bursts */
|
||||
if (L1SCHED_PRIM_IS_FACCH(lchan->prim)) {
|
||||
/* Encode payload */
|
||||
rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, GSM_MACBLOCK_LEN, 1);
|
||||
} else if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
|
||||
int len;
|
||||
uint8_t cmr_codec;
|
||||
int ft, cmr, i;
|
||||
enum osmo_amr_type ft_codec;
|
||||
enum osmo_amr_quality bfi;
|
||||
int8_t sti, cmi;
|
||||
bool amr_fn_is_cmr;
|
||||
/* the first FN 0,8,17 defines that CMI is included in frame,
|
||||
* the first FN 4,13,21 defines that CMR is included in frame.
|
||||
*/
|
||||
amr_fn_is_cmr = !ul_amr_fn_is_cmi(br->fn);
|
||||
|
||||
len = osmo_amr_rtp_dec(lchan->prim->payload, lchan->prim->payload_len,
|
||||
&cmr_codec, &cmi, &ft_codec,
|
||||
&bfi, &sti);
|
||||
if (len < 0) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "Cannot send invalid AMR payload (%zu): %s\n",
|
||||
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, lchan->prim->payload_len));
|
||||
goto free_bad_msg;
|
||||
}
|
||||
ft = -1;
|
||||
cmr = -1;
|
||||
for (i = 0; i < lchan->amr.codecs; i++) {
|
||||
if (lchan->amr.codec[i] == ft_codec)
|
||||
ft = i;
|
||||
if (lchan->amr.codec[i] == cmr_codec)
|
||||
cmr = i;
|
||||
}
|
||||
if (ft < 0) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Codec (FT = %d) of RTP frame not in list\n", ft_codec);
|
||||
goto free_bad_msg;
|
||||
}
|
||||
if (amr_fn_is_cmr && lchan->amr.ul_ft != ft) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Codec (FT = %d) of RTP cannot be changed now, but in next frame\n",
|
||||
ft_codec);
|
||||
goto free_bad_msg;
|
||||
}
|
||||
lchan->amr.ul_ft = ft;
|
||||
if (cmr < 0) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Codec (CMR = %d) of RTP frame not in list\n", cmr_codec);
|
||||
} else {
|
||||
lchan->amr.ul_cmr = cmr;
|
||||
}
|
||||
rc = gsm0503_tch_afs_encode(buffer, lchan->prim->payload + 2,
|
||||
lchan->prim->payload_len - 2, amr_fn_is_cmr,
|
||||
lchan->amr.codec, lchan->amr.codecs,
|
||||
lchan->amr.ul_ft,
|
||||
lchan->amr.ul_cmr);
|
||||
} else {
|
||||
/* Determine and check the payload length */
|
||||
switch (lchan->tch_mode) {
|
||||
case GSM48_CMODE_SIGN:
|
||||
case GSM48_CMODE_SPEECH_V1: /* FR */
|
||||
l2_len = GSM_FR_BYTES;
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_EFR: /* EFR */
|
||||
l2_len = GSM_EFR_BYTES;
|
||||
break;
|
||||
default:
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Invalid TCH mode: %u, dropping frame...\n",
|
||||
lchan->tch_mode);
|
||||
/* Forget this primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (lchan->prim->payload_len != l2_len) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "Primitive has odd length %zu "
|
||||
"(expected %zu for TCH or %u for FACCH), so dropping...\n",
|
||||
lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
|
||||
|
||||
l1sched_prim_drop(lchan);
|
||||
return -EINVAL;
|
||||
}
|
||||
rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, l2_len, 1);
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
|
||||
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
|
||||
lchan->prim->payload_len));
|
||||
free_bad_msg:
|
||||
/* Forget this primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
send_burst:
|
||||
/* Determine which burst should be sent */
|
||||
offset = buffer + br->bid * 116;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << br->bid);
|
||||
|
||||
/* Choose proper TSC */
|
||||
tsc = l1sched_nb_training_bits[lchan->tsc];
|
||||
|
||||
/* Compose a new burst */
|
||||
memset(br->burst, 0, 3); /* TB */
|
||||
memcpy(br->burst + 3, offset, 58); /* Payload 1/2 */
|
||||
memcpy(br->burst + 61, tsc, 26); /* TSC */
|
||||
memcpy(br->burst + 87, offset + 58, 58); /* Payload 2/2 */
|
||||
memset(br->burst + 145, 0, 3); /* TB */
|
||||
br->burst_len = GSM_BURST_LEN;
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled fn=%u burst=%u\n", br->fn, br->bid);
|
||||
|
||||
/* If we have sent the last (4/4) burst */
|
||||
if (*mask == 0x0f) {
|
||||
/* Confirm data / traffic sending */
|
||||
enum l1sched_data_type dt = L1SCHED_PRIM_IS_TCH(lchan->prim) ?
|
||||
L1SCHED_DT_TRAFFIC : L1SCHED_DT_SIGNALING;
|
||||
l1sched_handle_data_cnf(lchan, br->fn, dt);
|
||||
|
||||
/* Forget processed primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
|
||||
/* Reset mask */
|
||||
*mask = 0x00;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
587
trxcon/src/sched_lchan_tchh.c
Normal file
587
trxcon/src/sched_lchan_tchh.c
Normal file
@@ -0,0 +1,587 @@
|
||||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: handlers for DL / UL bursts on logical channels
|
||||
*
|
||||
* (C) 2018-2022 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
* (C) 2018 by Harald Welte <laforge@gnumonks.org>
|
||||
* Contributions by sysmocom - s.f.m.c. GmbH
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
#include <osmocom/coding/gsm0503_amr_dtx.h>
|
||||
#include <osmocom/codec/codec.h>
|
||||
|
||||
#include <osmocom/bb/l1sched/l1sched.h>
|
||||
#include <osmocom/bb/l1sched/logging.h>
|
||||
#include <osmocom/bb/trxcon/sched_utils.h>
|
||||
|
||||
/* 3GPP TS 45.009, table 3.2.1.3-{2,4}: AMR on Downlink TCH/H.
|
||||
*
|
||||
* +---+---+---+---+---+---+
|
||||
* | a | b | c | d | e | f | Burst 'a' received first
|
||||
* +---+---+---+---+---+---+
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^^ FACCH frame (bursts 'a' .. 'f')
|
||||
* ^^^^^^^^^^^^^^^ Speech frame (bursts 'a' .. 'd')
|
||||
*
|
||||
* TDMA frame number of burst 'f' is always used as the table index. */
|
||||
static const uint8_t sched_tchh_dl_amr_cmi_map[26] = {
|
||||
[15] = 1, /* TCH/H(0): a=4 / d=10 / f=15 */
|
||||
[23] = 1, /* TCH/H(0): a=13 / d=19 / f=23 */
|
||||
[6] = 1, /* TCH/H(0): a=21 / d=2 / f=6 */
|
||||
|
||||
[16] = 1, /* TCH/H(1): a=5 / d=11 / f=16 */
|
||||
[24] = 1, /* TCH/H(1): a=14 / d=20 / f=24 */
|
||||
[7] = 1, /* TCH/H(1): a=22 / d=3 / f=7 */
|
||||
};
|
||||
|
||||
static const uint8_t tch_h0_traffic_block_map[3][4] = {
|
||||
/* B0(0,2,4,6), B1(4,6,8,10), B2(8,10,0,2) */
|
||||
{ 0, 2, 4, 6 },
|
||||
{ 4, 6, 8, 10 },
|
||||
{ 8, 10, 0, 2 },
|
||||
};
|
||||
|
||||
static const uint8_t tch_h1_traffic_block_map[3][4] = {
|
||||
/* B0(1,3,5,7), B1(5,7,9,11), B2(9,11,1,3) */
|
||||
{ 1, 3, 5, 7 },
|
||||
{ 5, 7, 9, 11 },
|
||||
{ 9, 11, 1, 3 },
|
||||
};
|
||||
|
||||
static const uint8_t tch_h0_dl_facch_block_map[3][6] = {
|
||||
/* B0(4,6,8,10,13,15), B1(13,15,17,19,21,23), B2(21,23,0,2,4,6) */
|
||||
{ 4, 6, 8, 10, 13, 15 },
|
||||
{ 13, 15, 17, 19, 21, 23 },
|
||||
{ 21, 23, 0, 2, 4, 6 },
|
||||
};
|
||||
|
||||
static const uint8_t tch_h0_ul_facch_block_map[3][6] = {
|
||||
/* B0(0,2,4,6,8,10), B1(8,10,13,15,17,19), B2(17,19,21,23,0,2) */
|
||||
{ 0, 2, 4, 6, 8, 10 },
|
||||
{ 8, 10, 13, 15, 17, 19 },
|
||||
{ 17, 19, 21, 23, 0, 2 },
|
||||
};
|
||||
|
||||
static const uint8_t tch_h1_dl_facch_block_map[3][6] = {
|
||||
/* B0(5,7,9,11,14,16), B1(14,16,18,20,22,24), B2(22,24,1,3,5,7) */
|
||||
{ 5, 7, 9, 11, 14, 16 },
|
||||
{ 14, 16, 18, 20, 22, 24 },
|
||||
{ 22, 24, 1, 3, 5, 7 },
|
||||
};
|
||||
|
||||
const uint8_t tch_h1_ul_facch_block_map[3][6] = {
|
||||
/* B0(1,3,5,7,9,11), B1(9,11,14,16,18,20), B2(18,20,22,24,1,3) */
|
||||
{ 1, 3, 5, 7, 9, 11 },
|
||||
{ 9, 11, 14, 16, 18, 20 },
|
||||
{ 18, 20, 22, 24, 1, 3 },
|
||||
};
|
||||
|
||||
/* FACCH/H channel mapping for Downlink (see 3GPP TS 45.002, table 1).
|
||||
* This mapping is valid for both FACCH/H(0) and FACCH/H(1).
|
||||
* TDMA frame number of burst 'f' is used as the table index. */
|
||||
static const uint8_t sched_tchh_dl_facch_map[26] = {
|
||||
[15] = 1, /* FACCH/H(0): B0(4,6,8,10,13,15) */
|
||||
[16] = 1, /* FACCH/H(1): B0(5,7,9,11,14,16) */
|
||||
[23] = 1, /* FACCH/H(0): B1(13,15,17,19,21,23) */
|
||||
[24] = 1, /* FACCH/H(1): B1(14,16,18,20,22,24) */
|
||||
[6] = 1, /* FACCH/H(0): B2(21,23,0,2,4,6) */
|
||||
[7] = 1, /* FACCH/H(1): B2(22,24,1,3,5,7) */
|
||||
};
|
||||
|
||||
/**
|
||||
* Can a TCH/H block transmission be initiated / finished
|
||||
* on a given frame number and a given channel type?
|
||||
*
|
||||
* See GSM 05.02, clause 7, table 1
|
||||
*
|
||||
* @param chan channel type (L1SCHED_TCHH_0 or L1SCHED_TCHH_1)
|
||||
* @param fn the current frame number
|
||||
* @param ul Uplink or Downlink?
|
||||
* @param facch FACCH/H or traffic?
|
||||
* @param start init or end of transmission?
|
||||
* @return true (yes) or false (no)
|
||||
*/
|
||||
bool l1sched_tchh_block_map_fn(enum l1sched_lchan_type chan,
|
||||
uint32_t fn, bool ul, bool facch, bool start)
|
||||
{
|
||||
uint8_t fn_mf;
|
||||
int i = 0;
|
||||
|
||||
/* Just to be sure */
|
||||
OSMO_ASSERT(chan == L1SCHED_TCHH_0 || chan == L1SCHED_TCHH_1);
|
||||
|
||||
/* Calculate a modulo */
|
||||
fn_mf = facch ? (fn % 26) : (fn % 13);
|
||||
|
||||
#define MAP_GET_POS(map) \
|
||||
(start ? 0 : ARRAY_SIZE(map[i]) - 1)
|
||||
|
||||
#define BLOCK_MAP_FN(map) \
|
||||
do { \
|
||||
if (map[i][MAP_GET_POS(map)] == fn_mf) \
|
||||
return true; \
|
||||
} while (++i < ARRAY_SIZE(map))
|
||||
|
||||
/* Choose a proper block map */
|
||||
if (facch) {
|
||||
if (ul) {
|
||||
if (chan == L1SCHED_TCHH_0)
|
||||
BLOCK_MAP_FN(tch_h0_ul_facch_block_map);
|
||||
else
|
||||
BLOCK_MAP_FN(tch_h1_ul_facch_block_map);
|
||||
} else {
|
||||
if (chan == L1SCHED_TCHH_0)
|
||||
BLOCK_MAP_FN(tch_h0_dl_facch_block_map);
|
||||
else
|
||||
BLOCK_MAP_FN(tch_h1_dl_facch_block_map);
|
||||
}
|
||||
} else {
|
||||
if (chan == L1SCHED_TCHH_0)
|
||||
BLOCK_MAP_FN(tch_h0_traffic_block_map);
|
||||
else
|
||||
BLOCK_MAP_FN(tch_h1_traffic_block_map);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a frame number of the first burst
|
||||
* using given frame number of the last burst.
|
||||
*
|
||||
* See GSM 05.02, clause 7, table 1
|
||||
*
|
||||
* @param chan channel type (L1SCHED_TCHH_0 or L1SCHED_TCHH_1)
|
||||
* @param last_fn frame number of the last burst
|
||||
* @param facch FACCH/H or traffic?
|
||||
* @return either frame number of the first burst,
|
||||
* or fn=last_fn if calculation failed
|
||||
*/
|
||||
static uint32_t tchh_block_dl_first_fn(const struct l1sched_lchan_state *lchan,
|
||||
uint32_t last_fn, bool facch)
|
||||
{
|
||||
enum l1sched_lchan_type chan = lchan->type;
|
||||
uint8_t fn_mf, fn_diff;
|
||||
int i = 0;
|
||||
|
||||
/* Just to be sure */
|
||||
OSMO_ASSERT(chan == L1SCHED_TCHH_0 || chan == L1SCHED_TCHH_1);
|
||||
|
||||
/* Calculate a modulo */
|
||||
fn_mf = facch ? (last_fn % 26) : (last_fn % 13);
|
||||
|
||||
#define BLOCK_FIRST_FN(map) \
|
||||
do { \
|
||||
if (map[i][ARRAY_SIZE(map[i]) - 1] == fn_mf) { \
|
||||
fn_diff = GSM_TDMA_FN_DIFF(fn_mf, map[i][0]); \
|
||||
return GSM_TDMA_FN_SUB(last_fn, fn_diff); \
|
||||
} \
|
||||
} while (++i < ARRAY_SIZE(map))
|
||||
|
||||
/* Choose a proper block map */
|
||||
if (facch) {
|
||||
if (chan == L1SCHED_TCHH_0)
|
||||
BLOCK_FIRST_FN(tch_h0_dl_facch_block_map);
|
||||
else
|
||||
BLOCK_FIRST_FN(tch_h1_dl_facch_block_map);
|
||||
} else {
|
||||
if (chan == L1SCHED_TCHH_0)
|
||||
BLOCK_FIRST_FN(tch_h0_traffic_block_map);
|
||||
else
|
||||
BLOCK_FIRST_FN(tch_h1_traffic_block_map);
|
||||
}
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Failed to calculate TDMA frame number of the first burst of %s block, "
|
||||
"using the current fn=%u\n", facch ? "FACCH/H" : "TCH/H", last_fn);
|
||||
|
||||
/* Couldn't calculate the first fn, return the last */
|
||||
return last_fn;
|
||||
}
|
||||
|
||||
int rx_tchh_fn(struct l1sched_lchan_state *lchan,
|
||||
uint32_t fn, uint8_t bid, const sbit_t *bits,
|
||||
const struct l1sched_meas_set *meas)
|
||||
{
|
||||
int n_errors = -1, n_bits_total = 0, rc;
|
||||
sbit_t *buffer, *offset;
|
||||
uint8_t l2[128], *mask;
|
||||
size_t l2_len;
|
||||
int amr = 0;
|
||||
uint8_t ft;
|
||||
bool fn_is_cmi;
|
||||
|
||||
/* Set up pointers */
|
||||
mask = &lchan->rx_burst_mask;
|
||||
buffer = lchan->rx_bursts;
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_DEBUG, "Traffic received: fn=%u bid=%u\n", fn, bid);
|
||||
|
||||
if (*mask == 0x00) {
|
||||
/* Align to the first burst */
|
||||
if (bid > 0)
|
||||
return 0;
|
||||
|
||||
/* Align reception of the first FACCH/H frame */
|
||||
if (lchan->tch_mode == GSM48_CMODE_SIGN) {
|
||||
if (!l1sched_tchh_facch_start(lchan->type, fn, 0))
|
||||
return 0;
|
||||
} else { /* or TCH/H traffic frame */
|
||||
if (!l1sched_tchh_traffic_start(lchan->type, fn, 0))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* Store the measurements */
|
||||
l1sched_lchan_meas_push(lchan, meas);
|
||||
|
||||
/* Copy burst to the end of buffer of 6 bursts */
|
||||
offset = buffer + bid * 116 + 464;
|
||||
memcpy(offset, bits + 3, 58);
|
||||
memcpy(offset + 58, bits + 87, 58);
|
||||
|
||||
/* Wait until the second burst */
|
||||
if (bid != 1)
|
||||
return 0;
|
||||
|
||||
/* Wait for complete set of bursts */
|
||||
if (lchan->tch_mode == GSM48_CMODE_SIGN) {
|
||||
/* FACCH/H is interleaved over 6 bursts */
|
||||
if ((*mask & 0x3f) != 0x3f)
|
||||
goto bfi_shift;
|
||||
} else {
|
||||
/* Traffic is interleaved over 4 bursts */
|
||||
if ((*mask & 0x0f) != 0x0f)
|
||||
goto bfi_shift;
|
||||
}
|
||||
|
||||
/* Skip decoding attempt in case of FACCH/H */
|
||||
if (lchan->dl_ongoing_facch) {
|
||||
lchan->dl_ongoing_facch = false;
|
||||
goto bfi_shift; /* 2/2 BFI */
|
||||
}
|
||||
|
||||
switch (lchan->tch_mode) {
|
||||
case GSM48_CMODE_SIGN:
|
||||
case GSM48_CMODE_SPEECH_V1: /* HR */
|
||||
rc = gsm0503_tch_hr_decode(l2, buffer,
|
||||
!l1sched_tchh_facch_end(lchan->type, fn, 0),
|
||||
&n_errors, &n_bits_total);
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* AMR */
|
||||
/* the first FN FN 4,13,21 or 5,14,22 defines that CMI is
|
||||
* included in frame, the first FN FN 0,8,17 or 1,9,18 defines
|
||||
* that CMR/CMC is included in frame. */
|
||||
fn_is_cmi = sched_tchh_dl_amr_cmi_map[fn % 26];
|
||||
|
||||
/* See comment in function rx_tchf_fn() */
|
||||
amr = 2;
|
||||
rc = gsm0503_tch_ahs_decode_dtx(l2 + amr, buffer,
|
||||
!sched_tchh_dl_facch_map[fn % 26],
|
||||
!fn_is_cmi, lchan->amr.codec, lchan->amr.codecs, &lchan->amr.dl_ft,
|
||||
&lchan->amr.dl_cmr, &n_errors, &n_bits_total, &lchan->amr.last_dtx);
|
||||
|
||||
/* only good speech frames get rtp header */
|
||||
if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
|
||||
if (lchan->amr.last_dtx == AMR_OTHER) {
|
||||
ft = lchan->amr.codec[lchan->amr.dl_ft];
|
||||
} else {
|
||||
/* SID frames will always get Frame Type Index 8 (AMR_SID) */
|
||||
ft = AMR_SID;
|
||||
}
|
||||
rc = osmo_amr_rtp_enc(l2,
|
||||
lchan->amr.codec[lchan->amr.dl_cmr],
|
||||
ft, AMR_GOOD);
|
||||
if (rc < 0)
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"osmo_amr_rtp_enc() returned rc=%d\n", rc);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Shift buffer by 4 bursts for interleaving */
|
||||
memcpy(buffer, buffer + 232, 232);
|
||||
memcpy(buffer + 232, buffer + 464, 232);
|
||||
|
||||
/* Shift burst mask */
|
||||
*mask = *mask << 2;
|
||||
|
||||
/* Check decoding result */
|
||||
if (rc < 4) {
|
||||
/* Calculate AVG of the measurements (assuming 4 bursts) */
|
||||
l1sched_lchan_meas_avg(lchan, 4);
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Received bad frame (rc=%d, ber=%d/%d) at fn=%u\n",
|
||||
rc, n_errors, n_bits_total, lchan->meas_avg.fn);
|
||||
|
||||
/* Send BFI */
|
||||
goto bfi;
|
||||
} else if (rc == GSM_MACBLOCK_LEN) {
|
||||
/* Skip decoding of the next 2 stolen bursts */
|
||||
lchan->dl_ongoing_facch = true;
|
||||
|
||||
/* Calculate AVG of the measurements (FACCH/H takes 6 bursts) */
|
||||
l1sched_lchan_meas_avg(lchan, 6);
|
||||
|
||||
/* FACCH/H received, forward to the higher layers */
|
||||
l1sched_handle_data_ind(lchan, l2 + amr, GSM_MACBLOCK_LEN,
|
||||
n_errors, n_bits_total,
|
||||
L1SCHED_DT_SIGNALING);
|
||||
|
||||
/* Send BFI substituting 1/2 stolen TCH frames */
|
||||
n_errors = -1; /* ensure fake measurements */
|
||||
goto bfi;
|
||||
} else {
|
||||
/* A good TCH frame received */
|
||||
l2_len = rc;
|
||||
|
||||
/* Calculate AVG of the measurements (traffic takes 4 bursts) */
|
||||
l1sched_lchan_meas_avg(lchan, 4);
|
||||
}
|
||||
|
||||
/* Send a traffic frame to the higher layers */
|
||||
return l1sched_handle_data_ind(lchan, l2, l2_len,
|
||||
n_errors, n_bits_total,
|
||||
L1SCHED_DT_TRAFFIC);
|
||||
|
||||
bfi_shift:
|
||||
/* Shift buffer */
|
||||
memcpy(buffer, buffer + 232, 232);
|
||||
memcpy(buffer + 232, buffer + 464, 232);
|
||||
|
||||
/* Shift burst mask */
|
||||
*mask = *mask << 2;
|
||||
|
||||
bfi:
|
||||
/* Didn't try to decode, fake measurements */
|
||||
if (n_errors < 0) {
|
||||
lchan->meas_avg = (struct l1sched_meas_set) {
|
||||
.fn = tchh_block_dl_first_fn(lchan, fn, false),
|
||||
.toa256 = 0,
|
||||
.rssi = -110,
|
||||
};
|
||||
|
||||
/* No bursts => no errors */
|
||||
n_errors = 0;
|
||||
}
|
||||
|
||||
/* BFI is not applicable in signalling mode */
|
||||
if (lchan->tch_mode == GSM48_CMODE_SIGN) {
|
||||
return l1sched_handle_data_ind(lchan, NULL, 0,
|
||||
n_errors, n_bits_total,
|
||||
L1SCHED_DT_SIGNALING);
|
||||
}
|
||||
|
||||
/* Bad frame indication */
|
||||
l2_len = l1sched_bad_frame_ind(l2, lchan);
|
||||
|
||||
/* Send a BFI frame to the higher layers */
|
||||
return l1sched_handle_data_ind(lchan, l2, l2_len,
|
||||
n_errors, n_bits_total,
|
||||
L1SCHED_DT_TRAFFIC);
|
||||
}
|
||||
|
||||
int tx_tchh_fn(struct l1sched_lchan_state *lchan,
|
||||
struct l1sched_burst_req *br)
|
||||
{
|
||||
ubit_t *buffer, *offset;
|
||||
const uint8_t *tsc;
|
||||
uint8_t *mask;
|
||||
size_t l2_len;
|
||||
int rc;
|
||||
|
||||
/* Set up pointers */
|
||||
mask = &lchan->tx_burst_mask;
|
||||
buffer = lchan->tx_bursts;
|
||||
|
||||
if (br->bid > 0) {
|
||||
/* Align to the first burst */
|
||||
if (*mask == 0x00)
|
||||
return 0;
|
||||
goto send_burst;
|
||||
}
|
||||
|
||||
if (*mask == 0x00) {
|
||||
/* Align transmission of the first FACCH/H frame */
|
||||
if (lchan->tch_mode == GSM48_CMODE_SIGN)
|
||||
if (!l1sched_tchh_facch_start(lchan->type, br->fn, 1))
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Shift buffer by 2 bursts back for interleaving */
|
||||
memcpy(buffer, buffer + 232, 232);
|
||||
|
||||
/* Also shift TX burst mask */
|
||||
*mask = *mask << 2;
|
||||
|
||||
/* If FACCH/H blocks are still pending */
|
||||
if (lchan->ul_facch_blocks > 2) {
|
||||
memcpy(buffer + 232, buffer + 464, 232);
|
||||
goto send_burst;
|
||||
}
|
||||
|
||||
/* populate the buffer with bursts */
|
||||
if (L1SCHED_PRIM_IS_FACCH(lchan->prim)) {
|
||||
rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, lchan->prim->payload_len);
|
||||
lchan->ul_facch_blocks = 6;
|
||||
} else if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
|
||||
int len;
|
||||
uint8_t cmr_codec;
|
||||
int ft, cmr, i;
|
||||
enum osmo_amr_type ft_codec;
|
||||
enum osmo_amr_quality bfi;
|
||||
int8_t sti, cmi;
|
||||
bool amr_fn_is_cmr;
|
||||
/* the first FN 0,8,17 defines that CMI is included in frame,
|
||||
* the first FN 4,13,21 defines that CMR is included in frame.
|
||||
*/
|
||||
amr_fn_is_cmr = !ul_amr_fn_is_cmi(br->fn);
|
||||
|
||||
len = osmo_amr_rtp_dec(lchan->prim->payload, lchan->prim->payload_len,
|
||||
&cmr_codec, &cmi, &ft_codec,
|
||||
&bfi, &sti);
|
||||
if (len < 0) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "Cannot send invalid AMR payload (%zu): %s\n",
|
||||
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, lchan->prim->payload_len));
|
||||
goto free_bad_msg;
|
||||
}
|
||||
ft = -1;
|
||||
cmr = -1;
|
||||
for (i = 0; i < lchan->amr.codecs; i++) {
|
||||
if (lchan->amr.codec[i] == ft_codec)
|
||||
ft = i;
|
||||
if (lchan->amr.codec[i] == cmr_codec)
|
||||
cmr = i;
|
||||
}
|
||||
if (ft < 0) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Codec (FT = %d) of RTP frame not in list\n", ft_codec);
|
||||
goto free_bad_msg;
|
||||
}
|
||||
if (amr_fn_is_cmr && lchan->amr.ul_ft != ft) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Codec (FT = %d) of RTP cannot be changed now, but in next frame\n",
|
||||
ft_codec);
|
||||
goto free_bad_msg;
|
||||
}
|
||||
lchan->amr.ul_ft = ft;
|
||||
if (cmr < 0) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Codec (CMR = %d) of RTP frame not in list\n", cmr_codec);
|
||||
} else {
|
||||
lchan->amr.ul_cmr = cmr;
|
||||
}
|
||||
rc = gsm0503_tch_ahs_encode(buffer, lchan->prim->payload + 2,
|
||||
lchan->prim->payload_len - 2, amr_fn_is_cmr,
|
||||
lchan->amr.codec, lchan->amr.codecs,
|
||||
lchan->amr.ul_ft,
|
||||
lchan->amr.ul_cmr);
|
||||
} else {
|
||||
/* Determine and check the payload length */
|
||||
switch (lchan->tch_mode) {
|
||||
case GSM48_CMODE_SIGN:
|
||||
case GSM48_CMODE_SPEECH_V1: /* HR */
|
||||
l2_len = GSM_HR_BYTES + 1;
|
||||
break;
|
||||
default:
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Invalid TCH mode: %u, dropping frame...\n",
|
||||
lchan->tch_mode);
|
||||
/* Forget this primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (lchan->prim->payload_len != l2_len) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "Primitive has odd length %zu "
|
||||
"(expected %zu for TCH or %u for FACCH), so dropping...\n",
|
||||
lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
|
||||
/* Forget this primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
return -EINVAL;
|
||||
}
|
||||
rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, l2_len);
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
|
||||
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
|
||||
lchan->prim->payload_len));
|
||||
free_bad_msg:
|
||||
/* Forget this primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
send_burst:
|
||||
/* Determine which burst should be sent */
|
||||
offset = buffer + br->bid * 116;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << br->bid);
|
||||
|
||||
/* Choose proper TSC */
|
||||
tsc = l1sched_nb_training_bits[lchan->tsc];
|
||||
|
||||
/* Compose a new burst */
|
||||
memset(br->burst, 0, 3); /* TB */
|
||||
memcpy(br->burst + 3, offset, 58); /* Payload 1/2 */
|
||||
memcpy(br->burst + 61, tsc, 26); /* TSC */
|
||||
memcpy(br->burst + 87, offset + 58, 58); /* Payload 2/2 */
|
||||
memset(br->burst + 145, 0, 3); /* TB */
|
||||
br->burst_len = GSM_BURST_LEN;
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled fn=%u burst=%u\n", br->fn, br->bid);
|
||||
|
||||
/* In case of a FACCH/H frame, one block less */
|
||||
if (lchan->ul_facch_blocks)
|
||||
lchan->ul_facch_blocks--;
|
||||
|
||||
if ((*mask & 0x0f) == 0x0f) {
|
||||
/**
|
||||
* If no more FACCH/H blocks pending,
|
||||
* confirm data / traffic sending
|
||||
*/
|
||||
if (!lchan->ul_facch_blocks) {
|
||||
enum l1sched_data_type dt = L1SCHED_PRIM_IS_TCH(lchan->prim) ?
|
||||
L1SCHED_DT_TRAFFIC : L1SCHED_DT_SIGNALING;
|
||||
l1sched_handle_data_cnf(lchan, br->fn, dt);
|
||||
}
|
||||
|
||||
/* Forget processed primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
178
trxcon/src/sched_lchan_xcch.c
Normal file
178
trxcon/src/sched_lchan_xcch.c
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: handlers for DL / UL bursts on logical channels
|
||||
*
|
||||
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
* Contributions by sysmocom - s.f.m.c. GmbH
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
|
||||
#include <osmocom/bb/l1sched/l1sched.h>
|
||||
#include <osmocom/bb/l1sched/logging.h>
|
||||
|
||||
int rx_data_fn(struct l1sched_lchan_state *lchan,
|
||||
uint32_t fn, uint8_t bid, const sbit_t *bits,
|
||||
const struct l1sched_meas_set *meas)
|
||||
{
|
||||
uint8_t l2[GSM_MACBLOCK_LEN], *mask;
|
||||
int n_errors, n_bits_total, rc;
|
||||
sbit_t *buffer, *offset;
|
||||
|
||||
/* Set up pointers */
|
||||
mask = &lchan->rx_burst_mask;
|
||||
buffer = lchan->rx_bursts;
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_DEBUG, "Data received: fn=%u bid=%u\n", fn, bid);
|
||||
|
||||
/* Align to the first burst of a block */
|
||||
if (*mask == 0x00 && bid != 0)
|
||||
return 0;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* Store the measurements */
|
||||
l1sched_lchan_meas_push(lchan, meas);
|
||||
|
||||
/* Copy burst to buffer of 4 bursts */
|
||||
offset = buffer + bid * 116;
|
||||
memcpy(offset, bits + 3, 58);
|
||||
memcpy(offset + 58, bits + 87, 58);
|
||||
|
||||
/* Wait until complete set of bursts */
|
||||
if (bid != 3)
|
||||
return 0;
|
||||
|
||||
/* Calculate AVG of the measurements */
|
||||
l1sched_lchan_meas_avg(lchan, 4);
|
||||
|
||||
/* Check for complete set of bursts */
|
||||
if ((*mask & 0xf) != 0xf) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Received incomplete (%s) data frame at fn=%u (%u/%u)\n",
|
||||
l1sched_burst_mask2str(mask, 4), lchan->meas_avg.fn,
|
||||
lchan->meas_avg.fn % lchan->ts->mf_layout->period,
|
||||
lchan->ts->mf_layout->period);
|
||||
/* NOTE: xCCH has an insane amount of redundancy for error
|
||||
* correction, so even just 2 valid bursts might be enough
|
||||
* to reconstruct some L2 frames. This is why we do not
|
||||
* abort here. */
|
||||
}
|
||||
|
||||
/* Keep the mask updated */
|
||||
*mask = *mask << 4;
|
||||
|
||||
/* Attempt to decode */
|
||||
rc = gsm0503_xcch_decode(l2, buffer, &n_errors, &n_bits_total);
|
||||
if (rc) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Received bad frame (rc=%d, ber=%d/%d) at fn=%u\n",
|
||||
rc, n_errors, n_bits_total, lchan->meas_avg.fn);
|
||||
}
|
||||
|
||||
/* Send a L2 frame to the higher layers */
|
||||
return l1sched_handle_data_ind(lchan, l2, rc ? 0 : GSM_MACBLOCK_LEN,
|
||||
n_errors, n_bits_total,
|
||||
L1SCHED_DT_SIGNALING);
|
||||
}
|
||||
|
||||
int tx_data_fn(struct l1sched_lchan_state *lchan,
|
||||
struct l1sched_burst_req *br)
|
||||
{
|
||||
ubit_t *buffer, *offset;
|
||||
const uint8_t *tsc;
|
||||
uint8_t *mask;
|
||||
int rc;
|
||||
|
||||
/* Set up pointers */
|
||||
mask = &lchan->tx_burst_mask;
|
||||
buffer = lchan->tx_bursts;
|
||||
|
||||
if (br->bid > 0) {
|
||||
/* If we have encoded bursts */
|
||||
if (*mask)
|
||||
goto send_burst;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check the prim payload length */
|
||||
if (lchan->prim->payload_len != GSM_MACBLOCK_LEN) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR,
|
||||
"Primitive has odd length %zu (expected %u), so dropping...\n",
|
||||
lchan->prim->payload_len, GSM_MACBLOCK_LEN);
|
||||
|
||||
l1sched_prim_drop(lchan);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Encode payload */
|
||||
rc = gsm0503_xcch_encode(buffer, lchan->prim->payload);
|
||||
if (rc) {
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
|
||||
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
|
||||
lchan->prim->payload_len));
|
||||
|
||||
/* Forget this primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
send_burst:
|
||||
/* Determine which burst should be sent */
|
||||
offset = buffer + br->bid * 116;
|
||||
|
||||
/* Update mask */
|
||||
*mask |= (1 << br->bid);
|
||||
|
||||
/* Choose proper TSC */
|
||||
tsc = l1sched_nb_training_bits[lchan->tsc];
|
||||
|
||||
/* Compose a new burst */
|
||||
memset(br->burst, 0, 3); /* TB */
|
||||
memcpy(br->burst + 3, offset, 58); /* Payload 1/2 */
|
||||
memcpy(br->burst + 61, tsc, 26); /* TSC */
|
||||
memcpy(br->burst + 87, offset + 58, 58); /* Payload 2/2 */
|
||||
memset(br->burst + 145, 0, 3); /* TB */
|
||||
br->burst_len = GSM_BURST_LEN;
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_DEBUG, "Scheduled fn=%u burst=%u\n", br->fn, br->bid);
|
||||
|
||||
/* If we have sent the last (4/4) burst */
|
||||
if ((*mask & 0x0f) == 0x0f) {
|
||||
/* Confirm data sending */
|
||||
l1sched_handle_data_cnf(lchan, br->fn, L1SCHED_DT_SIGNALING);
|
||||
|
||||
/* Forget processed primitive */
|
||||
l1sched_prim_drop(lchan);
|
||||
|
||||
/* Reset mask */
|
||||
*mask = 0x00;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
2102
trxcon/src/sched_mframe.c
Normal file
2102
trxcon/src/sched_mframe.c
Normal file
File diff suppressed because it is too large
Load Diff
558
trxcon/src/sched_prim.c
Normal file
558
trxcon/src/sched_prim.c
Normal file
@@ -0,0 +1,558 @@
|
||||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: primitive management
|
||||
*
|
||||
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
* Contributions by sysmocom - s.f.m.c. GmbH
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <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 <osmocom/bb/l1sched/l1sched.h>
|
||||
#include <osmocom/bb/l1sched/logging.h>
|
||||
|
||||
/**
|
||||
* Initializes a new primitive by allocating memory
|
||||
* and filling some meta-information (e.g. lchan type).
|
||||
*
|
||||
* @param ctx parent talloc context
|
||||
* @param pl_len prim payload length
|
||||
* @param type prim payload type
|
||||
* @param chan_nr RSL channel description (used to set a proper chan)
|
||||
* @param link_id RSL link description (used to set a proper chan)
|
||||
* @return allocated primitive or NULL
|
||||
*/
|
||||
static struct l1sched_ts_prim *prim_alloc(void *ctx, size_t pl_len,
|
||||
enum l1sched_ts_prim_type type,
|
||||
uint8_t chan_nr, uint8_t link_id)
|
||||
{
|
||||
enum l1sched_lchan_type lchan_type;
|
||||
struct l1sched_ts_prim *prim;
|
||||
|
||||
/* Determine lchan type */
|
||||
lchan_type = l1sched_chan_nr2lchan_type(chan_nr, link_id);
|
||||
if (!lchan_type) {
|
||||
/* TODO: use proper logging context */
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "Couldn't determine lchan type "
|
||||
"for chan_nr=%02x and link_id=%02x\n", chan_nr, link_id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Allocate a new primitive */
|
||||
prim = talloc_zero_size(ctx, sizeof(*prim) + pl_len);
|
||||
if (prim == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Init primitive header */
|
||||
prim->payload_len = pl_len;
|
||||
prim->chan = lchan_type;
|
||||
prim->type = type;
|
||||
|
||||
return prim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a primitive to the end of transmit queue of a particular
|
||||
* timeslot, whose index is parsed from chan_nr.
|
||||
*
|
||||
* @param sched scheduler instance
|
||||
* @param chan_nr RSL channel description
|
||||
* @param link_id RSL link description
|
||||
* @param pl Payload data
|
||||
* @param pl_len Payload length
|
||||
* @return queued primitive or NULL
|
||||
*/
|
||||
struct l1sched_ts_prim *l1sched_prim_push(struct l1sched_state *sched,
|
||||
enum l1sched_ts_prim_type type,
|
||||
uint8_t chan_nr, uint8_t link_id,
|
||||
const uint8_t *pl, size_t pl_len)
|
||||
{
|
||||
struct l1sched_ts_prim *prim;
|
||||
struct l1sched_ts *ts;
|
||||
uint8_t tn;
|
||||
|
||||
/* Determine TS index */
|
||||
tn = chan_nr & 0x7;
|
||||
|
||||
/* Check whether required timeslot is allocated and configured */
|
||||
ts = sched->ts[tn];
|
||||
if (ts == NULL || ts->mf_layout == NULL) {
|
||||
LOGP_SCHEDC(sched, LOGL_ERROR, "Timeslot %u isn't configured\n", tn);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
prim = prim_alloc(ts, pl_len, type, chan_nr, link_id);
|
||||
if (prim == NULL)
|
||||
return NULL;
|
||||
|
||||
memcpy(&prim->payload[0], pl, pl_len);
|
||||
|
||||
/* Add primitive to TS transmit queue */
|
||||
llist_add_tail(&prim->list, &ts->tx_prims);
|
||||
|
||||
return prim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes a new primitive from cached RR Measurement Report.
|
||||
*
|
||||
* @param lchan lchan to assign a primitive
|
||||
* @return SACCH primitive to be transmitted
|
||||
*/
|
||||
static struct l1sched_ts_prim *prim_compose_mr(struct l1sched_lchan_state *lchan)
|
||||
{
|
||||
struct l1sched_ts_prim *prim;
|
||||
bool cached;
|
||||
|
||||
/* Allocate a new primitive */
|
||||
prim = prim_alloc(lchan, GSM_MACBLOCK_LEN, L1SCHED_PRIM_DATA,
|
||||
l1sched_lchan_desc[lchan->type].chan_nr,
|
||||
L1SCHED_CH_LID_SACCH);
|
||||
OSMO_ASSERT(prim != NULL);
|
||||
|
||||
/* Check if the MR cache is populated (verify LAPDm header) */
|
||||
cached = (lchan->sacch.mr_cache[2] != 0x00
|
||||
&& lchan->sacch.mr_cache[3] != 0x00
|
||||
&& lchan->sacch.mr_cache[4] != 0x00);
|
||||
if (!cached) {
|
||||
memcpy(&lchan->sacch.mr_cache[0],
|
||||
&lchan->ts->sched->sacch_cache[0],
|
||||
sizeof(lchan->sacch.mr_cache));
|
||||
}
|
||||
|
||||
/* Compose a new Measurement Report primitive */
|
||||
memcpy(&prim->payload[0], &lchan->sacch.mr_cache[0], GSM_MACBLOCK_LEN);
|
||||
|
||||
/* Inform about the cache usage count */
|
||||
if (++lchan->sacch.mr_cache_usage > 5) {
|
||||
LOGP_LCHAND(lchan, LOGL_NOTICE,
|
||||
"SACCH MR cache usage count=%u > 5 "
|
||||
"=> ancient measurements, please fix!\n",
|
||||
lchan->sacch.mr_cache_usage);
|
||||
}
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_NOTICE, "Using cached Measurement Report\n");
|
||||
|
||||
return 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 l1sched_ts_prim *prim_dequeue_sacch(struct llist_head *queue,
|
||||
struct l1sched_lchan_state *lchan)
|
||||
{
|
||||
struct l1sched_ts_prim *prim_nmr = NULL;
|
||||
struct l1sched_ts_prim *prim_mr = NULL;
|
||||
struct l1sched_ts_prim *prim;
|
||||
bool mr_now;
|
||||
|
||||
/* Shall we transmit MR now? */
|
||||
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_LCHAND(lchan, LOGL_DEBUG,
|
||||
"SACCH MR selection: mr_tx_last=%d prim_mr=%p prim_nmr=%p\n",
|
||||
lchan->sacch.mr_tx_last, prim_mr, prim_nmr);
|
||||
|
||||
/* Prioritize non-MR prim if possible */
|
||||
if (mr_now && prim_mr)
|
||||
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_LCHAND(lchan, LOGL_DEBUG, "SACCH MR cache has been updated\n");
|
||||
}
|
||||
|
||||
/* Update the MR transmission state */
|
||||
lchan->sacch.mr_tx_last = PRIM_IS_MR(prim);
|
||||
|
||||
LOGP_LCHAND(lchan, LOGL_DEBUG, "SACCH decision: %s\n",
|
||||
PRIM_IS_MR(prim) ? "Measurement Report" : "data frame");
|
||||
|
||||
return prim;
|
||||
}
|
||||
|
||||
/* Dequeues a primitive of a given channel type */
|
||||
static struct l1sched_ts_prim *prim_dequeue_one(struct llist_head *queue,
|
||||
enum l1sched_lchan_type lchan_type)
|
||||
{
|
||||
struct l1sched_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 L1SCHED_CHAN_IS_TCH(),
|
||||
* but the current approach is a bit more flexible,
|
||||
* and allows one to have both sub-slots of TCH/H
|
||||
* enabled on same timeslot e.g. for testing...
|
||||
*
|
||||
* @param queue transmit queue to take a prim from
|
||||
* @param lchan_type required channel type of a primitive,
|
||||
* e.g. L1SCHED_TCHF, L1SCHED_TCHH_0, or L1SCHED_TCHH_1
|
||||
* @param facch FACCH (true) or speech (false) prim?
|
||||
* @return either a FACCH, or a TCH primitive if found,
|
||||
* otherwise NULL
|
||||
*/
|
||||
static struct l1sched_ts_prim *prim_dequeue_tch(struct llist_head *queue,
|
||||
enum l1sched_lchan_type lchan_type, bool facch)
|
||||
{
|
||||
struct l1sched_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 (L1SCHED_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 l1sched_ts_prim *prim_dequeue_tch_f(struct llist_head *queue)
|
||||
{
|
||||
struct l1sched_ts_prim *facch;
|
||||
struct l1sched_ts_prim *tch;
|
||||
|
||||
/* Attempt to find a pair of both FACCH/F and TCH/F frames */
|
||||
facch = prim_dequeue_tch(queue, L1SCHED_TCHF, true);
|
||||
tch = prim_dequeue_tch(queue, L1SCHED_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 l1sched_ts_prim *prim_dequeue_tch_h(struct llist_head *queue,
|
||||
uint32_t fn, enum l1sched_lchan_type lchan_type)
|
||||
{
|
||||
struct l1sched_ts_prim *facch;
|
||||
struct l1sched_ts_prim *tch;
|
||||
bool facch_now;
|
||||
|
||||
/* May we initiate an UL FACCH/H frame transmission now? */
|
||||
facch_now = l1sched_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 l1sched_ts_prim *l1sched_prim_dequeue(struct llist_head *queue,
|
||||
uint32_t fn, struct l1sched_lchan_state *lchan)
|
||||
{
|
||||
/* SACCH is unorthodox, see 3GPP TS 04.08, section 3.4.1 */
|
||||
if (L1SCHED_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 L1SCHED_TCHF:
|
||||
return prim_dequeue_tch_f(queue);
|
||||
|
||||
/* FACCH/H prioritization is a bit more complex */
|
||||
case L1SCHED_TCHH_0:
|
||||
case L1SCHED_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 l1sched_prim_drop(struct l1sched_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 l1sched_prim_dummy(struct l1sched_lchan_state *lchan)
|
||||
{
|
||||
enum l1sched_lchan_type chan = lchan->type;
|
||||
uint8_t tch_mode = lchan->tch_mode;
|
||||
struct l1sched_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(!L1SCHED_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 (L1SCHED_CHAN_IS_TCH(chan) && L1SCHED_TCH_MODE_IS_SPEECH(tch_mode)) {
|
||||
/* Bad frame indication */
|
||||
prim_len = l1sched_bad_frame_ind(prim_buffer, lchan);
|
||||
} else if (L1SCHED_CHAN_IS_TCH(chan) && L1SCHED_TCH_MODE_IS_DATA(tch_mode)) {
|
||||
/* FIXME: should we do anything for CSD? */
|
||||
return 0;
|
||||
} else {
|
||||
/* 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 l1sched_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_LCHAND(lchan, LOGL_DEBUG, "Transmitting a dummy / silence frame\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes a queue of primitives
|
||||
*
|
||||
* @param list list of prims going to be flushed
|
||||
*/
|
||||
void l1sched_prim_flush_queue(struct llist_head *list)
|
||||
{
|
||||
struct l1sched_ts_prim *prim, *prim_next;
|
||||
|
||||
llist_for_each_entry_safe(prim, prim_next, list, list) {
|
||||
llist_del(&prim->list);
|
||||
talloc_free(prim);
|
||||
}
|
||||
}
|
||||
877
trxcon/src/sched_trx.c
Normal file
877
trxcon/src/sched_trx.c
Normal file
@@ -0,0 +1,877 @@
|
||||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* TDMA scheduler: GSM PHY routines
|
||||
*
|
||||
* (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
* Contributions by sysmocom - s.f.m.c. GmbH
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <error.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <talloc.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <osmocom/gsm/a5.h>
|
||||
#include <osmocom/gsm/protocol/gsm_08_58.h>
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
|
||||
#include <osmocom/bb/l1sched/l1sched.h>
|
||||
#include <osmocom/bb/l1sched/logging.h>
|
||||
|
||||
/* Logging categories to be used for common/data messages */
|
||||
int l1sched_log_cat_common = DLGLOBAL;
|
||||
int l1sched_log_cat_data = DLGLOBAL;
|
||||
|
||||
/* "Dummy" Measurement Report */
|
||||
static const uint8_t meas_rep_dummy[] = {
|
||||
/* L1 SACCH pseudo-header */
|
||||
0x0f, 0x00,
|
||||
|
||||
/* LAPDm header */
|
||||
0x01, 0x03, 0x49,
|
||||
|
||||
/* RR Management messages, Measurement Report */
|
||||
0x06, 0x15,
|
||||
|
||||
/* Measurement results (see 3GPP TS 44.018, section 10.5.2.20):
|
||||
* 0... .... = BA-USED: 0
|
||||
* .0.. .... = DTX-USED: DTX was not used
|
||||
* ..11 0110 = RXLEV-FULL-SERVING-CELL: -57 <= x < -56 dBm (54)
|
||||
* 0... .... = 3G-BA-USED: 0
|
||||
* .1.. .... = MEAS-VALID: The measurement results are not valid
|
||||
* ..11 0110 = RXLEV-SUB-SERVING-CELL: -57 <= x < -56 dBm (54)
|
||||
* 0... .... = SI23_BA_USED: 0
|
||||
* .000 .... = RXQUAL-FULL-SERVING-CELL: BER < 0.2%, Mean value 0.14% (0)
|
||||
* .... 000. = RXQUAL-SUB-SERVING-CELL: BER < 0.2%, Mean value 0.14% (0)
|
||||
* .... ...1 11.. .... = NO-NCELL-M: Neighbour cell information not available */
|
||||
0x36, 0x76, 0x01, 0xc0,
|
||||
|
||||
/* 0** -- Padding with zeroes */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static int l1sched_cfg_pchan_comb_req(struct l1sched_state *sched,
|
||||
uint8_t tn, enum gsm_phys_chan_config pchan)
|
||||
{
|
||||
const struct l1sched_config_req cr = {
|
||||
.type = L1SCHED_CFG_PCHAN_COMB,
|
||||
.pchan_comb = {
|
||||
.tn = tn,
|
||||
.pchan = pchan,
|
||||
},
|
||||
};
|
||||
|
||||
return l1sched_handle_config_req(sched, &cr);
|
||||
}
|
||||
|
||||
static void l1sched_a5_burst_enc(struct l1sched_lchan_state *lchan,
|
||||
struct l1sched_burst_req *br);
|
||||
|
||||
static void sched_frame_clck_cb(struct l1sched_state *sched)
|
||||
{
|
||||
struct l1sched_burst_req br[TRX_TS_COUNT];
|
||||
const struct l1sched_tdma_frame *frame;
|
||||
struct l1sched_lchan_state *lchan;
|
||||
l1sched_lchan_tx_func *handler;
|
||||
enum l1sched_lchan_type chan;
|
||||
uint8_t offset;
|
||||
struct l1sched_ts *ts;
|
||||
unsigned int tn;
|
||||
|
||||
/* Advance TDMA frame number in order to give the transceiver
|
||||
* more time to handle the burst before the actual transmission. */
|
||||
const uint32_t fn = GSM_TDMA_FN_SUM(sched->fn_counter_proc,
|
||||
sched->fn_counter_advance);
|
||||
|
||||
/* Iterate over timeslot list */
|
||||
for (tn = 0; tn < ARRAY_SIZE(br); tn++) {
|
||||
/* Initialize the buffer for this timeslot */
|
||||
br[tn] = (struct l1sched_burst_req) {
|
||||
.fn = fn,
|
||||
.tn = tn,
|
||||
.burst_len = 0, /* NOPE.ind */
|
||||
};
|
||||
|
||||
/* Timeslot is not allocated */
|
||||
ts = sched->ts[tn];
|
||||
if (ts == NULL)
|
||||
continue;
|
||||
|
||||
/* Timeslot is not configured */
|
||||
if (ts->mf_layout == NULL)
|
||||
continue;
|
||||
|
||||
/* Get frame from multiframe */
|
||||
offset = fn % ts->mf_layout->period;
|
||||
frame = ts->mf_layout->frames + offset;
|
||||
|
||||
/* Get required info from frame */
|
||||
br[tn].bid = frame->ul_bid;
|
||||
chan = frame->ul_chan;
|
||||
handler = l1sched_lchan_desc[chan].tx_fn;
|
||||
|
||||
/* Omit lchans without handler */
|
||||
if (!handler)
|
||||
continue;
|
||||
|
||||
/* Make sure that lchan was allocated and activated */
|
||||
lchan = l1sched_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 = l1sched_prim_dequeue(&ts->tx_prims, fn, lchan);
|
||||
|
||||
/* TODO: report TX buffers health to the higher layers */
|
||||
|
||||
/* If CBTX (Continuous Burst Transmission) is assumed */
|
||||
if (l1sched_lchan_desc[chan].flags & L1SCHED_CH_FLAG_CBTX) {
|
||||
/**
|
||||
* Probably, a TX buffer is empty. Nevertheless,
|
||||
* we shall continuously transmit anything on
|
||||
* CBTX channels.
|
||||
*/
|
||||
if (lchan->prim == NULL)
|
||||
l1sched_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 (L1SCHED_PRIM_IS_RACH(lchan->prim) && lchan->prim->chan != L1SCHED_RACH)
|
||||
handler = l1sched_lchan_desc[L1SCHED_RACH].tx_fn;
|
||||
|
||||
/* Poke lchan handler */
|
||||
handler(lchan, &br[tn]);
|
||||
|
||||
/* Perform A5/X burst encryption if required */
|
||||
if (lchan->a5.algo)
|
||||
l1sched_a5_burst_enc(lchan, &br[tn]);
|
||||
}
|
||||
|
||||
/* Send all bursts for this TDMA frame */
|
||||
for (tn = 0; tn < ARRAY_SIZE(br); tn++)
|
||||
l1sched_handle_burst_req(sched, &br[tn]);
|
||||
}
|
||||
|
||||
void l1sched_logging_init(int log_cat_common, int log_cat_data)
|
||||
{
|
||||
l1sched_log_cat_common = log_cat_common;
|
||||
l1sched_log_cat_data = log_cat_data;
|
||||
}
|
||||
|
||||
struct l1sched_state *l1sched_alloc(void *ctx, const struct l1sched_cfg *cfg, void *priv)
|
||||
{
|
||||
struct l1sched_state *sched;
|
||||
|
||||
sched = talloc(ctx, struct l1sched_state);
|
||||
if (!sched)
|
||||
return NULL;
|
||||
|
||||
*sched = (struct l1sched_state) {
|
||||
/* .clock_timer is set up in l1sched_clck_correct() */
|
||||
.clock_cb = &sched_frame_clck_cb,
|
||||
.fn_counter_advance = cfg->fn_advance,
|
||||
.priv = priv,
|
||||
};
|
||||
|
||||
memcpy(&sched->sacch_cache[0], &meas_rep_dummy[0], sizeof(meas_rep_dummy));
|
||||
|
||||
if (cfg->log_prefix == NULL)
|
||||
sched->log_prefix = talloc_asprintf(sched, "l1sched[0x%p]: ", sched);
|
||||
else
|
||||
sched->log_prefix = talloc_strdup(sched, cfg->log_prefix);
|
||||
|
||||
return sched;
|
||||
}
|
||||
|
||||
void l1sched_free(struct l1sched_state *sched)
|
||||
{
|
||||
unsigned int tn;
|
||||
|
||||
if (sched == NULL)
|
||||
return;
|
||||
|
||||
LOGP_SCHEDC(sched, LOGL_NOTICE, "Shutdown scheduler\n");
|
||||
|
||||
/* Free all potentially allocated timeslots */
|
||||
for (tn = 0; tn < ARRAY_SIZE(sched->ts); tn++)
|
||||
l1sched_del_ts(sched, tn);
|
||||
|
||||
l1sched_clck_reset(sched);
|
||||
talloc_free(sched);
|
||||
}
|
||||
|
||||
void l1sched_reset(struct l1sched_state *sched, bool reset_clock)
|
||||
{
|
||||
unsigned int tn;
|
||||
|
||||
if (sched == NULL)
|
||||
return;
|
||||
|
||||
LOGP_SCHEDC(sched, LOGL_NOTICE, "Reset scheduler %s\n",
|
||||
reset_clock ? "and clock counter" : "");
|
||||
|
||||
/* Free all potentially allocated timeslots */
|
||||
for (tn = 0; tn < ARRAY_SIZE(sched->ts); tn++)
|
||||
l1sched_del_ts(sched, tn);
|
||||
|
||||
/* Stop and reset clock counter if required */
|
||||
if (reset_clock)
|
||||
l1sched_clck_reset(sched);
|
||||
|
||||
memcpy(&sched->sacch_cache[0], &meas_rep_dummy[0], sizeof(meas_rep_dummy));
|
||||
}
|
||||
|
||||
struct l1sched_ts *l1sched_add_ts(struct l1sched_state *sched, int tn)
|
||||
{
|
||||
/* Make sure that ts isn't allocated yet */
|
||||
if (sched->ts[tn] != NULL) {
|
||||
LOGP_SCHEDC(sched, LOGL_ERROR, "Timeslot #%u already allocated\n", tn);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOGP_SCHEDC(sched, LOGL_NOTICE, "Add a new TDMA timeslot #%u\n", tn);
|
||||
|
||||
sched->ts[tn] = talloc_zero(sched, struct l1sched_ts);
|
||||
sched->ts[tn]->sched = sched;
|
||||
sched->ts[tn]->index = tn;
|
||||
|
||||
return sched->ts[tn];
|
||||
}
|
||||
|
||||
void l1sched_del_ts(struct l1sched_state *sched, int tn)
|
||||
{
|
||||
struct l1sched_lchan_state *lchan, *lchan_next;
|
||||
struct l1sched_ts *ts;
|
||||
|
||||
/* Find ts in list */
|
||||
ts = sched->ts[tn];
|
||||
if (ts == NULL)
|
||||
return;
|
||||
|
||||
LOGP_SCHEDC(sched, LOGL_NOTICE, "Delete TDMA timeslot #%u\n", tn);
|
||||
|
||||
/* Deactivate all logical channels */
|
||||
l1sched_deactivate_all_lchans(ts);
|
||||
|
||||
/* Free channel states */
|
||||
llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
|
||||
llist_del(&lchan->list);
|
||||
talloc_free(lchan);
|
||||
}
|
||||
|
||||
/* Flush queue primitives for TX */
|
||||
l1sched_prim_flush_queue(&ts->tx_prims);
|
||||
|
||||
/* Remove ts from list and free memory */
|
||||
sched->ts[tn] = NULL;
|
||||
talloc_free(ts);
|
||||
|
||||
/* Notify transceiver about that */
|
||||
l1sched_cfg_pchan_comb_req(sched, tn, GSM_PCHAN_NONE);
|
||||
}
|
||||
|
||||
#define LAYOUT_HAS_LCHAN(layout, lchan) \
|
||||
(layout->lchan_mask & ((uint64_t) 0x01 << lchan))
|
||||
|
||||
int l1sched_configure_ts(struct l1sched_state *sched, int tn,
|
||||
enum gsm_phys_chan_config config)
|
||||
{
|
||||
struct l1sched_lchan_state *lchan;
|
||||
enum l1sched_lchan_type type;
|
||||
struct l1sched_ts *ts;
|
||||
|
||||
/* Try to find specified ts */
|
||||
ts = sched->ts[tn];
|
||||
if (ts != NULL) {
|
||||
/* Reconfiguration of existing one */
|
||||
l1sched_reset_ts(sched, tn);
|
||||
} else {
|
||||
/* Allocate a new one if doesn't exist */
|
||||
ts = l1sched_add_ts(sched, tn);
|
||||
if (ts == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Choose proper multiframe layout */
|
||||
ts->mf_layout = l1sched_mframe_layout(config, tn);
|
||||
if (!ts->mf_layout)
|
||||
return -EINVAL;
|
||||
if (ts->mf_layout->chan_config != config)
|
||||
return -EINVAL;
|
||||
|
||||
LOGP_SCHEDC(sched, LOGL_NOTICE,
|
||||
"(Re)configure TDMA timeslot #%u as %s\n",
|
||||
tn, ts->mf_layout->name);
|
||||
|
||||
/* Init 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 < _L1SCHED_CHAN_MAX; type++) {
|
||||
if (!LAYOUT_HAS_LCHAN(ts->mf_layout, type))
|
||||
continue;
|
||||
|
||||
/* Allocate a channel state */
|
||||
lchan = talloc_zero(ts, struct l1sched_lchan_state);
|
||||
if (!lchan)
|
||||
return -ENOMEM;
|
||||
|
||||
/* set backpointer */
|
||||
lchan->ts = ts;
|
||||
|
||||
/* Set channel type */
|
||||
lchan->type = type;
|
||||
|
||||
/* Add to the list of channel states */
|
||||
llist_add_tail(&lchan->list, &ts->lchans);
|
||||
|
||||
/* Enable channel automatically if required */
|
||||
if (l1sched_lchan_desc[type].flags & L1SCHED_CH_FLAG_AUTO)
|
||||
l1sched_activate_lchan(ts, type);
|
||||
}
|
||||
|
||||
/* Notify transceiver about TS activation */
|
||||
l1sched_cfg_pchan_comb_req(sched, tn, config);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l1sched_reset_ts(struct l1sched_state *sched, int tn)
|
||||
{
|
||||
struct l1sched_lchan_state *lchan, *lchan_next;
|
||||
struct l1sched_ts *ts;
|
||||
|
||||
/* Try to find specified ts */
|
||||
ts = sched->ts[tn];
|
||||
if (ts == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
/* Undefine multiframe layout */
|
||||
ts->mf_layout = NULL;
|
||||
|
||||
/* Flush queue primitives for TX */
|
||||
l1sched_prim_flush_queue(&ts->tx_prims);
|
||||
|
||||
/* Deactivate all logical channels */
|
||||
l1sched_deactivate_all_lchans(ts);
|
||||
|
||||
/* Free channel states */
|
||||
llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
|
||||
llist_del(&lchan->list);
|
||||
talloc_free(lchan);
|
||||
}
|
||||
|
||||
/* Notify transceiver about that */
|
||||
l1sched_cfg_pchan_comb_req(sched, tn, GSM_PCHAN_NONE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l1sched_start_ciphering(struct l1sched_ts *ts, uint8_t algo,
|
||||
const uint8_t *key, uint8_t key_len)
|
||||
{
|
||||
struct l1sched_lchan_state *lchan;
|
||||
|
||||
/* Prevent NULL-pointer deference */
|
||||
if (!ts)
|
||||
return -EINVAL;
|
||||
|
||||
/* Make sure we can store this key */
|
||||
if (key_len > MAX_A5_KEY_LEN)
|
||||
return -ERANGE;
|
||||
|
||||
/* Iterate over all allocated logical channels */
|
||||
llist_for_each_entry(lchan, &ts->lchans, list) {
|
||||
/* Omit inactive channels */
|
||||
if (!lchan->active)
|
||||
continue;
|
||||
|
||||
/* Set key length and algorithm */
|
||||
lchan->a5.key_len = key_len;
|
||||
lchan->a5.algo = algo;
|
||||
|
||||
/* Copy requested key */
|
||||
if (key_len)
|
||||
memcpy(lchan->a5.key, key, key_len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct l1sched_lchan_state *l1sched_find_lchan(struct l1sched_ts *ts,
|
||||
enum l1sched_lchan_type chan)
|
||||
{
|
||||
struct l1sched_lchan_state *lchan;
|
||||
|
||||
llist_for_each_entry(lchan, &ts->lchans, list)
|
||||
if (lchan->type == chan)
|
||||
return lchan;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int l1sched_set_lchans(struct l1sched_ts *ts, uint8_t chan_nr,
|
||||
int active, uint8_t tch_mode, uint8_t tsc)
|
||||
{
|
||||
const struct l1sched_lchan_desc *lchan_desc;
|
||||
struct l1sched_lchan_state *lchan;
|
||||
int rc = 0;
|
||||
|
||||
/* Prevent NULL-pointer deference */
|
||||
OSMO_ASSERT(ts != NULL);
|
||||
|
||||
/* Iterate over all allocated lchans */
|
||||
llist_for_each_entry(lchan, &ts->lchans, list) {
|
||||
lchan_desc = &l1sched_lchan_desc[lchan->type];
|
||||
|
||||
if (lchan_desc->chan_nr == (chan_nr & 0xf8)) {
|
||||
if (active) {
|
||||
rc |= l1sched_activate_lchan(ts, lchan->type);
|
||||
lchan->tch_mode = tch_mode;
|
||||
lchan->tsc = tsc;
|
||||
} else
|
||||
rc |= l1sched_deactivate_lchan(ts, lchan->type);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int l1sched_activate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan)
|
||||
{
|
||||
const struct l1sched_lchan_desc *lchan_desc = &l1sched_lchan_desc[chan];
|
||||
struct l1sched_lchan_state *lchan;
|
||||
|
||||
/* Try to find requested logical channel */
|
||||
lchan = l1sched_find_lchan(ts, chan);
|
||||
if (lchan == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
if (lchan->active) {
|
||||
LOGP_LCHANC(lchan, LOGL_ERROR, "is already activated\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOGP_LCHANC(lchan, LOGL_NOTICE, "activating\n");
|
||||
|
||||
/* Conditionally allocate memory for bursts */
|
||||
if (lchan_desc->rx_fn && lchan_desc->burst_buf_size > 0) {
|
||||
lchan->rx_bursts = talloc_zero_size(lchan,
|
||||
lchan_desc->burst_buf_size);
|
||||
if (lchan->rx_bursts == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (lchan_desc->tx_fn && lchan_desc->burst_buf_size > 0) {
|
||||
lchan->tx_bursts = talloc_zero_size(lchan,
|
||||
lchan_desc->burst_buf_size);
|
||||
if (lchan->tx_bursts == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Finally, update channel status */
|
||||
lchan->active = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void l1sched_reset_lchan(struct l1sched_lchan_state *lchan)
|
||||
{
|
||||
/* Prevent NULL-pointer deference */
|
||||
OSMO_ASSERT(lchan != NULL);
|
||||
|
||||
/* Print some TDMA statistics for Downlink */
|
||||
if (l1sched_lchan_desc[lchan->type].rx_fn && lchan->active) {
|
||||
LOGP_LCHANC(lchan, LOGL_DEBUG, "TDMA statistics: "
|
||||
"%lu DL frames have been processed, "
|
||||
"%lu lost (compensated), last fn=%u\n",
|
||||
lchan->tdma.num_proc,
|
||||
lchan->tdma.num_lost,
|
||||
lchan->tdma.last_proc);
|
||||
}
|
||||
|
||||
/* Reset internal state variables */
|
||||
lchan->rx_burst_mask = 0x00;
|
||||
lchan->tx_burst_mask = 0x00;
|
||||
|
||||
/* Free burst memory */
|
||||
talloc_free(lchan->rx_bursts);
|
||||
talloc_free(lchan->tx_bursts);
|
||||
|
||||
lchan->rx_bursts = NULL;
|
||||
lchan->tx_bursts = NULL;
|
||||
|
||||
/* Forget the current prim */
|
||||
l1sched_prim_drop(lchan);
|
||||
|
||||
/* Channel specific stuff */
|
||||
if (L1SCHED_CHAN_IS_TCH(lchan->type)) {
|
||||
lchan->dl_ongoing_facch = 0;
|
||||
lchan->ul_facch_blocks = 0;
|
||||
|
||||
lchan->tch_mode = GSM48_CMODE_SIGN;
|
||||
|
||||
/* Reset AMR state */
|
||||
memset(&lchan->amr, 0x00, sizeof(lchan->amr));
|
||||
} else if (L1SCHED_CHAN_IS_SACCH(lchan->type)) {
|
||||
/* Reset SACCH state */
|
||||
memset(&lchan->sacch, 0x00, sizeof(lchan->sacch));
|
||||
}
|
||||
|
||||
/* Reset ciphering state */
|
||||
memset(&lchan->a5, 0x00, sizeof(lchan->a5));
|
||||
|
||||
/* Reset TDMA frame statistics */
|
||||
memset(&lchan->tdma, 0x00, sizeof(lchan->tdma));
|
||||
}
|
||||
|
||||
int l1sched_deactivate_lchan(struct l1sched_ts *ts, enum l1sched_lchan_type chan)
|
||||
{
|
||||
struct l1sched_lchan_state *lchan;
|
||||
|
||||
/* Try to find requested logical channel */
|
||||
lchan = l1sched_find_lchan(ts, chan);
|
||||
if (lchan == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
if (!lchan->active) {
|
||||
LOGP_LCHANC(lchan, LOGL_ERROR, "is already deactivated\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOGP_LCHANC(lchan, LOGL_DEBUG, "deactivating\n");
|
||||
|
||||
/* Reset internal state, free memory */
|
||||
l1sched_reset_lchan(lchan);
|
||||
|
||||
/* Update activation flag */
|
||||
lchan->active = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void l1sched_deactivate_all_lchans(struct l1sched_ts *ts)
|
||||
{
|
||||
struct l1sched_lchan_state *lchan;
|
||||
|
||||
LOGP_SCHEDC(ts->sched, LOGL_DEBUG,
|
||||
"Deactivating all logical channels on ts=%d\n",
|
||||
ts->index);
|
||||
|
||||
llist_for_each_entry(lchan, &ts->lchans, list) {
|
||||
/* Omit inactive channels */
|
||||
if (!lchan->active)
|
||||
continue;
|
||||
|
||||
/* Reset internal state, free memory */
|
||||
l1sched_reset_lchan(lchan);
|
||||
|
||||
/* Update activation flag */
|
||||
lchan->active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
enum gsm_phys_chan_config l1sched_chan_nr2pchan_config(uint8_t chan_nr)
|
||||
{
|
||||
uint8_t cbits = chan_nr >> 3;
|
||||
|
||||
if (cbits == ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs)
|
||||
return GSM_PCHAN_TCH_F;
|
||||
else if ((cbits & 0x1e) == ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0))
|
||||
return GSM_PCHAN_TCH_H;
|
||||
else if ((cbits & 0x1c) == ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0))
|
||||
return GSM_PCHAN_CCCH_SDCCH4;
|
||||
else if ((cbits & 0x18) == ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0))
|
||||
return GSM_PCHAN_SDCCH8_SACCH8C;
|
||||
else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH4)
|
||||
return GSM_PCHAN_CCCH_SDCCH4_CBCH;
|
||||
else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH8)
|
||||
return GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
|
||||
else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH)
|
||||
return GSM_PCHAN_PDCH;
|
||||
|
||||
return GSM_PCHAN_NONE;
|
||||
}
|
||||
|
||||
enum l1sched_lchan_type l1sched_chan_nr2lchan_type(uint8_t chan_nr,
|
||||
uint8_t link_id)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Iterate over all known lchan types */
|
||||
for (i = 0; i < _L1SCHED_CHAN_MAX; i++)
|
||||
if (l1sched_lchan_desc[i].chan_nr == (chan_nr & 0xf8))
|
||||
if (l1sched_lchan_desc[i].link_id == link_id)
|
||||
return i;
|
||||
|
||||
return L1SCHED_IDLE;
|
||||
}
|
||||
|
||||
static void l1sched_a5_burst_dec(struct l1sched_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 l1sched_a5_burst_enc(struct l1sched_lchan_state *lchan,
|
||||
struct l1sched_burst_req *br)
|
||||
{
|
||||
ubit_t ks[114];
|
||||
int i;
|
||||
|
||||
/* Generate keystream for an UL burst */
|
||||
osmo_a5(lchan->a5.algo, lchan->a5.key, br->fn, NULL, ks);
|
||||
|
||||
/* Apply keystream over plaintext */
|
||||
for (i = 0; i < 57; i++) {
|
||||
br->burst[i + 3] ^= ks[i];
|
||||
br->burst[i + 88] ^= ks[i + 57];
|
||||
}
|
||||
}
|
||||
|
||||
static int subst_frame_loss(struct l1sched_lchan_state *lchan,
|
||||
l1sched_lchan_rx_func *handler,
|
||||
uint32_t fn)
|
||||
{
|
||||
const struct l1sched_tdma_multiframe *mf;
|
||||
const struct l1sched_tdma_frame *fp;
|
||||
int elapsed, i;
|
||||
|
||||
/* Wait until at least one TDMA frame is processed */
|
||||
if (lchan->tdma.num_proc == 0)
|
||||
return -EAGAIN;
|
||||
|
||||
/* Short alias for the current multiframe */
|
||||
mf = lchan->ts->mf_layout;
|
||||
|
||||
/* Calculate how many frames elapsed since the last received one.
|
||||
* The algorithm is based on GSM::FNDelta() from osmo-trx. */
|
||||
elapsed = fn - lchan->tdma.last_proc;
|
||||
if (elapsed >= GSM_TDMA_HYPERFRAME / 2)
|
||||
elapsed -= GSM_TDMA_HYPERFRAME;
|
||||
else if (elapsed < -GSM_TDMA_HYPERFRAME / 2)
|
||||
elapsed += GSM_TDMA_HYPERFRAME;
|
||||
|
||||
/* Check TDMA frame order (wrong order is possible with fake_trx.py, see OS#4658) */
|
||||
if (elapsed < 0) {
|
||||
/* This burst has already been substituted by a dummy burst (all bits set to zero),
|
||||
* so better drop it. Otherwise we risk to get undefined behavior in handler(). */
|
||||
LOGP_LCHAND(lchan, LOGL_ERROR, "Rx burst with fn=%u older than the last "
|
||||
"processed fn=%u (see OS#4658) => dropping\n",
|
||||
fn, lchan->tdma.last_proc);
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
/* Check how many frames we (potentially) need to compensate */
|
||||
if (elapsed > mf->period) {
|
||||
LOGP_LCHANC(lchan, LOGL_NOTICE,
|
||||
"Too many (>%u) contiguous TDMA frames elapsed (%d) "
|
||||
"since the last processed fn=%u (current %u)\n",
|
||||
mf->period, elapsed, lchan->tdma.last_proc, fn);
|
||||
return -EIO;
|
||||
} else if (elapsed == 0) {
|
||||
LOGP_LCHANC(lchan, LOGL_ERROR,
|
||||
"No TDMA frames elapsed since the last processed "
|
||||
"fn=%u, must be a bug?\n", lchan->tdma.last_proc);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static const sbit_t bits[148] = { 0 };
|
||||
struct l1sched_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_LCHANC(lchan, LOGL_NOTICE,
|
||||
"Substituting lost TDMA frame fn=%u\n",
|
||||
fake_meas.fn);
|
||||
|
||||
handler(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 l1sched_handle_rx_burst(struct l1sched_state *sched, uint8_t tn,
|
||||
uint32_t fn, sbit_t *bits, uint16_t nbits,
|
||||
const struct l1sched_meas_set *meas)
|
||||
{
|
||||
struct l1sched_lchan_state *lchan;
|
||||
const struct l1sched_tdma_frame *frame;
|
||||
struct l1sched_ts *ts;
|
||||
|
||||
l1sched_lchan_rx_func *handler;
|
||||
enum l1sched_lchan_type chan;
|
||||
uint8_t offset, bid;
|
||||
int rc;
|
||||
|
||||
/* Check whether required timeslot is allocated and configured */
|
||||
ts = sched->ts[tn];
|
||||
if (ts == NULL || ts->mf_layout == NULL) {
|
||||
LOGP_SCHEDD(sched, LOGL_DEBUG,
|
||||
"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 = l1sched_lchan_desc[chan].rx_fn;
|
||||
|
||||
/* Omit bursts which have no handler, like IDLE bursts.
|
||||
* TODO: handle noise indications during IDLE frames. */
|
||||
if (!handler)
|
||||
return -ENODEV;
|
||||
|
||||
/* Find required channel state */
|
||||
lchan = l1sched_find_lchan(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)
|
||||
l1sched_a5_burst_dec(lchan, fn, bits);
|
||||
|
||||
/* Put burst to handler */
|
||||
handler(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_LCHAND(lchan, LOGL_NOTICE,
|
||||
"Too many TDMA frames have been processed. "
|
||||
"Are you running trxcon for more than 6 years?!?\n");
|
||||
lchan->tdma.num_proc = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MEAS_HIST_FIRST(hist) \
|
||||
(&hist->buf[0])
|
||||
#define MEAS_HIST_LAST(hist) \
|
||||
(MEAS_HIST_FIRST(hist) + ARRAY_SIZE(hist->buf) - 1)
|
||||
|
||||
/* Add a new set of measurements to the history */
|
||||
void l1sched_lchan_meas_push(struct l1sched_lchan_state *lchan, const struct l1sched_meas_set *meas)
|
||||
{
|
||||
struct l1sched_lchan_meas_hist *hist = &lchan->meas_hist;
|
||||
|
||||
/* Find a new position where to store the measurements */
|
||||
if (hist->head == MEAS_HIST_LAST(hist) || hist->head == NULL)
|
||||
hist->head = MEAS_HIST_FIRST(hist);
|
||||
else
|
||||
hist->head++;
|
||||
|
||||
*hist->head = *meas;
|
||||
}
|
||||
|
||||
/* Calculate the AVG of n measurements from the history */
|
||||
void l1sched_lchan_meas_avg(struct l1sched_lchan_state *lchan, unsigned int n)
|
||||
{
|
||||
struct l1sched_lchan_meas_hist *hist = &lchan->meas_hist;
|
||||
struct l1sched_meas_set *meas = hist->head;
|
||||
int toa256_sum = 0;
|
||||
int rssi_sum = 0;
|
||||
int i;
|
||||
|
||||
OSMO_ASSERT(n > 0 && n <= ARRAY_SIZE(hist->buf));
|
||||
OSMO_ASSERT(meas != NULL);
|
||||
|
||||
/* Traverse backwards up to n entries, calculate the sum */
|
||||
for (i = 0; i < n; i++) {
|
||||
toa256_sum += meas->toa256;
|
||||
rssi_sum += meas->rssi;
|
||||
|
||||
/* Do not go below the first burst */
|
||||
if (i + 1 == n)
|
||||
break;
|
||||
|
||||
if (meas == MEAS_HIST_FIRST(hist))
|
||||
meas = MEAS_HIST_LAST(hist);
|
||||
else
|
||||
meas--;
|
||||
}
|
||||
|
||||
/* Calculate the AVG */
|
||||
lchan->meas_avg.toa256 = toa256_sum / n;
|
||||
lchan->meas_avg.rssi = rssi_sum / n;
|
||||
|
||||
/* As a bonus, store TDMA frame number of the first burst */
|
||||
lchan->meas_avg.fn = meas->fn;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user