diff options
Diffstat (limited to 'Transceiver52M')
97 files changed, 11201 insertions, 947 deletions
diff --git a/Transceiver52M/Channelizer.cpp b/Transceiver52M/Channelizer.cpp index 341cd0a..0522f20 100644 --- a/Transceiver52M/Channelizer.cpp +++ b/Transceiver52M/Channelizer.cpp @@ -98,7 +98,7 @@ bool Channelizer::rotate(const float *in, size_t len) return true; } -/* Setup channelizer paramaters */ +/* Setup channelizer parameters */ Channelizer::Channelizer(size_t m, size_t blockLen, size_t hLen) : ChannelizerBase(m, blockLen, hLen) { diff --git a/Transceiver52M/ChannelizerBase.cpp b/Transceiver52M/ChannelizerBase.cpp index f3f07c1..9910091 100644 --- a/Transceiver52M/ChannelizerBase.cpp +++ b/Transceiver52M/ChannelizerBase.cpp @@ -225,7 +225,7 @@ bool ChannelizerBase::checkLen(size_t innerLen, size_t outerLen) } /* - * Setup channelizer paramaters + * Setup channelizer parameters */ ChannelizerBase::ChannelizerBase(size_t m, size_t blockLen, size_t hLen) : subFilters(NULL), hInputs(NULL), hOutputs(NULL), hist(NULL), @@ -244,6 +244,7 @@ ChannelizerBase::~ChannelizerBase() free(subFilters[i]); delete[] hist[i]; } + free(subFilters); fft_free(fftInput); fft_free(fftOutput); diff --git a/Transceiver52M/ChannelizerBase.h b/Transceiver52M/ChannelizerBase.h index 7da506b..025ff25 100644 --- a/Transceiver52M/ChannelizerBase.h +++ b/Transceiver52M/ChannelizerBase.h @@ -32,7 +32,7 @@ protected: /* Buffer length validity checking */ bool checkLen(size_t innerLen, size_t outerLen); public: - /* Initilize channelizer/synthesis filter internals */ + /* Initialize channelizer/synthesis filter internals */ bool init(); }; diff --git a/Transceiver52M/Complex.h b/Transceiver52M/Complex.h index d02944b..597a26f 100644 --- a/Transceiver52M/Complex.h +++ b/Transceiver52M/Complex.h @@ -5,7 +5,7 @@ unlike the built-in complex<> templates, these inline most operations for speed /* * Copyright 2008 Free Software Foundation, Inc. * -* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -29,7 +29,7 @@ unlike the built-in complex<> templates, these inline most operations for speed template<class Real> class Complex { public: - + typedef Real value_type; Real r, i; /**@name constructors */ diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am index ade4e30..0b63b16 100644 --- a/Transceiver52M/Makefile.am +++ b/Transceiver52M/Makefile.am @@ -40,7 +40,9 @@ COMMON_SOURCES = \ ChannelizerBase.cpp \ Channelizer.cpp \ Synthesis.cpp \ - proto_trxd.c + proto_trxd.c \ + grgsm_vitac/grgsm_vitac.cpp \ + grgsm_vitac/viterbi_detector.cc libtransceiver_common_la_SOURCES = \ $(COMMON_SOURCES) \ @@ -73,6 +75,47 @@ COMMON_LDADD = \ $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOVTY_LIBS) +if ENABLE_MS_TRX +AM_CPPFLAGS += -I$(top_srcdir)/osmocom-bb/src/host/trxcon/include/ +AM_CPPFLAGS += -I${srcdir} + +TRXCON_LDADD = \ + $(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libtrxcon.a \ + $(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libl1sched.a \ + $(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libl1gprs.a \ + $(LIBOSMOCODING_LIBS) + +MS_LOWER_SRC = \ + ms/sch.c \ + ms/ms.cpp \ + ms/threadsched.cpp \ + ms/ms_rx_lower.cpp \ + grgsm_vitac/grgsm_vitac.cpp \ + grgsm_vitac/viterbi_detector.cc + +MS_UPPER_SRC = \ + ms/ms_upper.cpp \ + ms/l1ctl_server.c \ + ms/logging.c \ + ms/l1ctl_server_cb.cpp \ + ms/ms_trxcon_if.cpp + +noinst_HEADERS += \ + ms/ms.h \ + ms/threadsched.h \ + ms/bladerf_specific.h \ + ms/uhd_specific.h \ + ms/ms_upper.h \ + ms/ms_trxcon_if.h \ + ms/itrq.h \ + ms/sch.h \ + ms/threadpool.h \ + grgsm_vitac/viterbi_detector.h \ + grgsm_vitac/constants.h \ + grgsm_vitac/grgsm_vitac.h + +endif + bin_PROGRAMS = if DEVICE_UHD @@ -83,6 +126,17 @@ osmo_trx_uhd_LDADD = \ $(COMMON_LDADD) \ $(UHD_LIBS) osmo_trx_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) + +#if ENABLE_MS_TRX +#bin_PROGRAMS += osmo-trx-ms-uhd +#osmo_trx_ms_uhd_SOURCES = $(MS_LOWER_SRC) $(MS_UPPER_SRC) +#osmo_trx_ms_uhd_LDADD = \ +# $(builddir)/device/uhd/libdevice.la \ +# $(COMMON_LDADD) \ +# $(UHD_LIBS) \ +# $(TRXCON_LDADD) +#osmo_trx_ms_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) -DBUILDUHD +#endif endif if DEVICE_USRP1 @@ -104,3 +158,33 @@ osmo_trx_lms_LDADD = \ $(LMS_LIBS) osmo_trx_lms_CPPFLAGS = $(AM_CPPFLAGS) $(LMS_CFLAGS) endif + +if DEVICE_BLADE +bin_PROGRAMS += osmo-trx-blade +osmo_trx_blade_SOURCES = osmo-trx.cpp +osmo_trx_blade_LDADD = \ + $(builddir)/device/bladerf/libdevice.la \ + $(COMMON_LDADD) \ + $(BLADE_LIBS) +osmo_trx_blade_CPPFLAGS = $(AM_CPPFLAGS) $(LMS_CFLAGS) + +if ENABLE_MS_TRX +bin_PROGRAMS += osmo-trx-ms-blade +osmo_trx_ms_blade_SOURCES = $(MS_LOWER_SRC) $(MS_UPPER_SRC) +osmo_trx_ms_blade_LDADD = \ + $(builddir)/device/bladerf/libdevice.la \ + $(COMMON_LDADD) \ + $(BLADE_LIBS) \ + $(TRXCON_LDADD) +osmo_trx_ms_blade_CPPFLAGS = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DBUILDBLADE +endif +endif + +if DEVICE_IPC +bin_PROGRAMS += osmo-trx-ipc +osmo_trx_ipc_SOURCES = osmo-trx.cpp +osmo_trx_ipc_LDADD = \ + $(builddir)/device/ipc/libdevice.la \ + $(COMMON_LDADD) +osmo_trx_ipc_CPPFLAGS = $(AM_CPPFLAGS) +endif diff --git a/Transceiver52M/Resampler.cpp b/Transceiver52M/Resampler.cpp index 7ba0219..841c3a9 100644 --- a/Transceiver52M/Resampler.cpp +++ b/Transceiver52M/Resampler.cpp @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <stdlib.h> @@ -36,7 +32,7 @@ extern "C" { #define M_PI 3.14159265358979323846264338327f #endif -#define MAX_OUTPUT_LEN 4096 +#define MAX_OUTPUT_LEN 4096*4 using namespace std; @@ -99,6 +95,7 @@ void Resampler::initFilters(float bw) reverse(&part[0], &part[filt_len]); } +#ifndef __OPTIMIZE__ static bool check_vec_len(int in_len, int out_len, int p, int q) { if (in_len % q) { @@ -129,14 +126,15 @@ static bool check_vec_len(int in_len, int out_len, int p, int q) return true; } +#endif int Resampler::rotate(const float *in, size_t in_len, float *out, size_t out_len) { int n, path; - +#ifndef __OPTIMIZE__ if (!check_vec_len(in_len, out_len, p, q)) return -1; - +#endif /* Generate output from precomputed input/output paths */ for (size_t i = 0; i < out_len; i++) { n = in_index[i]; diff --git a/Transceiver52M/Resampler.h b/Transceiver52M/Resampler.h index 139b857..e1962c7 100644 --- a/Transceiver52M/Resampler.h +++ b/Transceiver52M/Resampler.h @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _RESAMPLER_H_ @@ -35,12 +31,12 @@ public: Resampler(size_t p, size_t q, size_t filt_len = 16); ~Resampler(); - /* Initilize resampler filterbank. + /* Initialize resampler filterbank. * @param bw bandwidth factor on filter generation (pre-window) * @return false on error, zero otherwise * * Automatic setting is to compute the filter to prevent aliasing with - * a Blackman-Harris window. Adjustment is made through a bandwith + * a Blackman-Harris window. Adjustment is made through a bandwidth * factor to shift the cutoff and/or the constituent filter lengths. * Calculation of specific rolloff factors or 3-dB cutoff points is * left as an excersize for the reader. diff --git a/Transceiver52M/Transceiver.cpp b/Transceiver52M/Transceiver.cpp index 3901997..f5ddd9b 100644 --- a/Transceiver52M/Transceiver.cpp +++ b/Transceiver52M/Transceiver.cpp @@ -29,6 +29,7 @@ #include <fstream> #include "Transceiver.h" #include <Logger.h> +#include <grgsm_vitac/grgsm_vitac.h> extern "C" { #include "osmo_signal.h" @@ -37,6 +38,7 @@ extern "C" { #include <osmocom/core/utils.h> #include <osmocom/core/socket.h> #include <osmocom/core/bits.h> +#include <osmocom/vty/cpu_sched_vty.h> } #ifdef HAVE_CONFIG_H @@ -45,13 +47,24 @@ extern "C" { using namespace GSM; +Transceiver *transceiver; + #define USB_LATENCY_INTRVL 10,0 /* Number of running values use in noise average */ #define NOISE_CNT 20 + +static void dispatch_trx_rate_ctr_change(TransceiverState *state, unsigned int chan) { + thread_enable_cancel(false); + state->ctrs.chan = chan; + osmo_signal_dispatch(SS_DEVICE, S_TRX_COUNTER_CHANGE, &state->ctrs); + thread_enable_cancel(true); +} + TransceiverState::TransceiverState() - : mRetrans(false), mNoiseLev(0.0), mNoises(NOISE_CNT), mPower(0.0) + : mFiller(FILLER_ZERO), mRetrans(false), mNoiseLev(0.0), mNoises(NOISE_CNT), + mPower(0.0), mMuted(false), first_dl_fn_rcv() { for (int i = 0; i < 8; i++) { chanType[i] = Transceiver::NONE; @@ -63,6 +76,7 @@ TransceiverState::TransceiverState() for (int n = 0; n < 102; n++) fillerTable[n][i] = NULL; } + memset(&ctrs, 0, sizeof(struct trx_counters)); } TransceiverState::~TransceiverState() @@ -84,6 +98,8 @@ bool TransceiverState::init(FillerType filler, size_t sps, float scale, size_t r if ((sps != 1) && (sps != 4)) return false; + mFiller = filler; + for (size_t n = 0; n < 8; n++) { for (size_t i = 0; i < 102; i++) { switch (filler) { @@ -117,26 +133,22 @@ bool TransceiverState::init(FillerType filler, size_t sps, float scale, size_t r return false; } -Transceiver::Transceiver(int wBasePort, - const char *TRXAddress, - const char *GSMcoreAddress, - size_t tx_sps, size_t rx_sps, size_t chans, +Transceiver::Transceiver(const struct trx_cfg *cfg, GSM::Time wTransmitLatency, - RadioInterface *wRadioInterface, - double wRssiOffset, int wStackSize) - : mBasePort(wBasePort), mLocalAddr(TRXAddress), mRemoteAddr(GSMcoreAddress), - mClockSocket(-1), mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface), - rssiOffset(wRssiOffset), stackSize(wStackSize), - mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mExtRACH(false), mEdge(false), - mOn(false), mForceClockInterface(false), - mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0), mMaxExpectedDelayNB(0), - mWriteBurstToDiskMask(0) + RadioInterface *wRadioInterface) + : mChans(cfg->num_chans), cfg(cfg), + mCtrlSockets(mChans), mClockSocket(-1), + mTxPriorityQueues(mChans), mReceiveFIFO(mChans), + mRxServiceLoopThreads(mChans), mRxLowerLoopThread(nullptr), mTxLowerLoopThread(nullptr), + mTxPriorityQueueServiceLoopThreads(mChans), mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface), + mOn(false),mForceClockInterface(false), mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0), + mMaxExpectedDelayNB(0), mWriteBurstToDiskMask(0), mVersionTRXD(mChans), mStates(mChans) { txFullScale = mRadioInterface->fullScaleInputValue(); rxFullScale = mRadioInterface->fullScaleOutputValue(); - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 8; j++) + for (size_t i = 0; i < ARRAY_SIZE(mHandover); i++) { + for (size_t j = 0; j < ARRAY_SIZE(mHandover[i]); j++) mHandover[i][j] = false; } } @@ -151,20 +163,30 @@ Transceiver::~Transceiver() close(mClockSocket); for (size_t i = 0; i < mChans; i++) { - if (mControlServiceLoopThreads[i]) { - mControlServiceLoopThreads[i]->cancel(); - mControlServiceLoopThreads[i]->join(); - delete mControlServiceLoopThreads[i]; - } - mTxPriorityQueues[i].clear(); - if (mCtrlSockets[i] >= 0) - close(mCtrlSockets[i]); if (mDataSockets[i] >= 0) close(mDataSockets[i]); } } +int Transceiver::ctrl_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + int rc = 0; + int chan = static_cast<int>(reinterpret_cast<uintptr_t>(bfd->data)); + + if (flags & OSMO_FD_READ) + rc = transceiver->ctrl_sock_handle_rx(chan); + if (rc < 0) + osmo_signal_dispatch(SS_MAIN, S_MAIN_STOP_REQUIRED, NULL); + + if (flags & OSMO_FD_WRITE) + rc = transceiver->ctrl_sock_write(chan); + if (rc < 0) + osmo_signal_dispatch(SS_MAIN, S_MAIN_STOP_REQUIRED, NULL); + + return rc; +} + /* * Initialize transceiver * @@ -174,66 +196,66 @@ Transceiver::~Transceiver() * are still expected to report clock indications through control channel * activity. */ -bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay, - bool edge, bool ext_rach) +bool Transceiver::init() { int d_srcport, d_dstport, c_srcport, c_dstport; - if (!mChans) { - LOG(ALERT) << "No channels assigned"; + LOG(FATAL) << "No channels assigned"; return false; } if (!sigProcLibSetup()) { - LOG(ALERT) << "Failed to initialize signal processing library"; + LOG(FATAL) << "Failed to initialize signal processing library"; return false; } - mExtRACH = ext_rach; - mEdge = edge; + initvita(); mDataSockets.resize(mChans, -1); - mCtrlSockets.resize(mChans, -1); - mControlServiceLoopThreads.resize(mChans); - mTxPriorityQueueServiceLoopThreads.resize(mChans); - mRxServiceLoopThreads.resize(mChans); - mTxPriorityQueues.resize(mChans); - mReceiveFIFO.resize(mChans); - mStates.resize(mChans); - mVersionTRXD.resize(mChans); /* Filler table retransmissions - support only on channel 0 */ - if (filler == FILLER_DUMMY) + if (cfg->filler == FILLER_DUMMY) mStates[0].mRetrans = true; /* Setup sockets */ mClockSocket = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, - mLocalAddr.c_str(), mBasePort, - mRemoteAddr.c_str(), mBasePort + 100, + cfg->bind_addr, cfg->base_port, + cfg->remote_addr, cfg->base_port + 100, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT); if (mClockSocket < 0) return false; for (size_t i = 0; i < mChans; i++) { - c_srcport = mBasePort + 2 * i + 1; - c_dstport = mBasePort + 2 * i + 101; - d_srcport = mBasePort + 2 * i + 2; - d_dstport = mBasePort + 2 * i + 102; - - mCtrlSockets[i] = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, - mLocalAddr.c_str(), c_srcport, - mRemoteAddr.c_str(), c_dstport, + int rv; + FillerType filler = cfg->filler; + c_srcport = cfg->base_port + 2 * i + 1; + c_dstport = cfg->base_port + 2 * i + 101; + d_srcport = cfg->base_port + 2 * i + 2; + d_dstport = cfg->base_port + 2 * i + 102; + + rv = osmo_sock_init2_ofd(&mCtrlSockets[i].conn_bfd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, + cfg->bind_addr, c_srcport, + cfg->remote_addr, c_dstport, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT); - if (mCtrlSockets[i] < 0) + if (rv < 0) return false; + mCtrlSockets[i].conn_bfd.cb = ctrl_sock_cb; + mCtrlSockets[i].conn_bfd.data = reinterpret_cast<void*>(i); + + mDataSockets[i] = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, - mLocalAddr.c_str(), d_srcport, - mRemoteAddr.c_str(), d_dstport, + cfg->bind_addr, d_srcport, + cfg->remote_addr, d_dstport, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT); - if (mCtrlSockets[i] < 0) + if (mDataSockets[i] < 0) return false; + + if (i && filler == FILLER_DUMMY) + filler = FILLER_ZERO; + + mStates[i].init(filler, cfg->tx_sps, txFullScale, cfg->rtsc, cfg->rach_delay); } /* Randomize the central clock */ @@ -243,21 +265,6 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay, mLastClockUpdateTime = startTime; mLatencyUpdateTime = startTime; - /* Start control threads */ - for (size_t i = 0; i < mChans; i++) { - TrxChanThParams *params = (TrxChanThParams *)malloc(sizeof(struct TrxChanThParams)); - params->trx = this; - params->num = i; - mControlServiceLoopThreads[i] = new Thread(stackSize); - mControlServiceLoopThreads[i]->start((void * (*)(void*)) - ControlServiceLoopAdapter, (void*) params); - - if (i && filler == FILLER_DUMMY) - filler = FILLER_ZERO; - - mStates[i].init(filler, mSPSTx, txFullScale, rtsc, rach_delay); - } - return true; } @@ -285,13 +292,13 @@ bool Transceiver::start() mLatencyUpdateTime = time; if (!mRadioInterface->start()) { - LOG(ALERT) << "Device failed to start"; + LOG(FATAL) << "Device failed to start"; return false; } /* Device is running - launch I/O threads */ - mRxLowerLoopThread = new Thread(stackSize); - mTxLowerLoopThread = new Thread(stackSize); + mRxLowerLoopThread = new Thread(cfg->stack_size); + mTxLowerLoopThread = new Thread(cfg->stack_size); mTxLowerLoopThread->start((void * (*)(void*)) TxLowerLoopAdapter,(void*) this); mRxLowerLoopThread->start((void * (*)(void*)) @@ -302,14 +309,14 @@ bool Transceiver::start() TrxChanThParams *params = (TrxChanThParams *)malloc(sizeof(struct TrxChanThParams)); params->trx = this; params->num = i; - mRxServiceLoopThreads[i] = new Thread(stackSize); + mRxServiceLoopThreads[i] = new Thread(cfg->stack_size); mRxServiceLoopThreads[i]->start((void * (*)(void*)) RxUpperLoopAdapter, (void*) params); params = (TrxChanThParams *)malloc(sizeof(struct TrxChanThParams)); params->trx = this; params->num = i; - mTxPriorityQueueServiceLoopThreads[i] = new Thread(stackSize); + mTxPriorityQueueServiceLoopThreads[i] = new Thread(cfg->stack_size); mTxPriorityQueueServiceLoopThreads[i]->start((void * (*)(void*)) TxUpperLoopAdapter, (void*) params); } @@ -371,22 +378,22 @@ void Transceiver::addRadioVector(size_t chan, BitVector &bits, radioVector *radio_burst; if (chan >= mTxPriorityQueues.size()) { - LOG(ALERT) << "Invalid channel " << chan; + LOGCHAN(chan, DTRXDDL, FATAL) << "Invalid channel"; return; } if (wTime.TN() > 7) { - LOG(ALERT) << "Received burst with invalid slot " << wTime.TN(); + LOGCHAN(chan, DTRXDDL, FATAL) << "Received burst with invalid slot " << wTime.TN(); return; } /* Use the number of bits as the EDGE burst indicator */ if (bits.size() == EDGE_BURST_NBITS) - burst = modulateEdgeBurst(bits, mSPSTx); + burst = modulateEdgeBurst(bits, cfg->tx_sps); else - burst = modulateBurst(bits, 8 + (wTime.TN() % 4 == 0), mSPSTx); + burst = modulateBurst(bits, 8 + (wTime.TN() % 4 == 0), cfg->tx_sps); - scaleVector(*burst, txFullScale * pow(10, -RSSI / 10)); + scaleVector(*burst, txFullScale * pow(10, (double) -RSSI / 20)); radio_burst = new radioVector(wTime, burst); @@ -414,24 +421,29 @@ void Transceiver::pushRadioVector(GSM::Time &nowTime) std::vector<signalVector *> bursts(mChans); std::vector<bool> zeros(mChans); std::vector<bool> filler(mChans, true); + bool ratectr_changed; + + TN = nowTime.TN(); for (size_t i = 0; i < mChans; i ++) { state = &mStates[i]; + ratectr_changed = false; + + zeros[i] = state->chanType[TN] == NONE || state->mMuted; + + Mutex *mtx = mTxPriorityQueues[i].getMutex(); + mtx->lock(); while ((burst = mTxPriorityQueues[i].getStaleBurst(nowTime))) { - LOGCHAN(i, DMAIN, NOTICE) << "dumping STALE burst in TRX->SDR interface (" + LOGCHAN(i, DTRXDDL, INFO) << "dumping STALE burst in TRX->SDR interface (" << burst->getTime() <<" vs " << nowTime << "), retrans=" << state->mRetrans; + state->ctrs.tx_stale_bursts++; + ratectr_changed = true; if (state->mRetrans) updateFillerTable(i, burst); delete burst; } - TN = nowTime.TN(); - modFN = nowTime.FN() % state->fillerModulus[TN]; - - bursts[i] = state->fillerTable[modFN][TN]; - zeros[i] = state->chanType[TN] == NONE; - if ((burst = mTxPriorityQueues[i].getCurrentBurst(nowTime))) { bursts[i] = burst->getVector(); @@ -443,7 +455,21 @@ void Transceiver::pushRadioVector(GSM::Time &nowTime) } delete burst; + } else { + modFN = nowTime.FN() % state->fillerModulus[TN]; + bursts[i] = state->fillerTable[modFN][TN]; + if (i == 0 && state->mFiller == FILLER_ZERO) { + LOGCHAN(i, DTRXDDL, INFO) << "No Tx burst available for " << nowTime + << ", retrans=" << state->mRetrans; + state->ctrs.tx_unavailable_bursts++; + ratectr_changed = true; + } } + + mtx->unlock(); + + if (ratectr_changed) + dispatch_trx_rate_ctr_change(state, i); } mRadioInterface->driveTransmitRadio(bursts, zeros); @@ -528,16 +554,16 @@ CorrType Transceiver::expectedCorrType(GSM::Time currTime, break; case IV: case VI: - return mExtRACH ? EXT_RACH : RACH; + return cfg->ext_rach ? EXT_RACH : RACH; break; case V: { int mod51 = burstFN % 51; if ((mod51 <= 36) && (mod51 >= 14)) - return mExtRACH ? EXT_RACH : RACH; + return cfg->ext_rach ? EXT_RACH : RACH; else if ((mod51 == 4) || (mod51 == 5)) - return mExtRACH ? EXT_RACH : RACH; + return cfg->ext_rach ? EXT_RACH : RACH; else if ((mod51 == 45) || (mod51 == 46)) - return mExtRACH ? EXT_RACH : RACH; + return cfg->ext_rach ? EXT_RACH : RACH; else if (mHandover[burstTN][sdcch4_subslot[burstFN % 102]]) return RACH; else @@ -555,11 +581,11 @@ CorrType Transceiver::expectedCorrType(GSM::Time currTime, case XIII: { int mod52 = burstFN % 52; if ((mod52 == 12) || (mod52 == 38)) - return mExtRACH ? EXT_RACH : RACH; + return RACH; /* RACH is always 8-bit on PTCCH/U */ else if ((mod52 == 25) || (mod52 == 51)) return IDLE; else /* Enable 8-PSK burst detection if EDGE is enabled */ - return mEdge ? EDGE : TSC; + return cfg->egprs ? EDGE : TSC; break; } case LOOPBACK: @@ -584,12 +610,59 @@ void writeToFile(radioVector *radio_burst, size_t chan) outfile.close(); } +double Transceiver::rssiOffset(size_t chan) +{ + if (cfg->force_rssi_offset) + return cfg->rssi_offset; + return mRadioInterface->rssiOffset(chan) + cfg->rssi_offset; +} + +static SoftVector *demodAnyBurst_va(const signalVector &burst, CorrType type, int sps, int rach_max_toa, int tsc) +{ + auto conved_beg = reinterpret_cast<const std::complex<float> *>(&burst.begin()[0]); + std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR]; + float ncmax; + const unsigned burst_len_bits = 148 + 8; + char demodded_softbits[burst_len_bits]; + SoftVector *bits = new SoftVector(burst_len_bits); + + if (type == CorrType::TSC) { + auto rach_burst_start = get_norm_chan_imp_resp(conved_beg, chan_imp_resp, &ncmax, tsc); + rach_burst_start = std::max(rach_burst_start, 0); + detect_burst_nb(conved_beg, chan_imp_resp, rach_burst_start, demodded_softbits); + } else { + auto normal_burst_start = get_access_imp_resp(conved_beg, chan_imp_resp, &ncmax, 0); + normal_burst_start = std::max(normal_burst_start, 0); + detect_burst_ab(conved_beg, chan_imp_resp, normal_burst_start, demodded_softbits, rach_max_toa); + } + + float *s = &bits->begin()[0]; + for (unsigned int i = 0; i < 148; i++) + s[i] = demodded_softbits[i] * -1; + for (unsigned int i = 148; i < burst_len_bits; i++) + s[i] = 0; + return bits; +} + +#define USE_VA + +#ifdef USE_VA +// signalvector is owning despite claiming not to, but we can pretend, too.. +static void dummy_free(void *wData){}; +static void *dummy_alloc(size_t newSize) +{ + return 0; +}; +#endif + /* * Pull bursts from the FIFO and handle according to the slot * and burst correlation type. Equalzation is currently disabled. - * returns true on success (bi filled), false on error (bi content undefined). + * returns 0 on success (bi filled), negative on error (bi content undefined): + * -ENOENT: timeslot is off (fn and tn in bi are filled), + * -EIO: read error */ -bool Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) +int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) { int rc; struct estim_burst_params ebp; @@ -600,16 +673,21 @@ bool Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) GSM::Time burstTime; SoftVector *rxBurst; TransceiverState *state = &mStates[chan]; + bool ctr_changed = false; + double rssi_offset; + static complex burst_shift_buffer[625]; + static signalVector shift_vec(burst_shift_buffer, 0, 625, dummy_alloc, dummy_free); + signalVector *shvec_ptr = &shift_vec; /* Blocking FIFO read */ radioVector *radio_burst = mReceiveFIFO[chan]->read(); if (!radio_burst) { - LOGCHAN(chan, DMAIN, ERROR) << "ReceiveFIFO->read() returned no burst"; - return false; + LOGCHAN(chan, DTRXDUL, ERROR) << "ReceiveFIFO->read() returned no burst"; + return -EIO; } /* Set time and determine correlation type */ - burstTime = radio_burst->getTime(); + burstTime = radio_burst->getTime() + cfg->ul_fn_offset; CorrType type = expectedCorrType(burstTime, chan); /* Initialize struct bi */ @@ -635,12 +713,16 @@ bool Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) * Not even power level or noise calculation. */ if (type == OFF) { delete radio_burst; - return false; + return -ENOENT; } + /* If TRX RF is locked/muted by BTS, send idle burst indications */ + if (state->mMuted) + goto ret_idle; + /* Select the diversity channel with highest energy */ for (size_t i = 0; i < radio_burst->chans(); i++) { - float pow = energyDetect(*radio_burst->getVector(i), 20 * mSPSRx); + float pow = energyDetect(*radio_burst->getVector(i), 20 * cfg->rx_sps); if (pow > max) { max = pow; max_i = i; @@ -649,7 +731,9 @@ bool Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) } if (max_i < 0) { - LOG(ALERT) << "Received empty burst"; + LOGCHAN(chan, DTRXDUL, INFO) << "Received empty burst"; + state->ctrs.rx_empty_burst++; + ctr_changed = true; goto ret_idle; } @@ -663,8 +747,9 @@ bool Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) state->mNoiseLev = state->mNoises.avg(); } - bi->rssi = 20.0 * log10(rxFullScale / avg) + rssiOffset; - bi->noise = 20.0 * log10(rxFullScale / state->mNoiseLev) + rssiOffset; + rssi_offset = rssiOffset(chan); + bi->rssi = 20.0 * log10(rxFullScale / avg) + rssi_offset; + bi->noise = 20.0 * log10(rxFullScale / state->mNoiseLev) + rssi_offset; if (type == IDLE) goto ret_idle; @@ -672,21 +757,38 @@ bool Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) max_toa = (type == RACH || type == EXT_RACH) ? mMaxExpectedDelayAB : mMaxExpectedDelayNB; + if (cfg->use_va) { + // shifted burst copy to make the old demod and detection happy + std::copy(burst->begin() + 20, burst->end() - 20, shift_vec.begin()); + } else { + shvec_ptr = burst; + } + /* Detect normal or RACH bursts */ - rc = detectAnyBurst(*burst, mTSC, BURST_THRESH, mSPSRx, type, max_toa, &ebp); + rc = detectAnyBurst(*shvec_ptr, mTSC, BURST_THRESH, cfg->rx_sps, type, max_toa, &ebp); if (rc <= 0) { - if (rc == -SIGERR_CLIP) - LOG(WARNING) << "Clipping detected on received RACH or Normal Burst"; - else if (rc != SIGERR_NONE) - LOG(WARNING) << "Unhandled RACH or Normal Burst detection error"; + if (rc == -SIGERR_CLIP) { + LOGCHAN(chan, DTRXDUL, INFO) << "Clipping detected on received RACH or Normal Burst"; + state->ctrs.rx_clipping++; + ctr_changed = true; + } else if (rc != SIGERR_NONE) { + LOGCHAN(chan, DTRXDUL, INFO) << "Unhandled RACH or Normal Burst detection error"; + state->ctrs.rx_no_burst_detected++; + ctr_changed = true; + } goto ret_idle; } - type = (CorrType) rc; + if (cfg->use_va) { + scaleVector(*burst, { (1. / (float)((1 << 14) - 1)), 0 }); + rxBurst = demodAnyBurst_va(*burst, (CorrType)rc, cfg->rx_sps, max_toa, mTSC); + } else { + rxBurst = demodAnyBurst(*shvec_ptr, (CorrType)rc, cfg->rx_sps, &ebp); + } + bi->toa = ebp.toa; bi->tsc = ebp.tsc; bi->ci = ebp.ci; - rxBurst = demodAnyBurst(*burst, mSPSRx, ebp.amp, ebp.toa, type); /* EDGE demodulator returns 444 (gSlotLen * 3) bits */ if (rxBurst->size() == EDGE_BURST_NBITS) { @@ -702,12 +804,14 @@ bool Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) delete rxBurst; delete radio_burst; - return true; + return 0; ret_idle: + if (ctr_changed) + dispatch_trx_rate_ctr_change(state, chan); bi->idle = true; delete radio_burst; - return true; + return 0; } void Transceiver::reset() @@ -717,8 +821,6 @@ void Transceiver::reset() } -#define MAX_PACKET_LENGTH 100 - /** * Matches a buffer with a command. * @param buf a buffer to look command in @@ -748,27 +850,77 @@ static bool match_cmd(char *buf, return true; } -bool Transceiver::driveControl(size_t chan) +void Transceiver::ctrl_sock_send(ctrl_msg& m, int chan) +{ + ctrl_sock_state& s = mCtrlSockets[chan]; + struct osmo_fd *conn_bfd = &s.conn_bfd; + + s.txmsgqueue.push_back(m); + osmo_fd_write_enable(conn_bfd); +} + +int Transceiver::ctrl_sock_write(int chan) { - char buffer[MAX_PACKET_LENGTH + 1]; - char response[MAX_PACKET_LENGTH + 1]; + int rc; + ctrl_sock_state& s = mCtrlSockets[chan]; + + if (s.conn_bfd.fd < 0) { + return -EIO; + } + + while (s.txmsgqueue.size()) { + const ctrl_msg m = s.txmsgqueue.front(); + + osmo_fd_write_disable(&s.conn_bfd); + + /* try to send it over the socket */ + rc = write(s.conn_bfd.fd, m.data, strlen(m.data) + 1); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + osmo_fd_write_enable(&s.conn_bfd); + break; + } + goto close; + } + + s.txmsgqueue.pop_front(); + } + return 0; + +close: + LOGCHAN(chan, DTRXCTRL, NOTICE) << "mCtrlSockets write(" << s.conn_bfd.fd << ") failed: " << rc; + return -1; +} + +int Transceiver::ctrl_sock_handle_rx(int chan) +{ + ctrl_msg cmd_received; + ctrl_msg cmd_to_send; + char *buffer = cmd_received.data; + char *response = cmd_to_send.data; char *command, *params; int msgLen; + ctrl_sock_state& s = mCtrlSockets[chan]; /* Attempt to read from control socket */ - msgLen = read(mCtrlSockets[chan], buffer, MAX_PACKET_LENGTH); + msgLen = read(s.conn_bfd.fd, buffer, sizeof(cmd_received.data)-1); + if (msgLen < 0 && errno == EAGAIN) + return 0; /* Try again later */ if (msgLen <= 0) { - LOGCHAN(chan, DTRXCTRL, WARNING) << "mCtrlSockets read(" << mCtrlSockets[chan] << ") failed: " << msgLen; - return false; + LOGCHAN(chan, DTRXCTRL, NOTICE) << "mCtrlSockets read(" << s.conn_bfd.fd << ") failed: " << msgLen; + return -EIO; } + /* Zero-terminate received string */ buffer[msgLen] = '\0'; /* Verify a command signature */ if (strncmp(buffer, "CMD ", 4)) { - LOGC(DTRXCTRL, WARNING) << "bogus message on control interface"; - return false; + LOGCHAN(chan, DTRXCTRL, NOTICE) << "bogus message on control interface"; + return -EIO; } /* Set command pointer */ @@ -792,7 +944,7 @@ bool Transceiver::driveControl(size_t chan) unsigned ts = 0, ss = 0; sscanf(params, "%u %u", &ts, &ss); if (ts > 7 || ss > 7) { - sprintf(response, "RSP NOHANDOVER 1 %u %u", ts, ss); + sprintf(response, "RSP HANDOVER 1 %u %u", ts, ss); } else { mHandover[ts][ss] = true; sprintf(response, "RSP HANDOVER 0 %u %u", ts, ss); @@ -807,19 +959,18 @@ bool Transceiver::driveControl(size_t chan) sprintf(response, "RSP NOHANDOVER 0 %u %u", ts, ss); } } else if (match_cmd(command, "SETMAXDLY", ¶ms)) { - //set expected maximum time-of-arrival + //set expected maximum time-of-arrival for Access Bursts int maxDelay; sscanf(params, "%d", &maxDelay); mMaxExpectedDelayAB = maxDelay; // 1 GSM symbol is approx. 1 km sprintf(response,"RSP SETMAXDLY 0 %d",maxDelay); } else if (match_cmd(command, "SETMAXDLYNB", ¶ms)) { - //set expected maximum time-of-arrival + //set expected maximum time-of-arrival for Normal Bursts int maxDelay; sscanf(params, "%d", &maxDelay); mMaxExpectedDelayNB = maxDelay; // 1 GSM symbol is approx. 1 km sprintf(response,"RSP SETMAXDLYNB 0 %d",maxDelay); } else if (match_cmd(command, "SETRXGAIN", ¶ms)) { - //set expected maximum time-of-arrival int newGain; sscanf(params, "%d", &newGain); newGain = mRadioInterface->setRxGain(newGain, chan); @@ -831,7 +982,7 @@ bool Transceiver::driveControl(size_t chan) (int) round(20.0 * log10(rxFullScale / lev))); } else { - sprintf(response,"RSP NOISELEV 1 0"); + sprintf(response,"RSP NOISELEV 1 0"); } } else if (match_cmd(command, "SETPOWER", ¶ms)) { int power; @@ -846,13 +997,16 @@ bool Transceiver::driveControl(size_t chan) power = mRadioInterface->setPowerAttenuation(power, chan); mStates[chan].mPower = power; sprintf(response, "RSP ADJPOWER 0 %d", power); +} else if (match_cmd(command, "NOMTXPOWER", NULL)) { + int power = mRadioInterface->getNominalTxPower(chan); + sprintf(response, "RSP NOMTXPOWER 0 %d", power); } else if (match_cmd(command, "RXTUNE", ¶ms)) { // tune receiver int freqKhz; sscanf(params, "%d", &freqKhz); - mRxFreq = freqKhz * 1e3; + mRxFreq = (freqKhz + cfg->freq_offset_khz) * 1e3; if (!mRadioInterface->tuneRx(mRxFreq, chan)) { - LOGC(DTRXCTRL, ALERT) << "RX failed to tune"; + LOGCHAN(chan, DTRXCTRL, FATAL) << "RX failed to tune"; sprintf(response,"RSP RXTUNE 1 %d",freqKhz); } else @@ -861,9 +1015,9 @@ bool Transceiver::driveControl(size_t chan) // tune txmtr int freqKhz; sscanf(params, "%d", &freqKhz); - mTxFreq = freqKhz * 1e3; + mTxFreq = (freqKhz + cfg->freq_offset_khz) * 1e3; if (!mRadioInterface->tuneTx(mTxFreq, chan)) { - LOGC(DTRXCTRL, ALERT) << "TX failed to tune"; + LOGCHAN(chan, DTRXCTRL, FATAL) << "TX failed to tune"; sprintf(response,"RSP TXTUNE 1 %d",freqKhz); } else @@ -885,9 +1039,9 @@ bool Transceiver::driveControl(size_t chan) int timeslot; sscanf(params, "%d %d", ×lot, &corrCode); if ((timeslot < 0) || (timeslot > 7)) { - LOGC(DTRXCTRL, WARNING) << "bogus message on control interface"; + LOGCHAN(chan, DTRXCTRL, NOTICE) << "bogus message on control interface"; sprintf(response,"RSP SETSLOT 1 %d %d",timeslot,corrCode); - return true; + return 0; } mStates[chan].chanType[timeslot] = (ChannelCombination) corrCode; setModulus(timeslot, chan); @@ -899,32 +1053,34 @@ bool Transceiver::driveControl(size_t chan) LOGCHAN(chan, DTRXCTRL, INFO) << "BTS requests TRXD version switch: " << version_recv; if (version_recv > TRX_DATA_FORMAT_VER) { LOGCHAN(chan, DTRXCTRL, INFO) << "rejecting TRXD version " << version_recv - << "in favor of " << TRX_DATA_FORMAT_VER; + << " in favor of " << TRX_DATA_FORMAT_VER; sprintf(response, "RSP SETFORMAT %u %u", TRX_DATA_FORMAT_VER, version_recv); } else { LOGCHAN(chan, DTRXCTRL, NOTICE) << "switching to TRXD version " << version_recv; mVersionTRXD[chan] = version_recv; sprintf(response, "RSP SETFORMAT %u %u", version_recv, version_recv); } + } else if (match_cmd(command, "RFMUTE", ¶ms)) { + // (Un)mute RF TX and RX + unsigned mute; + sscanf(params, "%u", &mute); + mStates[chan].mMuted = mute ? true : false; + sprintf(response, "RSP RFMUTE 0 %u", mute); } else if (match_cmd(command, "_SETBURSTTODISKMASK", ¶ms)) { - // debug command! may change or disapear without notice + // debug command! may change or disappear without notice // set a mask which bursts to dump to disk int mask; sscanf(params, "%d", &mask); mWriteBurstToDiskMask = mask; sprintf(response,"RSP _SETBURSTTODISKMASK 0 %d",mask); } else { - LOGC(DTRXCTRL, WARNING) << "bogus command " << command << " on control interface."; + LOGCHAN(chan, DTRXCTRL, NOTICE) << "bogus command " << command << " on control interface."; sprintf(response,"RSP ERR 1"); } LOGCHAN(chan, DTRXCTRL, INFO) << "response is '" << response << "'"; - msgLen = write(mCtrlSockets[chan], response, strlen(response) + 1); - if (msgLen <= 0) { - LOGCHAN(chan, DTRXCTRL, WARNING) << "mCtrlSockets write(" << mCtrlSockets[chan] << ") failed: " << msgLen; - return false; - } - return true; + transceiver->ctrl_sock_send(cmd_to_send, chan); + return 0; } bool Transceiver::driveTxPriorityQueue(size_t chan) @@ -934,11 +1090,12 @@ bool Transceiver::driveTxPriorityQueue(size_t chan) struct trxd_hdr_v01_dl *dl; char buffer[sizeof(*dl) + EDGE_BURST_NBITS]; uint32_t fn; + uint8_t tn; // check data socket msgLen = read(mDataSockets[chan], buffer, sizeof(buffer)); if (msgLen <= 0) { - LOGCHAN(chan, DTRXCTRL, WARNING) << "mDataSockets read(" << mCtrlSockets[chan] << ") failed: " << msgLen; + LOGCHAN(chan, DTRXDDL, NOTICE) << "mDataSockets read(" << mDataSockets[chan] << ") failed: " << msgLen; return false; } @@ -947,14 +1104,14 @@ bool Transceiver::driveTxPriorityQueue(size_t chan) burstLen = gSlotLen; break; case sizeof(*dl) + EDGE_BURST_NBITS: /* EDGE burst */ - if (mSPSTx != 4) { - LOG(ERR) << "EDGE burst received but SPS is set to " << mSPSTx; + if (cfg->tx_sps != 4) { + LOGCHAN(chan, DTRXDDL, ERROR) << "EDGE burst received but SPS is set to " << cfg->tx_sps; return false; } burstLen = EDGE_BURST_NBITS; break; default: - LOG(ERR) << "badly formatted packet on GSM->TRX interface (len="<< msgLen << ")"; + LOGCHAN(chan, DTRXDDL, ERROR) << "badly formatted packet on GSM->TRX interface (len="<< msgLen << ")"; return false; } @@ -962,6 +1119,7 @@ bool Transceiver::driveTxPriorityQueue(size_t chan) /* Convert TDMA FN to the host endianness */ fn = osmo_load32be(&dl->common.fn); + tn = dl->common.tn; /* Make sure we support the received header format */ switch (dl->common.version) { @@ -970,13 +1128,49 @@ bool Transceiver::driveTxPriorityQueue(size_t chan) case 1: break; default: - LOG(ERR) << "Rx TRXD message with unknown header version " << unsigned(dl->common.version); + LOGCHAN(chan, DTRXDDL, ERROR) << "Rx TRXD message with unknown header version " << unsigned(dl->common.version); return false; } - LOG(DEBUG) << "Rx TRXD message (hdr_ver=" << unsigned(dl->common.version) << "): " - << "fn=" << fn << ", tn=" << unsigned(dl->common.tn) << ", " - << "burst_len=" << burstLen; + LOGCHAN(chan, DTRXDDL, DEBUG) << "Rx TRXD message (hdr_ver=" << unsigned(dl->common.version) + << "): fn=" << fn << ", tn=" << unsigned(tn) << ", burst_len=" << burstLen; + + TransceiverState *state = &mStates[chan]; + GSM::Time currTime = GSM::Time(fn, tn); + + /* Verify proper FN order in DL stream */ + if (state->first_dl_fn_rcv[tn]) { + int32_t delta = GSM::FNDelta(currTime.FN(), state->last_dl_time_rcv[tn].FN()); + if (delta == 1) { + /* usual expected scenario, continue code flow */ + } else if (delta == 0) { + LOGCHAN(chan, DTRXDDL, INFO) << "Rx TRXD msg with repeated FN " << currTime; + state->ctrs.tx_trxd_fn_repeated++; + dispatch_trx_rate_ctr_change(state, chan); + return true; + } else if (delta < 0) { + LOGCHAN(chan, DTRXDDL, INFO) << "Rx TRXD msg with previous FN " << currTime + << " vs last " << state->last_dl_time_rcv[tn]; + state->ctrs.tx_trxd_fn_outoforder++; + dispatch_trx_rate_ctr_change(state, chan); + /* Allow adding radio vector below, since it gets sorted in the queue */ + } else if (chan == 0 && state->mFiller == FILLER_ZERO) { + /* delta > 1. Some FN was lost in the middle. We can only easily rely + * on consecutive FNs in TRX0 since it must transmit continuously in all + * setups. Also, osmo-trx supports optionally filling empty bursts on + * its own. In that case bts-trx is not obliged to submit all bursts. */ + LOGCHAN(chan, DTRXDDL, INFO) << "Rx TRXD msg with future FN " << currTime + << " vs last " << state->last_dl_time_rcv[tn] + << ", " << delta - 1 << " FN lost"; + state->ctrs.tx_trxd_fn_skipped += delta - 1; + dispatch_trx_rate_ctr_change(state, chan); + } + if (delta > 0) + state->last_dl_time_rcv[tn] = currTime; + } else { /* Initial check, simply store state */ + state->first_dl_fn_rcv[tn] = true; + state->last_dl_time_rcv[tn] = currTime; + } BitVector newBurst(burstLen); BitVector::iterator itr = newBurst.begin(); @@ -984,8 +1178,6 @@ bool Transceiver::driveTxPriorityQueue(size_t chan) while (itr < newBurst.end()) *itr++ = *bufferItr++; - GSM::Time currTime = GSM::Time(fn, dl->common.tn); - addRadioVector(chan, newBurst, dl->tx_att, currTime); return true; @@ -1002,6 +1194,8 @@ bool Transceiver::driveReceiveRadio() return false; if (mForceClockInterface || mTransmitDeadlineClock > mLastClockUpdateTime + GSM::Time(216,0)) { + if (mForceClockInterface) + LOGC(DTRXCLK, NOTICE) << "Sending CLOCK indications"; mForceClockInterface = false; return writeClockInterface(); } @@ -1018,11 +1212,13 @@ void Transceiver::logRxBurst(size_t chan, const struct trx_ul_burst_ind *bi) else os << "-"; } - LOGCHAN(chan, DMAIN, DEBUG) << std::fixed << std::right + double rssi_offset = rssiOffset(chan); + + LOGCHAN(chan, DTRXDUL, DEBUG) << std::fixed << std::right << " time: " << unsigned(bi->tn) << ":" << bi->fn - << " RSSI: " << std::setw(5) << std::setprecision(1) << (bi->rssi - rssiOffset) + << " RSSI: " << std::setw(5) << std::setprecision(1) << (bi->rssi - rssi_offset) << "dBFS/" << std::setw(6) << -bi->rssi << "dBm" - << " noise: " << std::setw(5) << std::setprecision(1) << (bi->noise - rssiOffset) + << " noise: " << std::setw(5) << std::setprecision(1) << (bi->noise - rssi_offset) << "dBFS/" << std::setw(6) << -bi->noise << "dBm" << " TOA: " << std::setw(5) << std::setprecision(2) << bi->toa << " C/I: " << std::setw(5) << std::setprecision(2) << bi->ci << "dB" @@ -1032,11 +1228,17 @@ void Transceiver::logRxBurst(size_t chan, const struct trx_ul_burst_ind *bi) bool Transceiver::driveReceiveFIFO(size_t chan) { struct trx_ul_burst_ind bi; + int rc; - if (!pullRadioVector(chan, &bi)) - return false; + if ((rc = pullRadioVector(chan, &bi)) < 0) { + if (rc == -ENOENT) { /* timeslot off, continue processing */ + LOGCHAN(chan, DTRXDUL, DEBUG) << unsigned(bi.tn) << ":" << bi.fn << " timeslot is off"; + return true; + } + return false; /* other errors: we want to stop the process */ + } - if (!bi.idle) + if (!bi.idle && log_check_level(DTRXDUL, LOGL_DEBUG)) logRxBurst(chan, &bi); switch (mVersionTRXD[chan]) { @@ -1067,7 +1269,7 @@ void Transceiver::driveTxFIFO() if (mOn) { //radioClock->wait(); // wait until clock updates - LOG(DEBUG) << "radio clock " << radioClock->get(); + LOGC(DTRXCLK, DEBUG) << "radio clock " << radioClock->get(); while (radioClock->get() + mTransmitLatency > mTransmitDeadlineClock) { // if underrun, then we're not providing bursts to radio/USRP fast // enough. Need to increase latency by one GSM frame. @@ -1076,8 +1278,9 @@ void Transceiver::driveTxFIFO() // only update latency at the defined frame interval if (radioClock->get() > mLatencyUpdateTime + GSM::Time(USB_LATENCY_INTRVL)) { mTransmitLatency = mTransmitLatency + GSM::Time(1,0); - LOG(INFO) << "new latency: " << mTransmitLatency << " (underrun " - << radioClock->get() << " vs " << mLatencyUpdateTime + GSM::Time(USB_LATENCY_INTRVL) << ")"; + LOGC(DTRXCLK, INFO) << "new latency: " << mTransmitLatency << " (underrun " + << radioClock->get() << " vs " + << mLatencyUpdateTime + GSM::Time(USB_LATENCY_INTRVL) << ")"; mLatencyUpdateTime = radioClock->get(); } } @@ -1087,7 +1290,7 @@ void Transceiver::driveTxFIFO() if (mTransmitLatency > mRadioInterface->minLatency()) { if (radioClock->get() > mLatencyUpdateTime + GSM::Time(216,0)) { mTransmitLatency.decTN(); - LOG(INFO) << "reduced latency: " << mTransmitLatency; + LOGC(DTRXCLK, INFO) << "reduced latency: " << mTransmitLatency; mLatencyUpdateTime = radioClock->get(); } } @@ -1111,11 +1314,11 @@ bool Transceiver::writeClockInterface() // FIXME -- This should be adaptive. sprintf(command,"IND CLOCK %llu",(unsigned long long) (mTransmitDeadlineClock.FN()+2)); - LOG(INFO) << "ClockInterface: sending " << command; + LOGC(DTRXCLK, INFO) << "sending " << command; msgLen = write(mClockSocket, command, strlen(command) + 1); if (msgLen <= 0) { - LOG(ERROR) << "mClockSocket write(" << mClockSocket << ") failed: " << msgLen; + LOGC(DTRXCLK, ERROR) << "mClockSocket write(" << mClockSocket << ") failed: " << msgLen; return false; } @@ -1133,10 +1336,11 @@ void *RxUpperLoopAdapter(TrxChanThParams *params) snprintf(thread_name, 16, "RxUpper%zu", num); set_selfthread_name(thread_name); + OSMO_ASSERT(osmo_cpu_sched_vty_apply_localthread() == 0); while (1) { if (!trx->driveReceiveFIFO(num)) { - LOGCHAN(num, DMAIN, FATAL) << "Something went wrong in thread " << thread_name << ", requesting stop"; + LOGCHAN(num, DTRXDUL, FATAL) << "Something went wrong in thread " << thread_name << ", requesting stop"; osmo_signal_dispatch(SS_MAIN, S_MAIN_STOP_REQUIRED, NULL); break; } @@ -1148,10 +1352,11 @@ void *RxUpperLoopAdapter(TrxChanThParams *params) void *RxLowerLoopAdapter(Transceiver *transceiver) { set_selfthread_name("RxLower"); + OSMO_ASSERT(osmo_cpu_sched_vty_apply_localthread() == 0); while (1) { if (!transceiver->driveReceiveRadio()) { - LOG(FATAL) << "Something went wrong in thread RxLower, requesting stop"; + LOGC(DTRXDUL, FATAL) << "Something went wrong in thread RxLower, requesting stop"; osmo_signal_dispatch(SS_MAIN, S_MAIN_STOP_REQUIRED, NULL); break; } @@ -1163,6 +1368,7 @@ void *RxLowerLoopAdapter(Transceiver *transceiver) void *TxLowerLoopAdapter(Transceiver *transceiver) { set_selfthread_name("TxLower"); + OSMO_ASSERT(osmo_cpu_sched_vty_apply_localthread() == 0); while (1) { transceiver->driveTxFIFO(); @@ -1171,28 +1377,6 @@ void *TxLowerLoopAdapter(Transceiver *transceiver) return NULL; } -void *ControlServiceLoopAdapter(TrxChanThParams *params) -{ - char thread_name[16]; - Transceiver *trx = params->trx; - size_t num = params->num; - - free(params); - - snprintf(thread_name, 16, "CtrlService%zu", num); - set_selfthread_name(thread_name); - - while (1) { - if (!trx->driveControl(num)) { - LOGCHAN(num, DTRXCTRL, FATAL) << "Something went wrong in thread " << thread_name << ", requesting stop"; - osmo_signal_dispatch(SS_MAIN, S_MAIN_STOP_REQUIRED, NULL); - break; - } - pthread_testcancel(); - } - return NULL; -} - void *TxUpperLoopAdapter(TrxChanThParams *params) { char thread_name[16]; @@ -1203,10 +1387,11 @@ void *TxUpperLoopAdapter(TrxChanThParams *params) snprintf(thread_name, 16, "TxUpper%zu", num); set_selfthread_name(thread_name); + OSMO_ASSERT(osmo_cpu_sched_vty_apply_localthread() == 0); while (1) { if (!trx->driveTxPriorityQueue(num)) { - LOGCHAN(num, DMAIN, FATAL) << "Something went wrong in thread " << thread_name << ", requesting stop"; + LOGCHAN(num, DTRXDDL, FATAL) << "Something went wrong in thread " << thread_name << ", requesting stop"; osmo_signal_dispatch(SS_MAIN, S_MAIN_STOP_REQUIRED, NULL); break; } diff --git a/Transceiver52M/Transceiver.h b/Transceiver52M/Transceiver.h index 0d09854..babe420 100644 --- a/Transceiver52M/Transceiver.h +++ b/Transceiver52M/Transceiver.h @@ -33,11 +33,14 @@ extern "C" { #include <osmocom/core/signal.h> +#include <osmocom/core/select.h> #include "config_defs.h" } class Transceiver; +extern Transceiver *transceiver; + /** Channel descriptor for transceiver object and channel number pair */ struct TrxChanThParams { Transceiver *trx; @@ -60,6 +63,7 @@ struct TransceiverState { /* The filler table */ signalVector *fillerTable[102][8]; int fillerModulus[8]; + FillerType mFiller; bool mRetrans; /* Most recent channel estimate of all timeslots */ @@ -76,37 +80,39 @@ struct TransceiverState { /* Received noise energy levels */ float mNoiseLev; - noiseVector mNoises; + avgVector mNoises; /* Shadowed downlink attenuation */ int mPower; + + /* RF emission and reception disabled, as per NM Administrative State Locked */ + bool mMuted; + + /* counters */ + struct trx_counters ctrs; + + /* Used to keep track of lost and out of order frames */ + bool first_dl_fn_rcv[8]; + GSM::Time last_dl_time_rcv[8]; }; /** The Transceiver class, responsible for physical layer of basestation */ class Transceiver { public: /** Transceiver constructor - @param wBasePort base port number of UDP sockets - @param TRXAddress IP address of the TRX, as a string - @param GSMcoreAddress IP address of the GSM core, as a string - @param wSPS number of samples per GSM symbol + @param cfg VTY populated config @param wTransmitLatency initial setting of transmit latency @param radioInterface associated radioInterface object */ - Transceiver(int wBasePort, - const char *TRXAddress, - const char *GSMcoreAddress, - size_t tx_sps, size_t rx_sps, size_t chans, + Transceiver(const struct trx_cfg *cfg, GSM::Time wTransmitLatency, - RadioInterface *wRadioInterface, - double wRssiOffset, int stackSize); + RadioInterface *wRadioInterface); /** Destructor */ ~Transceiver(); /** Start the control loop */ - bool init(FillerType filler, size_t rtsc, unsigned rach_delay, - bool edge, bool ext_rach); + bool init(void); /** attach the radioInterface receive FIFO */ bool receiveFIFO(VectorFIFO *wFIFO, size_t chan) @@ -119,7 +125,7 @@ public: } /** accessor for number of channels */ - size_t numChans() const { return mChans; }; + size_t numChans() const { return cfg->num_chans; }; /** Codes for channel combinations */ typedef enum { @@ -142,12 +148,30 @@ public: } ChannelCombination; private: - int mBasePort; - std::string mLocalAddr; - std::string mRemoteAddr; + size_t mChans; +struct ctrl_msg { + char data[101]; + ctrl_msg() {}; +}; + +struct ctrl_sock_state { + osmo_fd conn_bfd; + std::deque<ctrl_msg> txmsgqueue; + ctrl_sock_state() { + conn_bfd.fd = -1; + } + ~ctrl_sock_state() { + if(conn_bfd.fd >= 0) { + osmo_fd_unregister(&conn_bfd); + close(conn_bfd.fd); + conn_bfd.fd = -1; + } + } +}; + const struct trx_cfg *cfg; ///< VTY populated config std::vector<int> mDataSockets; ///< socket for writing to/reading from GSM core - std::vector<int> mCtrlSockets; ///< socket for writing/reading control commands from GSM core + std::vector<ctrl_sock_state> mCtrlSockets; ///< socket for writing/reading control commands from GSM core int mClockSocket; ///< socket for writing clock updates to GSM core std::vector<VectorQueue> mTxPriorityQueues; ///< priority queue of transmit bursts received from GSM core @@ -156,7 +180,6 @@ private: std::vector<Thread *> mRxServiceLoopThreads; ///< thread to pull bursts into receive FIFO Thread *mRxLowerLoopThread; ///< thread to pull bursts into receive FIFO Thread *mTxLowerLoopThread; ///< thread to push bursts into transmit FIFO - std::vector<Thread *> mControlServiceLoopThreads; ///< thread to process control messages from GSM core std::vector<Thread *> mTxPriorityQueueServiceLoopThreads; ///< thread to process transmit bursts from GSM core GSM::Time mTransmitLatency; ///< latency between basestation clock and transmit deadline clock @@ -168,9 +191,6 @@ private: double txFullScale; ///< full scale input to radio double rxFullScale; ///< full scale output to radio - double rssiOffset; ///< RSSI to dBm conversion offset - int stackSize; ///< stack size for threads, 0 = OS default - /** modulate and add a burst to the transmit queue */ void addRadioVector(size_t chan, BitVector &bits, int RSSI, GSM::Time &wTime); @@ -182,7 +202,7 @@ private: void pushRadioVector(GSM::Time &nowTime); /** Pull and demodulate a burst from the receive FIFO */ - bool pullRadioVector(size_t chan, struct trx_ul_burst_ind *ind); + int pullRadioVector(size_t chan, struct trx_ul_burst_ind *ind); /** Set modulus for specific timeslot */ void setModulus(size_t timeslot, size_t chan); @@ -193,12 +213,13 @@ private: /** send messages over the clock socket */ bool writeClockInterface(void); - int mSPSTx; ///< number of samples per Tx symbol - int mSPSRx; ///< number of samples per Rx symbol - size_t mChans; + static int ctrl_sock_cb(struct osmo_fd *bfd, unsigned int flags); + int ctrl_sock_write(int chan); + void ctrl_sock_send(ctrl_msg& m, int chan); + /** drive handling of control messages from GSM core */ + int ctrl_sock_handle_rx(int chan); + - bool mExtRACH; - bool mEdge; bool mOn; ///< flag to indicate that transceiver is powered on bool mForceClockInterface; ///< flag to indicate whether IND CLOCK shall be sent unconditionally after transceiver is started bool mHandover[8][8]; ///< expect handover to the timeslot/subslot @@ -216,7 +237,7 @@ private: bool start(); void stop(); - /** Protect destructor accessable stop call */ + /** Protect destructor accessible stop call */ Mutex mLock; protected: @@ -229,9 +250,6 @@ protected: /** drive transmission of GSM bursts */ void driveTxFIFO(); - /** drive handling of control messages from GSM core */ - bool driveControl(size_t chan); - /** drive modulation and sorting of GSM bursts from GSM core @return true if a burst was transferred successfully @@ -242,9 +260,8 @@ protected: friend void *TxUpperLoopAdapter(TrxChanThParams *params); friend void *RxLowerLoopAdapter(Transceiver *transceiver); friend void *TxLowerLoopAdapter(Transceiver *transceiver); - friend void *ControlServiceLoopAdapter(TrxChanThParams *params); - + double rssiOffset(size_t chan); void reset(); void logRxBurst(size_t chan, const struct trx_ul_burst_ind *bi); @@ -256,8 +273,5 @@ void *RxUpperLoopAdapter(TrxChanThParams *params); void *RxLowerLoopAdapter(Transceiver *transceiver); void *TxLowerLoopAdapter(Transceiver *transceiver); -/** control message handler thread loop */ -void *ControlServiceLoopAdapter(TrxChanThParams *params); - /** transmit queueing thread loop */ void *TxUpperLoopAdapter(TrxChanThParams *params); diff --git a/Transceiver52M/arch/arm/convert.c b/Transceiver52M/arch/arm/convert.c index ace1b6f..7145579 100644 --- a/Transceiver52M/arch/arm/convert.c +++ b/Transceiver52M/arch/arm/convert.c @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <malloc.h> diff --git a/Transceiver52M/arch/arm/convert_neon.S b/Transceiver52M/arch/arm/convert_neon.S index a1fbd40..6cf3c03 100644 --- a/Transceiver52M/arch/arm/convert_neon.S +++ b/Transceiver52M/arch/arm/convert_neon.S @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ .syntax unified diff --git a/Transceiver52M/arch/arm/convolve.c b/Transceiver52M/arch/arm/convolve.c index 63b1655..c2611b4 100644 --- a/Transceiver52M/arch/arm/convolve.c +++ b/Transceiver52M/arch/arm/convolve.c @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <malloc.h> @@ -58,7 +54,7 @@ static void neon_conv_cmplx_4n(float *x, float *h, float *y, int h_len, int len) } #endif -/* API: Initalize convolve module */ +/* API: Initialize convolve module */ void convolve_init(void) { /* Stub */ diff --git a/Transceiver52M/arch/arm/convolve_neon.S b/Transceiver52M/arch/arm/convolve_neon.S index a3e1ba5..3209d60 100644 --- a/Transceiver52M/arch/arm/convolve_neon.S +++ b/Transceiver52M/arch/arm/convolve_neon.S @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H diff --git a/Transceiver52M/arch/arm/mult.c b/Transceiver52M/arch/arm/mult.c index 9851626..251b3c9 100644 --- a/Transceiver52M/arch/arm/mult.c +++ b/Transceiver52M/arch/arm/mult.c @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <malloc.h> diff --git a/Transceiver52M/arch/arm/mult_neon.S b/Transceiver52M/arch/arm/mult_neon.S index 6318c50..b4cd229 100644 --- a/Transceiver52M/arch/arm/mult_neon.S +++ b/Transceiver52M/arch/arm/mult_neon.S @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ .syntax unified diff --git a/Transceiver52M/arch/arm/scale.c b/Transceiver52M/arch/arm/scale.c index a3214f7..d44ffc8 100644 --- a/Transceiver52M/arch/arm/scale.c +++ b/Transceiver52M/arch/arm/scale.c @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <malloc.h> diff --git a/Transceiver52M/arch/arm/scale_neon.S b/Transceiver52M/arch/arm/scale_neon.S index f10de1e..c348f30 100644 --- a/Transceiver52M/arch/arm/scale_neon.S +++ b/Transceiver52M/arch/arm/scale_neon.S @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ .syntax unified diff --git a/Transceiver52M/arch/common/convert_base.c b/Transceiver52M/arch/common/convert_base.c index 9876e83..9a01a1e 100644 --- a/Transceiver52M/arch/common/convert_base.c +++ b/Transceiver52M/arch/common/convert_base.c @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "convert.h" diff --git a/Transceiver52M/arch/common/convolve_base.c b/Transceiver52M/arch/common/convolve_base.c index bfda783..3765c5c 100644 --- a/Transceiver52M/arch/common/convolve_base.c +++ b/Transceiver52M/arch/common/convolve_base.c @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <malloc.h> diff --git a/Transceiver52M/arch/common/fft.c b/Transceiver52M/arch/common/fft.c index ad096b1..2261672 100644 --- a/Transceiver52M/arch/common/fft.c +++ b/Transceiver52M/arch/common/fft.c @@ -103,7 +103,7 @@ void free_fft(struct fft_hdl *hdl) } /*! \brief Run multiple DFT operations with the initialized plan - * \param[in] hdl handle to an intitialized fft struct + * \param[in] hdl handle to an initialized fft struct * * Input and output buffers are configured with init_fft(). */ diff --git a/Transceiver52M/arch/x86/convert.c b/Transceiver52M/arch/x86/convert.c index 07cdf59..596233c 100644 --- a/Transceiver52M/arch/x86/convert.c +++ b/Transceiver52M/arch/x86/convert.c @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <malloc.h> @@ -27,7 +23,7 @@ #include "config.h" #endif -/* Architecture dependant function pointers */ +/* Architecture dependent function pointers */ struct convert_cpu_context { void (*convert_si16_ps_16n) (float *, const short *, int); void (*convert_si16_ps) (float *, const short *, int); diff --git a/Transceiver52M/arch/x86/convert_sse_3.c b/Transceiver52M/arch/x86/convert_sse_3.c index 255db67..f00ecf5 100644 --- a/Transceiver52M/arch/x86/convert_sse_3.c +++ b/Transceiver52M/arch/x86/convert_sse_3.c @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <malloc.h> diff --git a/Transceiver52M/arch/x86/convert_sse_3.h b/Transceiver52M/arch/x86/convert_sse_3.h index c2f87d7..b5abe47 100644 --- a/Transceiver52M/arch/x86/convert_sse_3.h +++ b/Transceiver52M/arch/x86/convert_sse_3.h @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once diff --git a/Transceiver52M/arch/x86/convert_sse_4_1.c b/Transceiver52M/arch/x86/convert_sse_4_1.c index 42a235c..736a376 100644 --- a/Transceiver52M/arch/x86/convert_sse_4_1.c +++ b/Transceiver52M/arch/x86/convert_sse_4_1.c @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <malloc.h> diff --git a/Transceiver52M/arch/x86/convert_sse_4_1.h b/Transceiver52M/arch/x86/convert_sse_4_1.h index 57a5efb..63305e5 100644 --- a/Transceiver52M/arch/x86/convert_sse_4_1.h +++ b/Transceiver52M/arch/x86/convert_sse_4_1.h @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once diff --git a/Transceiver52M/arch/x86/convolve.c b/Transceiver52M/arch/x86/convolve.c index 209d377..45a3719 100644 --- a/Transceiver52M/arch/x86/convolve.c +++ b/Transceiver52M/arch/x86/convolve.c @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <malloc.h> @@ -27,7 +23,7 @@ #include "config.h" #endif -/* Architecture dependant function pointers */ +/* Architecture dependent function pointers */ struct convolve_cpu_context { void (*conv_cmplx_4n) (const float *, int, const float *, int, float *, int, int, int); @@ -66,7 +62,7 @@ int _base_convolve_complex(const float *x, int x_len, int bounds_check(int x_len, int h_len, int y_len, int start, int len); -/* API: Initalize convolve module */ +/* API: Initialize convolve module */ void convolve_init(void) { c.conv_cmplx_4n = (void *)_base_convolve_complex; @@ -99,9 +95,10 @@ int convolve_real(const float *x, int x_len, const float *h, int h_len, float *y, int y_len, int start, int len) { +#ifndef __OPTIMIZE__ if (bounds_check(x_len, h_len, y_len, start, len) < 0) return -1; - +#endif memset(y, 0, len * 2 * sizeof(float)); switch (h_len) { @@ -138,9 +135,10 @@ int convolve_complex(const float *x, int x_len, float *y, int y_len, int start, int len) { +#ifndef __OPTIMIZE__ if (bounds_check(x_len, h_len, y_len, start, len) < 0) return -1; - +#endif memset(y, 0, len * 2 * sizeof(float)); if (!(h_len % 8)) diff --git a/Transceiver52M/arch/x86/convolve_sse_3.c b/Transceiver52M/arch/x86/convolve_sse_3.c index 8fd3b5e..ca4dc71 100644 --- a/Transceiver52M/arch/x86/convolve_sse_3.c +++ b/Transceiver52M/arch/x86/convolve_sse_3.c @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <malloc.h> diff --git a/Transceiver52M/arch/x86/convolve_sse_3.h b/Transceiver52M/arch/x86/convolve_sse_3.h index d929ef6..2cbc037 100644 --- a/Transceiver52M/arch/x86/convolve_sse_3.h +++ b/Transceiver52M/arch/x86/convolve_sse_3.h @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once diff --git a/Transceiver52M/device/Makefile.am b/Transceiver52M/device/Makefile.am index 369e877..9af18f7 100644 --- a/Transceiver52M/device/Makefile.am +++ b/Transceiver52M/device/Makefile.am @@ -2,6 +2,10 @@ include $(top_srcdir)/Makefile.common SUBDIRS = common +if DEVICE_IPC +SUBDIRS += ipc +endif + if DEVICE_USRP1 SUBDIRS += usrp1 endif @@ -13,3 +17,7 @@ endif if DEVICE_LMS SUBDIRS += lms endif + +if DEVICE_BLADE +SUBDIRS += bladerf +endif diff --git a/Transceiver52M/device/bladerf/Makefile.am b/Transceiver52M/device/bladerf/Makefile.am new file mode 100644 index 0000000..e9917af --- /dev/null +++ b/Transceiver52M/device/bladerf/Makefile.am @@ -0,0 +1,11 @@ +include $(top_srcdir)/Makefile.common + +AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(BLADE_CFLAGS) + +noinst_HEADERS = bladerf.h + +noinst_LTLIBRARIES = libdevice.la + +libdevice_la_SOURCES = bladerf.cpp +libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la diff --git a/Transceiver52M/device/bladerf/bladerf.cpp b/Transceiver52M/device/bladerf/bladerf.cpp new file mode 100644 index 0000000..2f3c86a --- /dev/null +++ b/Transceiver52M/device/bladerf/bladerf.cpp @@ -0,0 +1,611 @@ +/* + * Copyright 2022 sysmocom - s.f.m.c. GmbH + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * See the COPYING file in the main directory for details. + */ + +#include <map> +#include <libbladeRF.h> +#include "radioDevice.h" +#include "bladerf.h" +#include "Threads.h" +#include "Logger.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +extern "C" { +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/vty/cpu_sched_vty.h> +} + +#define SAMPLE_BUF_SZ (1 << 20) + +#define B2XX_TIMING_4_4SPS 6.18462e-5 + +#define CHKRET() \ + { \ + if (status != 0) \ + LOGC(DDEV, ERROR) << bladerf_strerror(status); \ + } + +static const dev_map_t dev_param_map{ + { std::make_tuple(blade_dev_type::BLADE2, 4, 4), { 1, 26e6, GSMRATE, B2XX_TIMING_4_4SPS, "B200 4 SPS" } }, +}; + +static const power_map_t dev_band_nom_power_param_map{ + { std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_850), { 89.75, 13.3, -7.5 } }, + { std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_900), { 89.75, 13.3, -7.5 } }, + { std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1800), { 89.75, 7.5, -11.0 } }, + { std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1900), { 89.75, 7.7, -11.0 } }, +}; + +/* So far measurements done for B210 show really close to linear relationship + * between gain and real output power, so we simply adjust the measured offset + */ +static double TxGain2TxPower(const dev_band_desc &desc, double tx_gain_db) +{ + return desc.nom_out_tx_power - (desc.nom_uhd_tx_gain - tx_gain_db); +} +static double TxPower2TxGain(const dev_band_desc &desc, double tx_power_dbm) +{ + return desc.nom_uhd_tx_gain - (desc.nom_out_tx_power - tx_power_dbm); +} + +blade_device::blade_device(InterfaceType iface, const struct trx_cfg *cfg) + : RadioDevice(iface, cfg), band_manager(dev_band_nom_power_param_map, dev_param_map), dev(nullptr), + rx_gain_min(0.0), rx_gain_max(0.0), tx_spp(0), rx_spp(0), started(false), aligned(false), drop_cnt(0), + prev_ts(0), ts_initial(0), ts_offset(0), async_event_thrd(NULL) +{ +} + +blade_device::~blade_device() +{ + if (dev) { + bladerf_enable_module(dev, BLADERF_CHANNEL_RX(0), false); + bladerf_enable_module(dev, BLADERF_CHANNEL_TX(0), false); + } + + stop(); + + for (size_t i = 0; i < rx_buffers.size(); i++) + delete rx_buffers[i]; +} + +void blade_device::init_gains() +{ + double tx_gain_min, tx_gain_max; + int status; + + const struct bladerf_range *r; + bladerf_get_gain_range(dev, BLADERF_RX, &r); + + rx_gain_min = r->min; + rx_gain_max = r->max; + LOGC(DDEV, INFO) << "Supported Rx gain range [" << rx_gain_min << "; " << rx_gain_max << "]"; + + for (size_t i = 0; i < rx_gains.size(); i++) { + double gain = (rx_gain_min + rx_gain_max) / 2; + status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(i), BLADERF_GAIN_MGC); + CHKRET() + bladerf_gain_mode m; + bladerf_get_gain_mode(dev, BLADERF_CHANNEL_RX(i), &m); + LOGC(DDEV, INFO) << (m == BLADERF_GAIN_MANUAL ? "gain manual" : "gain AUTO"); + + status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0); + CHKRET() + int actual_gain; + status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(i), &actual_gain); + CHKRET() + LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale " + << r->scale << " actual " << actual_gain; + rx_gains[i] = actual_gain; + + status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0); + CHKRET() + status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(i), &actual_gain); + CHKRET() + LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale " + << r->scale << " actual " << actual_gain; + rx_gains[i] = actual_gain; + } + + status = bladerf_get_gain_range(dev, BLADERF_TX, &r); + CHKRET() + tx_gain_min = r->min; + tx_gain_max = r->max; + LOGC(DDEV, INFO) << "Supported Tx gain range [" << tx_gain_min << "; " << tx_gain_max << "]"; + + for (size_t i = 0; i < tx_gains.size(); i++) { + double gain = (tx_gain_min + tx_gain_max) / 2; + status = bladerf_set_gain(dev, BLADERF_CHANNEL_TX(i), 30); + CHKRET() + int actual_gain; + status = bladerf_get_gain(dev, BLADERF_CHANNEL_TX(i), &actual_gain); + CHKRET() + LOGC(DDEV, INFO) << "Default setting Tx gain for channel " << i << " to " << gain << " scale " + << r->scale << " actual " << actual_gain; + tx_gains[i] = actual_gain; + } + + return; +} + +void blade_device::set_rates() +{ + struct bladerf_rational_rate rate = { 0, static_cast<uint64_t>((1625e3 * 4)), 6 }, actual; + auto status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_RX(0), &rate, &actual); + CHKRET() + status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_TX(0), &rate, &actual); + CHKRET() + + tx_rate = rx_rate = (double)rate.num / (double)rate.den; + + LOGC(DDEV, INFO) << "Rates set to" << tx_rate << " / " << rx_rate; + + bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth *)NULL); + bladerf_set_bandwidth(dev, BLADERF_CHANNEL_TX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth *)NULL); + + ts_offset = 60; // FIXME: actual blade offset, should equal b2xx +} + +double blade_device::setRxGain(double db, size_t chan) +{ + if (chan >= rx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + bladerf_set_gain(dev, BLADERF_CHANNEL_RX(chan), 30); //db); + int actual_gain; + bladerf_get_gain(dev, BLADERF_CHANNEL_RX(chan), &actual_gain); + + rx_gains[chan] = actual_gain; + + LOGC(DDEV, INFO) << "Set RX gain to " << rx_gains[chan] << "dB (asked for " << db << "dB)"; + + return rx_gains[chan]; +} + +double blade_device::getRxGain(size_t chan) +{ + if (chan >= rx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + return rx_gains[chan]; +} + +double blade_device::rssiOffset(size_t chan) +{ + double rssiOffset; + dev_band_desc desc; + + if (chan >= rx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + get_dev_band_desc(desc); + rssiOffset = rx_gains[chan] + desc.rxgain2rssioffset_rel; + return rssiOffset; +} + +double blade_device::setPowerAttenuation(int atten, size_t chan) +{ + double tx_power, db; + dev_band_desc desc; + + if (chan >= tx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel" << chan; + return 0.0f; + } + + get_dev_band_desc(desc); + tx_power = desc.nom_out_tx_power - atten; + db = TxPower2TxGain(desc, tx_power); + + bladerf_set_gain(dev, BLADERF_CHANNEL_TX(chan), 30); + int actual_gain; + bladerf_get_gain(dev, BLADERF_CHANNEL_RX(chan), &actual_gain); + + tx_gains[chan] = actual_gain; + + LOGC(DDEV, INFO) + << "Set TX gain to " << tx_gains[chan] << "dB, ~" << TxGain2TxPower(desc, tx_gains[chan]) << " dBm " + << "(asked for " << db << " dB, ~" << tx_power << " dBm)"; + + return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]); +} +double blade_device::getPowerAttenuation(size_t chan) +{ + dev_band_desc desc; + if (chan >= tx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + get_dev_band_desc(desc); + return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]); +} + +int blade_device::getNominalTxPower(size_t chan) +{ + dev_band_desc desc; + get_dev_band_desc(desc); + + return desc.nom_out_tx_power; +} + +int blade_device::open() +{ + bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_VERBOSE); + bladerf_set_usb_reset_on_open(true); + auto success = bladerf_open(&dev, cfg->dev_args); + if (success != 0) { + struct bladerf_devinfo *info; + auto num_devs = bladerf_get_device_list(&info); + LOGC(DDEV, ALERT) << "No bladerf devices found with identifier '" << cfg->dev_args << "'"; + if (num_devs) { + for (int i = 0; i < num_devs; i++) + LOGC(DDEV, ALERT) << "Found device:" << info[i].product << " serial " << info[i].serial; + } + + return -1; + } + if (strcmp("bladerf2", bladerf_get_board_name(dev))) { + LOGC(DDEV, ALERT) << "Only BladeRF2 supported! found:" << bladerf_get_board_name(dev); + return -1; + } + + dev_type = blade_dev_type::BLADE2; + tx_window = TX_WINDOW_FIXED; + update_band_dev(dev_key(dev_type, tx_sps, rx_sps)); + + struct bladerf_devinfo info; + bladerf_get_devinfo(dev, &info); + LOGC(DDEV, INFO) << "Using discovered bladerf device " << info.serial; + + tx_freqs.resize(chans); + rx_freqs.resize(chans); + tx_gains.resize(chans); + rx_gains.resize(chans); + rx_buffers.resize(chans); + + switch (cfg->clock_ref) { + case REF_INTERNAL: + case REF_EXTERNAL: + break; + default: + LOGC(DDEV, ALERT) << "Invalid reference type"; + return -1; + } + + if (cfg->clock_ref == REF_EXTERNAL) { + bool is_locked; + int status = bladerf_set_pll_enable(dev, true); + CHKRET() + status = bladerf_set_pll_refclk(dev, 10000000); + CHKRET() + for (int i = 0; i < 20; i++) { + usleep(50 * 1000); + status = bladerf_get_pll_lock_state(dev, &is_locked); + CHKRET() + if (is_locked) + break; + } + if (!is_locked) { + LOGC(DDEV, ALERT) << "unable to lock refclk!"; + return -1; + } + } + + LOGC(DDEV, INFO) + << "Selected clock source is " << ((cfg->clock_ref == REF_INTERNAL) ? "internal" : "external 10Mhz"); + + set_rates(); + + /* + 1ts = 3/5200s + 1024*2 = small gap(~180us) every 9.23ms = every 16 ts? -> every 2 frames + 1024*1 = large gap(~627us) every 9.23ms = every 16 ts? -> every 2 frames + + rif convertbuffer = 625*4 = 2500 -> 4 ts + rif rxtxbuf = 4 * segment(625*4) = 10000 -> 16 ts + */ + const unsigned int num_buffers = 256; + const unsigned int buffer_size = 1024 * 4; /* Must be a multiple of 1024 */ + const unsigned int num_transfers = 32; + const unsigned int timeout_ms = 3500; + + bladerf_sync_config(dev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11_META, num_buffers, buffer_size, num_transfers, + timeout_ms); + + bladerf_sync_config(dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11_META, num_buffers, buffer_size, num_transfers, + timeout_ms); + + /* Number of samples per over-the-wire packet */ + tx_spp = rx_spp = buffer_size; + + size_t buf_len = SAMPLE_BUF_SZ / sizeof(uint32_t); + for (size_t i = 0; i < rx_buffers.size(); i++) + rx_buffers[i] = new smpl_buf(buf_len); + + pkt_bufs = std::vector<std::vector<short> >(chans, std::vector<short>(2 * rx_spp)); + for (size_t i = 0; i < pkt_bufs.size(); i++) + pkt_ptrs.push_back(&pkt_bufs[i].front()); + + init_gains(); + + return NORMAL; +} + +bool blade_device::restart() +{ + /* Allow 100 ms delay to align multi-channel streams */ + double delay = 0.2; + int status; + + status = bladerf_enable_module(dev, BLADERF_CHANNEL_RX(0), true); + CHKRET() + status = bladerf_enable_module(dev, BLADERF_CHANNEL_TX(0), true); + CHKRET() + + bladerf_timestamp now; + status = bladerf_get_timestamp(dev, BLADERF_RX, &now); + ts_initial = now + rx_rate * delay; + LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl; + + return true; +} + +bool blade_device::start() +{ + LOGC(DDEV, INFO) << "Starting USRP..."; + + if (started) { + LOGC(DDEV, ERROR) << "Device already started"; + return false; + } + + if (!restart()) + return false; + + started = true; + return true; +} + +bool blade_device::stop() +{ + if (!started) + return false; + + /* reset internal buffer timestamps */ + for (size_t i = 0; i < rx_buffers.size(); i++) + rx_buffers[i]->reset(); + + band_reset(); + + started = false; + return true; +} + +int blade_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun) +{ + ssize_t rc; + uint64_t ts; + + if (bufs.size() != chans) { + LOGC(DDEV, ALERT) << "Invalid channel combination " << bufs.size(); + return -1; + } + + *overrun = false; + *underrun = false; + + // Shift read time with respect to transmit clock + timestamp += ts_offset; + + ts = timestamp; + LOGC(DDEV, DEBUG) << "Requested timestamp = " << ts; + + // Check that timestamp is valid + rc = rx_buffers[0]->avail_smpls(timestamp); + if (rc < 0) { + LOGC(DDEV, ERROR) << rx_buffers[0]->str_code(rc); + LOGC(DDEV, ERROR) << rx_buffers[0]->str_status(timestamp); + return 0; + } + + struct bladerf_metadata meta = {}; + meta.timestamp = ts; + + while (rx_buffers[0]->avail_smpls(timestamp) < len) { + thread_enable_cancel(false); + int status = bladerf_sync_rx(dev, pkt_ptrs[0], len, &meta, 200U); + thread_enable_cancel(true); + + if (status != 0) + LOGC(DDEV, ERROR) << "RX broken: " << bladerf_strerror(status); + if (meta.flags & BLADERF_META_STATUS_OVERRUN) + LOGC(DDEV, ERROR) << "RX borken, OVERRUN: " << bladerf_strerror(status); + + size_t num_smpls = meta.actual_count; + ; + ts = meta.timestamp; + + for (size_t i = 0; i < rx_buffers.size(); i++) { + rc = rx_buffers[i]->write((short *)&pkt_bufs[i].front(), num_smpls, ts); + + // Continue on local overrun, exit on other errors + if ((rc < 0)) { + LOGC(DDEV, ERROR) << rx_buffers[i]->str_code(rc); + LOGC(DDEV, ERROR) << rx_buffers[i]->str_status(timestamp); + if (rc != smpl_buf::ERROR_OVERFLOW) + return 0; + } + } + meta = {}; + meta.timestamp = ts + num_smpls; + } + + for (size_t i = 0; i < rx_buffers.size(); i++) { + rc = rx_buffers[i]->read(bufs[i], len, timestamp); + if ((rc < 0) || (rc != len)) { + LOGC(DDEV, ERROR) << rx_buffers[i]->str_code(rc); + LOGC(DDEV, ERROR) << rx_buffers[i]->str_status(timestamp); + return 0; + } + } + + return len; +} + +int blade_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun, unsigned long long timestamp) +{ + *underrun = false; + static bool first_tx = true; + struct bladerf_metadata meta = {}; + if (first_tx) { + meta.timestamp = timestamp; + meta.flags = BLADERF_META_FLAG_TX_BURST_START; + first_tx = false; + } + + thread_enable_cancel(false); + int status = bladerf_sync_tx(dev, (const void *)bufs[0], len, &meta, 200U); + thread_enable_cancel(true); + + if (status != 0) + LOGC(DDEV, ERROR) << "TX broken: " << bladerf_strerror(status); + + return len; +} + +bool blade_device::updateAlignment(TIMESTAMP timestamp) +{ + return true; +} + +bool blade_device::set_freq(double freq, size_t chan, bool tx) +{ + if (tx) { + bladerf_set_frequency(dev, BLADERF_CHANNEL_TX(chan), freq); + bladerf_frequency f; + bladerf_get_frequency(dev, BLADERF_CHANNEL_TX(chan), &f); + tx_freqs[chan] = f; + } else { + bladerf_set_frequency(dev, BLADERF_CHANNEL_RX(chan), freq); + bladerf_frequency f; + bladerf_get_frequency(dev, BLADERF_CHANNEL_RX(chan), &f); + rx_freqs[chan] = f; + } + LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << (tx ? "TX" : "RX") << "): " << std::endl; + + return true; +} + +bool blade_device::setTxFreq(double wFreq, size_t chan) +{ + if (chan >= tx_freqs.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return false; + } + ScopedLock lock(tune_lock); + + if (!update_band_from_freq(wFreq, chan, true)) + return false; + + if (!set_freq(wFreq, chan, true)) + return false; + + return true; +} + +bool blade_device::setRxFreq(double wFreq, size_t chan) +{ + if (chan >= rx_freqs.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return false; + } + ScopedLock lock(tune_lock); + + if (!update_band_from_freq(wFreq, chan, false)) + return false; + + return set_freq(wFreq, chan, false); +} + +double blade_device::getTxFreq(size_t chan) +{ + if (chan >= tx_freqs.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0; + } + + return tx_freqs[chan]; +} + +double blade_device::getRxFreq(size_t chan) +{ + if (chan >= rx_freqs.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0; + } + + return rx_freqs[chan]; +} + +bool blade_device::requiresRadioAlign() +{ + return false; +} + +GSM::Time blade_device::minLatency() +{ + return GSM::Time(6, 7); +} + +TIMESTAMP blade_device::initialWriteTimestamp() +{ + return ts_initial; +} + +TIMESTAMP blade_device::initialReadTimestamp() +{ + return ts_initial; +} + +double blade_device::fullScaleInputValue() +{ + return (double)2047; +} + +double blade_device::fullScaleOutputValue() +{ + return (double)2047; +} + +RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg) +{ + return new blade_device(type, cfg); +} diff --git a/Transceiver52M/device/bladerf/bladerf.h b/Transceiver52M/device/bladerf/bladerf.h new file mode 100644 index 0000000..e32581e --- /dev/null +++ b/Transceiver52M/device/bladerf/bladerf.h @@ -0,0 +1,195 @@ +/* + * Copyright 2022 sysmocom - s.f.m.c. GmbH + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * See the COPYING file in the main directory for details. + */ + +#pragma once + +#include <map> +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "bandmanager.h" +#include "radioDevice.h" +#include "smpl_buf.h" + +extern "C" { +#include <osmocom/gsm/gsm_utils.h> +} + +enum class blade_dev_type { BLADE1, BLADE2 }; + +struct dev_band_desc { + /* Maximum UHD Tx Gain which can be set/used without distorting the + output signal, and the resulting real output power measured when that + gain is used. Correct measured values only provided for B210 so far. */ + double nom_uhd_tx_gain; /* dB */ + double nom_out_tx_power; /* dBm */ + /* Factor used to infer base real RSSI offset on the Rx path based on current + configured RxGain. The resulting rssiOffset is added to the per burst + calculated energy in upper layers. These values were empirically + found and may change based on multiple factors, see OS#4468. + rssiOffset = rxGain + rxgain2rssioffset_rel; + */ + double rxgain2rssioffset_rel; /* dB */ +}; + +/* Device parameter descriptor */ +struct dev_desc { + unsigned channels; + double mcr; + double rate; + double offset; + std::string desc_str; +}; + +using dev_key = std::tuple<blade_dev_type, int, int>; +using dev_band_key = std::tuple<blade_dev_type, enum gsm_band>; +using power_map_t = std::map<dev_band_key, dev_band_desc>; +using dev_map_t = std::map<dev_key, dev_desc>; + +class blade_device : public RadioDevice, public band_manager<power_map_t, dev_map_t> { + public: + blade_device(InterfaceType iface, const struct trx_cfg *cfg); + ~blade_device(); + + int open(); + bool start(); + bool stop(); + bool restart(); + enum TxWindowType getWindowType() + { + return tx_window; + } + + int readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun); + + int writeSamples(std::vector<short *> &bufs, int len, bool *underrun, TIMESTAMP timestamp); + + bool updateAlignment(TIMESTAMP timestamp); + + bool setTxFreq(double wFreq, size_t chan); + bool setRxFreq(double wFreq, size_t chan); + + TIMESTAMP initialWriteTimestamp(); + TIMESTAMP initialReadTimestamp(); + + double fullScaleInputValue(); + double fullScaleOutputValue(); + + double setRxGain(double db, size_t chan); + double getRxGain(size_t chan); + double maxRxGain(void) + { + return rx_gain_max; + } + double minRxGain(void) + { + return rx_gain_min; + } + double rssiOffset(size_t chan); + + double setPowerAttenuation(int atten, size_t chan); + double getPowerAttenuation(size_t chan = 0); + + int getNominalTxPower(size_t chan = 0); + + double getTxFreq(size_t chan); + double getRxFreq(size_t chan); + double getRxFreq(); + + bool setRxAntenna(const std::string &ant, size_t chan) + { + return {}; + }; + std::string getRxAntenna(size_t chan) + { + return {}; + }; + bool setTxAntenna(const std::string &ant, size_t chan) + { + return {}; + }; + std::string getTxAntenna(size_t chan) + { + return {}; + }; + + bool requiresRadioAlign(); + + GSM::Time minLatency(); + + inline double getSampleRate() + { + return tx_rate; + } + + /** Receive and process asynchronous message + @return true if message received or false on timeout or error + */ + bool recv_async_msg(); + + enum err_code { + ERROR_TIMING = -1, + ERROR_TIMEOUT = -2, + ERROR_UNRECOVERABLE = -3, + ERROR_UNHANDLED = -4, + }; + + protected: + struct bladerf *dev; + void *usrp_dev; + + enum TxWindowType tx_window; + enum blade_dev_type dev_type; + + double tx_rate, rx_rate; + + double rx_gain_min, rx_gain_max; + + std::vector<double> tx_gains, rx_gains; + std::vector<double> tx_freqs, rx_freqs; + size_t tx_spp, rx_spp; + + bool started; + bool aligned; + + size_t drop_cnt; + uint64_t prev_ts; + + TIMESTAMP ts_initial, ts_offset; + std::vector<smpl_buf *> rx_buffers; + /* Sample buffers used to receive samples: */ + std::vector<std::vector<short> > pkt_bufs; + /* Used to call UHD API: Buffer pointer of each elem in pkt_ptrs will + point to corresponding buffer of vector pkt_bufs. */ + std::vector<short *> pkt_ptrs; + + void init_gains(); + void set_channels(bool swap); + void set_rates(); + bool flush_recv(size_t num_pkts); + + bool set_freq(double freq, size_t chan, bool tx); + + Thread *async_event_thrd; + Mutex tune_lock; +}; diff --git a/Transceiver52M/device/common/Makefile.am b/Transceiver52M/device/common/Makefile.am index e14cc38..1a33592 100644 --- a/Transceiver52M/device/common/Makefile.am +++ b/Transceiver52M/device/common/Makefile.am @@ -1,10 +1,10 @@ include $(top_srcdir)/Makefile.common AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LMS_CFLAGS) +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) -noinst_HEADERS = radioDevice.h smpl_buf.h +noinst_HEADERS = radioDevice.h smpl_buf.h bandmanager.h noinst_LTLIBRARIES = libdevice_common.la diff --git a/Transceiver52M/device/common/bandmanager.h b/Transceiver52M/device/common/bandmanager.h new file mode 100644 index 0000000..d07f21d --- /dev/null +++ b/Transceiver52M/device/common/bandmanager.h @@ -0,0 +1,137 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include <tuple> + +#include "Logger.h" + +extern "C" { +#include <osmocom/gsm/gsm_utils.h> +} + +template <typename powermapt, typename devmapt> +class band_manager { + using powerkeyt = typename powermapt::key_type; + using powermappedt = typename powermapt::mapped_type; + using devkeyt = typename devmapt::key_type; + devkeyt m_dev_type; + const powermapt &m_power_map; + const devmapt &m_dev_map; + powerkeyt m_fallback; + enum gsm_band m_band; + powermappedt m_band_desc; + bool band_ass_curr_sess{}; /* true if "band" was set after last POWEROFF */ + + // looks up either first tuple element (->enum) or straight enum + template <typename T, typename std::enable_if<std::is_enum<T>::value>::type *dummy = nullptr> + auto key_helper(T &t) -> T + { + return t; + } + + template <typename T> + auto key_helper(T t) -> typename std::tuple_element<0, T>::type + { + return std::get<0>(t); + } + + void assign_band_desc(enum gsm_band req_band) + { + auto key = key_helper(m_dev_type); + auto fallback_key = key_helper(m_fallback); + auto it = m_power_map.find({ key, req_band }); + if (it == m_power_map.end()) { + auto desc = m_dev_map.at(m_dev_type); + LOGC(DDEV, ERROR) << "No Tx Power measurements exist for device " << desc.desc_str + << " on band " << gsm_band_name(req_band) << ", using fallback.."; + it = m_power_map.find({ fallback_key, req_band }); + } + OSMO_ASSERT(it != m_power_map.end()); + m_band_desc = it->second; + } + + bool set_band(enum gsm_band req_band) + { + if (band_ass_curr_sess && req_band != m_band) { + LOGC(DDEV, ALERT) << "Requesting band " << gsm_band_name(req_band) + << " different from previous band " << gsm_band_name(m_band); + return false; + } + + if (req_band != m_band) { + m_band = req_band; + assign_band_desc(m_band); + } + band_ass_curr_sess = true; + return true; + } + + public: + band_manager(const devkeyt &dev_type, const powermapt &power_map, const devmapt &dev_map, powerkeyt fallback) + : m_dev_type(dev_type), m_power_map(power_map), m_dev_map(dev_map), m_fallback(fallback), + m_band((enum gsm_band)0) + { + } + band_manager(const powermapt &power_map, const devmapt &dev_map) + : m_dev_type(dev_map.begin()->first), m_power_map(power_map), m_dev_map(dev_map), + m_fallback(m_power_map.begin()->first), m_band((enum gsm_band)0) + { + } + void band_reset() + { + band_ass_curr_sess = false; + } + + void update_band_dev(devkeyt dev_type) { + m_dev_type = dev_type; + } + + void get_dev_band_desc(powermappedt &desc) + { + if (m_band == 0) { + LOGC(DDEV, ERROR) + << "Power parameters requested before Tx Frequency was set! Providing band 900 by default..."; + assign_band_desc(GSM_BAND_900); + } + desc = m_band_desc; + } + + bool update_band_from_freq(double wFreq, int chan, bool is_tx) + { + enum gsm_band req_band; + auto dirstr = is_tx ? "Tx" : "Rx"; + auto req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, !is_tx); + if (req_arfcn == 0xffff) { + LOGCHAN(chan, DDEV, ALERT) + << "Unknown ARFCN for " << dirstr << " Frequency " << wFreq / 1000 << " kHz"; + return false; + } + if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) { + LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for " << dirstr << " Frequency " << wFreq + << " Hz (ARFCN " << req_arfcn << " )"; + return false; + } + + return set_band(req_band); + } +};
\ No newline at end of file diff --git a/Transceiver52M/device/common/radioDevice.h b/Transceiver52M/device/common/radioDevice.h index b4928f2..5c962d1 100644 --- a/Transceiver52M/device/common/radioDevice.h +++ b/Transceiver52M/device/common/radioDevice.h @@ -1,7 +1,7 @@ /* * Copyright 2008 Free Software Foundation, Inc. * -* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -51,13 +51,10 @@ class RadioDevice { MULTI_ARFCN, }; - static RadioDevice *make(size_t tx_sps, size_t rx_sps, InterfaceType type, - size_t chans = 1, double offset = 0.0, - const std::vector<std::string>& tx_paths = std::vector<std::string>(1, ""), - const std::vector<std::string>& rx_paths = std::vector<std::string>(1, "")); + static RadioDevice *make(InterfaceType type, const struct trx_cfg *cfg); /** Initialize the USRP */ - virtual int open(const std::string &args, int ref, bool swap_channels)=0; + virtual int open() = 0; virtual ~RadioDevice() { } @@ -77,23 +74,20 @@ class RadioDevice { @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 radio does not have data to transmit, e.g. data not being sent fast enough - @param RSSI The received signal strength of the read result @return The number of samples actually read */ virtual int readSamples(std::vector<short *> &bufs, int len, bool *overrun, - TIMESTAMP timestamp = 0xffffffff, bool *underrun = 0, - unsigned *RSSI = 0) = 0; + TIMESTAMP timestamp = 0xffffffff, bool *underrun = 0) = 0; /** Write samples to the radio. @param buf Contains the data to be written. @param len number of samples to write. @param underrun Set if radio 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. - @param isControl Set if data is a control packet, e.g. a ping command @return The number of samples actually written */ virtual int writeSamples(std::vector<short *> &bufs, int len, bool *underrun, - TIMESTAMP timestamp, bool isControl = false) = 0; + TIMESTAMP timestamp) = 0; /** Update the alignment between the read and write timestamps */ virtual bool updateAlignment(TIMESTAMP timestamp)=0; @@ -128,14 +122,11 @@ class RadioDevice { /** return minimum Rx Gain **/ virtual double minRxGain(void) = 0; - /** sets the transmit chan gain, returns the gain setting **/ - virtual double setTxGain(double dB, size_t chan = 0) = 0; + /** return base RSSI offset to apply for received samples **/ + virtual double rssiOffset(size_t chan) = 0; - /** return maximum Tx Gain **/ - virtual double maxTxGain(void) = 0; - - /** return minimum Tx Gain **/ - virtual double minTxGain(void) = 0; + /** returns the Nominal transmit output power of the transceiver in dBm, negative on error **/ + virtual int getNominalTxPower(size_t chan = 0) = 0; /** sets the RX path to use, returns true if successful and false otherwise */ virtual bool setRxAntenna(const std::string &ant, size_t chan = 0) = 0; @@ -160,6 +151,9 @@ class RadioDevice { virtual double getRxFreq(size_t chan = 0) = 0; virtual double getSampleRate()=0; + virtual double setPowerAttenuation(int atten, size_t chan) = 0; + virtual double getPowerAttenuation(size_t chan=0) = 0; + protected: size_t tx_sps, rx_sps; InterfaceType iface; @@ -167,19 +161,30 @@ class RadioDevice { double lo_offset; std::vector<std::string> tx_paths, rx_paths; std::vector<struct device_counters> m_ctr; - - RadioDevice(size_t tx_sps, size_t rx_sps, InterfaceType type, size_t chans, double offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths): - tx_sps(tx_sps), rx_sps(rx_sps), iface(type), chans(chans), lo_offset(offset), - tx_paths(tx_paths), rx_paths(rx_paths) - { - m_ctr.resize(chans); - for (size_t i = 0; i < chans; i++) { - memset(&m_ctr[i], 0, sizeof(m_ctr[i])); - m_ctr[i].chan = i; - } - } + const struct trx_cfg *cfg; + +#define charp2str(a) ((a) ? std::string(a) : std::string("")) + + RadioDevice(InterfaceType type, const struct trx_cfg *cfg) + : tx_sps(cfg->tx_sps), rx_sps(cfg->rx_sps), iface(type), chans(cfg->num_chans), lo_offset(cfg->offset), + m_ctr(chans), cfg(cfg) + { + /* Generate vector of rx/tx_path: */ + for (unsigned int i = 0; i < cfg->num_chans; i++) { + rx_paths.push_back(charp2str(cfg->chans[i].rx_path)); + tx_paths.push_back(charp2str(cfg->chans[i].tx_path)); + } + + if (iface == MULTI_ARFCN) { + LOGC(DDEV, INFO) << "Multi-ARFCN: " << chans << " logical chans -> 1 physical chans"; + chans = 1; + } + + for (size_t i = 0; i < chans; i++) { + memset(&m_ctr[i], 0, sizeof(m_ctr[i])); + m_ctr[i].chan = i; + } + } bool set_antennas() { unsigned int i; @@ -187,6 +192,13 @@ class RadioDevice { for (i = 0; i < tx_paths.size(); i++) { if (tx_paths[i] == "") continue; + if (iface == MULTI_ARFCN && i > 0) { + LOGCHAN(i, DDEV, NOTICE) << "Not setting Tx antenna " + << tx_paths[i] + << " for a logical channel"; + continue; + } + LOGCHAN(i, DDEV, DEBUG) << "Configuring Tx antenna " << tx_paths[i]; if (!setTxAntenna(tx_paths[i], i)) { LOGCHAN(i, DDEV, ALERT) << "Failed configuring Tx antenna " << tx_paths[i]; @@ -197,6 +209,13 @@ class RadioDevice { for (i = 0; i < rx_paths.size(); i++) { if (rx_paths[i] == "") continue; + if (iface == MULTI_ARFCN && i > 0) { + LOGCHAN(i, DDEV, NOTICE) << "Not setting Rx antenna " + << rx_paths[i] + << " for a logical channel"; + continue; + } + LOGCHAN(i, DDEV, DEBUG) << "Configuring Rx antenna " << rx_paths[i]; if (!setRxAntenna(rx_paths[i], i)) { LOGCHAN(i, DDEV, ALERT) << "Failed configuring Rx antenna " << rx_paths[i]; diff --git a/Transceiver52M/device/common/smpl_buf.cpp b/Transceiver52M/device/common/smpl_buf.cpp index 33161bc..e57eb0c 100644 --- a/Transceiver52M/device/common/smpl_buf.cpp +++ b/Transceiver52M/device/common/smpl_buf.cpp @@ -28,9 +28,9 @@ #include <inttypes.h> smpl_buf::smpl_buf(size_t len) - : buf_len(len), time_start(0), time_end(0), - data_start(0), data_end(0) + : buf_len(len) { + reset(); data = new uint32_t[len]; } @@ -39,6 +39,14 @@ smpl_buf::~smpl_buf() delete[] data; } +void smpl_buf::reset() +{ + time_start = 0; + time_end = 0; + data_start = 0; + data_end = 0; +} + ssize_t smpl_buf::avail_smpls(TIMESTAMP timestamp) const { if (timestamp < time_start) @@ -154,7 +162,7 @@ std::string smpl_buf::str_status(TIMESTAMP timestamp) const return ost.str(); } -std::string smpl_buf::str_code(ssize_t code) +std::string smpl_buf::str_code(int code) { switch (code) { case ERROR_TIMESTAMP: @@ -166,6 +174,8 @@ std::string smpl_buf::str_code(ssize_t code) case ERROR_OVERFLOW: return "Sample buffer: Overrun"; default: - return "Sample buffer: Unknown error"; + std::stringstream ss; + ss << "Sample buffer: Unknown error " << code; + return ss.str(); } } diff --git a/Transceiver52M/device/common/smpl_buf.h b/Transceiver52M/device/common/smpl_buf.h index 383c814..ff02baf 100644 --- a/Transceiver52M/device/common/smpl_buf.h +++ b/Transceiver52M/device/common/smpl_buf.h @@ -33,7 +33,7 @@ /* Sample Buffer - Allows reading and writing of timed samples using osmo-trx timestamps. Time conversions are handled - internally or accessable through the static convert calls. + internally or accessible through the static convert calls. */ class smpl_buf { public: @@ -44,6 +44,10 @@ public: smpl_buf(size_t len); ~smpl_buf(); + /** Reset this buffer, keeps the size + */ + void reset(); + /** Query number of samples available for reading @param timestamp time of first sample @return number of available samples or error @@ -68,7 +72,7 @@ public: @param code an error code @return a formatted error string */ - static std::string str_code(ssize_t code); + static std::string str_code(int code); enum err_code { ERROR_TIMESTAMP = -1, diff --git a/Transceiver52M/device/ipc/IPCDevice.cpp b/Transceiver52M/device/ipc/IPCDevice.cpp new file mode 100644 index 0000000..1d2b89a --- /dev/null +++ b/Transceiver52M/device/ipc/IPCDevice.cpp @@ -0,0 +1,1273 @@ +/* +* 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 <cstdint> +#include <cstring> +#include <cstdlib> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "Logger.h" +#include "Threads.h" +#include "IPCDevice.h" +#include "smpl_buf.h" + +extern "C" { +#include <sys/mman.h> +#include <sys/stat.h> /* For mode constants */ +#include <fcntl.h> /* For O_* constants */ + +#include "osmo_signal.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 "ipc_shm.h" +} + +#define SAMPLE_BUF_SZ (1 << 20) + +using namespace std; + +static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags); + +IPCDevice::IPCDevice(InterfaceType iface, const struct trx_cfg *cfg) + : RadioDevice(iface, cfg), sk_chan_state(chans, ipc_per_trx_sock_state()), tx_attenuation(), + tmp_state(IPC_IF_MSG_GREETING_REQ), shm(NULL), shm_dec(0), rx_buffers(chans), started(false), tx_gains(chans), + rx_gains(chans) +{ + LOGC(DDEV, INFO) << "creating IPC device..."; + + /* 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)); +} + +IPCDevice::~IPCDevice() +{ + LOGC(DDEV, INFO) << "Closing IPC device"; + /* disable all channels */ + + for (size_t i = 0; i < rx_buffers.size(); i++) + delete rx_buffers[i]; + + ipc_sock_close(&master_sk_state); + + for (unsigned int i = 0; i < sk_chan_state.size(); i++) + ipc_sock_close(&sk_chan_state[i]); + + for (auto i : shm_io_rx_streams) + ipc_shm_close(i); + for (auto i : shm_io_tx_streams) + ipc_shm_close(i); + + if (shm_dec) + talloc_free(shm_dec); +} + +int IPCDevice::ipc_shm_connect(const char *shm_name) +{ + int fd; + char err_buf[256]; + size_t shm_len; + int rc; + + LOGP(DDEV, LOGL_NOTICE, "Opening shm path %s\n", shm_name); + if ((fd = shm_open(shm_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)) < 0) { + LOGP(DDEV, LOGL_ERROR, "shm_open %d: %s\n", errno, + strerror_r(errno, err_buf, sizeof(err_buf))); + rc = -errno; + goto err_shm_open; + } + + // Get size of the allocated memory + struct stat shm_stat; + if (fstat(fd, &shm_stat) < 0) { + LOGP(DDEV, LOGL_ERROR, "fstat %d: %s\n", errno, + strerror_r(errno, err_buf, sizeof(err_buf))); + rc = -errno; + goto err_mmap; + } + + shm_len = shm_stat.st_size; + + LOGP(DDEV, LOGL_NOTICE, "mmaping shared memory fd %d (size=%zu)\n", fd, shm_len); + if ((shm = mmap(NULL, shm_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { + LOGP(DDEV, LOGL_ERROR, "mmap %d: %s\n", errno, + strerror_r(errno, err_buf, sizeof(err_buf))); + rc = -errno; + goto err_mmap; + } + LOGP(DDEV, LOGL_NOTICE, "mmap'ed shared memory at addr %p\n", shm); + // LOGP(DDEV, LOGL_NOTICE, "%s\n", osmo_hexdump((const unsigned char *)shm, 80)); + /* 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; +} + +static int ipc_sock_send(struct ipc_per_trx_sock_state *state, struct msgb *msg); + +static 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_greeting_req(struct ipc_per_trx_sock_state *state, uint8_t req_version) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + + LOGC(DDEV, NOTICE) << "Tx Greeting Req (" << IPC_IF_MSG_GREETING_REQ << ")\n"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_GREETING_REQ); + if (!msg) { + LOGC(DDEV, INFO) << "ipc_msgb_alloc() returns NULL!"; + return -ENOMEM; + } + ipc_prim = (struct ipc_sk_if *)msg->data; + ipc_prim->u.greeting_req.req_version = req_version; + + return ipc_sock_send(state, msg); +} + +static int ipc_tx_info_req(struct ipc_per_trx_sock_state *state) +{ + struct msgb *msg; + //struct ipc_sk_if *ipc_prim; + + LOGC(DDEV, NOTICE) << "Tx INFO Req\n"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_INFO_REQ); + if (!msg) + return -ENOMEM; + + //ipc_prim = (struct ipc_sk_if *) msg->data; + + return ipc_sock_send(state, msg); +} + +int IPCDevice::ipc_tx_open_req(struct ipc_per_trx_sock_state *state, uint32_t num_chans, uint32_t ref) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + struct ipc_sk_if_open_req_chan *chan_info; + + LOGC(DDEV, NOTICE) << "Tx Open Req\n"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_OPEN_REQ); + if (!msg) { + return -ENOMEM; + } + ipc_prim = (struct ipc_sk_if *)msg->data; + ipc_prim->u.open_req.num_chans = num_chans; + + /* FIXME: this is actually the sps value, not the sample rate! + * sample rate is looked up according to the sps rate by uhd backend */ + ipc_prim->u.open_req.rx_sample_freq_num = rx_sps; + ipc_prim->u.open_req.rx_sample_freq_den = 1; + ipc_prim->u.open_req.tx_sample_freq_num = tx_sps; + ipc_prim->u.open_req.tx_sample_freq_den = 1; + + switch (ref) { + case ReferenceType::REF_EXTERNAL: + ipc_prim->u.open_req.clockref = FEATURE_MASK_CLOCKREF_EXTERNAL; + break; + case ReferenceType::REF_INTERNAL: + case ReferenceType::REF_GPS: + ipc_prim->u.open_req.clockref = FEATURE_MASK_CLOCKREF_INTERNAL; + break; + } + + /* FIXME: clock ref part of config, not open */ + ipc_prim->u.open_req.clockref = FEATURE_MASK_CLOCKREF_EXTERNAL; + + for (unsigned int i = 0; i < num_chans; i++) { + chan_info = &ipc_prim->u.open_req.chan_info[i]; + OSMO_STRLCPY_ARRAY(chan_info->rx_path, rx_paths[i].c_str()); + OSMO_STRLCPY_ARRAY(chan_info->tx_path, tx_paths[i].c_str()); + } + + return ipc_sock_send(state, msg); +} + +static void ipc_sock_timeout(void *_priv) +{ + LOGC(DDEV, INFO) << "UNIX SOCKET TIMEOUT!"; + exit(1); +} + +int IPCDevice::ipc_rx_greeting_cnf(const struct ipc_sk_if_greeting *greeting_cnf) +{ + if (greeting_cnf->req_version == IPC_SOCK_API_VERSION) { + LOGC(DDEV, NOTICE) << "Rx Greeting CNF: correct sock API version" << greeting_cnf->req_version; + tmp_state = IPC_IF_MSG_GREETING_CNF; + } else { + LOGC(DDEV, ERROR) << "Wrong IPC SOCK API VERSION RECEIVED!" << greeting_cnf->req_version; + exit(1); + } + return 0; +} + +int IPCDevice::ipc_rx_info_cnf(const struct ipc_sk_if_info_cnf *info_cnf) +{ + current_info_cnf = *info_cnf; + unsigned int i; + + if (info_cnf->max_num_chans < chans) { + LOGC(DDEV, ERROR) << "chan num mismatch:" << info_cnf->max_num_chans << " vs " << chans; + return -1; + } + + /* Here: + * verify info_cnf->max_num_chans >= requested chans + * verify supports setting reflock as asked by user looking in info_cnf->feature_mask + * cache locally min/max tx/rxGain values from info_cnf + * do whatever validations or print info_cnf->dev_desc + * cache rx/tx paths per channel, and make sure it matches the one the user wants to set + */ + + LOGC(DDEV, NOTICE) + << "Rx Info CNF:" + << " name=" << info_cnf->dev_desc << std::endl + << " max_num_chans=" << info_cnf->max_num_chans << " feature_mask=" << info_cnf->feature_mask; + for (i = 0; i < info_cnf->max_num_chans; i++) { + int j = 0; + bool rx_found = false, tx_found = false; + while (strcmp(info_cnf->chan_info[i].rx_path[j], "") != 0) { + LOGC(DDEV, NOTICE) + << "chan " << i << ": RxPath[" << j << "]: " << info_cnf->chan_info[i].rx_path[j] + << " min_rx_gain=" << info_cnf->chan_info[i].min_rx_gain + << " max_rx_gain=" << info_cnf->chan_info[i].max_rx_gain + << " min_tx_gain=" << info_cnf->chan_info[i].min_tx_gain + << " max_tx_gain=" << info_cnf->chan_info[i].max_tx_gain; + + if (rx_paths.size() < (i + 1) || + strcmp(rx_paths[i].c_str(), info_cnf->chan_info[i].rx_path[j]) == 0) { + rx_found = true; + break; + } + j++; + } + j = 0; + while (strcmp(info_cnf->chan_info[i].tx_path[j], "") != 0) { + LOGC(DDEV, NOTICE) + << "chan " << i << ": TxPath[" << j << "]: " << info_cnf->chan_info[i].tx_path[j]; + if (tx_paths.size() < (i + 1) || + strcmp(tx_paths[i].c_str(), info_cnf->chan_info[i].tx_path[j]) == 0) { + tx_found = true; + break; + } + j++; + } + + if (!rx_found) { + LOGC(DDEV, ERROR) << "rx antenna not found: " << rx_paths[i]; + exit(0); + } + if (!tx_found) { + LOGC(DDEV, ERROR) << "tx antenna not found: " << rx_paths[i]; + exit(0); + } + } + tmp_state = IPC_IF_MSG_INFO_CNF; + return 0; +} + +int IPCDevice::ipc_rx_open_cnf(const struct ipc_sk_if_open_cnf *open_cnf) +{ + unsigned int i; + current_open_cnf = *open_cnf; + + LOGC(DDEV, NOTICE) + << "Rx Open CNF:" + << " return_code=" << (unsigned int)open_cnf->return_code << " shm_name=" << open_cnf->shm_name; + LOGC(DDEV, NOTICE) << "Rx Open CNF:" + << " ipc device path delay: " << (unsigned int)open_cnf->path_delay; + for (i = 0; i < chans; i++) { + int rc; + LOGC(DDEV, NOTICE) << "chan " << i << ": sk_path=" << open_cnf->chan_info[i].chan_ipc_sk_path; + + /* FIXME: current limit IPC_MAX_NUM_TRX chans, make dynamic */ + if (i < IPC_MAX_NUM_TRX) { + struct ipc_per_trx_sock_state *state = &sk_chan_state[i]; + + INIT_LLIST_HEAD(&state->upqueue); + rc = osmo_sock_unix_init_ofd(&state->conn_bfd, SOCK_SEQPACKET, 0, + open_cnf->chan_info[i].chan_ipc_sk_path, OSMO_SOCK_F_CONNECT); + if (rc < 0) { + LOGC(DDEV, ERROR) << "Failed to connect to the BTS (" + << open_cnf->chan_info[i].chan_ipc_sk_path << "). " + << "Retrying...\n"; + osmo_timer_setup(&state->timer, ipc_sock_timeout, NULL); + osmo_timer_schedule(&state->timer, 5, 0); + return -1; + } + state->conn_bfd.cb = ipc_chan_sock_cb; + state->conn_bfd.data = this; + state->conn_bfd.priv_nr = i; + } + } + + OSMO_STRLCPY_ARRAY(shm_name, open_cnf->shm_name); + if (ipc_shm_connect(shm_name) < 0) + return -1; + shm_dec = ipc_shm_decode_region(NULL, (ipc_shm_raw_region *)shm); + LOGC(DDEV, NOTICE) << "shm: num_chans=" << shm_dec->num_chans; + + /* server inits both producers */ + for (unsigned int i = 0; i < shm_dec->num_chans; i++) { + LOGC(DDEV, NOTICE) + << "shm: chan" << i << "/dl: num_buffers=" << shm_dec->channels[i]->dl_stream->num_buffers; + LOGC(DDEV, NOTICE) + << "shm: chan" << i << "/dl: buffer_size=" << shm_dec->channels[i]->dl_stream->buffer_size; + LOGC(DDEV, NOTICE) + << "shm: chan" << i << "/ul: num_buffers=" << shm_dec->channels[i]->ul_stream->num_buffers; + LOGC(DDEV, NOTICE) + << "shm: chan" << i << "/ul: buffer_size=" << shm_dec->channels[i]->ul_stream->buffer_size; + shm_io_rx_streams.push_back(ipc_shm_init_consumer(shm_dec->channels[i]->ul_stream)); + shm_io_tx_streams.push_back(ipc_shm_init_consumer(shm_dec->channels[i]->dl_stream)); + // we should init a producer here, but delegating all producers and therefore lock management + // to the other side is the reasonable approach to circumvent shutdown issues + } + + tmp_state = IPC_IF_MSG_OPEN_CNF; + return 0; +} + +int IPCDevice::ipc_rx(uint8_t msg_type, struct ipc_sk_if *ipc_prim) +{ + int rc = 0; + + switch (msg_type) { + case IPC_IF_MSG_GREETING_CNF: + rc = ipc_rx_greeting_cnf(&ipc_prim->u.greeting_cnf); + break; + case IPC_IF_MSG_INFO_CNF: + rc = ipc_rx_info_cnf(&ipc_prim->u.info_cnf); + break; + case IPC_IF_MSG_OPEN_CNF: + rc = ipc_rx_open_cnf(&ipc_prim->u.open_cnf); + break; + default: + LOGP(DDEV, LOGL_ERROR, "Received unknown IPC msg type %d\n", msg_type); + rc = -EINVAL; + } + + return rc; +} + +int IPCDevice::ipc_rx_chan_start_cnf(ipc_sk_chan_if_op_rc *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal start response for chan #" << chan_nr << " ?!?"; + return 0; + } + return 0; +} +int IPCDevice::ipc_rx_chan_stop_cnf(ipc_sk_chan_if_op_rc *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal stop response for chan #" << chan_nr << " ?!?"; + return 0; + } + return 0; +} +int IPCDevice::ipc_rx_chan_setgain_cnf(ipc_sk_chan_if_gain *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal setgain response for chan #" << chan_nr << " ?!?"; + return 0; + } + + ret->is_tx ? tx_gains[chan_nr] = ret->gain : rx_gains[chan_nr] = ret->gain; + return 0; +} +int IPCDevice::ipc_rx_chan_settxattn_cnf(ipc_sk_chan_if_tx_attenuation *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal tx attn response for chan #" << chan_nr << " ?!?"; + return 0; + } + + tx_attenuation[chan_nr] = ret->attenuation; + return 0; +} +int IPCDevice::ipc_rx_chan_setfreq_cnf(ipc_sk_chan_if_freq_cnf *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal setfreq response for chan #" << chan_nr << " ?!?"; + return 0; + } + + return 0; +} +int IPCDevice::ipc_rx_chan_notify_underflow(ipc_sk_chan_if_notfiy *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal underfloww notification for chan #" << chan_nr << " ?!?"; + return 0; + } + + m_ctr[chan_nr].tx_underruns += 1; + osmo_signal_dispatch(SS_DEVICE, S_DEVICE_COUNTER_CHANGE, &m_ctr[chan_nr]); + + return 0; +} +int IPCDevice::ipc_rx_chan_notify_overflow(ipc_sk_chan_if_notfiy *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal overflow notification for chan #" << chan_nr << " ?!?"; + return 0; + } + + m_ctr[chan_nr].rx_overruns += 1; + osmo_signal_dispatch(SS_DEVICE, S_DEVICE_COUNTER_CHANGE, &m_ctr[chan_nr]); + return 0; +} + +int IPCDevice::ipc_chan_rx(uint8_t msg_type, struct ipc_sk_chan_if *ipc_prim, uint8_t chan_nr) +{ + int rc = 0; + + switch (msg_type) { + case IPC_IF_MSG_START_CNF: + rc = ipc_rx_chan_start_cnf(&ipc_prim->u.start_cnf, chan_nr); + break; + case IPC_IF_MSG_STOP_CNF: + rc = ipc_rx_chan_stop_cnf(&ipc_prim->u.stop_cnf, chan_nr); + break; + case IPC_IF_MSG_SETGAIN_CNF: + rc = ipc_rx_chan_setgain_cnf(&ipc_prim->u.set_gain_cnf, chan_nr); + break; + case IPC_IF_MSG_SETFREQ_CNF: + rc = ipc_rx_chan_setfreq_cnf(&ipc_prim->u.set_freq_cnf, chan_nr); + break; + case IPC_IF_NOTIFY_UNDERFLOW: + rc = ipc_rx_chan_notify_underflow(&ipc_prim->u.notify, chan_nr); + break; + case IPC_IF_NOTIFY_OVERFLOW: + rc = ipc_rx_chan_notify_overflow(&ipc_prim->u.notify, chan_nr); + break; + case IPC_IF_MSG_SETTXATTN_CNF: + rc = ipc_rx_chan_settxattn_cnf(&ipc_prim->u.txatten_cnf, chan_nr); + break; + default: + LOGP(DMAIN, LOGL_ERROR, "Received unknown IPC msg type %d\n", msg_type); + rc = -EINVAL; + } + + return rc; +} + +static int ipc_sock_send(struct ipc_per_trx_sock_state *state, struct msgb *msg) +{ + struct osmo_fd *conn_bfd; + + if (!state) { + LOGP(DMAIN, LOGL_INFO, + "IPC socket not created, " + "dropping message\n"); + msgb_free(msg); + return -EINVAL; + } + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd <= 0) { + LOGP(DMAIN, LOGL_NOTICE, + "IPC socket not connected, " + "dropping message\n"); + msgb_free(msg); + return -EIO; + } + msgb_enqueue(&state->upqueue, msg); + osmo_fd_write_enable(conn_bfd); + + return 0; +} + +void IPCDevice::ipc_sock_close(struct ipc_per_trx_sock_state *state) +{ + if (state == 0) + return; + + struct osmo_fd *bfd = &state->conn_bfd; + + if (bfd->fd <= 0) + return; + + LOGP(DDEV, LOGL_NOTICE, "IPC socket has LOST connection\n"); + + osmo_fd_unregister(bfd); + close(bfd->fd); + bfd->fd = -1; + + /* flush the queue */ + while (!llist_empty(&state->upqueue)) { + struct msgb *msg = msgb_dequeue(&state->upqueue); + msgb_free(msg); + } +} + +int IPCDevice::ipc_sock_read(struct osmo_fd *bfd) +{ + struct ipc_sk_if *ipc_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*ipc_prim) + 1000, "ipc_sock_rx"); + if (!msg) + return -ENOMEM; + + ipc_prim = (struct ipc_sk_if *)msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) { + msgb_free(msg); + return 0; + } + goto close; + } + + if ((size_t)rc < sizeof(*ipc_prim)) { + LOGP(DDEV, LOGL_ERROR, + "Received %d bytes on Unix Socket, but primitive size " + "is %zu, discarding\n", + rc, sizeof(*ipc_prim)); + msgb_free(msg); + return 0; + } + + rc = ipc_rx(ipc_prim->msg_type, ipc_prim); + + /* as we always synchronously process the message in IPC_rx() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + ipc_sock_close(&master_sk_state); + return -1; +} + +int IPCDevice::ipc_chan_sock_read(struct osmo_fd *bfd) +{ + struct ipc_sk_chan_if *ipc_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*ipc_prim) + 1000, "ipc_chan_sock_rx"); + if (!msg) + return -ENOMEM; + + ipc_prim = (struct ipc_sk_chan_if *)msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) { + msgb_free(msg); + return 0; + } + goto close; + } + + if ((size_t)rc < sizeof(*ipc_prim)) { + LOGP(DDEV, LOGL_ERROR, + "Received %d bytes on Unix Socket, but primitive size " + "is %zu, discarding\n", + rc, sizeof(*ipc_prim)); + msgb_free(msg); + return 0; + } + + /* store mask of last received messages so we can check later */ + sk_chan_state[bfd->priv_nr].messages_processed_mask |= (1 << (ipc_prim->msg_type - IPC_IF_CHAN_MSG_OFFSET)); + + rc = ipc_chan_rx(ipc_prim->msg_type, ipc_prim, bfd->priv_nr); + + /* as we always synchronously process the message in IPC_rx() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + ipc_sock_close(&sk_chan_state[bfd->priv_nr]); + return -1; +} + +int IPCDevice::ipc_sock_write(struct osmo_fd *bfd) +{ + int rc; + + while (!llist_empty(&master_sk_state.upqueue)) { + struct msgb *msg, *msg2; + struct ipc_sk_if *ipc_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(master_sk_state.upqueue.next, struct msgb, list); + ipc_prim = (struct ipc_sk_if *)msg->data; + + osmo_fd_write_disable(bfd); + + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + if (!msgb_length(msg)) { + LOGP(DDEV, LOGL_ERROR, + "message type (%d) with ZERO " + "bytes!\n", + ipc_prim->msg_type); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + osmo_fd_write_enable(bfd); + break; + } + goto close; + } + + dontsend: + /* _after_ we send it, we can deueue */ + msg2 = msgb_dequeue(&master_sk_state.upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + ipc_sock_close(&master_sk_state); + return -1; +} + +int IPCDevice::ipc_chan_sock_write(struct osmo_fd *bfd) +{ + int rc; + + while (!llist_empty(&sk_chan_state[bfd->priv_nr].upqueue)) { + struct msgb *msg, *msg2; + struct ipc_sk_chan_if *ipc_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(sk_chan_state[bfd->priv_nr].upqueue.next, struct msgb, list); + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + osmo_fd_write_disable(bfd); + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + if (!msgb_length(msg)) { + LOGP(DDEV, LOGL_ERROR, + "message type (%d) with ZERO " + "bytes!\n", + ipc_prim->msg_type); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + osmo_fd_write_enable(bfd); + break; + } + goto close; + } + + dontsend: + /* _after_ we send it, we can dequeue */ + msg2 = msgb_dequeue(&sk_chan_state[bfd->priv_nr].upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + ipc_sock_close(&sk_chan_state[bfd->priv_nr]); + return -1; +} + +static int ipc_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + IPCDevice *device = static_cast<IPCDevice *>(bfd->data); + int rc = 0; + + if (flags & OSMO_FD_READ) + rc = device->ipc_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & OSMO_FD_WRITE) + rc = device->ipc_sock_write(bfd); + + return rc; +} + +static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + IPCDevice *device = static_cast<IPCDevice *>(bfd->data); + int rc = 0; + + if (flags & OSMO_FD_READ) + rc = device->ipc_chan_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & OSMO_FD_WRITE) + rc = device->ipc_chan_sock_write(bfd); + + return rc; +} + +int IPCDevice::open() +{ + std::string k, v; + std::string::size_type keyend; + int rc; + std::string args(cfg->dev_args); + + if ((keyend = args.find('=')) != std::string::npos) { + k = args.substr(0, keyend++); + v = args.substr(keyend); + } + if (k != "ipc_msock" || !v.length()) { + LOGC(DDEV, ERROR) << "Invalid device args provided, expected \"dev-args ipc_msock=/path/to/socket\"\n"; + return -1; + } + + LOGC(DDEV, INFO) << "Opening IPC device" << v << ".."; + + INIT_LLIST_HEAD(&master_sk_state.upqueue); + rc = osmo_sock_unix_init_ofd(&master_sk_state.conn_bfd, SOCK_SEQPACKET, 0, v.c_str(), OSMO_SOCK_F_CONNECT); + if (rc < 0) { + LOGC(DDEV, ERROR) << "Failed to connect to the IPC device (" << v << "). " + << "Retrying...\n"; + osmo_timer_setup(&master_sk_state.timer, ipc_sock_timeout, NULL); + osmo_timer_schedule(&master_sk_state.timer, 5, 0); + return -1; + } + master_sk_state.conn_bfd.cb = ipc_sock_cb; + master_sk_state.conn_bfd.data = this; + + ipc_tx_greeting_req(&master_sk_state, IPC_SOCK_API_VERSION); + /* Wait until confirmation is recieved */ + while (tmp_state != IPC_IF_MSG_GREETING_CNF) + osmo_select_main(0); + + ipc_tx_info_req(&master_sk_state); + /* Wait until confirmation is recieved */ + while (tmp_state != IPC_IF_MSG_INFO_CNF) + osmo_select_main(0); + + ipc_tx_open_req(&master_sk_state, chans, cfg->clock_ref); + /* Wait until confirmation is recieved */ + while (tmp_state != IPC_IF_MSG_OPEN_CNF) + osmo_select_main(0); + LOGC(DDEV, NOTICE) << "Device driver opened successfuly!"; + + /* 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; +} + +void IPCDevice::manually_poll_sock_fds() +{ + struct timeval wait = { 0, 100000 }; + fd_set crfds, cwfds; + char err_buf[256]; + int max_fd = 0; + + FD_ZERO(&crfds); + FD_ZERO(&cwfds); + for (unsigned int i = 0; i < chans; i++) { + struct osmo_fd *curr_fd = &sk_chan_state[i].conn_bfd; + max_fd = curr_fd->fd > max_fd ? curr_fd->fd : max_fd; + + if (curr_fd->when & OSMO_FD_READ) + FD_SET(curr_fd->fd, &crfds); + if (curr_fd->when & OSMO_FD_WRITE) + FD_SET(curr_fd->fd, &cwfds); + } + + if (select(max_fd + 1, &crfds, &cwfds, 0, &wait) < 0) { + LOGP(DDEV, LOGL_ERROR, "select() failed: %s\n", + strerror_r(errno, err_buf, sizeof(err_buf))); + return; + } + + for (unsigned int i = 0; i < chans; i++) { + int flags = 0; + struct osmo_fd *ofd = &sk_chan_state[i].conn_bfd; + + if (FD_ISSET(ofd->fd, &crfds)) { + flags |= OSMO_FD_READ; + FD_CLR(ofd->fd, &crfds); + } + + if (FD_ISSET(ofd->fd, &cwfds)) { + flags |= OSMO_FD_WRITE; + FD_CLR(ofd->fd, &cwfds); + } + if (flags) + ipc_chan_sock_cb(ofd, flags); + } +} + +bool IPCDevice::send_chan_wait_rsp(uint32_t chan, struct msgb *msg_to_send, uint32_t expected_rsp_msg_id) +{ + struct timeval timer_now, timeout; + + sk_chan_state[chan].messages_processed_mask = 0; + ipc_sock_send(&sk_chan_state[chan], msg_to_send); + + gettimeofday(&timeout, 0); + timeout.tv_sec += 2; + + while (!(sk_chan_state[chan].messages_processed_mask & (1 << (expected_rsp_msg_id - IPC_IF_CHAN_MSG_OFFSET)))) { + /* just poll here, we're already in select, so there is no other way to drive + * the fds and "wait" for a response or retry */ + manually_poll_sock_fds(); + + gettimeofday(&timer_now, 0); + if (timercmp(&timer_now, &timeout, >)) + return false; + } + return true; +} + +bool IPCDevice::send_all_chan_wait_rsp(uint32_t msgid_to_send, uint32_t msgid_to_expect) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + struct timeval timer_now, timeout; + + for (unsigned int i = 0; i < chans; i++) { + msg = ipc_msgb_alloc(msgid_to_send); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.start_req.dummy = 0; + + sk_chan_state[i].messages_processed_mask = 0; + ipc_sock_send(&sk_chan_state[i], msg); + } + + gettimeofday(&timeout, 0); + timeout.tv_sec += 2; + + unsigned int msg_received_count = 0; + while (msg_received_count != chans) { + msg_received_count = 0; + + /* just poll here, we're already in select, so there is no other way to drive + * the fds and "wait" for a response or retry */ + manually_poll_sock_fds(); + + for (unsigned int i = 0; i < sk_chan_state.size(); i++) + if (sk_chan_state[i].messages_processed_mask & + (1 << (msgid_to_expect - IPC_IF_CHAN_MSG_OFFSET))) + msg_received_count++; + + gettimeofday(&timer_now, 0); + if (timercmp(&timer_now, &timeout, >)) + return false; + } + + return true; +} + +/* the call stack is rather difficult here, we're already in select: +>~"#0 IPCDevice::start (this=<optimized out>) at IPCDevice.cpp:789\n" +>~"#1 in RadioInterface::start (this=0x614000001640) at radioInterface.cpp:187\n" +>~"#2 in Transceiver::start (this=<optimized out>) at Transceiver.cpp:293\n" +>~"#3 in Transceiver::ctrl_sock_handle_rx (this=0x61600000b180, chan=0) at Transceiver.cpp:838\n" +>~"#4 in Transceiver::ctrl_sock_cb (bfd=<optimized out>, flags=1) at Transceiver.cpp:168\n" +>~"#5 in osmo_fd_disp_fds (_rset=<optimized out>, _wset=<optimized out>, _eset=<optimized out>) at select.c:227\n" +>~"#6 _osmo_select_main (polling=<optimized out>) at select.c:265\n" +>~"#7 in osmo_select_main (polling=128) at select.c:274\n" +>~"#8 in main (argc=<optimized out>, argv=<optimized out>) at osmo-trx.cpp:649\n" + * */ +bool IPCDevice::start() +{ + LOGC(DDEV, INFO) << "starting IPC..."; + + if (started) { + LOGC(DDEV, ERR) << "Device already started"; + return true; + } + + if (!(send_all_chan_wait_rsp(IPC_IF_MSG_START_REQ, IPC_IF_MSG_START_CNF))) { + LOGC(DDEV, ERR) << "start timeout!"; + return false; + } + + int max_bufs_to_flush = 0; + for (unsigned int i = 0; i < shm_dec->num_chans; i++) { + int buf_per_chan = shm_dec->channels[i]->ul_stream->num_buffers; + max_bufs_to_flush = max_bufs_to_flush < buf_per_chan ? buf_per_chan : max_bufs_to_flush; + } + flush_recv(max_bufs_to_flush); + + started = true; + return true; +} + +bool IPCDevice::stop() +{ + if (!started) + return true; + + if (!(send_all_chan_wait_rsp(IPC_IF_MSG_STOP_REQ, IPC_IF_MSG_STOP_CNF))) { + LOGC(DDEV, ERR) << "stop timeout!"; + return false; + } + + 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 IPCDevice::maxRxGain() +{ + return current_info_cnf.chan_info[0].max_rx_gain; +} + +double IPCDevice::minRxGain() +{ + return current_info_cnf.chan_info[0].min_rx_gain; +} + +int IPCDevice::getNominalTxPower(size_t chan) +{ + return current_info_cnf.chan_info[chan].nominal_tx_power; +} + +double IPCDevice::setPowerAttenuation(int atten, size_t chan) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + + if (chan >= chans) + return 0; + + LOGCHAN(chan, DDEV, NOTICE) << "Setting TX attenuation to " << atten << " dB" + << " chan " << chan; + + msg = ipc_msgb_alloc(IPC_IF_MSG_SETTXATTN_REQ); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.txatten_req.attenuation = atten; + + if (!send_chan_wait_rsp(chan, msg, IPC_IF_MSG_SETTXATTN_CNF)) + LOGCHAN(chan, DDEV, ERROR) << "Setting TX attenuation timeout! "; + + return atten; +} + +double IPCDevice::getPowerAttenuation(size_t chan) +{ + if (chan >= chans) + return 0; + + return tx_attenuation[chan]; +} + +double IPCDevice::setRxGain(double dB, size_t chan) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + + if (dB > maxRxGain()) + dB = maxRxGain(); + if (dB < minRxGain()) + dB = minRxGain(); + + LOGCHAN(chan, DDEV, NOTICE) << "Setting RX gain to " << dB << " dB"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_SETGAIN_REQ); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.set_gain_req.is_tx = 0; + ipc_prim->u.set_gain_req.gain = dB; + + if (!send_chan_wait_rsp(chan, msg, IPC_IF_MSG_SETGAIN_CNF)) + LOGCHAN(chan, DDEV, ERROR) << "Setting RX gain timeout! "; + + return rx_gains[chan]; +} + +bool IPCDevice::flush_recv(size_t num_pkts) +{ + std::vector<uint16_t> tmp(4096); + uint64_t tmps; + uint32_t read = 0; + + for (uint32_t j = 0; j < num_pkts; j++) { + for (unsigned int i = 0; i < chans; i++) + read = ipc_shm_read(shm_io_rx_streams[i], (uint16_t *)&tmp.front(), 4096 / 2, &tmps, 3); + } + ts_initial = tmps + read; + + LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl; + return true; +} + +bool IPCDevice::setRxAntenna(const std::string &ant, size_t chan) +{ + return true; +} + +std::string IPCDevice::getRxAntenna(size_t chan) +{ + return ""; +} + +bool IPCDevice::setTxAntenna(const std::string &ant, size_t chan) +{ + return true; +} + +std::string IPCDevice::getTxAntenna(size_t chan) +{ + return ""; +} + +bool IPCDevice::requiresRadioAlign() +{ + return false; +} + +GSM::Time IPCDevice::minLatency() +{ + /* UNUSED */ + return GSM::Time(0, 0); +} + +/** Returns the starting write Timestamp*/ +TIMESTAMP IPCDevice::initialWriteTimestamp(void) +{ + return ts_initial; +} + +/** Returns the starting read Timestamp*/ +TIMESTAMP IPCDevice::initialReadTimestamp(void) +{ + return ts_initial; +} + +// NOTE: Assumes sequential reads +int IPCDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun) +{ + int rc, num_smpls; //, expect_smpls; + ssize_t avail_smpls; + TIMESTAMP expect_timestamp; + unsigned int i; + + if (bufs.size() != chans) { + LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size(); + return -1; + } + + *overrun = false; + *underrun = false; + + timestamp += current_open_cnf.path_delay; + + /* 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; + } + + for (i = 0; i < chans; i++) { + /* Receive samples from HW until we have enough */ + while ((avail_smpls = rx_buffers[i]->avail_smpls(timestamp)) < len) { + uint64_t recv_timestamp = 0; + + thread_enable_cancel(false); + num_smpls = ipc_shm_read(shm_io_rx_streams[i], (uint16_t *)bufs[i], len - avail_smpls, + &recv_timestamp, 1); + expect_timestamp = timestamp + avail_smpls; + thread_enable_cancel(true); + + if (num_smpls == -ETIMEDOUT) + continue; + + LOGCHAN(i, DDEV, DEBUG) + "Received timestamp = " << (TIMESTAMP)recv_timestamp << " (" << num_smpls << ")"; + + if (expect_timestamp != (TIMESTAMP)recv_timestamp) + LOGCHAN(i, DDEV, ERROR) << "Unexpected recv buffer timestamp: expect " + << expect_timestamp << " got " << recv_timestamp << ", diff=" + << ((uint64_t)recv_timestamp > expect_timestamp ? + (uint64_t)recv_timestamp - expect_timestamp : + expect_timestamp - recv_timestamp); + + 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 */ + for (size_t i = 0; i < rx_buffers.size(); i++) { + 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 IPCDevice::writeSamples(std::vector<short *> &bufs, int len, bool *underrun, unsigned long long timestamp) +{ + int rc = 0; + unsigned int i; + + if (bufs.size() != chans) { + LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size(); + return -1; + } + + *underrun = false; + + for (i = 0; i < chans; i++) { + LOGCHAN(i, DDEV, DEBUG) << "send buffer of len " << len << " timestamp " << std::hex << timestamp; + thread_enable_cancel(false); + rc = ipc_shm_enqueue(shm_io_tx_streams[i], timestamp, len, (uint16_t *)bufs[i]); + thread_enable_cancel(true); + + if (rc != len) { + LOGCHAN(i, DDEV, ERROR) << "LMS: Device Tx timed out (" << rc << " vs exp " << len << ")."; + return -1; + } + } + + return rc; +} + +bool IPCDevice::updateAlignment(TIMESTAMP timestamp) +{ + return true; +} + +bool IPCDevice::setTxFreq(double wFreq, size_t chan) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_SETFREQ_REQ); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.set_freq_req.is_tx = 1; + ipc_prim->u.set_freq_req.freq = wFreq; + + return send_chan_wait_rsp(chan, msg, IPC_IF_MSG_SETFREQ_CNF); +} + +bool IPCDevice::setRxFreq(double wFreq, size_t chan) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_SETFREQ_REQ); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.set_freq_req.is_tx = 0; + ipc_prim->u.set_freq_req.freq = wFreq; + + return send_chan_wait_rsp(chan, msg, IPC_IF_MSG_SETFREQ_CNF); +} + +RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg) +{ + if (cfg->tx_sps != cfg->rx_sps) { + LOGC(DDEV, ERROR) << "IPC Requires tx_sps == rx_sps"; + return NULL; + } + if (cfg->offset != 0.0) { + LOGC(DDEV, ERROR) << "IPC doesn't support lo_offset"; + return NULL; + } + return new IPCDevice(type, cfg); +} diff --git a/Transceiver52M/device/ipc/IPCDevice.h b/Transceiver52M/device/ipc/IPCDevice.h new file mode 100644 index 0000000..0cf3d46 --- /dev/null +++ b/Transceiver52M/device/ipc/IPCDevice.h @@ -0,0 +1,241 @@ +/* +* 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 <cstdint> +#include <cstddef> +#include <climits> +#include <string> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +extern "C" { +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include "shm.h" +} + +#include "radioDevice.h" + +class smpl_buf; + +#define IPC_MAX_NUM_TRX 8 + +struct ipc_per_trx_sock_state { + struct osmo_fd conn_bfd; /* fd for connection to the BTS */ + struct osmo_timer_list timer; /* socket connect retry timer */ + struct llist_head upqueue; /* queue for sending messages */ + uint32_t messages_processed_mask; // (=| IPC_IF_MSG_xxx-IPC_IF_CHAN_MSG_OFFSET) bitmask + ipc_per_trx_sock_state() : conn_bfd(), timer(), upqueue(), messages_processed_mask() + { + conn_bfd.fd = -1; + } +}; + +class IPCDevice : public RadioDevice { + protected: + struct ipc_per_trx_sock_state master_sk_state; + + std::vector<struct ipc_per_trx_sock_state> sk_chan_state; + + uint32_t tx_attenuation[IPC_MAX_NUM_TRX]; + uint8_t tmp_state; + char shm_name[SHM_NAME_MAX]; + int ipc_shm_connect(const char *shm_name); + void *shm; + struct ipc_shm_region *shm_dec; + + std::vector<smpl_buf *> rx_buffers; + double actualSampleRate; + + bool started; + + TIMESTAMP ts_initial, ts_offset; + + std::vector<double> tx_gains, rx_gains; + + struct ipc_sk_if_info_req current_info_req; + struct ipc_sk_if_info_cnf current_info_cnf; + struct ipc_sk_if_open_cnf current_open_cnf; + + std::vector<struct ipc_shm_io *> shm_io_rx_streams; + std::vector<struct ipc_shm_io *> shm_io_tx_streams; + + 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); + void manually_poll_sock_fds(); + + void ipc_sock_close(ipc_per_trx_sock_state *state); + + int ipc_rx(uint8_t msg_type, struct ipc_sk_if *ipc_prim); + int ipc_rx_greeting_cnf(const struct ipc_sk_if_greeting *greeting_cnf); + int ipc_rx_info_cnf(const struct ipc_sk_if_info_cnf *info_cnf); + int ipc_rx_open_cnf(const struct ipc_sk_if_open_cnf *open_cnf); + int ipc_tx_open_req(struct ipc_per_trx_sock_state *state, uint32_t num_chans, uint32_t ref); + + int ipc_chan_rx(uint8_t msg_type, ipc_sk_chan_if *ipc_prim, uint8_t chan_nr); + int ipc_rx_chan_start_cnf(ipc_sk_chan_if_op_rc *ret, uint8_t chan_nr); + int ipc_rx_chan_stop_cnf(ipc_sk_chan_if_op_rc *ret, uint8_t chan_nr); + int ipc_rx_chan_setgain_cnf(ipc_sk_chan_if_gain *ret, uint8_t chan_nr); + int ipc_rx_chan_setfreq_cnf(ipc_sk_chan_if_freq_cnf *ret, uint8_t chan_nr); + int ipc_rx_chan_notify_underflow(ipc_sk_chan_if_notfiy *ret, uint8_t chan_nr); + int ipc_rx_chan_notify_overflow(ipc_sk_chan_if_notfiy *ret, uint8_t chan_nr); + int ipc_rx_chan_settxattn_cnf(ipc_sk_chan_if_tx_attenuation *ret, uint8_t chan_nr); + + 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: + int ipc_sock_read(struct osmo_fd *bfd); + int ipc_sock_write(struct osmo_fd *bfd); + int ipc_chan_sock_read(osmo_fd *bfd); + int ipc_chan_sock_write(osmo_fd *bfd); + + /** Object constructor */ + IPCDevice(InterfaceType iface, const struct trx_cfg *cfg); + virtual ~IPCDevice() override; + + /** Instantiate the IPC */ + virtual int open() 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_LMS1; + } + + /** + 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 * current_info_cnf.iq_scaling_val_rx; + } + + /** returns the full-scale receive amplitude **/ + virtual double fullScaleOutputValue() override + { + return (double)SHRT_MAX * current_info_cnf.iq_scaling_val_tx; + } + + /** 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_ diff --git a/Transceiver52M/device/ipc/Makefile.am b/Transceiver52M/device/ipc/Makefile.am new file mode 100644 index 0000000..c08621f --- /dev/null +++ b/Transceiver52M/device/ipc/Makefile.am @@ -0,0 +1,34 @@ +include $(top_srcdir)/Makefile.common + +AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(UHD_CFLAGS) +AM_CXXFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(UHD_CFLAGS) + +noinst_HEADERS = IPCDevice.h shm.h ipc_shm.h ipc_chan.h ipc_sock.h + +if DEVICE_UHD +noinst_HEADERS += ../uhd/UHDDevice.h uhdwrap.h ipc-driver-test.h +endif + +noinst_LTLIBRARIES = libdevice.la + +libdevice_la_SOURCES = IPCDevice.cpp shm.c ipc_shm.c ipc_chan.c ipc_sock.c +libdevice_la_CPPFLAGS = $(AM_CPPFLAGS) -DIPCMAGIC +libdevice_la_LIBADD = \ + $(top_builddir)/Transceiver52M/device/common/libdevice_common.la \ + -lpthread \ + -lrt \ + $(NULL) + +if DEVICE_UHD + +bin_PROGRAMS = ipc-driver-test +#ipc_driver_test_SHORTNAME = drvt +ipc_driver_test_SOURCES = ipc-driver-test.c uhdwrap.cpp ../uhd/UHDDevice.cpp +ipc_driver_test_LDADD = \ + libdevice.la \ + $(COMMON_LA) \ + $(LIBOSMOCORE_LIBS) \ + $(UHD_LIBS) \ + $(NULL) +endif diff --git a/Transceiver52M/device/ipc/ipc-driver-test.c b/Transceiver52M/device/ipc/ipc-driver-test.c new file mode 100644 index 0000000..d8284c7 --- /dev/null +++ b/Transceiver52M/device/ipc/ipc-driver-test.c @@ -0,0 +1,492 @@ +/* +* 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 <getopt.h> + +#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 struct ipc_shm_region *decoded_region; + +static struct { + int msocknum; + char *ud_prefix_dir; +} cmdline_cfg; + +static const struct log_info_cat default_categories[] = { + [DMAIN] = { + .name = "DMAIN", + .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), +}; + +#include "uhdwrap.h" + +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_greeting_cnf(uint8_t req_version) +{ + 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 = req_version; + + return ipc_sock_send(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; + + uhdwrap_fill_info_cnf(ipc_prim); + + 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) +{ + if (greeting_req->req_version == IPC_SOCK_API_VERSION) + ipc_tx_greeting_cnf(IPC_SOCK_API_VERSION); + else + ipc_tx_greeting_cnf(0); + 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 = uhdwrap_get_bufsizerx(global_dev) * 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, uhdwrap_get_timingoffset(global_dev)); + 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); + 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); + } + 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); + + /* 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; + + /* 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; + + 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; + + 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; + + 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; +} + +static void print_help(void) +{ + printf("ipc-driver-test Usage:\n" + " -h --help This message\n" + " -u --unix-sk-dir DIR Existing directory where to create the Master socket\n" + " -n --sock-num NR Master socket suffix number NR\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + const struct option long_options[] = { { "help", 0, 0, 'h' }, + { "unix-sk-dir", 1, 0, 'u' }, + { "sock-num", 1, 0, 'n' }, + { 0, 0, 0, 0 } }; + + c = getopt_long(argc, argv, "hu:n:", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(); + exit(0); + break; + case 'u': + cmdline_cfg.ud_prefix_dir = talloc_strdup(tall_ctx, optarg); + break; + case 'n': + cmdline_cfg.msocknum = atoi(optarg); + break; + + default: + exit(2); + break; + } + } + + if (argc > optind) { + fprintf(stderr, "Unsupported positional arguments on command line\n"); + exit(2); + } +} + +int main(int argc, char **argv) +{ + char ipc_msock_path[128]; + tall_ctx = talloc_named_const(NULL, 0, "OsmoTRX"); + msgb_talloc_ctx_init(tall_ctx, 0); + osmo_init_logging2(tall_ctx, &log_infox); + log_enable_multithread(); + + handle_options(argc, argv); + + if (!cmdline_cfg.ud_prefix_dir) + cmdline_cfg.ud_prefix_dir = talloc_strdup(tall_ctx, IPC_SOCK_PATH_PREFIX); + + + snprintf(ipc_msock_path, sizeof(ipc_msock_path), "%s/ipc_sock%d", cmdline_cfg.ud_prefix_dir, cmdline_cfg.msocknum); + + LOGP(DMAIN, LOGL_INFO, "Starting %s\n", argv[0]); + ipc_sock_init(ipc_msock_path, &global_ipc_sock_state, ipc_sock_accept, 0); + 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); + return 0; +} diff --git a/Transceiver52M/device/ipc/ipc-driver-test.h b/Transceiver52M/device/ipc/ipc-driver-test.h new file mode 100644 index 0000000..d5b4591 --- /dev/null +++ b/Transceiver52M/device/ipc/ipc-driver-test.h @@ -0,0 +1,45 @@ +/* +* 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. +*/ +#pragma once + +#include <osmocom/core/select.h> +#include "shm.h" + +extern struct ipc_sock_state *global_ipc_sock_state; + +/* 8 channels are plenty */ +extern struct ipc_sock_state *global_ctrl_socks[8]; +extern struct ipc_shm_io *ios_tx_to_device[8]; +extern struct ipc_shm_io *ios_rx_from_device[8]; + +struct ipc_sock_state { + struct osmo_fd listen_bfd; /* fd for listen socket */ + struct osmo_fd conn_bfd; /* fd for connection */ + struct llist_head upqueue; /* queue for sending messages */ +}; + +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); + +int ipc_rx_greeting_req(struct ipc_sk_if_greeting *greeting_req); +int ipc_rx_info_req(struct ipc_sk_if_info_req *info_req); +int ipc_rx_open_req(struct ipc_sk_if_open_req *open_req); + +int ipc_rx_chan_start_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr); +int ipc_rx_chan_stop_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr); +int ipc_rx_chan_setgain_req(struct ipc_sk_chan_if_gain *req, uint8_t chan_nr); +int ipc_rx_chan_setfreq_req(struct ipc_sk_chan_if_freq_req *req, uint8_t chan_nr); +int ipc_rx_chan_settxatten_req(struct ipc_sk_chan_if_tx_attenuation *req, uint8_t chan_nr); diff --git a/Transceiver52M/device/ipc/ipc_chan.c b/Transceiver52M/device/ipc/ipc_chan.c new file mode 100644 index 0000000..2a6f490 --- /dev/null +++ b/Transceiver52M/device/ipc/ipc_chan.c @@ -0,0 +1,254 @@ +/* +* 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. +*/ +#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 <debug.h> +#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-driver-test.h" +#include "ipc_chan.h" +#include "ipc_sock.h" + +static int ipc_chan_rx(uint8_t msg_type, struct ipc_sk_chan_if *ipc_prim, uint8_t chan_nr) +{ + int rc = 0; + + switch (msg_type) { + case IPC_IF_MSG_START_REQ: + rc = ipc_rx_chan_start_req(&ipc_prim->u.start_req, chan_nr); + break; + case IPC_IF_MSG_STOP_REQ: + rc = ipc_rx_chan_stop_req(&ipc_prim->u.stop_req, chan_nr); + break; + case IPC_IF_MSG_SETGAIN_REQ: + rc = ipc_rx_chan_setgain_req(&ipc_prim->u.set_gain_req, chan_nr); + break; + case IPC_IF_MSG_SETFREQ_REQ: + rc = ipc_rx_chan_setfreq_req(&ipc_prim->u.set_freq_req, chan_nr); + break; + case IPC_IF_MSG_SETTXATTN_REQ: + rc = ipc_rx_chan_settxatten_req(&ipc_prim->u.txatten_req, chan_nr); + break; + default: + LOGP(DDEV, LOGL_ERROR, "Received unknown IPC msg type 0x%02x on chan %d\n", msg_type, chan_nr); + rc = -EINVAL; + } + + return rc; +} + +static int ipc_chan_sock_read(struct osmo_fd *bfd) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + struct ipc_sk_chan_if *ipc_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*ipc_prim) + 1000, "ipc_chan_sock_rx"); + if (!msg) + return -ENOMEM; + + ipc_prim = (struct ipc_sk_chan_if *)msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) { + msgb_free(msg); + return 0; + } + goto close; + } + + if (rc < (int)sizeof(*ipc_prim)) { + LOGP(DDEV, LOGL_ERROR, + "Received %d bytes on Unix Socket, but primitive size " + "is %zu, discarding\n", + rc, sizeof(*ipc_prim)); + msgb_free(msg); + return 0; + } + + rc = ipc_chan_rx(ipc_prim->msg_type, ipc_prim, bfd->priv_nr); + + /* as we always synchronously process the message in IPC_rx() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + ipc_sock_close(state); + return -1; +} + +int ipc_chan_sock_send(struct msgb *msg, uint8_t chan_nr) +{ + struct ipc_sock_state *state = global_ctrl_socks[chan_nr]; + struct osmo_fd *conn_bfd; + + if (!state) + return -EINVAL; + + if (!state) { + LOGP(DDEV, LOGL_INFO, + "IPC socket not created, " + "dropping message\n"); + msgb_free(msg); + return -EINVAL; + } + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd <= 0) { + LOGP(DDEV, LOGL_NOTICE, + "IPC socket not connected, " + "dropping message\n"); + msgb_free(msg); + return -EIO; + } + msgb_enqueue(&state->upqueue, msg); + osmo_fd_write_enable(conn_bfd); + + return 0; +} + +static int ipc_chan_sock_write(struct osmo_fd *bfd) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + int rc; + + while (!llist_empty(&state->upqueue)) { + struct msgb *msg, *msg2; + struct ipc_sk_chan_if *ipc_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(state->upqueue.next, struct msgb, list); + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + + osmo_fd_write_disable(bfd); + + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + if (!msgb_length(msg)) { + LOGP(DDEV, LOGL_ERROR, + "message type (%d) with ZERO " + "bytes!\n", + ipc_prim->msg_type); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + osmo_fd_write_enable(bfd); + break; + } + goto close; + } + + dontsend: + /* _after_ we send it, we can dequeue */ + msg2 = msgb_dequeue(&state->upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + ipc_sock_close(state); + return -1; +} + +static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + int rc = 0; + + if (flags & OSMO_FD_READ) + rc = ipc_chan_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & OSMO_FD_WRITE) + rc = ipc_chan_sock_write(bfd); + + return rc; +} + +int ipc_chan_sock_accept(struct osmo_fd *bfd, unsigned int flags) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + struct osmo_fd *conn_bfd = &state->conn_bfd; + struct sockaddr_un un_addr; + socklen_t len; + int rc; + + len = sizeof(un_addr); + rc = accept(bfd->fd, (struct sockaddr *)&un_addr, &len); + if (rc < 0) { + LOGP(DDEV, LOGL_ERROR, "Failed to accept a new connection\n"); + return -1; + } + + if (conn_bfd->fd >= 0) { + LOGP(DDEV, LOGL_NOTICE, + "osmo-trx connects but we already have " + "another active connection ?!?\n"); + /* We already have one IPC connected, this is all we support */ + osmo_fd_read_disable(&state->listen_bfd); + close(rc); + return 0; + } + + /* copy chan nr, required for proper bfd<->chan # mapping */ + osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, ipc_chan_sock_cb, state, bfd->priv_nr); + + if (osmo_fd_register(conn_bfd) != 0) { + LOGP(DDEV, LOGL_ERROR, + "Failed to register new connection " + "fd\n"); + close(conn_bfd->fd); + conn_bfd->fd = -1; + return -1; + } + + LOGP(DDEV, LOGL_NOTICE, "Unix socket connected to external osmo-trx\n"); + + return 0; +} diff --git a/Transceiver52M/device/ipc/ipc_chan.h b/Transceiver52M/device/ipc/ipc_chan.h new file mode 100644 index 0000000..d606cce --- /dev/null +++ b/Transceiver52M/device/ipc/ipc_chan.h @@ -0,0 +1,25 @@ +/* +* 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. +*/ +#ifndef IPC_CHAN_H +#define IPC_CHAN_H + +#include "shm.h" +#include "ipc-driver-test.h" + +int ipc_chan_sock_send(struct msgb *msg, uint8_t chan_nr); +int ipc_chan_sock_accept(struct osmo_fd *bfd, unsigned int flags); + +#endif // IPC_CHAN_H diff --git a/Transceiver52M/device/ipc/ipc_shm.c b/Transceiver52M/device/ipc/ipc_shm.c new file mode 100644 index 0000000..ff70975 --- /dev/null +++ b/Transceiver52M/device/ipc/ipc_shm.c @@ -0,0 +1,200 @@ +/* +* 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. +*/ +#ifdef __cplusplus +extern "C" { +#endif + +#include <shm.h> +#include "ipc_shm.h" +#include <pthread.h> +#include <semaphore.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <osmocom/core/panic.h> + +#include <debug.h> + +#ifdef __cplusplus +} +#endif + +#define SAMPLE_SIZE_BYTE (sizeof(uint16_t) * 2) + +struct ipc_shm_io *ipc_shm_init_consumer(struct ipc_shm_stream *s) +{ + unsigned int i; + + struct ipc_shm_io *r = (struct ipc_shm_io *)malloc(sizeof(struct ipc_shm_io)); + r->this_stream = s->raw; + r->buf_ptrs = + (volatile struct ipc_shm_raw_smpl_buf **)malloc(sizeof(struct ipc_shm_raw_smpl_buf *) * s->num_buffers); + + /* save actual ptrs */ + for (i = 0; i < s->num_buffers; i++) + r->buf_ptrs[i] = s->buffers[i]; + + r->partial_read_begin_ptr = 0; + return r; +} + +struct ipc_shm_io *ipc_shm_init_producer(struct ipc_shm_stream *s) +{ + int rv; + pthread_mutexattr_t att; + pthread_condattr_t t1, t2; + struct ipc_shm_io *r = ipc_shm_init_consumer(s); + rv = pthread_mutexattr_init(&att); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + rv = pthread_mutexattr_setrobust(&att, PTHREAD_MUTEX_ROBUST); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + rv = pthread_mutexattr_setpshared(&att, PTHREAD_PROCESS_SHARED); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + rv = pthread_mutex_init((pthread_mutex_t *)&r->this_stream->lock, &att); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + pthread_mutexattr_destroy(&att); + + rv = pthread_condattr_setpshared(&t1, PTHREAD_PROCESS_SHARED); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + rv = pthread_condattr_setpshared(&t2, PTHREAD_PROCESS_SHARED); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + rv = pthread_cond_init((pthread_cond_t *)&r->this_stream->cf, &t1); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + rv = pthread_cond_init((pthread_cond_t *)&r->this_stream->ce, &t2); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + pthread_condattr_destroy(&t1); + pthread_condattr_destroy(&t2); + + r->this_stream->read_next = 0; + r->this_stream->write_next = 0; + return r; +} + +void ipc_shm_close(struct ipc_shm_io *r) +{ + if (r) { + free(r->buf_ptrs); + free(r); + } +} + +int32_t ipc_shm_enqueue(struct ipc_shm_io *r, uint64_t timestamp, uint32_t len_in_sps, uint16_t *data) +{ + volatile struct ipc_shm_raw_smpl_buf *buf; + int32_t rv; + struct timespec tv; + clock_gettime(CLOCK_REALTIME, &tv); + tv.tv_sec += 1; + + rv = pthread_mutex_timedlock((pthread_mutex_t *)&r->this_stream->lock, &tv); + if (rv != 0) + return -rv; + + while (((r->this_stream->write_next + 1) & (r->this_stream->num_buffers - 1)) == r->this_stream->read_next && + rv == 0) + rv = pthread_cond_timedwait((pthread_cond_t *)&r->this_stream->ce, + (pthread_mutex_t *)&r->this_stream->lock, &tv); + if (rv != 0) + return -rv; + + buf = r->buf_ptrs[r->this_stream->write_next]; + buf->timestamp = timestamp; + + rv = len_in_sps <= r->this_stream->buffer_size ? len_in_sps : r->this_stream->buffer_size; + + memcpy((void *)buf->samples, data, SAMPLE_SIZE_BYTE * rv); + buf->data_len = rv; + + r->this_stream->write_next = (r->this_stream->write_next + 1) & (r->this_stream->num_buffers - 1); + + pthread_cond_signal((pthread_cond_t *)&r->this_stream->cf); + pthread_mutex_unlock((pthread_mutex_t *)&r->this_stream->lock); + + return rv; +} + +int32_t ipc_shm_read(struct ipc_shm_io *r, uint16_t *out_buf, uint32_t num_samples, uint64_t *timestamp, + uint32_t timeout_seconds) +{ + volatile struct ipc_shm_raw_smpl_buf *buf; + int32_t rv; + uint8_t freeflag = 0; + struct timespec tv; + clock_gettime(CLOCK_REALTIME, &tv); + tv.tv_sec += timeout_seconds; + + rv = pthread_mutex_timedlock((pthread_mutex_t *)&r->this_stream->lock, &tv); + if (rv != 0) + return -rv; + + while (r->this_stream->write_next == r->this_stream->read_next && rv == 0) + rv = pthread_cond_timedwait((pthread_cond_t *)&r->this_stream->cf, + (pthread_mutex_t *)&r->this_stream->lock, &tv); + if (rv != 0) + return -rv; + + buf = r->buf_ptrs[r->this_stream->read_next]; + if (buf->data_len <= num_samples) { + memcpy(out_buf, (void *)&buf->samples[r->partial_read_begin_ptr * 2], SAMPLE_SIZE_BYTE * buf->data_len); + r->partial_read_begin_ptr = 0; + rv = buf->data_len; + buf->data_len = 0; + r->this_stream->read_next = (r->this_stream->read_next + 1) & (r->this_stream->num_buffers - 1); + freeflag = 1; + + } else /*if (buf->data_len > num_samples)*/ { + memcpy(out_buf, (void *)&buf->samples[r->partial_read_begin_ptr * 2], SAMPLE_SIZE_BYTE * num_samples); + r->partial_read_begin_ptr += num_samples; + buf->data_len -= num_samples; + rv = num_samples; + } + + *timestamp = buf->timestamp; + buf->timestamp += rv; + + if (freeflag) + pthread_cond_signal((pthread_cond_t *)&r->this_stream->ce); + + pthread_mutex_unlock((pthread_mutex_t *)&r->this_stream->lock); + + return rv; +} diff --git a/Transceiver52M/device/ipc/ipc_shm.h b/Transceiver52M/device/ipc/ipc_shm.h new file mode 100644 index 0000000..348e703 --- /dev/null +++ b/Transceiver52M/device/ipc/ipc_shm.h @@ -0,0 +1,45 @@ +/* +* 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_SHM_H +#define IPC_SHM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <shm.h> +#include <stdint.h> + +#ifdef __cplusplus +} +#endif + +struct ipc_shm_io { + volatile struct ipc_shm_raw_stream *this_stream; + volatile struct ipc_shm_raw_smpl_buf **volatile buf_ptrs; + uint32_t partial_read_begin_ptr; +}; + +struct ipc_shm_io *ipc_shm_init_consumer(struct ipc_shm_stream *s); +struct ipc_shm_io *ipc_shm_init_producer(struct ipc_shm_stream *s); +void ipc_shm_close(struct ipc_shm_io *r); +int32_t ipc_shm_enqueue(struct ipc_shm_io *r, uint64_t timestamp, uint32_t len_in_sps, uint16_t *data); +int32_t ipc_shm_tryenqueue(struct ipc_shm_io *r, uint64_t timestamp, uint32_t len_in_sps, uint16_t *data); +volatile struct ipc_shm_raw_smpl_buf *ipc_shm_dequeue(struct ipc_shm_io *r); +int32_t ipc_shm_read(struct ipc_shm_io *r, uint16_t *out_buf, uint32_t num_samples, uint64_t *timestamp, + uint32_t timeout_seconds); + +#endif // IPC_SHM_H diff --git a/Transceiver52M/device/ipc/ipc_sock.c b/Transceiver52M/device/ipc/ipc_sock.c new file mode 100644 index 0000000..9e8ab82 --- /dev/null +++ b/Transceiver52M/device/ipc/ipc_sock.c @@ -0,0 +1,266 @@ +/* +* 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. +*/ +#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 <debug.h> +#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-driver-test.h" + +extern volatile int ipc_exit_requested; +static int ipc_rx(uint8_t msg_type, struct ipc_sk_if *ipc_prim) +{ + int rc = 0; + + switch (msg_type) { + case IPC_IF_MSG_GREETING_REQ: + rc = ipc_rx_greeting_req(&ipc_prim->u.greeting_req); + break; + case IPC_IF_MSG_INFO_REQ: + rc = ipc_rx_info_req(&ipc_prim->u.info_req); + break; + case IPC_IF_MSG_OPEN_REQ: + rc = ipc_rx_open_req(&ipc_prim->u.open_req); + break; + default: + LOGP(DDEV, LOGL_ERROR, "Received unknown IPC msg type 0x%02x\n", msg_type); + rc = -EINVAL; + } + + return rc; +} + +int ipc_sock_send(struct msgb *msg) +{ + struct ipc_sock_state *state = global_ipc_sock_state; + struct osmo_fd *conn_bfd; + + if (!state) { + LOGP(DDEV, LOGL_INFO, + "IPC socket not created, " + "dropping message\n"); + msgb_free(msg); + return -EINVAL; + } + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd <= 0) { + LOGP(DDEV, LOGL_NOTICE, + "IPC socket not connected, " + "dropping message\n"); + msgb_free(msg); + return -EIO; + } + msgb_enqueue(&state->upqueue, msg); + osmo_fd_write_enable(conn_bfd); + + return 0; +} + +void ipc_sock_close(struct ipc_sock_state *state) +{ + struct osmo_fd *bfd = &state->conn_bfd; + + LOGP(DDEV, LOGL_NOTICE, "IPC socket has LOST connection\n"); + + ipc_exit_requested = 1; + + osmo_fd_unregister(bfd); + close(bfd->fd); + bfd->fd = -1; + + /* re-enable the generation of ACCEPT for new connections */ + osmo_fd_read_enable(&state->listen_bfd); + + /* flush the queue */ + while (!llist_empty(&state->upqueue)) { + struct msgb *msg = msgb_dequeue(&state->upqueue); + msgb_free(msg); + } +} + +int ipc_sock_read(struct osmo_fd *bfd) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + struct ipc_sk_if *ipc_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*ipc_prim) + 1000, "ipc_sock_rx"); + if (!msg) + return -ENOMEM; + + ipc_prim = (struct ipc_sk_if *)msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) { + msgb_free(msg); + return 0; + } + goto close; + } + + if (rc < (int)sizeof(*ipc_prim)) { + LOGP(DDEV, LOGL_ERROR, + "Received %d bytes on Unix Socket, but primitive size " + "is %zu, discarding\n", + rc, sizeof(*ipc_prim)); + msgb_free(msg); + return 0; + } + + rc = ipc_rx(ipc_prim->msg_type, ipc_prim); + + /* as we always synchronously process the message in IPC_rx() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + ipc_sock_close(state); + return -1; +} + +static int ipc_sock_write(struct osmo_fd *bfd) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + int rc; + + while (!llist_empty(&state->upqueue)) { + struct msgb *msg, *msg2; + struct ipc_sk_if *ipc_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(state->upqueue.next, struct msgb, list); + ipc_prim = (struct ipc_sk_if *)msg->data; + + osmo_fd_write_disable(bfd); + + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + if (!msgb_length(msg)) { + LOGP(DDEV, LOGL_ERROR, + "message type (%d) with ZERO " + "bytes!\n", + ipc_prim->msg_type); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + osmo_fd_write_enable(bfd); + break; + } + goto close; + } + + dontsend: + /* _after_ we send it, we can deueue */ + msg2 = msgb_dequeue(&state->upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + ipc_sock_close(state); + return -1; +} + +static int ipc_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + int rc = 0; + + if (flags & OSMO_FD_READ) + rc = ipc_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & OSMO_FD_WRITE) + rc = ipc_sock_write(bfd); + + return rc; +} + +/* accept connection coming from IPC */ +int ipc_sock_accept(struct osmo_fd *bfd, unsigned int flags) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + struct osmo_fd *conn_bfd = &state->conn_bfd; + struct sockaddr_un un_addr; + socklen_t len; + int rc; + + len = sizeof(un_addr); + rc = accept(bfd->fd, (struct sockaddr *)&un_addr, &len); + if (rc < 0) { + LOGP(DDEV, LOGL_ERROR, "Failed to accept a new connection\n"); + return -1; + } + + if (conn_bfd->fd >= 0) { + LOGP(DDEV, LOGL_NOTICE, + "ip clent connects but we already have " + "another active connection ?!?\n"); + /* We already have one IPC connected, this is all we support */ + osmo_fd_read_disable(&state->listen_bfd); + close(rc); + return 0; + } + + osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, ipc_sock_cb, state, 0); + + if (osmo_fd_register(conn_bfd) != 0) { + LOGP(DDEV, LOGL_ERROR, + "Failed to register new connection " + "fd\n"); + close(conn_bfd->fd); + conn_bfd->fd = -1; + return -1; + } + + LOGP(DDEV, LOGL_NOTICE, "Unix socket connected to external osmo-trx\n"); + + return 0; +} diff --git a/Transceiver52M/device/ipc/ipc_sock.h b/Transceiver52M/device/ipc/ipc_sock.h new file mode 100644 index 0000000..918e70a --- /dev/null +++ b/Transceiver52M/device/ipc/ipc_sock.h @@ -0,0 +1,26 @@ +/* +* 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. +*/ +#ifndef IPC_SOCK_H +#define IPC_SOCK_H + +#include "shm.h" +#include "ipc-driver-test.h" + +int ipc_sock_send(struct msgb *msg); +int ipc_sock_accept(struct osmo_fd *bfd, unsigned int flags); +void ipc_sock_close(struct ipc_sock_state *state); + +#endif // IPC_SOCK_H diff --git a/Transceiver52M/device/ipc/shm.c b/Transceiver52M/device/ipc/shm.c new file mode 100644 index 0000000..1e78b97 --- /dev/null +++ b/Transceiver52M/device/ipc/shm.c @@ -0,0 +1,149 @@ +/* +* 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. +*/ + +#include <stdint.h> +#include <stddef.h> +#include <osmocom/core/talloc.h> + +#include "shm.h" + +#define ENCDECDEBUG(...) //fprintf(stderr, __VA_ARGS__) + +/* Convert offsets to pointers */ +struct ipc_shm_stream *ipc_shm_decode_stream(void *tall_ctx, struct ipc_shm_raw_region *root_raw, + struct ipc_shm_raw_stream *stream_raw) +{ + unsigned int i; + struct ipc_shm_stream *stream; + stream = talloc_zero(tall_ctx, struct ipc_shm_stream); + stream = talloc_zero_size(tall_ctx, sizeof(struct ipc_shm_stream) + + sizeof(struct ipc_shm_raw_smpl_buf *) * stream_raw->num_buffers); + if (!stream) + return NULL; + stream->num_buffers = stream_raw->num_buffers; + stream->buffer_size = stream_raw->buffer_size; + stream->raw = stream_raw; + for (i = 0; i < stream->num_buffers; i++) { + ENCDECDEBUG("decode: smpl_buf %d at offset %u\n", i, stream_raw->buffer_offset[i]); + stream->buffers[i] = + (struct ipc_shm_raw_smpl_buf *)(((uint8_t *)root_raw) + stream_raw->buffer_offset[i]); + } + return stream; +} + +struct ipc_shm_channel *ipc_shm_decode_channel(void *tall_ctx, struct ipc_shm_raw_region *root_raw, + struct ipc_shm_raw_channel *chan_raw) +{ + struct ipc_shm_channel *chan; + chan = talloc_zero(tall_ctx, struct ipc_shm_channel); + if (!chan) + return NULL; + ENCDECDEBUG("decode: streams at offset %u and %u\n", chan_raw->dl_buf_offset, chan_raw->ul_buf_offset); + chan->dl_stream = ipc_shm_decode_stream( + chan, root_raw, (struct ipc_shm_raw_stream *)(((uint8_t *)root_raw) + chan_raw->dl_buf_offset)); + chan->ul_stream = ipc_shm_decode_stream( + chan, root_raw, (struct ipc_shm_raw_stream *)(((uint8_t *)root_raw) + chan_raw->ul_buf_offset)); + return chan; +} +struct ipc_shm_region *ipc_shm_decode_region(void *tall_ctx, struct ipc_shm_raw_region *root_raw) +{ + unsigned int i; + struct ipc_shm_region *root; + root = talloc_zero_size(tall_ctx, + sizeof(struct ipc_shm_region) + sizeof(struct ipc_shm_channel *) * root_raw->num_chans); + if (!root) + return NULL; + + root->num_chans = root_raw->num_chans; + for (i = 0; i < root->num_chans; i++) { + ENCDECDEBUG("decode: channel %d at offset %u\n", i, root_raw->chan_offset[i]); + root->channels[i] = ipc_shm_decode_channel( + root, root_raw, + (struct ipc_shm_raw_channel *)(((uint8_t *)root_raw) + root_raw->chan_offset[i])); + } + return root; +} + +unsigned int ipc_shm_encode_smpl_buf(struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_smpl_buf *smpl_buf_raw, + uint32_t buffer_size) +{ + unsigned int offset = sizeof(struct ipc_shm_raw_smpl_buf); + offset = (((uintptr_t)offset + 7) & ~0x07ULL); + ENCDECDEBUG("encode: smpl_buf at offset %u\n", offset); + offset += buffer_size * sizeof(uint16_t) * 2; /* samples */ + return offset; +} + +unsigned int ipc_shm_encode_stream(struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_stream *stream_raw, + uint32_t num_buffers, uint32_t buffer_size) +{ + unsigned int i; + ptrdiff_t start = (ptrdiff_t)stream_raw; + unsigned int offset = sizeof(struct ipc_shm_raw_stream) + sizeof(uint32_t) * num_buffers; + offset = (((uintptr_t)offset + 7) & ~0x07ULL); + ENCDECDEBUG("encode: stream at offset %lu\n", (start - (ptrdiff_t)root_raw)); + if (root_raw) { + stream_raw->num_buffers = num_buffers; + stream_raw->buffer_size = buffer_size; + stream_raw->read_next = 0; + stream_raw->write_next = 0; + } + for (i = 0; i < num_buffers; i++) { + if (root_raw) + stream_raw->buffer_offset[i] = (start + offset - (ptrdiff_t)root_raw); + offset += + ipc_shm_encode_smpl_buf(root_raw, (struct ipc_shm_raw_smpl_buf *)(start + offset), buffer_size); + } + return offset; +} +unsigned int ipc_shm_encode_channel(struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_channel *chan_raw, + uint32_t num_buffers, uint32_t buffer_size) +{ + uint8_t *start = (uint8_t *)chan_raw; + unsigned int offset = sizeof(struct ipc_shm_raw_channel); + offset = (((uintptr_t)offset + 7) & ~0x07ULL); + ENCDECDEBUG("encode: channel at offset %lu\n", (start - (uint8_t *)root_raw)); + if (root_raw) + chan_raw->dl_buf_offset = (start + offset - (uint8_t *)root_raw); + offset += ipc_shm_encode_stream(root_raw, (struct ipc_shm_raw_stream *)(start + offset), num_buffers, + buffer_size); + if (root_raw) + chan_raw->ul_buf_offset = (start + offset - (uint8_t *)root_raw); + offset += ipc_shm_encode_stream(root_raw, (struct ipc_shm_raw_stream *)(start + offset), num_buffers, + buffer_size); + return offset; +} +/* if root_raw is NULL, then do a dry run, aka only calculate final offset */ +unsigned int ipc_shm_encode_region(struct ipc_shm_raw_region *root_raw, uint32_t num_chans, uint32_t num_buffers, + uint32_t buffer_size) +{ + unsigned i; + uintptr_t start = (uintptr_t)root_raw; + unsigned int offset = sizeof(struct ipc_shm_raw_region) + sizeof(uint32_t) * num_chans; + offset = (((uintptr_t)offset + 7) & ~0x07ULL); + + if (root_raw) + root_raw->num_chans = num_chans; + for (i = 0; i < num_chans; i++) { + if (root_raw) + root_raw->chan_offset[i] = (start + offset - (uintptr_t)root_raw); + ENCDECDEBUG("encode: channel %d chan_offset[i]=%lu\n", i, start + offset - (uintptr_t)root_raw); + offset += ipc_shm_encode_channel(root_raw, (struct ipc_shm_raw_channel *)(start + offset), num_buffers, + buffer_size); + } + //TODO: pass maximum size and verify we didn't go through + return offset; +} diff --git a/Transceiver52M/device/ipc/shm.h b/Transceiver52M/device/ipc/shm.h new file mode 100644 index 0000000..46c3add --- /dev/null +++ b/Transceiver52M/device/ipc/shm.h @@ -0,0 +1,234 @@ +/* +* 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. +*/ +#pragma once + +#include <stdint.h> +#include <unistd.h> +#include <limits.h> +#include <pthread.h> +#include <semaphore.h> + +/* RAW structures */ +struct ipc_shm_raw_smpl_buf { + uint64_t timestamp; + uint32_t data_len; /* In samples */ + uint16_t samples[0]; +}; + +struct ipc_shm_raw_stream { + pthread_mutex_t lock; /* protects this struct */ + pthread_cond_t cf; /* signals fill to reader */ + pthread_cond_t ce; /* signals empty nbuf to writer */ + uint32_t num_buffers; + uint32_t buffer_size; /* In samples */ + uint32_t read_next; + uint32_t write_next; + uint32_t buffer_offset[0]; + //struct ipc_shm_smpl_buf buffers[0]; +}; + +struct ipc_shm_raw_channel { + uint32_t dl_buf_offset; + uint32_t ul_buf_offset; +}; + +struct ipc_shm_raw_region { + uint32_t num_chans; + uint32_t chan_offset[0]; +}; + +/* non-raw, Pointer converted structures */ +struct ipc_shm_stream { + uint32_t num_buffers; + uint32_t buffer_size; + volatile struct ipc_shm_raw_stream *raw; + volatile struct ipc_shm_raw_smpl_buf *buffers[0]; +}; + +struct ipc_shm_channel { + struct ipc_shm_stream *dl_stream; + struct ipc_shm_stream *ul_stream; +}; + +/* Pointer converted structures */ +struct ipc_shm_region { + uint32_t num_chans; + struct ipc_shm_channel *channels[0]; +}; + +unsigned int ipc_shm_encode_region(struct ipc_shm_raw_region *root_raw, uint32_t num_chans, uint32_t num_buffers, + uint32_t buffer_size); +struct ipc_shm_region *ipc_shm_decode_region(void *tall_ctx, struct ipc_shm_raw_region *root_raw); +/****************************************/ +/* UNIX SOCKET API */ +/****************************************/ + +////////////////// +// Master socket +////////////////// +#define IPC_SOCK_API_VERSION 1 + +/* msg_type */ +#define IPC_IF_MSG_GREETING_REQ 0x00 +#define IPC_IF_MSG_GREETING_CNF 0x01 +#define IPC_IF_MSG_INFO_REQ 0x02 +#define IPC_IF_MSG_INFO_CNF 0x03 +#define IPC_IF_MSG_OPEN_REQ 0x04 +#define IPC_IF_MSG_OPEN_CNF 0x05 + +#define MAX_NUM_CHANS 30 +#define RF_PATH_NAME_SIZE 25 +#define MAX_NUM_RF_PATHS 10 +#define SHM_NAME_MAX NAME_MAX /* 255 */ + +#define FEATURE_MASK_CLOCKREF_INTERNAL (0x1 << 0) +#define FEATURE_MASK_CLOCKREF_EXTERNAL (0x1 << 1) +struct ipc_sk_if_info_chan { + char tx_path[MAX_NUM_RF_PATHS][RF_PATH_NAME_SIZE]; + char rx_path[MAX_NUM_RF_PATHS][RF_PATH_NAME_SIZE]; + double min_rx_gain; + double max_rx_gain; + double min_tx_gain; + double max_tx_gain; + double nominal_tx_power; /* dBm */ +} __attribute__((packed)); + +struct ipc_sk_if_open_req_chan { + char tx_path[RF_PATH_NAME_SIZE]; + char rx_path[RF_PATH_NAME_SIZE]; +} __attribute__((packed)); + +struct ipc_sk_if_open_cnf_chan { + char chan_ipc_sk_path[108]; +} __attribute__((packed)); + +struct ipc_sk_if_greeting { + uint8_t req_version; +} __attribute__((packed)); + +struct ipc_sk_if_info_req { + uint8_t spare; +} __attribute__((packed)); + +struct ipc_sk_if_info_cnf { + uint32_t feature_mask; + double iq_scaling_val_rx; /* for scaling, sample format is 16 bit, but adc/dac might be less */ + double iq_scaling_val_tx; + uint32_t max_num_chans; + char dev_desc[200]; + struct ipc_sk_if_info_chan chan_info[MAX_NUM_CHANS]; +} __attribute__((packed)); + +struct ipc_sk_if_open_req { + uint32_t num_chans; + uint32_t clockref; /* One of FEATUER_MASK_CLOCKREF_* */ + uint32_t rx_sample_freq_num; + uint32_t rx_sample_freq_den; + uint32_t tx_sample_freq_num; + uint32_t tx_sample_freq_den; + uint32_t bandwidth; + struct ipc_sk_if_open_req_chan chan_info[MAX_NUM_CHANS]; +} __attribute__((packed)); + +struct ipc_sk_if_open_cnf { + uint8_t return_code; + uint32_t path_delay; + char shm_name[SHM_NAME_MAX]; + struct ipc_sk_if_open_cnf_chan chan_info[MAX_NUM_CHANS]; +} __attribute__((packed)); + +struct ipc_sk_if { + uint8_t msg_type; /* message type */ + uint8_t spare[2]; + + union { + struct ipc_sk_if_greeting greeting_req; + struct ipc_sk_if_greeting greeting_cnf; + struct ipc_sk_if_info_req info_req; + struct ipc_sk_if_info_cnf info_cnf; + struct ipc_sk_if_open_req open_req; + struct ipc_sk_if_open_cnf open_cnf; + } u; +} __attribute__((packed)); + +////////////////// +// Channel socket +////////////////// +#define IPC_IF_CHAN_MSG_OFFSET 50 +#define IPC_IF_MSG_START_REQ IPC_IF_CHAN_MSG_OFFSET + 0 +#define IPC_IF_MSG_START_CNF IPC_IF_CHAN_MSG_OFFSET + 1 +#define IPC_IF_MSG_STOP_REQ IPC_IF_CHAN_MSG_OFFSET + 2 +#define IPC_IF_MSG_STOP_CNF IPC_IF_CHAN_MSG_OFFSET + 3 +#define IPC_IF_MSG_SETGAIN_REQ IPC_IF_CHAN_MSG_OFFSET + 4 +#define IPC_IF_MSG_SETGAIN_CNF IPC_IF_CHAN_MSG_OFFSET + 5 +#define IPC_IF_MSG_SETFREQ_REQ IPC_IF_CHAN_MSG_OFFSET + 6 +#define IPC_IF_MSG_SETFREQ_CNF IPC_IF_CHAN_MSG_OFFSET + 7 + +#define IPC_IF_NOTIFY_UNDERFLOW IPC_IF_CHAN_MSG_OFFSET + 8 +#define IPC_IF_NOTIFY_OVERFLOW IPC_IF_CHAN_MSG_OFFSET + 9 + +#define IPC_IF_MSG_SETTXATTN_REQ IPC_IF_CHAN_MSG_OFFSET + 10 +#define IPC_IF_MSG_SETTXATTN_CNF IPC_IF_CHAN_MSG_OFFSET + 11 + +struct ipc_sk_chan_if_op_void { + // at least one dummy byte, to allow c/c++ compatibility + uint8_t dummy; +} __attribute__((packed)); + +struct ipc_sk_chan_if_op_rc { + uint8_t return_code; +} __attribute__((packed)); + +struct ipc_sk_chan_if_gain { + double gain; + uint8_t is_tx; +} __attribute__((packed)); + +struct ipc_sk_chan_if_freq_req { + double freq; + uint8_t is_tx; +} __attribute__((packed)); + +struct ipc_sk_chan_if_freq_cnf { + uint8_t return_code; +} __attribute__((packed)); + +struct ipc_sk_chan_if_notfiy { + uint8_t dummy; +} __attribute__((packed)); + +struct ipc_sk_chan_if_tx_attenuation { + double attenuation; +} __attribute__((packed)); + +struct ipc_sk_chan_if { + uint8_t msg_type; /* message type */ + uint8_t spare[2]; + + union { + struct ipc_sk_chan_if_op_void start_req; + struct ipc_sk_chan_if_op_rc start_cnf; + struct ipc_sk_chan_if_op_void stop_req; + struct ipc_sk_chan_if_op_rc stop_cnf; + struct ipc_sk_chan_if_gain set_gain_req; + struct ipc_sk_chan_if_gain set_gain_cnf; + struct ipc_sk_chan_if_freq_req set_freq_req; + struct ipc_sk_chan_if_freq_cnf set_freq_cnf; + struct ipc_sk_chan_if_notfiy notify; + struct ipc_sk_chan_if_tx_attenuation txatten_req; + struct ipc_sk_chan_if_tx_attenuation txatten_cnf; + } u; +} __attribute__((packed)); diff --git a/Transceiver52M/device/ipc/uhdwrap.cpp b/Transceiver52M/device/ipc/uhdwrap.cpp new file mode 100644 index 0000000..302f763 --- /dev/null +++ b/Transceiver52M/device/ipc/uhdwrap.cpp @@ -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" + +// no vty source for cfg params here, so we have to build our own +static struct trx_cfg actual_cfg = {}; + +int uhd_wrap::open() +{ + int rv = uhd_device::open(); + 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) +{ + actual_cfg.num_chans = open_req->num_chans; + actual_cfg.swap_channels = false; + /* FIXME: this is actually the sps value, not the sample rate! + * sample rate is looked up according to the sps rate by uhd backend */ + actual_cfg.rx_sps = open_req->rx_sample_freq_num / open_req->rx_sample_freq_den; + actual_cfg.tx_sps = open_req->tx_sample_freq_num / open_req->tx_sample_freq_den; + + /* FIXME: dev arg string* */ + /* FIXME: rx frontend bw? */ + /* FIXME: tx frontend bw? */ + switch (open_req->clockref) { + case FEATURE_MASK_CLOCKREF_EXTERNAL: + actual_cfg.clock_ref = ReferenceType::REF_EXTERNAL; + break; + case FEATURE_MASK_CLOCKREF_INTERNAL: + default: + actual_cfg.clock_ref = ReferenceType::REF_INTERNAL; + break; + } + + for (unsigned int i = 0; i < open_req->num_chans; i++) { + actual_cfg.chans[i].rx_path = open_req->chan_info[i].tx_path; + actual_cfg.chans[i].tx_path = open_req->chan_info[i].rx_path; + } + + uhd_wrap *uhd_wrap_dev = new uhd_wrap(RadioDevice::NORMAL, &actual_cfg); + uhd_wrap_dev->open(); + + 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++; + } +} diff --git a/Transceiver52M/device/ipc/uhdwrap.h b/Transceiver52M/device/ipc/uhdwrap.h new file mode 100644 index 0000000..44cd9aa --- /dev/null +++ b/Transceiver52M/device/ipc/uhdwrap.h @@ -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() 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 diff --git a/Transceiver52M/device/lms/LMSDevice.cpp b/Transceiver52M/device/lms/LMSDevice.cpp index b7cb31d..7c220d2 100644 --- a/Transceiver52M/device/lms/LMSDevice.cpp +++ b/Transceiver52M/device/lms/LMSDevice.cpp @@ -20,6 +20,9 @@ #include <stdint.h> #include <string.h> #include <stdlib.h> + +#include <map> + #include "Logger.h" #include "Threads.h" #include "LMSDevice.h" @@ -28,6 +31,7 @@ #include <lime/LimeSuite.h> extern "C" { +#include "trx_vty.h" #include "osmo_signal.h" #include <osmocom/core/utils.h> } @@ -36,27 +40,92 @@ extern "C" { #include "config.h" #endif -using namespace std; - #define MAX_ANTENNA_LIST_SIZE 10 -#define LMS_SAMPLE_RATE GSMRATE*32 #define GSM_CARRIER_BW 270000.0 /* 270kHz */ #define LMS_MIN_BW_SUPPORTED 2.5e6 /* 2.5mHz, minimum supported by LMS */ #define LMS_CALIBRATE_BW_HZ OSMO_MAX(GSM_CARRIER_BW, LMS_MIN_BW_SUPPORTED) #define SAMPLE_BUF_SZ (1 << 20) /* Size of Rx timestamp based Ring buffer, in bytes */ -LMSDevice::LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths): - RadioDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths), - m_lms_dev(NULL) + +/* Device Name Prefixes as presented by LimeSuite API LMS_GetDeviceInfo(): */ +#define LMS_DEV_SDR_USB_PREFIX_NAME "LimeSDR-USB" +#define LMS_DEV_SDR_MINI_PREFIX_NAME "LimeSDR-Mini" +#define LMS_DEV_NET_MICRO_PREFIX_NAME "LimeNET-Micro" + + + +static const dev_map_t dev_param_map { + { LMS_DEV_SDR_USB, { true, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_SDR_USB_PREFIX_NAME } }, + { LMS_DEV_SDR_MINI, { false, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 8.2e-5, LMS_DEV_SDR_MINI_PREFIX_NAME } }, + { LMS_DEV_NET_MICRO, { true, false, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_NET_MICRO_PREFIX_NAME } }, + { LMS_DEV_UNKNOWN, { true, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, "UNKNOWN" } }, +}; + +static const power_map_t dev_band_nom_power_param_map { + { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_850), { 73.0, 11.2, -6.0 } }, + { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_900), { 73.0, 10.8, -6.0 } }, + { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1800), { 65.0, -3.5, -17.0 } }, /* FIXME: OS#4583: 1800Mhz is failing above TxGain=65, which is around -3.5dBm (already < 0 dBm) */ + { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1900), { 73.0, 1.7, -17.0 } }, /* FIXME: OS#4583: 1900MHz is failing in all TxGain values */ + { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_850), { 66.0, 3.1, -6.0 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */ + { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_900), { 66.0, 2.8, -6.0 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */ + { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1800), { 66.0, -11.6, -17.0 } }, /* OS#4583: Any of BAND1 or BAND2 is fine */ + { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1900), { 66.0, -9.2, -17.0 } }, /* FIXME: OS#4583: Ensure BAND1 is used at startup */ + { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_850), { 71.0, 6.8, -6.0 } }, + { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_900), { 71.0, 6.8, -6.0 } }, + { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1800), { 65.0, -10.5, -17.0 } }, /* OS#4583: TxGain=71 (-4.4dBm) FAIL rms phase errors ~10° */ + { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1900), { 71.0, -6.3, -17.0 } }, /* FIXME: OS#4583: all FAIL, BAND1/BAND2 rms phase errors >23° */ +}; + +/* So far measurements done for B210 show really close to linear relationship + * 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_lms_tx_gain - tx_gain_db); +} +static double TxPower2TxGain(const dev_band_desc &desc, double tx_power_dbm) +{ + return desc.nom_lms_tx_gain - (desc.nom_out_tx_power - tx_power_dbm); +} + +static enum lms_dev_type parse_dev_type(lms_device_t *m_lms_dev) +{ + std::map<enum lms_dev_type, struct dev_desc>::const_iterator it = dev_param_map.begin(); + + const lms_dev_info_t* device_info = LMS_GetDeviceInfo(m_lms_dev); + + while (it != dev_param_map.end()) + { + enum lms_dev_type dev_type = it->first; + struct dev_desc desc = it->second; + + if (strncmp(device_info->deviceName, desc.desc_str.c_str(), desc.desc_str.length()) == 0) { + LOGC(DDEV, INFO) << "Device identified as " << desc.desc_str; + return dev_type; + } + it++; + } + return LMS_DEV_UNKNOWN; +} + +LMSDevice::LMSDevice(InterfaceType iface, const struct trx_cfg *cfg) + : RadioDevice(iface, cfg), + band_manager(m_dev_type, dev_band_nom_power_param_map, dev_param_map, {LMS_DEV_SDR_USB, GSM_BAND_850}), m_lms_dev(NULL), + started(false), m_dev_type(LMS_DEV_UNKNOWN) { LOGC(DDEV, INFO) << "creating LMS device..."; m_lms_stream_rx.resize(chans); m_lms_stream_tx.resize(chans); + rx_gains.resize(chans); + tx_gains.resize(chans); rx_buffers.resize(chans); + + /* 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)); + } LMSDevice::~LMSDevice() @@ -91,7 +160,7 @@ static void lms_log_callback(int lvl, const char *msg) if ((unsigned int) lvl >= ARRAY_SIZE(lvl_map)) lvl = ARRAY_SIZE(lvl_map)-1; - LOGLV(DLMS, lvl_map[lvl]) << msg; + LOGLV(DDEVDRV, lvl_map[lvl]) << msg; } static void print_range(const char* name, lms_range_t *range) @@ -108,7 +177,7 @@ static void print_range(const char* name, lms_range_t *range) int info_list_find(lms_info_str_t* info_list, unsigned int count, const std::string &args) { unsigned int i, j; - vector<string> filters; + std::vector<std::string> filters; filters = comma_delimited_to_vector(args.c_str()); @@ -129,24 +198,25 @@ int info_list_find(lms_info_str_t* info_list, unsigned int count, const std::str return -1; } -int LMSDevice::open(const std::string &args, int ref, bool swap_channels) +int LMSDevice::open() { lms_info_str_t* info_list; - const lms_dev_info_t* device_info; lms_range_t range_sr; float_type sr_host, sr_rf; unsigned int i, n; int rc, dev_id; + struct dev_desc dev_desc; LOGC(DDEV, INFO) << "Opening LMS device.."; LMS_RegisterLogHandler(&lms_log_callback); - if ((n = LMS_GetDeviceList(NULL)) < 0) + if ((rc = LMS_GetDeviceList(NULL)) < 0) LOGC(DDEV, ERROR) << "LMS_GetDeviceList(NULL) failed"; - LOGC(DDEV, INFO) << "Devices found: " << n; - if (n < 1) + LOGC(DDEV, INFO) << "Devices found: " << rc; + if (rc < 1) return -1; + n = rc; info_list = new lms_info_str_t[n]; @@ -156,9 +226,9 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels) for (i = 0; i < n; i++) LOGC(DDEV, INFO) << "Device [" << i << "]: " << info_list[i]; - dev_id = info_list_find(info_list, n, args); + dev_id = info_list_find(info_list, n, cfg->dev_args); if (dev_id == -1) { - LOGC(DDEV, ERROR) << "No LMS device found with address '" << args << "'"; + LOGC(DDEV, ERROR) << "No LMS device found with address '" << cfg->dev_args << "'"; delete[] info_list; return -1; } @@ -173,19 +243,21 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels) delete [] info_list; - device_info = LMS_GetDeviceInfo(m_lms_dev); + m_dev_type = parse_dev_type(m_lms_dev); + dev_desc = dev_param_map.at(m_dev_type); + update_band_dev(m_dev_type); - if ((ref != REF_EXTERNAL) && (ref != REF_INTERNAL)){ + if ((cfg->clock_ref != REF_EXTERNAL) && (cfg->clock_ref != REF_INTERNAL)) { LOGC(DDEV, ERROR) << "Invalid reference type"; goto out_close; } - /* if reference clock is external setup must happen _before_ calling LMS_Init */ - /* FIXME make external reference frequency configurable */ - if (ref == REF_EXTERNAL) { + /* if reference clock is external, setup must happen _before_ calling LMS_Init */ + if (cfg->clock_ref == REF_EXTERNAL) { LOGC(DDEV, INFO) << "Setting External clock reference to 10MHz"; - /* Assume an external 10 MHz reference clock */ - if (LMS_SetClockFreq(m_lms_dev, LMS_CLOCK_EXTREF, 10000000.0) < 0) + /* FIXME: Assume an external 10 MHz reference clock. make + external reference frequency configurable */ + if (!do_clock_src_freq(REF_EXTERNAL, 10000000.0)) goto out_close; } @@ -195,22 +267,13 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels) goto out_close; } - /* LimeSDR-Mini does not have switches but needs soldering to select external/internal clock */ - /* LimeNET-Micro also does not like selecting internal clock*/ - /* also set device specific maximum tx levels selected by phasenoise measurements*/ - if (strncmp(device_info->deviceName,"LimeSDR-USB",11) == 0){ - /* if reference clock is internal setup must happen _after_ calling LMS_Init */ - /* according to lms using LMS_CLOCK_EXTREF with a frequency <= 0 is the correct way to set clock to internal reference*/ - if (ref == REF_INTERNAL) { - LOGC(DDEV, INFO) << "Setting Internal clock reference"; - if (LMS_SetClockFreq(m_lms_dev, LMS_CLOCK_EXTREF, -1) < 0) - goto out_close; - } - maxTxGainClamp = 73.0; - } else if (strncmp(device_info->deviceName,"LimeSDR-Mini",12) == 0) - maxTxGainClamp = 66.0; - else - maxTxGainClamp = 71.0; /* "LimeNET-Micro", etc FIXME pciE based LMS boards?*/ + /* if reference clock is internal, setup must happen _after_ calling LMS_Init */ + if (cfg->clock_ref == REF_INTERNAL) { + LOGC(DDEV, INFO) << "Setting Internal clock reference"; + /* Internal freq param is not used */ + if (!do_clock_src_freq(REF_INTERNAL, 0)) + goto out_close; + } /* enable all used channels */ for (i=0; i<chans; i++) { @@ -225,16 +288,22 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels) goto out_close; print_range("Sample Rate", &range_sr); - LOGC(DDEV, INFO) << "Setting sample rate to " << GSMRATE*tx_sps << " " << tx_sps; - if (LMS_SetSampleRate(m_lms_dev, GSMRATE*tx_sps, 32) < 0) + if (iface == MULTI_ARFCN) + sr_host = dev_desc.rate_multiarfcn * tx_sps; + else + sr_host = dev_desc.rate * tx_sps; + LOGC(DDEV, INFO) << "Setting sample rate to " << sr_host << " " << tx_sps; + if (LMS_SetSampleRate(m_lms_dev, sr_host, 32) < 0) goto out_close; if (LMS_GetSampleRate(m_lms_dev, LMS_CH_RX, 0, &sr_host, &sr_rf)) goto out_close; LOGC(DDEV, INFO) << "Sample Rate: Host=" << sr_host << " RF=" << sr_rf; - /* FIXME: make this device/model dependent, like UHDDevice:dev_param_map! */ - ts_offset = static_cast<TIMESTAMP>(8.9e-5 * GSMRATE * tx_sps); /* time * sample_rate */ + if (iface == MULTI_ARFCN) + ts_offset = static_cast<TIMESTAMP>(dev_desc.ts_offset_coef_multiarfcn * sr_host); + else + ts_offset = static_cast<TIMESTAMP>(dev_desc.ts_offset_coef * sr_host); /* configure antennas */ if (!set_antennas()) { @@ -242,13 +311,7 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels) goto out_close; } - /* 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)); - - started = false; - - return NORMAL; + return iface == MULTI_ARFCN ? MULTI_ARFCN : NORMAL; out_close: LOGC(DDEV, FATAL) << "Error in LMS open, closing: " << LMS_GetLastErrorMessage(); @@ -262,17 +325,20 @@ bool LMSDevice::start() LOGC(DDEV, INFO) << "starting LMS..."; unsigned int i; + dev_band_desc desc; if (started) { LOGC(DDEV, ERR) << "Device already started"; return false; } + get_dev_band_desc(desc); + /* configure the channels/streams */ for (i=0; i<chans; i++) { /* Set gains for calibration/filter setup */ /* TX gain to maximum */ - setTxGain(maxTxGain(), i); + LMS_SetGaindB(m_lms_dev, LMS_CH_TX, i, TxPower2TxGain(desc, desc.nom_out_tx_power)); /* RX gain to midpoint */ setRxGain((minRxGain() + maxRxGain()) / 2, i); @@ -338,10 +404,49 @@ bool LMSDevice::stop() LMS_DestroyStream(m_lms_dev, &m_lms_stream_rx[i]); } + band_reset(); + started = false; return true; } +bool LMSDevice::do_clock_src_freq(enum ReferenceType ref, double freq) +{ + struct dev_desc dev_desc = dev_param_map.at(m_dev_type); + size_t lms_clk_id; + + switch (ref) { + case REF_EXTERNAL: + lms_clk_id = LMS_CLOCK_EXTREF; + break; + case REF_INTERNAL: + if (!dev_desc.clock_src_int_usable) { + LOGC(DDEV, ERROR) + << "Device type " << dev_desc.desc_str << " doesn't support internal reference clock"; + return false; + } + /* According to lms using LMS_CLOCK_EXTREF with a + frequency <= 0 is the correct way to set clock to + internal reference */ + lms_clk_id = LMS_CLOCK_EXTREF; + freq = -1; + break; + default: + LOGC(DDEV, ERROR) << "Invalid reference type " << get_value_string(clock_ref_names, ref); + return false; + } + + if (dev_desc.clock_src_switchable) { + if (LMS_SetClockFreq(m_lms_dev, lms_clk_id, freq) < 0) + return false; + } else { + LOGC(DDEV, INFO) + << "Device type " << dev_desc.desc_str << " doesn't support switching clock source through SW"; + } + + return true; +} + /* do rx/tx calibration - depends on gain, freq and bw */ bool LMSDevice::do_calib(size_t chan) { @@ -380,17 +485,6 @@ bool LMSDevice::do_filters(size_t chan) return true; } - -double LMSDevice::maxTxGain() -{ - return maxTxGainClamp; -} - -double LMSDevice::minTxGain() -{ - return 0.0; -} - double LMSDevice::maxRxGain() { return 73.0; @@ -401,21 +495,6 @@ double LMSDevice::minRxGain() return 0.0; } -double LMSDevice::setTxGain(double dB, size_t chan) -{ - if (dB > maxTxGain()) - dB = maxTxGain(); - if (dB < minTxGain()) - dB = minTxGain(); - - LOGCHAN(chan, DDEV, NOTICE) << "Setting TX gain to " << dB << " dB"; - - if (LMS_SetGaindB(m_lms_dev, LMS_CH_TX, chan, dB) < 0) - LOGCHAN(chan, DDEV, ERR) << "Error setting TX gain to " << dB << " dB"; - - return dB; -} - double LMSDevice::setRxGain(double dB, size_t chan) { if (dB > maxRxGain()) @@ -427,8 +506,66 @@ double LMSDevice::setRxGain(double dB, size_t chan) if (LMS_SetGaindB(m_lms_dev, LMS_CH_RX, chan, dB) < 0) LOGCHAN(chan, DDEV, ERR) << "Error setting RX gain to " << dB << " dB"; + else + rx_gains[chan] = dB; + return rx_gains[chan]; +} + +double LMSDevice::rssiOffset(size_t chan) +{ + double rssiOffset; + dev_band_desc desc; - return dB; + if (chan >= rx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + get_dev_band_desc(desc); + rssiOffset = rx_gains[chan] + desc.rxgain2rssioffset_rel; + return rssiOffset; +} + +double LMSDevice::setPowerAttenuation(int atten, size_t chan) +{ + double tx_power, dB; + 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); + + LOGCHAN(chan, DDEV, NOTICE) << "Setting TX gain to " << dB << " dB (~" << tx_power << " dBm)"; + + if (LMS_SetGaindB(m_lms_dev, LMS_CH_TX, chan, dB) < 0) + LOGCHAN(chan, DDEV, ERR) << "Error setting TX gain to " << dB << " dB (~" << tx_power << " dBm)"; + else + tx_gains[chan] = dB; + return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]); +} + +double LMSDevice::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 LMSDevice::getNominalTxPower(size_t chan) +{ + dev_band_desc desc; + get_dev_band_desc(desc); + + return desc.nom_out_tx_power; } void LMSDevice::log_ant_list(bool dir_tx, size_t chan, std::ostringstream& os) @@ -620,7 +757,7 @@ void LMSDevice::update_stream_stats_rx(size_t chan, bool *overrun) m_ctr[chan].rx_overruns += status.overrun; /* Dropped packets in Rx are counted when gaps in Rx timestamps are - detected (likely because buffer oveflow in hardware). Value count + detected (likely because buffer overflow in hardware). Value count since the last call to LMS_GetStreamStatus(stream). */ if (status.droppedPackets) { changed = true; @@ -640,7 +777,7 @@ void LMSDevice::update_stream_stats_rx(size_t chan, bool *overrun) // NOTE: Assumes sequential reads int LMSDevice::readSamples(std::vector < short *>&bufs, int len, bool * overrun, - TIMESTAMP timestamp, bool * underrun, unsigned *RSSI) + TIMESTAMP timestamp, bool * underrun) { int rc, num_smpls, expect_smpls; ssize_t avail_smpls; @@ -707,8 +844,9 @@ int LMSDevice::readSamples(std::vector < short *>&bufs, int len, bool * overrun, 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); + LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_code(rc) << ". " + << rx_buffers[i]->str_status(timestamp) + << ", (len=" << len << ")"; return 0; } } @@ -758,8 +896,7 @@ void LMSDevice::update_stream_stats_tx(size_t chan, bool *underrun) } int LMSDevice::writeSamples(std::vector < short *>&bufs, int len, - bool * underrun, unsigned long long timestamp, - bool isControl) + bool * underrun, unsigned long long timestamp) { int rc = 0; unsigned int i; @@ -768,11 +905,6 @@ int LMSDevice::writeSamples(std::vector < short *>&bufs, int len, tx_metadata.waitForTimestamp = true; tx_metadata.timestamp = timestamp - ts_offset; /* Shift Tx time by offset */ - if (isControl) { - LOGC(DDEV, ERROR) << "Control packets not supported"; - return 0; - } - if (bufs.size() != chans) { LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size(); return -1; @@ -802,8 +934,16 @@ bool LMSDevice::updateAlignment(TIMESTAMP timestamp) bool LMSDevice::setTxFreq(double wFreq, size_t chan) { + if (chan >= chans) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return false; + } + LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz"; + if (!update_band_from_freq(wFreq, chan, true)) + return false; + if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_TX, chan, wFreq) < 0) { LOGCHAN(chan, DDEV, ERROR) << "Error setting Tx Freq to " << wFreq << " Hz"; return false; @@ -816,6 +956,9 @@ bool LMSDevice::setRxFreq(double wFreq, size_t chan) { LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz"; + if (!update_band_from_freq(wFreq, chan, false)) + return false; + if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_RX, chan, wFreq) < 0) { LOGCHAN(chan, DDEV, ERROR) << "Error setting Rx Freq to " << wFreq << " Hz"; return false; @@ -824,18 +967,15 @@ bool LMSDevice::setRxFreq(double wFreq, size_t chan) return true; } -RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, - InterfaceType iface, size_t chans, double lo_offset, - const std::vector < std::string > &tx_paths, - const std::vector < std::string > &rx_paths) +RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg) { - if (tx_sps != rx_sps) { - LOGC(DDEV, ERROR) << "LMS Requires tx_sps == rx_sps"; + if (cfg->tx_sps != cfg->rx_sps) { + LOGC(DDEV, ERROR) << "LMS requires tx_sps == rx_sps"; return NULL; } - if (lo_offset != 0.0) { + if (cfg->offset != 0.0) { LOGC(DDEV, ERROR) << "LMS doesn't support lo_offset"; return NULL; } - return new LMSDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); + return new LMSDevice(type, cfg); } diff --git a/Transceiver52M/device/lms/LMSDevice.h b/Transceiver52M/device/lms/LMSDevice.h index ab45b08..2e5ca4c 100644 --- a/Transceiver52M/device/lms/LMSDevice.h +++ b/Transceiver52M/device/lms/LMSDevice.h @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0+ * * This software is distributed under multiple licenses; see the COPYING file in -* the main directory for licensing information for this specific distribuion. +* the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -18,11 +18,13 @@ #ifndef _LMS_DEVICE_H_ #define _LMS_DEVICE_H_ +#include <map> #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "radioDevice.h" +#include "bandmanager.h" #include "smpl_buf.h" #include <sys/time.h> @@ -32,6 +34,10 @@ #include <iostream> #include <lime/LimeSuite.h> +extern "C" { +#include <osmocom/gsm/gsm_utils.h> +} + /* Definition of LIMESDR_TX_AMPL limits maximum amplitude of I and Q * channels separately. Hence LIMESDR_TX_AMPL value must be 1/sqrt(2) = * 0.7071.... to get an amplitude of 1 of the complex signal: @@ -41,8 +47,59 @@ * A^2 = 1 */ #define LIMESDR_TX_AMPL 0.707 +enum lms_dev_type { + LMS_DEV_SDR_USB, /* LimeSDR-USB */ + LMS_DEV_SDR_MINI, /* LimeSDR-Mini */ + LMS_DEV_NET_MICRO, /* LimeNet-micro */ + LMS_DEV_UNKNOWN, +}; + +struct dev_band_desc { + /* Maximum LimeSuite Tx Gain which can be set/used without distorting + the output * signal, and the resulting real output power measured + when that gain is used. + */ + double nom_lms_tx_gain; /* dB */ + double nom_out_tx_power; /* dBm */ + /* Factor used to infer base real RSSI offset on the Rx path based on current + configured RxGain. The resulting rssiOffset is added to the per burst + calculated energy in upper layers. These values were empirically + found and may change based on multiple factors, see OS#4468. + Correct measured values only provided for LimeSDR-USB so far. + rssiOffset = rxGain + rxgain2rssioffset_rel; + */ + double rxgain2rssioffset_rel; /* dB */ +}; + +/* Device parameter descriptor */ +struct dev_desc { + /* Does LimeSuite allow switching the clock source for this device? + * LimeSDR-Mini does not have switches but needs soldering to select + * external/internal clock. Any call to LMS_SetClockFreq() will fail. + */ + bool clock_src_switchable; + /* Does LimeSuite allow using REF_INTERNAL for this device? + * LimeNET-Micro does not like selecting internal clock + */ + bool clock_src_int_usable; + /* Sample rate coef (without having TX/RX samples per symbol into account) */ + double rate; + /* Sample rate coef (without having TX/RX samples per symbol into account), if multi-arfcn is enabled */ + double rate_multiarfcn; + /* Coefficient multiplied by TX sample rate in order to shift Tx time */ + double ts_offset_coef; + /* Coefficient multiplied by TX sample rate in order to shift Tx time, if multi-arfcn is enabled */ + double ts_offset_coef_multiarfcn; + /* Device Name Prefix as presented by LimeSuite API LMS_GetDeviceInfo() */ + std::string desc_str; +}; + +using dev_band_key_t = std::tuple<lms_dev_type, gsm_band>; +using power_map_t = std::map<dev_band_key_t, dev_band_desc>; +using dev_map_t = std::map<lms_dev_type, struct dev_desc>; + /** A class to handle a LimeSuite supported device */ -class LMSDevice:public RadioDevice { +class LMSDevice:public RadioDevice, public band_manager<power_map_t, dev_map_t> { private: lms_device_t *m_lms_dev; @@ -58,8 +115,9 @@ private: TIMESTAMP ts_initial, ts_offset; - double rxGain; - double maxTxGainClamp; + std::vector<double> tx_gains, rx_gains; + + enum lms_dev_type m_dev_type; bool do_calib(size_t chan); bool do_filters(size_t chan); @@ -68,27 +126,26 @@ private: 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 do_clock_src_freq(enum ReferenceType ref, double freq); public: /** Object constructor */ - LMSDevice(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); - ~LMSDevice(); + LMSDevice(InterfaceType iface, const struct trx_cfg *cfg); + ~LMSDevice(); - /** Instantiate the LMS */ - int open(const std::string &args, int ref, bool swap_channels); + /** Instantiate the LMS */ + int open(); - /** Start the LMS */ - bool start(); + /** Start the LMS */ + bool start(); - /** Stop the LMS */ - bool stop(); + /** Stop the LMS */ + bool stop(); - enum TxWindowType getWindowType() { - return TX_WINDOW_LMS1; - } + enum TxWindowType getWindowType() + { + return TX_WINDOW_LMS1; + } /** Read samples from the LMS. @@ -97,24 +154,21 @@ public: @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 LMS does not have data to transmit, e.g. data not being sent fast enough - @param RSSI The received signal strength of the read result @return The number of samples actually read */ int readSamples(std::vector < short *>&buf, int len, bool * overrun, TIMESTAMP timestamp = 0xffffffff, bool * underrun = - NULL, unsigned *RSSI = NULL); + NULL); /** Write samples to the LMS. @param buf Contains the data to be written. @param len number of samples to write. @param underrun Set if LMS 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. - @param isControl Set if data is a control packet, e.g. a ping command @return The number of samples actually written */ int writeSamples(std::vector < short *>&bufs, int len, bool * underrun, - TIMESTAMP timestamp = 0xffffffff, bool isControl = - false); + TIMESTAMP timestamp = 0xffffffff); /** Update the alignment between the read and write timestamps */ bool updateAlignment(TIMESTAMP timestamp); @@ -150,7 +204,7 @@ public: /** get the current receive gain */ double getRxGain(size_t chan = 0) { - return rxGain; + return rx_gains[chan]; } /** return maximum Rx Gain **/ @@ -159,14 +213,12 @@ public: /** return minimum Rx Gain **/ double minRxGain(void); - /** sets the transmit chan gain, returns the gain setting **/ - double setTxGain(double dB, size_t chan = 0); + double rssiOffset(size_t chan); - /** return maximum Tx Gain **/ - double maxTxGain(void); + double setPowerAttenuation(int atten, size_t chan); + double getPowerAttenuation(size_t chan = 0); - /** return minimum Rx Gain **/ - double minTxGain(void); + int getNominalTxPower(size_t chan = 0); /** sets the RX path to use, returns true if successful and false otherwise */ bool setRxAntenna(const std::string & ant, size_t chan = 0); diff --git a/Transceiver52M/device/lms/Makefile.am b/Transceiver52M/device/lms/Makefile.am index 77fd0e9..6665077 100644 --- a/Transceiver52M/device/lms/Makefile.am +++ b/Transceiver52M/device/lms/Makefile.am @@ -1,7 +1,7 @@ include $(top_srcdir)/Makefile.common AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common -AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LMS_CFLAGS) +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LMS_CFLAGS) noinst_HEADERS = LMSDevice.h diff --git a/Transceiver52M/device/uhd/Makefile.am b/Transceiver52M/device/uhd/Makefile.am index ab63a4a..21df631 100644 --- a/Transceiver52M/device/uhd/Makefile.am +++ b/Transceiver52M/device/uhd/Makefile.am @@ -1,7 +1,7 @@ include $(top_srcdir)/Makefile.common AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common -AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(UHD_CFLAGS) +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(UHD_CFLAGS) noinst_HEADERS = UHDDevice.h diff --git a/Transceiver52M/device/uhd/UHDDevice.cpp b/Transceiver52M/device/uhd/UHDDevice.cpp index 5b38df4..85e9e38 100644 --- a/Transceiver52M/device/uhd/UHDDevice.cpp +++ b/Transceiver52M/device/uhd/UHDDevice.cpp @@ -33,11 +33,18 @@ #include "config.h" #endif -#ifndef USE_UHD_3_11 +extern "C" { +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/vty/cpu_sched_vty.h> +} + +#ifdef USE_UHD_3_11 +#include <uhd/utils/log_add.hpp> +#include <uhd/utils/thread.hpp> +#else #include <uhd/utils/msg.hpp> #include <uhd/utils/thread_priority.hpp> -#else -#include <uhd/utils/thread.hpp> #endif #define USRP_TX_AMPL 0.3 @@ -84,19 +91,7 @@ * USRP1 with timestamps is not supported by UHD. */ -/* Device Type, Tx-SPS, Rx-SPS */ -typedef std::tuple<uhd_dev_type, int, int> dev_key; - -/* Device parameter descriptor */ -struct dev_desc { - unsigned channels; - double mcr; - double rate; - double offset; - std::string str; -}; - -static const std::map<dev_key, dev_desc> dev_param_map { +static const dev_map_t dev_param_map { { std::make_tuple(USRP2, 1, 1), { 1, 0.0, 390625, 1.2184e-4, "N2XX 1 SPS" } }, { std::make_tuple(USRP2, 4, 1), { 1, 0.0, 390625, 7.6547e-5, "N2XX 4/1 Tx/Rx SPS" } }, { std::make_tuple(USRP2, 4, 4), { 1, 0.0, 390625, 4.6080e-5, "N2XX 4 SPS" } }, @@ -122,9 +117,21 @@ static const std::map<dev_key, dev_desc> dev_param_map { { std::make_tuple(B2XX_MCBTS, 4, 4), { 1, 51.2e6, MCBTS_SPACING*4, B2XX_TIMING_MCBTS, "B200/B210 4 SPS Multi-ARFCN" } }, }; +static const power_map_t dev_band_nom_power_param_map { + { std::make_tuple(B200, GSM_BAND_850), { 89.75, 13.3, -7.5 } }, + { std::make_tuple(B200, GSM_BAND_900), { 89.75, 13.3, -7.5 } }, + { std::make_tuple(B200, GSM_BAND_1800), { 89.75, 7.5, -11.0 } }, + { std::make_tuple(B200, GSM_BAND_1900), { 89.75, 7.7, -11.0 } }, + { std::make_tuple(B210, GSM_BAND_850), { 89.75, 13.3, -7.5 } }, + { std::make_tuple(B210, GSM_BAND_900), { 89.75, 13.3, -7.5 } }, + { std::make_tuple(B210, GSM_BAND_1800), { 89.75, 7.5, -11.0 } }, + { std::make_tuple(B210, GSM_BAND_1900), { 89.75, 7.7, -11.0 } }, +}; + void *async_event_loop(uhd_device *dev) { set_selfthread_name("UHDAsyncEvent"); + osmo_cpu_sched_vty_apply_localthread(); while (1) { dev->recv_async_msg(); @@ -134,23 +141,52 @@ void *async_event_loop(uhd_device *dev) return NULL; } -#ifndef USE_UHD_3_11 +#ifdef USE_UHD_3_11 +static void uhd_log_handler(const uhd::log::logging_info &info) +{ + int level; + + switch (info.verbosity) + { + case uhd::log::trace: + case uhd::log::debug: + level = LOGL_DEBUG; + break; + case uhd::log::info: + level = LOGL_INFO; + break; + case uhd::log::warning: + level = LOGL_NOTICE; + break; + case uhd::log::error: + level = LOGL_ERROR; + break; + case uhd::log::fatal: + level = LOGL_FATAL; + break; + default: + level = LOGL_NOTICE; + } + + LOGSRC(DDEVDRV, level, info.file.c_str(), info.line) << "[" << info.component << "] " << info.message; +} +#else /* Catch and drop underrun 'U' and overrun 'O' messages from stdout since we already report using the logging facility. Direct everything else appropriately. */ -void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg) +static void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg) { switch (type) { case uhd::msg::status: - LOGC(DDEV, INFO) << msg; + LOGC(DDEVDRV, INFO) << msg; break; case uhd::msg::warning: - LOGC(DDEV, WARNING) << msg; + LOGC(DDEVDRV, NOTICE) << msg; break; case uhd::msg::error: - LOGC(DDEV, ERROR) << msg; + LOGC(DDEVDRV, ERROR) << msg; break; case uhd::msg::fastpath: break; @@ -158,16 +194,22 @@ void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg) } #endif -uhd_device::uhd_device(size_t tx_sps, size_t rx_sps, - InterfaceType iface, size_t chans, double lo_offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths) - : RadioDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths), - tx_gain_min(0.0), tx_gain_max(0.0), - rx_gain_min(0.0), rx_gain_max(0.0), - tx_spp(0), rx_spp(0), - started(false), aligned(false), drop_cnt(0), - prev_ts(0,0), ts_initial(0), ts_offset(0), async_event_thrd(NULL) +/* 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); +} + +uhd_device::uhd_device(InterfaceType iface, const struct trx_cfg *cfg) + : RadioDevice(iface, cfg), band_manager(dev_band_nom_power_param_map, dev_param_map), rx_gain_min(0.0), + rx_gain_max(0.0), tx_spp(0), rx_spp(0), started(false), aligned(false), drop_cnt(0), prev_ts(0, 0), + ts_initial(0), ts_offset(0), async_event_thrd(NULL) { } @@ -181,6 +223,7 @@ uhd_device::~uhd_device() void uhd_device::init_gains() { + double tx_gain_min, tx_gain_max; uhd::gain_range_t range; if (dev_type == UMTRX) { @@ -242,19 +285,68 @@ void uhd_device::set_rates() rx_rate = usrp_dev->get_rx_rate(); ts_offset = static_cast<TIMESTAMP>(desc.offset * rx_rate); - LOGC(DDEV, INFO) << "Rates configured for " << desc.str; + LOGC(DDEV, INFO) << "Rates configured for " << desc.desc_str; } -double uhd_device::setTxGain(double db, size_t chan) +double uhd_device::setRxGain(double db, size_t chan) { - if (iface == MULTI_ARFCN) - chan = 0; + if (chan >= rx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + if (cfg->overrides.ul_gain_override) + return rx_gains[chan]; + + usrp_dev->set_rx_gain(db, chan); + rx_gains[chan] = usrp_dev->get_rx_gain(chan); + + LOGC(DDEV, INFO) << "Set RX gain to " << rx_gains[chan] << "dB (asked for " << db << "dB)"; + + return rx_gains[chan]; +} + +double uhd_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 uhd_device::rssiOffset(size_t chan) +{ + double rssiOffset; + dev_band_desc desc; + + if (chan >= rx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + get_dev_band_desc(desc); + rssiOffset = rx_gains[chan] + desc.rxgain2rssioffset_rel; + return rssiOffset; +} + +double uhd_device::setPowerAttenuation(int atten, size_t chan) { + double tx_power, db; + dev_band_desc desc; if (chan >= tx_gains.size()) { LOGC(DDEV, ALERT) << "Requested non-existent channel" << chan; return 0.0f; } + if (cfg->overrides.dl_gain_override) + return atten; // ensures caller does not apply digital attenuation + + get_dev_band_desc(desc); + tx_power = desc.nom_out_tx_power - atten; + db = TxPower2TxGain(desc, tx_power); + if (dev_type == UMTRX) { std::vector<std::string> gain_stages = usrp_dev->get_tx_gain_names(0); if (gain_stages[0] == "VGA" || gain_stages[0] == "PA") { @@ -274,40 +366,29 @@ double uhd_device::setTxGain(double db, size_t chan) tx_gains[chan] = usrp_dev->get_tx_gain(chan); - LOGC(DDEV, INFO) << "Set TX gain to " << tx_gains[chan] << "dB (asked for " << db << "dB)"; + 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 tx_gains[chan]; + return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]); } - -double uhd_device::setRxGain(double db, size_t chan) -{ - if (iface == MULTI_ARFCN) - chan = 0; - - if (chan >= rx_gains.size()) { +double uhd_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; } - usrp_dev->set_rx_gain(db, chan); - rx_gains[chan] = usrp_dev->get_rx_gain(chan); - - LOGC(DDEV, INFO) << "Set RX gain to " << rx_gains[chan] << "dB (asked for " << db << "dB)"; - - return rx_gains[chan]; + get_dev_band_desc(desc); + return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]); } -double uhd_device::getRxGain(size_t chan) +int uhd_device::getNominalTxPower(size_t chan) { - if (iface == MULTI_ARFCN) - chan = 0; - - if (chan >= rx_gains.size()) { - LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; - return 0.0f; - } + dev_band_desc desc; + get_dev_band_desc(desc); - return rx_gains[chan]; + return desc.nom_out_tx_power; } /* @@ -381,7 +462,6 @@ void uhd_device::set_channels(bool swap) if (dev_type != B200 && dev_type != B210) throw std::invalid_argument("Device does not support MCBTS"); dev_type = B2XX_MCBTS; - chans = 1; } if (chans > dev_param_map.at(dev_key(dev_type, tx_sps, rx_sps)).channels) @@ -414,15 +494,26 @@ void uhd_device::set_channels(bool swap) } } -int uhd_device::open(const std::string &args, int ref, bool swap_channels) +int uhd_device::open() { const char *refstr; + int clock_lock_attempts = 15; + + /* Register msg handler. Different APIs depending on UHD version */ +#ifdef USE_UHD_3_11 + uhd::log::add_logger("OsmoTRX", &uhd_log_handler); + uhd::log::set_log_level(uhd::log::debug); + uhd::log::set_console_level(uhd::log::off); + uhd::log::set_logger_level("OsmoTRX", uhd::log::debug); +#else + uhd::msg::register_handler(&uhd_msg_handler); +#endif // Find UHD devices - uhd::device_addr_t addr(args); + uhd::device_addr_t addr(cfg->dev_args); uhd::device_addrs_t dev_addrs = uhd::device::find(addr); if (dev_addrs.size() == 0) { - LOGC(DDEV, ALERT) << "No UHD devices found with address '" << args << "'"; + LOGC(DDEV, ALERT) << "No UHD devices found with address '" << cfg->dev_args << "'"; return -1; } @@ -431,7 +522,7 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) try { usrp_dev = uhd::usrp::multi_usrp::make(addr); } catch(uhd::key_error::exception &e) { - LOGC(DDEV, ALERT) << "UHD make failed, device " << args << ", exception:\n" << e.what(); + LOGC(DDEV, ALERT) << "UHD make failed, device " << cfg->dev_args << ", exception:\n" << e.what(); return -1; } @@ -439,14 +530,16 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) if (!parse_dev_type()) return -1; + update_band_dev(dev_key(dev_type, tx_sps, rx_sps)); + if ((dev_type == E3XX) && !uhd_e3xx_version_chk()) { LOGC(DDEV, ALERT) << "E3XX requires UHD 003.009.000 or greater"; return -1; } try { - set_channels(swap_channels); - } catch (const std::exception &e) { + set_channels(cfg->swap_channels); + } catch (const std::exception &e) { LOGC(DDEV, ALERT) << "Channel setting failed - " << e.what(); return -1; } @@ -462,7 +555,7 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) rx_gains.resize(chans); rx_buffers.resize(chans); - switch (ref) { + switch (cfg->clock_ref) { case REF_INTERNAL: refstr = "internal"; break; @@ -479,6 +572,19 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) usrp_dev->set_clock_source(refstr); + std::vector<std::string> sensor_names = usrp_dev->get_mboard_sensor_names(); + if (std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end()) { + LOGC(DDEV, INFO) << "Waiting for clock reference lock (max " << clock_lock_attempts << "s)..." << std::flush; + while (!usrp_dev->get_mboard_sensor("ref_locked", 0).to_bool() && clock_lock_attempts--) + sleep(1); + + if (!clock_lock_attempts) { + LOGC(DDEV, ALERT) << "Locking to external 10Mhz failed!"; + return -1; + } + } + LOGC(DDEV, INFO) << "Selected clock source is " << usrp_dev->get_clock_source(0); + try { set_rates(); } catch (const std::exception &e) { @@ -526,7 +632,33 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) init_gains(); // Print configuration - LOGC(DDEV, INFO) << "\n" << usrp_dev->get_pp_string(); + LOGC(DDEV, INFO) << "Device configuration: " << usrp_dev->get_pp_string(); + + if (cfg->overrides.dl_freq_override) { + uhd::tune_request_t treq_tx = uhd::tune_request_t(cfg->overrides.dl_freq, 0); + auto tres = usrp_dev->set_tx_freq(treq_tx, 0); + tx_freqs[0] = usrp_dev->get_tx_freq(0); + LOGCHAN(0, DDEV, INFO) << "OVERRIDE set_freq(" << tx_freqs[0] << ", TX): " << tres.to_pp_string() << std::endl; + } + + if (cfg->overrides.ul_freq_override) { + uhd::tune_request_t treq_rx = uhd::tune_request_t(cfg->overrides.ul_freq, 0); + auto tres = usrp_dev->set_rx_freq(treq_rx, 0); + rx_freqs[0] = usrp_dev->get_rx_freq(0); + LOGCHAN(0, DDEV, INFO) << "OVERRIDE set_freq(" << rx_freqs[0] << ", RX): " << tres.to_pp_string() << std::endl; + } + + if (cfg->overrides.ul_gain_override) { + usrp_dev->set_rx_gain(cfg->overrides.ul_gain, 0); + rx_gains[0] = usrp_dev->get_rx_gain(0); + LOGCHAN(0, DDEV, INFO) << " OVERRIDE RX gain:" << rx_gains[0] << std::endl; + } + + if (cfg->overrides.dl_gain_override) { + usrp_dev->set_tx_gain(cfg->overrides.dl_gain, 0); + tx_gains[0] = usrp_dev->get_tx_gain(0); + LOGCHAN(0, DDEV, INFO) << " OVERRIDE TX gain:" << tx_gains[0] << std::endl; + } if (iface == MULTI_ARFCN) return MULTI_ARFCN; @@ -604,10 +736,6 @@ bool uhd_device::start() return false; } -#ifndef USE_UHD_3_11 - // Register msg handler - uhd::msg::register_handler(&uhd_msg_handler); -#endif // Start asynchronous event (underrun check) loop async_event_thrd = new Thread(); async_event_thrd->start((void * (*)(void*))async_event_loop, (void*)this); @@ -638,6 +766,12 @@ bool uhd_device::stop() async_event_thrd->join(); delete async_event_thrd; + /* reset internal buffer timestamps */ + for (size_t i = 0; i < rx_buffers.size(); i++) + rx_buffers[i]->reset(); + + band_reset(); + started = false; return true; } @@ -685,7 +819,7 @@ int uhd_device::check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls) } int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun, - TIMESTAMP timestamp, bool *underrun, unsigned *RSSI) + TIMESTAMP timestamp, bool *underrun) { ssize_t rc; uhd::time_spec_t ts; @@ -742,7 +876,7 @@ int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun, for (size_t i = 0; i < rx_buffers.size(); i++) { rc = rx_buffers[i]->write((short *) &pkt_bufs[i].front(), num_smpls, - metadata.time_spec.to_ticks(rx_rate)); + ts.to_ticks(rx_rate)); // Continue on local overrun, exit on other errors if ((rc < 0)) { @@ -768,7 +902,7 @@ int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun, } int uhd_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun, - unsigned long long timestamp,bool isControl) + unsigned long long timestamp) { uhd::tx_metadata_t metadata; metadata.has_time_spec = true; @@ -778,12 +912,6 @@ int uhd_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun *underrun = false; - // No control packets - if (isControl) { - LOGC(DDEV, ERROR) << "Control packets not supported"; - return 0; - } - if (bufs.size() != chans) { LOGC(DDEV, ALERT) << "Invalid channel combination " << bufs.size(); return -1; @@ -882,6 +1010,14 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx) { std::vector<double> freqs; uhd::tune_result_t tres; + std::string str_dir = tx ? "Tx" : "Rx"; + + if (cfg->overrides.dl_freq_override || cfg->overrides.ul_freq_override) + return true; + + if (!update_band_from_freq(freq, chan, tx)) + return false; + uhd::tune_request_t treq = select_freq(freq, chan, tx); if (tx) { @@ -891,7 +1027,7 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx) tres = usrp_dev->set_rx_freq(treq, chan); rx_freqs[chan] = usrp_dev->get_rx_freq(chan); } - LOGC(DDEV, INFO) << "\n" << tres.to_pp_string() << std::endl; + LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << str_dir << "): " << tres.to_pp_string() << std::endl; if ((chans == 1) || ((chans == 2) && dev_type == UMTRX)) return true; @@ -911,7 +1047,7 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx) rx_freqs[!chan] = usrp_dev->get_rx_freq(!chan); } - LOGC(DDEV, INFO) << "\n" << tres.to_pp_string() << std::endl; + LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << str_dir << "): " << tres.to_pp_string() << std::endl; } return true; @@ -923,7 +1059,6 @@ bool uhd_device::setTxFreq(double wFreq, size_t chan) LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; return false; } - ScopedLock lock(tune_lock); return set_freq(wFreq, chan, true); } @@ -934,7 +1069,6 @@ bool uhd_device::setRxFreq(double wFreq, size_t chan) LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; return false; } - ScopedLock lock(tune_lock); return set_freq(wFreq, chan, false); } @@ -967,7 +1101,14 @@ bool uhd_device::setRxAntenna(const std::string &ant, size_t chan) return false; } - avail = usrp_dev->get_rx_antennas(chan); + /* UHD may throw a LookupError/IndexError here (see OS#4636) */ + try { + avail = usrp_dev->get_rx_antennas(chan); + } catch (const uhd::index_error &e) { + LOGC(DDEV, ALERT) << "UHD Error: " << e.what(); + return false; + } + if (std::find(avail.begin(), avail.end(), ant) == avail.end()) { LOGC(DDEV, ALERT) << "Requested non-existent Rx antenna " << ant << " on channel " << chan; LOGC(DDEV, INFO) << "Available Rx antennas: "; @@ -1003,7 +1144,14 @@ bool uhd_device::setTxAntenna(const std::string &ant, size_t chan) return false; } - avail = usrp_dev->get_tx_antennas(chan); + /* UHD may throw a LookupError/IndexError here (see OS#4636) */ + try { + avail = usrp_dev->get_tx_antennas(chan); + } catch (const uhd::index_error &e) { + LOGC(DDEV, ALERT) << "UHD Error: " << e.what(); + return false; + } + if (std::find(avail.begin(), avail.end(), ant) == avail.end()) { LOGC(DDEV, ALERT) << "Requested non-existent Tx antenna " << ant << " on channel " << chan; LOGC(DDEV, INFO) << "Available Tx antennas: "; @@ -1169,10 +1317,9 @@ std::string uhd_device::str_code(uhd::async_metadata_t metadata) return ost.str(); } -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) +#ifndef IPCMAGIC +RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg) { - return new uhd_device(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); + return new uhd_device(type, cfg); } +#endif diff --git a/Transceiver52M/device/uhd/UHDDevice.h b/Transceiver52M/device/uhd/UHDDevice.h index 944578a..f5e5232 100644 --- a/Transceiver52M/device/uhd/UHDDevice.h +++ b/Transceiver52M/device/uhd/UHDDevice.h @@ -30,6 +30,7 @@ #include "config.h" #endif +#include "bandmanager.h" #include "radioDevice.h" #include "smpl_buf.h" @@ -37,6 +38,10 @@ #include <uhd/property_tree.hpp> #include <uhd/usrp/multi_usrp.hpp> +extern "C" { +#include <osmocom/gsm/gsm_utils.h> +} + enum uhd_dev_type { USRP1, @@ -52,6 +57,34 @@ enum uhd_dev_type { LIMESDR, }; +struct dev_band_desc { + /* Maximum UHD Tx Gain which can be set/used without distorting the + output signal, and the resulting real output power measured when that + gain is used. Correct measured values only provided for B210 so far. */ + double nom_uhd_tx_gain; /* dB */ + double nom_out_tx_power; /* dBm */ + /* Factor used to infer base real RSSI offset on the Rx path based on current + configured RxGain. The resulting rssiOffset is added to the per burst + calculated energy in upper layers. These values were empirically + found and may change based on multiple factors, see OS#4468. + rssiOffset = rxGain + rxgain2rssioffset_rel; + */ + double rxgain2rssioffset_rel; /* dB */ +}; + +struct dev_desc { + unsigned channels; + double mcr; + double rate; + double offset; + std::string desc_str; +}; + +using dev_key = std::tuple<uhd_dev_type, int, int>; +using dev_band_key = std::tuple<uhd_dev_type, enum gsm_band>; +using power_map_t = std::map<dev_band_key, dev_band_desc>; +using dev_map_t = std::map<dev_key, dev_desc>; + /* uhd_device - UHD implementation of the Device interface. Timestamped samples are sent to and received from the device. An intermediate buffer @@ -59,25 +92,25 @@ enum uhd_dev_type { Events and errors such as underruns are reported asynchronously by the device and received in a separate thread. */ -class uhd_device : public RadioDevice { +class uhd_device : public RadioDevice, public band_manager<power_map_t, dev_map_t> { public: - uhd_device(size_t tx_sps, size_t rx_sps, InterfaceType type, - size_t chans, double offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths); - ~uhd_device(); - - int open(const std::string &args, int ref, bool swap_channels); - bool start(); - bool stop(); - bool restart(); - enum TxWindowType getWindowType() { return tx_window; } + uhd_device(InterfaceType iface, const struct trx_cfg *cfg); + ~uhd_device(); + + int open(); + bool start(); + bool stop(); + bool restart(); + enum TxWindowType getWindowType() + { + return tx_window; + } int readSamples(std::vector<short *> &bufs, int len, bool *overrun, - TIMESTAMP timestamp, bool *underrun, unsigned *RSSI); + TIMESTAMP timestamp, bool *underrun); int writeSamples(std::vector<short *> &bufs, int len, bool *underrun, - TIMESTAMP timestamp, bool isControl); + TIMESTAMP timestamp); bool updateAlignment(TIMESTAMP timestamp); @@ -94,10 +127,12 @@ public: double getRxGain(size_t chan); double maxRxGain(void) { return rx_gain_max; } double minRxGain(void) { return rx_gain_min; } + double rssiOffset(size_t chan); + + double setPowerAttenuation(int atten, size_t chan); + double getPowerAttenuation(size_t chan = 0); - double setTxGain(double db, size_t chan); - double maxTxGain(void) { return tx_gain_max; } - double minTxGain(void) { return tx_gain_min; } + int getNominalTxPower(size_t chan = 0); double getTxFreq(size_t chan); double getRxFreq(size_t chan); @@ -126,7 +161,7 @@ public: ERROR_UNHANDLED = -4, }; -private: +protected: uhd::usrp::multi_usrp::sptr usrp_dev; uhd::tx_streamer::sptr tx_stream; uhd::rx_streamer::sptr rx_stream; @@ -135,7 +170,6 @@ private: double tx_rate, rx_rate; - double tx_gain_min, tx_gain_max; double rx_gain_min, rx_gain_max; std::vector<double> tx_gains, rx_gains; @@ -170,5 +204,4 @@ private: bool set_freq(double freq, size_t chan, bool tx); Thread *async_event_thrd; - Mutex tune_lock; }; diff --git a/Transceiver52M/device/usrp1/Makefile.am b/Transceiver52M/device/usrp1/Makefile.am index 24760f7..ba93c7e 100644 --- a/Transceiver52M/device/usrp1/Makefile.am +++ b/Transceiver52M/device/usrp1/Makefile.am @@ -1,7 +1,7 @@ include $(top_srcdir)/Makefile.common AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common -AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(USRP_CFLAGS) +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(USRP_CFLAGS) rev2dir = $(datadir)/usrp/rev2 rev4dir = $(datadir)/usrp/rev4 diff --git a/Transceiver52M/device/usrp1/USRPDevice.cpp b/Transceiver52M/device/usrp1/USRPDevice.cpp index 63debee..63a9bcc 100644 --- a/Transceiver52M/device/usrp1/USRPDevice.cpp +++ b/Transceiver52M/device/usrp1/USRPDevice.cpp @@ -60,17 +60,14 @@ const dboardConfigType dboardConfig = TXA_RXB; const double USRPDevice::masterClockRate = 52.0e6; -USRPDevice::USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, - size_t chans, double lo_offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths): - RadioDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths) +USRPDevice::USRPDevice(InterfaceType iface, const struct trx_cfg *cfg) : RadioDevice(iface, cfg) { LOGC(DDEV, INFO) << "creating USRP device..."; decimRate = (unsigned int) round(masterClockRate/((GSMRATE) * (double) tx_sps)); actualSampleRate = masterClockRate/decimRate; rxGain = 0; + txGain = 0; /* * Undetermined delay b/w ping response timestamp and true @@ -93,7 +90,7 @@ USRPDevice::USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, #endif } -int USRPDevice::open(const std::string &, int, bool) +int USRPDevice::open() { writeLock.unlock(); @@ -204,8 +201,8 @@ bool USRPDevice::start() writeLock.unlock(); // Set gains to midpoint - setTxGain((minTxGain() + maxTxGain()) / 2); - setRxGain((minRxGain() + maxRxGain()) / 2); + setTxGain((m_dbTx->gain_min() + m_dbTx->gain_max()) / 2); + setRxGain((m_dbTx->gain_min() + m_dbTx->gain_max()) / 2); data = new short[currDataSize]; dataStart = 0; @@ -242,16 +239,6 @@ bool USRPDevice::stop() #endif } -double USRPDevice::maxTxGain() -{ - return m_dbTx->gain_max(); -} - -double USRPDevice::minTxGain() -{ - return m_dbTx->gain_min(); -} - double USRPDevice::maxRxGain() { return m_dbRx->gain_max(); @@ -270,19 +257,20 @@ double USRPDevice::setTxGain(double dB, size_t chan) } writeLock.lock(); - if (dB > maxTxGain()) - dB = maxTxGain(); - if (dB < minTxGain()) - dB = minTxGain(); + if (dB > m_dbTx->gain_max()) + dB = m_dbTx->gain_max(); + if (dB < m_dbTx->gain_min()) + dB = m_dbTx->gain_min(); LOGC(DDEV, NOTICE) << "Setting TX gain to " << dB << " dB."; if (!m_dbTx->set_gain(dB)) LOGC(DDEV, ERR) << "Error setting TX gain"; - + else + txGain = dB; writeLock.unlock(); - return dB; + return txGain; } @@ -305,10 +293,28 @@ double USRPDevice::setRxGain(double dB, size_t chan) if (!m_dbRx->set_gain(dB)) LOGC(DDEV, ERR) << "Error setting RX gain"; - + else + rxGain = dB; writeLock.unlock(); - return dB; + return rxGain; +} + +double USRPDevice::setPowerAttenuation(int atten, size_t chan) { + double rfGain; + rfGain = setTxGain(m_dbTx->gain_max() - atten, chan); + return m_dbTx->gain_max() - rfGain; +} +double USRPDevice::getPowerAttenuation(size_t chan) { + return m_dbTx->gain_max() - getTxGain(chan); +} + +int USRPDevice::getNominalTxPower(size_t chan) +{ + /* TODO: return value based on some experimentally generated table depending on + * band/arfcn, which is known here thanks to TXTUNE + */ + return 23; } bool USRPDevice::setRxAntenna(const std::string &ant, size_t chan) @@ -362,7 +368,7 @@ GSM::Time USRPDevice::minLatency() { // NOTE: Assumes sequential reads int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun, - TIMESTAMP timestamp, bool *underrun, unsigned *RSSI) + TIMESTAMP timestamp, bool *underrun) { #ifndef SWLOOPBACK if (!m_uRx) @@ -430,8 +436,10 @@ int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun, *underrun = true; LOGC(DDEV, DEBUG) << "UNDERRUN in TRX->USRP interface"; } - if (RSSI) *RSSI = (word0 >> 21) & 0x3f; - +#if 0 + /* FIXME: Do something with this ? */ + unsigned RSSI = (word0 >> 21) & 0x3f; +#endif if (!isAligned) continue; unsigned cursorStart = pktTimestamp - timeStart + dataStart; @@ -510,9 +518,8 @@ int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun, #endif } -int USRPDevice::writeSamples(std::vector<short *> &bufs, int len, - bool *underrun, unsigned long long timestamp, - bool isControl) +int USRPDevice::writeSamplesControl(std::vector<short *> &bufs, int len, + bool *underrun, unsigned long long timestamp, bool isControl) { writeLock.lock(); @@ -566,16 +573,21 @@ int USRPDevice::writeSamples(std::vector<short *> &bufs, int len, #endif } +int USRPDevice::writeSamples(std::vector<short *> &bufs, int len, + bool *underrun, unsigned long long timestamp) +{ + return writeSamplesControl(bufs, len, underrun, timestamp, false); +} + bool USRPDevice::updateAlignment(TIMESTAMP timestamp) { #ifndef SWLOOPBACK short data[] = {0x00,0x02,0x00,0x00}; - uint32_t *wordPtr = (uint32_t *) data; - *wordPtr = host_to_usrp_u32(*wordPtr); + /* FIXME: big endian */ bool tmpUnderrun; std::vector<short *> buf(1, data); - if (writeSamples(buf, 1, &tmpUnderrun, timestamp & 0x0ffffffffll, true)) { + if (writeSamplesControl(buf, 1, &tmpUnderrun, timestamp & 0x0ffffffffll, true)) { pingTimestamp = timestamp; return true; } @@ -642,22 +654,19 @@ bool USRPDevice::setTxFreq(double wFreq) { return true;}; bool USRPDevice::setRxFreq(double wFreq) { return true;}; #endif -RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, - InterfaceType iface, size_t chans, double lo_offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths) +RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg) { - if (tx_sps != rx_sps) { - LOGC(DDEV, ERROR) << "USRP1 requires tx_sps == rx_sps"; - return NULL; - } - if (chans != 1) { - LOGC(DDEV, ERROR) << "USRP1 supports only 1 channel"; - return NULL; - } - if (lo_offset != 0.0) { - LOGC(DDEV, ERROR) << "USRP1 doesn't support lo_offset"; - return NULL; - } - return new USRPDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); + if (cfg->tx_sps != cfg->rx_sps) { + LOGC(DDEV, ERROR) << "USRP1 requires tx_sps == rx_sps"; + return NULL; + } + if (cfg->num_chans != 1) { + LOGC(DDEV, ERROR) << "USRP1 supports only 1 channel"; + return NULL; + } + if (cfg->offset != 0.0) { + LOGC(DDEV, ERROR) << "USRP1 doesn't support lo_offset"; + return NULL; + } + return new USRPDevice(type, cfg); } diff --git a/Transceiver52M/device/usrp1/USRPDevice.h b/Transceiver52M/device/usrp1/USRPDevice.h index 4123c7d..4957ee6 100644 --- a/Transceiver52M/device/usrp1/USRPDevice.h +++ b/Transceiver52M/device/usrp1/USRPDevice.h @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0+ * * This software is distributed under multiple licenses; see the COPYING file in -* the main directory for licensing information for this specific distribuion. +* the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -80,6 +80,16 @@ private: unsigned long lastPktTimestamp; double rxGain; + double txGain; + + int writeSamplesControl(std::vector<short *> &bufs, int len, bool *underrun, + TIMESTAMP timestamp = 0xffffffff, bool isControl = false); + + /** sets the transmit chan gain, returns the gain setting **/ + double setTxGain(double dB, size_t chan = 0); + + /** get transmit gain */ + double getTxGain(size_t chan = 0) { return txGain; } #ifdef SWLOOPBACK short loopbackBuffer[1000000]; @@ -94,20 +104,21 @@ private: public: /** Object constructor */ - USRPDevice(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); + USRPDevice(InterfaceType iface, const struct trx_cfg *cfg); - /** Instantiate the USRP */ - int open(const std::string &, int, bool); + /** Instantiate the USRP */ + int open(); - /** Start the USRP */ - bool start(); + /** Start the USRP */ + bool start(); - /** Stop the USRP */ - bool stop(); + /** Stop the USRP */ + bool stop(); - enum TxWindowType getWindowType() { return TX_WINDOW_USRP1; } + enum TxWindowType getWindowType() + { + return TX_WINDOW_USRP1; + } /** Read samples from the USRP. @@ -116,23 +127,20 @@ private: @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 USRP does not have data to transmit, e.g. data not being sent fast enough - @param RSSI The received signal strength of the read result @return The number of samples actually read */ int readSamples(std::vector<short *> &buf, int len, bool *overrun, - TIMESTAMP timestamp = 0xffffffff, bool *underrun = NULL, - unsigned *RSSI = NULL); + TIMESTAMP timestamp = 0xffffffff, bool *underrun = NULL); /** Write samples to the USRP. @param buf Contains the data to be written. @param len number of samples to write. @param underrun Set if USRP 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. - @param isControl Set if data is a control packet, e.g. a ping command @return The number of samples actually written */ int writeSamples(std::vector<short *> &bufs, int len, bool *underrun, - TIMESTAMP timestamp = 0xffffffff, bool isControl = false); + TIMESTAMP timestamp = 0xffffffff); /** Update the alignment between the read and write timestamps */ bool updateAlignment(TIMESTAMP timestamp); @@ -167,14 +175,12 @@ private: /** return minimum Rx Gain **/ double minRxGain(void); - /** sets the transmit chan gain, returns the gain setting **/ - double setTxGain(double dB, size_t chan = 0); + double rssiOffset(size_t chan) { return 0.0f; } /* FIXME: not implemented */ - /** return maximum Tx Gain **/ - double maxTxGain(void); + double setPowerAttenuation(int atten, size_t chan); + double getPowerAttenuation(size_t chan=0); - /** return minimum Rx Gain **/ - double minTxGain(void); + int getNominalTxPower(size_t chan = 0); /** sets the RX path to use, returns true if successful and false otherwise */ bool setRxAntenna(const std::string &ant, size_t chan = 0); diff --git a/Transceiver52M/grgsm_vitac/constants.h b/Transceiver52M/grgsm_vitac/constants.h new file mode 100644 index 0000000..27bf6f4 --- /dev/null +++ b/Transceiver52M/grgsm_vitac/constants.h @@ -0,0 +1,149 @@ +#pragma once +/* -*- c++ -*- */ +/* + * @file + * @author (C) 2009-2017 by Piotr Krysik <ptrkrysik@gmail.com> + * @section LICENSE + * + * Gr-gsm is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * Gr-gsm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gr-gsm; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include <complex> + +#define gr_complex std::complex<float> + + +#define GSM_SYMBOL_RATE (1625000.0/6.0) //symbols per second +#define GSM_SYMBOL_PERIOD (1.0/GSM_SYMBOL_RATE) //seconds per symbol + +//Burst timing +#define TAIL_BITS 3 +#define GUARD_BITS 8 +#define GUARD_FRACTIONAL 0.25 //fractional part of guard period +#define GUARD_PERIOD GUARD_BITS + GUARD_FRACTIONAL +#define DATA_BITS 57 //size of 1 data block in normal burst +#define STEALING_BIT 1 +#define N_TRAIN_BITS 26 +#define N_SYNC_BITS 64 +#define N_ACCESS_BITS 41 +#define USEFUL_BITS 142 //(2*(DATA_BITS+STEALING_BIT) + N_TRAIN_BITS ) +#define FCCH_BITS USEFUL_BITS +#define BURST_SIZE (USEFUL_BITS+2*TAIL_BITS) +#define ACCESS_BURST_SIZE 88 +#define PROCESSED_CHUNK BURST_SIZE+2*GUARD_PERIOD + +#define SCH_DATA_LEN 39 +#define TS_BITS (TAIL_BITS+USEFUL_BITS+TAIL_BITS+GUARD_BITS) //a full TS (156 bits) +#define TS_PER_FRAME 8 +#define FRAME_BITS (TS_PER_FRAME * TS_BITS + 2) // 156.25 * 8 +#define FCCH_POS TAIL_BITS +#define SYNC_POS (TAIL_BITS + 39) +#define TRAIN_POS ( TAIL_BITS + (DATA_BITS+STEALING_BIT) + 5) //first 5 bits of a training sequence + //aren't used for channel impulse response estimation +#define TRAIN_BEGINNING 5 +#define SAFETY_MARGIN 6 // + +#define FCCH_HITS_NEEDED (USEFUL_BITS - 4) +#define FCCH_MAX_MISSES 1 +#define FCCH_MAX_FREQ_OFFSET 100 + +#define CHAN_IMP_RESP_LENGTH 5 + +#define MAX_SCH_ERRORS 10 //maximum number of subsequent sch errors after which gsm receiver goes to find_next_fcch state + +typedef enum { empty, fcch_burst, sch_burst, normal_burst, rach_burst, dummy, dummy_or_normal, normal_or_noise } burst_type; +typedef enum { unknown, multiframe_26, multiframe_51 } multiframe_type; + +static const unsigned char SYNC_BITS[] = { + 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, + 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1 +}; + +static const unsigned char ACCESS_BITS [] = { + 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, + 0, 1, 1, 1, 1, 0, 0, 0 +}; + +const unsigned FCCH_FRAMES[] = { 0, 10, 20, 30, 40 }; +const unsigned SCH_FRAMES[] = { 1, 11, 21, 31, 41 }; + +const unsigned BCCH_FRAMES[] = { 2, 3, 4, 5 }; //!!the receiver shouldn't care about logical + //!!channels so this will be removed from this header +const unsigned TEST_CCH_FRAMES[] = { 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 16, 17, 18, 19, 22, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 44, 45, 46, 47, 48, 49 }; +const unsigned TRAFFIC_CHANNEL_F[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 }; +const unsigned TEST51[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 }; + + +#define TSC0 0 +#define TSC1 1 +#define TSC2 2 +#define TSC3 3 +#define TSC4 4 +#define TSC5 5 +#define TSC6 6 +#define TSC7 7 +#define TS_DUMMY 8 + +#define TRAIN_SEQ_NUM 9 + +#define TIMESLOT0 0 +#define TIMESLOT1 1 +#define TIMESLOT2 2 +#define TIMESLOT3 3 +#define TIMESLOT4 4 +#define TIMESLOT5 5 +#define TIMESLOT6 6 +#define TIMESLOT7 7 + + +static const unsigned char train_seq[TRAIN_SEQ_NUM][N_TRAIN_BITS] = { + {0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1}, + {0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1}, + {0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0}, + {0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0}, + {0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1}, + {0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0}, + {1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1}, + {1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0}, + {0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1} // DUMMY +}; + + +//Dummy burst 0xFB 76 0A 4E 09 10 1F 1C 5C 5C 57 4A 33 39 E9 F1 2F A8 +static const unsigned char dummy_burst[] = { + 0, 0, 0, + 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, + 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, + 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 0, 0, + + 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, + 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, + 0, 0, 0, 1, 0, 1, + + 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, + 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, + 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, + 1, 1, 1, 0, 1, 0, 1, 0, + 0, 0, 0 +}; diff --git a/Transceiver52M/grgsm_vitac/grgsm_vitac.cpp b/Transceiver52M/grgsm_vitac/grgsm_vitac.cpp new file mode 100644 index 0000000..2016541 --- /dev/null +++ b/Transceiver52M/grgsm_vitac/grgsm_vitac.cpp @@ -0,0 +1,305 @@ +/* -*- c++ -*- */ +/* + * @file + * @author (C) 2009-2017 by Piotr Krysik <ptrkrysik@gmail.com> + * @author Contributions by sysmocom - s.f.m.c. GmbH / Eric Wild <ewild@sysmocom.de> + * @section LICENSE + * + * Gr-gsm is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * Gr-gsm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gr-gsm; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "constants.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <complex> + + +#include <algorithm> +#include <string.h> +#include <iostream> +#include <numeric> +#include <vector> +#include <fstream> + +#include "viterbi_detector.h" +#include "grgsm_vitac.h" + +gr_complex d_acc_training_seq[N_ACCESS_BITS]; ///<encoded training sequence of a RACH burst +gr_complex d_sch_training_seq[N_SYNC_BITS]; ///<encoded training sequence of a SCH burst +gr_complex d_norm_training_seq[TRAIN_SEQ_NUM][N_TRAIN_BITS]; ///<encoded training sequences of a normal and dummy burst +const int d_chan_imp_length = CHAN_IMP_RESP_LENGTH; + +void initvita() +{ + /** + * Prepare SCH sequence bits + * + * (TS_BITS + 2 * GUARD_PERIOD) + * Burst and two guard periods + * (one guard period is an arbitrary overlap) + */ + gmsk_mapper(SYNC_BITS, N_SYNC_BITS, d_sch_training_seq, gr_complex(0.0, -1.0)); + for (auto &i : d_sch_training_seq) + i = conj(i); + + /* ab */ + gmsk_mapper(ACCESS_BITS, N_ACCESS_BITS, d_acc_training_seq, gr_complex(0.0, -1.0)); + for (auto &i : d_acc_training_seq) + i = conj(i); + + /* Prepare bits of training sequences */ + for (int i = 0; i < TRAIN_SEQ_NUM; i++) { + /** + * If first bit of the sequence is 0 + * => first symbol is 1, else -1 + */ + gr_complex startpoint = train_seq[i][0] == 0 ? gr_complex(1.0, 0.0) : gr_complex(-1.0, 0.0); + gmsk_mapper(train_seq[i], N_TRAIN_BITS, d_norm_training_seq[i], startpoint); + for (auto &i : d_norm_training_seq[i]) + i = conj(i); + } +} + +template <unsigned int burst_size> +NO_UBSAN static void detect_burst_generic(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, + char *output_binary, int ss) +{ + std::vector<gr_complex> rhh_temp(CHAN_IMP_RESP_LENGTH * d_OSR); + unsigned int stop_states[2] = { 4, 12 }; + gr_complex filtered_burst[burst_size]; + gr_complex rhh[CHAN_IMP_RESP_LENGTH]; + float output[burst_size]; + int start_state = ss; + + autocorrelation(chan_imp_resp, &rhh_temp[0], d_chan_imp_length * d_OSR); + for (int ii = 0; ii < d_chan_imp_length; ii++) + rhh[ii] = conj(rhh_temp[ii * d_OSR]); + + mafi(&input[burst_start], burst_size, chan_imp_resp, d_chan_imp_length * d_OSR, filtered_burst); + + viterbi_detector(filtered_burst, burst_size, rhh, start_state, stop_states, 2, output); + + for (unsigned int i = 0; i < burst_size; i++) + output_binary[i] = output[i] > 0 ? -127 : 127; // pre flip bits! +} + +NO_UBSAN void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, + int ss) +{ + return detect_burst_generic<BURST_SIZE>(input, chan_imp_resp, burst_start, output_binary, ss); +} +NO_UBSAN void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, + int ss) +{ + return detect_burst_generic<8 + 41 + 36 + 3>(input, chan_imp_resp, burst_start, output_binary, ss); +} + +NO_UBSAN void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary) +{ + return detect_burst_nb(input, chan_imp_resp, burst_start, output_binary, 3); +} +NO_UBSAN void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary) +{ + return detect_burst_ab(input, chan_imp_resp, burst_start, output_binary, 3); +} + +void gmsk_mapper(const unsigned char *input, int nitems, gr_complex *gmsk_output, gr_complex start_point) +{ + gr_complex j = gr_complex(0.0, 1.0); + gmsk_output[0] = start_point; + + int previous_symbol = 2 * input[0] - 1; + int current_symbol; + int encoded_symbol; + + for (int i = 1; i < nitems; i++) { + /* Change bits representation to NRZ */ + current_symbol = 2 * input[i] - 1; + + /* Differentially encode */ + encoded_symbol = current_symbol * previous_symbol; + + /* And do GMSK mapping */ + gmsk_output[i] = j * gr_complex(encoded_symbol, 0.0) * gmsk_output[i - 1]; + + previous_symbol = current_symbol; + } +} + +gr_complex correlate_sequence(const gr_complex *sequence, int length, const gr_complex *input) +{ + gr_complex result(0.0, 0.0); + + for (int ii = 0; ii < length; ii++) + result += sequence[ii] * input[ii * d_OSR]; + + return conj(result) / gr_complex(length, 0); +} + +/* Computes autocorrelation for positive arguments */ +inline void autocorrelation(const gr_complex *input, gr_complex *out, int nitems) +{ + for (int k = nitems - 1; k >= 0; k--) { + out[k] = gr_complex(0, 0); + for (int i = k; i < nitems; i++) + out[k] += input[i] * conj(input[i - k]); + } +} + +inline void mafi(const gr_complex *input, int nitems, gr_complex *filter, int filter_length, gr_complex *output) +{ + for (int n = 0; n < nitems; n++) { + int a = n * d_OSR; + output[n] = 0; + + for (int ii = 0; ii < filter_length; ii++) { + if ((a + ii) >= nitems * d_OSR) + break; + + output[n] += input[a + ii] * filter[ii]; + } + } +} + +int get_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, int search_start_pos, int search_stop_pos, + gr_complex *tseq, int tseqlen, float *corr_max) +{ + const int num_search_windows = search_stop_pos - search_start_pos; + const int power_search_window_len = d_chan_imp_length * d_OSR; + std::vector<float> window_energy_buffer; + std::vector<float> power_buffer; + std::vector<gr_complex> correlation_buffer; + + power_buffer.reserve(num_search_windows); + correlation_buffer.reserve(num_search_windows); + window_energy_buffer.reserve(num_search_windows); + + for (int ii = 0; ii < num_search_windows; ii++) { + gr_complex correlation = correlate_sequence(tseq, tseqlen, &input[search_start_pos + ii]); + correlation_buffer.push_back(correlation); + power_buffer.push_back(std::pow(abs(correlation), 2)); + } + + /* Compute window energies */ + float windowSum = 0; + + // first window + for (int i = 0; i < power_search_window_len; i++) { + windowSum += power_buffer[i]; + } + window_energy_buffer.push_back(windowSum); + + // slide windows + for (int i = power_search_window_len; i < num_search_windows; i++) { + windowSum += power_buffer[i] - power_buffer[i - power_search_window_len]; + window_energy_buffer.push_back(windowSum); + } + + int strongest_window_nr = std::max_element(window_energy_buffer.begin(), window_energy_buffer.end()) - + window_energy_buffer.begin(); + + float max_correlation = 0; + for (int ii = 0; ii < power_search_window_len; ii++) { + gr_complex correlation = correlation_buffer[strongest_window_nr + ii]; + if (abs(correlation) > max_correlation) + max_correlation = abs(correlation); + chan_imp_resp[ii] = correlation; + } + + *corr_max = max_correlation; + + /** + * Compute first sample position, which corresponds + * to the first sample of the impulse response + */ + return search_start_pos + strongest_window_nr; +} + +/* +8 ext tail bits +41 sync seq +36 encrypted bits +3 tail bits +68.25 extended tail bits (!) + +center at 8+5 (actually known tb -> known isi, start at 8?) FIXME +*/ +int get_access_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int max_delay) +{ + const int search_center = 8 + 5; + const int search_start_pos = (search_center - 5) * d_OSR + 1; + const int search_stop_pos = (search_center + 5 + d_chan_imp_length + max_delay) * d_OSR; + const auto tseq = &d_acc_training_seq[TRAIN_BEGINNING]; + const auto tseqlen = N_ACCESS_BITS - (2 * TRAIN_BEGINNING); + return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, corr_max) - + search_center * d_OSR; +} + +/* + +3 + 57 + 1 + 26 + 1 + 57 + 3 + 8.25 + +search center = 3 + 57 + 1 + 5 (due to tsc 5+16+5 split) +this is +-5 samples around (+5 beginning) of truncated t16 tsc + +*/ +int get_norm_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int bcc) +{ + const int search_center = TRAIN_POS; + const int search_start_pos = (search_center - 5) * d_OSR + 1; + const int search_stop_pos = (search_center + 5 + d_chan_imp_length) * d_OSR; + const auto tseq = &d_norm_training_seq[bcc][TRAIN_BEGINNING]; + const auto tseqlen = N_TRAIN_BITS - (2 * TRAIN_BEGINNING); + return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, corr_max) - + search_center * d_OSR; +} + +/* + +3 tail | 39 data | 64 tsc | 39 data | 3 tail | 8.25 guard +start 3+39 - 10 +end 3+39 + SYNC_SEARCH_RANGE + +*/ +int get_sch_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp) +{ + const int search_center = SYNC_POS + TRAIN_BEGINNING; + const int search_start_pos = (search_center - 10) * d_OSR; + const int search_stop_pos = (search_center + SYNC_SEARCH_RANGE) * d_OSR; + const auto tseq = &d_sch_training_seq[TRAIN_BEGINNING]; + const auto tseqlen = N_SYNC_BITS - (2 * TRAIN_BEGINNING); + + // strongest_window_nr + chan_imp_resp_center + SYNC_POS *d_OSR - 48 * d_OSR - 2 * d_OSR + 2 ; + float corr_max; + return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, &corr_max) - + search_center * d_OSR; + ; +} + +int get_sch_buffer_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, unsigned int len, float *corr_max) +{ + const auto tseqlen = N_SYNC_BITS - (2 * TRAIN_BEGINNING); + const int search_center = SYNC_POS + TRAIN_BEGINNING; + const int search_start_pos = 0; + // FIXME: proper end offset + const int search_stop_pos = len - (N_SYNC_BITS * 8); + auto tseq = &d_sch_training_seq[TRAIN_BEGINNING]; + + return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, corr_max) - + search_center * d_OSR; +}
\ No newline at end of file diff --git a/Transceiver52M/grgsm_vitac/grgsm_vitac.h b/Transceiver52M/grgsm_vitac/grgsm_vitac.h new file mode 100644 index 0000000..c1e470c --- /dev/null +++ b/Transceiver52M/grgsm_vitac/grgsm_vitac.h @@ -0,0 +1,89 @@ +#pragma once +/* -*- c++ -*- */ +/* + * @file + * @author (C) 2009-2017 by Piotr Krysik <ptrkrysik@gmail.com> + * @section LICENSE + * + * Gr-gsm is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * Gr-gsm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gr-gsm; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include <vector> +#include "constants.h" + +/* may only be used for for the DEFINITIONS! +* see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91664 +*/ +#if defined(__has_attribute) +#if __has_attribute(target_clones) && defined(__x86_64) && true +#define MULTI_VER_TARGET_ATTR __attribute__((target_clones("avx", "sse4.2", "sse3", "sse2", "sse", "default"))) +#else +#define MULTI_VER_TARGET_ATTR +#endif +#endif + +/* ... but apparently clang disagrees... */ +#if defined(__clang__) +#define MULTI_VER_TARGET_ATTR_CLANGONLY MULTI_VER_TARGET_ATTR +#else +#define MULTI_VER_TARGET_ATTR_CLANGONLY +#endif + +/* ancient gcc < 8 has no attribute, clang always pretends to be gcc 4 */ +#if !defined(__clang__) && __GNUC__ < 8 +#define NO_UBSAN __attribute__((no_sanitize_undefined)) +#else +#if defined(__has_attribute) +#if __has_attribute(no_sanitize) +#define NO_UBSAN __attribute__((no_sanitize("undefined"))) +#endif +#else +#define NO_UBSAN +#endif +#endif + +#define SYNC_SEARCH_RANGE 30 +const int d_OSR(4); + +void initvita(); + +int process_vita_burst(gr_complex *input, int tsc, unsigned char *output_binary); +int process_vita_sc_burst(gr_complex *input, int tsc, unsigned char *output_binary, int *offset); + +void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, int ss); +void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, int ss); +void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary); +void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary); + +void gmsk_mapper(const unsigned char *input, int nitems, gr_complex *gmsk_output, gr_complex start_point); +gr_complex correlate_sequence(const gr_complex *sequence, int length, const gr_complex *input); +inline void autocorrelation(const gr_complex *input, gr_complex *out, int nitems); +inline void mafi(const gr_complex *input, int nitems, gr_complex *filter, int filter_length, gr_complex *output); +int get_sch_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp); +int get_norm_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int bcc); +int get_sch_buffer_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, unsigned int len, float *corr_max); +int get_access_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int max_delay); + +enum class btype { NB, SCH }; +struct fdata { + btype t; + unsigned int fn; + int tn; + int bcc; + std::string fpath; + std::vector<gr_complex> data; + unsigned int data_start_offset; +};
\ No newline at end of file diff --git a/Transceiver52M/grgsm_vitac/viterbi_detector.cc b/Transceiver52M/grgsm_vitac/viterbi_detector.cc new file mode 100644 index 0000000..4ac4505 --- /dev/null +++ b/Transceiver52M/grgsm_vitac/viterbi_detector.cc @@ -0,0 +1,392 @@ +/* -*- c++ -*- */ +/* + * @file + * @author (C) 2009 by Piotr Krysik <ptrkrysik@gmail.com> + * @section LICENSE + * + * Gr-gsm is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * Gr-gsm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gr-gsm; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +/* + * viterbi_detector: + * This part does the detection of received sequnece. + * Employed algorithm is viterbi Maximum Likehood Sequence Estimation. + * At this moment it gives hard decisions on the output, but + * it was designed with soft decisions in mind. + * + * SYNTAX: void viterbi_detector( + * const gr_complex * input, + * unsigned int samples_num, + * gr_complex * rhh, + * unsigned int start_state, + * const unsigned int * stop_states, + * unsigned int stops_num, + * float * output) + * + * INPUT: input: Complex received signal afted matched filtering. + * samples_num: Number of samples in the input table. + * rhh: The autocorrelation of the estimated channel + * impulse response. + * start_state: Number of the start point. In GSM each burst + * starts with sequence of three bits (0,0,0) which + * indicates start point of the algorithm. + * stop_states: Table with numbers of possible stop states. + * stops_num: Number of possible stop states + * + * + * OUTPUT: output: Differentially decoded hard output of the algorithm: + * -1 for logical "0" and 1 for logical "1" + * + * SUB_FUNC: none + * + * TEST(S): Tested with real world normal burst. + */ + +#include "constants.h" +#include <cmath> + +#define PATHS_NUM (1 << (CHAN_IMP_RESP_LENGTH-1)) + +void viterbi_detector(const gr_complex * input, unsigned int samples_num, gr_complex * rhh, unsigned int start_state, const unsigned int * stop_states, unsigned int stops_num, float * output) +{ + float increment[8]; + float path_metrics1[16]; + float path_metrics2[16]; + float paths_difference; + float * new_path_metrics; + float * old_path_metrics; + float * tmp; + float trans_table[BURST_SIZE][16]; + float pm_candidate1, pm_candidate2; + bool real_imag; + float input_symbol_real, input_symbol_imag; + unsigned int i, sample_nr; + +/* +* Setup first path metrics, so only state pointed by start_state is possible. +* Start_state metric is equal to zero, the rest is written with some very low value, +* which makes them practically impossible to occur. +*/ + for(i=0; i<PATHS_NUM; i++){ + path_metrics1[i]=(-10e30); + } + path_metrics1[start_state]=0; + +/* +* Compute Increment - a table of values which does not change for subsequent input samples. +* Increment is table of reference levels for computation of branch metrics: +* branch metric = (+/-)received_sample (+/-) reference_level +*/ + increment[0] = -rhh[1].imag() -rhh[2].real() -rhh[3].imag() +rhh[4].real(); + increment[1] = rhh[1].imag() -rhh[2].real() -rhh[3].imag() +rhh[4].real(); + increment[2] = -rhh[1].imag() +rhh[2].real() -rhh[3].imag() +rhh[4].real(); + increment[3] = rhh[1].imag() +rhh[2].real() -rhh[3].imag() +rhh[4].real(); + increment[4] = -rhh[1].imag() -rhh[2].real() +rhh[3].imag() +rhh[4].real(); + increment[5] = rhh[1].imag() -rhh[2].real() +rhh[3].imag() +rhh[4].real(); + increment[6] = -rhh[1].imag() +rhh[2].real() +rhh[3].imag() +rhh[4].real(); + increment[7] = rhh[1].imag() +rhh[2].real() +rhh[3].imag() +rhh[4].real(); + + +/* +* Computation of path metrics and decisions (Add-Compare-Select). +* It's composed of two parts: one for odd input samples (imaginary numbers) +* and one for even samples (real numbers). +* Each part is composed of independent (parallelisable) statements like +* this one: +* pm_candidate1 = old_path_metrics[0] -input_symbol_imag +increment[2]; +* pm_candidate2 = old_path_metrics[8] -input_symbol_imag -increment[5]; +* paths_difference=pm_candidate2-pm_candidate1; +* new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; +* trans_table[sample_nr][1] = paths_difference; +* This is very good point for optimisations (SIMD or OpenMP) as it's most time +* consuming part of this function. +*/ + sample_nr=0; + old_path_metrics=path_metrics1; + new_path_metrics=path_metrics2; + while(sample_nr<samples_num){ + //Processing imag states + real_imag=1; + input_symbol_imag = input[sample_nr].imag(); + + pm_candidate1 = old_path_metrics[0] +input_symbol_imag -increment[2]; + pm_candidate2 = old_path_metrics[8] +input_symbol_imag +increment[5]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[0]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][0] = paths_difference; + + pm_candidate1 = old_path_metrics[0] -input_symbol_imag +increment[2]; + pm_candidate2 = old_path_metrics[8] -input_symbol_imag -increment[5]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][1] = paths_difference; + + pm_candidate1 = old_path_metrics[1] +input_symbol_imag -increment[3]; + pm_candidate2 = old_path_metrics[9] +input_symbol_imag +increment[4]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[2]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][2] = paths_difference; + + pm_candidate1 = old_path_metrics[1] -input_symbol_imag +increment[3]; + pm_candidate2 = old_path_metrics[9] -input_symbol_imag -increment[4]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[3]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][3] = paths_difference; + + pm_candidate1 = old_path_metrics[2] +input_symbol_imag -increment[0]; + pm_candidate2 = old_path_metrics[10] +input_symbol_imag +increment[7]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[4]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][4] = paths_difference; + + pm_candidate1 = old_path_metrics[2] -input_symbol_imag +increment[0]; + pm_candidate2 = old_path_metrics[10] -input_symbol_imag -increment[7]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[5]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][5] = paths_difference; + + pm_candidate1 = old_path_metrics[3] +input_symbol_imag -increment[1]; + pm_candidate2 = old_path_metrics[11] +input_symbol_imag +increment[6]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[6]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][6] = paths_difference; + + pm_candidate1 = old_path_metrics[3] -input_symbol_imag +increment[1]; + pm_candidate2 = old_path_metrics[11] -input_symbol_imag -increment[6]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[7]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][7] = paths_difference; + + pm_candidate1 = old_path_metrics[4] +input_symbol_imag -increment[6]; + pm_candidate2 = old_path_metrics[12] +input_symbol_imag +increment[1]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[8]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][8] = paths_difference; + + pm_candidate1 = old_path_metrics[4] -input_symbol_imag +increment[6]; + pm_candidate2 = old_path_metrics[12] -input_symbol_imag -increment[1]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[9]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][9] = paths_difference; + + pm_candidate1 = old_path_metrics[5] +input_symbol_imag -increment[7]; + pm_candidate2 = old_path_metrics[13] +input_symbol_imag +increment[0]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[10]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][10] = paths_difference; + + pm_candidate1 = old_path_metrics[5] -input_symbol_imag +increment[7]; + pm_candidate2 = old_path_metrics[13] -input_symbol_imag -increment[0]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[11]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][11] = paths_difference; + + pm_candidate1 = old_path_metrics[6] +input_symbol_imag -increment[4]; + pm_candidate2 = old_path_metrics[14] +input_symbol_imag +increment[3]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[12]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][12] = paths_difference; + + pm_candidate1 = old_path_metrics[6] -input_symbol_imag +increment[4]; + pm_candidate2 = old_path_metrics[14] -input_symbol_imag -increment[3]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[13]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][13] = paths_difference; + + pm_candidate1 = old_path_metrics[7] +input_symbol_imag -increment[5]; + pm_candidate2 = old_path_metrics[15] +input_symbol_imag +increment[2]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[14]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][14] = paths_difference; + + pm_candidate1 = old_path_metrics[7] -input_symbol_imag +increment[5]; + pm_candidate2 = old_path_metrics[15] -input_symbol_imag -increment[2]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[15]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][15] = paths_difference; + tmp=old_path_metrics; + old_path_metrics=new_path_metrics; + new_path_metrics=tmp; + + sample_nr++; + if(sample_nr==samples_num) + break; + + //Processing real states + real_imag=0; + input_symbol_real = input[sample_nr].real(); + + pm_candidate1 = old_path_metrics[0] -input_symbol_real -increment[7]; + pm_candidate2 = old_path_metrics[8] -input_symbol_real +increment[0]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[0]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][0] = paths_difference; + + pm_candidate1 = old_path_metrics[0] +input_symbol_real +increment[7]; + pm_candidate2 = old_path_metrics[8] +input_symbol_real -increment[0]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][1] = paths_difference; + + pm_candidate1 = old_path_metrics[1] -input_symbol_real -increment[6]; + pm_candidate2 = old_path_metrics[9] -input_symbol_real +increment[1]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[2]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][2] = paths_difference; + + pm_candidate1 = old_path_metrics[1] +input_symbol_real +increment[6]; + pm_candidate2 = old_path_metrics[9] +input_symbol_real -increment[1]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[3]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][3] = paths_difference; + + pm_candidate1 = old_path_metrics[2] -input_symbol_real -increment[5]; + pm_candidate2 = old_path_metrics[10] -input_symbol_real +increment[2]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[4]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][4] = paths_difference; + + pm_candidate1 = old_path_metrics[2] +input_symbol_real +increment[5]; + pm_candidate2 = old_path_metrics[10] +input_symbol_real -increment[2]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[5]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][5] = paths_difference; + + pm_candidate1 = old_path_metrics[3] -input_symbol_real -increment[4]; + pm_candidate2 = old_path_metrics[11] -input_symbol_real +increment[3]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[6]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][6] = paths_difference; + + pm_candidate1 = old_path_metrics[3] +input_symbol_real +increment[4]; + pm_candidate2 = old_path_metrics[11] +input_symbol_real -increment[3]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[7]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][7] = paths_difference; + + pm_candidate1 = old_path_metrics[4] -input_symbol_real -increment[3]; + pm_candidate2 = old_path_metrics[12] -input_symbol_real +increment[4]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[8]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][8] = paths_difference; + + pm_candidate1 = old_path_metrics[4] +input_symbol_real +increment[3]; + pm_candidate2 = old_path_metrics[12] +input_symbol_real -increment[4]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[9]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][9] = paths_difference; + + pm_candidate1 = old_path_metrics[5] -input_symbol_real -increment[2]; + pm_candidate2 = old_path_metrics[13] -input_symbol_real +increment[5]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[10]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][10] = paths_difference; + + pm_candidate1 = old_path_metrics[5] +input_symbol_real +increment[2]; + pm_candidate2 = old_path_metrics[13] +input_symbol_real -increment[5]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[11]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][11] = paths_difference; + + pm_candidate1 = old_path_metrics[6] -input_symbol_real -increment[1]; + pm_candidate2 = old_path_metrics[14] -input_symbol_real +increment[6]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[12]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][12] = paths_difference; + + pm_candidate1 = old_path_metrics[6] +input_symbol_real +increment[1]; + pm_candidate2 = old_path_metrics[14] +input_symbol_real -increment[6]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[13]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][13] = paths_difference; + + pm_candidate1 = old_path_metrics[7] -input_symbol_real -increment[0]; + pm_candidate2 = old_path_metrics[15] -input_symbol_real +increment[7]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[14]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][14] = paths_difference; + + pm_candidate1 = old_path_metrics[7] +input_symbol_real +increment[0]; + pm_candidate2 = old_path_metrics[15] +input_symbol_real -increment[7]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[15]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][15] = paths_difference; + + tmp=old_path_metrics; + old_path_metrics=new_path_metrics; + new_path_metrics=tmp; + + sample_nr++; + } + +/* +* Find the best from the stop states by comparing their path metrics. +* Not every stop state is always possible, so we are searching in +* a subset of them. +*/ + unsigned int best_stop_state; + float stop_state_metric, max_stop_state_metric; + best_stop_state = stop_states[0]; + max_stop_state_metric = old_path_metrics[best_stop_state]; + for(i=1; i< stops_num; i++){ + stop_state_metric = old_path_metrics[stop_states[i]]; + if(stop_state_metric > max_stop_state_metric){ + max_stop_state_metric = stop_state_metric; + best_stop_state = stop_states[i]; + } + } + +/* +* This table was generated with hope that it gives a litle speedup during +* traceback stage. +* Received bit is related to the number of state in the trellis. +* I've numbered states so their parity (number of ones) is related +* to a received bit. +*/ + static const unsigned int parity_table[PATHS_NUM] = { 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, }; + +/* +* Table of previous states in the trellis diagram. +* For GMSK modulation every state has two previous states. +* Example: +* previous_state_nr1 = prev_table[current_state_nr][0] +* previous_state_nr2 = prev_table[current_state_nr][1] +*/ + static const unsigned int prev_table[PATHS_NUM][2] = { {0,8}, {0,8}, {1,9}, {1,9}, {2,10}, {2,10}, {3,11}, {3,11}, {4,12}, {4,12}, {5,13}, {5,13}, {6,14}, {6,14}, {7,15}, {7,15}, }; + +/* +* Traceback and differential decoding of received sequence. +* Decisions stored in trans_table are used to restore best path in the trellis. +*/ + sample_nr=samples_num; + unsigned int state_nr=best_stop_state; + unsigned int decision; + bool out_bit=0; + + while(sample_nr>0){ + sample_nr--; + decision = (trans_table[sample_nr][state_nr]>0); + + if(decision != out_bit) + output[sample_nr]=-trans_table[sample_nr][state_nr]; + else + output[sample_nr]=trans_table[sample_nr][state_nr]; + + out_bit = out_bit ^ real_imag ^ parity_table[state_nr]; + state_nr = prev_table[state_nr][decision]; + real_imag = !real_imag; + } +} diff --git a/Transceiver52M/grgsm_vitac/viterbi_detector.h b/Transceiver52M/grgsm_vitac/viterbi_detector.h new file mode 100644 index 0000000..92bc2a8 --- /dev/null +++ b/Transceiver52M/grgsm_vitac/viterbi_detector.h @@ -0,0 +1,64 @@ +/* -*- c++ -*- */ +/* + * @file + * @author (C) 2009 Piotr Krysik <ptrkrysik@gmail.com> + * @section LICENSE + * + * Gr-gsm is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * Gr-gsm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gr-gsm; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +/* + * viterbi_detector: + * This part does the detection of received sequnece. + * Employed algorithm is viterbi Maximum Likehood Sequence Estimation. + * At this moment it gives hard decisions on the output, but + * it was designed with soft decisions in mind. + * + * SYNTAX: void viterbi_detector( + * const gr_complex * input, + * unsigned int samples_num, + * gr_complex * rhh, + * unsigned int start_state, + * const unsigned int * stop_states, + * unsigned int stops_num, + * float * output) + * + * INPUT: input: Complex received signal afted matched filtering. + * samples_num: Number of samples in the input table. + * rhh: The autocorrelation of the estimated channel + * impulse response. + * start_state: Number of the start point. In GSM each burst + * starts with sequence of three bits (0,0,0) which + * indicates start point of the algorithm. + * stop_states: Table with numbers of possible stop states. + * stops_num: Number of possible stop states + * + * + * OUTPUT: output: Differentially decoded hard output of the algorithm: + * -1 for logical "0" and 1 for logical "1" + * + * SUB_FUNC: none + * + * TEST(S): Tested with real world normal burst. + */ + +#ifndef INCLUDED_VITERBI_DETECTOR_H +#define INCLUDED_VITERBI_DETECTOR_H +#include "constants.h" + +void viterbi_detector(const gr_complex * input, unsigned int samples_num, gr_complex * rhh, unsigned int start_state, const unsigned int * stop_states, unsigned int stops_num, float * output); + +#endif /* INCLUDED_VITERBI_DETECTOR_H */ diff --git a/Transceiver52M/ms/bladerf_specific.h b/Transceiver52M/ms/bladerf_specific.h new file mode 100644 index 0000000..9db8bf0 --- /dev/null +++ b/Transceiver52M/ms/bladerf_specific.h @@ -0,0 +1,479 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "itrq.h" +#include <atomic> +#include <complex> +#include <cstdint> +#include <functional> +#include <iostream> +#include <cassert> +#include <cstring> + +#include <libbladeRF.h> +#include <Timeval.h> +#include <unistd.h> + +const size_t BLADE_BUFFER_SIZE = 1024 * 1; +const size_t BLADE_NUM_BUFFERS = 32 * 1; +const size_t NUM_TRANSFERS = 16 * 2; +const int SAMPLE_SCALE_FACTOR = 15; // actually 16 but sigproc complains about clipping.. + +// see https://en.cppreference.com/w/cpp/language/parameter_pack "Brace-enclosed initializers" example +template <typename Arg, typename... Args> +void expand_args(std::ostream &out, Arg &&arg, Args &&...args) +{ + out << '(' << std::forward<Arg>(arg); + (void)(int[]){ 0, (void((out << "," << std::forward<Args>(args))), 0)... }; + out << ')' << std::endl; +} + +template <class R, class... Args> +using RvalFunc = R (*)(Args...); + +template <class R, class... Args> +R exec_and_check(RvalFunc<R, Args...> func, const char *fname, const char *finame, const char *funcname, int line, + Args... args) +{ + R rval = func(std::forward<Args>(args)...); + if (rval != 0) { + std::cerr << ((rval >= 0) ? "OK:" : bladerf_strerror(rval)) << ':' << finame << ':' << line << ':' + << funcname << ':' << fname; + expand_args(std::cerr, args...); + } + return rval; +} + +// only macros can pass a func name string +#define blade_check(func, ...) exec_and_check(func, #func, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) + +#pragma pack(push, 1) +using blade_sample_type = std::complex<int16_t>; +enum class blade_speed_buffer_type { HS, SS }; +template <blade_speed_buffer_type T> +struct blade_usb_message { + uint32_t reserved; + uint64_t ts; + uint32_t meta_flags; + blade_sample_type d[(T == blade_speed_buffer_type::SS ? 512 : 256) - 4]; +}; + +static_assert(sizeof(blade_usb_message<blade_speed_buffer_type::SS>) == 2048, "blade buffer mismatch!"); +static_assert(sizeof(blade_usb_message<blade_speed_buffer_type::HS>) == 1024, "blade buffer mismatch!"); +template <unsigned int SZ, blade_speed_buffer_type T> +struct blade_otw_buffer { + static_assert((SZ >= 2 && !(SZ % 2)), "min size is 2x usb buffer!"); + blade_usb_message<T> m[SZ]; + int actual_samples_per_msg() + { + return sizeof(blade_usb_message<T>::d) / sizeof(typeof(blade_usb_message<T>::d[0])); + } + int actual_samples_per_buffer() + { + return SZ * actual_samples_per_msg(); + } + int samples_per_buffer() + { + return SZ * sizeof(blade_usb_message<T>) / sizeof(typeof(blade_usb_message<T>::d[0])); + } + int num_msgs_per_buffer() + { + return SZ; + } + auto get_first_ts() + { + return m[0].ts; + } + constexpr auto *getsampleoffset(int ofs) + { + auto full = ofs / actual_samples_per_msg(); + auto rem = ofs % actual_samples_per_msg(); + return &m[full].d[rem]; + } + int readall(blade_sample_type *outaddr) + { + blade_sample_type *addr = outaddr; + for (unsigned int i = 0; i < SZ; i++) { + memcpy(addr, &m[i].d[0], actual_samples_per_msg() * sizeof(blade_sample_type)); + addr += actual_samples_per_msg(); + } + return actual_samples_per_buffer(); + } + int read_n(blade_sample_type *outaddr, int start, int num) + { + assert((start + num) <= actual_samples_per_buffer()); + assert(start >= 0); + + if (!num) + return 0; + + // which buffer? + int start_buf_idx = (start > 0) ? start / actual_samples_per_msg() : 0; + // offset from actual buffer start + auto start_offset_in_buf = (start - (start_buf_idx * actual_samples_per_msg())); + auto samp_rem_in_first_buf = actual_samples_per_msg() - start_offset_in_buf; + auto remaining_first_buf = num > samp_rem_in_first_buf ? samp_rem_in_first_buf : num; + + memcpy(outaddr, &m[start_buf_idx].d[start_offset_in_buf], + remaining_first_buf * sizeof(blade_sample_type)); + outaddr += remaining_first_buf; + + auto remaining = num - remaining_first_buf; + + if (!remaining) + return num; + + start_buf_idx++; + + auto rem_full_bufs = remaining / actual_samples_per_msg(); + remaining -= rem_full_bufs * actual_samples_per_msg(); + + for (int i = 0; i < rem_full_bufs; i++) { + memcpy(outaddr, &m[start_buf_idx++].d[0], actual_samples_per_msg() * sizeof(blade_sample_type)); + outaddr += actual_samples_per_msg(); + } + + if (remaining) + memcpy(outaddr, &m[start_buf_idx].d[0], remaining * sizeof(blade_sample_type)); + return num; + } + int write_n_burst(blade_sample_type *in, int num, uint64_t first_ts) + { + assert(num <= actual_samples_per_buffer()); + int len_rem = num; + for (unsigned int i = 0; i < SZ; i++) { + m[i] = {}; + m[i].ts = first_ts + i * actual_samples_per_msg(); + if (len_rem) { + int max_to_copy = + len_rem > actual_samples_per_msg() ? actual_samples_per_msg() : len_rem; + memcpy(&m[i].d[0], in, max_to_copy * sizeof(blade_sample_type)); + len_rem -= max_to_copy; + in += actual_samples_per_msg(); + } + } + return num; + } +}; +#pragma pack(pop) + +template <unsigned int SZ, blade_speed_buffer_type T> +struct blade_otw_buffer_helper { + static_assert((SZ >= 1024 && ((SZ & (SZ - 1)) == 0)), "only buffer size multiples of 1024 allowed!"); + static blade_otw_buffer<SZ / 512, T> x; +}; + +using dev_buf_t = typeof(blade_otw_buffer_helper<BLADE_BUFFER_SIZE, blade_speed_buffer_type::SS>::x); +// using buf_in_use = blade_otw_buffer<2, blade_speed_buffer_type::SS>; +using bh_fn_t = std::function<int(dev_buf_t *)>; + +template <typename T> +struct blade_hw { + struct bladerf *dev; + struct bladerf_stream *rx_stream; + struct bladerf_stream *tx_stream; + // using pkt2buf = blade_otw_buffer<2, blade_speed_buffer_type::SS>; + using tx_buf_q_type = spsc_cond_timeout<BLADE_NUM_BUFFERS, dev_buf_t *, true, false>; + const unsigned int rxFullScale, txFullScale; + const int rxtxdelay; + + float rxgain, txgain; + static std::atomic<bool> stop_lower_threads_flag; + double rxfreq_cache, txfreq_cache; + + struct ms_trx_config { + int tx_freq; + int rx_freq; + int sample_rate; + int bandwidth; + + public: + ms_trx_config() : tx_freq(881e6), rx_freq(926e6), sample_rate(((1625e3 / 6) * 4)), bandwidth(1e6) + { + } + } cfg; + + struct buf_mgmt { + void **rx_samples; + void **tx_samples; + tx_buf_q_type bufptrqueue; + + } buf_mgmt; + + virtual ~blade_hw() + { + close_device(); + } + blade_hw() + : rxFullScale(2047), txFullScale(2047), rxtxdelay(-60), rxgain(30), txgain(30), rxfreq_cache(0), + txfreq_cache(0) + { + } + + void close_device() + { + if (dev) { + if (tx_stream) { + bladerf_deinit_stream(tx_stream); + } + + if (rx_stream) { + bladerf_deinit_stream(rx_stream); + } + + bladerf_enable_module(dev, BLADERF_MODULE_RX, false); + bladerf_enable_module(dev, BLADERF_MODULE_TX, false); + + bladerf_close(dev); + dev = NULL; + } + } + + int init_device(bh_fn_t rxh, bh_fn_t txh) + { + struct bladerf_rational_rate rate = { 0, static_cast<uint64_t>((1625e3 * 4)) * 64, 6 * 64 }, actual; + + bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_DEBUG); + bladerf_set_usb_reset_on_open(true); + + blade_check(bladerf_open, &dev, ""); + if (!dev) { + std::cerr << "open failed, device missing?" << std::endl; + exit(0); + } + if (bladerf_device_speed(dev) != bladerf_dev_speed::BLADERF_DEVICE_SPEED_SUPER) { + std::cerr << "open failed, only superspeed (usb3) supported!" << std::endl; + return -1; + } + + blade_check(bladerf_set_tuning_mode, dev, bladerf_tuning_mode::BLADERF_TUNING_MODE_FPGA); + + bool is_locked; + blade_check(bladerf_set_pll_enable, dev, true); + uint64_t refclock = 10000000UL; + blade_check(bladerf_set_pll_refclk, dev, refclock); + for (int i = 0; i < 20; i++) { + usleep(50 * 1000); + bladerf_get_pll_lock_state(dev, &is_locked); + + if (is_locked) + break; + } + if (!is_locked) { + std::cerr << "unable to lock refclk!" << std::endl; + return -1; + } + + blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_RX(0), &rate, &actual); + blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_TX(0), &rate, &actual); + + blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)cfg.rx_freq); + blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)cfg.tx_freq); + + blade_check(bladerf_set_bandwidth, dev, BLADERF_CHANNEL_RX(0), (bladerf_bandwidth)cfg.bandwidth, + (bladerf_bandwidth *)NULL); + blade_check(bladerf_set_bandwidth, dev, BLADERF_CHANNEL_TX(0), (bladerf_bandwidth)cfg.bandwidth, + (bladerf_bandwidth *)NULL); + + blade_check(bladerf_set_gain_mode, dev, BLADERF_CHANNEL_RX(0), BLADERF_GAIN_MGC); + setRxGain(rxgain, 0); + setTxGain(txgain, 0); + usleep(1000); + + bladerf_set_stream_timeout(dev, BLADERF_TX, 10); + bladerf_set_stream_timeout(dev, BLADERF_RX, 10); + + blade_check(bladerf_init_stream, &rx_stream, dev, getrxcb(rxh), &buf_mgmt.rx_samples, BLADE_NUM_BUFFERS, + BLADERF_FORMAT_SC16_Q11_META, BLADE_BUFFER_SIZE, NUM_TRANSFERS, (void *)this); + + blade_check(bladerf_init_stream, &tx_stream, dev, gettxcb(txh), &buf_mgmt.tx_samples, BLADE_NUM_BUFFERS, + BLADERF_FORMAT_SC16_Q11_META, BLADE_BUFFER_SIZE, NUM_TRANSFERS, (void *)this); + + for (unsigned int i = 0; i < BLADE_NUM_BUFFERS; i++) { + auto cur_buffer = reinterpret_cast<tx_buf_q_type::elem_t *>(buf_mgmt.tx_samples); + buf_mgmt.bufptrqueue.spsc_push(&cur_buffer[i]); + } + + return 0; + } + + void actually_enable_streams() + { + blade_check(bladerf_enable_module, dev, BLADERF_MODULE_RX, true); + usleep(1000); + blade_check(bladerf_enable_module, dev, BLADERF_MODULE_TX, true); + } + + bool tuneTx(double freq, size_t chan = 0) + { + if (txfreq_cache == freq) + return true; + msleep(15); + blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)freq); + txfreq_cache = freq; + msleep(15); + return true; + }; + bool tuneRx(double freq, size_t chan = 0) + { + if (rxfreq_cache == freq) + return true; + msleep(15); + blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)freq); + rxfreq_cache = freq; + msleep(15); + return true; + }; + bool tuneRxOffset(double offset, size_t chan = 0) + { + return true; + }; + + double setRxGain(double dB, size_t chan = 0) + { + rxgain = dB; + msleep(15); + blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_RX(0), (bladerf_gain)dB); + msleep(15); + return dB; + }; + double setTxGain(double dB, size_t chan = 0) + { + txgain = dB; + msleep(15); + blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_TX(0), (bladerf_gain)dB); + msleep(15); + return dB; + }; + int setPowerAttenuation(int atten, size_t chan = 0) + { + return atten; + }; + + static void check_timestamp(dev_buf_t *rcd) + { + static bool first = true; + static uint64_t last_ts; + if (first) { + first = false; + last_ts = rcd->m[0].ts; + } else if (last_ts + rcd->actual_samples_per_buffer() != rcd->m[0].ts) { + std::cerr << "RX Overrun!" << last_ts << " " << rcd->actual_samples_per_buffer() << " " + << last_ts + rcd->actual_samples_per_buffer() << " " << rcd->m[0].ts << std::endl; + last_ts = rcd->m[0].ts; + } else { + last_ts = rcd->m[0].ts; + } + } + + bladerf_stream_cb getrxcb(bh_fn_t rxbh) + { + // C cb -> no capture! + static auto rxbhfn = rxbh; + return [](struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta, + void *samples, size_t num_samples, void *user_data) -> void * { + // struct blade_hw *trx = (struct blade_hw *)user_data; + static int to_skip = 0; + dev_buf_t *rcd = (dev_buf_t *)samples; + + if (stop_lower_threads_flag) + return BLADERF_STREAM_SHUTDOWN; + + if (to_skip < 120) // prevents weird overflows on startup + to_skip++; + else { + check_timestamp(rcd); + rxbhfn(rcd); + } + + return samples; + }; + } + bladerf_stream_cb gettxcb(bh_fn_t txbh) + { + // C cb -> no capture! + static auto txbhfn = txbh; + return [](struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta, + void *samples, size_t num_samples, void *user_data) -> void * { + struct blade_hw *trx = (struct blade_hw *)user_data; + auto ptr = reinterpret_cast<tx_buf_q_type::elem_t>(samples); + + if (samples) // put buffer address back into queue, ready to be reused + trx->buf_mgmt.bufptrqueue.spsc_push(&ptr); + + if (stop_lower_threads_flag) + return BLADERF_STREAM_SHUTDOWN; + + return BLADERF_STREAM_NO_DATA; + }; + } + + auto get_rx_burst_handler_fn(bh_fn_t burst_handler) + { + using thist = decltype(this); + auto fn = [](void *args) -> void * { + thist t = reinterpret_cast<thist>(args); + int status = 0; + if (!stop_lower_threads_flag) + status = bladerf_stream(t->rx_stream, BLADERF_RX_X1); + if (status < 0) + std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl; + + return 0; + }; + return fn; + } + auto get_tx_burst_handler_fn(bh_fn_t burst_handler) + { + using thist = decltype(this); + auto fn = [](void *args) -> void * { + thist t = reinterpret_cast<thist>(args); + int status = 0; + if (!stop_lower_threads_flag) + status = bladerf_stream(t->tx_stream, BLADERF_TX_X1); + if (status < 0) + std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl; + + return 0; + }; + return fn; + } + + void submit_burst_ts(blade_sample_type *buffer, int len, uint64_t ts) + { + tx_buf_q_type::elem_t rcd; + + // exit by submitting a dummy buffer to assure the libbladerf stream mutex is happy (thread!) + if (!buffer) { + bladerf_submit_stream_buffer(tx_stream, (void *)BLADERF_STREAM_SHUTDOWN, 1000); + return; + } + + //get empty bufer from list + while (!buf_mgmt.bufptrqueue.spsc_pop(&rcd)) + buf_mgmt.bufptrqueue.spsc_prep_pop(); + assert(rcd != nullptr); + + rcd->write_n_burst(buffer, len, ts + rxtxdelay); // blade xa4 specific delay! + blade_check(bladerf_submit_stream_buffer_nb, tx_stream, (void *)rcd); + } +}; diff --git a/Transceiver52M/ms/itrq.h b/Transceiver52M/ms/itrq.h new file mode 100644 index 0000000..69ff515 --- /dev/null +++ b/Transceiver52M/ms/itrq.h @@ -0,0 +1,249 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <atomic> + +#include <condition_variable> +#include <mutex> +#include <sys/eventfd.h> +#include <unistd.h> + +namespace spsc_detail +{ +template <bool block_read, bool block_write> +class spsc_cond_timeout_detail { + std::condition_variable cond_r, cond_w; + std::mutex lr, lw; + std::atomic_int r_flag, w_flag; + const int timeout_ms = 200; + + public: + explicit spsc_cond_timeout_detail() : r_flag(0), w_flag(0) + { + } + + ~spsc_cond_timeout_detail() + { + } + + ssize_t spsc_check_r() + { + std::unique_lock<std::mutex> lk(lr); + if (cond_r.wait_for(lk, std::chrono::milliseconds(timeout_ms), [&] { return r_flag != 0; })) { + r_flag--; + return 1; + } else { + return 0; + } + } + ssize_t spsc_check_w() + { + std::unique_lock<std::mutex> lk(lw); + if (cond_w.wait_for(lk, std::chrono::milliseconds(timeout_ms), [&] { return w_flag != 0; })) { + w_flag--; + return 1; + } else { + return 0; + } + } + void spsc_notify_r() + { + std::unique_lock<std::mutex> lk(lr); + r_flag++; + cond_r.notify_one(); + } + void spsc_notify_w() + { + std::unique_lock<std::mutex> lk(lw); + w_flag++; + cond_w.notify_one(); + } +}; + +template <bool block_read, bool block_write> +class spsc_cond_detail { + std::condition_variable cond_r, cond_w; + std::mutex lr, lw; + std::atomic_int r_flag, w_flag; + + public: + explicit spsc_cond_detail() : r_flag(0), w_flag(0) + { + } + + ~spsc_cond_detail() + { + } + + ssize_t spsc_check_r() + { + std::unique_lock<std::mutex> lk(lr); + while (r_flag == 0) + cond_r.wait(lk); + r_flag--; + return 1; + } + ssize_t spsc_check_w() + { + std::unique_lock<std::mutex> lk(lw); + while (w_flag == 0) + cond_w.wait(lk); + w_flag--; + return 1; + } + void spsc_notify_r() + { + std::unique_lock<std::mutex> lk(lr); + r_flag++; + cond_r.notify_one(); + } + void spsc_notify_w() + { + std::unique_lock<std::mutex> lk(lw); + w_flag++; + cond_w.notify_one(); + } +}; + +// originally designed for select loop integration +template <bool block_read, bool block_write> +class spsc_efd_detail { + int efd_r, efd_w; /* eventfds used to block/notify readers/writers */ + + public: + explicit spsc_efd_detail() + : efd_r(eventfd(0, block_read ? 0 : EFD_NONBLOCK)), efd_w(eventfd(1, block_write ? 0 : EFD_NONBLOCK)) + { + } + + ~spsc_efd_detail() + { + close(efd_r); + close(efd_w); + } + + ssize_t spsc_check_r() + { + uint64_t efdr; + return read(efd_r, &efdr, sizeof(uint64_t)); + } + ssize_t spsc_check_w() + { + uint64_t efdr; + return read(efd_w, &efdr, sizeof(uint64_t)); + } + void spsc_notify_r() + { + uint64_t efdu = 1; + write(efd_r, &efdu, sizeof(uint64_t)); + } + void spsc_notify_w() + { + uint64_t efdu = 1; + write(efd_w, &efdu, sizeof(uint64_t)); + } + int get_r_efd() + { + return efd_r; + } + int get_w_efd() + { + return efd_w; + } +}; + +template <unsigned int SZ, typename ELEM, bool block_read, bool block_write, template <bool, bool> class T> +class spsc : public T<block_read, block_write> { + static_assert(SZ > 0, "queues need a size..."); + std::atomic<unsigned int> readptr; + std::atomic<unsigned int> writeptr; + + ELEM buf[SZ]; + + public: + using base_t = T<block_read, block_write>; + using elem_t = ELEM; + explicit spsc() : readptr(0), writeptr(0) + { + } + + ~spsc() + { + } + + /*! Adds element to the queue by copying the data. + * \param[in] elem input buffer, must match the originally configured queue buffer size!. + * \returns true if queue was not full and element was successfully pushed */ + bool spsc_push(const ELEM *elem) + { + size_t cur_wp, cur_rp; + cur_wp = writeptr.load(std::memory_order_relaxed); + cur_rp = readptr.load(std::memory_order_acquire); + if ((cur_wp + 1) % SZ == cur_rp) { + if (block_write) + base_t::spsc_check_w(); /* blocks, ensures next (!) call succeeds */ + return false; + } + buf[cur_wp] = *elem; + writeptr.store((cur_wp + 1) % SZ, std::memory_order_release); + if (block_read) + base_t::spsc_notify_r(); /* fine after release */ + return true; + } + + /*! Removes element from the queue by copying the data. + * \param[in] elem output buffer, must match the originally configured queue buffer size!. + * \returns true if queue was not empty and element was successfully removed */ + bool spsc_pop(ELEM *elem) + { + size_t cur_wp, cur_rp; + cur_wp = writeptr.load(std::memory_order_acquire); + cur_rp = readptr.load(std::memory_order_relaxed); + + if (cur_wp == cur_rp) /* blocks via prep_pop */ + return false; + + *elem = buf[cur_rp]; + readptr.store((cur_rp + 1) % SZ, std::memory_order_release); + if (block_write) + base_t::spsc_notify_w(); + return true; + } + + /*! Reads the read-fd of the queue, which, depending on settings passed on queue creation, blocks. + * This function can be used to deliberately wait for a non-empty queue on the read side. + * \returns result of reading the fd. */ + ssize_t spsc_prep_pop() + { + return base_t::spsc_check_r(); + } +}; + +} // namespace spsc_detail + +template <unsigned int SZ, typename ELEM, bool block_read, bool block_write> +class spsc_evfd : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_efd_detail> {}; +template <unsigned int SZ, typename ELEM, bool block_read, bool block_write> +class spsc_cond : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_cond_detail> {}; +template <unsigned int SZ, typename ELEM, bool block_read, bool block_write> +class spsc_cond_timeout + : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_cond_timeout_detail> {};
\ No newline at end of file diff --git a/Transceiver52M/ms/l1ctl_server.c b/Transceiver52M/ms/l1ctl_server.c new file mode 100644 index 0000000..9e9083f --- /dev/null +++ b/Transceiver52M/ms/l1ctl_server.c @@ -0,0 +1,275 @@ +/* + * OsmocomBB <-> SDR connection bridge + * UNIX socket server for L1CTL + * + * (C) 2013 by Sylvain Munaut <tnt@246tNt.com> + * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com> + * (C) 2022 by by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#include <sys/un.h> +#include <arpa/inet.h> +#include <sys/socket.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/write_queue.h> + +#include <osmocom/bb/trxcon/logging.h> +#include <osmocom/bb/trxcon/l1ctl_server.h> + +#define LOGP_CLI(cli, cat, level, fmt, args...) LOGP(cat, level, "%s" fmt, (cli)->log_prefix, ##args) + +static int l1ctl_client_read_cb(struct osmo_fd *ofd) +{ + struct l1ctl_client *client = (struct l1ctl_client *)ofd->data; + struct msgb *msg; + uint16_t len; + int rc; + + /* Attempt to read from socket */ + rc = read(ofd->fd, &len, L1CTL_MSG_LEN_FIELD); + if (rc != L1CTL_MSG_LEN_FIELD) { + if (rc <= 0) { + LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: read() failed (rc=%d): %s\n", rc, + strerror(errno)); + } else { + LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: short read\n"); + rc = -EIO; + } + l1ctl_client_conn_close(client); + return rc; + } + + /* Check message length */ + len = ntohs(len); + if (len > L1CTL_LENGTH) { + LOGP_CLI(client, DL1D, LOGL_ERROR, "Length is too big: %u\n", len); + return -EINVAL; + } + + /* Allocate a new msg */ + msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM, L1CTL_HEADROOM, "l1ctl_rx_msg"); + if (!msg) { + LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to allocate msg\n"); + return -ENOMEM; + } + + msg->l1h = msgb_put(msg, len); + rc = read(ofd->fd, msg->l1h, msgb_l1len(msg)); + if (rc != len) { + LOGP_CLI(client, DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: %s\n", len, rc, strerror(errno)); + msgb_free(msg); + return rc; + } + + /* Debug print */ + LOGP_CLI(client, DL1D, LOGL_DEBUG, "RX: '%s'\n", osmo_hexdump(msg->data, msg->len)); + + /* Call L1CTL handler */ + client->server->cfg->conn_read_cb(client, msg); + + return 0; +} + +static int l1ctl_client_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + struct l1ctl_client *client = (struct l1ctl_client *)ofd->data; + int len; + + if (ofd->fd <= 0) + return -EINVAL; + + len = write(ofd->fd, msg->data, msg->len); + if (len != msg->len) { + LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to write data: written (%d) < msg_len (%d)\n", len, + msg->len); + return -1; + } + + return 0; +} + +/* Connection handler */ +static int l1ctl_server_conn_cb(struct osmo_fd *sfd, unsigned int flags) +{ + struct l1ctl_server *server = (struct l1ctl_server *)sfd->data; + struct l1ctl_client *client; + int rc, client_fd; + + client_fd = accept(sfd->fd, NULL, NULL); + if (client_fd < 0) { + LOGP(DL1C, LOGL_ERROR, + "Failed to accept() a new connection: " + "%s\n", + strerror(errno)); + return client_fd; + } + + if (server->cfg->num_clients_max > 0 /* 0 means unlimited */ && + server->num_clients >= server->cfg->num_clients_max) { + LOGP(DL1C, LOGL_NOTICE, + "L1CTL server cannot accept more " + "than %u connection(s)\n", + server->cfg->num_clients_max); + close(client_fd); + return -ENOMEM; + } + + client = talloc_zero(server, struct l1ctl_client); + if (client == NULL) { + LOGP(DL1C, LOGL_ERROR, "Failed to allocate an L1CTL client\n"); + close(client_fd); + return -ENOMEM; + } + + /* Init the client's write queue */ + osmo_wqueue_init(&client->wq, 100); + INIT_LLIST_HEAD(&client->wq.bfd.list); + + client->wq.write_cb = &l1ctl_client_write_cb; + client->wq.read_cb = &l1ctl_client_read_cb; + osmo_fd_setup(&client->wq.bfd, client_fd, OSMO_FD_READ, &osmo_wqueue_bfd_cb, client, 0); + + /* Register the client's write queue */ + rc = osmo_fd_register(&client->wq.bfd); + if (rc != 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to register a new connection fd\n"); + close(client->wq.bfd.fd); + talloc_free(client); + return rc; + } + + llist_add_tail(&client->list, &server->clients); + client->id = server->next_client_id++; + client->server = server; + server->num_clients++; + + LOGP(DL1C, LOGL_NOTICE, "L1CTL server got a new connection (id=%u)\n", client->id); + + if (client->server->cfg->conn_accept_cb != NULL) + client->server->cfg->conn_accept_cb(client); + + return 0; +} + +int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg) +{ + uint8_t *len; + + /* Debug print */ + LOGP_CLI(client, DL1D, LOGL_DEBUG, "TX: '%s'\n", osmo_hexdump(msg->data, msg->len)); + + if (msg->l1h != msg->data) + LOGP_CLI(client, DL1D, LOGL_INFO, "Message L1 header != Message Data\n"); + + /* Prepend 16-bit length before sending */ + len = msgb_push(msg, L1CTL_MSG_LEN_FIELD); + osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len); + + if (osmo_wqueue_enqueue(&client->wq, msg) != 0) { + LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to enqueue msg!\n"); + msgb_free(msg); + return -EIO; + } + + return 0; +} + +void l1ctl_client_conn_close(struct l1ctl_client *client) +{ + struct l1ctl_server *server = client->server; + + LOGP_CLI(client, DL1C, LOGL_NOTICE, "Closing L1CTL connection\n"); + + if (server->cfg->conn_close_cb != NULL) + server->cfg->conn_close_cb(client); + + /* Close connection socket */ + osmo_fd_unregister(&client->wq.bfd); + close(client->wq.bfd.fd); + client->wq.bfd.fd = -1; + + /* Clear pending messages */ + osmo_wqueue_clear(&client->wq); + + client->server->num_clients--; + llist_del(&client->list); + talloc_free(client); + + /* If this was the last client, reset the client IDs generator to 0. + * This way avoid assigning huge unreadable client IDs like 26545. */ + if (llist_empty(&server->clients)) + server->next_client_id = 0; +} + +struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg) +{ + struct l1ctl_server *server; + int rc; + + LOGP(DL1C, LOGL_NOTICE, "Init L1CTL server (sock_path=%s)\n", cfg->sock_path); + + server = talloc(ctx, struct l1ctl_server); + OSMO_ASSERT(server != NULL); + + *server = (struct l1ctl_server){ + .clients = LLIST_HEAD_INIT(server->clients), + .cfg = cfg, + }; + + /* conn_read_cb shall not be NULL */ + OSMO_ASSERT(cfg->conn_read_cb != NULL); + + /* Bind connection handler */ + osmo_fd_setup(&server->ofd, -1, OSMO_FD_READ, &l1ctl_server_conn_cb, server, 0); + + rc = osmo_sock_unix_init_ofd(&server->ofd, SOCK_STREAM, 0, cfg->sock_path, OSMO_SOCK_F_BIND); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n", strerror(errno)); + talloc_free(server); + return NULL; + } + + return server; +} + +void l1ctl_server_free(struct l1ctl_server *server) +{ + LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL server\n"); + + /* Close all client connections */ + while (!llist_empty(&server->clients)) { + struct l1ctl_client *client = llist_entry(server->clients.next, struct l1ctl_client, list); + l1ctl_client_conn_close(client); + } + + /* Unbind listening socket */ + if (server->ofd.fd != -1) { + osmo_fd_unregister(&server->ofd); + close(server->ofd.fd); + server->ofd.fd = -1; + } + + talloc_free(server); +} diff --git a/Transceiver52M/ms/l1ctl_server_cb.cpp b/Transceiver52M/ms/l1ctl_server_cb.cpp new file mode 100644 index 0000000..42f64ac --- /dev/null +++ b/Transceiver52M/ms/l1ctl_server_cb.cpp @@ -0,0 +1,71 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +extern "C" { +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> +#include <osmocom/bb/trxcon/l1ctl_server.h> +} +#include "ms_trxcon_if.h" + +static struct l1ctl_server_cfg server_cfg; +static struct l1ctl_server *server = NULL; + +static int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg) +{ + struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv; + + return trxcon_l1ctl_receive(trxcon, msg); +} + +static void l1ctl_conn_accept_cb(struct l1ctl_client *l1c) +{ + l1c->log_prefix = talloc_strdup(l1c, g_trxcon->log_prefix); + l1c->priv = g_trxcon; + g_trxcon->l2if = l1c; +} + +static void l1ctl_conn_close_cb(struct l1ctl_client *l1c) +{ + struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv; + + if (trxcon == NULL || trxcon->fi == NULL) + return; + + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_L2IF_FAILURE, NULL); +} + +bool trxc_l1ctl_init(void *tallctx) +{ + /* Start the L1CTL server */ + server_cfg = (struct l1ctl_server_cfg){ + /* TODO: make path configurable */ + .sock_path = "/tmp/osmocom_l2", .num_clients_max = 1, + .conn_read_cb = &l1ctl_rx_cb, .conn_accept_cb = &l1ctl_conn_accept_cb, + .conn_close_cb = &l1ctl_conn_close_cb, + }; + + server = l1ctl_server_alloc(tallctx, &server_cfg); + if (server == NULL) { + return false; + } + return true; +} diff --git a/Transceiver52M/ms/logging.c b/Transceiver52M/ms/logging.c new file mode 100644 index 0000000..af397e0 --- /dev/null +++ b/Transceiver52M/ms/logging.c @@ -0,0 +1,98 @@ +/* + * OsmocomBB <-> SDR connection bridge + * + * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <osmocom/core/application.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> + +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/logging.h> + +static struct log_info_cat trxcon_log_info_cat[] = { + [DAPP] = { + .name = "DAPP", + .description = "Application", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DL1C] = { + .name = "DL1C", + .description = "Layer 1 control interface", + .color = "\033[1;31m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DL1D] = { + .name = "DL1D", + .description = "Layer 1 data", + .color = "\033[1;31m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DTRXC] = { + .name = "DTRXC", + .description = "Transceiver control interface", + .color = "\033[1;33m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DTRXD] = { + .name = "DTRXD", + .description = "Transceiver data interface", + .color = "\033[1;33m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DSCH] = { + .name = "DSCH", + .description = "Scheduler management", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DSCHD] = { + .name = "DSCHD", + .description = "Scheduler data", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DGPRS] = { + .name = "DGPRS", + .description = "L1 GPRS (MAC layer)", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, +}; + +static const struct log_info trxcon_log_info = { + .cat = trxcon_log_info_cat, + .num_cat = ARRAY_SIZE(trxcon_log_info_cat), +}; + +static const int trxcon_log_cfg[] = { + [TRXCON_LOGC_FSM] = DAPP, + [TRXCON_LOGC_L1C] = DL1C, + [TRXCON_LOGC_L1D] = DL1D, + [TRXCON_LOGC_SCHC] = DSCH, + [TRXCON_LOGC_SCHD] = DSCHD, + [TRXCON_LOGC_GPRS] = DGPRS, +}; + +void trxc_log_init(void *tallctx) +{ + osmo_init_logging2(tallctx, &trxcon_log_info); + log_target_file_switch_to_wqueue(osmo_stderr_target); + + trxcon_set_log_cfg(&trxcon_log_cfg[0], ARRAY_SIZE(trxcon_log_cfg)); +} diff --git a/Transceiver52M/ms/ms.cpp b/Transceiver52M/ms/ms.cpp new file mode 100644 index 0000000..4ae8668 --- /dev/null +++ b/Transceiver52M/ms/ms.cpp @@ -0,0 +1,160 @@ + +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#include "GSMCommon.h" +#include <atomic> +#include <cassert> +#include <complex> +#include <iostream> +#include <cstdlib> +#include <cstdio> +#include <thread> +#include <fstream> + +#include "ms.h" + +extern "C" { +#include "sch.h" +} + +#include "threadsched.h" + +dummylog ms_trx::dummy_log; + +#ifdef DBGXX +const int offsetrange = 200; +const int offset_start = -15; +static int offset_ctr = 0; +#endif + +template <> +std::atomic<bool> ms_trx::base::stop_lower_threads_flag(false); + +int ms_trx::init_dev_and_streams() +{ + int status = 0; + status = init_device(rx_bh(), tx_bh()); + if (status < 0) { + std::cerr << "failed to init dev!" << std::endl; + return -1; + } + return status; +} + +bh_fn_t ms_trx::rx_bh() +{ + return [this](dev_buf_t *rcd) -> int { + if (this->search_for_sch(rcd) == SCH_STATE::FOUND) + this->grab_bursts(rcd); + return 0; + }; +} + +bh_fn_t ms_trx::tx_bh() +{ + return [this](dev_buf_t *rcd) -> int { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" + auto y = this; +#pragma GCC diagnostic pop + /* nothing to do here */ + return 0; + }; +} + +void ms_trx::start_lower_ms() +{ + if (stop_lower_threads_flag) + return; + auto fn = get_rx_burst_handler_fn(rx_bh()); + lower_rx_task = spawn_worker_thread(sched_params::thread_names::RXRUN, fn, this); + + usleep(1000); + auto fn2 = get_tx_burst_handler_fn(tx_bh()); + lower_tx_task = spawn_worker_thread(sched_params::thread_names::TXRUN, fn2, this); + + actually_enable_streams(); +} + +void ms_trx::set_upper_ready(bool is_ready) +{ + upper_is_ready = is_ready; +} + +void ms_trx::stop_threads() +{ + std::cerr << "killing threads..." << std::endl; + stop_lower_threads_flag = true; + close_device(); + std::cerr << "dev closed..." << std::endl; + pthread_join(lower_rx_task, nullptr); + std::cerr << "L rx dead..." << std::endl; + pthread_join(lower_tx_task, nullptr); + std::cerr << "L tx dead..." << std::endl; +} + +void ms_trx::submit_burst(blade_sample_type *buffer, int len, GSM::Time target) +{ + int64_t now_ts; + GSM::Time now_time; + target.incTN(3); // ul dl offset + int target_fn = target.FN(); + int target_tn = target.TN(); + timekeeper.get_both(&now_time, &now_ts); + + auto diff_fn = GSM::FNDelta(target_fn, now_time.FN()); + int diff_tn = (target_tn - (int)now_time.TN()) % 8; + auto tosend = GSM::Time(diff_fn, 0); + + if (diff_tn > 0) + tosend.incTN(diff_tn); + else + tosend.decTN(-diff_tn); + + // in theory fn equal and tn+3 equal is also a problem... + if (diff_fn < 0 || (diff_fn == 0 && (target_tn-now_time.TN() < 3))) { + std::cerr << "## TX too late?! fn DIFF:" << diff_fn << " tn LOCAL: " << now_time.TN() + << " tn OTHER: " << target_tn << std::endl; + return; + } + + int64_t send_ts = now_ts + tosend.FN() * 8 * ONE_TS_BURST_LEN + tosend.TN() * ONE_TS_BURST_LEN - timing_advance; +#ifdef DBGXX + auto check = now_time + tosend; + std::cerr << "## fn DIFF: " << diff_fn << " ## tn DIFF: " << diff_tn << " tn LOCAL/OTHER: " << now_time.TN() + << "/" << target_tn << " tndiff" << diff_tn << " tosend:" << tosend.FN() << ":" << tosend.TN() + << " check: " << check.FN() << ":" << check.TN() << " target: " << target.FN() << ":" << target.TN() + << " ts now: " << now_ts << " target ts:" << send_ts << std::endl; +#endif +#if 0 + auto check = now_time + tosend; + unsigned int pad = 4 * 4; + blade_sample_type buf2[len + pad]; + std::fill(buf2, buf2 + pad, 0); + memcpy(&buf2[pad], buffer, len * sizeof(blade_sample_type)); + + assert(target.FN() == check.FN()); + assert(target.TN() == check.TN()); + submit_burst_ts(buf2, len + pad, send_ts - pad); +#else + submit_burst_ts(buffer, len, send_ts); +#endif +} diff --git a/Transceiver52M/ms/ms.h b/Transceiver52M/ms/ms.h new file mode 100644 index 0000000..2ad78de --- /dev/null +++ b/Transceiver52M/ms/ms.h @@ -0,0 +1,301 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <atomic> +#include <cassert> +#include <complex> +#include <cstdint> +#include <mutex> +#include <iostream> +// #include <thread> + +#if defined(BUILDBLADE) +#include "bladerf_specific.h" +#define BASET blade_hw<ms_trx> +#elif defined(BUILDUHD) +#include "uhd_specific.h" +#define BASET uhd_hw<ms_trx> +#else +#error wat? no device.. +#endif + +#include "Complex.h" +#include "GSMCommon.h" +#include "itrq.h" +#include "threadpool.h" +#include "threadsched.h" + +const unsigned int ONE_TS_BURST_LEN = (3 + 58 + 26 + 58 + 3 + 8.25) * 4 /*sps*/; +const unsigned int SCH_LEN_SPS = (ONE_TS_BURST_LEN * 8 /*ts*/ * 12 /*frames*/); + +template <typename T> +void clamp_array(T *start2, unsigned int len, T max) +{ + for (unsigned int i = 0; i < len; i++) { + const T t1 = start2[i] < -max ? -max : start2[i]; + const T t2 = t1 > max ? max : t1; + start2[i] = t2; + } +} + +namespace cvt_internal +{ + +template <typename SRC_T, typename ST> +void convert_and_scale_i(float *dst, const SRC_T *src, unsigned int src_len, ST scale) +{ + for (unsigned int i = 0; i < src_len; i++) + dst[i] = static_cast<float>(src[i]) * scale; +} + +template <typename DST_T, typename ST> +void convert_and_scale_i(DST_T *dst, const float *src, unsigned int src_len, ST scale) +{ + for (unsigned int i = 0; i < src_len; i++) + dst[i] = static_cast<DST_T>(src[i] * scale); +} + +template <typename ST> +void convert_and_scale_i(float *dst, const float *src, unsigned int src_len, ST scale) +{ + for (unsigned int i = 0; i < src_len; i++) + dst[i] = src[i] * scale; +} + +template <typename T> +struct is_complex : std::false_type { + using baset = T; + static const unsigned int len_mul = 1; +}; + +template <typename T> +struct is_complex<std::complex<T>> : std::true_type { + using baset = typename std::complex<T>::value_type; + static const unsigned int len_mul = 2; +}; + +template <typename T> +struct is_complex<Complex<T>> : std::true_type { + using baset = typename Complex<T>::value_type; + static const unsigned int len_mul = 2; +}; + +} // namespace cvt_internal + +template <typename DST_T, typename SRC_T, typename ST> +void convert_and_scale(DST_T *dst, const SRC_T *src, unsigned int src_len, ST scale) +{ + using vd = typename cvt_internal::is_complex<DST_T>::baset; + using vs = typename cvt_internal::is_complex<SRC_T>::baset; + return cvt_internal::convert_and_scale_i((vd *)dst, (vs *)src, src_len, scale); +} + +template <typename array_t> +float normed_abs_sum(array_t *src, int len) +{ + using vd = typename cvt_internal::is_complex<array_t>::baset; + auto len_mul = cvt_internal::is_complex<array_t>::len_mul; + auto ptr = reinterpret_cast<const vd *>(src); + float sum = 0; + for (unsigned int i = 0; i < len * len_mul; i++) + sum += std::abs(ptr[i]); + sum /= len * len_mul; + return sum; +} + +struct one_burst { + one_burst() + { + } + GSM::Time gsmts; + union { + blade_sample_type burst[ONE_TS_BURST_LEN]; + char sch_bits[148]; + }; +}; + +using rx_queue_t = spsc_cond_timeout<4, one_burst, true, false>; + +enum class SCH_STATE { SEARCHING, FOUND }; + +class dummylog : private std::streambuf { + std::ostream null_stream; + + public: + dummylog() : null_stream(this){}; + ~dummylog() override{}; + std::ostream &operator()() + { + return null_stream; + } + int overflow(int c) override + { + return c; + } +}; + +// keeps relationship between gsm time and (continuously adjusted) ts +class time_keeper { + GSM::Time global_time_keeper; + int64_t global_ts_keeper; + std::mutex m; + + public: + time_keeper() : global_time_keeper(0), global_ts_keeper(0) + { + } + + void set(GSM::Time t, int64_t ts) + { + std::lock_guard<std::mutex> g(m); + global_time_keeper = t; + global_ts_keeper = ts; + } + void inc_both() + { + std::lock_guard<std::mutex> g(m); + global_time_keeper.incTN(1); + global_ts_keeper += ONE_TS_BURST_LEN; + } + void inc_and_update(int64_t new_ts) + { + std::lock_guard<std::mutex> g(m); + global_time_keeper.incTN(1); + global_ts_keeper = new_ts; + // std::cerr << "u " << new_ts << std::endl; + } + void inc_and_update_safe(int64_t new_ts) + { + std::lock_guard<std::mutex> g(m); + auto diff = new_ts - global_ts_keeper; + assert(diff < 1.5 * ONE_TS_BURST_LEN); + assert(diff > 0.5 * ONE_TS_BURST_LEN); + global_time_keeper.incTN(1); + global_ts_keeper = new_ts; + // std::cerr << "s " << new_ts << std::endl; + } + void dec_by_one() + { + std::lock_guard<std::mutex> g(m); + global_time_keeper.decTN(1); + global_ts_keeper -= ONE_TS_BURST_LEN; + } + auto get_ts() + { + std::lock_guard<std::mutex> g(m); + return global_ts_keeper; + } + auto gsmtime() + { + std::lock_guard<std::mutex> g(m); + return global_time_keeper; + } + void get_both(GSM::Time *t, int64_t *ts) + { + std::lock_guard<std::mutex> g(m); + *t = global_time_keeper; + *ts = global_ts_keeper; + } +}; + +using ts_hitter_q_t = spsc_cond<64, GSM::Time, true, false>; + +// used to globally initialize the sched/hw information +struct sched_hw_info { + int hw_cpus; + sched_params::target hw_target; + + sched_hw_info() + { + hw_cpus = std::thread::hardware_concurrency(); + hw_target = hw_cpus > 4 ? sched_params::target::ODROID : sched_params::target::PI4; + set_sched_target(hw_target); + std::cerr << "scheduling for: " << (hw_cpus > 4 ? "odroid" : "pi4") << std::endl; + } +}; + +struct ms_trx : public BASET, public sched_hw_info { + using base = BASET; + static dummylog dummy_log; + unsigned int mTSC; + unsigned int mBSIC; + int timing_advance; + bool do_auto_gain; + + pthread_t lower_rx_task; + pthread_t lower_tx_task; + + // provides bursts to upper rx thread + rx_queue_t rxqueue; + + blade_sample_type *first_sch_buf; + blade_sample_type *burst_copy_buffer; + + uint64_t first_sch_buf_rcv_ts; + std::atomic<bool> rcv_done; + std::atomic<bool> sch_thread_done; + + int64_t temp_ts_corr_offset = 0; + int64_t first_sch_ts_start = -1; + + time_keeper timekeeper; + single_thread_pool worker_thread; // uses base class sched target hw info + + void start_lower_ms(); + std::atomic<bool> upper_is_ready; + void set_upper_ready(bool is_ready); + + bool handle_sch_or_nb(); + bool handle_sch(bool first = false); + bool decode_sch(char *bits, bool update_global_clock); + SCH_STATE search_for_sch(dev_buf_t *rcd); + void grab_bursts(dev_buf_t *rcd); + + int init_dev_and_streams(); + void stop_threads(); + void *rx_cb(ms_trx *t); + void *tx_cb(); + void maybe_update_gain(one_burst &brst); + + ms_trx() + : mTSC(0), mBSIC(0), timing_advance(0), do_auto_gain(false), rxqueue(), + first_sch_buf(new blade_sample_type[SCH_LEN_SPS]), + burst_copy_buffer(new blade_sample_type[ONE_TS_BURST_LEN]), first_sch_buf_rcv_ts(0), + rcv_done{ false }, sch_thread_done{ false }, upper_is_ready(false) + { + } + + virtual ~ms_trx() + { + delete[] burst_copy_buffer; + delete[] first_sch_buf; + } + bh_fn_t rx_bh(); + bh_fn_t tx_bh(); + + void submit_burst(blade_sample_type *buffer, int len, GSM::Time); + void set_ta(int val) + { + assert(val > -127 && val < 128); + timing_advance = val * 4; + } +}; diff --git a/Transceiver52M/ms/ms_rx_lower.cpp b/Transceiver52M/ms/ms_rx_lower.cpp new file mode 100644 index 0000000..d894e96 --- /dev/null +++ b/Transceiver52M/ms/ms_rx_lower.cpp @@ -0,0 +1,420 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "sigProcLib.h" +#include "signalVector.h" +#include <atomic> +#include <cassert> +#include <complex> +#include <iostream> +#include <future> + +#include "ms.h" +#include "grgsm_vitac/grgsm_vitac.h" + +#include "threadpool.h" + +extern "C" { +#include "sch.h" +} + +#ifdef LOG +#undef LOG +#endif + +#if !defined(NODAMNLOG) +#define DBGLG(...) ms_trx::dummy_log() +#else +#define DBGLG(...) std::cerr +#endif + +#if !defined(NODAMNLOG) +#define DBGLG2(...) ms_trx::dummy_log() +#else +#define DBGLG2(...) std::cerr +#endif + +#define PRINT_Q_OVERFLOW + +bool ms_trx::decode_sch(char *bits, bool update_global_clock) +{ + int fn; + struct sch_info sch; + ubit_t info[GSM_SCH_INFO_LEN]; + sbit_t data[GSM_SCH_CODED_LEN]; + + memcpy(&data[0], &bits[3], 39); + memcpy(&data[39], &bits[106], 39); + + if (!gsm_sch_decode(info, data)) { + gsm_sch_parse(info, &sch); + + if (update_global_clock) { + DBGLG() << "SCH : Decoded values" << std::endl; + DBGLG() << " BSIC: " << sch.bsic << std::endl; + DBGLG() << " TSC: " << (sch.bsic & 0x7) << std::endl; + DBGLG() << " T1 : " << sch.t1 << std::endl; + DBGLG() << " T2 : " << sch.t2 << std::endl; + DBGLG() << " T3p : " << sch.t3p << std::endl; + DBGLG() << " FN : " << gsm_sch_to_fn(&sch) << std::endl; + } + + fn = gsm_sch_to_fn(&sch); + if (fn < 0) { // how? wh? + DBGLG() << "SCH : Failed to convert FN " << std::endl; + return false; + } + + if (update_global_clock) { + mBSIC = sch.bsic; + mTSC = sch.bsic & 0x7; + timekeeper.set(fn, 0); + // global_time_keeper.FN(fn); + // global_time_keeper.TN(0); + } + + return true; + } + return false; +} + +void ms_trx::maybe_update_gain(one_burst &brst) +{ + static_assert((sizeof(brst.burst) / sizeof(brst.burst[0])) == ONE_TS_BURST_LEN, "wtf, buffer size mismatch?"); + const int avgburst_num = 8 * 20; // ~ 50*4.5ms = 90ms? + static_assert(avgburst_num * 577 > (50 * 1000), "can't update faster then blade wait time?"); + const unsigned int rx_max_cutoff = (rxFullScale * 2) / 3; + static int gain_check = 0; + static float runmean = 0; + float sum = normed_abs_sum(&brst.burst[0], ONE_TS_BURST_LEN); + runmean = gain_check ? (runmean * (gain_check + 2) - 1 + sum) / (gain_check + 2) : sum; + + if (gain_check == avgburst_num - 1) { + DBGLG2() << "\x1B[32m #RXG \033[0m" << rxgain << " " << runmean << " " << sum << std::endl; + auto gainoffset = runmean < (rxFullScale / 4 ? 4 : 2); + gainoffset = runmean < (rxFullScale / 2 ? 2 : 1); + float newgain = runmean < rx_max_cutoff ? rxgain + gainoffset : rxgain - gainoffset; + // FIXME: gian cutoff + if (newgain != rxgain && newgain <= 60) { + auto gain_fun = [this, newgain] { setRxGain(newgain); }; + worker_thread.add_task(gain_fun); + } + + runmean = 0; + } + gain_check = (gain_check + 1) % avgburst_num; +} + +static char sch_demod_bits[148]; + +bool ms_trx::handle_sch_or_nb() +{ + one_burst brst; + const auto current_gsm_time = timekeeper.gsmtime(); + const auto is_sch = gsm_sch_check_ts(current_gsm_time.TN(), current_gsm_time.FN()); + + //either pass burst to upper layer for demod, OR pass demodded SCH to upper layer so we don't waste time processing it twice + brst.gsmts = current_gsm_time; + + if (!is_sch) { + memcpy(brst.burst, burst_copy_buffer, sizeof(blade_sample_type) * ONE_TS_BURST_LEN); + } else { + handle_sch(false); + memcpy(brst.sch_bits, sch_demod_bits, sizeof(sch_demod_bits)); + } + + while (upper_is_ready && !rxqueue.spsc_push(&brst)) + ; + + if (do_auto_gain) + maybe_update_gain(brst); + + return false; +} + +static float sch_acq_buffer[SCH_LEN_SPS * 2]; + +bool ms_trx::handle_sch(bool is_first_sch_acq) +{ + auto current_gsm_time = timekeeper.gsmtime(); + const auto buf_len = is_first_sch_acq ? SCH_LEN_SPS : ONE_TS_BURST_LEN; + const auto which_in_buffer = is_first_sch_acq ? first_sch_buf : burst_copy_buffer; + memset((void *)&sch_acq_buffer[0], 0, sizeof(sch_acq_buffer)); +#if 1 + const auto which_out_buffer = is_first_sch_acq ? sch_acq_buffer : &sch_acq_buffer[40 * 2]; + const auto ss = reinterpret_cast<std::complex<float> *>(which_out_buffer); + std::complex<float> channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR]; + int start; + convert_and_scale(which_out_buffer, which_in_buffer, buf_len * 2, 1.f / float(rxFullScale)); + if (is_first_sch_acq) { + float max_corr = 0; + start = get_sch_buffer_chan_imp_resp(ss, &channel_imp_resp[0], buf_len, &max_corr); + } else { + start = get_sch_chan_imp_resp(ss, &channel_imp_resp[0]); + start = start < 39 ? start : 39; + start = start > -39 ? start : -39; + } + detect_burst_nb(&ss[start], &channel_imp_resp[0], 0, sch_demod_bits); + + auto sch_decode_success = decode_sch(sch_demod_bits, is_first_sch_acq); +#if 0 + auto burst = new signalVector(buf_len, 50); + const auto corr_type = is_first_sch_acq ? sch_detect_type::SCH_DETECT_BUFFER : sch_detect_type::SCH_DETECT_FULL; + struct estim_burst_params ebp; + + // scale like uhd, +-2k -> +-32k + convert_and_scale(burst->begin(), which_in_buffer, buf_len * 2, SAMPLE_SCALE_FACTOR); + + auto rv = detectSCHBurst(*burst, 4, 4, corr_type, &ebp); + + int howmuchdelay = ebp.toa * 4; + std::cerr << "ooffs: " << howmuchdelay << " " << std::endl; + std::cerr << "voffs: " << start << " " << sch_decode_success << std::endl; +#endif + if (sch_decode_success) { + const auto ts_offset_symb = 4; + if (is_first_sch_acq) { + // update ts to first sample in sch buffer, to allow delay calc for current ts + first_sch_ts_start = first_sch_buf_rcv_ts + start - (ts_offset_symb * 4) - 1; + } else if (abs(start) > 1) { + // continuous sch tracking, only update if off too much + temp_ts_corr_offset += -start; + std::cerr << "offs: " << start << " " << temp_ts_corr_offset << std::endl; + } + + return true; + } else { + DBGLG2() << "L SCH : \x1B[31m decode fail \033[0m @ toa:" << start << " " << current_gsm_time.FN() + << ":" << current_gsm_time.TN() << std::endl; + } +#else + const auto ts_offset_symb = 4; + auto burst = new signalVector(buf_len, 50); + const auto corr_type = is_first_sch_acq ? sch_detect_type::SCH_DETECT_BUFFER : sch_detect_type::SCH_DETECT_FULL; + struct estim_burst_params ebp; + + // scale like uhd, +-2k -> +-32k + convert_and_scale(burst->begin(), which_in_buffer, buf_len * 2, SAMPLE_SCALE_FACTOR); + + auto rv = detectSCHBurst(*burst, 4, 4, corr_type, &ebp); + + int howmuchdelay = ebp.toa * 4; + + if (!rv) { + delete burst; + DBGLG() << "SCH : \x1B[31m detect fail \033[0m NOOOOOOOOOOOOOOOOOO toa:" << ebp.toa << " " + << current_gsm_time.FN() << ":" << current_gsm_time.TN() << std::endl; + return false; + } + + SoftVector *bits; + if (is_first_sch_acq) { + // can't be legit with a buf size spanning _at least_ one SCH but delay that implies partial sch burst + if (howmuchdelay < 0 || (buf_len - howmuchdelay) < ONE_TS_BURST_LEN) { + delete burst; + return false; + } + + struct estim_burst_params ebp2; + // auto sch_chunk = new signalVector(ONE_TS_BURST_LEN, 50); + // auto sch_chunk_start = sch_chunk->begin(); + // memcpy(sch_chunk_start, sch_buf_f.data() + howmuchdelay, sizeof(std::complex<float>) * ONE_TS_BURST_LEN); + + auto delay = delayVector(burst, NULL, -howmuchdelay); + + scaleVector(*delay, (complex)1.0 / ebp.amp); + + auto rv2 = detectSCHBurst(*delay, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp2); + DBGLG() << "FIRST SCH : " << (rv2 ? "yes " : " ") << "Timing offset " << ebp2.toa << " symbols" + << std::endl; + + bits = demodAnyBurst(*delay, SCH, 4, &ebp2); + delete delay; + } else { + bits = demodAnyBurst(*burst, SCH, 4, &ebp); + } + + delete burst; + + // clamp to +-1.5 because +-127 softbits scaled by 64 after -0.5 can be at most +-1.5 + clamp_array(bits->begin(), 148, 1.5f); + + float_to_sbit(&bits->begin()[0], (signed char *)&sch_demod_bits[0], 62, 148); + // float_to_sbit(&bits->begin()[106], &data[39], 62, 39); + + if (decode_sch((char *)sch_demod_bits, is_first_sch_acq)) { + auto current_gsm_time_updated = timekeeper.gsmtime(); + if (is_first_sch_acq) { + // update ts to first sample in sch buffer, to allow delay calc for current ts + first_sch_ts_start = first_sch_buf_rcv_ts + howmuchdelay - (ts_offset_symb * 4); + } else { + // continuous sch tracking, only update if off too much + auto diff = [](float x, float y) { return x > y ? x - y : y - x; }; + + auto d = diff(ebp.toa, ts_offset_symb); + if (abs(d) > 0.3) { + if (ebp.toa < ts_offset_symb) + ebp.toa = d; + else + ebp.toa = -d; + temp_ts_corr_offset += ebp.toa * 4; + + DBGLG() << "offs: " << ebp.toa << " " << temp_ts_corr_offset << std::endl; + } + } + + auto a = gsm_sch_check_fn(current_gsm_time_updated.FN() - 1); + auto b = gsm_sch_check_fn(current_gsm_time_updated.FN()); + auto c = gsm_sch_check_fn(current_gsm_time_updated.FN() + 1); + DBGLG() << "L SCH : Timing offset " << rv << " " << ebp.toa << " " << a << b << c << "fn " + << current_gsm_time_updated.FN() << ":" << current_gsm_time_updated.TN() << std::endl; + + delete bits; + return true; + } else { + DBGLG2() << "L SCH : \x1B[31m decode fail \033[0m @ toa:" << ebp.toa << " " << current_gsm_time.FN() + << ":" << current_gsm_time.TN() << std::endl; + } + + delete bits; +#endif + return false; +} + +/* +accumulates a full big buffer consisting of 8*12 timeslots, then: +either +1) adjusts gain if necessary and starts over +2) searches and finds SCH and is done +*/ +SCH_STATE ms_trx::search_for_sch(dev_buf_t *rcd) +{ + static unsigned int sch_pos = 0; + auto to_copy = SCH_LEN_SPS - sch_pos; + + if (sch_thread_done) + return SCH_STATE::FOUND; + + if (rcv_done) + return SCH_STATE::SEARCHING; + + if (sch_pos == 0) // keep first ts for time delta calc + first_sch_buf_rcv_ts = rcd->get_first_ts(); + + if (to_copy) { + auto spsmax = rcd->actual_samples_per_buffer(); + if (to_copy > (unsigned int)spsmax) + sch_pos += rcd->readall(first_sch_buf + sch_pos); + else + sch_pos += rcd->read_n(first_sch_buf + sch_pos, 0, to_copy); + } else { // (!to_copy) + sch_pos = 0; + rcv_done = true; + auto sch_search_fun = [this] { + const auto target_val = rxFullScale / 8; + float sum = normed_abs_sum(first_sch_buf, SCH_LEN_SPS); + + //FIXME: arbitrary value, gain cutoff + if (sum > target_val || rxgain >= 60) // enough ? + sch_thread_done = this->handle_sch(true); + else { + std::cerr << "\x1B[32m #RXG \033[0m gain " << rxgain << " -> " << rxgain + 4 + << " sample avg:" << sum << " target: >=" << target_val << std::endl; + setRxGain(rxgain + 4); + } + + if (!sch_thread_done) + rcv_done = false; // retry! + }; + worker_thread.add_task(sch_search_fun); + } + return SCH_STATE::SEARCHING; +} + +void ms_trx::grab_bursts(dev_buf_t *rcd) +{ + // partial burst samples read from the last buffer + static int partial_rdofs = 0; + static bool first_call = true; + int to_skip = 0; + + // round up to next burst by calculating the time between sch detection and now + if (first_call) { + const auto next_burst_start = rcd->get_first_ts() - first_sch_ts_start; + const auto fullts = next_burst_start / ONE_TS_BURST_LEN; + const auto fracts = next_burst_start % ONE_TS_BURST_LEN; + to_skip = ONE_TS_BURST_LEN - fracts; + + for (unsigned int i = 0; i < fullts; i++) + timekeeper.inc_and_update(first_sch_ts_start + i * ONE_TS_BURST_LEN); + + if (fracts) + timekeeper.inc_both(); + // timekeeper.inc_and_update(first_sch_ts_start + 1 * ONE_TS_BURST_LEN); + + timekeeper.dec_by_one(); // oops, off by one? + + timekeeper.set(timekeeper.gsmtime(), rcd->get_first_ts() - ONE_TS_BURST_LEN + to_skip); + + DBGLG() << "this ts: " << rcd->get_first_ts() << " diff full TN: " << fullts << " frac TN: " << fracts + << " GSM now: " << timekeeper.gsmtime().FN() << ":" << timekeeper.gsmtime().TN() << " is sch? " + << gsm_sch_check_fn(timekeeper.gsmtime().FN()) << std::endl; + first_call = false; + } + + if (partial_rdofs) { + auto first_remaining = ONE_TS_BURST_LEN - partial_rdofs; + auto rd = rcd->read_n(burst_copy_buffer + partial_rdofs, 0, first_remaining); + if (rd != (int)first_remaining) { + partial_rdofs += rd; + return; + } + + timekeeper.inc_and_update_safe(rcd->get_first_ts() - partial_rdofs); + handle_sch_or_nb(); + to_skip = first_remaining; + } + + // apply sample rate slippage compensation + to_skip -= temp_ts_corr_offset; + + // FIXME: happens rarely, read_n start -1 blows up + // this is fine: will just be corrected one buffer later + if (to_skip < 0) + to_skip = 0; + else + temp_ts_corr_offset = 0; + + const auto left_after_burst = rcd->actual_samples_per_buffer() - to_skip; + + const int full = left_after_burst / ONE_TS_BURST_LEN; + const int frac = left_after_burst % ONE_TS_BURST_LEN; + + for (int i = 0; i < full; i++) { + rcd->read_n(burst_copy_buffer, to_skip + i * ONE_TS_BURST_LEN, ONE_TS_BURST_LEN); + timekeeper.inc_and_update_safe(rcd->get_first_ts() + to_skip + i * ONE_TS_BURST_LEN); + handle_sch_or_nb(); + } + + if (frac) + rcd->read_n(burst_copy_buffer, to_skip + full * ONE_TS_BURST_LEN, frac); + partial_rdofs = frac; +} diff --git a/Transceiver52M/ms/ms_trxcon_if.cpp b/Transceiver52M/ms/ms_trxcon_if.cpp new file mode 100644 index 0000000..7de9710 --- /dev/null +++ b/Transceiver52M/ms/ms_trxcon_if.cpp @@ -0,0 +1,78 @@ +/* + * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <atomic> +#include "ms_trxcon_if.h" +extern "C" { +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/l1ctl_server.h> +#include <osmocom/core/panic.h> +} + +extern tx_queue_t txq; +extern cmd_queue_t cmdq_to_phy; +extern cmdr_queue_t cmdq_from_phy; +extern std::atomic<bool> g_exit_flag; +// trxcon C call(back) if +extern "C" { +int trxcon_phyif_handle_burst_req(void *phyif, const struct trxcon_phyif_burst_req *br) +{ + if (br->burst_len == 0) // dummy/nope + return 0; + OSMO_ASSERT(br->burst != 0); + + internal_q_tx_buf b(br); + if (!g_exit_flag) + txq.spsc_push(&b); + return 0; +} + +int trxcon_phyif_handle_cmd(void *phyif, const struct trxcon_phyif_cmd *cmd) +{ +#ifdef TXDEBUG + DBGLG() << "TOP C: " << cmd2str(cmd->type) << std::endl; +#endif + if (!g_exit_flag) + cmdq_to_phy.spsc_push(cmd); + // q for resp polling happens in main loop + return 0; +} + +void trxcon_phyif_close(void *phyif) +{ +} + +void trxcon_l1ctl_close(struct trxcon_inst *trxcon) +{ + /* Avoid use-after-free: both *fi and *trxcon are children of + * the L2IF (L1CTL connection), so we need to re-parent *fi + * to NULL before calling l1ctl_client_conn_close(). */ + talloc_steal(NULL, trxcon->fi); + l1ctl_client_conn_close((struct l1ctl_client *)trxcon->l2if); +} + +int trxcon_l1ctl_send(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct l1ctl_client *l1c = (struct l1ctl_client *)trxcon->l2if; + + return l1ctl_client_send(l1c, msg); +} +} diff --git a/Transceiver52M/ms/ms_trxcon_if.h b/Transceiver52M/ms/ms_trxcon_if.h new file mode 100644 index 0000000..0928d40 --- /dev/null +++ b/Transceiver52M/ms/ms_trxcon_if.h @@ -0,0 +1,42 @@ +#pragma once +/* + * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "ms.h" +extern "C" { +#include <osmocom/bb/trxcon/phyif.h> +} + +extern struct trxcon_inst *g_trxcon; +struct internal_q_tx_buf { + trxcon_phyif_burst_req r; + uint8_t buf[148]; + internal_q_tx_buf() = default; + internal_q_tx_buf(const internal_q_tx_buf &) = delete; + internal_q_tx_buf &operator=(const internal_q_tx_buf &) = default; + internal_q_tx_buf(const struct trxcon_phyif_burst_req *br) : r(*br) + { + memcpy(buf, (void *)br->burst, br->burst_len); + } +}; +using tx_queue_t = spsc_cond<8 * 1, internal_q_tx_buf, true, false>; +using cmd_queue_t = spsc_cond_timeout<8 * 1, trxcon_phyif_cmd, true, false>; +using cmdr_queue_t = spsc_cond<8 * 1, trxcon_phyif_rsp, false, false>; diff --git a/Transceiver52M/ms/ms_upper.cpp b/Transceiver52M/ms/ms_upper.cpp new file mode 100644 index 0000000..2e8bc11 --- /dev/null +++ b/Transceiver52M/ms/ms_upper.cpp @@ -0,0 +1,512 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <csignal> +#include "sigProcLib.h" +#include "ms.h" +#include <signalVector.h> +#include <radioVector.h> +#include <radioInterface.h> +#include <grgsm_vitac/grgsm_vitac.h> + +// #define TXDEBUG + +extern "C" { + +#include "sch.h" +#include "convolve.h" +#include "convert.h" + +#include <osmocom/core/application.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> +#include <osmocom/bb/trxcon/l1ctl_server.h> + +extern void trxc_log_init(void *tallctx); +#ifdef LSANDEBUG +void __lsan_do_recoverable_leak_check(); +#endif +} + +#include "ms_trxcon_if.h" +#include "ms_upper.h" +#include "threadsched.h" + +extern bool trxc_l1ctl_init(void *tallctx); +struct trxcon_inst *g_trxcon; +tx_queue_t txq; +cmd_queue_t cmdq_to_phy; +cmdr_queue_t cmdq_from_phy; + +#ifdef LOG +#undef LOG +#define LOG(...) upper_trx::dummy_log() +#endif + +#define DBGLG(...) upper_trx::dummy_log() + +std::atomic<bool> g_exit_flag; + +void upper_trx::stop_upper_threads() +{ + g_exit_flag = true; + + pthread_join(thr_control, NULL); + pthread_join(thr_tx, NULL); +} + +void upper_trx::start_threads() +{ + DBGLG(...) << "spawning threads.." << std::endl; + + thr_control = spawn_worker_thread( + sched_params::thread_names::U_CTL, + [](void *args) -> void * { + upper_trx *t = reinterpret_cast<upper_trx *>(args); +#ifdef TXDEBUG + struct sched_param param; + int policy; + pthread_getschedparam(pthread_self(), &policy, ¶m); + printf("ID: %lu, CPU: %d policy = %d priority = %d\n", pthread_self(), sched_getcpu(), policy, + param.sched_priority); +#endif + std::cerr << "started U control!" << std::endl; + while (!g_exit_flag) { + t->driveControl(); + } + std::cerr << "exit U control!" << std::endl; + + return 0; + }, + this); + thr_tx = spawn_worker_thread( + sched_params::thread_names::U_TX, + [](void *args) -> void * { + upper_trx *t = reinterpret_cast<upper_trx *>(args); +#ifdef TXDEBUG + struct sched_param param; + int policy; + pthread_getschedparam(pthread_self(), &policy, ¶m); + printf("ID: %lu, CPU: %d policy = %d priority = %d\n", pthread_self(), sched_getcpu(), policy, + param.sched_priority); +#endif + std::cerr << "started U tx!" << std::endl; + while (!g_exit_flag) { + t->driveTx(); + } + std::cerr << "exit U tx!" << std::endl; + + return 0; + }, + this); + +#ifdef LSANDEBUG + std::thread([this] { + set_name_aff_sched(sched_params::thread_names::LEAKCHECK); + + while (1) { + std::this_thread::sleep_for(std::chrono::seconds{ 5 }); + __lsan_do_recoverable_leak_check(); + } + }).detach(); +#endif +} + +void upper_trx::main_loop() +{ + set_name_aff_sched(sched_params::thread_names::U_RX); + set_upper_ready(true); + while (!g_exit_flag) { + driveReceiveFIFO(); + osmo_select_main(1); + + trxcon_phyif_rsp r; + if (cmdq_from_phy.spsc_pop(&r)) { + DBGLG() << "HAVE RESP:" << r.type << std::endl; + trxcon_phyif_handle_rsp(g_trxcon, &r); + } + } + set_upper_ready(false); + std::cerr << "exit U rx!" << std::endl; + mOn = false; +} + +// signalvector is owning despite claiming not to, but we can pretend, too.. +static void static_free(void *wData){}; +static void *static_alloc(size_t newSize) +{ + return 0; +}; + +bool upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset) +{ + float pow, avg = 1.0; + const auto zero_pad_len = 40; // give the VA some runway for misaligned bursts + const auto workbuf_size = zero_pad_len + ONE_TS_BURST_LEN + zero_pad_len; + static complex workbuf[workbuf_size]; + + static signalVector sv(workbuf, zero_pad_len, ONE_TS_BURST_LEN, static_alloc, static_free); + one_burst e; + auto ss = reinterpret_cast<std::complex<float> *>(&workbuf[zero_pad_len]); + std::fill(workbuf, workbuf + workbuf_size, 0); + // assert(sv.begin() == &workbuf[40]); + + while (!rxqueue.spsc_pop(&e)) { + rxqueue.spsc_prep_pop(); + } + + wTime = e.gsmts; + + const auto is_sch = gsm_sch_check_ts(wTime.TN(), wTime.FN()); + const auto is_fcch = gsm_fcch_check_ts(wTime.TN(), wTime.FN()); + + trxcon_phyif_rtr_ind i = { static_cast<uint32_t>(wTime.FN()), static_cast<uint8_t>(wTime.TN()) }; + trxcon_phyif_rtr_rsp r = {}; + trxcon_phyif_handle_rtr_ind(g_trxcon, &i, &r); + if (!(r.flags & TRXCON_PHYIF_RTR_F_ACTIVE)) + return false; + + if (is_fcch) { + // return trash + return true; + } + + if (is_sch) { + for (int i = 0; i < 148; i++) + (demodded_softbits)[i] = (e.sch_bits[i]); + RSSI = 10; + timingOffset = 0; + return true; + } + +#if 1 + convert_and_scale(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(rxFullScale)); + + pow = energyDetect(sv, 20 * 4 /*sps*/); + if (pow < -1) { + LOG(ALERT) << "Received empty burst"; + return false; + } + + avg = sqrt(pow); + { + float ncmax; + std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR]; + auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, mTSC); +#ifdef DBGXX + float dcmax; + std::complex<float> chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR]; + auto dummy_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp2[0], &dcmax, TS_DUMMY); + auto is_nb = ncmax > dcmax; + // DBGLG() << " U " << (is_nb ? "NB" : "DB") << "@ o nb: " << normal_burst_start + // << " o db: " << dummy_burst_start << std::endl; +#endif + normal_burst_start = normal_burst_start < 39 ? normal_burst_start : 39; + normal_burst_start = normal_burst_start > -39 ? normal_burst_start : -39; +#ifdef DBGXX + // fprintf(stderr, "%s %d\n", (is_nb ? "N":"D"), burst_time.FN()); + // if (is_nb) +#endif + detect_burst_nb(ss, &chan_imp_resp[0], normal_burst_start, demodded_softbits); +#ifdef DBGXX + // else + // detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin); +#endif + } +#else + + // lower layer sch detection offset, easy to verify by just printing the detected value using both the va+sigproc code. + convert_and_scale(ss + 16, e.burst, ONE_TS_BURST_LEN * 2, 15); + + pow = energyDetect(sv, 20 * 4 /*sps*/); + if (pow < -1) { + LOG(ALERT) << "Received empty burst"; + return false; + } + + avg = sqrt(pow); + + /* Detect normal or RACH bursts */ + CorrType type = CorrType::TSC; + struct estim_burst_params ebp; + auto rc = detectAnyBurst(sv, mTSC, 3, 4, type, 48, &ebp); + if (rc > 0) { + type = (CorrType)rc; + } + + if (rc < 0) { + std::cerr << "UR : \x1B[31m rx fail \033[0m @ toa:" << ebp.toa << " " << e.gsmts.FN() << ":" + << e.gsmts.TN() << std::endl; + return false; + } + SoftVector *bits = demodAnyBurst(sv, type, 4, &ebp); + + SoftVector::const_iterator burstItr = bits->begin(); + // invert and fix to +-127 sbits + for (int ii = 0; ii < 148; ii++) { + demodded_softbits[ii] = *burstItr++ > 0.0f ? -127 : 127; + } + delete bits; + +#endif + RSSI = (int)floor(20.0 * log10(rxFullScale / avg)); + // FIXME: properly handle offset, sch/nb alignment diff? handled by lower anyway... + timingOffset = (int)round(0); + + return true; +} + +void upper_trx::driveReceiveFIFO() +{ + int RSSI; + int TOA; // in 1/256 of a symbol + GSM::Time burstTime; + + if (!mOn) + return; + + if (pullRadioVector(burstTime, RSSI, TOA)) { + trxcon_phyif_burst_ind bi; + bi.fn = burstTime.FN(); + bi.tn = burstTime.TN(); + bi.rssi = RSSI; + bi.toa256 = TOA; + bi.burst = (sbit_t *)demodded_softbits; + bi.burst_len = sizeof(demodded_softbits); + trxcon_phyif_handle_burst_ind(g_trxcon, &bi); + } + + burstTime.incTN(2); + struct trxcon_phyif_rts_ind rts { + static_cast<uint32_t>(burstTime.FN()), static_cast<uint8_t>(burstTime.TN()) + }; + trxcon_phyif_handle_rts_ind(g_trxcon, &rts); +} + +void upper_trx::driveTx() +{ + internal_q_tx_buf e; + static BitVector newBurst(sizeof(e.buf)); + while (!txq.spsc_pop(&e)) { + txq.spsc_prep_pop(); + } + + // ensure our tx cb is tickled and can exit + if (g_exit_flag) { + submit_burst_ts(0, 1337, 1); + return; + } + + internal_q_tx_buf *burst = &e; + +#ifdef TXDEBUG2 + DBGLG() << "got burst!" << burst->r.fn << ":" << burst->ts << " current: " << timekeeper.gsmtime().FN() + << " dff: " << (int64_t)((int64_t)timekeeper.gsmtime().FN() - (int64_t)burst->r.fn) << std::endl; +#endif + + auto currTime = GSM::Time(burst->r.fn, burst->r.tn); + int RSSI = (int)burst->r.pwr; + + BitVector::iterator itr = newBurst.begin(); + auto *bufferItr = burst->buf; + while (itr < newBurst.end()) + *itr++ = *bufferItr++; + + auto txburst = modulateBurst(newBurst, 8 + (currTime.TN() % 4 == 0), 4); + scaleVector(*txburst, txFullScale * pow(10, -RSSI / 10)); + + // float -> int16 + blade_sample_type burst_buf[txburst->size()]; + convert_and_scale(burst_buf, txburst->begin(), txburst->size() * 2, 1); +#ifdef TXDEBUG2 + auto check = signalVector(txburst->size(), 40); + convert_and_scale(check.begin(), burst_buf, txburst->size() * 2, 1); + estim_burst_params ebp; + auto d = detectAnyBurst(check, 2, 4, 4, CorrType::RACH, 40, &ebp); + if (d) + DBGLG() << "RACH D! " << ebp.toa << std::endl; + else + DBGLG() << "RACH NOOOOOOOOOO D! " << ebp.toa << std::endl; + + // memory read --binary --outfile /tmp/mem.bin &burst_buf[0] --count 2500 --force +#endif + submit_burst(burst_buf, txburst->size(), currTime); + delete txburst; +} + +#ifdef TXDEBUG +static const char *cmd2str(trxcon_phyif_cmd_type c) +{ + switch (c) { + case TRXCON_PHYIF_CMDT_RESET: + return "TRXCON_PHYIF_CMDT_RESET"; + case TRXCON_PHYIF_CMDT_POWERON: + return "TRXCON_PHYIF_CMDT_POWERON"; + case TRXCON_PHYIF_CMDT_POWEROFF: + return "TRXCON_PHYIF_CMDT_POWEROFF"; + case TRXCON_PHYIF_CMDT_MEASURE: + return "TRXCON_PHYIF_CMDT_MEASURE"; + case TRXCON_PHYIF_CMDT_SETFREQ_H0: + return "TRXCON_PHYIF_CMDT_SETFREQ_H0"; + case TRXCON_PHYIF_CMDT_SETFREQ_H1: + return "TRXCON_PHYIF_CMDT_SETFREQ_H1"; + case TRXCON_PHYIF_CMDT_SETSLOT: + return "TRXCON_PHYIF_CMDT_SETSLOT"; + case TRXCON_PHYIF_CMDT_SETTA: + return "TRXCON_PHYIF_CMDT_SETTA"; + default: + return "UNKNOWN COMMAND!"; + } +} + +static void print_cmd(trxcon_phyif_cmd_type c) +{ + DBGLG() << "handling " << cmd2str(c) << std::endl; +} +#endif + +bool upper_trx::driveControl() +{ + trxcon_phyif_rsp r; + trxcon_phyif_cmd cmd; + while (!cmdq_to_phy.spsc_pop(&cmd)) { + cmdq_to_phy.spsc_prep_pop(); + if (g_exit_flag) + return false; + } + + if (g_exit_flag) + return false; + +#ifdef TXDEBUG + print_cmd(cmd.type); +#endif + + switch (cmd.type) { + case TRXCON_PHYIF_CMDT_RESET: + set_ta(0); + break; + case TRXCON_PHYIF_CMDT_POWERON: + if (!mOn) { + mOn = true; + start_lower_ms(); + } + break; + case TRXCON_PHYIF_CMDT_POWEROFF: + break; + case TRXCON_PHYIF_CMDT_MEASURE: + r.type = trxcon_phyif_cmd_type::TRXCON_PHYIF_CMDT_MEASURE; + r.param.measure.band_arfcn = cmd.param.measure.band_arfcn; + // FIXME: do we want to measure anything, considering the transceiver just syncs by.. syncing? + r.param.measure.dbm = -80; + tuneRx(gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 0) * 1000 * 100); + tuneTx(gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 1) * 1000 * 100); + cmdq_from_phy.spsc_push(&r); + break; + case TRXCON_PHYIF_CMDT_SETFREQ_H0: + tuneRx(gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 0) * 1000 * 100); + tuneTx(gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 1) * 1000 * 100); + break; + case TRXCON_PHYIF_CMDT_SETFREQ_H1: + break; + case TRXCON_PHYIF_CMDT_SETSLOT: + break; + case TRXCON_PHYIF_CMDT_SETTA: + set_ta(cmd.param.setta.ta); + break; + } + return false; +} + +void sighandler(int sigset) +{ + // we might get a sigpipe in case the l1ctl ud socket disconnects because mobile quits + if (sigset == SIGPIPE || sigset == SIGINT) { + g_exit_flag = true; + + // we know the flag is atomic and it prevents the trxcon cb handlers from writing + // to the queues, so submit some trash to unblock the threads & exit + trxcon_phyif_cmd cmd = {}; + internal_q_tx_buf b = {}; + txq.spsc_push(&b); + cmdq_to_phy.spsc_push(&cmd); + msleep(200); + + return; + } +} + +int main(int argc, char *argv[]) +{ + auto tall_trxcon_ctx = talloc_init("trxcon context"); + signal(SIGPIPE, sighandler); + signal(SIGINT, sighandler); + + msgb_talloc_ctx_init(tall_trxcon_ctx, 0); + trxc_log_init(tall_trxcon_ctx); + + /* Configure pretty logging */ + log_set_print_extended_timestamp(osmo_stderr_target, 1); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + log_set_print_level(osmo_stderr_target, 1); + + log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME); + log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END); + + osmo_fsm_log_timeouts(true); + + g_trxcon = trxcon_inst_alloc(tall_trxcon_ctx, 0); + g_trxcon->gsmtap = nullptr; + g_trxcon->phyif = nullptr; + g_trxcon->phy_quirks.fbsb_extend_fns = 866; // 4 seconds, known to work. + + convolve_init(); + convert_init(); + sigProcLibSetup(); + initvita(); + + int status = 0; + auto trx = new upper_trx(); + trx->do_auto_gain = true; + + status = trx->init_dev_and_streams(); + if (status < 0) { + std::cerr << "Error initializing hardware, quitting.." << std::endl; + return -1; + } + set_name_aff_sched(sched_params::thread_names::MAIN); + + if (!trxc_l1ctl_init(tall_trxcon_ctx)) { + std::cerr << "Error initializing l1ctl, quitting.." << std::endl; + return -1; + } + + // blocking, will return when global exit is requested + trx->start_threads(); + trx->main_loop(); + trx->stop_threads(); + trx->stop_upper_threads(); + + return status; +} diff --git a/Transceiver52M/ms/ms_upper.h b/Transceiver52M/ms/ms_upper.h new file mode 100644 index 0000000..06f7c54 --- /dev/null +++ b/Transceiver52M/ms/ms_upper.h @@ -0,0 +1,48 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#include <netdb.h> +#include <sys/socket.h> +#include <arpa/inet.h> + +#include "GSMCommon.h" +#include "ms.h" + +class upper_trx : public ms_trx { + volatile bool mOn; + char demodded_softbits[444]; + + // void driveControl(); + bool pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset); + + pthread_t thr_control, thr_tx; + + public: + void start_threads(); + void main_loop(); + void stop_upper_threads(); + + bool driveControl(); + void driveReceiveFIFO(); + void driveTx(); + + upper_trx() : mOn(false){}; +}; diff --git a/Transceiver52M/ms/sch.c b/Transceiver52M/ms/sch.c new file mode 100644 index 0000000..34809a2 --- /dev/null +++ b/Transceiver52M/ms/sch.c @@ -0,0 +1,329 @@ +/* + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> + * (C) 2016 by Tom Tsou <tom.tsou@ettus.com> + * (C) 2017 by Harald Welte <laforge@gnumonks.org> + * (C) 2022 by 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> / Eric Wild <ewild@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <complex.h> +#include <stdio.h> +#include <math.h> +#include <string.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/conv.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/crcgen.h> +#include <osmocom/coding/gsm0503_coding.h> +#include <osmocom/coding/gsm0503_parity.h> + +#include "sch.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" + +/* GSM 04.08, 9.1.30 Synchronization channel information */ +struct sch_packed_info { + ubit_t t1_hi[2]; + ubit_t bsic[6]; + ubit_t t1_md[8]; + ubit_t t3p_hi[2]; + ubit_t t2[5]; + ubit_t t1_lo[1]; + ubit_t t3p_lo[1]; +} __attribute__((packed)); + +struct sch_burst { + sbit_t tail0[3]; + sbit_t data0[39]; + sbit_t etsc[64]; + sbit_t data1[39]; + sbit_t tail1[3]; + sbit_t guard[8]; +} __attribute__((packed)); + +static const uint8_t sch_next_output[][2] = { + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, +}; + +static const uint8_t sch_next_state[][2] = { + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, +}; + +static const struct osmo_conv_code gsm_conv_sch = { + .N = 2, + .K = 5, + .len = GSM_SCH_UNCODED_LEN, + .next_output = sch_next_output, + .next_state = sch_next_state, +}; + +#define GSM_MAX_BURST_LEN 157 * 4 +#define GSM_SYM_RATE (1625e3 / 6) * 4 + +/* Pre-generated FCCH measurement tone */ +static complex float fcch_ref[GSM_MAX_BURST_LEN]; + +int float_to_sbit(const float *in, sbit_t *out, float scale, int len) +{ + int i; + + for (i = 0; i < len; i++) { + out[i] = (in[i] - 0.5f) * scale; + } + + return 0; +} + +/* Check if FN contains a FCCH burst */ +int gsm_fcch_check_fn(int fn) +{ + int fn51 = fn % 51; + + switch (fn51) { + case 0: + case 10: + case 20: + case 30: + case 40: + return 1; + } + + return 0; +} + +/* Check if FN contains a SCH burst */ +int gsm_sch_check_fn(int fn) +{ + int fn51 = fn % 51; + + switch (fn51) { + case 1: + case 11: + case 21: + case 31: + case 41: + return 1; + } + + return 0; +} + +int gsm_fcch_check_ts(int ts, int fn) { + return ts == 0 && gsm_fcch_check_fn(fn); +} + +int gsm_sch_check_ts(int ts, int fn) { + return ts == 0 && gsm_sch_check_fn(fn); +} + +/* SCH (T1, T2, T3p) to full FN value */ +int gsm_sch_to_fn(struct sch_info *sch) +{ + int t1 = sch->t1; + int t2 = sch->t2; + int t3p = sch->t3p; + + if ((t1 < 0) || (t2 < 0) || (t3p < 0)) + return -1; + int tt; + int t3 = t3p * 10 + 1; + + if (t3 < t2) + tt = (t3 + 26) - t2; + else + tt = (t3 - t2) % 26; + + return t1 * 51 * 26 + tt * 51 + t3; +} + +/* Parse encoded SCH message */ +int gsm_sch_parse(const uint8_t *info, struct sch_info *desc) +{ + struct sch_packed_info *p = (struct sch_packed_info *) info; + + desc->bsic = (p->bsic[0] << 0) | (p->bsic[1] << 1) | + (p->bsic[2] << 2) | (p->bsic[3] << 3) | + (p->bsic[4] << 4) | (p->bsic[5] << 5); + + desc->t1 = (p->t1_lo[0] << 0) | (p->t1_md[0] << 1) | + (p->t1_md[1] << 2) | (p->t1_md[2] << 3) | + (p->t1_md[3] << 4) | (p->t1_md[4] << 5) | + (p->t1_md[5] << 6) | (p->t1_md[6] << 7) | + (p->t1_md[7] << 8) | (p->t1_hi[0] << 9) | + (p->t1_hi[1] << 10); + + desc->t2 = (p->t2[0] << 0) | (p->t2[1] << 1) | + (p->t2[2] << 2) | (p->t2[3] << 3) | + (p->t2[4] << 4); + + desc->t3p = (p->t3p_lo[0] << 0) | (p->t3p_hi[0] << 1) | + (p->t3p_hi[1] << 2); + + return 0; +} + +/* From osmo-bts */ +int gsm_sch_decode(uint8_t *info, sbit_t *data) +{ + int rc; + ubit_t uncoded[GSM_SCH_UNCODED_LEN]; + + osmo_conv_decode(&gsm_conv_sch, data, uncoded); + + rc = osmo_crc16gen_check_bits(&gsm0503_sch_crc10, + uncoded, GSM_SCH_INFO_LEN, + uncoded + GSM_SCH_INFO_LEN); + if (rc) + return -1; + + memcpy(info, uncoded, GSM_SCH_INFO_LEN * sizeof(ubit_t)); + + return 0; +} + +#define FCCH_TAIL_BITS_LEN 3*4 +#define FCCH_DATA_LEN 100*4// 142 +#if 1 +/* Compute FCCH frequency offset */ +double org_gsm_fcch_offset(float *burst, int len) +{ + int i, start, end; + float a, b, c, d, ang, avg = 0.0f; + double freq; + + if (len > GSM_MAX_BURST_LEN) + len = GSM_MAX_BURST_LEN; + + for (i = 0; i < len; i++) { + a = burst[2 * i + 0]; + b = burst[2 * i + 1]; + c = crealf(fcch_ref[i]); + d = cimagf(fcch_ref[i]); + + burst[2 * i + 0] = a * c - b * d; + burst[2 * i + 1] = a * d + b * c; + } + + start = FCCH_TAIL_BITS_LEN; + end = start + FCCH_DATA_LEN; + + for (i = start; i < end; i++) { + a = cargf(burst[2 * (i - 1) + 0] + + burst[2 * (i - 1) + 1] * I); + b = cargf(burst[2 * i + 0] + + burst[2 * i + 1] * I); + + ang = b - a; + + if (ang > M_PI) + ang -= 2 * M_PI; + else if (ang < -M_PI) + ang += 2 * M_PI; + + avg += ang; + } + + avg /= (float) (end - start); + freq = avg / (2 * M_PI) * GSM_SYM_RATE; + + return freq; +} + + +static const int L1 = 3; +static const int L2 = 32; +static const int N1 = 92; +static const int N2 = 92; + +static struct { int8_t r; int8_t s; } P_inv_table[3+32]; + +void pinv(int P, int8_t* r, int8_t* s, int L1, int L2) { + for (int i = 0; i < L1; i++) + for (int j = 0; j < L2; j++) + if (P == L2 * i - L1 * j) { + *r = i; + *s = j; + return; + } +} + + +float ac_sum_with_lag( complex float* in, int lag, int offset, int N) { + complex float v = 0 + 0*I; + int total_offset = offset + lag; + for (int s = 0; s < N; s++) + v += in[s + total_offset] * conjf(in[s + total_offset - lag]); + return cargf(v); +} + + +double gsm_fcch_offset(float *burst, int len) +{ + int start; + + const float fs = 13. / 48. * 1e6 * 4; + const float expected_fcch_val = ((2 * M_PI) / (fs)) * 67700; + + if (len > GSM_MAX_BURST_LEN) + len = GSM_MAX_BURST_LEN; + + start = FCCH_TAIL_BITS_LEN+10 * 4; + float alpha_one = ac_sum_with_lag((complex float*)burst, L1, start, N1); + float alpha_two = ac_sum_with_lag((complex float*)burst, L2, start, N2); + + float P_unrounded = (L1 * alpha_two - L2 * alpha_one) / (2 * M_PI); + int P = roundf(P_unrounded); + + int8_t r = 0, s = 0; + pinv(P, &r, &s, L1, L2); + + float omegal1 = (alpha_one + 2 * M_PI * r) / L1; + float omegal2 = (alpha_two + 2 * M_PI * s) / L2; + + float rv = org_gsm_fcch_offset(burst, len); + //return rv; + + float reval = GSM_SYM_RATE / (2 * M_PI) * (expected_fcch_val - (omegal1+omegal2)/2); + //fprintf(stderr, "XX rv %f %f %f %f\n", rv, reval, omegal1 / (2 * M_PI) * fs, omegal2 / (2 * M_PI) * fs); + + //fprintf(stderr, "XX rv %f %f\n", rv, reval); + + return -reval; +} +#endif +/* Generate FCCH measurement tone */ +static __attribute__((constructor)) void init() +{ + int i; + double freq = 0.25; + + for (i = 0; i < GSM_MAX_BURST_LEN; i++) { + fcch_ref[i] = sin(2 * M_PI * freq * (double) i) + + cos(2 * M_PI * freq * (double) i) * I; + } + +} + +#pragma GCC diagnostic pop diff --git a/Transceiver52M/ms/sch.h b/Transceiver52M/ms/sch.h new file mode 100644 index 0000000..ea8ba85 --- /dev/null +++ b/Transceiver52M/ms/sch.h @@ -0,0 +1,48 @@ +#pragma once +/* + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> + * (C) 2016 by Tom Tsou <tom.tsou@ettus.com> + * (C) 2017 by Harald Welte <laforge@gnumonks.org> + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> / Eric Wild <ewild@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <osmocom/core/bits.h> + +struct sch_info { + int bsic; + int t1; + int t2; + int t3p; +}; + +#define GSM_SCH_INFO_LEN 25 +#define GSM_SCH_UNCODED_LEN 35 +#define GSM_SCH_CODED_LEN 78 + +int gsm_sch_decode(uint8_t *sb_info, sbit_t *burst); +int gsm_sch_parse(const uint8_t *sb_info, struct sch_info *desc); +int gsm_sch_to_fn(struct sch_info *sch); +int gsm_sch_check_fn(int fn); +int gsm_fcch_check_fn(int fn); +int gsm_fcch_check_ts(int ts, int fn); +int gsm_sch_check_ts(int ts, int fn); + +double gsm_fcch_offset(float *burst, int len); + +int float_to_sbit(const float *in, sbit_t *out, float scale, int len); + diff --git a/Transceiver52M/ms/threadpool.h b/Transceiver52M/ms/threadpool.h new file mode 100644 index 0000000..4b1eefd --- /dev/null +++ b/Transceiver52M/ms/threadpool.h @@ -0,0 +1,95 @@ +#pragma once +/* + * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <atomic> +#include <vector> +#include <future> +#include <mutex> +#include <queue> +#include "threadsched.h" + +struct single_thread_pool { + std::mutex m; + std::condition_variable cv; + std::atomic<bool> stop_flag; + std::atomic<bool> is_ready; + std::deque<std::function<void()>> wq; + pthread_t worker_thread; + + template <class F> + void add_task(F &&f) + { + std::unique_lock<std::mutex> l(m); + wq.emplace_back(std::forward<F>(f)); + cv.notify_one(); + return; + } + + single_thread_pool() : stop_flag(false), is_ready(false) + { + worker_thread = spawn_worker_thread( + sched_params::thread_names::SCH_SEARCH, + [](void *args) -> void * { + using thist = decltype(this); + thist t = reinterpret_cast<thist>(args); + t->thread_loop(); + return 0; + }, + this); + } + ~single_thread_pool() + { + stop(); + } + + private: + void stop() + { + { + std::unique_lock<std::mutex> l(m); + wq.clear(); + stop_flag = true; + cv.notify_one(); + } + pthread_join(worker_thread, nullptr); + } + + void thread_loop() + { + while (true) { + is_ready = true; + std::function<void()> f; + { + std::unique_lock<std::mutex> l(m); + if (wq.empty()) { + cv.wait(l, [&] { return !wq.empty() || stop_flag; }); + } + if (stop_flag) + return; + is_ready = false; + f = std::move(wq.front()); + wq.pop_front(); + } + f(); + } + } +};
\ No newline at end of file diff --git a/Transceiver52M/ms/threadsched.cpp b/Transceiver52M/ms/threadsched.cpp new file mode 100644 index 0000000..ba5cae7 --- /dev/null +++ b/Transceiver52M/ms/threadsched.cpp @@ -0,0 +1,104 @@ +/* + * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <iostream> +#include <thread> + +extern "C" { +#include <pthread.h> +} + +#include "threadsched.h" + +sched_params::target scheduling_target; + +void set_sched_target(sched_params::target t) +{ + scheduling_target = t; +} + +void set_name_aff_sched(std::thread::native_handle_type h, const char *name, int cpunum, int schedtype, int prio) +{ + pthread_setname_np(h, name); + + cpu_set_t cpuset; + + CPU_ZERO(&cpuset); + CPU_SET(cpunum, &cpuset); + + if (pthread_setaffinity_np(h, sizeof(cpuset), &cpuset) < 0) { + std::cerr << name << " affinity: errreur! " << std::strerror(errno); + return exit(0); + } + + sched_param sch_params; + sch_params.sched_priority = prio; + if (pthread_setschedparam(h, schedtype, &sch_params) < 0) { + std::cerr << name << " sched: errreur! " << std::strerror(errno); + return exit(0); + } +} + +static pthread_t do_spawn_thr(const char *name, int cpunum, int schedtype, int prio, worker_func_sig fun, void *arg) +{ + pthread_t thread; + + pthread_attr_t attr; + pthread_attr_init(&attr); + + sched_param sch_params; + sch_params.sched_priority = prio; + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(cpunum, &cpuset); + auto a = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset); + a |= pthread_attr_setschedpolicy(&attr, schedtype); + a |= pthread_attr_setschedparam(&attr, &sch_params); + a |= pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + if (a) + std::cerr << "thread arg rc:" << a << std::endl; + pthread_create(&thread, &attr, fun, arg); + pthread_setname_np(thread, name); + pthread_attr_destroy(&attr); + return thread; +} + +void set_name_aff_sched(std::thread::native_handle_type h, sched_params::thread_names name) +{ + auto tgt = schdp[scheduling_target][name]; + // std::cerr << "scheduling for: " << tgt.name << ":" << tgt.core << std::endl; + set_name_aff_sched(h, tgt.name, tgt.core, tgt.schedtype, tgt.prio); +} + +void set_name_aff_sched(sched_params::thread_names name) +{ + set_name_aff_sched(pthread_self(), name); +} + +pthread_t spawn_worker_thread(sched_params::thread_names name, worker_func_sig fun, void *arg) +{ + auto tgt = schdp[scheduling_target][name]; + // std::cerr << "scheduling for: " << tgt.name << ":" << tgt.core << " prio:" << tgt.prio << std::endl; + return do_spawn_thr(tgt.name, tgt.core, tgt.schedtype, tgt.prio, fun, arg); +} diff --git a/Transceiver52M/ms/threadsched.h b/Transceiver52M/ms/threadsched.h new file mode 100644 index 0000000..7cc9176 --- /dev/null +++ b/Transceiver52M/ms/threadsched.h @@ -0,0 +1,68 @@ +#pragma once +/* + * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +extern "C" { +#include <pthread.h> +#include <sched.h> +} + +static struct sched_params { + enum thread_names { U_CTL = 0, U_RX, U_TX, SCH_SEARCH, MAIN, LEAKCHECK, RXRUN, TXRUN, _THRD_NAME_COUNT }; + enum target { ODROID = 0, PI4 }; + const char *name; + int core; + int schedtype; + int prio; +} schdp[][sched_params::_THRD_NAME_COUNT]{ + { + { "upper_ctrl", 2, SCHED_RR, sched_get_priority_max(SCHED_RR) }, + { "upper_rx", 2, SCHED_RR, sched_get_priority_max(SCHED_RR) - 5 }, + { "upper_tx", 2, SCHED_RR, sched_get_priority_max(SCHED_RR) - 1 }, + + { "sch_search", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 }, + { "main", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 }, + { "leakcheck", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10 }, + + { "rxrun", 4, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2 }, + { "txrun", 5, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1 }, + }, + { + { "upper_ctrl", 1, SCHED_RR, sched_get_priority_max(SCHED_RR) }, + { "upper_rx", 1, SCHED_RR, sched_get_priority_max(SCHED_RR) - 5 }, + { "upper_tx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1 }, + + { "sch_search", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 }, + { "main", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 }, + { "leakcheck", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10 }, + + { "rxrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2 }, + { "txrun", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1 }, + }, +}; + +void set_sched_target(sched_params::target t); + +using worker_func_sig = void *(*)(void *); + +void set_name_aff_sched(sched_params::thread_names name); + +pthread_t spawn_worker_thread(sched_params::thread_names name, worker_func_sig fun, void *arg); diff --git a/Transceiver52M/ms/uhd_specific.h b/Transceiver52M/ms/uhd_specific.h new file mode 100644 index 0000000..151c002 --- /dev/null +++ b/Transceiver52M/ms/uhd_specific.h @@ -0,0 +1,279 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <uhd/version.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/types/metadata.hpp> +#include <complex> +#include <cstring> +#include <iostream> +#include <thread> + +#include <Timeval.h> +#include <vector> + +using blade_sample_type = std::complex<int16_t>; +const int SAMPLE_SCALE_FACTOR = 1; + +struct uhd_buf_wrap { + double rxticks; + size_t num_samps; + uhd::rx_metadata_t *md; + blade_sample_type *buf; + auto actual_samples_per_buffer() + { + return num_samps; + } + long get_first_ts() + { + return md->time_spec.to_ticks(rxticks); + } + int readall(blade_sample_type *outaddr) + { + memcpy(outaddr, buf, num_samps * sizeof(blade_sample_type)); + return num_samps; + } + int read_n(blade_sample_type *outaddr, int start, int num) + { + assert(start >= 0); + auto to_read = std::min((int)num_samps - start, num); + assert(to_read >= 0); + memcpy(outaddr, buf + start, to_read * sizeof(blade_sample_type)); + return to_read; + } +}; + +using dev_buf_t = uhd_buf_wrap; +using bh_fn_t = std::function<int(dev_buf_t *)>; + +template <typename T> +struct uhd_hw { + uhd::usrp::multi_usrp::sptr dev; + uhd::rx_streamer::sptr rx_stream; + uhd::tx_streamer::sptr tx_stream; + blade_sample_type *one_pkt_buf; + std::vector<blade_sample_type *> pkt_ptrs; + size_t rx_spp; + double rxticks; + const unsigned int rxFullScale, txFullScale; + const int rxtxdelay; + float rxgain, txgain; + static std::atomic<bool> stop_lower_threads_flag; + double rxfreq_cache, txfreq_cache; + + virtual ~uhd_hw() + { + delete[] one_pkt_buf; + } + uhd_hw() : rxFullScale(32767), txFullScale(32767 * 0.3), rxtxdelay(-67), rxfreq_cache(0), txfreq_cache(0) + { + } + + void close_device() + { + } + + bool tuneTx(double freq, size_t chan = 0) + { + if (txfreq_cache == freq) + return true; + msleep(25); + dev->set_tx_freq(freq, chan); + txfreq_cache = freq; + msleep(25); + return true; + }; + bool tuneRx(double freq, size_t chan = 0) + { + if (rxfreq_cache == freq) + return true; + msleep(25); + dev->set_rx_freq(freq, chan); + rxfreq_cache = freq; + msleep(25); + return true; + }; + bool tuneRxOffset(double offset, size_t chan = 0) + { + return true; + }; + + double setRxGain(double dB, size_t chan = 0) + { + rxgain = dB; + msleep(25); + dev->set_rx_gain(dB, chan); + msleep(25); + return dB; + }; + double setTxGain(double dB, size_t chan = 0) + { + txgain = dB; + msleep(25); + dev->set_tx_gain(dB, chan); + msleep(25); + return dB; + }; + int setPowerAttenuation(int atten, size_t chan = 0) + { + return atten; + }; + + int init_device(bh_fn_t rxh, bh_fn_t txh) + { + auto const lock_delay_ms = 500; + auto clock_lock_attempts = 15; // x lock_delay_ms + auto const mcr = 26e6; + auto const rate = (1625e3 / 6) * 4; + auto const ref = "external"; + auto const gain = 35; + auto const freq = 931.4e6; // 936.8e6 + auto bw = 0.5e6; + auto const channel = 0; + // aligned to blade: 1020 samples per transfer + std::string args = { "recv_frame_size=4092,send_frame_size=4092" }; + + dev = uhd::usrp::multi_usrp::make(args); + std::cout << "Using Device: " << dev->get_pp_string() << std::endl; + dev->set_clock_source(ref); + dev->set_master_clock_rate(mcr); + dev->set_rx_rate(rate, channel); + dev->set_tx_rate(rate, channel); + uhd::tune_request_t tune_request(freq, 0); + dev->set_rx_freq(tune_request, channel); + dev->set_rx_gain(gain, channel); + dev->set_tx_gain(60, channel); + dev->set_rx_bandwidth(bw, channel); + dev->set_tx_bandwidth(bw, channel); + + while (!(dev->get_rx_sensor("lo_locked", channel).to_bool() && + dev->get_mboard_sensor("ref_locked").to_bool()) && + clock_lock_attempts > 0) { + std::cerr << "clock source lock attempts remaining: " << clock_lock_attempts << ".." + << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(lock_delay_ms)); + clock_lock_attempts--; + } + + if (clock_lock_attempts <= 0) { + std::cerr << "Error locking clock, gpsdo missing? quitting.." << std::endl; + return -1; + } + + uhd::stream_args_t stream_args("sc16", "sc16"); + rx_stream = dev->get_rx_stream(stream_args); + uhd::stream_args_t stream_args2("sc16", "sc16"); + tx_stream = dev->get_tx_stream(stream_args2); + + rx_spp = rx_stream->get_max_num_samps(); + rxticks = dev->get_rx_rate(); + assert(rxticks == dev->get_tx_rate()); + one_pkt_buf = new blade_sample_type[rx_spp]; + pkt_ptrs = { 1, &one_pkt_buf[0] }; + return 0; + } + + void actually_enable_streams() + { + // nop: stream cmd in handler + } + + void *rx_cb(bh_fn_t burst_handler) + { + void *ret = nullptr; + static int to_skip = 0; + + uhd::rx_metadata_t md; + auto num_rx_samps = rx_stream->recv(pkt_ptrs.front(), rx_spp, md, 1.0, true); + + if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) { + std::cerr << boost::format("Timeout while streaming") << std::endl; + exit(0); + } + if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW) { + std::cerr << boost::format("Got an overflow indication\n") << std::endl; + exit(0); + } + if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE) { + std::cerr << str(boost::format("Receiver error: %s") % md.strerror()); + exit(0); + } + + dev_buf_t rcd = { rxticks, num_rx_samps, &md, &one_pkt_buf[0] }; + + if (to_skip < 120) // prevents weird overflows on startup + to_skip++; + else { + burst_handler(&rcd); + } + + return ret; + } + + auto get_rx_burst_handler_fn(bh_fn_t burst_handler) + { + // C cb -> ghetto closure capture, which is fine, the args never change. + static auto rx_burst_cap_this = this; + static auto rx_burst_cap_bh = burst_handler; + auto fn = [](void *args) -> void * { + pthread_setname_np(pthread_self(), "rxrun"); + + uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + stream_cmd.stream_now = true; + stream_cmd.time_spec = uhd::time_spec_t(); + rx_burst_cap_this->rx_stream->issue_stream_cmd(stream_cmd); + + while (!rx_burst_cap_this->stop_lower_threads_flag) { + rx_burst_cap_this->rx_cb(rx_burst_cap_bh); + } + return 0; + }; + return fn; + } + auto get_tx_burst_handler_fn(bh_fn_t burst_handler) + { + auto fn = [](void *args) -> void * { + // dummy + return 0; + }; + return fn; + } + void submit_burst_ts(blade_sample_type *buffer, int len, uint64_t ts) + { + uhd::tx_metadata_t m = {}; + m.end_of_burst = true; + m.start_of_burst = true; + m.has_time_spec = true; + m.time_spec = m.time_spec.from_ticks(ts + rxtxdelay, rxticks); // uhd specific b210 delay! + std::vector<void *> ptrs(1, buffer); + + tx_stream->send(ptrs, len, m, 1.0); +#ifdef DBGXX + uhd::async_metadata_t async_md; + bool tx_ack = false; + while (!tx_ack && tx_stream->recv_async_msg(async_md)) { + tx_ack = (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_BURST_ACK); + } + std::cout << (tx_ack ? "yay" : "nay") << " " << async_md.time_spec.to_ticks(rxticks) << std::endl; +#endif + } +};
\ No newline at end of file diff --git a/Transceiver52M/osmo-trx.cpp b/Transceiver52M/osmo-trx.cpp index ab0b631..8f5cc85 100644 --- a/Transceiver52M/osmo-trx.cpp +++ b/Transceiver52M/osmo-trx.cpp @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H @@ -55,6 +51,7 @@ extern "C" { #include <osmocom/ctrl/control_if.h> #include <osmocom/vty/stats.h> #include <osmocom/vty/command.h> +#include <osmocom/vty/cpu_sched_vty.h> #include "convolve.h" #include "convert.h" @@ -79,7 +76,24 @@ static struct ctrl_handle *g_ctrlh; static RadioDevice *usrp; static RadioInterface *radio; -static Transceiver *transceiver; + +/* adjusts read timestamp offset to make the viterbi equalizer happy by including the start tail bits */ +template <typename B> +class rif_va_wrapper : public B { + bool use_va; + + public: + template <typename... Args> + rif_va_wrapper(bool use_va, Args &&...args) : B(std::forward<Args>(args)...), use_va(use_va) + { + } + bool start() override + { + auto rv = B::start(); + B::readTimestamp -= use_va ? 20 : 0; + return rv; + }; +}; /* Create radio interface * The interface consists of sample rate changes, frequency shifts, @@ -95,17 +109,17 @@ RadioInterface *makeRadioInterface(struct trx_ctx *trx, switch (type) { case RadioDevice::NORMAL: - radio = new RadioInterface(usrp, trx->cfg.tx_sps, - trx->cfg.rx_sps, trx->cfg.num_chans); + radio = new rif_va_wrapper<RadioInterface>(trx->cfg.use_va, usrp, trx->cfg.tx_sps, trx->cfg.rx_sps, + trx->cfg.num_chans); break; case RadioDevice::RESAMP_64M: case RadioDevice::RESAMP_100M: - radio = new RadioInterfaceResamp(usrp, trx->cfg.tx_sps, - trx->cfg.rx_sps); + radio = new rif_va_wrapper<RadioInterfaceResamp>(trx->cfg.use_va, usrp, trx->cfg.tx_sps, + trx->cfg.rx_sps); break; case RadioDevice::MULTI_ARFCN: - radio = new RadioInterfaceMulti(usrp, trx->cfg.tx_sps, - trx->cfg.rx_sps, trx->cfg.num_chans); + radio = new rif_va_wrapper<RadioInterfaceMulti>(trx->cfg.use_va, usrp, trx->cfg.tx_sps, trx->cfg.rx_sps, + trx->cfg.num_chans); break; default: LOG(ALERT) << "Unsupported radio interface configuration"; @@ -144,12 +158,8 @@ int makeTransceiver(struct trx_ctx *trx, RadioInterface *radio) { VectorFIFO *fifo; - transceiver = new Transceiver(trx->cfg.base_port, trx->cfg.bind_addr, - trx->cfg.remote_addr, trx->cfg.tx_sps, - trx->cfg.rx_sps, trx->cfg.num_chans, GSM::Time(3,0), - radio, trx->cfg.rssi_offset, trx->cfg.stack_size); - if (!transceiver->init(trx->cfg.filler, trx->cfg.rtsc, - trx->cfg.rach_delay, trx->cfg.egprs, trx->cfg.ext_rach)) { + transceiver = new Transceiver(&trx->cfg, GSM::Time(3,0), radio); + if (!transceiver->init()) { LOG(ALERT) << "Failed to initialize transceiver"; return -1; } @@ -181,6 +191,17 @@ static void sig_handler(int signo) gshutdown = true; break; case SIGABRT: + /* in case of abort, we want to obtain a talloc report and + * then run default SIGABRT handler, who will generate coredump + * and abort the process. abort() should do this for us after we + * return, but program wouldn't exit if an external SIGABRT is + * received. + */ + talloc_report(tall_trx_ctx, stderr); + talloc_report_full(tall_trx_ctx, stderr); + signal(SIGABRT, SIG_DFL); + raise(SIGABRT); + break; case SIGUSR1: talloc_report(tall_trx_ctx, stderr); talloc_report_full(tall_trx_ctx, stderr); @@ -237,7 +258,7 @@ static void setup_signal_handlers() exit(EXIT_FAILURE); } - osmo_fd_setup(&signal_ofd, sfd, BSC_FD_READ, signalfd_callback, NULL, 0); + osmo_fd_setup(&signal_ofd, sfd, OSMO_FD_READ, signalfd_callback, NULL, 0); if (osmo_fd_register(&signal_ofd) < 0) { fprintf(stderr, "osmo_fd_register() failed.\n"); exit(EXIT_FAILURE); @@ -246,10 +267,13 @@ static void setup_signal_handlers() static void print_help() { - fprintf(stdout, "Options:\n" - " -h, --help This text\n" - " -C, --config Filename The config file to use\n" - " -V, --version Print the version of OsmoTRX\n" + printf( "Some useful options:\n" + " -h, --help This text\n" + " -C, --config Filename The config file to use\n" + " -V, --version Print the version of OsmoTRX\n" + "\nVTY reference generation:\n" + " --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n" + " --vty-ref-xml Generate the VTY reference XML output and exit.\n" ); } @@ -260,16 +284,44 @@ static void print_deprecated(char opt) << " All cmd line options are already being overridden by VTY options if set."; } +static void handle_long_options(const char *prog_name, const int long_option) +{ + static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT; + + switch (long_option) { + case 1: + vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg); + if (vty_ref_mode < 0) { + fprintf(stderr, "%s: Unknown VTY reference generation " + "mode '%s'\n", prog_name, optarg); + exit(2); + } + break; + case 2: + fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n", + get_value_string(vty_ref_gen_mode_names, vty_ref_mode), + get_value_string(vty_ref_gen_mode_desc, vty_ref_mode)); + vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode); + exit(0); + default: + fprintf(stderr, "%s: error parsing cmdline options\n", prog_name); + exit(2); + } +} + static void handle_options(int argc, char **argv, struct trx_ctx* trx) { int option; unsigned int i; std::vector<std::string> rx_paths, tx_paths; bool rx_paths_set = false, tx_paths_set = false; + static int long_option = 0; static struct option long_options[] = { {"help", 0, 0, 'h'}, {"config", 1, 0, 'C'}, {"version", 0, 0, 'V'}, + {"vty-ref-mode", 1, &long_option, 1}, + {"vty-ref-xml", 0, &long_option, 2}, {NULL, 0, 0, 0} }; @@ -280,6 +332,9 @@ static void handle_options(int argc, char **argv, struct trx_ctx* trx) print_help(); exit(0); break; + case 0: + handle_long_options(argv[0], long_option); + break; case 'a': print_deprecated(option); osmo_talloc_replace_string(trx, &trx->cfg.dev_args, optarg); @@ -334,20 +389,19 @@ static void handle_options(int argc, char **argv, struct trx_ctx* trx) break; case 'r': print_deprecated(option); - trx->cfg.rtsc_set = true; trx->cfg.rtsc = atoi(optarg); if (!trx->cfg.egprs) /* Don't override egprs which sets different filler */ trx->cfg.filler = FILLER_NORM_RAND; break; case 'A': print_deprecated(option); - trx->cfg.rach_delay_set = true; trx->cfg.rach_delay = atoi(optarg); trx->cfg.filler = FILLER_ACCESS_RAND; break; case 'R': print_deprecated(option); trx->cfg.rssi_offset = atof(optarg); + trx->cfg.force_rssi_offset = true; break; case 'S': print_deprecated(option); @@ -383,6 +437,11 @@ static void handle_options(int argc, char **argv, struct trx_ctx* trx) } } + if (argc > optind) { + LOG(ERROR) << "Unsupported positional arguments on command line"; + goto bad_config; + } + /* Cmd line option specific validation & setup */ if (trx->cfg.num_chans > TRX_CHAN_MAX) { @@ -424,6 +483,12 @@ int trx_validate_config(struct trx_ctx *trx) return -1; } + if (trx->cfg.use_va && + (trx->cfg.egprs || trx->cfg.multi_arfcn || trx->cfg.tx_sps != 4 || trx->cfg.rx_sps != 4)) { + LOG(ERROR) << "Viterbi equalizer only works for gmsk with 4 tx/rx samples per symbol!"; + return -1; + } + return 0; } @@ -433,7 +498,9 @@ static int set_sched_rr(unsigned int prio) int rc; memset(¶m, 0, sizeof(param)); param.sched_priority = prio; - LOG(INFO) << "Setting SCHED_RR priority " << param.sched_priority; + LOG(INFO) << "Setting SCHED_RR priority " << param.sched_priority + << ". This setting is DEPRECATED, please use 'policy rr " << param.sched_priority + << "' under the 'sched' VTY node instead."; rc = sched_setscheduler(getpid(), SCHED_RR, ¶m); if (rc != 0) { LOG(ERROR) << "Config: Setting SCHED_RR failed"; @@ -442,6 +509,38 @@ static int set_sched_rr(unsigned int prio) return 0; } +static void print_simd_info(void) +{ +#ifdef HAVE_SSE3 + LOGP(DMAIN, LOGL_INFO, "SSE3 support compiled in"); +#ifdef HAVE___BUILTIN_CPU_SUPPORTS + if (__builtin_cpu_supports("sse3")) + LOGPC(DMAIN, LOGL_INFO, " and supported by CPU\n"); + else + LOGPC(DMAIN, LOGL_INFO, ", but not supported by CPU\n"); +#else + LOGPC(DMAIN, LOGL_INFO, ", but runtime SIMD detection disabled\n"); +#endif +#endif + +#ifdef HAVE_SSE4_1 + LOGP(DMAIN, LOGL_INFO, "SSE4.1 support compiled in"); +#ifdef HAVE___BUILTIN_CPU_SUPPORTS + if (__builtin_cpu_supports("sse4.1")) + LOGPC(DMAIN, LOGL_INFO, " and supported by CPU\n"); + else + LOGPC(DMAIN, LOGL_INFO, ", but not supported by CPU\n"); +#else + LOGPC(DMAIN, LOGL_INFO, ", but runtime SIMD detection disabled\n"); +#endif +#endif + +#ifndef HAVE_ATOMIC_OPS +#pragma message ("Built without atomic operation support. Using Mutex, it may affect performance!") + LOG(NOTICE) << "Built without atomic operation support. Using Mutex, it may affect performance!"; +#endif +} + static void print_config(struct trx_ctx *trx) { unsigned int i; @@ -459,10 +558,14 @@ static void print_config(struct trx_ctx *trx) ost << " EDGE support............ " << trx->cfg.egprs << std::endl; ost << " Extended RACH support... " << trx->cfg.ext_rach << std::endl; ost << " Reference............... " << trx->cfg.clock_ref << std::endl; - ost << " C0 Filler Table......... " << trx->cfg.filler << std::endl; + ost << " Filler Burst Type....... " << get_value_string(filler_names, trx->cfg.filler) << std::endl; + ost << " Filler Burst TSC........ " << trx->cfg.rtsc << std::endl; + ost << " Filler Burst RACH Delay. " << trx->cfg.rach_delay << std::endl; ost << " Multi-Carrier........... " << trx->cfg.multi_arfcn << std::endl; - ost << " Tuning offset........... " << trx->cfg.offset << std::endl; - ost << " RSSI to dBm offset...... " << trx->cfg.rssi_offset << std::endl; + ost << " LO freq. offset......... " << trx->cfg.offset << std::endl; + if (trx->cfg.freq_offset_khz != 0) + ost << " Tune freq. offset....... " << trx->cfg.freq_offset_khz << std::endl; + ost << " RSSI to dBm offset...... " << trx->cfg.rssi_offset << (trx->cfg.force_rssi_offset ? "" : " (relative)") << std::endl; ost << " Swap channels........... " << trx->cfg.swap_channels << std::endl; ost << " Tx Antennas............."; for (i = 0; i < trx->cfg.num_chans; i++) { @@ -492,24 +595,14 @@ static void trx_stop() static int trx_start(struct trx_ctx *trx) { int type, chans; - unsigned int i; - std::vector<std::string> rx_paths, tx_paths; RadioDevice::InterfaceType iface = RadioDevice::NORMAL; /* Create the low level device object */ if (trx->cfg.multi_arfcn) iface = RadioDevice::MULTI_ARFCN; - /* Generate vector of rx/tx_path: */ - for (i = 0; i < trx->cfg.num_chans; i++) { - rx_paths.push_back(charp2str(trx->cfg.chans[i].rx_path)); - tx_paths.push_back(charp2str(trx->cfg.chans[i].tx_path)); - } - - usrp = RadioDevice::make(trx->cfg.tx_sps, trx->cfg.rx_sps, iface, - trx->cfg.num_chans, trx->cfg.offset, - tx_paths, rx_paths); - type = usrp->open(charp2str(trx->cfg.dev_args), trx->cfg.clock_ref, trx->cfg.swap_channels); + usrp = RadioDevice::make(iface, &trx->cfg); + type = usrp->open(); if (type < 0) { LOG(ALERT) << "Failed to create radio device" << std::endl; goto shutdown; @@ -547,44 +640,18 @@ int main(int argc, char *argv[]) g_trx_ctx = vty_trx_ctx_alloc(tall_trx_ctx); -#ifdef HAVE_SSE3 - printf("Info: SSE3 support compiled in"); -#ifdef HAVE___BUILTIN_CPU_SUPPORTS - if (__builtin_cpu_supports("sse3")) - printf(" and supported by CPU\n"); - else - printf(", but not supported by CPU\n"); -#else - printf(", but runtime SIMD detection disabled\n"); -#endif -#endif - -#ifdef HAVE_SSE4_1 - printf("Info: SSE4.1 support compiled in"); -#ifdef HAVE___BUILTIN_CPU_SUPPORTS - if (__builtin_cpu_supports("sse4.1")) - printf(" and supported by CPU\n"); - else - printf(", but not supported by CPU\n"); -#else - printf(", but runtime SIMD detection disabled\n"); -#endif -#endif - - if (!log_mutex_init()) { - fprintf(stderr, "Failed to initialize log mutex!\n"); - exit(2); - } convolve_init(); convert_init(); osmo_init_logging2(tall_trx_ctx, &log_info); + log_enable_multithread(); osmo_stats_init(tall_trx_ctx); vty_init(&g_vty_info); + logging_vty_add_cmds(); ctrl_vty_init(tall_trx_ctx); + osmo_cpu_sched_vty_init(tall_trx_ctx); trx_vty_init(g_trx_ctx); - logging_vty_add_cmds(); osmo_talloc_vty_add_cmds(); osmo_stats_vty_add_cmds(); @@ -598,11 +665,11 @@ int main(int argc, char *argv[]) exit(2); } - rc = telnet_init_dynif(tall_trx_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_TRX); + rc = telnet_init_default(tall_trx_ctx, NULL, OSMO_VTY_PORT_TRX); if (rc < 0) exit(1); - g_ctrlh = ctrl_interface_setup_dynip(NULL, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_TRX, NULL); + g_ctrlh = ctrl_interface_setup(NULL, OSMO_CTRL_PORT_TRX, NULL); if (!g_ctrlh) { LOG(ERROR) << "Failed to create CTRL interface.\n"; exit(1); @@ -619,6 +686,7 @@ int main(int argc, char *argv[]) " but expect your config to break in the future."; } + print_simd_info(); print_config(g_trx_ctx); if (trx_validate_config(g_trx_ctx) < 0) { diff --git a/Transceiver52M/proto_trxd.c b/Transceiver52M/proto_trxd.c index fbef77a..e5a889d 100644 --- a/Transceiver52M/proto_trxd.c +++ b/Transceiver52M/proto_trxd.c @@ -62,17 +62,17 @@ static void trxd_fill_burst_normalized255(uint8_t* soft_bits, const struct trx_u { unsigned i; for (i = 0; i < bi->nbits; i++) - soft_bits[i] = (char) round(bi->rx_burst[i] * 255.0); + soft_bits[i] = (uint8_t) round(bi->rx_burst[i] * 255.0); } bool trxd_send_burst_ind_v0(size_t chan, int fd, const struct trx_ul_burst_ind *bi) { int rc; /* v0 doesn't support idle frames, they are simply dropped, not sent */ - if(bi->idle) + if (bi->idle) return true; - /* +2: Historically (OpenBTS times), two extra non-used bytes are sent appeneded to each burst */ + /* +2: Historically (OpenBTS times), two extra non-used bytes are sent appended to each burst */ char buf[sizeof(struct trxd_hdr_v0) + bi->nbits + 2]; struct trxd_hdr_v0* pkt = (struct trxd_hdr_v0*)buf; diff --git a/Transceiver52M/proto_trxd.h b/Transceiver52M/proto_trxd.h index d72cfdd..c250a74 100644 --- a/Transceiver52M/proto_trxd.h +++ b/Transceiver52M/proto_trxd.h @@ -48,9 +48,8 @@ struct trxd_hdr_common { reserved:1, version:4; #elif OSMO_IS_BIG_ENDIAN - uint8_t version:4, - reserved:1, - tn:3; +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ + uint8_t version:4, reserved:1, tn:3; #endif uint32_t fn; /* big endian */ } __attribute__ ((packed)); @@ -86,9 +85,8 @@ struct trxd_hdr_v1_specific { modulation:4, idle:1; #elif OSMO_IS_BIG_ENDIAN - uint8_t idle:1, - modulation:4, - tsc:3; +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ + uint8_t idle:1, modulation:4, tsc:3; #endif int16_t ci; /* big endian, in centiBels */ } __attribute__ ((packed)); diff --git a/Transceiver52M/radioBuffer.cpp b/Transceiver52M/radioBuffer.cpp index 62f6553..ec868e5 100644 --- a/Transceiver52M/radioBuffer.cpp +++ b/Transceiver52M/radioBuffer.cpp @@ -28,7 +28,7 @@ RadioBuffer::RadioBuffer(size_t numSegments, size_t segmentLen, size_t hLen, bool outDirection) - : writeIndex(0), readIndex(0), availSamples(0) + : writeIndex(0), readIndex(0), availSamples(0), segments(numSegments) { if (!outDirection) hLen = 0; @@ -36,7 +36,6 @@ RadioBuffer::RadioBuffer(size_t numSegments, size_t segmentLen, buffer = new float[2 * (hLen + numSegments * segmentLen)]; bufferLen = numSegments * segmentLen; - segments.resize(numSegments); for (size_t i = 0; i < numSegments; i++) segments[i] = &buffer[2 * (hLen + i * segmentLen)]; diff --git a/Transceiver52M/radioInterface.cpp b/Transceiver52M/radioInterface.cpp index 0d33f22..311af34 100644 --- a/Transceiver52M/radioInterface.cpp +++ b/Transceiver52M/radioInterface.cpp @@ -24,20 +24,25 @@ #include "radioInterface.h" #include "Resampler.h" #include <Logger.h> +#include <Threads.h> extern "C" { +#include <osmocom/core/utils.h> +#include <osmocom/vty/cpu_sched_vty.h> + #include "convert.h" } #define CHUNK 625 #define NUMCHUNKS 4 -RadioInterface::RadioInterface(RadioDevice *wRadio, size_t tx_sps, +RadioInterface::RadioInterface(RadioDevice *wDevice, size_t tx_sps, size_t rx_sps, size_t chans, int wReceiveOffset, GSM::Time wStartTime) - : mRadio(wRadio), 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); } @@ -54,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); @@ -93,16 +89,16 @@ void RadioInterface::close() } double RadioInterface::fullScaleInputValue(void) { - return mRadio->fullScaleInputValue(); + return mDevice->fullScaleInputValue(); } double RadioInterface::fullScaleOutputValue(void) { - return mRadio->fullScaleOutputValue(); + return mDevice->fullScaleOutputValue(); } int RadioInterface::setPowerAttenuation(int atten, size_t chan) { - double rfGain, digAtten; + double rfAtten, digAtten; if (chan >= mChans) { LOG(ALERT) << "Invalid channel requested"; @@ -112,8 +108,8 @@ int RadioInterface::setPowerAttenuation(int atten, size_t chan) if (atten < 0.0) atten = 0.0; - rfGain = mRadio->setTxGain(mRadio->maxTxGain() - (double) atten, chan); - digAtten = (double) atten - mRadio->maxTxGain() + rfGain; + rfAtten = mDevice->setPowerAttenuation((double) atten, chan); + digAtten = (double) atten - rfAtten; if (digAtten < 1.0) powerScaling[chan] = 1.0; @@ -123,6 +119,16 @@ int RadioInterface::setPowerAttenuation(int atten, size_t chan) return atten; } +int RadioInterface::getNominalTxPower(size_t chan) +{ + if (chan >= mChans) { + LOG(ALERT) << "Invalid channel requested"; + return -1; + } + + return mDevice->getNominalTxPower(chan); +} + int RadioInterface::radioifyVector(signalVector &wVector, size_t chan, bool zero) { @@ -148,18 +154,19 @@ int RadioInterface::unRadioifyVector(signalVector *newVector, size_t chan) bool RadioInterface::tuneTx(double freq, size_t chan) { - return mRadio->setTxFreq(freq, chan); + return mDevice->setTxFreq(freq, chan); } bool RadioInterface::tuneRx(double freq, size_t chan) { - return mRadio->setRxFreq(freq, chan); + return mDevice->setRxFreq(freq, chan); } /** synchronization thread loop */ void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface) { set_selfthread_name("AlignRadio"); + OSMO_ASSERT(osmo_cpu_sched_vty_apply_localthread() == 0); while (1) { sleep(60); radioInterface->alignRadio(); @@ -169,7 +176,7 @@ void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface) } void RadioInterface::alignRadio() { - mRadio->updateAlignment(writeTimestamp+ (TIMESTAMP) 10000); + mDevice->updateAlignment(writeTimestamp+ (TIMESTAMP) 10000); } bool RadioInterface::start() @@ -178,12 +185,12 @@ bool RadioInterface::start() return true; LOG(INFO) << "Starting radio device"; - if (mRadio->requiresRadioAlign()) + if (mDevice->requiresRadioAlign()) mAlignRadioServiceLoopThread.start( (void * (*)(void*))AlignRadioServiceLoopAdapter, (void*)this); - if (!mRadio->start()) + if (!mDevice->start()) return false; for (size_t i = 0; i < mChans; i++) { @@ -191,11 +198,11 @@ bool RadioInterface::start() recvBuffer[i]->reset(); } - writeTimestamp = mRadio->initialWriteTimestamp(); - readTimestamp = mRadio->initialReadTimestamp(); + writeTimestamp = mDevice->initialWriteTimestamp(); + readTimestamp = mDevice->initialReadTimestamp(); - mRadio->updateAlignment(writeTimestamp-10000); - mRadio->updateAlignment(writeTimestamp-10000); + mDevice->updateAlignment(writeTimestamp-10000); + mDevice->updateAlignment(writeTimestamp-10000); mOn = true; LOG(INFO) << "Radio started"; @@ -211,7 +218,7 @@ bool RadioInterface::start() */ bool RadioInterface::stop() { - if (!mOn || !mRadio->stop()) + if (!mOn || !mDevice->stop()) return false; mOn = false; @@ -288,9 +295,9 @@ int RadioInterface::driveReceiveRadio() bool RadioInterface::isUnderrun() { - bool retVal = underrun; - underrun = false; - + bool retVal; + /* atomically get previous value of "underrun" and set the var to false */ + retVal = osmo_trx_sync_fetch_and_and(&underrun, false); return retVal; } @@ -304,12 +311,12 @@ VectorFIFO* RadioInterface::receiveFIFO(size_t chan) double RadioInterface::setRxGain(double dB, size_t chan) { - return mRadio->setRxGain(dB, chan); + return mDevice->setRxGain(dB, chan); } -double RadioInterface::getRxGain(size_t chan) +double RadioInterface::rssiOffset(size_t chan) { - return mRadio->getRxGain(chan); + return mDevice->rssiOffset(chan); } /* Receive a timestamped chunk from the device */ @@ -323,7 +330,7 @@ int RadioInterface::pullBuffer() return -1; /* Outer buffer access size is fixed */ - numRecv = mRadio->readSamples(convertRecvBuffer, + numRecv = mDevice->readSamples(convertRecvBuffer, segmentLen, &overrun, readTimestamp, @@ -340,7 +347,7 @@ int RadioInterface::pullBuffer() segmentLen * 2); } - underrun |= local_underrun; + osmo_trx_sync_or_and_fetch(&underrun, local_underrun); readTimestamp += numRecv; return 0; } @@ -362,11 +369,11 @@ bool RadioInterface::pushBuffer() } /* Send the all samples in the send buffer */ - numSent = mRadio->writeSamples(convertSendBuffer, + numSent = mDevice->writeSamples(convertSendBuffer, segmentLen, &local_underrun, writeTimestamp); - underrun |= local_underrun; + osmo_trx_sync_or_and_fetch(&underrun, local_underrun); writeTimestamp += numSent; return true; diff --git a/Transceiver52M/radioInterface.h b/Transceiver52M/radioInterface.h index dcfb67f..b05af78 100644 --- a/Transceiver52M/radioInterface.h +++ b/Transceiver52M/radioInterface.h @@ -1,7 +1,7 @@ /* * Copyright 2008 Free Software Foundation, Inc. * -* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -31,16 +31,15 @@ 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 std::vector<VectorFIFO> mReceiveFIFO; ///< FIFO that holds receive bursts - RadioDevice *mRadio; ///< the USRP object - - size_t mSPSTx; - size_t mSPSRx; - size_t mChans; + RadioDevice *mDevice; ///< the USRP object std::vector<RadioBuffer *> sendBuffer; std::vector<RadioBuffer *> recvBuffer; @@ -48,7 +47,7 @@ protected: std::vector<short *> convertRecvBuffer; std::vector<short *> convertSendBuffer; std::vector<float> powerScaling; - bool underrun; ///< indicates writes to USRP are too slow + int underrun; ///< indicates writes to USRP are too slow bool overrun; ///< indicates reads from USRP are too slow TIMESTAMP writeTimestamp; ///< sample timestamp of next packet written to USRP TIMESTAMP readTimestamp; ///< sample timestamp of next packet read from USRP @@ -76,15 +75,15 @@ private: public: /** start the interface */ - bool start(); + virtual bool start(); bool stop(); - /** intialization */ + /** initialization */ virtual bool init(int type); virtual void close(); /** constructor */ - RadioInterface(RadioDevice* wRadio, size_t tx_sps, size_t rx_sps, + RadioInterface(RadioDevice* wDevice, size_t tx_sps, size_t rx_sps, size_t chans = 1, int receiveOffset = 3, GSM::Time wStartTime = GSM::Time(0)); @@ -107,10 +106,10 @@ public: virtual bool tuneRx(double freq, size_t chan = 0); /** set receive gain */ - double setRxGain(double dB, size_t chan = 0); + virtual double setRxGain(double dB, size_t chan = 0); - /** get receive gain */ - double getRxGain(size_t chan = 0); + /** return base RSSI offset to apply for received samples **/ + virtual double rssiOffset(size_t chan = 0); /** drive transmission of GSM bursts */ void driveTransmitRadio(std::vector<signalVector *> &bursts, @@ -119,7 +118,9 @@ public: /** drive reception of GSM bursts. -1: Error. 0: Radio off. 1: Received something. */ int driveReceiveRadio(); - int setPowerAttenuation(int atten, size_t chan = 0); + /** set transmit power attenuation */ + virtual int setPowerAttenuation(int atten, size_t chan = 0); + int getNominalTxPower(size_t chan = 0); /** returns the full-scale transmit amplitude **/ double fullScaleInputValue(); @@ -128,10 +129,10 @@ public: double fullScaleOutputValue(); /** get transport window type of attached device */ - enum RadioDevice::TxWindowType getWindowType() { return mRadio->getWindowType(); } + enum RadioDevice::TxWindowType getWindowType() { return mDevice->getWindowType(); } /** Minimum latency that the device can achieve */ - GSM::Time minLatency() { return mRadio->minLatency(); } + GSM::Time minLatency() { return mDevice->minLatency(); } protected: /** drive synchronization of Tx/Rx of USRP */ @@ -149,22 +150,31 @@ private: int pullBuffer(); public: - RadioInterfaceResamp(RadioDevice* wRadio, size_t tx_sps, size_t rx_sps); - ~RadioInterfaceResamp(); + RadioInterfaceResamp(RadioDevice* wDevice, size_t tx_sps, size_t rx_sps); + virtual ~RadioInterfaceResamp(); bool init(int type); void close(); }; +struct freq_cfg_state { + bool set; + double freq_hz; +}; + class RadioInterfaceMulti : public RadioInterface { private: bool pushBuffer(); int pullBuffer(); + bool verify_arfcn_consistency(double freq, size_t chan, bool tx); + virtual int setPowerAttenuation(int atten, size_t chan = 0); signalVector *outerSendBuffer; signalVector *outerRecvBuffer; std::vector<signalVector *> history; std::vector<bool> active; + std::vector<struct freq_cfg_state> rx_freq_state; + std::vector<struct freq_cfg_state> tx_freq_state; Resampler *dnsampler; Resampler *upsampler; @@ -174,12 +184,13 @@ private: public: RadioInterfaceMulti(RadioDevice* radio, size_t tx_sps, size_t rx_sps, size_t chans = 1); - ~RadioInterfaceMulti(); + virtual ~RadioInterfaceMulti(); bool init(int type); void close(); bool tuneTx(double freq, size_t chan); bool tuneRx(double freq, size_t chan); - double setRxGain(double dB, size_t chan); + virtual double setRxGain(double dB, size_t chan); + virtual double rssiOffset(size_t chan = 0); }; diff --git a/Transceiver52M/radioInterfaceMulti.cpp b/Transceiver52M/radioInterfaceMulti.cpp index 0208e82..a7195b4 100644 --- a/Transceiver52M/radioInterfaceMulti.cpp +++ b/Transceiver52M/radioInterfaceMulti.cpp @@ -38,13 +38,15 @@ extern "C" { /* Universal resampling parameters */ #define NUMCHUNKS 24 +/* number of narrow-band virtual ARFCNs in this wide-band multi-ARFCN device */ #define MCHANS 4 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) { } @@ -69,14 +71,24 @@ void RadioInterfaceMulti::close() channelizer = NULL; synthesis = NULL; - mReceiveFIFO.resize(0); - powerScaling.resize(0); - history.resize(0); - active.resize(0); + + for (std::vector<signalVector*>::iterator it = history.begin(); it != history.end(); ++it) + delete *it; + + mReceiveFIFO.clear(); + powerScaling.clear(); + history.clear(); + active.clear(); + rx_freq_state.clear(); + tx_freq_state.clear(); RadioInterface::close(); } +/*! we re-map the physical channels from the filter bank to logical per-TRX channels + * \param[in] pchan physical channel number within the channelizer + * \param[in] chans total number of narrow-band ARFCN channels + * \returns logical (TRX) channel number, or -1 in case there is none */ static int getLogicalChan(size_t pchan, size_t chans) { switch (chans) { @@ -111,6 +123,9 @@ static int getLogicalChan(size_t pchan, size_t chans) return -1; } +/*! do we need to frequency shift our spectrum or not? + * \param chans total number of channels + * \returns 1 if we need to shift; 0 if not; -1 on error */ static int getFreqShift(size_t chans) { switch (chans) { @@ -138,18 +153,10 @@ bool RadioInterfaceMulti::init(int type) return false; } - close(); - - sendBuffer.resize(mChans); - recvBuffer.resize(mChans); convertSendBuffer.resize(1); convertRecvBuffer.resize(1); - mReceiveFIFO.resize(mChans); - powerScaling.resize(mChans); - history.resize(mChans); - active.resize(MCHANS, false); - + /* 4 == sps */ inchunk = RESAMP_INRATE * 4; outchunk = RESAMP_OUTRATE * 4; @@ -238,7 +245,7 @@ int RadioInterfaceMulti::pullBuffer() return -1; /* Outer buffer access size is fixed */ - num = mRadio->readSamples(convertRecvBuffer, + num = mDevice->readSamples(convertRecvBuffer, outerRecvBuffer->size(), &overrun, readTimestamp, @@ -251,7 +258,7 @@ int RadioInterfaceMulti::pullBuffer() convert_short_float((float *) outerRecvBuffer->begin(), convertRecvBuffer[0], 2 * outerRecvBuffer->size()); - underrun |= local_underrun; + osmo_trx_sync_or_and_fetch(&underrun, local_underrun); readTimestamp += num; channelizer->rotate((float *) outerRecvBuffer->begin(), @@ -288,7 +295,7 @@ int RadioInterfaceMulti::pullBuffer() complex *dst = history[lchan]->begin(); float *fsrc = &buf[2 * (cLen - hLen)]; for (i = 0; i < hLen; i++) { - *dst = complex(fdst[0], fdst[1]); + *dst = complex(fsrc[0], fsrc[1]); fsrc += 2; dst++; } @@ -309,6 +316,7 @@ int RadioInterfaceMulti::pullBuffer() /* Send a timestamped chunk to the device */ bool RadioInterfaceMulti::pushBuffer() { + bool local_underrun; if (sendBuffer[0]->getAvailSegments() <= 0) return false; @@ -339,14 +347,15 @@ bool RadioInterfaceMulti::pushBuffer() (float *) outerSendBuffer->begin(), 1.0 / (float) mChans, 2 * outerSendBuffer->size()); - size_t num = mRadio->writeSamples(convertSendBuffer, + size_t num = mDevice->writeSamples(convertSendBuffer, outerSendBuffer->size(), - &underrun, + &local_underrun, writeTimestamp); if (num != outerSendBuffer->size()) { LOG(ALERT) << "Transmit error " << num; } + osmo_trx_sync_or_and_fetch(&underrun, local_underrun); writeTimestamp += num; return true; @@ -360,48 +369,83 @@ static bool fltcmp(double a, double b) return fabs(a - b) < FREQ_DELTA_LIMIT ? true : false; } +bool RadioInterfaceMulti::verify_arfcn_consistency(double freq, size_t chan, bool tx) +{ + double freq_i; + std::string str_dir = tx ? "Tx" : "Rx"; + std::vector<struct freq_cfg_state> &v = tx ? tx_freq_state : rx_freq_state; + + for (size_t i = 0; i < mChans; i++) { + if (i == chan) + continue; + if (!v[i].set) + continue; + + freq_i = v[i].freq_hz + (double) ((int)chan - (int)i) * MCBTS_SPACING; + if (!fltcmp(freq, freq_i)) { + LOGCHAN(chan, DMAIN, ERROR) + << "Setting " << str_dir << " frequency " << freq + << " is incompatible: already configured channel " + << i << " uses frequency " << v[i].freq_hz + << " (expected " << freq_i << ")"; + return false; + } + } + v[chan].set = true; + v[chan].freq_hz = freq; + return true; +} + bool RadioInterfaceMulti::tuneTx(double freq, size_t chan) { - if (chan >= mChans) - return false; + double shift; - double shift = (double) getFreqShift(mChans); + if (chan >= mChans) + return false; - if (!chan) - return mRadio->setTxFreq(freq + shift * MCBTS_SPACING); + if (!verify_arfcn_consistency(freq, chan, true)) + return false; - double center = mRadio->getTxFreq(); - if (!fltcmp(freq, center + (double) (chan - shift) * MCBTS_SPACING)) { - LOG(NOTICE) << "Channel " << chan << " RF frequency offset is " - << freq / 1e6 << " MHz"; - } + if (chan == 0) { + shift = (double) getFreqShift(mChans); + return mDevice->setTxFreq(freq + shift * MCBTS_SPACING); + } - return true; + return true; } bool RadioInterfaceMulti::tuneRx(double freq, size_t chan) { - if (chan >= mChans) - return false; + double shift; - double shift = (double) getFreqShift(mChans); + if (chan >= mChans) + return false; - if (!chan) - return mRadio->setRxFreq(freq + shift * MCBTS_SPACING); + if (!verify_arfcn_consistency(freq, chan, false)) + return false; - double center = mRadio->getRxFreq(); - if (!fltcmp(freq, center + (double) (chan - shift) * MCBTS_SPACING)) { - LOG(NOTICE) << "Channel " << chan << " RF frequency offset is " - << freq / 1e6 << " MHz"; - } + if (chan == 0) { + shift = (double) getFreqShift(mChans); + return mDevice->setRxFreq(freq + shift * MCBTS_SPACING); + } - return true; + return true; } double RadioInterfaceMulti::setRxGain(double db, size_t chan) { - if (!chan) - return mRadio->setRxGain(db); - else - return mRadio->getRxGain(); + if (chan == 0) + return mDevice->setRxGain(db); + else + return mDevice->getRxGain(); +} + +double RadioInterfaceMulti::rssiOffset(size_t chan) +{ + return mDevice->rssiOffset(0); +} + +int RadioInterfaceMulti::setPowerAttenuation(int atten, size_t chan) +{ + return RadioInterface::setPowerAttenuation(atten, 0); } diff --git a/Transceiver52M/radioInterfaceResamp.cpp b/Transceiver52M/radioInterfaceResamp.cpp index e2f69f2..869acd0 100644 --- a/Transceiver52M/radioInterfaceResamp.cpp +++ b/Transceiver52M/radioInterfaceResamp.cpp @@ -59,9 +59,9 @@ static size_t resamp_inchunk = 0; static size_t resamp_outrate = 0; static size_t resamp_outchunk = 0; -RadioInterfaceResamp::RadioInterfaceResamp(RadioDevice *wRadio, +RadioInterfaceResamp::RadioInterfaceResamp(RadioDevice *wDevice, size_t tx_sps, size_t rx_sps) - : RadioInterface(wRadio, tx_sps, rx_sps, 1), + : RadioInterface(wDevice, tx_sps, rx_sps, 1), outerSendBuffer(NULL), outerRecvBuffer(NULL) { } @@ -98,15 +98,6 @@ bool RadioInterfaceResamp::init(int type) { float cutoff = 1.0f; - close(); - - sendBuffer.resize(1); - recvBuffer.resize(1); - convertSendBuffer.resize(1); - convertRecvBuffer.resize(1); - mReceiveFIFO.resize(1); - powerScaling.resize(1); - switch (type) { case RadioDevice::RESAMP_64M: resamp_inrate = RESAMP_64M_INRATE; @@ -171,7 +162,7 @@ int RadioInterfaceResamp::pullBuffer() return -1; /* Outer buffer access size is fixed */ - num_recv = mRadio->readSamples(convertRecvBuffer, + num_recv = mDevice->readSamples(convertRecvBuffer, resamp_outchunk, &overrun, readTimestamp, @@ -184,7 +175,7 @@ int RadioInterfaceResamp::pullBuffer() convert_short_float((float *) outerRecvBuffer->begin(), convertRecvBuffer[0], 2 * resamp_outchunk); - underrun |= local_underrun; + osmo_trx_sync_or_and_fetch(&underrun, local_underrun); readTimestamp += (TIMESTAMP) resamp_outchunk; /* Write to the end of the inner receive buffer */ @@ -204,6 +195,7 @@ int RadioInterfaceResamp::pullBuffer() /* Send a timestamped chunk to the device */ bool RadioInterfaceResamp::pushBuffer() { + bool local_underrun; int rc; size_t numSent; @@ -223,14 +215,15 @@ bool RadioInterfaceResamp::pushBuffer() (float *) outerSendBuffer->begin(), powerScaling[0], 2 * resamp_outchunk); - numSent = mRadio->writeSamples(convertSendBuffer, + numSent = mDevice->writeSamples(convertSendBuffer, resamp_outchunk, - &underrun, + &local_underrun, writeTimestamp); if (numSent != resamp_outchunk) { LOG(ALERT) << "Transmit error " << numSent; } + osmo_trx_sync_or_and_fetch(&underrun, local_underrun); writeTimestamp += resamp_outchunk; return true; diff --git a/Transceiver52M/radioVector.cpp b/Transceiver52M/radioVector.cpp index ad40a11..acefc97 100644 --- a/Transceiver52M/radioVector.cpp +++ b/Transceiver52M/radioVector.cpp @@ -76,22 +76,25 @@ bool radioVector::setVector(signalVector *vector, size_t chan) return true; } -noiseVector::noiseVector(size_t size) +avgVector::avgVector(size_t size) : std::vector<float>(size), itr(0) { } -float noiseVector::avg() const +float avgVector::avg() const { float val = 0.0; + if (!size()) + return 0.0f; + for (size_t i = 0; i < size(); i++) val += (*this)[i]; return val / (float) size(); } -bool noiseVector::insert(float val) +bool avgVector::insert(float val) { if (!size()) return false; @@ -120,38 +123,26 @@ GSM::Time VectorQueue::nextTime() const radioVector* VectorQueue::getStaleBurst(const GSM::Time& targTime) { - mLock.lock(); - if ((mQ.size()==0)) { - mLock.unlock(); + if ((mQ.size()==0)) return NULL; - } if (mQ.top()->getTime() < targTime) { radioVector* retVal = mQ.top(); mQ.pop(); - mLock.unlock(); return retVal; } - mLock.unlock(); - return NULL; } radioVector* VectorQueue::getCurrentBurst(const GSM::Time& targTime) { - mLock.lock(); - if ((mQ.size()==0)) { - mLock.unlock(); + if ((mQ.size()==0)) return NULL; - } if (mQ.top()->getTime() == targTime) { radioVector* retVal = mQ.top(); mQ.pop(); - mLock.unlock(); return retVal; } - mLock.unlock(); - return NULL; } diff --git a/Transceiver52M/radioVector.h b/Transceiver52M/radioVector.h index 0a14a4d..90db626 100644 --- a/Transceiver52M/radioVector.h +++ b/Transceiver52M/radioVector.h @@ -48,9 +48,9 @@ private: GSM::Time mTime; }; -class noiseVector : std::vector<float> { +class avgVector : std::vector<float> { public: - noiseVector(size_t size = 0); + avgVector(size_t size = 0); bool insert(float val); float avg() const; @@ -65,6 +65,7 @@ public: GSM::Time nextTime() const; radioVector* getStaleBurst(const GSM::Time& targTime); radioVector* getCurrentBurst(const GSM::Time& targTime); + Mutex *getMutex() const { return &mLock; }; }; #endif /* RADIOVECTOR_H */ diff --git a/Transceiver52M/sigProcLib.cpp b/Transceiver52M/sigProcLib.cpp index 011ddff..5fac365 100644 --- a/Transceiver52M/sigProcLib.cpp +++ b/Transceiver52M/sigProcLib.cpp @@ -34,6 +34,7 @@ #include "Resampler.h" extern "C" { +#include <osmocom/core/panic.h> #include "convolve.h" #include "scale.h" #include "mult.h" @@ -128,6 +129,8 @@ struct PulseSequence { static CorrelationSequence *gMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}; static CorrelationSequence *gEdgeMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}; static CorrelationSequence *gRACHSequences[] = {NULL,NULL,NULL}; +static CorrelationSequence *gSCHSequence = NULL; +static CorrelationSequence *gDummySequence = NULL; static PulseSequence *GSMPulse1 = NULL; static PulseSequence *GSMPulse4 = NULL; @@ -150,6 +153,12 @@ void sigProcLibDestroy() gRACHSequences[i] = NULL; } + delete gSCHSequence; + gSCHSequence = NULL; + + delete gDummySequence; + gDummySequence = NULL; + delete GMSKRotation1; delete GMSKReverseRotation1; delete GMSKRotation4; @@ -314,6 +323,7 @@ static signalVector *convolve(const signalVector *x, const signalVector *h, append = true; break; case CUSTOM: + // FIXME: x->getstart? if (start < h->size() - 1) { head = h->size() - start; append = true; @@ -345,7 +355,7 @@ static signalVector *convolve(const signalVector *x, const signalVector *h, _x = x; /* - * Four convovle types: + * Four convolve types: * 1. Complex-Real (aligned) * 2. Complex-Complex (aligned) * 3. Complex-Real (!aligned) @@ -723,7 +733,7 @@ static signalVector *mapEdgeSymbols(const BitVector &bits) * * Delay the EDGE downlink bursts by one symbol in order to match GMSK pulse * shaping group delay. The difference in group delay arises from the dual - * pulse filter combination of the GMSK Laurent represenation whereas 8-PSK + * pulse filter combination of the GMSK Laurent representation whereas 8-PSK * uses a single pulse linear filter. */ static signalVector *shapeEdgeBurst(const signalVector &symbols) @@ -1288,6 +1298,77 @@ release: return status; } +static bool generateDummyMidamble(int sps) +{ + bool status = true; + float toa; + complex *data = NULL; + signalVector *autocorr = NULL, *midamble = NULL; + signalVector *midMidamble = NULL, *_midMidamble = NULL; + + delete gDummySequence; + + /* Use middle 16 bits of each TSC. Correlation sequence is not pulse shaped */ + midMidamble = modulateBurst(gDummyBurstTSC.segment(5,16), 0, sps, true); + if (!midMidamble) + return false; + + /* Simulated receive sequence is pulse shaped */ + midamble = modulateBurst(gDummyBurstTSC, 0, sps, false); + if (!midamble) { + status = false; + goto release; + } + + // NOTE: Because ideal TSC 16-bit midamble is 66 symbols into burst, + // the ideal TSC has an + 180 degree phase shift, + // due to the pi/2 frequency shift, that + // needs to be accounted for. + // 26-midamble is 61 symbols into burst, has +90 degree phase shift. + scaleVector(*midMidamble, complex(-1.0, 0.0)); + scaleVector(*midamble, complex(0.0, 1.0)); + + conjugateVector(*midMidamble); + + /* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */ + data = (complex *) convolve_h_alloc(midMidamble->size()); + _midMidamble = new signalVector(data, 0, midMidamble->size(), convolve_h_alloc, free); + _midMidamble->setAligned(true); + midMidamble->copyTo(*_midMidamble); + + autocorr = convolve(midamble, _midMidamble, NULL, NO_DELAY); + if (!autocorr) { + status = false; + goto release; + } + + gDummySequence = new CorrelationSequence; + gDummySequence->sequence = _midMidamble; + gDummySequence->gain = peakDetect(*autocorr, &toa, NULL); + + /* For 1 sps only + * (Half of correlation length - 1) + midpoint of pulse shape + remainder + * 13.5 = (16 / 2 - 1) + 1.5 + (26 - 10) / 2 + */ + if (sps == 1) + gDummySequence->toa = toa - 13.5; + else + gDummySequence->toa = 0; + +release: + delete autocorr; + delete midamble; + delete midMidamble; + + if (!status) { + delete _midMidamble; + free(data); + gDummySequence = NULL; + } + + return status; +} + static CorrelationSequence *generateEdgeMidamble(int tsc) { complex *data = NULL; @@ -1383,6 +1464,69 @@ release: return status; } +bool generateSCHSequence(int sps) +{ + bool status = true; + float toa; + complex *data = NULL; + signalVector *autocorr = NULL; + signalVector *seq0 = NULL, *seq1 = NULL, *_seq1 = NULL; + + delete gSCHSequence; + + seq0 = modulateBurst(gSCHSynchSequence, 0, sps, false); + if (!seq0) + return false; + + seq1 = modulateBurst(gSCHSynchSequence, 0, sps, true); + if (!seq1) { + status = false; + goto release; + } + + conjugateVector(*seq1); + + /* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */ + data = (complex *) convolve_h_alloc(seq1->size()); + _seq1 = new signalVector(data, 0, seq1->size(), convolve_h_alloc, free); + _seq1->setAligned(true); + seq1->copyTo(*_seq1); + + autocorr = convolve(seq0, _seq1, autocorr, NO_DELAY); + if (!autocorr) { + status = false; + goto release; + } + + gSCHSequence = new CorrelationSequence; + gSCHSequence->sequence = _seq1; + gSCHSequence->buffer = data; + gSCHSequence->gain = peakDetect(*autocorr, &toa, NULL); + + /* For 1 sps only + * (Half of correlation length - 1) + midpoint of pulse shaping filer + * 20.5 = (64 / 2 - 1) + 1.5 + */ + if (sps == 1) + gSCHSequence->toa = toa - 32.5; + else + gSCHSequence->toa = 0.0; + +release: + delete autocorr; + delete seq0; + delete seq1; + + if (!status) { + delete _seq1; + free(data); + gSCHSequence = NULL; + } + + return status; +} + + /* * Peak-to-average computation +/- range from peak in symbols */ @@ -1440,14 +1584,15 @@ float energyDetect(const signalVector &rxBurst, unsigned windowLength) return energy/windowLength; } -static signalVector *downsampleBurst(const signalVector &burst) +static signalVector *downsampleBurst(const signalVector &burst, int in_len = DOWNSAMPLE_IN_LEN, + int out_len = DOWNSAMPLE_OUT_LEN) { - signalVector in(DOWNSAMPLE_IN_LEN, dnsampler->len()); - signalVector *out = new signalVector(DOWNSAMPLE_OUT_LEN); - burst.copyToSegment(in, 0, DOWNSAMPLE_IN_LEN); + signalVector in(in_len, dnsampler->len()); + // gSCHSequence->sequence->size(), ensure next conv has no realloc + signalVector *out = new signalVector(out_len, 64); + burst.copyToSegment(in, 0, in_len); - if (dnsampler->rotate((float *) in.begin(), DOWNSAMPLE_IN_LEN, - (float *) out->begin(), DOWNSAMPLE_OUT_LEN) < 0) { + if (dnsampler->rotate((float *)in.begin(), in_len, (float *)out->begin(), out_len) < 0) { delete out; out = NULL; } @@ -1460,25 +1605,36 @@ static signalVector *downsampleBurst(const signalVector &burst) * It is computed from the training sequence of each received burst, * by comparing the "ideal" training sequence with the actual one. */ -static float computeCI(const signalVector *burst, CorrelationSequence *sync, - float toa, int start, complex xcorr) +static float computeCI(const signalVector *burst, const CorrelationSequence *sync, + float toa, int start, const complex &xcorr) { + const int N = sync->sequence->size(); float S, C; - int ps; - /* Integer position where the sequence starts */ - ps = start + 1 - sync->sequence->size() + (int)roundf(toa); + const int ps = start + 1 - N + (int)roundf(toa); + + if(ps < 0) // might be -22 for toa 40 with N=64, if off by a lot during sch ms sync + return 0; + + if (ps + N > (int)burst->size()) + return 0; /* Estimate Signal power */ S = 0.0f; - for (int i=0, j=ps; i<(int)sync->sequence->size(); i++,j++) + for (int i=0, j=ps; i<(int)N; i++,j++) S += (*burst)[j].norm2(); - S /= sync->sequence->size(); + S /= N; /* Esimate Carrier power */ - C = xcorr.norm2() / ((sync->sequence->size() - 1) * sync->gain.abs()); - - /* Interference = Signal - Carrier, so C/I = C / (S - C) */ + C = xcorr.norm2() / ((N - 1) * sync->gain.abs()); + + /* Interference = Signal - Carrier, so C/I = C / (S - C). + * Calculated in dB: + * C/I_dB = 10 * log10(C/I) + * C/I_dB = 10 * (1/log2(10)) * log2(C/I) + * C/I_dB = 10 * 0.30103 * log2(C/I) + * C/I_dB = 3.0103 * log2(C/I) + */ return 3.0103f * log2f(C / (S - C)); } @@ -1491,7 +1647,7 @@ static float computeCI(const signalVector *burst, CorrelationSequence *sync, * and we run full interpolating peak detection. */ static int detectBurst(const signalVector &burst, - signalVector &corr, CorrelationSequence *sync, + signalVector &corr, const CorrelationSequence *sync, float thresh, int sps, int start, int len, struct estim_burst_params *ebp) { @@ -1500,12 +1656,18 @@ static int detectBurst(const signalVector &burst, complex xcorr; int rc = 1; - if (sps == 4) { - dec = downsampleBurst(burst); - corr_in = dec; - sps = 1; - } else { + switch (sps) { + case 1: corr_in = &burst; + break; + case 4: + dec = downsampleBurst(burst); + /* Running at the downsampled rate at this point: */ + corr_in = dec; + sps = 1; + break; + default: + osmo_panic("%s:%d SPS %d not supported! Only 1 or 4 supported", __FILE__, __LINE__, sps); } /* Correlate */ @@ -1515,9 +1677,6 @@ static int detectBurst(const signalVector &burst, goto del_ret; } - /* Running at the downsampled rate at this point */ - sps = 1; - /* Peak detection - place restrictions at correlation edges */ ebp->amp = fastPeakDetect(corr, &ebp->toa); @@ -1586,7 +1745,7 @@ static int detectGeneralBurst(const signalVector &rxBurst, float thresh, int sps // and only report clipping if we can't demod. float maxAmpl = maxAmplitude(rxBurst); if (maxAmpl > CLIP_THRESH) { - LOG(DEBUG) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl; + LOG(INFO) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl; clipping = true; } @@ -1643,6 +1802,80 @@ static int detectRACHBurst(const signalVector &burst, float threshold, int sps, return rc; } +int detectSCHBurst(signalVector &burst, + float thresh, + int sps, + sch_detect_type state, struct estim_burst_params *ebp) +{ + int rc, start, target, head, tail, len; + complex _amp; + CorrelationSequence *sync; + + if ((sps != 1) && (sps != 4)) + return -1; + + target = 3 + 39 + 64; + + switch (state) { + case sch_detect_type::SCH_DETECT_NARROW: + head = 4; + tail = 4; + break; + case sch_detect_type::SCH_DETECT_BUFFER: + target = 1; + head = 0; + tail = (12 * 8 * 625) / 4; // 12 frames, downsampled /4 to 1 sps + break; + case sch_detect_type::SCH_DETECT_FULL: + default: + head = target - 1; + tail = 39 + 3 + 9; + break; + } + + start = (target - head) * 1 - 1; + len = (head + tail) * 1; + sync = gSCHSequence; + signalVector corr(len); + + signalVector *dec = downsampleBurst(burst, len * 4, len); + rc = detectBurst(*dec, corr, sync, thresh, 1, start, len, ebp); + delete dec; + + if (rc < 0) { + return -1; + } else if (!rc) { + ebp->amp = 0.0f; + ebp->toa = 0.0f; + return 0; + } + + if (state == sch_detect_type::SCH_DETECT_BUFFER) + ebp->toa = ebp->toa - (3 + 39 + 64); + else { + /* Subtract forward search bits from delay */ + ebp->toa = ebp->toa - head; + } + + return rc; +} + +static int detectDummyBurst(const signalVector &burst, float threshold, + int sps, unsigned max_toa, struct estim_burst_params *ebp) +{ + int rc, target, head, tail; + CorrelationSequence *sync; + + target = 3 + 58 + 16 + 5; + head = 10; + tail = 6 + max_toa; + sync = gDummySequence; + + ebp->tsc = 0; + rc = detectGeneralBurst(burst, threshold, sps, target, head, tail, sync, ebp); + return rc; +} + /* * Normal burst detection * @@ -1661,7 +1894,7 @@ static int analyzeTrafficBurst(const signalVector &burst, unsigned tsc, float th return -SIGERR_UNSUPPORTED; target = 3 + 58 + 16 + 5; - head = 6; + head = 10; tail = 6 + max_toa; sync = gMidambles[tsc]; @@ -1710,6 +1943,9 @@ int detectAnyBurst(const signalVector &burst, unsigned tsc, float threshold, case RACH: rc = detectRACHBurst(burst, threshold, sps, max_toa, type == EXT_RACH, ebp); break; + case IDLE: + rc = detectDummyBurst(burst, threshold, sps, max_toa, ebp); + break; default: LOG(ERR) << "Invalid correlation type"; } @@ -1792,15 +2028,15 @@ static SoftVector *signalToSoftVector(signalVector *dec) * stages. */ static signalVector *demodCommon(const signalVector &burst, int sps, - complex chan, float toa) + const struct estim_burst_params *ebp) { signalVector *delay, *dec; if ((sps != 1) && (sps != 4)) return NULL; - delay = delayVector(&burst, NULL, -toa * (float) sps); - scaleVector(*delay, (complex) 1.0 / chan); + delay = delayVector(&burst, NULL, -ebp->toa * (float) sps); + scaleVector(*delay, (complex) 1.0 / ebp->amp); if (sps == 1) return delay; @@ -1816,13 +2052,13 @@ static signalVector *demodCommon(const signalVector &burst, int sps, * 4 SPS (if activated) to minimize distortion through the fractional * delay filters. Symbol rotation and after always operates at 1 SPS. */ -static SoftVector *demodGmskBurst(const signalVector &rxBurst, - int sps, complex channel, float TOA) +static SoftVector *demodGmskBurst(const signalVector &rxBurst, int sps, + const struct estim_burst_params *ebp) { SoftVector *bits; signalVector *dec; - dec = demodCommon(rxBurst, sps, channel, TOA); + dec = demodCommon(rxBurst, sps, ebp); if (!dec) return NULL; @@ -1835,29 +2071,51 @@ static SoftVector *demodGmskBurst(const signalVector &rxBurst, return bits; } +static float computeEdgeCI(const signalVector *rot) +{ + float err_pwr = 0.0f; + float step = 2.0f * M_PI_F / 8.0f; + + for (size_t i = 8; i < rot->size() - 8; i++) { + /* Compute the ideal symbol */ + complex sym = (*rot)[i]; + float phase = step * roundf(sym.arg() / step); + complex ideal = complex(cos(phase), sin(phase)); + + /* Compute the error vector */ + complex err = ideal - sym; + + /* Accumulate power */ + err_pwr += err.norm2(); + } + + return 3.0103f * log2f(1.0f * (rot->size() - 16) / err_pwr); +} + /* * Demodulate an 8-PSK burst. Prior to symbol rotation, operate at * 4 SPS (if activated) to minimize distortion through the fractional * delay filters. Symbol rotation and after always operates at 1 SPS. * * Allow 1 SPS demodulation here, but note that other parts of the - * transceiver restrict EDGE operatoin to 4 SPS - 8-PSK distortion + * transceiver restrict EDGE operation to 4 SPS - 8-PSK distortion * through the fractional delay filters at 1 SPS renders signal * nearly unrecoverable. */ -static SoftVector *demodEdgeBurst(const signalVector &burst, - int sps, complex chan, float toa) +static SoftVector *demodEdgeBurst(const signalVector &burst, int sps, + struct estim_burst_params *ebp) { SoftVector *bits; signalVector *dec, *rot, *eq; - dec = demodCommon(burst, sps, chan, toa); + dec = demodCommon(burst, sps, ebp); if (!dec) return NULL; /* Equalize and derotate */ eq = convolve(dec, GSMPulse4->c0_inv, NULL, NO_DELAY); rot = derotateEdgeBurst(*eq, 1); + ebp->ci = computeEdgeCI(rot); /* Soft slice and normalize */ bits = softSliceEdgeBurst(*rot); @@ -1869,13 +2127,13 @@ static SoftVector *demodEdgeBurst(const signalVector &burst, return bits; } -SoftVector *demodAnyBurst(const signalVector &burst, int sps, complex amp, - float toa, CorrType type) +SoftVector *demodAnyBurst(const signalVector &burst, CorrType type, + int sps, struct estim_burst_params *ebp) { if (type == EDGE) - return demodEdgeBurst(burst, sps, amp, toa); + return demodEdgeBurst(burst, sps, ebp); else - return demodGmskBurst(burst, sps, amp, toa); + return demodGmskBurst(burst, sps, ebp); } bool sigProcLibSetup() @@ -1890,6 +2148,9 @@ bool sigProcLibSetup() generateRACHSequence(&gRACHSequences[1], gRACHSynchSequenceTS1, 1); generateRACHSequence(&gRACHSequences[2], gRACHSynchSequenceTS2, 1); + generateSCHSequence(1); + generateDummyMidamble(1); + for (int tsc = 0; tsc < 8; tsc++) { generateMidamble(1, tsc); gEdgeMidambles[tsc] = generateEdgeMidamble(tsc); diff --git a/Transceiver52M/sigProcLib.h b/Transceiver52M/sigProcLib.h index fd9a5f0..39c8ddd 100644 --- a/Transceiver52M/sigProcLib.h +++ b/Transceiver52M/sigProcLib.h @@ -1,7 +1,7 @@ /* * Copyright 2008 Free Software Foundation, Inc. * -* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -31,6 +31,7 @@ enum CorrType{ TSC, ///< timeslot should contain a normal burst EXT_RACH, ///< timeslot should contain an extended access burst RACH, ///< timeslot should contain an access burst + SCH, EDGE, ///< timeslot should contain an EDGE burst IDLE ///< timeslot is an idle (or dummy) burst }; @@ -93,6 +94,8 @@ signalVector *generateDummyBurst(int sps, int tn); void scaleVector(signalVector &x, complex scale); +signalVector *delayVector(const signalVector *in, signalVector *out, float delay); + /** Rough energy estimator. @param rxBurst A GSM burst. @@ -133,8 +136,19 @@ int detectAnyBurst(const signalVector &burst, unsigned max_toa, struct estim_burst_params *ebp); +enum class sch_detect_type { + SCH_DETECT_FULL, + SCH_DETECT_NARROW, + SCH_DETECT_BUFFER, +}; + +int detectSCHBurst(signalVector &rxBurst, + float detectThreshold, + int sps, + sch_detect_type state, struct estim_burst_params *ebp); + /** Demodulate burst basde on type and output soft bits */ -SoftVector *demodAnyBurst(const signalVector &burst, int sps, - complex amp, float toa, CorrType type); +SoftVector *demodAnyBurst(const signalVector &burst, CorrType type, + int sps, struct estim_burst_params *ebp); #endif /* SIGPROCLIB_H */ diff --git a/Transceiver52M/signalVector.cpp b/Transceiver52M/signalVector.cpp index 710eda5..4410156 100644 --- a/Transceiver52M/signalVector.cpp +++ b/Transceiver52M/signalVector.cpp @@ -47,7 +47,7 @@ void signalVector::operator=(const signalVector& vector) complex *src = vector.mData; for (i = 0; i < size(); i++, src++, dst++) *dst = *src; - /* TODO: optimize for non non-trivially copyable types: */ + /* TODO: optimize for non non-trivially copiable types: */ /*memcpy(mData, vector.mData, bytes()); */ mStart = mData + vector.getStart(); } @@ -70,7 +70,7 @@ size_t signalVector::updateHistory() complex *src = mStart + this->size() - num; for (i = 0; i < num; i++, src++, dst++) *dst = *src; - /* TODO: optimize for non non-trivially copyable types: */ + /* TODO: optimize for non non-trivially copiable types: */ /*memmove(mData, mStart + this->size() - num, num * sizeof(complex)); */ return num; |