Compare commits

..

25 Commits

Author SHA1 Message Date
Harald Welte
35a067ea7c lms: Fix coding style
In Change-Id Ib2fca81b76d027b08e2891056fa076d071597783 we introduced
some coding style violations.  Let's make newly-added code follows
standard Osmocom coding style.

Change-Id: Ib7ddd275014f03a2eed3cddc02b1356e2b00c0bc
2018-06-13 23:32:42 +02:00
Harald Welte
9939ccd0de radioDevice: better encapsulation in base class
It's not good style to have the derived classes initialize members
inherited from the base class using "this->foo = bar".  Rather, let's
make the base class have a constructor, and call that constructor to
initialize the members of the base class.

While doing this
* rename 'offset' to 'lo_offset' to avoid confusion with timestamp offset
* move 'InterfaceType' into the base class
* move 'chans' into the base class
* move 'rx_sps' into the base class
* mark base class members as 'protected'

Change-Id: Ib885675a7612a392aa7f75fca81269ddcff2f6ab
2018-06-13 23:21:57 +02:00
Harald Welte
9eda0229a3 radioDevice: Move tx_sps from derived into base class
All three derived classes use a tx_sps member, let's move this into
the base class.

Change-Id: I73b4aa2705c5049561e2d7b21301a0d2b3c96ced
2018-06-13 22:47:48 +02:00
Harald Welte
a1031f1c9b lms: Call set_antennas() during open() method
Without this call, the antenna/path configuration is not applied.

Change-Id: I0bca58266b59f1315ec72b6407fe4f4495aff678
2018-06-13 21:56:24 +02:00
Harald Welte
66efb7c538 lms: Fix support for rx_paths / tx_paths
Before this patch, any configuration in osmo-trx.cfg regarding the rx
and tx "antenna" (path) would have been completely ignored, as the
radioDevice::make() function would simply drop those arguments to the
floor.

Change-Id: Ie50f854abbc9dcf351cddc052d10206382e1d5d3
2018-06-13 21:55:09 +02:00
Harald Welte
4f3aedbfee move set_antennas() from UHD to generic radioDevice base class
Change-Id: I806143e9db21f0be4dcc6a376b3a630be7aeb3ba
2018-06-13 19:48:43 +02:00
Zydrunas Tamosevicius
70621b7484 lms: Reduce Rx gain from 47 to 34 dB
Initially, Rx gain was hardcoded to be 47. This was too high for our
setup and we were constantly getting "clipping detected" messages.

Reducing Rx gain to 34 solved the issue. However, it looks like gains
should be controlled through configuration files.

Change-Id: I30580f18c4ad630c09f725b1d24c125fc3119809
2018-06-12 00:36:49 +02:00
Zydrunas Tamosevicius
1568f87014 lms: fix LMS_StartStream() handling for multiple channels
LMS_StartStream() (in LMSDevice::start()) was moved to separate loop. It
is because LMS_SetupStream() would fail for second channel if streaming
has already been started (LMS_StartStream()) for single channel
configuration.

Change-Id: I6704bb92864aa81417507c4ae24a22f41dc529c1
2018-06-12 00:36:49 +02:00
Zydrunas Tamosevicius
cace86ec0d lms: Reduce log level of "send buffer of len ..."
Log level of "send buffer of len ..." messages was changed as it was
causing problems on some machines.

Change-Id: I605d50e81966c7bd169b27788d62af6fb54c84e1
2018-06-12 00:36:49 +02:00
Zydrunas Tamosevicius
59437da099 lms: Use same timestamp offset like when using LimeSDR via UHD
The tx timestamp offset was not set. We set it to the same value as it
was in UHD interface for LimeSDR

Change-Id: I78bc40cd575097f71a5f82b63467fa81c3f8d837
2018-06-12 00:36:45 +02:00
Pau Espin Pedrol
c9ea6e3e8c lms: Check LPBFW to set is within supported range
As of LimeSuite 618fbb9c3188b36d75ad5785a97b8887dcc468f6, it seems 5e6
is within the returned range, but LMS_SetLPFBW fails anyway.

See for more information: https://github.com/myriadrf/LimeSuite/issues/184

Change-Id: I967e7da7c0e3e8138b76733ee4a0e6311d20b62e
2018-05-29 19:00:32 +02:00
Pau Espin Pedrol
4b00e736f0 lms: Makefile.am: Reorder params to fix link issue
It seems the order in which static code and -lfoo is passed to the
linker matters.

This commit is a lms specific follow-up of commit
2a8183bdf0.

Change-Id: I59c20d268ecac4c22689124165c47295bd9176d4
2018-05-25 17:14:43 +02:00
Pau Espin Pedrol
aeccb44c7a LMSDevice: Fix setup failure with LimeSuite > 18.04.1
Fixes: https://github.com/myriadrf/LimeSuite/issues/184

Change-Id: Ia9f37995cd10d19d6820e3e12b8ee8f3efbff5d4
2018-05-25 17:14:43 +02:00
Pau Espin Pedrol
09aa5a3e9f LMSDevice: Set correct values for Max{Tx,Rx}Gain
Change-Id: I3b3a7080a69e15d8d6770186810d922227439099
2018-05-25 17:14:43 +02:00
Harald Welte
1a090b698c LMSDevice: Reduce Rx logging verbosity: Only log unexpected timestamps
Change-Id: I06b35efb7368616b9f4d348da574cd524ffe3ea6
2018-05-25 17:14:43 +02:00
Harald Welte
c01ddf5ff3 LMS_Device: Set ts_offset to 0.
I'm not quite sure what the ts_offset is for, but by using "0"
we are now receiving exactly the timestamp that we're expecting:

LMSDevice.cpp:486 [tid=140576250332928] chan 0 recv buffer of len 2500 expect 305ed0 got 305ed0 (305ed0) diff=0

Change-Id: I270c94945b1af9662cfc468cfda1ae3af3ac0a27
2018-05-25 17:14:43 +02:00
Harald Welte
380067eeea LMSDevice: Fix initial timestamp offset of 2500
ts_initial must not point to the timestamp of the first sample
in the last "flush" sample buffer, but to the first timestamp we
expect in the next buffer.

Change-Id: I23af62870544d4c6cf5f6e2d6578936603bceb91
2018-05-25 17:14:43 +02:00
Harald Welte
6d000ba2f7 LMSDevice: Set low-pass filters to smallest possible option
Rx 1.4 MHz, Tx 5MHz.  Both massively too wide for GSM, but there's
no smaller band-width available.

Change-Id: I9723c9a2ea77f65bfa9d796d7c44adc2417e89cf
2018-05-25 17:14:43 +02:00
Harald Welte
2b764c33e5 LMSDevice: Typo fix: s/Internal/External
Change-Id: Icacfe6da90a89c7f00d62c580948fb913998eaa7
2018-05-25 17:14:43 +02:00
Harald Welte
e0d2f507ea LMSDevice: Print sample rate range + actual sample rate after setting it
Change-Id: I19c1a5b2d2431b8d39e277244e313f6e559e4d25
2018-05-25 17:14:43 +02:00
Harald Welte
7ca30375c9 LMSDevice: Call LMS_Init() before setting sample rate
LMS_Init() will override basically all device settings with their
default value, including the sample rate.  We hence have to make sure
to call it before any other API function that changes the device config
such as sample rate, frequency, filter bandwidth, ...

Change-Id: I4cdbae8406b5e1e93da491e90f8bad41d4be748b
2018-05-25 17:14:43 +02:00
Harald Welte
ce092147ac update .gitignore to include osmo-trx-lms
Change-Id: I52efd2f71eb61baa80427ab9f7b518f17d514792
2018-05-25 17:14:43 +02:00
Pau Espin Pedrol
6437192bf3 build: Add support for LimeSuite device backend
Change-Id: I239e1b37263a62b374d84974c9347e3654072e87
2018-05-25 17:14:43 +02:00
Pau Espin Pedrol
cdbe1e7ce2 lms: Several improvements and compilation/runtime fixes
Continuation of initial work done on LimeSuite support from Harald.

Change-Id: Ib2fca81b76d027b08e2891056fa076d071597783
2018-05-25 17:14:43 +02:00
Harald Welte
4a5484c770 Initial work towards direct LimeSuite support in OsmoTRX
This is work in progress towards a direct LimeSuite driver in OsmoTRX,
bypassing the currently rather complex stack of wrappers by going
through UHD, SoapyUHD, SoapySDR and LimeSuite.

Change-Id: Iaef29c4c2585ef8c2f94866c9591919f538c1a2d
2018-05-25 17:14:43 +02:00
153 changed files with 4406 additions and 7178 deletions

12
.gitignore vendored
View File

@@ -18,7 +18,6 @@ tests/CommonLibs/URLEncodeTest
tests/CommonLibs/VectorTest
tests/CommonLibs/PRBSTest
tests/Transceiver52M/convolve_test
tests/Transceiver52M/LMSDeviceTest
# automake/autoconf
*.in
@@ -52,14 +51,3 @@ tests/testsuite.log
# vim
*.sw?
# manuals
doc/manuals/*.html
doc/manuals/*.svg
doc/manuals/*.pdf
doc/manuals/*__*.png
doc/manuals/*.check
doc/manuals/generated/
doc/manuals/osmomsc-usermanual.xml
doc/manuals/common
doc/manuals/build

127
AUTHORS Normal file
View File

@@ -0,0 +1,127 @@
#
# Copyright 2008, 2009 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio 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.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
David A. Burgess, dburgess@kestrelsp.com:
CLI/CLI.cpp
CLI/CLI.h
CommonLibs/Assert.h
CommonLibs/BitVector.cpp
CommonLibs/Interthread.h
CommonLibs/LinkedLists.cpp
CommonLibs/LinkedLists.h
CommonLibs/Regexp.h
CommonLibs/Sockets.cpp
CommonLibs/Sockets.h
CommonLibs/Threads.cpp
CommonLibs/Threads.h
CommonLibs/Timeval.cpp
CommonLibs/Timeval.h
CommonLibs/Vector.h
GSM/GSM610Tables.cpp
GSM/GSM610Tables.h
GSM/GSMCommon.cpp
GSM/GSMCommon.h
GSM/GSMConfig.h
GSM/GSML1FEC.cpp
GSM/GSML1FEC.h
GSM/GSML2LAPDm.cpp
GSM/GSML2LAPDm.h
GSM/GSML3CCElements.cpp
GSM/GSML3CCElements.h
GSM/GSML3CCMessages.cpp
GSM/GSML3CCMessages.h
GSM/GSML3CommonElements.cpp
GSM/GSML3CommonElements.h
GSM/GSML3MMElements.cpp
GSM/GSML3MMElements.h
GSM/GSML3MMMessages.cpp
GSM/GSML3MMMessages.h
GSM/GSML3Message.cpp
GSM/GSML3Message.h
GSM/GSML3RRElements.cpp
GSM/GSML3RRElements.h
GSM/GSML3RRMessages.cpp
GSM/GSML3RRMessages.h
GSM/GSMLogicalChannel.h
GSM/GSMTDMA.cpp
GSM/GSMTDMA.h
GSM/GSMTransfer.cpp
GSM/GSMTransfer.h
LICENSEBLOCK
TRXManager/TRXManager.cpp
Transceiver/Complex.h
tests/CommonLibs/BitVectorTest.cpp
tests/CommonLibs/InterthreadTest.cpp
tests/CommonLibs/SocketsTest.cpp
tests/CommonLibs/TimevalTest.cpp
tests/CommonLibs/VectorTest.cpp
Harvind S. Samra, hssamra@kestrelsp.com:
GSM/GSMConfig.h
GSM/GSMTransfer.h
LICENSEBLOCK
Transceiver/ComplexTest.cpp
Transceiver/Transceiver.cpp
Transceiver/Transceiver.h
Transceiver/USRPDevice.cpp
Transceiver/USRPDevice.h
Transceiver/USRPping.cpp
Transceiver/radioInterface.cpp
Transceiver/radioInterface.h
Transceiver/rcvLPF_651.h
Transceiver/runTransceiver.cpp
Transceiver/sendLPF_961.h
Transceiver/sigProcLib.cpp
Transceiver/sigProcLib.h
Transceiver/sigProcLibTest.cpp
Transceiver/sweepGenerator.cpp
Transceiver/testRadio.cpp
Raffi Sevlian, raffisev@gmail.com:
GSM/GSMCommon.h
GSM/GSMConfig.h
GSM/GSML1FEC.h
GSM/GSML3CCElements.cpp
GSM/GSML3CCElements.h
GSM/GSML3CCMessages.cpp
GSM/GSML3CCMessages.h
GSM/GSML3CommonElements.cpp
GSM/GSML3CommonElements.h
GSM/GSML3MMElements.cpp
GSM/GSML3MMElements.h
GSM/GSML3MMMessages.cpp
GSM/GSML3MMMessages.h
GSM/GSML3Message.cpp
GSM/GSML3Message.h
GSM/GSML3RRElements.cpp
GSM/GSML3RRElements.h
GSM/GSML3RRMessages.cpp
GSM/GSML3RRMessages.h
GSM/GSMLogicalChannel.h
GSM/GSMSAPMux.cpp
GSM/GSMSAPMux.h
GSM/GSMTransfer.h
LICENSEBLOCK
TRXManager/TRXManager.h
Alon Levy, alonlevy1@gmail.com
RRLPMessages.cpp
RRLPMessages.h
RRLPTest.cpp

0
ChangeLog Normal file
View File

View File

@@ -1,7 +1,6 @@
/*
* Copyright 2008, 2009 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.

View File

@@ -1,8 +1,6 @@
/*
* Copyright 2008, 2009 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*

View File

@@ -1,8 +1,6 @@
/*
* Copyright 2008, 2011 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
@@ -55,7 +53,7 @@ template <class T, class Fifo=PointerFIFO> class InterthreadQueue {
protected:
Fifo mQ;
Fifo mQ;
mutable Mutex mLock;
mutable Signal mWriteSignal;
@@ -162,7 +160,7 @@ template <class T, class Fifo=PointerFIFO> class InterthreadQueue2 {
protected:
Fifo mQ;
Fifo mQ;
mutable Mutex mLock;
mutable Signal mWriteSignal;
@@ -258,7 +256,7 @@ template <class T, class Fifo=PointerFIFO> class InterthreadQueue2 {
// This recurs (and the InterthreadQueue fills up with data)
// until the read thread's accumulated temporary priority causes it to
// get a second pre-emptive activation over the writing thread,
// resulting in bursts of activity by the read thread.
// resulting in bursts of activity by the read thread.
{ ScopedLock lock(mLock);
mQ.put(val);
}
@@ -283,7 +281,7 @@ template <class T> class InterthreadQueueWithWait {
protected:
PointerFIFO mQ;
PointerFIFO mQ;
mutable Mutex mLock;
mutable Signal mWriteSignal;
mutable Signal mReadSignal;

View File

@@ -1,7 +1,6 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
@@ -30,25 +29,6 @@
#include "LinkedLists.h"
PointerFIFO::~PointerFIFO()
{
ListNode *node, *next;
node = mHead;
while (node != NULL) {
next = node->next();
delete node;
node = next;
}
node = mFreeList;
while (node != NULL) {
next = node->next();
delete node;
node = next;
}
}
void PointerFIFO::push_front(void* val) // by pat
{
// Pat added this routine for completeness, but never used or tested.

View File

@@ -1,10 +1,7 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
*
* 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.
* 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 the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
@@ -73,7 +70,6 @@ class PointerFIFO {
:mHead(NULL),mTail(NULL),mFreeList(NULL),
mSize(0)
{}
~PointerFIFO();
unsigned size() const { return mSize; }
unsigned totalSize() const { return 0; } // Not used in this version.

View File

@@ -1,7 +1,6 @@
/*
* Copyright (C) 2018 sysmocom - s.f.m.c. GmbH
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
@@ -36,6 +35,8 @@
using namespace std;
Mutex gLogToLock;
std::ostream& operator<<(std::ostream& os, std::ostringstream& ss)
{
return os << ss.str();
@@ -43,14 +44,13 @@ std::ostream& operator<<(std::ostream& os, std::ostringstream& ss)
Log::~Log()
{
int old_state;
int mlen = mStream.str().size();
int neednl = (mlen==0 || mStream.str()[mlen-1] != '\n');
const char *fmt = neednl ? "%s\n" : "%s";
log_mutex_lock_canceldisable(&old_state);
ScopedLock lock(gLogToLock);
// The COUT() macro prevents messages from stomping each other but adds uninteresting thread numbers,
// so just use std::cout.
LOGPSRC(mCategory, mPriority, filename, line, fmt, mStream.str().c_str());
log_mutex_unlock_canceldisable(old_state);
}
ostringstream& Log::get()

View File

@@ -2,8 +2,6 @@
* Copyright 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
@@ -58,9 +56,6 @@ extern "C" {
#define LOGLV(category, level) \
Log(category, level, __BASE_FILE__, __LINE__).get() << "[tid=" << pthread_self() << "] "
#define LOGCHAN(chan, category, level) \
Log(category, LOGL_##level, __BASE_FILE__, __LINE__).get() << "[tid=" << pthread_self() << "][chan=" << chan << "] "
/**
A C++ stream-based thread-safe logger.
This object is NOT the global logger;

View File

@@ -30,27 +30,23 @@ noinst_LTLIBRARIES = libcommon.la
libcommon_la_SOURCES = \
BitVector.cpp \
LinkedLists.cpp \
Sockets.cpp \
Threads.cpp \
Timeval.cpp \
Logger.cpp \
Utils.cpp \
trx_rate_ctr.cpp \
trx_vty.c \
debug.c
libcommon_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBOSMOVTY_LIBS)
noinst_HEADERS = \
BitVector.h \
PRBS.h \
Interthread.h \
LinkedLists.h \
Sockets.h \
Threads.h \
Timeval.h \
Vector.h \
Logger.h \
Utils.h \
trx_rate_ctr.h \
trx_vty.h \
debug.h \
osmo_signal.h \
config_defs.h

View File

@@ -1,8 +1,6 @@
/*
* Copyright (C) 2017 Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either

287
CommonLibs/Sockets.cpp Normal file
View File

@@ -0,0 +1,287 @@
/*
* Copyright 2008, 2010 Free Software Foundation, Inc.
*
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU 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 <config.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstdio>
#include <sys/select.h>
#include "Threads.h"
#include "Sockets.h"
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
bool resolveAddress(struct sockaddr_in *address, const char *hostAndPort)
{
assert(address);
assert(hostAndPort);
char *copy = strdup(hostAndPort);
char *colon = strchr(copy,':');
if (!colon) return false;
*colon = '\0';
char *host = copy;
unsigned port = strtol(colon+1,NULL,10);
bool retVal = resolveAddress(address,host,port);
free(copy);
return retVal;
}
bool resolveAddress(struct sockaddr_in *address, const char *host, unsigned short port)
{
assert(address);
assert(host);
// FIXME -- Need to ignore leading/trailing spaces in hostname.
struct hostent *hp;
int h_errno_local;
#ifdef HAVE_GETHOSTBYNAME2_R
struct hostent hostData;
char tmpBuffer[2048];
// There are different flavors of gethostbyname_r(), but
// latest Linux use the following form:
if (gethostbyname2_r(host, AF_INET, &hostData, tmpBuffer, sizeof(tmpBuffer), &hp, &h_errno_local)!=0) {
CERR("WARNING -- gethostbyname2_r() failed for " << host << ", " << hstrerror(h_errno_local));
return false;
}
#else
static Mutex sGethostbynameMutex;
// gethostbyname() is NOT thread-safe, so we should use a mutex here.
// Ideally it should be a global mutex for all non thread-safe socket
// operations and it should protect access to variables such as
// global h_errno.
sGethostbynameMutex.lock();
hp = gethostbyname(host);
h_errno_local = h_errno;
sGethostbynameMutex.unlock();
#endif
if (hp==NULL) {
CERR("WARNING -- gethostbyname() failed for " << host << ", " << hstrerror(h_errno_local));
return false;
}
if (hp->h_addrtype != AF_INET) {
CERR("WARNING -- gethostbyname() resolved " << host << " to something other then AF_INET");
return false;
}
address->sin_family = hp->h_addrtype;
assert(sizeof(address->sin_addr) == hp->h_length);
memcpy(&(address->sin_addr), hp->h_addr_list[0], hp->h_length);
address->sin_port = htons(port);
return true;
}
DatagramSocket::DatagramSocket()
{
memset(mDestination, 0, sizeof(mDestination));
}
void DatagramSocket::nonblocking()
{
fcntl(mSocketFD,F_SETFL,O_NONBLOCK);
}
void DatagramSocket::blocking()
{
fcntl(mSocketFD,F_SETFL,0);
}
void DatagramSocket::close()
{
::close(mSocketFD);
}
DatagramSocket::~DatagramSocket()
{
close();
}
int DatagramSocket::write( const char * message, size_t length )
{
assert(length<=MAX_UDP_LENGTH);
int retVal = sendto(mSocketFD, message, length, 0,
(struct sockaddr *)mDestination, addressSize());
if (retVal == -1 ) perror("DatagramSocket::write() failed");
return retVal;
}
int DatagramSocket::writeBack( const char * message, size_t length )
{
assert(length<=MAX_UDP_LENGTH);
int retVal = sendto(mSocketFD, message, length, 0,
(struct sockaddr *)mSource, addressSize());
if (retVal == -1 ) perror("DatagramSocket::write() failed");
return retVal;
}
int DatagramSocket::write( const char * message)
{
size_t length=strlen(message)+1;
return write(message,length);
}
int DatagramSocket::writeBack( const char * message)
{
size_t length=strlen(message)+1;
return writeBack(message,length);
}
int DatagramSocket::send(const struct sockaddr* dest, const char * message, size_t length )
{
assert(length<=MAX_UDP_LENGTH);
int retVal = sendto(mSocketFD, message, length, 0, dest, addressSize());
if (retVal == -1 ) perror("DatagramSocket::send() failed");
return retVal;
}
int DatagramSocket::send(const struct sockaddr* dest, const char * message)
{
size_t length=strlen(message)+1;
return send(dest,message,length);
}
int DatagramSocket::read(char* buffer, size_t length)
{
socklen_t addr_len = sizeof(mSource);
int rd_length = recvfrom(mSocketFD, (void *) buffer, length, 0,
(struct sockaddr*) &mSource, &addr_len);
if ((rd_length==-1) && (errno!=EAGAIN)) {
perror("DatagramSocket::read() failed");
throw SocketError();
}
return rd_length;
}
int DatagramSocket::read(char* buffer, size_t length, unsigned timeout)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(mSocketFD,&fds);
struct timeval tv;
tv.tv_sec = timeout/1000;
tv.tv_usec = (timeout%1000)*1000;
int sel = select(mSocketFD+1,&fds,NULL,NULL,&tv);
if (sel<0) {
perror("DatagramSocket::read() select() failed");
throw SocketError();
}
if (sel==0) return -1;
if (FD_ISSET(mSocketFD,&fds)) return read(buffer, length);
return -1;
}
UDPSocket::UDPSocket(const char *wSrcIP, unsigned short wSrcPort)
:DatagramSocket()
{
open(wSrcPort, wSrcIP);
}
UDPSocket::UDPSocket(const char *wSrcIP, unsigned short wSrcPort,
const char *wDestIP, unsigned short wDestPort)
:DatagramSocket()
{
open(wSrcPort, wSrcIP);
destination(wDestPort, wDestIP);
}
void UDPSocket::destination( unsigned short wDestPort, const char * wDestIP )
{
resolveAddress((sockaddr_in*)mDestination, wDestIP, wDestPort );
}
void UDPSocket::open(unsigned short localPort, const char *wlocalIP)
{
// create
mSocketFD = socket(AF_INET,SOCK_DGRAM,0);
if (mSocketFD<0) {
perror("socket() failed");
throw SocketError();
}
// pat added: This lets the socket be reused immediately, which is needed if OpenBTS crashes.
int on = 1;
setsockopt(mSocketFD, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
// bind
struct sockaddr_in address;
size_t length = sizeof(address);
bzero(&address,length);
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr(wlocalIP);
address.sin_port = htons(localPort);
if (bind(mSocketFD,(struct sockaddr*)&address,length)<0) {
perror("bind() failed");
throw SocketError();
}
}
unsigned short UDPSocket::port() const
{
struct sockaddr_in name;
socklen_t nameSize = sizeof(name);
int retVal = getsockname(mSocketFD, (struct sockaddr*)&name, &nameSize);
if (retVal==-1) throw SocketError();
return ntohs(name.sin_port);
}
// vim:ts=4:sw=4

173
CommonLibs/Sockets.h Normal file
View File

@@ -0,0 +1,173 @@
/*
* Copyright 2008, 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU 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/>.
*/
#ifndef SOCKETS_H
#define SOCKETS_H
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <errno.h>
#include <list>
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#define MAX_UDP_LENGTH 1500
/** A function to resolve IP host names. */
bool resolveAddress(struct sockaddr_in *address, const char *host, unsigned short port);
/** Resolve an address of the form "<host>:<port>". */
bool resolveAddress(struct sockaddr_in *address, const char *hostAndPort);
/** An exception to throw when a critical socket operation fails. */
class SocketError {};
#define SOCKET_ERROR {throw SocketError(); }
/** Abstract class for connectionless sockets. */
class DatagramSocket {
protected:
int mSocketFD; ///< underlying file descriptor
char mDestination[256]; ///< address to which packets are sent
char mSource[256]; ///< return address of most recent received packet
public:
/** An almost-does-nothing constructor. */
DatagramSocket();
virtual ~DatagramSocket();
/** Return the address structure size for this socket type. */
virtual size_t addressSize() const = 0;
/**
Send a binary packet.
@param buffer The data bytes to send to mDestination.
@param length Number of bytes to send, or strlen(buffer) if defaulted to -1.
@return number of bytes written, or -1 on error.
*/
int write( const char * buffer, size_t length);
/**
Send a C-style string packet.
@param buffer The data bytes to send to mDestination.
@return number of bytes written, or -1 on error.
*/
int write( const char * buffer);
/**
Send a binary packet.
@param buffer The data bytes to send to mSource.
@param length Number of bytes to send, or strlen(buffer) if defaulted to -1.
@return number of bytes written, or -1 on error.
*/
int writeBack(const char * buffer, size_t length);
/**
Send a C-style string packet.
@param buffer The data bytes to send to mSource.
@return number of bytes written, or -1 on error.
*/
int writeBack(const char * buffer);
/**
Receive a packet.
@param buffer A char[MAX_UDP_LENGTH] procured by the caller.
@return The number of bytes received or -1 on non-blocking pass.
*/
int read(char* buffer, size_t length);
/**
Receive a packet with a timeout.
@param buffer A char[MAX_UDP_LENGTH] procured by the caller.
@param maximum wait time in milliseconds
@return The number of bytes received or -1 on timeout.
*/
int read(char* buffer, size_t length, unsigned timeout);
/** Send a packet to a given destination, other than the default. */
int send(const struct sockaddr *dest, const char * buffer, size_t length);
/** Send a C-style string to a given destination, other than the default. */
int send(const struct sockaddr *dest, const char * buffer);
/** Make the socket non-blocking. */
void nonblocking();
/** Make the socket blocking (the default). */
void blocking();
/** Close the socket. */
void close();
};
/** UDP/IP User Datagram Socket */
class UDPSocket : public DatagramSocket {
public:
/** Open a USP socket with an OS-assigned port and no default destination. */
UDPSocket(const char *localIP, unsigned short localPort);
/** Given a full specification, open the socket and set the dest address. */
UDPSocket(const char *localIP, unsigned short localPort,
const char *remoteIP, unsigned short remotePort);
/** Set the destination port. */
void destination( unsigned short wDestPort, const char * wDestIP );
/** Return the actual port number in use. */
unsigned short port() const;
/** Open and bind the UDP socket to a local port. */
void open(unsigned short localPort=0, const char *wlocalIP="127.0.0.1");
/** Give the return address of the most recently received packet. */
const struct sockaddr_in* source() const { return (const struct sockaddr_in*)mSource; }
size_t addressSize() const { return sizeof(struct sockaddr_in); }
};
#endif
// vim:ts=4:sw=4

View File

@@ -1,7 +1,6 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
@@ -25,17 +24,11 @@
*/
#include <string.h>
#include <sys/types.h>
#include "Threads.h"
#include "Timeval.h"
#include "Logger.h"
#ifndef gettid
#include <sys/syscall.h>
#define gettid() syscall(SYS_gettid)
#endif
using namespace std;
@@ -109,25 +102,6 @@ void Signal::wait(Mutex& wMutex, unsigned timeout) const
pthread_cond_timedwait(&mSignal,&wMutex.mMutex,&waitTime);
}
void set_selfthread_name(const char *name)
{
pthread_t selfid = pthread_self();
pid_t tid = gettid();
if (pthread_setname_np(selfid, name) == 0) {
LOG(INFO) << "Thread "<< selfid << " (task " << tid << ") set name: " << name;
} else {
char buf[256];
int err = errno;
char* err_str = strerror_r(err, buf, sizeof(buf));
LOG(NOTICE) << "Thread "<< selfid << " (task " << tid << ") set name \"" << name << "\" failed: (" << err << ") " << err_str;
}
}
void thread_enable_cancel(bool cancel)
{
cancel ? pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) :
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
}
void Thread::start(void *(*task)(void*), void *arg)
{
@@ -136,10 +110,8 @@ void Thread::start(void *(*task)(void*), void *arg)
// (pat) Moved initialization to constructor to avoid crash in destructor.
//res = pthread_attr_init(&mAttrib);
//assert(!res);
if (mStackSize != 0) {
res = pthread_attr_setstacksize(&mAttrib, mStackSize);
assert(!res);
}
res = pthread_attr_setstacksize(&mAttrib, mStackSize);
assert(!res);
res = pthread_create(&mThread, &mAttrib, task, arg);
assert(!res);
}

View File

@@ -1,8 +1,6 @@
/*
* Copyright 2008, 2011 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
@@ -57,7 +55,7 @@ void unlockCout(); ///< call after writing cout
#define OBJDCOUT(text) {}
#else
#define DCOUT(text) { COUT(__FILE__ << ":" << __LINE__ << " " << text); }
#define OBJDCOUT(text) { DCOUT(this << " " << text); }
#define OBJDCOUT(text) { DCOUT(this << " " << text); }
#endif
//@}
//@}
@@ -143,9 +141,6 @@ class Signal {
#define START_THREAD(thread,function,argument) \
thread.start((void *(*)(void*))function, (void*)argument);
void set_selfthread_name(const char *name);
void thread_enable_cancel(bool cancel);
/** A C++ wrapper for pthread threads. */
class Thread {
@@ -155,12 +150,12 @@ class Thread {
pthread_attr_t mAttrib;
// FIXME -- Can this be reduced now?
size_t mStackSize;
public:
/** Create a thread in a non-running state. */
Thread(size_t wStackSize = 0):mThread((pthread_t)0) {
Thread(size_t wStackSize = (65536*4)):mThread((pthread_t)0) {
pthread_attr_init(&mAttrib); // (pat) moved this here.
mStackSize=wStackSize;
}

View File

@@ -1,7 +1,6 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
@@ -28,49 +27,43 @@
#include "Timeval.h"
extern "C" {
#include <osmocom/core/timer.h>
}
using namespace std;
void Timeval::now()
{
osmo_clock_gettime(CLOCK_REALTIME, &mTimespec);
}
void Timeval::future(unsigned offset)
{
now();
unsigned sec = offset/1000;
unsigned msec = offset%1000;
mTimespec.tv_nsec += msec*1000*1000;
mTimespec.tv_sec += sec;
if (mTimespec.tv_nsec > 1000*1000*1000) {
mTimespec.tv_nsec -= 1000*1000*1000;
mTimespec.tv_sec += 1;
mTimeval.tv_usec += msec*1000;
mTimeval.tv_sec += sec;
if (mTimeval.tv_usec>1000000) {
mTimeval.tv_usec -= 1000000;
mTimeval.tv_sec += 1;
}
}
struct timespec Timeval::timespec() const
{
return mTimespec;
struct timespec retVal;
retVal.tv_sec = mTimeval.tv_sec;
retVal.tv_nsec = 1000 * (long)mTimeval.tv_usec;
return retVal;
}
bool Timeval::passed() const
{
Timeval nowTime;
if (nowTime.mTimespec.tv_sec < mTimespec.tv_sec) return false;
if (nowTime.mTimespec.tv_sec > mTimespec.tv_sec) return true;
if (nowTime.mTimespec.tv_nsec >= mTimespec.tv_nsec) return true;
if (nowTime.mTimeval.tv_sec < mTimeval.tv_sec) return false;
if (nowTime.mTimeval.tv_sec > mTimeval.tv_sec) return true;
if (nowTime.mTimeval.tv_usec > mTimeval.tv_usec) return true;
return false;
}
double Timeval::seconds() const
{
return ((double)mTimespec.tv_sec) + 1e-9*((double)mTimespec.tv_nsec);
return ((double)mTimeval.tv_sec) + 1e-6*((double)mTimeval.tv_usec);
}
@@ -79,25 +72,24 @@ long Timeval::delta(const Timeval& other) const
{
// 2^31 milliseconds is just over 4 years.
int32_t deltaS = other.sec() - sec();
int32_t deltaNs = other.nsec() - nsec();
return 1000*deltaS + deltaNs/1000000;
int32_t deltaUs = other.usec() - usec();
return 1000*deltaS + deltaUs/1000;
}
ostream& operator<<(ostream& os, const Timeval& tv)
{
ios_base::fmtflags flags_backup = os.setf( ios::fixed, ios::floatfield );
os.setf( ios::fixed, ios::floatfield );
os << tv.seconds();
os.flags( flags_backup );
return os;
}
ostream& operator<<(ostream& os, const struct timespec& ts)
{
os << ts.tv_sec << "," << ts.tv_nsec/1000;
os << ts.tv_sec << "," << ts.tv_nsec;
return os;
}

View File

@@ -1,8 +1,6 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
@@ -44,12 +42,12 @@ class Timeval {
private:
struct timespec mTimespec;
struct timeval mTimeval;
public:
/** Set the value to current time. */
void now();
/** Set the value to gettimeofday. */
void now() { gettimeofday(&mTimeval,NULL); }
/** Set the value to gettimeofday plus an offset. */
void future(unsigned ms);
@@ -57,18 +55,16 @@ class Timeval {
//@{
Timeval(unsigned sec, unsigned usec)
{
mTimespec.tv_sec = sec;
mTimespec.tv_nsec = usec*1000;
mTimeval.tv_sec = sec;
mTimeval.tv_usec = usec;
}
Timeval(const struct timeval& wTimeval)
{
mTimespec.tv_sec = wTimeval.tv_sec;
mTimespec.tv_nsec = wTimeval.tv_sec*1000;
}
:mTimeval(wTimeval)
{}
/**
Create a Timespec offset into the future.
Create a Timeval offset into the future.
@param offset milliseconds
*/
Timeval(unsigned offset=0) { future(offset); }
@@ -80,9 +76,8 @@ class Timeval {
/** Return total seconds. */
double seconds() const;
uint32_t sec() const { return mTimespec.tv_sec; }
uint32_t usec() const { return mTimespec.tv_nsec / 1000; }
uint32_t nsec() const { return mTimespec.tv_nsec; }
uint32_t sec() const { return mTimeval.tv_sec; }
uint32_t usec() const { return mTimeval.tv_usec; }
/** Return differnce from other (other-self), in ms. */
long delta(const Timeval& other) const;
@@ -93,11 +88,11 @@ class Timeval {
/** Remaining time in ms. */
long remaining() const { return -elapsed(); }
/** Return true if the time has passed, as per clock_gettime(CLOCK_REALTIME). */
/** Return true if the time has passed, as per gettimeofday. */
bool passed() const;
/** Add a given number of minutes to the time. */
void addMinutes(unsigned minutes) { mTimespec.tv_sec += minutes*60; }
void addMinutes(unsigned minutes) { mTimeval.tv_sec += minutes*60; }
};

View File

@@ -1,38 +0,0 @@
/*
* Copyright 2018 sysmocom - s.f.m.c. GmbH
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* 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 <vector>
#include <string>
#include <sstream>
std::vector<std::string> comma_delimited_to_vector(const char* opt)
{
std::string str = std::string(opt);
std::vector<std::string> result;
std::stringstream ss(str);
while( ss.good() )
{
std::string substr;
getline(ss, substr, ',');
result.push_back(substr);
}
return result;
}

View File

@@ -1,26 +0,0 @@
/*
* Copyright 2018 sysmocom - s.f.m.c. GmbH
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* 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
#include <vector>
#include <string>
std::vector<std::string> comma_delimited_to_vector(const char* opt);

View File

@@ -2,8 +2,6 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
@@ -34,14 +32,11 @@
#include <string.h>
#include <iostream>
#include <assert.h>
#include <stdlib.h>
// We cant use Logger.h in this file...
extern int gVectorDebug;
#define BVDEBUG(msg) if (gVectorDebug) {std::cout << msg;}
typedef void (*vector_free_func)(void* wData);
typedef void *(*vector_alloc_func)(size_t newSize);
/**
A simplified Vector template with aliases.
@@ -65,8 +60,6 @@ template <class T> class Vector {
T* mData; ///< allocated data block, if any
T* mStart; ///< start of useful data
T* mEnd; ///< end of useful data + 1
vector_alloc_func mAllocFunc; ///< function used to alloc new mData during resize.
vector_free_func mFreeFunc; ///< function used to free mData.
public:
@@ -92,19 +85,9 @@ template <class T> class Vector {
/** Change the size of the Vector, discarding content. */
void resize(size_t newSize)
{
if (mData!=NULL) {
if (mFreeFunc)
mFreeFunc(mData);
else
delete[] mData;
}
if (mData!=NULL) delete[] mData;
if (newSize==0) mData=NULL;
else {
if (mAllocFunc)
mData = (T*) mAllocFunc(newSize);
else
mData = new T[newSize];
}
else mData = new T[newSize];
mStart = mData;
mEnd = mStart + newSize;
}
@@ -124,7 +107,7 @@ template <class T> class Vector {
void clone(const Vector<T>& other)
{
resize(other.size());
other.copyTo(*this);
memcpy(mData,other.mStart,other.bytes());
}
@@ -133,31 +116,29 @@ template <class T> class Vector {
//@{
/** Build an empty Vector of a given size. */
Vector(size_t wSize=0, vector_alloc_func wAllocFunc=NULL, vector_free_func wFreeFunc=NULL)
:mData(NULL), mAllocFunc(wAllocFunc), mFreeFunc(wFreeFunc)
{ resize(wSize); }
Vector(size_t wSize=0):mData(NULL) { resize(wSize); }
/** Build a Vector by moving another. */
Vector(Vector<T>&& other)
:mData(other.mData),mStart(other.mStart),mEnd(other.mEnd), mAllocFunc(other.mAllocFunc), mFreeFunc(other.mFreeFunc)
:mData(other.mData),mStart(other.mStart),mEnd(other.mEnd)
{ other.mData=NULL; }
/** Build a Vector by copying another. */
Vector(const Vector<T>& other):mData(NULL), mAllocFunc(other.mAllocFunc), mFreeFunc(other.mFreeFunc) { clone(other); }
Vector(const Vector<T>& other):mData(NULL) { clone(other); }
/** Build a Vector with explicit values. */
Vector(T* wData, T* wStart, T* wEnd, vector_alloc_func wAllocFunc=NULL, vector_free_func wFreeFunc=NULL)
:mData(wData),mStart(wStart),mEnd(wEnd), mAllocFunc(wAllocFunc), mFreeFunc(wFreeFunc)
Vector(T* wData, T* wStart, T* wEnd)
:mData(wData),mStart(wStart),mEnd(wEnd)
{ }
/** Build a vector from an existing block, NOT to be deleted upon destruction. */
Vector(T* wStart, size_t span, vector_alloc_func wAllocFunc=NULL, vector_free_func wFreeFunc=NULL)
:mData(NULL),mStart(wStart),mEnd(wStart+span),mAllocFunc(wAllocFunc), mFreeFunc(wFreeFunc)
Vector(T* wStart, size_t span)
:mData(NULL),mStart(wStart),mEnd(wStart+span)
{ }
/** Build a Vector by concatenation. */
Vector(const Vector<T>& other1, const Vector<T>& other2, vector_alloc_func wAllocFunc=NULL, vector_free_func wFreeFunc=NULL)
:mData(NULL), mAllocFunc(wAllocFunc), mFreeFunc(wFreeFunc)
Vector(const Vector<T>& other1, const Vector<T>& other2)
:mData(NULL)
{
resize(other1.size()+other2.size());
memcpy(mStart, other1.mStart, other1.bytes());
@@ -181,8 +162,6 @@ template <class T> class Vector {
mData=other.mData;
mStart=other.mStart;
mEnd=other.mEnd;
mAllocFunc=other.mAllocFunc;
mFreeFunc=other.mFreeFunc;
other.mData=NULL;
}
@@ -225,15 +204,10 @@ template <class T> class Vector {
*/
void copyToSegment(Vector<T>& other, size_t start, size_t span) const
{
unsigned int i;
T* dst = other.mStart + start;
T* src = mStart;
assert(dst+span<=other.mEnd);
T* base = other.mStart + start;
assert(base+span<=other.mEnd);
assert(mStart+span<=mEnd);
for (i = 0; i < span; i++, src++, dst++)
*dst = *src;
/*TODO if not non-trivially copyable type class, optimize:
memcpy(dst,mStart,span*sizeof(T)); */
memcpy(base,mStart,span*sizeof(T));
}
/** Copy all of this Vector to a segment of another Vector. */
@@ -308,7 +282,7 @@ template <class T> class Vector {
T* end() { return mEnd; }
bool isOwner() { return !!mData; } // Do we own any memory ourselves?
//@}
};

View File

@@ -1,28 +1,3 @@
/*
* Copyright (C) 2018-2019 sysmocom - s.f.m.c. GmbH
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Pau Espin Pedrol <pespin@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/>.
* See the COPYING file in the main directory for details.
*/
#include <pthread.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include "debug.h"
@@ -35,21 +10,9 @@ static const struct log_info_cat default_categories[] = {
.color = NULL,
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DTRXCTRL] = {
.name = "DTRXCTRL",
.description = "TRX CTRL interface",
.color = "\033[1;33m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DDEV] = {
.name = "DDEV",
.description = "Device/Driver specific code",
.color = NULL,
.enabled = 1, .loglevel = LOGL_INFO,
},
[DLMS] = {
.name = "DLMS",
.description = "Logging from within LimeSuite itself",
.description = "LimeSuite category",
.color = NULL,
.enabled = 1, .loglevel = LOGL_NOTICE,
},
@@ -59,49 +22,3 @@ const struct log_info log_info = {
.cat = default_categories,
.num_cat = ARRAY_SIZE(default_categories),
};
pthread_mutex_t log_mutex;
bool log_mutex_init() {
int rc;
pthread_mutexattr_t attr;
if ((rc = pthread_mutexattr_init(&attr))) {
fprintf(stderr, "pthread_mutexattr_init() failed: %d\n", rc);
return false;
}
if ((rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE))) {
fprintf(stderr, "pthread_mutexattr_settype() failed: %d\n", rc);
return false;
}
if ((rc = pthread_mutex_init(&log_mutex, &attr))) {
fprintf(stderr, "pthread_mutex_init() failed: %d\n", rc);
return false;
}
if ((rc = pthread_mutexattr_destroy(&attr))) {
fprintf(stderr, "pthread_mutexattr_destroy() failed: %d\n", rc);
return false;
}
return true;
/* FIXME: do we need to call pthread_mutex_destroy() during process exit? */
}
/* If called inside a C++ destructor, use log_mutex_(un)lock_canceldisable() APIs instead.
See osmo-trx commit 86be40b4eb762d5c12e8e3f7388ca9f254e77b36 for more information */
void log_mutex_lock() {
OSMO_ASSERT(!pthread_mutex_lock(&log_mutex));
}
void log_mutex_unlock() {
OSMO_ASSERT(!pthread_mutex_unlock(&log_mutex));
}
void log_mutex_lock_canceldisable(int *st) {
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, st);
log_mutex_lock();
}
void log_mutex_unlock_canceldisable(int st) {
log_mutex_unlock();
pthread_setcancelstate(st, NULL);
}

View File

@@ -1,35 +1,9 @@
#pragma once
#include <stdbool.h>
#include <pthread.h>
#include <osmocom/core/logging.h>
extern const struct log_info log_info;
/* Debug Areas of the code */
enum {
DMAIN,
DTRXCTRL,
DDEV,
DLMS,
};
bool log_mutex_init();
void log_mutex_lock();
void log_mutex_unlock();
void log_mutex_lock_canceldisable(int *st);
void log_mutex_unlock_canceldisable(int st);
#define CLOGC(category, level, fmt, args...) do { \
log_mutex_lock(); \
LOGP(category, level, "[tid=%lu] " fmt, pthread_self(), ##args); \
log_mutex_unlock(); \
} while(0)
#define CLOGCHAN(chan, category, level, fmt, args...) do { \
log_mutex_lock(); \
LOGP(category, level, "[tid=%lu][chan=%lu] " fmt, pthread_self(), chan, ##args); \
log_mutex_unlock(); \
} while(0)

View File

@@ -1,57 +0,0 @@
/* Generic signalling/notification infrastructure */
/* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* All Rights Reserved
*
* 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/>.
*
*/
#pragma once
#include <osmocom/core/signal.h>
/* Signalling subsystems */
enum signal_subsystems {
SS_MAIN,
SS_DEVICE,
};
/* SS_MAIN signals */
enum SS_MAIN {
S_MAIN_STOP_REQUIRED, /* TRX fatal error, it should be stopped */
};
/* SS_DEVICE signals */
enum SS_DEVICE {
/* Device internal counters changed. Counters are provided as cb data
(struct device_counters). Must be sent with PTHREAD_CANCEL_DISABLE
to avoid deadlocks in case osmo-trx process is asked to exit. */
S_DEVICE_COUNTER_CHANGE,
};
/* signal cb for signal <SS_DEVICE,S_DEVICE_COUNTER_CHANGE> */
struct device_counters {
size_t chan;
unsigned int rx_overruns;
unsigned int tx_underruns;
unsigned int rx_dropped_events;
unsigned int rx_dropped_samples;
unsigned int tx_dropped_events;
unsigned int tx_dropped_samples;
};

View File

@@ -1,336 +0,0 @@
/*
* Copyright (C) 2019 sysmocom - s.f.m.c. GmbH
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Pau Espin Pedrol <pespin@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/>.
* See the COPYING file in the main directory for details.
*/
/*
* rate_ctr API uses several osmocom select loop features, and as a result,
* calls to it must be done through the main thread (the one running the osmocom
* loop in osmo-trx).
* Since read/write from/to SDR is done in separate threads (even read and write
* each use a different thread), we must use some sort of message passing system
* between main thread feeding rate_ctr structures and the Rx/Tx threads
* generating the events.
* The idea is that upon read/write issues, lower layers (SDR APIs) provide us with
* underrun/overrun/droppedPackets information, and in that case we pass that up
* the stack through signal <SS_DEVICE,S_DEVICE_COUNTER_CHANGE> with signal_cb
* being a pointer to a "struct device_counters" structure, which contains
* device (implementation agnostic) statful counters for different kind of
* statistics.
* That signal is processed here in device_sig_cb, where a copy of the "struct
* device_counters" structure is held and the main thread is instructed through
* a timerfd to update rate_ctr APIs against this copy. All this is done inside
* a mutex to avoid different race conditons (between Rx andTx threads, and
* between Rx/Tx and main thread). For the same reason, callers of signal
* <SS_DEVICE,S_DEVICE_COUNTER_CHANGE> (device_sig_cb), that is Rx/Tx threads,
* must do so with PTHREAD_CANCEL_DISABLE, in order to avoid possible deadlocks
* in case the main thread decides to cancel other threads due to a shutdown
* operation (fi SIGKILL received)
*/
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <netinet/in.h>
#include <arpa/inet.h>
extern "C" {
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/select.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/timer.h>
#include "osmo_signal.h"
#include "trx_vty.h"
#include "trx_rate_ctr.h"
}
#include "Threads.h"
#include "Logger.h"
/* Used in ctrs_pending, when set it means that channel slot contains unused
(non-pending) counter data */
#define PENDING_CHAN_NONE SIZE_MAX
static void *trx_rate_ctr_ctx;
static struct rate_ctr_group** rate_ctrs;
static struct device_counters* ctrs_pending;
static size_t chan_len;
static struct osmo_fd rate_ctr_timerfd;
static Mutex rate_ctr_mutex;
struct osmo_timer_list threshold_timer;
static LLIST_HEAD(threshold_list);
static int threshold_timer_sched_secs;
static bool threshold_initied;
const struct value_string rate_ctr_intv[] = {
{ RATE_CTR_INTV_SEC, "per-second" },
{ RATE_CTR_INTV_MIN, "per-minute" },
{ RATE_CTR_INTV_HOUR, "per-hour" },
{ RATE_CTR_INTV_DAY, "per-day" },
{ 0, NULL }
};
const struct value_string trx_chan_ctr_names[] = {
{ TRX_CTR_RX_OVERRUNS, "rx_overruns" },
{ TRX_CTR_TX_UNDERRUNS, "tx_underruns" },
{ TRX_CTR_RX_DROP_EV, "rx_drop_events" },
{ TRX_CTR_RX_DROP_SMPL, "rx_drop_samples" },
{ TRX_CTR_TX_DROP_EV, "tx_drop_events" },
{ TRX_CTR_TX_DROP_SMPL, "tx_drop_samples" },
{ 0, NULL }
};
static const struct rate_ctr_desc trx_chan_ctr_desc[] = {
[TRX_CTR_RX_OVERRUNS] = { "device:rx_overruns", "Number of Rx overruns in FIFO queue" },
[TRX_CTR_TX_UNDERRUNS] = { "device:tx_underruns", "Number of Tx underruns in FIFO queue" },
[TRX_CTR_RX_DROP_EV] = { "device:rx_drop_events", "Number of times Rx samples were dropped by HW" },
[TRX_CTR_RX_DROP_SMPL] = { "device:rx_drop_samples", "Number of Rx samples dropped by HW" },
[TRX_CTR_TX_DROP_EV] = { "device:tx_drop_events", "Number of times Tx samples were dropped by HW" },
[TRX_CTR_TX_DROP_SMPL] = { "device:tx_drop_samples", "Number of Tx samples dropped by HW" }
};
static const struct rate_ctr_group_desc trx_chan_ctr_group_desc = {
.group_name_prefix = "trx:chan",
.group_description = "osmo-trx statistics",
.class_id = OSMO_STATS_CLASS_GLOBAL,
.num_ctr = ARRAY_SIZE(trx_chan_ctr_desc),
.ctr_desc = trx_chan_ctr_desc,
};
static int rate_ctr_timerfd_cb(struct osmo_fd *ofd, unsigned int what) {
size_t chan;
struct rate_ctr *ctr;
LOGC(DMAIN, NOTICE) << "Main thread is updating counters";
rate_ctr_mutex.lock();
for (chan = 0; chan < chan_len; chan++) {
if (ctrs_pending[chan].chan == PENDING_CHAN_NONE)
continue;
LOGCHAN(chan, DMAIN, INFO) << "rate_ctr update";
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_RX_OVERRUNS];
rate_ctr_add(ctr, ctrs_pending[chan].rx_overruns - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TX_UNDERRUNS];
rate_ctr_add(ctr, ctrs_pending[chan].tx_underruns - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_RX_DROP_EV];
rate_ctr_add(ctr, ctrs_pending[chan].rx_dropped_events - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_RX_DROP_SMPL];
rate_ctr_add(ctr, ctrs_pending[chan].rx_dropped_samples - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TX_DROP_EV];
rate_ctr_add(ctr, ctrs_pending[chan].tx_dropped_events - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TX_DROP_SMPL];
rate_ctr_add(ctr, ctrs_pending[chan].tx_dropped_samples - ctr->current);
/* Mark as done */
ctrs_pending[chan].chan = PENDING_CHAN_NONE;
}
if (osmo_timerfd_disable(&rate_ctr_timerfd) < 0)
LOGC(DMAIN, ERROR) << "Failed to disable timerfd";
rate_ctr_mutex.unlock();
return 0;
}
/* Callback function to be called every time we receive a signal from DEVICE */
static int device_sig_cb(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
{
struct device_counters *ctr;
/* Delay sched around 20 ms, in case we receive several calls from several
* channels batched */
struct timespec next_sched = {.tv_sec = 0, .tv_nsec = 20*1000*1000};
/* no automatic re-trigger */
struct timespec intv_sched = {.tv_sec = 0, .tv_nsec = 0};
switch (signal) {
case S_DEVICE_COUNTER_CHANGE:
ctr = (struct device_counters *)signal_data;
LOGCHAN(ctr->chan, DMAIN, NOTICE) << "Received counter change from radioDevice";
rate_ctr_mutex.lock();
ctrs_pending[ctr->chan] = *ctr;
if (osmo_timerfd_schedule(&rate_ctr_timerfd, &next_sched, &intv_sched) < 0) {
LOGC(DMAIN, ERROR) << "Failed to schedule timerfd: " << errno << " = "<< strerror(errno);
}
rate_ctr_mutex.unlock();
break;
default:
break;
}
return 0;
}
/************************************
* ctr_threshold APIs
************************************/
static const char* ctr_threshold_2_vty_str(struct ctr_threshold *ctr)
{
static char buf[256];
int rc = 0;
rc += snprintf(buf, sizeof(buf), "ctr-error-threshold %s", get_value_string(trx_chan_ctr_names, ctr->ctr_id));
rc += snprintf(buf + rc, sizeof(buf) - rc, " %d %s", ctr->val, get_value_string(rate_ctr_intv, ctr->intv));
return buf;
}
static void threshold_timer_cb(void *data)
{
struct ctr_threshold *ctr_thr;
struct rate_ctr *rate_ctr;
size_t chan;
LOGC(DMAIN, DEBUG) << "threshold_timer_cb fired!";
llist_for_each_entry(ctr_thr, &threshold_list, list) {
for (chan = 0; chan < chan_len; chan++) {
rate_ctr = &rate_ctrs[chan]->ctr[ctr_thr->ctr_id];
LOGCHAN(chan, DMAIN, INFO) << "checking threshold: " << ctr_threshold_2_vty_str(ctr_thr)
<< " ("<< rate_ctr->intv[ctr_thr->intv].rate << " vs " << ctr_thr->val << ")";
if (rate_ctr->intv[ctr_thr->intv].rate >= ctr_thr->val) {
LOGCHAN(chan, DMAIN, FATAL) << "threshold reached, stopping! " << ctr_threshold_2_vty_str(ctr_thr)
<< " ("<< rate_ctr->intv[ctr_thr->intv].rate << " vs " << ctr_thr->val << ")";
osmo_signal_dispatch(SS_MAIN, S_MAIN_STOP_REQUIRED, NULL);
return;
}
}
}
osmo_timer_schedule(&threshold_timer, threshold_timer_sched_secs, 0);
}
static size_t ctr_threshold_2_seconds(struct ctr_threshold *ctr)
{
size_t mult = 0;
switch (ctr->intv) {
case RATE_CTR_INTV_SEC:
mult = 1;
break;
case RATE_CTR_INTV_MIN:
mult = 60;
break;
case RATE_CTR_INTV_HOUR:
mult = 60*60;
break;
case RATE_CTR_INTV_DAY:
mult = 60*60*24;
break;
default:
OSMO_ASSERT(false);
}
return mult;
}
static void threshold_timer_update_intv() {
struct ctr_threshold *ctr, *min_ctr;
size_t secs, min_secs;
/* Avoid scheduling timer until itself and other structures are prepared
by trx_rate_ctr_init */
if (!threshold_initied)
return;
if (llist_empty(&threshold_list)) {
if (osmo_timer_pending(&threshold_timer))
osmo_timer_del(&threshold_timer);
return;
}
min_ctr = llist_first_entry(&threshold_list, struct ctr_threshold, list);
min_secs = ctr_threshold_2_seconds(min_ctr);
llist_for_each_entry(ctr, &threshold_list, list) {
secs = ctr_threshold_2_seconds(ctr);
if( min_secs > secs)
min_secs = secs;
}
threshold_timer_sched_secs = OSMO_MAX(min_secs / 2 - 1, 1);
LOGC(DMAIN, INFO) << "New ctr-error-threshold check interval: "
<< threshold_timer_sched_secs << " seconds";
osmo_timer_schedule(&threshold_timer, threshold_timer_sched_secs, 0);
}
/* Init rate_ctr subsystem. Expected to be called during process start by main thread before VTY is ready */
void trx_rate_ctr_init(void *ctx, struct trx_ctx* trx_ctx)
{
size_t i;
trx_rate_ctr_ctx = ctx;
chan_len = trx_ctx->cfg.num_chans;
ctrs_pending = (struct device_counters*) talloc_zero_size(ctx, chan_len * sizeof(struct device_counters));
rate_ctrs = (struct rate_ctr_group**) talloc_zero_size(ctx, chan_len * sizeof(struct rate_ctr_group*));
for (i = 0; i < chan_len; i++) {
ctrs_pending[i].chan = PENDING_CHAN_NONE;
rate_ctrs[i] = rate_ctr_group_alloc(ctx, &trx_chan_ctr_group_desc, i);
if (!rate_ctrs[i]) {
LOGCHAN(i, DMAIN, ERROR) << "Failed to allocate rate ctr";
exit(1);
}
}
rate_ctr_timerfd.fd = -1;
if (osmo_timerfd_setup(&rate_ctr_timerfd, rate_ctr_timerfd_cb, NULL) < 0) {
LOGC(DMAIN, ERROR) << "Failed to setup timerfd";
exit(1);
}
osmo_signal_register_handler(SS_DEVICE, device_sig_cb, NULL);
/* Now set up threshold checks */
threshold_initied = true;
osmo_timer_setup(&threshold_timer, threshold_timer_cb, NULL);
threshold_timer_update_intv();
}
void trx_rate_ctr_threshold_add(struct ctr_threshold *ctr)
{
struct ctr_threshold *new_ctr;
new_ctr = talloc_zero(trx_rate_ctr_ctx, struct ctr_threshold);
*new_ctr = *ctr;
LOGC(DMAIN, NOTICE) << "Adding new threshold check: " << ctr_threshold_2_vty_str(new_ctr);
llist_add(&new_ctr->list, &threshold_list);
threshold_timer_update_intv();
}
int trx_rate_ctr_threshold_del(struct ctr_threshold *del_ctr)
{
struct ctr_threshold *ctr;
llist_for_each_entry(ctr, &threshold_list, list) {
if (ctr->intv != del_ctr->intv ||
ctr->ctr_id != del_ctr->ctr_id ||
ctr->val != del_ctr->val)
continue;
LOGC(DMAIN, NOTICE) << "Deleting threshold check: " << ctr_threshold_2_vty_str(del_ctr);
llist_del(&ctr->list);
talloc_free(ctr);
threshold_timer_update_intv();
return 0;
}
return -1;
}
void trx_rate_ctr_threshold_write_config(struct vty *vty, char *indent_prefix)
{
struct ctr_threshold *ctr;
llist_for_each_entry(ctr, &threshold_list, list) {
vty_out(vty, "%s%s%s", indent_prefix, ctr_threshold_2_vty_str(ctr), VTY_NEWLINE);
}
}

View File

@@ -1,30 +0,0 @@
#pragma once
#include <osmocom/core/rate_ctr.h>
#include <osmocom/vty/command.h>
enum TrxCtr {
TRX_CTR_RX_OVERRUNS,
TRX_CTR_TX_UNDERRUNS,
TRX_CTR_RX_DROP_EV,
TRX_CTR_RX_DROP_SMPL,
TRX_CTR_TX_DROP_EV,
TRX_CTR_TX_DROP_SMPL,
};
struct ctr_threshold {
/*! Linked list of all counter groups in the system */
struct llist_head list;
enum rate_ctr_intv intv;
enum TrxCtr ctr_id;
uint32_t val;
};
extern const struct value_string rate_ctr_intv[];
extern const struct value_string trx_chan_ctr_names[];
struct trx_ctx;
void trx_rate_ctr_init(void *ctx, struct trx_ctx* trx_ctx);
void trx_rate_ctr_threshold_add(struct ctr_threshold *ctr);
int trx_rate_ctr_threshold_del(struct ctr_threshold *del_ctr);
void trx_rate_ctr_threshold_write_config(struct vty *vty, char *indent_prefix);

View File

@@ -1,24 +1,20 @@
/*
* Copyright (C) 2018-2019 sysmocom - s.f.m.c. GmbH
* Copyright (C) 2012-2017 sysmocom - s.f.m.c. GmbH
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Pau Espin Pedrol <pespin@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
* 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 Affero General Public License for more details.
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* You should have received a copy of the GNU 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 <string.h>
@@ -35,7 +31,6 @@
#include <osmocom/vty/vty.h>
#include <osmocom/vty/misc.h>
#include "trx_rate_ctr.h"
#include "trx_vty.h"
#include "../config.h"
@@ -44,7 +39,7 @@ static struct trx_ctx* g_trx_ctx;
static const struct value_string clock_ref_names[] = {
{ REF_INTERNAL, "internal" },
{ REF_EXTERNAL, "external" },
{ REF_GPS, "gpsdo" },
{ REF_GPS, "gspdo" },
{ 0, NULL }
};
@@ -241,16 +236,12 @@ DEFUN(cfg_multi_arfcn, cfg_multi_arfcn_cmd,
if (strcmp("disable", argv[0]) == 0) {
trx->cfg.multi_arfcn = false;
return CMD_SUCCESS;
}
if (trx->cfg.num_chans > TRX_MCHAN_MAX) {
vty_out(vty, "Up to %i channels are supported for multi-TRX mode%s",
TRX_MCHAN_MAX, VTY_NEWLINE);
} else if (strcmp("enable", argv[0]) == 0) {
trx->cfg.multi_arfcn = true;
} else {
return CMD_WARNING;
}
trx->cfg.multi_arfcn = true;
return CMD_SUCCESS;
}
@@ -313,21 +304,6 @@ DEFUN(cfg_egprs, cfg_egprs_cmd,
return CMD_SUCCESS;
}
DEFUN(cfg_ext_rach, cfg_ext_rach_cmd,
"ext-rach (disable|enable)",
"Enable extended (11-bit) RACH (default=disable)\n")
{
struct trx_ctx *trx = trx_from_vty(vty);
if (strcmp("disable", argv[0]) == 0)
trx->cfg.ext_rach = false;
if (strcmp("enable", argv[0]) == 0)
trx->cfg.ext_rach = true;
return CMD_SUCCESS;
}
DEFUN(cfg_rt_prio, cfg_rt_prio_cmd,
"rt-prio <1-32>",
"Set the SCHED_RR real-time priority\n"
@@ -340,18 +316,6 @@ DEFUN(cfg_rt_prio, cfg_rt_prio_cmd,
return CMD_SUCCESS;
}
DEFUN(cfg_stack_size, cfg_stack_size_cmd,
"stack-size <0-2147483647>",
"Set the stack size per thread in BYTE, 0 = OS default\n"
"Stack size per thread in BYTE\n")
{
struct trx_ctx *trx = trx_from_vty(vty);
trx->cfg.stack_size = atoi(argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_filler, cfg_filler_cmd,
"filler dummy",
"Enable C0 filler table\n"
@@ -364,108 +328,6 @@ DEFUN(cfg_filler, cfg_filler_cmd,
return CMD_SUCCESS;
}
static int vty_ctr_name_2_id(const char* str) {
size_t i;
for (i = 0; trx_chan_ctr_names[i].str; i++) {
if (strstr(trx_chan_ctr_names[i].str, str)) {
return i;
}
}
return -1;
}
static int vty_intv_name_2_id(const char* str) {
size_t i;
for (i = 0; rate_ctr_intv[i].str; i++) {
if (strcmp(rate_ctr_intv[i].str, str) == 0) {
return i;
}
}
return -1;
}
#define THRESHOLD_ARGS "(rx_overruns|tx_underruns|rx_drop_events|rx_drop_samples|tx_drop_events|tx_drop_samples)"
#define THRESHOLD_STR_VAL(s) "Set threshold value for rate_ctr device:" OSMO_STRINGIFY_VAL(s) "\n"
#define THRESHOLD_STRS \
THRESHOLD_STR_VAL(rx_overruns) \
THRESHOLD_STR_VAL(tx_underruns) \
THRESHOLD_STR_VAL(rx_drop_events) \
THRESHOLD_STR_VAL(rx_drop_samples) \
THRESHOLD_STR_VAL(tx_drop_events) \
THRESHOLD_STR_VAL(tx_drop_samples)
#define INTV_ARGS "(per-second|per-minute|per-hour|per-day)"
#define INTV_STR_VAL(s) "Threshold value sampled " OSMO_STRINGIFY_VAL(s) "\n"
#define INTV_STRS \
INTV_STR_VAL(per-second) \
INTV_STR_VAL(per-minute) \
INTV_STR_VAL(per-hour) \
INTV_STR_VAL(per-day)
DEFUN(cfg_ctr_error_threshold, cfg_ctr_error_threshold_cmd,
"ctr-error-threshold " THRESHOLD_ARGS " <0-65535> " INTV_ARGS,
"Threshold rate for error counter\n"
THRESHOLD_STRS
"Value to set for threshold\n"
INTV_STRS)
{
int rc;
struct ctr_threshold ctr;
struct trx_ctx *trx = trx_from_vty(vty);
rc = vty_ctr_name_2_id(argv[0]);
if (rc < 0) {
vty_out(vty, "No valid ctr_name found for ctr-error-threshold %s%s",
argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
ctr.ctr_id = (enum TrxCtr)rc;
ctr.val = atoi(argv[1]);
rc = vty_intv_name_2_id(argv[2]);
if (rc < 0) {
vty_out(vty, "No valid time frame found for ctr-error-threshold %s %d %s%s",
argv[0], ctr.val, argv[2], VTY_NEWLINE);
return CMD_WARNING;
}
ctr.intv = (enum rate_ctr_intv) rc;
trx_rate_ctr_threshold_add(&ctr);
return CMD_SUCCESS;
}
DEFUN(cfg_no_ctr_error_threshold, cfg_no_ctr_error_threshold_cmd,
"no ctr-error-threshold " THRESHOLD_ARGS " <0-65535> " INTV_ARGS,
NO_STR "Threshold rate for error counter\n"
THRESHOLD_STRS
"Value to set for threshold\n"
INTV_STRS)
{
int rc;
struct ctr_threshold ctr;
struct trx_ctx *trx = trx_from_vty(vty);
rc = vty_ctr_name_2_id(argv[0]);
if (rc < 0) {
vty_out(vty, "No valid ctr_name found for ctr-error-threshold %s%s",
argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
ctr.ctr_id = (enum TrxCtr)rc;
ctr.val = atoi(argv[1]);
rc = vty_intv_name_2_id(argv[2]);
if (rc < 0) {
vty_out(vty, "No valid time frame found for ctr-error-threshold %s %d %s%s",
argv[0], ctr.val, argv[2], VTY_NEWLINE);
return CMD_WARNING;
}
ctr.intv = (enum rate_ctr_intv) rc;
if (trx_rate_ctr_threshold_del(&ctr) < 0) {
vty_out(vty, "no ctr-error-threshold: Entry to delete not found%s", VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
DEFUN(cfg_chan, cfg_chan_cmd,
"chan <0-100>",
"Select a channel to configure\n"
@@ -477,12 +339,7 @@ DEFUN(cfg_chan, cfg_chan_cmd,
if (idx >= TRX_CHAN_MAX) {
vty_out(vty, "Chan list full.%s", VTY_NEWLINE);
return CMD_WARNING;
} else if (trx->cfg.multi_arfcn && trx->cfg.num_chans >= TRX_MCHAN_MAX) {
vty_out(vty, "Up to %i channels are supported for multi-TRX mode%s",
TRX_MCHAN_MAX, VTY_NEWLINE);
return CMD_WARNING;
}
if (trx->cfg.num_chans < idx) { /* Unexisting or creating non-consecutive */
vty_out(vty, "Non-existent or non-consecutive chan %d.%s",
idx, VTY_NEWLINE);
@@ -560,20 +417,16 @@ static int config_write_trx(struct vty *vty)
vty_out(vty, " rssi-offset %f%s", trx->cfg.rssi_offset, VTY_NEWLINE);
vty_out(vty, " swap-channels %s%s", trx->cfg.swap_channels ? "enable" : "disable", VTY_NEWLINE);
vty_out(vty, " egprs %s%s", trx->cfg.egprs ? "enable" : "disable", VTY_NEWLINE);
vty_out(vty, " ext-rach %s%s", trx->cfg.ext_rach ? "enable" : "disable", VTY_NEWLINE);
if (trx->cfg.sched_rr != 0)
vty_out(vty, " rt-prio %u%s", trx->cfg.sched_rr, VTY_NEWLINE);
if (trx->cfg.stack_size != 0)
vty_out(vty, " stack-size %u%s", trx->cfg.stack_size, VTY_NEWLINE);
trx_rate_ctr_threshold_write_config(vty, " ");
for (i = 0; i < trx->cfg.num_chans; i++) {
chan = &trx->cfg.chans[i];
vty_out(vty, " chan %u%s", chan->idx, VTY_NEWLINE);
if (chan->rx_path)
vty_out(vty, " rx-path %s%s", chan->rx_path, VTY_NEWLINE);
vty_out(vty, " rx-path %s%s", chan->rx_path, VTY_NEWLINE);
if (chan->tx_path)
vty_out(vty, " tx-path %s%s", chan->tx_path, VTY_NEWLINE);
vty_out(vty, " tx-path %s%s", chan->tx_path, VTY_NEWLINE);
}
return CMD_SUCCESS;
@@ -601,10 +454,8 @@ static void trx_dump_vty(struct vty *vty, struct trx_ctx *trx)
vty_out(vty, " RSSI to dBm offset: %f%s", trx->cfg.rssi_offset, VTY_NEWLINE);
vty_out(vty, " Swap channels: %s%s", trx->cfg.swap_channels ? "Enabled" : "Disabled", VTY_NEWLINE);
vty_out(vty, " EDGE support: %s%s", trx->cfg.egprs ? "Enabled" : "Disabled", VTY_NEWLINE);
vty_out(vty, " Extended RACH support: %s%s", trx->cfg.ext_rach ? "Enabled" : "Disabled", VTY_NEWLINE);
vty_out(vty, " Real Time Priority: %u (%s)%s", trx->cfg.sched_rr,
trx->cfg.sched_rr ? "Enabled" : "Disabled", VTY_NEWLINE);
vty_out(vty, " Stack size per Thread in BYTE (0 = OS default): %u%s", trx->cfg.stack_size, VTY_NEWLINE);
vty_out(vty, " Channels: %u%s", trx->cfg.num_chans, VTY_NEWLINE);
for (i = 0; i < trx->cfg.num_chans; i++) {
chan = &trx->cfg.chans[i];
@@ -713,12 +564,8 @@ int trx_vty_init(struct trx_ctx* trx)
install_element(TRX_NODE, &cfg_rssi_offset_cmd);
install_element(TRX_NODE, &cfg_swap_channels_cmd);
install_element(TRX_NODE, &cfg_egprs_cmd);
install_element(TRX_NODE, &cfg_ext_rach_cmd);
install_element(TRX_NODE, &cfg_rt_prio_cmd);
install_element(TRX_NODE, &cfg_filler_cmd);
install_element(TRX_NODE, &cfg_ctr_error_threshold_cmd);
install_element(TRX_NODE, &cfg_no_ctr_error_threshold_cmd);
install_element(TRX_NODE, &cfg_stack_size_cmd);
install_element(TRX_NODE, &cfg_chan_cmd);
install_node(&chan_node, dummy_config_write);

View File

@@ -6,10 +6,7 @@
extern struct vty_app_info g_vty_info;
/* Maximum number of physical RF channels */
#define TRX_CHAN_MAX 8
/* Maximum number of carriers in multi-ARFCN mode */
#define TRX_MCHAN_MAX 3
/* Samples-per-symbol for downlink path
* 4 - Uses precision modulator (more computation, less distortion)
@@ -60,10 +57,8 @@ struct trx_ctx {
double offset;
double rssi_offset;
bool swap_channels;
bool ext_rach;
bool egprs;
unsigned int sched_rr;
unsigned int stack_size;
unsigned int num_chans;
struct trx_chan chans[TRX_CHAN_MAX];
} cfg;

View File

@@ -2,8 +2,6 @@
* Copyright 2008 Free Software Foundation, Inc.
* Copyright 2011 Range Networks, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
@@ -56,10 +54,7 @@ const BitVector GSM::gEdgeTrainingSequence[] = {
const BitVector GSM::gDummyBurst("0001111101101110110000010100100111000001001000100000001111100011100010111000101110001010111010010100011001100111001111010011111000100101111101010000");
/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */
const BitVector GSM::gRACHSynchSequenceTS0("01001011011111111001100110101010001111000"); /* GSM, GMSK (default) */
const BitVector GSM::gRACHSynchSequenceTS1("01010100111110001000011000101111001001101"); /* EGPRS, 8-PSK */
const BitVector GSM::gRACHSynchSequenceTS2("11101111001001110101011000001101101110111"); /* EGPRS, GMSK */
const BitVector GSM::gRACHSynchSequence("01001011011111111001100110101010001111000");
// |-head-||---------midamble----------------------||--------------data----------------||t|
const BitVector GSM::gRACHBurst("0011101001001011011111111001100110101010001111000110111101111110000111001001010110011000");

View File

@@ -2,8 +2,6 @@
/*
* Copyright 2008-2011 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
@@ -54,9 +52,7 @@ extern const BitVector gEdgeTrainingSequence[];
extern const BitVector gDummyBurst;
/** Random access burst synch. sequence */
extern const BitVector gRACHSynchSequenceTS0;
extern const BitVector gRACHSynchSequenceTS1;
extern const BitVector gRACHSynchSequenceTS2;
extern const BitVector gRACHSynchSequence;
/** Random access burst synch. sequence, GSM 05.02 5.2.7 */
extern const BitVector gRACHBurst;
@@ -168,7 +164,7 @@ class Time {
unsigned newTN = (mTN + other.mTN) % 8;
uint64_t newFN = (mFN+other.mFN + (mTN + other.mTN)/8) % gHyperframe;
return Time(newFN,newTN);
}
}
int operator-(const Time& other) const
{

19
INSTALLATION Normal file
View File

@@ -0,0 +1,19 @@
Installation Requirements
osmo-trx compiles to a simple Unix binary and does not require special
installation.
One some systems (Ubuntu), you will need to define LIBS = -lpthread prior to
running configure.
To run osmo-trx, the following should be installed:
libuhd (https://gnuradio.org).
This is part of the GNURadio installation.
For information on specific executables, see tests/README.tests and
apps/README.apps.
See https://osmocom.org/projects/osmotrx/wiki/OsmoTRX for more
information.

View File

@@ -28,20 +28,17 @@ AM_CXXFLAGS = -Wall -pthread
# Order must be preserved
SUBDIRS = \
doc \
CommonLibs \
GSM \
Transceiver52M \
contrib \
tests
EXTRA_DIST = \
autogen.sh \
INSTALLATION \
LEGAL \
COPYING \
README.md
AM_DISTCHECK_CONFIGURE_FLAGS = \
--with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
README
.PHONY: release

0
NEWS Normal file
View File

116
README Normal file
View File

@@ -0,0 +1,116 @@
This is the interface to the transcevier.
Each TRX Manager UDP socket interface represents a single ARFCN.
Each of these per-ARFCN interfaces is a pair of UDP sockets, one for control and one for data.
Give a base port B (5700), the master clock interface is at port P=B.
The TRX-side control interface for C(N) is on port P=B+2N+1 and the data interface is on an odd numbered port P=B+2N+2.
The corresponding core-side interface for every socket is at P+100.
For any given build, the number of ARFCN interfaces can be fixed.
Indications on the Master Clock Interface
The master clock interface is output only (from the radio).
Messages are "indications".
CLOCK gives the current value of the transceiver clock to be used by the core.
This message is sent whenever a trasmission packet arrives that is too late or too early. The clock value is NOT the current transceiver time. It is a time setting the the core should use to give better packet arrival times.
IND CLOCK <totalFrames>
Commands on the Per-ARFCN Control Interface
The per-ARFCN control interface uses a command-reponse protocol.
Commands are NULL-terminated ASCII strings, one per UDP socket.
Each command has a corresponding response.
Every command is of the form:
CMD <cmdtype> [params]
The <cmdtype> is the actual command.
Parameters are optional depending on the commands type.
Every response is of the form:
RSP <cmdtype> <status> [result]
The <status> is 0 for success and a non-zero error code for failure.
Successful responses may include results, depending on the command type.
Power Control
POWEROFF shuts off transmitter power and stops the demodulator.
CMD POWEROFF
RSP POWEROFF <status>
POWERON starts the transmitter and starts the demodulator. Initial power level is very low.
This command fails if the transmitter and receiver are not yet tuned.
This command fails if the transmit or receive frequency creates a conflict with another ARFCN that is already runnng.
If the transceiver is already on, it response with success to this command.
CMD POWERON
RSP POWERON <status>
SETPOWER sets output power in dB wrt full scale.
This command fails if the transmitter and receiver are not running.
CMD SETPOWER <dB>
RSP SETPOWER <status> <dB>
ADJPOWER adjusts power by the given dB step. Response returns resulting power level wrt full scale.
This command fails if the transmitter and receiver are not running.
CMD ADJPOWER <dBStep>
RSP ADJPOWER <status> <dBLevel>
Tuning Control
RXTUNE tunes the receiver to a given frequency in kHz.
This command fails if the receiver is already running.
(To re-tune you stop the radio, re-tune, and restart.)
This command fails if the transmit or receive frequency creates a conflict with another ARFCN that is already runnng.
CMD RXTUNE <kHz>
RSP RXTUNE <status> <kHz>
TXTUNE tunes the transmitter to a given frequency in kHz.
This command fails if the transmitter is already running.
(To re-tune you stop the radio, re-tune, and restart.)
This command fails if the transmit or receive frequency creates a conflict with another ARFCN that is already runnng.
CMD TXTUNE <kHz>
RSP TXTUNE <status> <kHz>
Timeslot Control
SETSLOT sets the format of the uplink timeslots in the ARFCN.
The <timeslot> indicates the timeslot of interest.
The <chantype> indicates the type of channel that occupies the timeslot.
A chantype of zero indicates the timeslot is off.
CMD SETSLOT <timeslot> <chantype>
RSP SETSLOT <status> <timeslot> <chantype>
Messages on the per-ARFCN Data Interface
Messages on the data interface carry one radio burst per UDP message.
Received Data Burst
1 byte timeslot index
4 bytes GSM frame number, big endian
1 byte RSSI in -dBm
2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, big endian
148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1"
Transmit Data Burst
1 byte timeslot index
4 bytes GSM frame number, big endian
1 byte transmit level wrt ARFCN max, -dB (attenuation)
148 bytes output symbol values, 0 & 1

View File

@@ -1,66 +0,0 @@
About OsmTRX
============
OsmoTRX is a software-defined radio transceiver that implements the Layer 1
physical layer of a BTS comprising the following 3GPP specifications:
* TS 05.01 "Physical layer on the radio path"
* TS 05.02 "Multiplexing and Multiple Access on the Radio Path"
* TS 05.04 "Modulation"
* TS 05.10 "Radio subsystem synchronization"
OsmoTRX is based on the transceiver code from the
[OpenBTS](https://osmocom.org/projects/osmobts/wiki/OpenBTS) project, but setup
to operate independently with the purpose of using with non-OpenBTS software and
projects, while still maintaining backwards compatibility with OpenBTS when
possible. Currently there are numerous features contained in OsmoTRX that extend
the functionality of the OpenBTS transceiver. These features include enhanced
support for various embedded platforms - notably ARM - and dual channel
diversity support for the Fairwaves umtrx.
Homepage
--------
The official homepage of the project is
<https://osmocom.org/projects/osmotrx/wiki/OsmoTRX>
GIT Repository
--------------
You can clone from the official osmo-trx.git repository using
git clone git://git.osmocom.org/osmo-trx.git
There is a cgit interface at <http://git.osmocom.org/osmo-trx/>
Documentation
-------------
Doxygen-generated API documentation is generated during the build process, but
also available online for each of the sub-libraries at User Manual for OsmoTRX
can be generated during the build process, and is also available online at
<http://ftp.osmocom.org/docs/latest/osmotrx-usermanual.pdf>.
Mailing List
------------
Discussions related to OsmoTRX are happening on the openbsc@lists.osmocom.org
mailing list, please see <https://lists.osmocom.org/mailman/listinfo/openbsc>
for subscription options and the list archive.
Please observe the [Osmocom Mailing List
Rules](https://osmocom.org/projects/cellular-infrastructure/wiki/Mailing_List_Rules)
when posting.
Contributing
------------
Our coding standards are described at
<https://osmocom.org/projects/cellular-infrastructure/wiki/Coding_standards>
We us a gerrit based patch submission/review process for managing contributions.
Please see <https://osmocom.org/projects/cellular-infrastructure/wiki/Gerrit>
for more details
The current patch queue for OsmoTRX can be seen at
<https://gerrit.osmocom.org/q/project:osmo-trx+status:open>

View File

@@ -1,11 +1,9 @@
/*
* Polyphase channelizer
*
*
* Copyright (C) 2012-2014 Tom Tsou <tom@tsou.cc>
* Copyright (C) 2015 Ettus Research LLC
*
* 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
@@ -65,7 +63,7 @@ float *Channelizer::outputBuffer(size_t chan) const
return hInputs[chan];
}
/*
/*
* Implementation based on material found in:
*
* "harris, fred, Multirate Signal Processing, Upper Saddle River, NJ,
@@ -80,8 +78,8 @@ bool Channelizer::rotate(const float *in, size_t len)
deinterleave(in, len, hInputs, blockLen, m);
/*
* Convolve through filterbank while applying and saving sample history
/*
* Convolve through filterbank while applying and saving sample history
*/
for (size_t i = 0; i < m; i++) {
memcpy(&hInputs[i][2 * -hLen], hist[i], hSize);
@@ -90,7 +88,7 @@ bool Channelizer::rotate(const float *in, size_t len)
convolve_real(hInputs[i], blockLen,
subFilters[i], hLen,
hOutputs[i], blockLen,
0, blockLen);
0, blockLen, 1, 0);
}
cxvec_fft(fftHandle);

View File

@@ -1,10 +1,8 @@
/*
* Polyphase channelizer
*
*
* Copyright (C) 2012-2014 Tom Tsou <tom@tsou.cc>
* Copyright (C) 2015 Ettus Research LLC
*
* SPDX-License-Identifier: AGPL-3.0+
* Copyright (C) 2015 Ettus Research LLC
*
* 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
@@ -57,10 +55,10 @@ static void reverse(float *buf, size_t len)
}
}
/*
/*
* Create polyphase filterbank
*
* Implementation based material found in,
* Implementation based material found in,
*
* "harris, fred, Multirate Signal Processing, Upper Saddle River, NJ,
* Prentice Hall, 2006."
@@ -72,7 +70,7 @@ bool ChannelizerBase::initFilters()
float sum = 0.0f, scale = 0.0f;
float midpt = (float) (protoLen - 1.0) / 2.0;
/*
/*
* Allocate 'M' partition filters and the temporary prototype
* filter. Coefficients are real only and must be 16-byte memory
* aligned for SSE usage.
@@ -92,7 +90,7 @@ bool ChannelizerBase::initFilters()
memalign(16, hLen * 2 * sizeof(float));
}
/*
/*
* Generate the prototype filter with a Blackman-harris window.
* Scale coefficients with DC filter gain set to unity divided
* by the number of channels.
@@ -112,7 +110,7 @@ bool ChannelizerBase::initFilters()
}
scale = (float) m / sum;
/*
/*
* Populate partition filters and reverse the coefficients per
* convolution requirements.
*/
@@ -176,7 +174,7 @@ bool ChannelizerBase::mapBuffers()
return true;
}
/*
/*
* Setup filterbank internals
*/
bool ChannelizerBase::init()
@@ -224,12 +222,11 @@ bool ChannelizerBase::checkLen(size_t innerLen, size_t outerLen)
return true;
}
/*
/*
* Setup channelizer paramaters
*/
ChannelizerBase::ChannelizerBase(size_t m, size_t blockLen, size_t hLen)
: subFilters(NULL), hInputs(NULL), hOutputs(NULL), hist(NULL),
fftInput(NULL), fftOutput(NULL), fftHandle(NULL)
: fftInput(NULL), fftOutput(NULL), fftHandle(NULL)
{
this->m = m;
this->hLen = hLen;
@@ -242,7 +239,7 @@ ChannelizerBase::~ChannelizerBase()
for (size_t i = 0; i < m; i++) {
free(subFilters[i]);
delete[] hist[i];
delete hist[i];
}
fft_free(fftInput);

View File

@@ -23,9 +23,16 @@ include $(top_srcdir)/Makefile.common
SUBDIRS = arch device
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/arch/common -I${srcdir}/device/common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/arch/common -I${srcdir}/device
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
rev2dir = $(datadir)/usrp/rev2
rev4dir = $(datadir)/usrp/rev4
dist_rev2_DATA = std_inband.rbf
dist_rev4_DATA = std_inband.rbf
EXTRA_DIST = README
noinst_LTLIBRARIES = libtransceiver_common.la
@@ -39,8 +46,7 @@ COMMON_SOURCES = \
Transceiver.cpp \
ChannelizerBase.cpp \
Channelizer.cpp \
Synthesis.cpp \
proto_trxd.c
Synthesis.cpp
libtransceiver_common_la_SOURCES = \
$(COMMON_SOURCES) \
@@ -60,8 +66,7 @@ noinst_HEADERS = \
Resampler.h \
ChannelizerBase.h \
Channelizer.h \
Synthesis.h \
proto_trxd.h
Synthesis.h
COMMON_LDADD = \
libtransceiver_common.la \

35
Transceiver52M/README Normal file
View File

@@ -0,0 +1,35 @@
The Transceiver
The transceiver consists of three modules:
--- transceiver
--- radioInterface
--- USRPDevice
The USRPDevice module is basically a driver that reads/writes
packets to a USRP with two RFX900 daughterboards, board
A is the Tx chain and board B is the Rx chain.
The radioInterface module is basically an interface b/w the
transceiver and the USRP. It operates the basestation clock
based upon the sample count of received USRP samples. Packets
from the USRP are queued and segmented into GSM bursts that are
passed up to the transceiver; bursts from the transceiver are
passed down to the USRP.
The transceiver basically operates "layer 0" of the GSM stack,
performing the modulation, detection, and demodulation of GSM
bursts. It communicates with the GSM stack via three UDP sockets,
one socket for data, one for control messages, and one socket to
pass clocking information. The transceiver contains a priority
queue to sort to-be-transmitted bursts, and a filler table to fill
in timeslots that do not have bursts in the priority queue. The
transceiver tries to stay ahead of the basestation clock, adapting
its latency when underruns are reported by the radioInterface/USRP.
Received bursts (from the radioInterface) pass through a simple
energy detector, a RACH or midamble correlator, and a DFE-based demodulator.
NOTE: There's a SWLOOPBACK #define statement, where the USRP is replaced
with a memory buffer. In this mode, data written to the USRP is actually stored
in a buffer, and read commands to the USRP simply pull data from this buffer.
This was very useful in early testing, and still may be useful in testing basic
Transceiver and radioInterface functionality.

View File

@@ -2,8 +2,6 @@
* Rational Sample Rate Conversion
* Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
@@ -53,7 +51,7 @@ void Resampler::initFilters(float bw)
float cutoff;
float sum = 0.0f, scale = 0.0f;
/*
/*
* Allocate partition filters and the temporary prototype filter
* according to numerator of the rational rate. Coefficients are
* real only and must be 16-byte memory aligned for SSE usage.
@@ -62,10 +60,10 @@ void Resampler::initFilters(float bw)
for (auto &part : partitions)
part = (complex<float> *) memalign(16, filt_len * sizeof(complex<float>));
/*
/*
* Generate the prototype filter with a Blackman-harris window.
* Scale coefficients with DC filter gain set to unity divided
* by the number of filter partitions.
* by the number of filter partitions.
*/
float a0 = 0.35875;
float a1 = 0.48829;
@@ -139,13 +137,13 @@ int Resampler::rotate(const float *in, size_t in_len, float *out, size_t out_len
/* Generate output from precomputed input/output paths */
for (size_t i = 0; i < out_len; i++) {
n = in_index[i];
path = out_path[i];
n = in_index[i];
path = out_path[i];
convolve_real(in, in_len,
reinterpret_cast<float *>(partitions[path]),
filt_len, &out[2 * i], out_len - i,
n, 1);
n, 1, 1, 0);
}
return out_len;

View File

@@ -2,8 +2,6 @@
* Rational Sample Rate Conversion
* Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
@@ -30,7 +28,7 @@ public:
/* Constructor for rational sample rate conversion
* @param p numerator of resampling ratio
* @param q denominator of resampling ratio
* @param filt_len length of each polyphase subfilter
* @param filt_len length of each polyphase subfilter
*/
Resampler(size_t p, size_t q, size_t filt_len = 16);
~Resampler();
@@ -60,7 +58,7 @@ public:
int rotate(const float *in, size_t in_len, float *out, size_t out_len);
/* Get filter length
* @return number of taps in each filter partition
* @return number of taps in each filter partition
*/
size_t len();

View File

@@ -1,11 +1,9 @@
/*
* Polyphase synthesis filter
*
*
* Copyright (C) 2012-2014 Tom Tsou <tom@tsou.cc>
* Copyright (C) 2015 Ettus Research LLC
*
* 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
@@ -76,7 +74,7 @@ bool Synthesis::resetBuffer(size_t chan)
return true;
}
/*
/*
* Implementation based on material found in:
*
* "harris, fred, Multirate Signal Processing, Upper Saddle River, NJ,
@@ -94,8 +92,8 @@ bool Synthesis::rotate(float *out, size_t len)
cxvec_fft(fftHandle);
/*
* Convolve through filterbank while applying and saving sample history
/*
* Convolve through filterbank while applying and saving sample history
*/
for (size_t i = 0; i < m; i++) {
memcpy(&hInputs[i][2 * -hLen], hist[i], hSize);
@@ -104,7 +102,7 @@ bool Synthesis::rotate(float *out, size_t len)
convolve_real(hInputs[i], blockLen,
subFilters[i], hLen,
hOutputs[i], blockLen,
0, blockLen);
0, blockLen, 1, 0);
}
/* Interleave into output vector */

View File

@@ -1,8 +1,6 @@
/*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: GPL-3.0+
*
* This software is distributed under the terms of the GNU Public License.
* See the COPYING file in the main directory for details.
*
@@ -24,21 +22,11 @@
*/
#include <stdio.h>
#include <netinet/in.h>
#include <iomanip> // std::setprecision
#include <fstream>
#include "Transceiver.h"
#include <Logger.h>
extern "C" {
#include "osmo_signal.h"
#include "proto_trxd.h"
#include <osmocom/core/utils.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/bits.h>
}
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
@@ -123,12 +111,12 @@ Transceiver::Transceiver(int wBasePort,
size_t tx_sps, size_t rx_sps, size_t chans,
GSM::Time wTransmitLatency,
RadioInterface *wRadioInterface,
double wRssiOffset, int wStackSize)
double wRssiOffset)
: 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),
mClockSocket(TRXAddress, wBasePort, GSMcoreAddress, wBasePort + 100),
mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface),
rssiOffset(wRssiOffset),
mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mEdge(false), mOn(false), mForceClockInterface(false),
mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0), mMaxExpectedDelayNB(0),
mWriteBurstToDiskMask(0)
{
@@ -147,19 +135,14 @@ Transceiver::~Transceiver()
sigProcLibDestroy();
if (mClockSocket >= 0)
close(mClockSocket);
for (size_t i = 0; i < mChans; 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]);
delete mCtrlSockets[i];
delete mDataSockets[i];
}
}
@@ -172,8 +155,7 @@ 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(FillerType filler, size_t rtsc, unsigned rach_delay, bool edge)
{
int d_srcport, d_dstport, c_srcport, c_dstport;
@@ -187,11 +169,10 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay,
return false;
}
mExtRACH = ext_rach;
mEdge = edge;
mDataSockets.resize(mChans, -1);
mCtrlSockets.resize(mChans, -1);
mDataSockets.resize(mChans);
mCtrlSockets.resize(mChans);
mControlServiceLoopThreads.resize(mChans);
mTxPriorityQueueServiceLoopThreads.resize(mChans);
mRxServiceLoopThreads.resize(mChans);
@@ -199,37 +180,20 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay,
mTxPriorityQueues.resize(mChans);
mReceiveFIFO.resize(mChans);
mStates.resize(mChans);
mVersionTRXD.resize(mChans);
/* Filler table retransmissions - support only on channel 0 */
if (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,
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
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,
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
if (mCtrlSockets[i] < 0)
return false;
mDataSockets[i] = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
mLocalAddr.c_str(), d_srcport,
mRemoteAddr.c_str(), d_dstport,
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
if (mCtrlSockets[i] < 0)
return false;
mCtrlSockets[i] = new UDPSocket(mLocalAddr.c_str(), c_srcport, mRemoteAddr.c_str(), c_dstport);
mDataSockets[i] = new UDPSocket(mLocalAddr.c_str(), d_srcport, mRemoteAddr.c_str(), d_dstport);
}
/* Randomize the central clock */
@@ -241,12 +205,10 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay,
/* 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);
TransceiverChannel *chan = new TransceiverChannel(this, i);
mControlServiceLoopThreads[i] = new Thread(32768);
mControlServiceLoopThreads[i]->start((void * (*)(void*))
ControlServiceLoopAdapter, (void*) params);
ControlServiceLoopAdapter, (void*) chan);
if (i && filler == FILLER_DUMMY)
filler = FILLER_ZERO;
@@ -286,8 +248,8 @@ bool Transceiver::start()
}
/* Device is running - launch I/O threads */
mRxLowerLoopThread = new Thread(stackSize);
mTxLowerLoopThread = new Thread(stackSize);
mRxLowerLoopThread = new Thread(32768);
mTxLowerLoopThread = new Thread(32768);
mTxLowerLoopThread->start((void * (*)(void*))
TxLowerLoopAdapter,(void*) this);
mRxLowerLoopThread->start((void * (*)(void*))
@@ -295,19 +257,15 @@ bool Transceiver::start()
/* Launch uplink and downlink burst processing threads */
for (size_t i = 0; i < mChans; i++) {
TrxChanThParams *params = (TrxChanThParams *)malloc(sizeof(struct TrxChanThParams));
params->trx = this;
params->num = i;
mRxServiceLoopThreads[i] = new Thread(stackSize);
TransceiverChannel *chan = new TransceiverChannel(this, i);
mRxServiceLoopThreads[i] = new Thread(32768);
mRxServiceLoopThreads[i]->start((void * (*)(void*))
RxUpperLoopAdapter, (void*) params);
RxUpperLoopAdapter, (void*) chan);
params = (TrxChanThParams *)malloc(sizeof(struct TrxChanThParams));
params->trx = this;
params->num = i;
mTxPriorityQueueServiceLoopThreads[i] = new Thread(stackSize);
chan = new TransceiverChannel(this, i);
mTxPriorityQueueServiceLoopThreads[i] = new Thread(32768);
mTxPriorityQueueServiceLoopThreads[i]->start((void * (*)(void*))
TxUpperLoopAdapter, (void*) params);
TxUpperLoopAdapter, (void*) chan);
}
mForceClockInterface = true;
@@ -415,7 +373,7 @@ void Transceiver::pushRadioVector(GSM::Time &nowTime)
state = &mStates[i];
while ((burst = mTxPriorityQueues[i].getStaleBurst(nowTime))) {
LOGCHAN(i, DMAIN, NOTICE) << "dumping STALE burst in TRX->SDR interface ("
LOG(NOTICE) << "chan " << i << " dumping STALE burst in TRX->USRP interface ("
<< burst->getTime() <<" vs " << nowTime << "), retrans=" << state->mRetrans;
if (state->mRetrans)
updateFillerTable(i, burst);
@@ -524,16 +482,16 @@ CorrType Transceiver::expectedCorrType(GSM::Time currTime,
break;
case IV:
case VI:
return mExtRACH ? EXT_RACH : RACH;
return RACH;
break;
case V: {
int mod51 = burstFN % 51;
if ((mod51 <= 36) && (mod51 >= 14))
return mExtRACH ? EXT_RACH : RACH;
return RACH;
else if ((mod51 == 4) || (mod51 == 5))
return mExtRACH ? EXT_RACH : RACH;
return RACH;
else if ((mod51 == 45) || (mod51 == 46))
return mExtRACH ? EXT_RACH : RACH;
return RACH;
else if (mHandover[burstTN][sdcch4_subslot[burstFN % 102]])
return RACH;
else
@@ -551,7 +509,7 @@ CorrType Transceiver::expectedCorrType(GSM::Time currTime,
case XIII: {
int mod52 = burstFN % 52;
if ((mod52 == 12) || (mod52 == 38))
return mExtRACH ? EXT_RACH : RACH;
return RACH;
else if ((mod52 == 25) || (mod52 == 51))
return IDLE;
else
@@ -584,26 +542,27 @@ void writeToFile(radioVector *radio_burst, size_t chan)
* Pull bursts from the FIFO and handle according to the slot
* and burst correlation type. Equalzation is currently disabled.
*/
bool Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
SoftVector *Transceiver::pullRadioVector(GSM::Time &wTime, double &RSSI, bool &isRssiValid,
double &timingOffset, double &noise,
size_t chan)
{
int rc;
struct estim_burst_params ebp;
float max = -1.0, avg = 0.0;
unsigned max_toa;
complex amp;
float toa, max = -1.0, avg = 0.0;
int max_i = -1;
signalVector *burst;
GSM::Time burstTime;
SoftVector *rxBurst;
SoftVector *bits = NULL;
TransceiverState *state = &mStates[chan];
isRssiValid = false;
/* Blocking FIFO read */
radioVector *radio_burst = mReceiveFIFO[chan]->read();
if (!radio_burst)
return false;
return NULL;
/* Set time and determine correlation type */
burstTime = radio_burst->getTime();
CorrType type = expectedCorrType(burstTime, chan);
GSM::Time time = radio_burst->getTime();
CorrType type = expectedCorrType(time, chan);
/* Enable 8-PSK burst detection if EDGE is enabled */
if (mEdge && (type == TSC))
@@ -612,29 +571,16 @@ bool Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
/* Debug: dump bursts to disk */
/* bits 0-7 - chan 0 timeslots
* bits 8-15 - chan 1 timeslots */
if (mWriteBurstToDiskMask & ((1<<bi->tn) << (8*chan)))
if (mWriteBurstToDiskMask & ((1<<time.TN()) << (8*chan)))
writeToFile(radio_burst, chan);
/* No processing if the timeslot is off.
* Not even power level or noise calculation. */
if (type == OFF) {
delete radio_burst;
return false;
return NULL;
}
/* Initialize struct bi */
bi->nbits = 0;
bi->fn = burstTime.FN();
bi->tn = burstTime.TN();
bi->rssi = 0.0;
bi->toa = 0.0;
bi->noise = 0.0;
bi->idle = false;
bi->modulation = MODULATION_GMSK;
bi->tss = 0; /* TODO: we only support tss 0 right now */
bi->tsc = 0;
bi->ci = 0.0;
/* 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);
@@ -647,64 +593,56 @@ bool Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
if (max_i < 0) {
LOG(ALERT) << "Received empty burst";
goto ret_idle;
delete radio_burst;
return NULL;
}
/* Average noise on diversity paths and update global levels */
burst = radio_burst->getVector(max_i);
avg = sqrt(avg / radio_burst->chans());
wTime = time;
RSSI = 20.0 * log10(rxFullScale / avg);
/* RSSI estimation are valid */
isRssiValid = true;
if (type == IDLE) {
/* Update noise levels */
state->mNoises.insert(avg);
state->mNoiseLev = state->mNoises.avg();
noise = 20.0 * log10(rxFullScale / state->mNoiseLev);
delete radio_burst;
return NULL;
} else {
/* Do not update noise levels */
noise = 20.0 * log10(rxFullScale / state->mNoiseLev);
}
bi->rssi = 20.0 * log10(rxFullScale / avg) + rssiOffset;
bi->noise = 20.0 * log10(rxFullScale / state->mNoiseLev) + rssiOffset;
if (type == IDLE)
goto ret_idle;
max_toa = (type == RACH || type == EXT_RACH) ?
mMaxExpectedDelayAB : mMaxExpectedDelayNB;
/* Detect normal or RACH bursts */
rc = detectAnyBurst(*burst, mTSC, BURST_THRESH, mSPSRx, type, max_toa, &ebp);
if (rc <= 0) {
if (rc == -SIGERR_CLIP)
rc = detectAnyBurst(*burst, mTSC, BURST_THRESH, mSPSRx, type, amp, toa,
(type==RACH)?mMaxExpectedDelayAB:mMaxExpectedDelayNB);
if (rc > 0) {
type = (CorrType) rc;
} else if (rc <= 0) {
if (rc == -SIGERR_CLIP) {
LOG(WARNING) << "Clipping detected on received RACH or Normal Burst";
else if (rc != SIGERR_NONE)
} else if (rc != SIGERR_NONE) {
LOG(WARNING) << "Unhandled RACH or Normal Burst detection error";
goto ret_idle;
}
delete radio_burst;
return NULL;
}
type = (CorrType) rc;
bi->toa = ebp.toa;
bi->tsc = ebp.tsc;
bi->ci = ebp.ci;
rxBurst = demodAnyBurst(*burst, mSPSRx, ebp.amp, ebp.toa, type);
timingOffset = toa;
/* EDGE demodulator returns 444 (gSlotLen * 3) bits */
if (rxBurst->size() == EDGE_BURST_NBITS) {
bi->modulation = MODULATION_8PSK;
bi->nbits = EDGE_BURST_NBITS;
} else { /* size() here is actually gSlotLen + 8, due to guard periods */
bi->modulation = MODULATION_GMSK;
bi->nbits = gSlotLen;
}
bits = demodAnyBurst(*burst, mSPSRx, amp, toa, type);
// Convert -1..+1 soft bits to 0..1 soft bits
vectorSlicer(bi->rx_burst, rxBurst->begin(), bi->nbits);
delete rxBurst;
delete radio_burst;
return true;
ret_idle:
bi->idle = true;
delete radio_burst;
return false;
return bits;
}
void Transceiver::reset()
@@ -753,24 +691,22 @@ void Transceiver::driveControl(size_t chan)
int msgLen;
/* Attempt to read from control socket */
msgLen = read(mCtrlSockets[chan], buffer, MAX_PACKET_LENGTH);
if (msgLen <= 0) {
LOGCHAN(chan, DTRXCTRL, WARNING) << "mCtrlSockets read(" << mCtrlSockets[chan] << ") failed: " << msgLen;
msgLen = mCtrlSockets[chan]->read(buffer, MAX_PACKET_LENGTH);
if (msgLen < 1)
return;
}
/* Zero-terminate received string */
buffer[msgLen] = '\0';
/* Verify a command signature */
if (strncmp(buffer, "CMD ", 4)) {
LOGC(DTRXCTRL, WARNING) << "bogus message on control interface";
LOG(WARNING) << "bogus message on control interface";
return;
}
/* Set command pointer */
command = buffer + 4;
LOGCHAN(chan, DTRXCTRL, INFO) << "command is '" << command << "'";
LOG(INFO) << "command is " << command;
if (match_cmd(command, "POWEROFF", NULL)) {
stop();
@@ -849,7 +785,7 @@ void Transceiver::driveControl(size_t chan)
sscanf(params, "%d", &freqKhz);
mRxFreq = freqKhz * 1e3;
if (!mRadioInterface->tuneRx(mRxFreq, chan)) {
LOGC(DTRXCTRL, ALERT) << "RX failed to tune";
LOG(ALERT) << "RX failed to tune";
sprintf(response,"RSP RXTUNE 1 %d",freqKhz);
}
else
@@ -860,7 +796,7 @@ void Transceiver::driveControl(size_t chan)
sscanf(params, "%d", &freqKhz);
mTxFreq = freqKhz * 1e3;
if (!mRadioInterface->tuneTx(mTxFreq, chan)) {
LOGC(DTRXCTRL, ALERT) << "TX failed to tune";
LOG(ALERT) << "TX failed to tune";
sprintf(response,"RSP TXTUNE 1 %d",freqKhz);
}
else
@@ -872,7 +808,7 @@ void Transceiver::driveControl(size_t chan)
if (TSC > 7) {
sprintf(response, "RSP SETTSC 1 %d", TSC);
} else {
LOGC(DTRXCTRL, NOTICE) << "Changing TSC from " << mTSC << " to " << TSC;
LOG(NOTICE) << "Changing TSC from " << mTSC << " to " << TSC;
mTSC = TSC;
sprintf(response,"RSP SETTSC 0 %d", TSC);
}
@@ -882,27 +818,13 @@ void Transceiver::driveControl(size_t chan)
int timeslot;
sscanf(params, "%d %d", &timeslot, &corrCode);
if ((timeslot < 0) || (timeslot > 7)) {
LOGC(DTRXCTRL, WARNING) << "bogus message on control interface";
LOG(WARNING) << "bogus message on control interface";
sprintf(response,"RSP SETSLOT 1 %d %d",timeslot,corrCode);
return;
}
mStates[chan].chanType[timeslot] = (ChannelCombination) corrCode;
setModulus(timeslot, chan);
sprintf(response,"RSP SETSLOT 0 %d %d",timeslot,corrCode);
} else if (match_cmd(command, "SETFORMAT", &params)) {
// set TRXD protocol version
unsigned version_recv;
sscanf(params, "%u", &version_recv);
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;
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, "_SETBURSTTODISKMASK", &params)) {
// debug command! may change or disapear without notice
// set a mask which bursts to dump to disk
@@ -911,30 +833,20 @@ void Transceiver::driveControl(size_t chan)
mWriteBurstToDiskMask = mask;
sprintf(response,"RSP _SETBURSTTODISKMASK 0 %d",mask);
} else {
LOGC(DTRXCTRL, WARNING) << "bogus command " << command << " on control interface.";
LOG(WARNING) << "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;
mCtrlSockets[chan]->write(response, strlen(response) + 1);
}
bool Transceiver::driveTxPriorityQueue(size_t chan)
{
int msgLen;
int burstLen;
char buffer[EDGE_BURST_NBITS + 50];
struct trxd_hdr_common *chdr;
uint32_t fn;
// check data socket
msgLen = read(mDataSockets[chan], buffer, sizeof(buffer));
if (msgLen <= 0) {
LOGCHAN(chan, DTRXCTRL, WARNING) << "mDataSockets read(" << mCtrlSockets[chan] << ") failed: " << msgLen;
return false;
}
size_t msgLen = mDataSockets[chan]->read(buffer, sizeof(buffer));
if (msgLen == gSlotLen + 1 + 4 + 1) {
burstLen = gSlotLen;
@@ -948,27 +860,12 @@ bool Transceiver::driveTxPriorityQueue(size_t chan)
return false;
}
/* Common header part: HDR version, TDMA TN & FN */
chdr = (struct trxd_hdr_common *) buffer;
int timeSlot = (int) buffer[0];
uint64_t frameNum = 0;
for (int i = 0; i < 4; i++)
frameNum = (frameNum << 8) | (0x0ff & buffer[i+1]);
/* Convert TDMA FN to the host endianness */
fn = osmo_load32be(&chdr->fn);
/* Make sure we support the received header format */
switch (chdr->version) {
case 0:
/* Version 1 has the same format */
case 1:
break;
default:
LOG(ERR) << "Rx TRXD message with unknown header version " << chdr->version;
return false;
}
LOG(DEBUG) << "Rx TRXD message (hdr_ver=" << chdr->version << "): "
<< "fn=" << fn << ", tn=" << chdr->tn << ", "
<< "burst_len=" << burstLen;
LOG(DEBUG) << "rcvd. burst at: " << GSM::Time(frameNum,timeSlot);
int RSSI = (int) buffer[5];
BitVector newBurst(burstLen);
@@ -977,7 +874,7 @@ bool Transceiver::driveTxPriorityQueue(size_t chan)
while (itr < newBurst.end())
*itr++ = *bufferItr++;
GSM::Time currTime = GSM::Time(fn, chdr->tn);
GSM::Time currTime = GSM::Time(frameNum,timeSlot);
addRadioVector(chan, newBurst, RSSI, currTime);
@@ -988,59 +885,74 @@ bool Transceiver::driveTxPriorityQueue(size_t chan)
void Transceiver::driveReceiveRadio()
{
int rc = mRadioInterface->driveReceiveRadio();
if (rc == 0) {
if (!mRadioInterface->driveReceiveRadio()) {
usleep(100000);
} else if (rc < 0) {
LOG(FATAL) << "radio Interface receive failed, requesting stop.";
osmo_signal_dispatch(SS_MAIN, S_MAIN_STOP_REQUIRED, NULL);
} else if (mForceClockInterface || mTransmitDeadlineClock > mLastClockUpdateTime + GSM::Time(216,0)) {
mForceClockInterface = false;
writeClockInterface();
}
}
void Transceiver::logRxBurst(size_t chan, const struct trx_ul_burst_ind *bi)
void Transceiver::logRxBurst(size_t chan, SoftVector *burst, GSM::Time time, double dbm,
double rssi, double noise, double toa)
{
std::ostringstream os;
for (size_t i=0; i < bi->nbits; i++) {
if (bi->rx_burst[i] > 0.5) os << "1";
else if (bi->rx_burst[i] > 0.25) os << "|";
else if (bi->rx_burst[i] > 0.0) os << "'";
else os << "-";
}
LOG(DEBUG) << std::fixed << std::right
<< " chan: " << chan
<< " time: " << bi->tn << ":" << bi->fn
<< " RSSI: " << std::setw(5) << std::setprecision(1) << (bi->rssi - rssiOffset)
<< "dBFS/" << std::setw(6) << -bi->rssi << "dBm"
<< " noise: " << std::setw(5) << std::setprecision(1) << (bi->noise - rssiOffset)
<< "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"
<< " bits: " << os;
<< " time: " << time
<< " RSSI: " << std::setw(5) << std::setprecision(1) << rssi
<< "dBFS/" << std::setw(6) << -dbm << "dBm"
<< " noise: " << std::setw(5) << std::setprecision(1) << noise
<< "dBFS/" << std::setw(6) << -(noise + rssiOffset) << "dBm"
<< " TOA: " << std::setw(5) << std::setprecision(2) << toa
<< " bits: " << *burst;
}
void Transceiver::driveReceiveFIFO(size_t chan)
{
struct trx_ul_burst_ind bi;
SoftVector *rxBurst = NULL;
double RSSI; // in dBFS
double dBm; // in dBm
double TOA; // in symbols
int TOAint; // in 1/256 symbols
double noise; // noise level in dBFS
GSM::Time burstTime;
bool isRssiValid; // are RSSI, noise and burstTime valid
unsigned nbits = gSlotLen;
if (!pullRadioVector(chan, &bi))
return;
if (!bi.idle)
logRxBurst(chan, &bi);
rxBurst = pullRadioVector(burstTime, RSSI, isRssiValid, TOA, noise, chan);
if (!rxBurst)
return;
switch (mVersionTRXD[chan]) {
case 0:
trxd_send_burst_ind_v0(chan, mDataSockets[chan], &bi);
break;
case 1:
trxd_send_burst_ind_v1(chan, mDataSockets[chan], &bi);
break;
default:
OSMO_ASSERT(false);
}
// Convert -1..+1 soft bits to 0..1 soft bits
vectorSlicer(rxBurst);
/*
* EDGE demodulator returns 444 (148 * 3) bits
*/
if (rxBurst->size() == gSlotLen * 3)
nbits = gSlotLen * 3;
dBm = RSSI + rssiOffset;
logRxBurst(chan, rxBurst, burstTime, dBm, RSSI, noise, TOA);
TOAint = (int) (TOA * 256.0 + 0.5); // round to closest integer
char burstString[nbits + 10];
burstString[0] = burstTime.TN();
for (int i = 0; i < 4; i++)
burstString[1+i] = (burstTime.FN() >> ((3-i)*8)) & 0x0ff;
burstString[5] = (int)dBm;
burstString[6] = (TOAint >> 8) & 0x0ff;
burstString[7] = TOAint & 0x0ff;
SoftVector::iterator burstItr = rxBurst->begin();
for (unsigned i = 0; i < nbits; i++)
burstString[8 + i] = (char) round((*burstItr++) * 255.0);
burstString[nbits + 9] = '\0';
delete rxBurst;
mDataSockets[chan]->write(burstString, nbits + 10);
}
void Transceiver::driveTxFIFO()
@@ -1070,8 +982,7 @@ 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) << ")";
LOG(INFO) << "new latency: " << mTransmitLatency;
mLatencyUpdateTime = radioClock->get();
}
}
@@ -1100,31 +1011,24 @@ void Transceiver::driveTxFIFO()
void Transceiver::writeClockInterface()
{
int msgLen;
char command[50];
// FIXME -- This should be adaptive.
sprintf(command,"IND CLOCK %llu",(unsigned long long) (mTransmitDeadlineClock.FN()+2));
LOG(INFO) << "ClockInterface: sending " << command;
msgLen = write(mClockSocket, command, strlen(command) + 1);
if (msgLen <= 0)
LOG(WARNING) << "mClockSocket write(" << mClockSocket << ") failed: " << msgLen;
mClockSocket.write(command, strlen(command) + 1);
mLastClockUpdateTime = mTransmitDeadlineClock;
}
void *RxUpperLoopAdapter(TrxChanThParams *params)
void *RxUpperLoopAdapter(TransceiverChannel *chan)
{
char thread_name[16];
Transceiver *trx = params->trx;
size_t num = params->num;
Transceiver *trx = chan->trx;
size_t num = chan->num;
free(params);
snprintf(thread_name, 16, "RxUpper%zu", num);
set_selfthread_name(thread_name);
delete chan;
trx->setPriority(0.42);
@@ -1137,8 +1041,6 @@ void *RxUpperLoopAdapter(TrxChanThParams *params)
void *RxLowerLoopAdapter(Transceiver *transceiver)
{
set_selfthread_name("RxLower");
transceiver->setPriority(0.45);
while (1) {
@@ -1150,8 +1052,6 @@ void *RxLowerLoopAdapter(Transceiver *transceiver)
void *TxLowerLoopAdapter(Transceiver *transceiver)
{
set_selfthread_name("TxLower");
transceiver->setPriority(0.44);
while (1) {
@@ -1161,16 +1061,12 @@ void *TxLowerLoopAdapter(Transceiver *transceiver)
return NULL;
}
void *ControlServiceLoopAdapter(TrxChanThParams *params)
void *ControlServiceLoopAdapter(TransceiverChannel *chan)
{
char thread_name[16];
Transceiver *trx = params->trx;
size_t num = params->num;
Transceiver *trx = chan->trx;
size_t num = chan->num;
free(params);
snprintf(thread_name, 16, "CtrlService%zu", num);
set_selfthread_name(thread_name);
delete chan;
while (1) {
trx->driveControl(num);
@@ -1179,16 +1075,12 @@ void *ControlServiceLoopAdapter(TrxChanThParams *params)
return NULL;
}
void *TxUpperLoopAdapter(TrxChanThParams *params)
void *TxUpperLoopAdapter(TransceiverChannel *chan)
{
char thread_name[16];
Transceiver *trx = params->trx;
size_t num = params->num;
Transceiver *trx = chan->trx;
size_t num = chan->num;
free(params);
snprintf(thread_name, 16, "TxUpper%zu", num);
set_selfthread_name(thread_name);
delete chan;
trx->setPriority(0.40);

View File

@@ -1,8 +1,6 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: GPL-3.0+
*
* This software is distributed under the terms of the GNU Public License.
* See the COPYING file in the main directory for details.
*
@@ -27,21 +25,31 @@
#include "radioInterface.h"
#include "Interthread.h"
#include "GSMCommon.h"
#include "Sockets.h"
#include <sys/types.h>
#include <sys/socket.h>
extern "C" {
#include <osmocom/core/signal.h>
#include "config_defs.h"
}
class Transceiver;
/** Channel descriptor for transceiver object and channel number pair */
struct TrxChanThParams {
Transceiver *trx;
size_t num;
struct TransceiverChannel {
TransceiverChannel(Transceiver *trx, int num)
{
this->trx = trx;
this->num = num;
}
~TransceiverChannel()
{
}
Transceiver *trx;
size_t num;
};
/** Internal transceiver state variables */
@@ -99,14 +107,13 @@ public:
size_t tx_sps, size_t rx_sps, size_t chans,
GSM::Time wTransmitLatency,
RadioInterface *wRadioInterface,
double wRssiOffset, int stackSize);
double wRssiOffset);
/** Destructor */
~Transceiver();
/** Start the control loop */
bool init(FillerType filler, size_t rtsc, unsigned rach_delay,
bool edge, bool ext_rach);
bool init(FillerType filler, size_t rtsc, unsigned rach_delay, bool edge);
/** attach the radioInterface receive FIFO */
bool receiveFIFO(VectorFIFO *wFIFO, size_t chan)
@@ -146,9 +153,9 @@ private:
std::string mLocalAddr;
std::string mRemoteAddr;
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
int mClockSocket; ///< socket for writing clock updates to GSM core
std::vector<UDPSocket *> mDataSockets; ///< socket for writing to/reading from GSM core
std::vector<UDPSocket *> mCtrlSockets; ///< socket for writing/reading control commands from GSM core
UDPSocket mClockSocket; ///< socket for writing clock updates to GSM core
std::vector<VectorQueue> mTxPriorityQueues; ///< priority queue of transmit bursts received from GSM core
std::vector<VectorFIFO *> mReceiveFIFO; ///< radioInterface FIFO of receive bursts
@@ -169,7 +176,6 @@ private:
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,
@@ -182,7 +188,9 @@ 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);
SoftVector *pullRadioVector(GSM::Time &wTime, double &RSSI, bool &isRssiValid,
double &timingOffset, double &noise,
size_t chan = 0);
/** Set modulus for specific timeslot */
void setModulus(size_t timeslot, size_t chan);
@@ -197,7 +205,6 @@ private:
int mSPSRx; ///< number of samples per Rx symbol
size_t mChans;
bool mExtRACH;
bool mEdge;
bool mOn; ///< flag to indicate that transceiver is powered on
bool mForceClockInterface; ///< flag to indicate whether IND CLOCK shall be sent unconditionally after transceiver is started
@@ -209,7 +216,6 @@ private:
unsigned mMaxExpectedDelayNB; ///< maximum expected time-of-arrival offset in GSM symbols for Normal Bursts
unsigned mWriteBurstToDiskMask; ///< debug: bitmask to indicate which timeslots to dump to disk
std::vector<unsigned> mVersionTRXD; ///< Format version to use for TRXD protocol communication, per channel
std::vector<TransceiverState> mStates;
/** Start and stop I/O threads through the control socket API */
@@ -238,11 +244,15 @@ protected:
*/
bool driveTxPriorityQueue(size_t chan);
friend void *RxUpperLoopAdapter(TrxChanThParams *params);
friend void *TxUpperLoopAdapter(TrxChanThParams *params);
friend void *RxLowerLoopAdapter(Transceiver *transceiver);
friend void *TxLowerLoopAdapter(Transceiver *transceiver);
friend void *ControlServiceLoopAdapter(TrxChanThParams *params);
friend void *RxUpperLoopAdapter(TransceiverChannel *);
friend void *TxUpperLoopAdapter(TransceiverChannel *);
friend void *RxLowerLoopAdapter(Transceiver *);
friend void *TxLowerLoopAdapter(Transceiver *);
friend void *ControlServiceLoopAdapter(TransceiverChannel *);
void reset();
@@ -250,17 +260,18 @@ protected:
/** set priority on current thread */
void setPriority(float prio = 0.5) { mRadioInterface->setPriority(prio); }
void logRxBurst(size_t chan, const struct trx_ul_burst_ind *bi);
void logRxBurst(size_t chan, SoftVector *burst, GSM::Time time, double dbm,
double rssi, double noise, double toa);
};
void *RxUpperLoopAdapter(TrxChanThParams *params);
void *RxUpperLoopAdapter(TransceiverChannel *);
/** Main drive threads */
void *RxLowerLoopAdapter(Transceiver *transceiver);
void *TxLowerLoopAdapter(Transceiver *transceiver);
void *RxLowerLoopAdapter(Transceiver *);
void *TxLowerLoopAdapter(Transceiver *);
/** control message handler thread loop */
void *ControlServiceLoopAdapter(TrxChanThParams *params);
void *ControlServiceLoopAdapter(TransceiverChannel *);
/** transmit queueing thread loop */
void *TxUpperLoopAdapter(TrxChanThParams *params);
void *TxUpperLoopAdapter(TransceiverChannel *);

View File

@@ -2,8 +2,6 @@
* NEON type conversions
* Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either

View File

@@ -2,8 +2,6 @@
* NEON type conversions
* Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either

View File

@@ -2,8 +2,6 @@
* NEON Convolution
* Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
@@ -31,15 +29,17 @@
int _base_convolve_real(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len,
int step, int offset);
int _base_convolve_complex(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len,
int step, int offset);
int bounds_check(int x_len, int h_len, int y_len,
int start, int len);
int start, int len, int step);
#ifdef HAVE_NEON
/* Calls into NEON assembler */
@@ -69,32 +69,35 @@ void convolve_init(void)
int convolve_real(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len,
int step, int offset)
{
void (*conv_func)(float *, float *, float *, int) = NULL;
if (bounds_check(x_len, h_len, y_len, start, len) < 0)
if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
return -1;
memset(y, 0, len * 2 * sizeof(float));
#ifdef HAVE_NEON
switch (h_len) {
case 4:
conv_func = neon_conv_real4;
break;
case 8:
conv_func = neon_conv_real8;
break;
case 12:
conv_func = neon_conv_real12;
break;
case 16:
conv_func = neon_conv_real16;
break;
case 20:
conv_func = neon_conv_real20;
break;
if (step <= 4) {
switch (h_len) {
case 4:
conv_func = neon_conv_real4;
break;
case 8:
conv_func = neon_conv_real8;
break;
case 12:
conv_func = neon_conv_real12;
break;
case 16:
conv_func = neon_conv_real16;
break;
case 20:
conv_func = neon_conv_real20;
break;
}
}
#endif
if (conv_func) {
@@ -104,7 +107,7 @@ int convolve_real(float *x, int x_len,
_base_convolve_real(x, x_len,
h, h_len,
y, y_len,
start, len);
start, len, step, offset);
}
return len;
@@ -115,17 +118,18 @@ int convolve_real(float *x, int x_len,
int convolve_complex(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len,
int step, int offset)
{
void (*conv_func)(float *, float *, float *, int, int) = NULL;
if (bounds_check(x_len, h_len, y_len, start, len) < 0)
if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
return -1;
memset(y, 0, len * 2 * sizeof(float));
#ifdef HAVE_NEON
if (!(h_len % 4))
if (step <= 4 && !(h_len % 4))
conv_func = neon_conv_cmplx_4n;
#endif
if (conv_func) {
@@ -135,7 +139,7 @@ int convolve_complex(float *x, int x_len,
_base_convolve_complex(x, x_len,
h, h_len,
y, y_len,
start, len);
start, len, step, offset);
}
return len;

View File

@@ -2,8 +2,6 @@
* NEON Convolution
* Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
@@ -94,8 +92,8 @@ neon_conv_real12:
vld2.32 {q8-q9}, [r4], r6
vld2.32 {q10-q11}, [r5], r6
#ifdef HAVE_NEON_FMA
vmul.f32 q1, q6, q0
vmul.f32 q3, q7, q0
vfma.f32 q1, q6, q0
vfma.f32 q3, q7, q0
vfma.f32 q1, q8, q2
vfma.f32 q3, q9, q2
vfma.f32 q1, q10, q4

View File

@@ -2,8 +2,6 @@
* NEON scaling
* Copyright (C) 2012,2013 Thomas Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either

View File

@@ -1,9 +1,7 @@
/*
* NEON complex multiplication
* NEON complex multiplication
* Copyright (C) 2012,2013 Thomas Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either

View File

@@ -2,8 +2,6 @@
* NEON scaling
* Copyright (C) 2012,2013 Thomas Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either

View File

@@ -2,8 +2,6 @@
* ARM NEON Scaling
* Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either

View File

@@ -2,8 +2,6 @@
* Conversion
* Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
@@ -33,3 +31,4 @@ void base_convert_short_float(float *out, const short *in, int len)
for (int i = 0; i < len; i++)
out[i] = in[i];
}

View File

@@ -1,27 +1,31 @@
#ifndef _CONVOLVE_H_
#define _CONVOLVE_H_
void *convolve_h_alloc(size_t num);
void *convolve_h_alloc(int num);
int convolve_real(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len,
int step, int offset);
int convolve_complex(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len,
int step, int offset);
int base_convolve_real(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len,
int step, int offset);
int base_convolve_complex(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len,
int step, int offset);
void convolve_init(void);

View File

@@ -2,8 +2,6 @@
* Convolution
* Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
@@ -43,17 +41,17 @@ static void mac_cmplx(const float *x, const float *h, float *y)
/* Base vector complex-complex multiply and accumulate */
static void mac_real_vec_n(const float *x, const float *h, float *y,
int len)
int len, int step, int offset)
{
for (int i=0; i<len; i++)
for (int i = offset; i < len; i += step)
mac_real(&x[2 * i], &h[2 * i], y);
}
/* Base vector complex-complex multiply and accumulate */
static void mac_cmplx_vec_n(const float *x, const float *h, float *y,
int len)
int len, int step, int offset)
{
for (int i=0; i<len; i++)
for (int i = offset; i < len; i += step)
mac_cmplx(&x[2 * i], &h[2 * i], y);
}
@@ -61,12 +59,14 @@ static void mac_cmplx_vec_n(const float *x, const float *h, float *y,
int _base_convolve_real(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len,
int step, int offset)
{
for (int i = 0; i < len; i++) {
mac_real_vec_n(&x[2 * (i - (h_len - 1) + start)],
h,
&y[2 * i], h_len);
&y[2 * i], h_len,
step, offset);
}
return len;
@@ -76,13 +76,14 @@ int _base_convolve_real(const float *x, int x_len,
int _base_convolve_complex(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len,
int step, int offset)
{
for (int i = 0; i < len; i++) {
mac_cmplx_vec_n(&x[2 * (i - (h_len - 1) + start)],
h,
&y[2 * i],
h_len);
h_len, step, offset);
}
return len;
@@ -90,10 +91,10 @@ int _base_convolve_complex(const float *x, int x_len,
/* Buffer validity checks */
int bounds_check(int x_len, int h_len, int y_len,
int start, int len)
int start, int len, int step)
{
if ((x_len < 1) || (h_len < 1) ||
(y_len < 1) || (len < 1)) {
(y_len < 1) || (len < 1) || (step < 1)) {
fprintf(stderr, "Convolve: Invalid input\n");
return -1;
}
@@ -112,9 +113,10 @@ int bounds_check(int x_len, int h_len, int y_len,
int base_convolve_real(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len,
int step, int offset)
{
if (bounds_check(x_len, h_len, y_len, start, len) < 0)
if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
return -1;
memset(y, 0, len * 2 * sizeof(float));
@@ -122,16 +124,17 @@ int base_convolve_real(const float *x, int x_len,
return _base_convolve_real(x, x_len,
h, h_len,
y, y_len,
start, len);
start, len, step, offset);
}
/* API: Non-aligned (no SSE) complex-complex */
int base_convolve_complex(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len,
int step, int offset)
{
if (bounds_check(x_len, h_len, y_len, start, len) < 0)
if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
return -1;
memset(y, 0, len * 2 * sizeof(float));
@@ -139,11 +142,11 @@ int base_convolve_complex(const float *x, int x_len,
return _base_convolve_complex(x, x_len,
h, h_len,
y, y_len,
start, len);
start, len, step, offset);
}
/* Aligned filter tap allocation */
void *convolve_h_alloc(size_t len)
void *convolve_h_alloc(int len)
{
#ifdef HAVE_SSE3
return memalign(16, len * 2 * sizeof(float));

View File

@@ -1,20 +1,18 @@
/*
* Fast Fourier transform
* Fast Fourier transform
*
* Copyright (C) 2012 Tom Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
*
* 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.
@@ -34,9 +32,9 @@ struct fft_hdl {
fftwf_plan fft_plan;
};
/*! \brief Initialize FFT backend
/*! \brief Initialize FFT backend
* \param[in] reverse FFT direction
* \param[in] m FFT length
* \param[in] m FFT length
* \param[in] istride input stride count
* \param[in] ostride output stride count
* \param[in] in input buffer (FFTW aligned)
@@ -94,7 +92,7 @@ void fft_free(void *ptr)
free(ptr);
}
/*! \brief Free FFT backend resources
/*! \brief Free FFT backend resources
*/
void free_fft(struct fft_hdl *hdl)
{

View File

@@ -30,25 +30,25 @@
/* Architecture dependant function pointers */
struct convolve_cpu_context {
void (*conv_cmplx_4n) (const float *, int, const float *, int, float *,
int, int, int);
int, int, int, int, int);
void (*conv_cmplx_8n) (const float *, int, const float *, int, float *,
int, int, int);
int, int, int, int, int);
void (*conv_cmplx) (const float *, int, const float *, int, float *,
int, int, int);
int, int, int, int, int);
void (*conv_real4) (const float *, int, const float *, int, float *,
int, int, int);
int, int, int, int, int);
void (*conv_real8) (const float *, int, const float *, int, float *,
int, int, int);
int, int, int, int, int);
void (*conv_real12) (const float *, int, const float *, int, float *,
int, int, int);
int, int, int, int, int);
void (*conv_real16) (const float *, int, const float *, int, float *,
int, int, int);
int, int, int, int, int);
void (*conv_real20) (const float *, int, const float *, int, float *,
int, int, int);
int, int, int, int, int);
void (*conv_real4n) (const float *, int, const float *, int, float *,
int, int, int);
int, int, int, int, int);
void (*conv_real) (const float *, int, const float *, int, float *, int,
int, int);
int, int, int, int);
};
static struct convolve_cpu_context c;
@@ -56,15 +56,17 @@ static struct convolve_cpu_context c;
int _base_convolve_real(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len,
int step, int offset);
int _base_convolve_complex(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len,
int step, int offset);
int bounds_check(int x_len, int h_len, int y_len,
int start, int len);
int start, int len, int step);
/* API: Initalize convolve module */
void convolve_init(void)
@@ -97,37 +99,46 @@ void convolve_init(void)
/* API: Aligned complex-real */
int convolve_real(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len, int start, int len)
float *y, int y_len, int start, int len, int step, int offset)
{
if (bounds_check(x_len, h_len, y_len, start, len) < 0)
if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
return -1;
memset(y, 0, len * 2 * sizeof(float));
switch (h_len) {
case 4:
c.conv_real4(x, x_len, h, h_len, y, y_len, start, len);
break;
case 8:
c.conv_real8(x, x_len, h, h_len, y, y_len, start, len);
break;
case 12:
c.conv_real12(x, x_len, h, h_len, y, y_len, start, len);
break;
case 16:
c.conv_real16(x, x_len, h, h_len, y, y_len, start, len);
break;
case 20:
c.conv_real20(x, x_len, h, h_len, y, y_len, start, len);
break;
default:
if (!(h_len % 4))
c.conv_real4n(x, x_len, h, h_len, y, y_len,
start, len);
else
c.conv_real(x, x_len, h, h_len, y, y_len, start,
len);
}
if (step <= 4) {
switch (h_len) {
case 4:
c.conv_real4(x, x_len, h, h_len, y, y_len, start, len,
step, offset);
break;
case 8:
c.conv_real8(x, x_len, h, h_len, y, y_len, start, len,
step, offset);
break;
case 12:
c.conv_real12(x, x_len, h, h_len, y, y_len, start, len,
step, offset);
break;
case 16:
c.conv_real16(x, x_len, h, h_len, y, y_len, start, len,
step, offset);
break;
case 20:
c.conv_real20(x, x_len, h, h_len, y, y_len, start, len,
step, offset);
break;
default:
if (!(h_len % 4))
c.conv_real4n(x, x_len, h, h_len, y, y_len,
start, len, step, offset);
else
c.conv_real(x, x_len, h, h_len, y, y_len, start,
len, step, offset);
}
} else
c.conv_real(x, x_len, h, h_len, y, y_len, start, len, step,
offset);
return len;
}
@@ -136,19 +147,26 @@ int convolve_real(const float *x, int x_len,
int convolve_complex(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len, int step, int offset)
{
if (bounds_check(x_len, h_len, y_len, start, len) < 0)
if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
return -1;
memset(y, 0, len * 2 * sizeof(float));
if (!(h_len % 8))
c.conv_cmplx_8n(x, x_len, h, h_len, y, y_len, start, len);
else if (!(h_len % 4))
c.conv_cmplx_4n(x, x_len, h, h_len, y, y_len, start, len);
else
c.conv_cmplx(x, x_len, h, h_len, y, y_len, start, len);
if (step <= 4) {
if (!(h_len % 8))
c.conv_cmplx_8n(x, x_len, h, h_len, y, y_len, start,
len, step, offset);
else if (!(h_len % 4))
c.conv_cmplx_4n(x, x_len, h, h_len, y, y_len, start,
len, step, offset);
else
c.conv_cmplx(x, x_len, h, h_len, y, y_len, start, len,
step, offset);
} else
c.conv_cmplx(x, x_len, h, h_len, y, y_len, start, len, step,
offset);
return len;
}

View File

@@ -34,12 +34,12 @@
void sse_conv_real4(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len, int step, int offset)
{
/* NOTE: The parameter list of this function has to match the parameter
* list of _base_convolve_real() in convolve_base.c. This specific
* implementation, ignores some of the parameters of
* _base_convolve_complex(), which are: x_len, y_len. */
* _base_convolve_complex(), which are: x_len, y_len, offset, step */
__m128 m0, m1, m2, m3, m4, m5, m6, m7;
@@ -75,7 +75,7 @@ void sse_conv_real4(const float *x, int x_len,
void sse_conv_real8(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len, int step, int offset)
{
/* See NOTE in sse_conv_real4() */
@@ -126,7 +126,7 @@ void sse_conv_real8(const float *x, int x_len,
void sse_conv_real12(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len, int step, int offset)
{
/* See NOTE in sse_conv_real4() */
@@ -192,7 +192,7 @@ void sse_conv_real12(const float *x, int x_len,
void sse_conv_real16(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len, int step, int offset)
{
/* See NOTE in sse_conv_real4() */
@@ -271,7 +271,7 @@ void sse_conv_real16(const float *x, int x_len,
void sse_conv_real20(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len, int step, int offset)
{
/* See NOTE in sse_conv_real4() */
@@ -361,7 +361,7 @@ void sse_conv_real20(const float *x, int x_len,
void sse_conv_real4n(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len, int step, int offset)
{
/* See NOTE in sse_conv_real4() */
@@ -408,12 +408,12 @@ void sse_conv_real4n(const float *x, int x_len,
void sse_conv_cmplx_4n(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len, int step, int offset)
{
/* NOTE: The parameter list of this function has to match the parameter
* list of _base_convolve_complex() in convolve_base.c. This specific
* implementation, ignores some of the parameters of
* _base_convolve_complex(), which are: x_len, y_len. */
* _base_convolve_complex(), which are: x_len, y_len, offset, step. */
__m128 m0, m1, m2, m3, m4, m5, m6, m7;
@@ -466,7 +466,7 @@ void sse_conv_cmplx_4n(const float *x, int x_len,
void sse_conv_cmplx_8n(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len)
int start, int len, int step, int offset)
{
/* See NOTE in sse_conv_cmplx_4n() */

View File

@@ -23,46 +23,46 @@
void sse_conv_real4(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len, int step, int offset);
/* 8-tap SSE complex-real convolution */
void sse_conv_real8(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len, int step, int offset);
/* 12-tap SSE complex-real convolution */
void sse_conv_real12(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len, int step, int offset);
/* 16-tap SSE complex-real convolution */
void sse_conv_real16(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len, int step, int offset);
/* 20-tap SSE complex-real convolution */
void sse_conv_real20(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len, int step, int offset);
/* 4*N-tap SSE complex-real convolution */
void sse_conv_real4n(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len, int step, int offset);
/* 4*N-tap SSE complex-complex convolution */
void sse_conv_cmplx_4n(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len, int step, int offset);
/* 8*N-tap SSE complex-complex convolution */
void sse_conv_cmplx_8n(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len,
int start, int len);
int start, int len, int step, int offset);

View File

@@ -1,6 +1,8 @@
include $(top_srcdir)/Makefile.common
SUBDIRS = common
noinst_HEADERS = radioDevice.h
SUBDIRS =
if DEVICE_USRP1
SUBDIRS += usrp1

View File

@@ -1,12 +0,0 @@
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES)
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LMS_CFLAGS)
noinst_HEADERS = radioDevice.h smpl_buf.h
noinst_LTLIBRARIES = libdevice_common.la
libdevice_common_la_SOURCES = \
smpl_buf.cpp

View File

@@ -1,171 +0,0 @@
/*
* Sample Buffer - Allows reading and writing of timed samples
*
* Copyright 2010,2011 Free Software Foundation, Inc.
* Copyright (C) 2015 Ettus Research LLC
* Copyright 2019 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* Author: Tom Tsou <tom.tsou@ettus.com>
*
* 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 "smpl_buf.h"
#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)
{
data = new uint32_t[len];
}
smpl_buf::~smpl_buf()
{
delete[] data;
}
ssize_t smpl_buf::avail_smpls(TIMESTAMP timestamp) const
{
if (timestamp < time_start)
return ERROR_TIMESTAMP;
else if (timestamp >= time_end)
return 0;
else
return time_end - timestamp;
}
ssize_t smpl_buf::read(void *buf, size_t len, TIMESTAMP timestamp)
{
int type_sz = 2 * sizeof(short);
// Check for valid read
if (timestamp < time_start)
return ERROR_TIMESTAMP;
if (timestamp >= time_end)
return 0;
if (len >= buf_len)
return ERROR_READ;
// How many samples should be copied
size_t num_smpls = time_end - timestamp;
if (num_smpls > len)
num_smpls = len;
// Starting index
size_t read_start = (data_start + (timestamp - time_start)) % buf_len;
// Read it
if (read_start + num_smpls < buf_len) {
size_t numBytes = len * type_sz;
memcpy(buf, data + read_start, numBytes);
} else {
size_t first_cp = (buf_len - read_start) * type_sz;
size_t second_cp = len * type_sz - first_cp;
memcpy(buf, data + read_start, first_cp);
memcpy((char*) buf + first_cp, data, second_cp);
}
data_start = (read_start + len) % buf_len;
time_start = timestamp + len;
if (time_start > time_end)
return ERROR_READ;
else
return num_smpls;
}
ssize_t smpl_buf::write(void *buf, size_t len, TIMESTAMP timestamp)
{
int type_sz = 2 * sizeof(short);
// Check for valid write
if ((len == 0) || (len >= buf_len))
return ERROR_WRITE;
if ((timestamp + len) <= time_end)
return ERROR_TIMESTAMP;
if (timestamp < time_end) {
LOGC(DDEV, ERR) << "Overwriting old buffer data: timestamp="
<< timestamp << " time_end=" << time_end;
// Do not return error here, because it's a rounding error and is not fatal
}
if (timestamp > time_end && time_end != 0) {
LOGC(DDEV, ERR) << "Skipping buffer data: timestamp="
<< timestamp << " time_end=" << time_end;
// Do not return error here, because it's a rounding error and is not fatal
}
// Starting index
size_t write_start = (data_start + (timestamp - time_start)) % buf_len;
// Write it
if ((write_start + len) < buf_len) {
size_t numBytes = len * type_sz;
memcpy(data + write_start, buf, numBytes);
} else {
size_t first_cp = (buf_len - write_start) * type_sz;
size_t second_cp = len * type_sz - first_cp;
memcpy(data + write_start, buf, first_cp);
memcpy(data, (char*) buf + first_cp, second_cp);
}
data_end = (write_start + len) % buf_len;
time_end = timestamp + len;
if (!data_start)
data_start = write_start;
if (((write_start + len) > buf_len) && (data_end > data_start))
return ERROR_OVERFLOW;
else if (time_end <= time_start)
return ERROR_WRITE;
else
return len;
}
std::string smpl_buf::str_status(TIMESTAMP timestamp) const
{
std::ostringstream ost("Sample buffer: ");
ost << "timestamp = " << timestamp;
ost << ", length = " << buf_len;
ost << ", time_start = " << time_start;
ost << ", time_end = " << time_end;
ost << ", data_start = " << data_start;
ost << ", data_end = " << data_end;
return ost.str();
}
std::string smpl_buf::str_code(ssize_t code)
{
switch (code) {
case ERROR_TIMESTAMP:
return "Sample buffer: Requested timestamp is not valid";
case ERROR_READ:
return "Sample buffer: Read error";
case ERROR_WRITE:
return "Sample buffer: Write error";
case ERROR_OVERFLOW:
return "Sample buffer: Overrun";
default:
return "Sample buffer: Unknown error";
}
}

View File

@@ -1,89 +0,0 @@
/*
* Sample Buffer - Allows reading and writing of timed samples
*
* Copyright 2010,2011 Free Software Foundation, Inc.
* Copyright (C) 2015 Ettus Research LLC
* Copyright 2019 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* Author: Tom Tsou <tom.tsou@ettus.com>
*
* 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 <unistd.h>
#include "radioDevice.h"
/*
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.
*/
class smpl_buf {
public:
/** Sample buffer constructor
@param len number of 32-bit samples the buffer should hold
@param timestamp
*/
smpl_buf(size_t len);
~smpl_buf();
/** Query number of samples available for reading
@param timestamp time of first sample
@return number of available samples or error
*/
ssize_t avail_smpls(TIMESTAMP timestamp) const;
/** Read and write
@param buf pointer to buffer
@param len number of samples desired to read or write
@param timestamp time of first stample
@return number of actual samples read or written or error
*/
ssize_t read(void *buf, size_t len, TIMESTAMP timestamp);
ssize_t write(void *buf, size_t len, TIMESTAMP timestamp);
/** Buffer status string
@return a formatted string describing internal buffer state
*/
std::string str_status(TIMESTAMP timestamp) const;
/** Formatted error string
@param code an error code
@return a formatted error string
*/
static std::string str_code(ssize_t code);
enum err_code {
ERROR_TIMESTAMP = -1,
ERROR_READ = -2,
ERROR_WRITE = -3,
ERROR_OVERFLOW = -4
};
private:
uint32_t *data;
size_t buf_len;
TIMESTAMP time_start;
TIMESTAMP time_end;
size_t data_start;
size_t data_end;
};

View File

@@ -1,7 +1,5 @@
/*
* Copyright 2018 sysmocom - s.f.m.c. GmbH
*
* 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
@@ -23,14 +21,10 @@
#include "Logger.h"
#include "Threads.h"
#include "LMSDevice.h"
#include "Utils.h"
#include <lime/LimeSuite.h>
extern "C" {
#include "osmo_signal.h"
#include <osmocom/core/utils.h>
}
#ifdef HAVE_CONFIG_H
#include "config.h"
@@ -38,12 +32,13 @@ extern "C" {
using namespace std;
constexpr double LMSDevice::masterClockRate;
#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,
@@ -51,30 +46,15 @@ LMSDevice::LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t c
RadioDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths),
m_lms_dev(NULL)
{
LOGC(DDEV, INFO) << "creating LMS device...";
LOG(INFO) << "creating LMS device...";
m_lms_stream_rx.resize(chans);
m_lms_stream_tx.resize(chans);
rx_buffers.resize(chans);
}
LMSDevice::~LMSDevice()
{
unsigned int i;
LOGC(DDEV, INFO) << "Closing LMS device";
if (m_lms_dev) {
/* disable all channels */
for (i=0; i<chans; i++) {
LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, false);
LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, false);
}
LMS_Close(m_lms_dev);
m_lms_dev = NULL;
}
for (size_t i = 0; i < rx_buffers.size(); i++)
delete rx_buffers[i];
m_last_rx_underruns.resize(chans, 0);
m_last_rx_overruns.resize(chans, 0);
m_last_tx_underruns.resize(chans, 0);
m_last_tx_overruns.resize(chans, 0);
}
static void lms_log_callback(int lvl, const char *msg)
@@ -82,208 +62,169 @@ static void lms_log_callback(int lvl, const char *msg)
/* map lime specific log levels */
static const int lvl_map[5] = {
[0] = LOGL_FATAL,
[LMS_LOG_ERROR] = LOGL_ERROR,
[LMS_LOG_WARNING] = LOGL_NOTICE,
[LMS_LOG_INFO] = LOGL_INFO,
[LMS_LOG_DEBUG] = LOGL_DEBUG,
[1] = LOGL_ERROR,
[2] = LOGL_NOTICE,
[3] = LOGL_INFO,
[4] = LOGL_DEBUG,
};
/* protect against future higher log level values (lower importance) */
if ((unsigned int) lvl >= ARRAY_SIZE(lvl_map))
lvl = ARRAY_SIZE(lvl_map)-1;
LOGLV(DLMS, lvl_map[lvl]) << msg;
LOGLV(DLMS, lvl) << msg;
}
static void thread_enable_cancel(bool cancel)
{
cancel ? pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) :
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
}
static void print_range(const char* name, lms_range_t *range)
{
LOGC(DDEV, INFO) << name << ": Min=" << range->min << " Max=" << range->max
LOG(DEBUG) << name << ": Min=" << range->min << " Max=" << range->max
<< " Step=" << range->step;
}
/*! Find the device string that matches all filters from \a args.
* \param[in] info_list device addresses found by LMS_GetDeviceList()
* \param[in] count length of info_list
* \param[in] args dev-args value from osmo-trx.cfg, containing comma separated key=value pairs
* \return index of first matching device or -1 (no match) */
int info_list_find(lms_info_str_t* info_list, unsigned int count, const std::string &args)
{
unsigned int i, j;
vector<string> filters;
filters = comma_delimited_to_vector(args.c_str());
/* iterate over device addresses */
for (i=0; i < count; i++) {
/* check if all filters match */
bool match = true;
for (j=0; j < filters.size(); j++) {
if (!strstr(info_list[i], filters[j].c_str())) {
match = false;
break;
}
}
if (match)
return i;
}
return -1;
}
int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
{
//lms_info_str_t dev_str;
lms_info_str_t* info_list;
const lms_dev_info_t* device_info;
lms_range_t range_sr;
float_type sr_host, sr_rf;
lms_range_t range_lpfbw_rx, range_lpfbw_tx, range_sr;
float_type sr_host, sr_rf, lpfbw_rx, lpfbw_tx;
uint16_t dac_val;
unsigned int i, n;
int rc, dev_id;
int rc;
LOGC(DDEV, INFO) << "Opening LMS device..";
LOG(INFO) << "Opening LMS device..";
LMS_RegisterLogHandler(&lms_log_callback);
if ((n = LMS_GetDeviceList(NULL)) < 0)
LOGC(DDEV, ERROR) << "LMS_GetDeviceList(NULL) failed";
LOGC(DDEV, INFO) << "Devices found: " << n;
LOG(ERROR) << "LMS_GetDeviceList(NULL) failed";
LOG(DEBUG) << "Devices found: " << n;
if (n < 1)
return -1;
info_list = new lms_info_str_t[n];
if (LMS_GetDeviceList(info_list) < 0)
LOGC(DDEV, ERROR) << "LMS_GetDeviceList(info_list) failed";
LOG(ERROR) << "LMS_GetDeviceList(info_list) failed";
for (i = 0; i < n; i++)
LOGC(DDEV, INFO) << "Device [" << i << "]: " << info_list[i];
LOG(DEBUG) << "Device [" << i << "]: " << info_list[i];
dev_id = info_list_find(info_list, n, args);
if (dev_id == -1) {
LOGC(DDEV, ERROR) << "No LMS device found with address '" << args << "'";
delete[] info_list;
return -1;
}
LOGC(DDEV, INFO) << "Using device[" << dev_id << "]";
rc = LMS_Open(&m_lms_dev, info_list[dev_id], NULL);
rc = LMS_Open(&m_lms_dev, info_list[0], NULL);
if (rc != 0) {
LOGC(DDEV, ERROR) << "LMS_GetDeviceList() failed)";
LOG(ERROR) << "LMS_GetDeviceList() failed)";
delete [] info_list;
return -1;
}
delete [] info_list;
device_info = LMS_GetDeviceInfo(m_lms_dev);
if ((ref != REF_EXTERNAL) && (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) {
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)
goto out_close;
}
LOGC(DDEV, INFO) << "Init LMS device";
LOG(INFO) << "Init LMS device";
if (LMS_Init(m_lms_dev) != 0) {
LOGC(DDEV, ERROR) << "LMS_Init() failed";
goto out_close;
LOG(ERROR) << "LMS_Init() failed";
return -1;
}
/* 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?*/
/* enable all used channels */
for (i=0; i<chans; i++) {
if (LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, true) < 0)
goto out_close;
if (LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, true) < 0)
goto out_close;
}
/* set samplerate */
if (LMS_GetSampleRateRange(m_lms_dev, LMS_CH_RX, &range_sr))
goto out_close;
print_range("Sample Rate", &range_sr);
LOGC(DDEV, INFO) << "Setting sample rate to " << GSMRATE*tx_sps << " " << tx_sps;
LOG(DEBUG) << "Setting sample rate to " << GSMRATE*tx_sps << " " << tx_sps;
if (LMS_SetSampleRate(m_lms_dev, GSMRATE*tx_sps, 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;
LOG(DEBUG) << "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 */
/* configure antennas */
if (!set_antennas()) {
LOGC(DDEV, FATAL) << "LMS antenna setting failed";
switch (ref) {
case REF_INTERNAL:
LOG(DEBUG) << "Setting Internal clock reference";
/* Ugly API: Selecting clock source implicit by writing to VCTCXO DAC ?!? */
if (LMS_VCTCXORead(m_lms_dev, &dac_val) < 0)
goto out_close;
LOG(DEBUG) << "Setting VCTCXO to " << dac_val;
if (LMS_VCTCXOWrite(m_lms_dev, dac_val) < 0)
goto out_close;
break;
case REF_EXTERNAL:
LOG(DEBUG) << "Setting External clock reference to " << 10000000.0;
/* Assume an external 10 MHz reference clock */
if (LMS_SetClockFreq(m_lms_dev, LMS_CLOCK_EXTREF, 10000000.0) < 0)
goto out_close;
break;
default:
LOG(ALERT) << "Invalid reference type";
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));
if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_rx))
goto out_close;
print_range("LPFBWRange Rx", &range_lpfbw_rx);
if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_tx))
goto out_close;
print_range("LPFBWRange Tx", &range_lpfbw_tx);
lpfbw_rx = OSMO_MIN(OSMO_MAX(1.4001e6, range_lpfbw_rx.min), range_lpfbw_rx.max);
lpfbw_tx = OSMO_MIN(OSMO_MAX(5.2e6, range_lpfbw_tx.min), range_lpfbw_tx.max);
LOG(DEBUG) << "LPFBW: Rx=" << lpfbw_rx << " Tx=" << lpfbw_tx;
if (!set_antennas()) {
LOG(ALERT) << "LMS antenna setting failed";
return -1;
}
/* Perform Rx and Tx calibration */
for (i=0; i<chans; i++) {
LOG(INFO) << "Setting LPFBW chan " << i;
if (LMS_SetLPFBW(m_lms_dev, LMS_CH_RX, i, lpfbw_rx) < 0)
goto out_close;
if (LMS_SetLPFBW(m_lms_dev, LMS_CH_TX, i, lpfbw_tx) < 0)
goto out_close;
LOG(INFO) << "Calibrating chan " << i;
if (LMS_Calibrate(m_lms_dev, LMS_CH_RX, i, LMS_CALIBRATE_BW_HZ, 0) < 0)
goto out_close;
if (LMS_Calibrate(m_lms_dev, LMS_CH_TX, i, LMS_CALIBRATE_BW_HZ, 0) < 0)
goto out_close;
}
samplesRead = 0;
samplesWritten = 0;
started = false;
return NORMAL;
out_close:
LOGC(DDEV, FATAL) << "Error in LMS open, closing: " << LMS_GetLastErrorMessage();
LOG(ALERT) << "Error in LMS open, closing: " << LMS_GetLastErrorMessage();
LMS_Close(m_lms_dev);
m_lms_dev = NULL;
return -1;
}
bool LMSDevice::start()
{
LOGC(DDEV, INFO) << "starting LMS...";
LOG(INFO) << "starting LMS...";
unsigned int i;
if (started) {
LOGC(DDEV, ERR) << "Device already started";
return false;
}
/* configure the channels/streams */
for (i=0; i<chans; i++) {
/* Set gains for calibration/filter setup */
/* TX gain to maximum */
setTxGain(maxTxGain(), i);
/* RX gain to midpoint */
if (LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, true) < 0)
return false;
if (LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, true) < 0)
return false;
// Set gains to midpoint
setTxGain((minTxGain() + maxTxGain()) / 2, i);
setRxGain((minRxGain() + maxRxGain()) / 2, i);
/* set up Rx and Tx filters */
if (!do_filters(i))
return false;
/* Perform Rx and Tx calibration */
if (!do_calib(i))
return false;
/* configure Streams */
m_lms_stream_rx[i] = {};
m_lms_stream_rx[i].isTx = false;
m_lms_stream_rx[i].channel = i;
@@ -331,59 +272,17 @@ bool LMSDevice::stop()
for (i=0; i<chans; i++) {
LMS_StopStream(&m_lms_stream_tx[i]);
LMS_StopStream(&m_lms_stream_rx[i]);
LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, false);
LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, false);
}
for (i=0; i<chans; i++) {
LMS_DestroyStream(m_lms_dev, &m_lms_stream_tx[i]);
LMS_DestroyStream(m_lms_dev, &m_lms_stream_rx[i]);
}
started = false;
return true;
}
/* do rx/tx calibration - depends on gain, freq and bw */
bool LMSDevice::do_calib(size_t chan)
{
LOGCHAN(chan, DDEV, INFO) << "Calibrating";
if (LMS_Calibrate(m_lms_dev, LMS_CH_RX, chan, LMS_CALIBRATE_BW_HZ, 0) < 0)
return false;
if (LMS_Calibrate(m_lms_dev, LMS_CH_TX, chan, LMS_CALIBRATE_BW_HZ, 0) < 0)
return false;
return true;
}
/* do rx/tx filter config - depends on bw only? */
bool LMSDevice::do_filters(size_t chan)
{
lms_range_t range_lpfbw_rx, range_lpfbw_tx;
float_type lpfbw_rx, lpfbw_tx;
LOGCHAN(chan, DDEV, INFO) << "Setting filters";
if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_rx))
return false;
print_range("LPFBWRange Rx", &range_lpfbw_rx);
if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_tx))
return false;
print_range("LPFBWRange Tx", &range_lpfbw_tx);
lpfbw_rx = OSMO_MIN(OSMO_MAX(1.4001e6, range_lpfbw_rx.min), range_lpfbw_rx.max);
lpfbw_tx = OSMO_MIN(OSMO_MAX(5.2e6, range_lpfbw_tx.min), range_lpfbw_tx.max);
LOGCHAN(chan, DDEV, INFO) << "LPFBW: Rx=" << lpfbw_rx << " Tx=" << lpfbw_tx;
LOGCHAN(chan, DDEV, INFO) << "Setting LPFBW";
if (LMS_SetLPFBW(m_lms_dev, LMS_CH_RX, chan, lpfbw_rx) < 0)
return false;
if (LMS_SetLPFBW(m_lms_dev, LMS_CH_TX, chan, lpfbw_tx) < 0)
return false;
return true;
}
double LMSDevice::maxTxGain()
{
return maxTxGainClamp;
return 73.0;
}
double LMSDevice::minTxGain()
@@ -403,30 +302,42 @@ double LMSDevice::minRxGain()
double LMSDevice::setTxGain(double dB, size_t chan)
{
if (chan) {
LOG(ALERT) << "Invalid channel " << chan;
return 0.0;
}
if (dB > maxTxGain())
dB = maxTxGain();
if (dB < minTxGain())
dB = minTxGain();
LOGCHAN(chan, DDEV, NOTICE) << "Setting TX gain to " << dB << " dB";
LOG(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";
LOG(ERR) << "Error setting TX gain";
return dB;
}
double LMSDevice::setRxGain(double dB, size_t chan)
{
if (chan) {
LOG(ALERT) << "Invalid channel " << chan;
return 0.0;
}
dB = 34.0;
if (dB > maxRxGain())
dB = maxRxGain();
if (dB < minRxGain())
dB = minRxGain();
LOGCHAN(chan, DDEV, NOTICE) << "Setting RX gain to " << dB << " dB";
LOG(NOTICE) << "Setting RX gain to " << dB << " dB.";
if (LMS_SetGaindB(m_lms_dev, LMS_CH_RX, chan, dB) < 0)
LOGCHAN(chan, DDEV, ERR) << "Error setting RX gain to " << dB << " dB";
LOG(ERR) << "Error setting RX gain";
return dB;
}
@@ -450,7 +361,7 @@ bool LMSDevice::flush_recv(size_t num_pkts)
{
#define CHUNK 625
int len = CHUNK * tx_sps;
short *buffer = (short*) alloca(sizeof(short) * len * 2);
short *buffer = new short[len * 2];
int rc;
lms_stream_meta_t rx_metadata = {};
rx_metadata.flushPartialPacket = false;
@@ -460,16 +371,18 @@ bool LMSDevice::flush_recv(size_t num_pkts)
while (!ts_initial || (num_pkts-- > 0)) {
rc = LMS_RecvStream(&m_lms_stream_rx[0], &buffer[0], len, &rx_metadata, 100);
LOGC(DDEV, DEBUG) << "Flush: Recv buffer of len " << rc << " at " << std::hex << rx_metadata.timestamp;
LOG(DEBUG) << "Flush: Recv buffer of len " << rc << " at " << std::hex << rx_metadata.timestamp;
if (rc != len) {
LOGC(DDEV, ERROR) << "Flush: Device receive timed out";
LOG(ALERT) << "LMS: Device receive timed out";
delete[] buffer;
return false;
}
ts_initial = rx_metadata.timestamp + len;
}
LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl;
LOG(INFO) << "Initial timestamp " << ts_initial << std::endl;
delete[] buffer;
return true;
}
@@ -478,18 +391,18 @@ bool LMSDevice::setRxAntenna(const std::string & ant, size_t chan)
int idx;
if (chan >= rx_paths.size()) {
LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return false;
}
idx = get_ant_idx(ant, LMS_CH_RX, chan);
if (idx < 0) {
LOGCHAN(chan, DDEV, ERROR) << "Invalid Rx Antenna";
LOG(ALERT) << "Invalid Rx Antenna";
return false;
}
if (LMS_SetAntenna(m_lms_dev, LMS_CH_RX, chan, idx) < 0) {
LOGCHAN(chan, DDEV, ERROR) << "Unable to set Rx Antenna";
LOG(ALERT) << "Unable to set Rx Antenna";
}
return true;
@@ -501,18 +414,18 @@ std::string LMSDevice::getRxAntenna(size_t chan)
int idx;
if (chan >= rx_paths.size()) {
LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return "";
}
idx = LMS_GetAntenna(m_lms_dev, LMS_CH_RX, chan);
if (idx < 0) {
LOGCHAN(chan, DDEV, ERROR) << "Error getting Rx Antenna";
LOG(ALERT) << "Error getting Rx Antenna";
return "";
}
if (LMS_GetAntennaList(m_lms_dev, LMS_CH_RX, chan, name_list) < idx) {
LOGCHAN(chan, DDEV, ERROR) << "Error getting Rx Antenna List";
LOG(ALERT) << "Error getting Rx Antenna List";
return "";
}
@@ -524,18 +437,18 @@ bool LMSDevice::setTxAntenna(const std::string & ant, size_t chan)
int idx;
if (chan >= tx_paths.size()) {
LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return false;
}
idx = get_ant_idx(ant, LMS_CH_TX, chan);
if (idx < 0) {
LOGCHAN(chan, DDEV, ERROR) << "Invalid Rx Antenna";
LOG(ALERT) << "Invalid Rx Antenna";
return false;
}
if (LMS_SetAntenna(m_lms_dev, LMS_CH_TX, chan, idx) < 0) {
LOGCHAN(chan, DDEV, ERROR) << "Unable to set Rx Antenna";
LOG(ALERT) << "Unable to set Rx Antenna";
}
return true;
@@ -547,18 +460,18 @@ std::string LMSDevice::getTxAntenna(size_t chan)
int idx;
if (chan >= tx_paths.size()) {
LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return "";
}
idx = LMS_GetAntenna(m_lms_dev, LMS_CH_TX, chan);
if (idx < 0) {
LOGCHAN(chan, DDEV, ERROR) << "Error getting Tx Antenna";
LOG(ALERT) << "Error getting Tx Antenna";
return "";
}
if (LMS_GetAntennaList(m_lms_dev, LMS_CH_TX, chan, name_list) < idx) {
LOGCHAN(chan, DDEV, ERROR) << "Error getting Tx Antenna List";
LOG(ALERT) << "Error getting Tx Antenna List";
return "";
}
@@ -571,207 +484,103 @@ bool LMSDevice::requiresRadioAlign()
}
GSM::Time LMSDevice::minLatency() {
/* UNUSED on limesdr (only used on usrp1/2) */
return GSM::Time(0,0);
}
/*!
* Issue tracking description of several events: https://github.com/myriadrf/LimeSuite/issues/265
*/
void LMSDevice::update_stream_stats_rx(size_t chan, bool *overrun)
{
lms_stream_status_t status;
bool changed = false;
if (LMS_GetStreamStatus(&m_lms_stream_rx[chan], &status) != 0) {
LOGCHAN(chan, DDEV, ERROR) << "Rx LMS_GetStreamStatus failed";
return;
}
/* FIFO overrun is counted when Rx FIFO is full but new data comes from
the board and oldest samples in FIFO are overwritte. Value count
since the last call to LMS_GetStreamStatus(stream). */
if (status.overrun) {
changed = true;
*overrun = true;
LOGCHAN(chan, DDEV, ERROR) << "Rx Overrun! ("
<< m_ctr[chan].rx_overruns << " -> "
<< status.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
since the last call to LMS_GetStreamStatus(stream). */
if (status.droppedPackets) {
changed = true;
LOGCHAN(chan, DDEV, ERROR) << "Rx Dropped packets by HW! ("
<< m_ctr[chan].rx_dropped_samples << " -> "
<< m_ctr[chan].rx_dropped_samples +
status.droppedPackets
<< ")";
m_ctr[chan].rx_dropped_events++;
}
m_ctr[chan].rx_dropped_samples += status.droppedPackets;
if (changed)
osmo_signal_dispatch(SS_DEVICE, S_DEVICE_COUNTER_CHANGE, &m_ctr[chan]);
/* Empirical data from a handful of
relatively recent machines shows that the B100 will underrun when
the transmit threshold is reduced to a time of 6 and a half frames,
so we set a minimum 7 frame threshold. */
return GSM::Time(6,7);
}
// NOTE: Assumes sequential reads
int LMSDevice::readSamples(std::vector < short *>&bufs, int len, bool * overrun,
TIMESTAMP timestamp, bool * underrun, unsigned *RSSI)
{
int rc, num_smpls, expect_smpls;
ssize_t avail_smpls;
TIMESTAMP expect_timestamp;
int rc = 0;
unsigned int i;
lms_stream_status_t status;
lms_stream_meta_t rx_metadata = {};
rx_metadata.flushPartialPacket = false;
rx_metadata.waitForTimestamp = false;
rx_metadata.timestamp = 0;
if (bufs.size() != chans) {
LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size();
LOG(ALERT) << "Invalid channel combination " << bufs.size();
return -1;
}
*overrun = false;
*underrun = false;
/* 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) {
thread_enable_cancel(false);
num_smpls = LMS_RecvStream(&m_lms_stream_rx[i], bufs[i], len - avail_smpls, &rx_metadata, 100);
update_stream_stats_rx(i, overrun);
thread_enable_cancel(true);
if (num_smpls <= 0) {
LOGCHAN(i, DDEV, ERROR) << "Device receive timed out (" << rc << " vs exp " << len << ").";
return -1;
}
LOGCHAN(i, DDEV, DEBUG) "Received timestamp = " << (TIMESTAMP)rx_metadata.timestamp << " (" << num_smpls << ")";
expect_smpls = len - avail_smpls;
if (expect_smpls != num_smpls)
LOGCHAN(i, DDEV, NOTICE) << "Unexpected recv buffer len: expect "
<< expect_smpls << " got " << num_smpls
<< ", diff=" << expect_smpls - num_smpls;
expect_timestamp = timestamp + avail_smpls;
if (expect_timestamp != (TIMESTAMP)rx_metadata.timestamp)
LOGCHAN(i, DDEV, ERROR) << "Unexpected recv buffer timestamp: expect "
<< expect_timestamp << " got " << (TIMESTAMP)rx_metadata.timestamp
<< ", diff=" << rx_metadata.timestamp - expect_timestamp;
rc = rx_buffers[i]->write(bufs[i], num_smpls, (TIMESTAMP)rx_metadata.timestamp);
if (rc < 0) {
LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_code(rc);
LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_status(timestamp);
if (rc != smpl_buf::ERROR_OVERFLOW)
return 0;
}
thread_enable_cancel(false);
rc = LMS_RecvStream(&m_lms_stream_rx[i], bufs[i], len, &rx_metadata, 100);
if (timestamp != (TIMESTAMP)rx_metadata.timestamp)
LOG(ALERT) << "chan "<< i << " recv buffer of len " << rc << " expect " << std::hex << timestamp << " got " << std::hex << (TIMESTAMP)rx_metadata.timestamp << " (" << std::hex << rx_metadata.timestamp <<") diff=" << rx_metadata.timestamp - timestamp;
if (rc != len) {
LOG(ALERT) << "LMS: Device receive timed out";
}
}
/* We have enough samples */
for (size_t i = 0; i < rx_buffers.size(); i++) {
rc = rx_buffers[i]->read(bufs[i], len, timestamp);
if ((rc < 0) || (rc != len)) {
LOGC(DDEV, ERROR) << rx_buffers[i]->str_code(rc);
LOGC(DDEV, ERROR) << rx_buffers[i]->str_status(timestamp);
return 0;
if (LMS_GetStreamStatus(&m_lms_stream_rx[i], &status) == 0) {
if (status.underrun > m_last_rx_underruns[i])
*underrun = true;
m_last_rx_underruns[i] = status.underrun;
if (status.overrun > m_last_rx_overruns[i])
*overrun = true;
m_last_rx_overruns[i] = status.overrun;
}
thread_enable_cancel(true);
}
return len;
}
samplesRead += rc;
void LMSDevice::update_stream_stats_tx(size_t chan, bool *underrun)
{
lms_stream_status_t status;
bool changed = false;
if (LMS_GetStreamStatus(&m_lms_stream_tx[chan], &status) != 0) {
LOGCHAN(chan, DDEV, ERROR) << "Tx LMS_GetStreamStatus failed";
return;
}
/* FIFO underrun is counted when Tx is running but FIFO is empty for
>100 ms (500ms in older versions). Value count since the last call to
LMS_GetStreamStatus(stream). */
if (status.underrun) {
changed = true;
*underrun = true;
LOGCHAN(chan, DDEV, ERROR) << "Tx Underrun! ("
<< m_ctr[chan].tx_underruns << " -> "
<< status.underrun << ")";
}
m_ctr[chan].tx_underruns += status.underrun;
/* Dropped packets in Tx are counted only when timestamps are enabled
and SDR drops packet because of late timestamp. Value count since the
last call to LMS_GetStreamStatus(stream). */
if (status.droppedPackets) {
changed = true;
LOGCHAN(chan, DDEV, ERROR) << "Tx Dropped packets by HW! ("
<< m_ctr[chan].tx_dropped_samples << " -> "
<< m_ctr[chan].tx_dropped_samples +
status.droppedPackets
<< ")";
m_ctr[chan].tx_dropped_events++;
}
m_ctr[chan].tx_dropped_samples += status.droppedPackets;
if (changed)
osmo_signal_dispatch(SS_DEVICE, S_DEVICE_COUNTER_CHANGE, &m_ctr[chan]);
if (((TIMESTAMP) rx_metadata.timestamp) < timestamp)
rc = 0;
return rc;
}
int LMSDevice::writeSamples(std::vector < short *>&bufs, int len,
bool * underrun, unsigned long long timestamp,
bool isControl)
{
int rc = 0;
int rc;
unsigned int i;
lms_stream_status_t status;
lms_stream_meta_t tx_metadata = {};
tx_metadata.flushPartialPacket = false;
tx_metadata.waitForTimestamp = true;
tx_metadata.timestamp = timestamp - ts_offset; /* Shift Tx time by offset */
if (isControl) {
LOGC(DDEV, ERROR) << "Control packets not supported";
LOG(ERR) << "Control packets not supported";
return 0;
}
if (bufs.size() != chans) {
LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size();
LOG(ALERT) << "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 << tx_metadata.timestamp;
LOG(DEBUG) << "chan "<< i << " send buffer of len " << len << " timestamp " << std::hex << tx_metadata.timestamp;
thread_enable_cancel(false);
rc = LMS_SendStream(&m_lms_stream_tx[i], bufs[i], len, &tx_metadata, 100);
update_stream_stats_tx(i, underrun);
thread_enable_cancel(true);
if (rc != len) {
LOGCHAN(i, DDEV, ERROR) << "LMS: Device Tx timed out (" << rc << " vs exp " << len << ").";
return -1;
LOG(ALERT) << "LMS: Device send timed out";
}
if (LMS_GetStreamStatus(&m_lms_stream_tx[i], &status) == 0) {
if (status.underrun > m_last_tx_underruns[i])
*underrun = true;
m_last_tx_underruns[i] = status.underrun;
}
thread_enable_cancel(true);
}
samplesWritten += rc;
return rc;
}
@@ -782,10 +591,14 @@ bool LMSDevice::updateAlignment(TIMESTAMP timestamp)
bool LMSDevice::setTxFreq(double wFreq, size_t chan)
{
LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz";
if (chan) {
LOG(ALERT) << "Invalid channel " << chan;
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";
LOG(ALERT) << "set Tx: " << wFreq << " failed!";
return false;
}
@@ -794,10 +607,13 @@ bool LMSDevice::setTxFreq(double wFreq, size_t chan)
bool LMSDevice::setRxFreq(double wFreq, size_t chan)
{
LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz";
if (chan) {
LOG(ALERT) << "Invalid channel " << chan;
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";
LOG(ALERT) << "set Rx: " << wFreq << " failed!";
return false;
}
@@ -809,13 +625,5 @@ RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
const std::vector < std::string > &tx_paths,
const std::vector < std::string > &rx_paths)
{
if (tx_sps != rx_sps) {
LOGC(DDEV, ERROR) << "LMS Requires tx_sps == rx_sps";
return NULL;
}
if (lo_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);
}

View File

@@ -1,10 +1,7 @@
/*
* Copyright 2018 sysmocom - s.f.m.c. GmbH
*
* 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.
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -23,7 +20,6 @@
#endif
#include "radioDevice.h"
#include "smpl_buf.h"
#include <sys/time.h>
#include <math.h>
@@ -32,41 +28,38 @@
#include <iostream>
#include <lime/LimeSuite.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:
* A^2 = I^2 + Q^2
* A^2 = (1/sqrt(2))^2 + (1/sqrt(2))^2
* A^2 = 1/2 + 1/2
* A^2 = 1 */
#define LIMESDR_TX_AMPL 0.707
#define LIMESDR_TX_AMPL 0.3
/** A class to handle a LimeSuite supported device */
class LMSDevice:public RadioDevice {
private:
static constexpr double masterClockRate = 52.0e6;
lms_device_t *m_lms_dev;
std::vector<lms_stream_t> m_lms_stream_rx;
std::vector<lms_stream_t> m_lms_stream_tx;
std::vector<smpl_buf *> rx_buffers;
std::vector<uint32_t> m_last_rx_underruns;
std::vector<uint32_t> m_last_rx_overruns;
std::vector<uint32_t> m_last_tx_underruns;
std::vector<uint32_t> m_last_tx_overruns;
double actualSampleRate; ///< the actual USRP sampling rate
unsigned long long samplesRead; ///< number of samples read from LMS
unsigned long long samplesWritten; ///< number of samples sent to LMS
bool started; ///< flag indicates LMS has started
bool skipRx; ///< set if LMS is transmit-only.
TIMESTAMP ts_initial, ts_offset;
double rxGain;
double maxTxGainClamp;
bool do_calib(size_t chan);
bool do_filters(size_t chan);
int get_ant_idx(const std::string & name, bool dir_tx, size_t chan);
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);
public:
@@ -74,7 +67,6 @@ public:
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();
/** Instantiate the LMS */
int open(const std::string &args, int ref, bool swap_channels);
@@ -199,6 +191,12 @@ public:
inline double getSampleRate() {
return actualSampleRate;
}
inline double numberRead() {
return samplesRead;
}
inline double numberWritten() {
return samplesWritten;
}
};
#endif // _LMS_DEVICE_H_

View File

@@ -1,6 +1,6 @@
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/..
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LMS_CFLAGS)
noinst_HEADERS = LMSDevice.h
@@ -8,4 +8,3 @@ noinst_HEADERS = LMSDevice.h
noinst_LTLIBRARIES = libdevice.la
libdevice_la_SOURCES = LMSDevice.cpp
libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la

View File

@@ -23,7 +23,6 @@
extern "C" {
#include "config_defs.h"
#include "osmo_signal.h"
}
#ifdef HAVE_CONFIG_H
@@ -59,6 +58,13 @@ class RadioDevice {
/** Initialize the USRP */
virtual int open(const std::string &args, int ref, bool swap_channels)=0;
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)
{ }
virtual ~RadioDevice() { }
/** Start the USRP */
@@ -162,6 +168,8 @@ class RadioDevice {
virtual double getTxFreq(size_t chan = 0) = 0;
virtual double getRxFreq(size_t chan = 0) = 0;
virtual double getSampleRate()=0;
virtual double numberRead()=0;
virtual double numberWritten()=0;
protected:
size_t tx_sps, rx_sps;
@@ -169,21 +177,6 @@ class RadioDevice {
size_t chans;
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;
}
}
bool set_antennas() {
unsigned int i;

View File

@@ -1,11 +1,8 @@
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/..
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(UHD_CFLAGS)
noinst_HEADERS = UHDDevice.h
noinst_LTLIBRARIES = libdevice.la
libdevice_la_SOURCES = UHDDevice.cpp
libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la

View File

@@ -6,8 +6,6 @@
*
* Author: Tom Tsou <tom.tsou@ettus.com>
*
* 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
@@ -25,9 +23,12 @@
#include <map>
#include "radioDevice.h"
#include "UHDDevice.h"
#include "Threads.h"
#include "Logger.h"
#include <uhd/version.hpp>
#include <uhd/property_tree.hpp>
#include <uhd/usrp/multi_usrp.hpp>
#include <uhd/utils/thread_priority.hpp>
#ifdef HAVE_CONFIG_H
#include "config.h"
@@ -35,9 +36,6 @@
#ifndef USE_UHD_3_11
#include <uhd/utils/msg.hpp>
#include <uhd/utils/thread_priority.hpp>
#else
#include <uhd/utils/thread.hpp>
#endif
#define USRP_TX_AMPL 0.3
@@ -58,6 +56,20 @@
*/
#define UMTRX_VGA1_DEF -18
enum uhd_dev_type {
USRP1,
USRP2,
B100,
B200,
B210,
B2XX_MCBTS,
E1XX,
E3XX,
X3XX,
UMTRX,
LIMESDR,
};
/*
* USRP version dependent device timings
*/
@@ -122,9 +134,192 @@ 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" } },
};
/*
Sample Buffer - Allows reading and writing of timed samples using osmo-trx
or UHD style timestamps. Time conversions are handled
internally or accessable through the static convert calls.
*/
class smpl_buf {
public:
/** Sample buffer constructor
@param len number of 32-bit samples the buffer should hold
@param rate sample clockrate
@param timestamp
*/
smpl_buf(size_t len, double rate);
~smpl_buf();
/** Query number of samples available for reading
@param timestamp time of first sample
@return number of available samples or error
*/
ssize_t avail_smpls(TIMESTAMP timestamp) const;
ssize_t avail_smpls(uhd::time_spec_t timestamp) const;
/** Read and write
@param buf pointer to buffer
@param len number of samples desired to read or write
@param timestamp time of first stample
@return number of actual samples read or written or error
*/
ssize_t read(void *buf, size_t len, TIMESTAMP timestamp);
ssize_t read(void *buf, size_t len, uhd::time_spec_t timestamp);
ssize_t write(void *buf, size_t len, TIMESTAMP timestamp);
ssize_t write(void *buf, size_t len, uhd::time_spec_t timestamp);
/** Buffer status string
@return a formatted string describing internal buffer state
*/
std::string str_status(size_t ts) const;
/** Formatted error string
@param code an error code
@return a formatted error string
*/
static std::string str_code(ssize_t code);
enum err_code {
ERROR_TIMESTAMP = -1,
ERROR_READ = -2,
ERROR_WRITE = -3,
ERROR_OVERFLOW = -4
};
private:
uint32_t *data;
size_t buf_len;
double clk_rt;
TIMESTAMP time_start;
TIMESTAMP time_end;
size_t data_start;
size_t data_end;
};
/*
uhd_device - UHD implementation of the Device interface. Timestamped samples
are sent to and received from the device. An intermediate buffer
on the receive side collects and aligns packets of samples.
Events and errors such as underruns are reported asynchronously
by the device and received in a separate thread.
*/
class uhd_device : public RadioDevice {
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();
void setPriority(float prio);
enum TxWindowType getWindowType() { return tx_window; }
int readSamples(std::vector<short *> &bufs, int len, bool *overrun,
TIMESTAMP timestamp, bool *underrun, unsigned *RSSI);
int writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
TIMESTAMP timestamp, bool isControl);
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 setTxGain(double db, size_t chan);
double maxTxGain(void) { return tx_gain_max; }
double minTxGain(void) { return tx_gain_min; }
double getTxFreq(size_t chan);
double getRxFreq(size_t chan);
double getRxFreq();
bool setRxAntenna(const std::string &ant, size_t chan);
std::string getRxAntenna(size_t chan);
bool setTxAntenna(const std::string &ant, size_t chan);
std::string getTxAntenna(size_t chan);
bool requiresRadioAlign();
GSM::Time minLatency();
inline double getSampleRate() { return tx_rate; }
inline double numberRead() { return rx_pkt_cnt; }
inline double numberWritten() { return 0; }
/** 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,
};
private:
uhd::usrp::multi_usrp::sptr usrp_dev;
uhd::tx_streamer::sptr tx_stream;
uhd::rx_streamer::sptr rx_stream;
enum TxWindowType tx_window;
enum uhd_dev_type dev_type;
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;
std::vector<double> tx_freqs, rx_freqs;
size_t tx_spp, rx_spp;
bool started;
bool aligned;
size_t rx_pkt_cnt;
size_t drop_cnt;
uhd::time_spec_t prev_ts;
TIMESTAMP ts_initial, ts_offset;
std::vector<smpl_buf *> rx_buffers;
void init_gains();
void set_channels(bool swap);
void set_rates();
bool parse_dev_type();
bool flush_recv(size_t num_pkts);
int check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls);
std::string str_code(uhd::rx_metadata_t metadata);
std::string str_code(uhd::async_metadata_t metadata);
uhd::tune_request_t select_freq(double wFreq, size_t chan, bool tx);
bool set_freq(double freq, size_t chan, bool tx);
Thread *async_event_thrd;
Mutex tune_lock;
};
void *async_event_loop(uhd_device *dev)
{
set_selfthread_name("UHDAsyncEvent");
dev->setPriority(0.43);
while (1) {
@@ -145,13 +340,13 @@ void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg)
{
switch (type) {
case uhd::msg::status:
LOGC(DDEV, INFO) << msg;
LOG(INFO) << msg;
break;
case uhd::msg::warning:
LOGC(DDEV, WARNING) << msg;
LOG(WARNING) << msg;
break;
case uhd::msg::error:
LOGC(DDEV, ERROR) << msg;
LOG(ERR) << msg;
break;
case uhd::msg::fastpath:
break;
@@ -159,6 +354,12 @@ void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg)
}
#endif
static void thread_enable_cancel(bool cancel)
{
cancel ? pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) :
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
}
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,
@@ -167,7 +368,7 @@ uhd_device::uhd_device(size_t tx_sps, size_t rx_sps,
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),
started(false), aligned(false), rx_pkt_cnt(0), drop_cnt(0),
prev_ts(0,0), ts_initial(0), ts_offset(0), async_event_thrd(NULL)
{
}
@@ -187,7 +388,7 @@ void uhd_device::init_gains()
if (dev_type == UMTRX) {
std::vector<std::string> gain_stages = usrp_dev->get_tx_gain_names(0);
if (gain_stages[0] == "VGA") {
LOGC(DDEV, WARNING) << "Update your UHD version for a proper Tx gain support";
LOG(WARNING) << "Update your UHD version for a proper Tx gain support";
}
if (gain_stages[0] == "VGA" || gain_stages[0] == "PA") {
range = usrp_dev->get_tx_gain_range();
@@ -203,23 +404,23 @@ void uhd_device::init_gains()
tx_gain_min = range.start();
tx_gain_max = range.stop();
}
LOGC(DDEV, INFO) << "Supported Tx gain range [" << tx_gain_min << "; " << tx_gain_max << "]";
LOG(INFO) << "Supported Tx gain range [" << tx_gain_min << "; " << tx_gain_max << "]";
range = usrp_dev->get_rx_gain_range();
rx_gain_min = range.start();
rx_gain_max = range.stop();
LOGC(DDEV, INFO) << "Supported Rx gain range [" << rx_gain_min << "; " << rx_gain_max << "]";
LOG(INFO) << "Supported Rx gain range [" << rx_gain_min << "; " << rx_gain_max << "]";
for (size_t i = 0; i < tx_gains.size(); i++) {
double gain = (tx_gain_min + tx_gain_max) / 2;
LOGC(DDEV, INFO) << "Default setting Tx gain for channel " << i << " to " << gain;
LOG(INFO) << "Default setting Tx gain for channel " << i << " to " << gain;
usrp_dev->set_tx_gain(gain, i);
tx_gains[i] = usrp_dev->get_tx_gain(i);
}
for (size_t i = 0; i < rx_gains.size(); i++) {
double gain = (rx_gain_min + rx_gain_max) / 2;
LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain;
LOG(INFO) << "Default setting Rx gain for channel " << i << " to " << gain;
usrp_dev->set_rx_gain(gain, i);
rx_gains[i] = usrp_dev->get_rx_gain(i);
}
@@ -243,7 +444,7 @@ void uhd_device::set_rates()
rx_rate = usrp_dev->get_rx_rate();
ts_offset = static_cast<TIMESTAMP>(desc.offset * rx_rate);
LOGC(DDEV, INFO) << "Rates configured for " << desc.str;
LOG(INFO) << "Rates configured for " << desc.str;
}
double uhd_device::setTxGain(double db, size_t chan)
@@ -252,7 +453,7 @@ double uhd_device::setTxGain(double db, size_t chan)
chan = 0;
if (chan >= tx_gains.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel" << chan;
LOG(ALERT) << "Requested non-existent channel" << chan;
return 0.0f;
}
@@ -275,25 +476,22 @@ 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)";
LOG(INFO) << "Set TX gain to " << tx_gains[chan] << "dB (asked for " << db << "dB)";
return tx_gains[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;
LOG(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)";
LOG(INFO) << "Set RX gain to " << rx_gains[chan] << "dB (asked for " << db << "dB)";
return rx_gains[chan];
}
@@ -304,7 +502,7 @@ double uhd_device::getRxGain(size_t chan)
chan = 0;
if (chan >= rx_gains.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return 0.0f;
}
@@ -352,7 +550,7 @@ bool uhd_device::parse_dev_type()
mapIter++;
}
LOGC(DDEV, ALERT) << "Unsupported device " << devString;
LOG(ALERT) << "Unsupported device " << devString;
return false;
}
@@ -423,16 +621,16 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
uhd::device_addr_t addr(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 << "'";
LOG(ALERT) << "No UHD devices found with address '" << args << "'";
return -1;
}
// Use the first found device
LOGC(DDEV, INFO) << "Using discovered UHD device " << dev_addrs[0].to_string();
LOG(INFO) << "Using discovered UHD device " << dev_addrs[0].to_string();
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();
} catch(...) {
LOG(ALERT) << "UHD make failed, device " << args;
return -1;
}
@@ -441,19 +639,19 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
return -1;
if ((dev_type == E3XX) && !uhd_e3xx_version_chk()) {
LOGC(DDEV, ALERT) << "E3XX requires UHD 003.009.000 or greater";
LOG(ALERT) << "E3XX requires UHD 003.009.000 or greater";
return -1;
}
try {
set_channels(swap_channels);
} catch (const std::exception &e) {
LOGC(DDEV, ALERT) << "Channel setting failed - " << e.what();
LOG(ALERT) << "Channel setting failed - " << e.what();
return -1;
}
if (!set_antennas()) {
LOGC(DDEV, ALERT) << "UHD antenna setting failed";
LOG(ALERT) << "UHD antenna setting failed";
return -1;
}
@@ -474,7 +672,7 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
refstr = "gpsdo";
break;
default:
LOGC(DDEV, ALERT) << "Invalid reference type";
LOG(ALERT) << "Invalid reference type";
return -1;
}
@@ -483,7 +681,7 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
try {
set_rates();
} catch (const std::exception &e) {
LOGC(DDEV, ALERT) << "UHD rate setting failed - " << e.what();
LOG(ALERT) << "UHD rate setting failed - " << e.what();
return -1;
}
@@ -516,18 +714,13 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
// Create receive buffer
size_t buf_len = SAMPLE_BUF_SZ / sizeof(uint32_t);
for (size_t i = 0; i < rx_buffers.size(); i++)
rx_buffers[i] = new smpl_buf(buf_len);
// Create vector buffer
pkt_bufs = std::vector<std::vector<short> >(chans, std::vector<short>(2 * rx_spp));
for (size_t i = 0; i < pkt_bufs.size(); i++)
pkt_ptrs.push_back(&pkt_bufs[i].front());
rx_buffers[i] = new smpl_buf(buf_len, rx_rate);
// Initialize and shadow gain values
init_gains();
// Print configuration
LOGC(DDEV, INFO) << "\n" << usrp_dev->get_pp_string();
LOG(INFO) << "\n" << usrp_dev->get_pp_string();
if (iface == MULTI_ARFCN)
return MULTI_ARFCN;
@@ -556,6 +749,13 @@ bool uhd_device::flush_recv(size_t num_pkts)
size_t num_smpls;
float timeout = UHD_RESTART_TIMEOUT;
std::vector<std::vector<short> >
pkt_bufs(chans, std::vector<short>(2 * rx_spp));
std::vector<short *> pkt_ptrs;
for (size_t i = 0; i < pkt_bufs.size(); i++)
pkt_ptrs.push_back(&pkt_bufs[i].front());
ts_initial = 0;
while (!ts_initial || (num_pkts-- > 0)) {
num_smpls = rx_stream->recv(pkt_ptrs, rx_spp, md,
@@ -563,7 +763,7 @@ bool uhd_device::flush_recv(size_t num_pkts)
if (!num_smpls) {
switch (md.error_code) {
case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT:
LOGC(DDEV, ALERT) << "Device timed out";
LOG(ALERT) << "Device timed out";
return false;
default:
continue;
@@ -573,7 +773,7 @@ bool uhd_device::flush_recv(size_t num_pkts)
ts_initial = md.time_spec.to_ticks(rx_rate);
}
LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl;
LOG(INFO) << "Initial timestamp " << ts_initial << std::endl;
return true;
}
@@ -598,10 +798,10 @@ bool uhd_device::restart()
bool uhd_device::start()
{
LOGC(DDEV, INFO) << "Starting USRP...";
LOG(INFO) << "Starting USRP...";
if (started) {
LOGC(DDEV, ERROR) << "Device already started";
LOG(ERR) << "Device already started";
return false;
}
@@ -619,7 +819,7 @@ bool uhd_device::start()
// Display usrp time
double time_now = usrp_dev->get_time_now().get_real_secs();
LOGC(DDEV, INFO) << "The current time is " << time_now << " seconds";
LOG(INFO) << "The current time is " << time_now << " seconds";
started = true;
return true;
@@ -652,11 +852,11 @@ void uhd_device::setPriority(float prio)
int uhd_device::check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls)
{
if (!num_smpls) {
LOGC(DDEV, ERROR) << str_code(md);
LOG(ERR) << str_code(md);
switch (md.error_code) {
case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT:
LOGC(DDEV, ALERT) << "UHD: Receive timed out";
LOG(ALERT) << "UHD: Receive timed out";
return ERROR_TIMEOUT;
case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW:
case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND:
@@ -669,14 +869,14 @@ int uhd_device::check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls)
// Missing timestamp
if (!md.has_time_spec) {
LOGC(DDEV, ALERT) << "UHD: Received packet missing timestamp";
LOG(ALERT) << "UHD: Received packet missing timestamp";
return ERROR_UNRECOVERABLE;
}
// Monotonicity check
if (md.time_spec < prev_ts) {
LOGC(DDEV, ALERT) << "UHD: Loss of monotonic time";
LOGC(DDEV, ALERT) << "Current time: " << md.time_spec.get_real_secs() << ", "
LOG(ALERT) << "UHD: Loss of monotonic time";
LOG(ALERT) << "Current time: " << md.time_spec.get_real_secs() << ", "
<< "Previous time: " << prev_ts.get_real_secs();
return ERROR_TIMING;
}
@@ -699,7 +899,7 @@ int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
uhd::rx_metadata_t metadata;
if (bufs.size() != chans) {
LOGC(DDEV, ALERT) << "Invalid channel combination " << bufs.size();
LOG(ALERT) << "Invalid channel combination " << bufs.size();
return -1;
}
@@ -710,16 +910,24 @@ int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
timestamp += ts_offset;
ts = uhd::time_spec_t::from_ticks(timestamp, rx_rate);
LOGC(DDEV, DEBUG) << "Requested timestamp = " << ts.get_real_secs();
LOG(DEBUG) << "Requested timestamp = " << ts.get_real_secs();
// 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);
LOG(ERR) << rx_buffers[0]->str_code(rc);
LOG(ERR) << rx_buffers[0]->str_status(timestamp);
return 0;
}
// Create vector buffer
std::vector<std::vector<short> >
pkt_bufs(chans, std::vector<short>(2 * rx_spp));
std::vector<short *> pkt_ptrs;
for (size_t i = 0; i < pkt_bufs.size(); i++)
pkt_ptrs.push_back(&pkt_bufs[i].front());
// Receive samples from the usrp until we have enough
while (rx_buffers[0]->avail_smpls(timestamp) < len) {
thread_enable_cancel(false);
@@ -727,12 +935,14 @@ int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
metadata, 0.1, true);
thread_enable_cancel(true);
rx_pkt_cnt++;
// Check for errors
rc = check_rx_md_err(metadata, num_smpls);
switch (rc) {
case ERROR_UNRECOVERABLE:
LOGC(DDEV, ALERT) << "UHD: Version " << uhd::get_version_string();
LOGC(DDEV, ALERT) << "UHD: Unrecoverable error, exiting...";
LOG(ALERT) << "UHD: Version " << uhd::get_version_string();
LOG(ALERT) << "UHD: Unrecoverable error, exiting...";
exit(-1);
case ERROR_TIMEOUT:
// Assume stopping condition
@@ -744,17 +954,17 @@ int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
}
ts = metadata.time_spec;
LOGC(DDEV, DEBUG) << "Received timestamp = " << ts.get_real_secs();
LOG(DEBUG) << "Received timestamp = " << ts.get_real_secs();
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));
metadata.time_spec);
// 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);
LOG(ERR) << rx_buffers[i]->str_code(rc);
LOG(ERR) << rx_buffers[i]->str_status(timestamp);
if (rc != smpl_buf::ERROR_OVERFLOW)
return 0;
}
@@ -765,8 +975,8 @@ 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]->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);
LOG(ERR) << rx_buffers[i]->str_code(rc);
LOG(ERR) << rx_buffers[i]->str_status(timestamp);
return 0;
}
}
@@ -787,12 +997,12 @@ int uhd_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun
// No control packets
if (isControl) {
LOGC(DDEV, ERROR) << "Control packets not supported";
LOG(ERR) << "Control packets not supported";
return 0;
}
if (bufs.size() != chans) {
LOGC(DDEV, ALERT) << "Invalid channel combination " << bufs.size();
LOG(ALERT) << "Invalid channel combination " << bufs.size();
return -1;
}
@@ -801,14 +1011,14 @@ int uhd_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun
drop_cnt++;
if (drop_cnt == 1) {
LOGC(DDEV, DEBUG) << "Aligning transmitter: stop burst";
LOG(DEBUG) << "Aligning transmitter: stop burst";
*underrun = true;
metadata.end_of_burst = true;
} else if (drop_cnt < 30) {
LOGC(DDEV, DEBUG) << "Aligning transmitter: packet advance";
LOG(DEBUG) << "Aligning transmitter: packet advance";
return len;
} else {
LOGC(DDEV, DEBUG) << "Aligning transmitter: start burst";
LOG(DEBUG) << "Aligning transmitter: start burst";
metadata.start_of_burst = true;
aligned = true;
drop_cnt = 0;
@@ -820,7 +1030,7 @@ int uhd_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun
thread_enable_cancel(true);
if (num_smpls != (unsigned) len) {
LOGC(DDEV, ALERT) << "UHD: Device send timed out";
LOG(ALERT) << "UHD: Device send timed out";
}
return num_smpls;
@@ -856,7 +1066,7 @@ uhd::tune_request_t uhd_device::select_freq(double freq, size_t chan, bool tx)
return uhd::tune_request_t(freq, lo_offset);
} else if ((dev_type != B210) || (chans > 2) || (chan > 1)) {
LOGC(DDEV, ALERT) << chans << " channels unsupported";
LOG(ALERT) << chans << " channels unsupported";
return treq;
}
@@ -872,7 +1082,7 @@ uhd::tune_request_t uhd_device::select_freq(double freq, size_t chan, bool tx)
/* Find center frequency between channels */
rf_spread = fabs(freqs[!chan] - freq);
if (rf_spread > dev_param_map.at(dev_key(B210, tx_sps, rx_sps)).mcr) {
LOGC(DDEV, ALERT) << rf_spread << "Hz tuning spread not supported\n";
LOG(ALERT) << rf_spread << "Hz tuning spread not supported\n";
return treq;
}
@@ -898,7 +1108,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;
LOG(INFO) << "\n" << tres.to_pp_string() << std::endl;
if ((chans == 1) || ((chans == 2) && dev_type == UMTRX))
return true;
@@ -918,7 +1128,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;
LOG(INFO) << "\n" << tres.to_pp_string() << std::endl;
}
return true;
@@ -927,7 +1137,7 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx)
bool uhd_device::setTxFreq(double wFreq, size_t chan)
{
if (chan >= tx_freqs.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return false;
}
ScopedLock lock(tune_lock);
@@ -938,7 +1148,7 @@ bool uhd_device::setTxFreq(double wFreq, size_t chan)
bool uhd_device::setRxFreq(double wFreq, size_t chan)
{
if (chan >= rx_freqs.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return false;
}
ScopedLock lock(tune_lock);
@@ -949,7 +1159,7 @@ bool uhd_device::setRxFreq(double wFreq, size_t chan)
double uhd_device::getTxFreq(size_t chan)
{
if (chan >= tx_freqs.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return 0.0;
}
@@ -959,7 +1169,7 @@ double uhd_device::getTxFreq(size_t chan)
double uhd_device::getRxFreq(size_t chan)
{
if (chan >= rx_freqs.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return 0.0;
}
@@ -970,23 +1180,23 @@ bool uhd_device::setRxAntenna(const std::string &ant, size_t chan)
{
std::vector<std::string> avail;
if (chan >= rx_paths.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return false;
}
avail = usrp_dev->get_rx_antennas(chan);
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: ";
LOG(ALERT) << "Requested non-existent Rx antenna " << ant << " on channel " << chan;
LOG(INFO) << "Available Rx antennas: ";
for (std::vector<std::string>::const_iterator i = avail.begin(); i != avail.end(); ++i)
LOGC(DDEV, INFO) << "- '" << *i << "'";
LOG(INFO) << "- '" << *i << "'";
return false;
}
usrp_dev->set_rx_antenna(ant, chan);
rx_paths[chan] = usrp_dev->get_rx_antenna(chan);
if (ant != rx_paths[chan]) {
LOGC(DDEV, ALERT) << "Failed setting antenna " << ant << " on channel " << chan << ", got instead " << rx_paths[chan];
LOG(ALERT) << "Failed setting antenna " << ant << " on channel " << chan << ", got instead " << rx_paths[chan];
return false;
}
@@ -996,7 +1206,7 @@ bool uhd_device::setRxAntenna(const std::string &ant, size_t chan)
std::string uhd_device::getRxAntenna(size_t chan)
{
if (chan >= rx_paths.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return "";
}
return usrp_dev->get_rx_antenna(chan);
@@ -1006,23 +1216,23 @@ bool uhd_device::setTxAntenna(const std::string &ant, size_t chan)
{
std::vector<std::string> avail;
if (chan >= tx_paths.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return false;
}
avail = usrp_dev->get_tx_antennas(chan);
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: ";
LOG(ALERT) << "Requested non-existent Tx antenna " << ant << " on channel " << chan;
LOG(INFO) << "Available Tx antennas: ";
for (std::vector<std::string>::const_iterator i = avail.begin(); i != avail.end(); ++i)
LOGC(DDEV, INFO) << "- '" << *i << "'";
LOG(INFO) << "- '" << *i << "'";
return false;
}
usrp_dev->set_tx_antenna(ant, chan);
tx_paths[chan] = usrp_dev->get_tx_antenna(chan);
if (ant != tx_paths[chan]) {
LOGC(DDEV, ALERT) << "Failed setting antenna " << ant << " on channel " << chan << ", got instead " << tx_paths[chan];
LOG(ALERT) << "Failed setting antenna " << ant << " on channel " << chan << ", got instead " << tx_paths[chan];
return false;
}
@@ -1032,7 +1242,7 @@ bool uhd_device::setTxAntenna(const std::string &ant, size_t chan)
std::string uhd_device::getTxAntenna(size_t chan)
{
if (chan >= tx_paths.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return "";
}
return usrp_dev->get_tx_antenna(chan);
@@ -1100,7 +1310,7 @@ bool uhd_device::recv_async_msg()
if ((md.event_code != uhd::async_metadata_t::EVENT_CODE_UNDERFLOW) &&
(md.event_code != uhd::async_metadata_t::EVENT_CODE_TIME_ERROR)) {
LOGC(DDEV, ERROR) << str_code(md);
LOG(ERR) << str_code(md);
}
}
@@ -1176,6 +1386,166 @@ std::string uhd_device::str_code(uhd::async_metadata_t metadata)
return ost.str();
}
smpl_buf::smpl_buf(size_t len, double rate)
: buf_len(len), clk_rt(rate),
time_start(0), time_end(0), data_start(0), data_end(0)
{
data = new uint32_t[len];
}
smpl_buf::~smpl_buf()
{
delete[] data;
}
ssize_t smpl_buf::avail_smpls(TIMESTAMP timestamp) const
{
if (timestamp < time_start)
return ERROR_TIMESTAMP;
else if (timestamp >= time_end)
return 0;
else
return time_end - timestamp;
}
ssize_t smpl_buf::avail_smpls(uhd::time_spec_t timespec) const
{
return avail_smpls(timespec.to_ticks(clk_rt));
}
ssize_t smpl_buf::read(void *buf, size_t len, TIMESTAMP timestamp)
{
int type_sz = 2 * sizeof(short);
// Check for valid read
if (timestamp < time_start)
return ERROR_TIMESTAMP;
if (timestamp >= time_end)
return 0;
if (len >= buf_len)
return ERROR_READ;
// How many samples should be copied
size_t num_smpls = time_end - timestamp;
if (num_smpls > len)
num_smpls = len;
// Starting index
size_t read_start = (data_start + (timestamp - time_start)) % buf_len;
// Read it
if (read_start + num_smpls < buf_len) {
size_t numBytes = len * type_sz;
memcpy(buf, data + read_start, numBytes);
} else {
size_t first_cp = (buf_len - read_start) * type_sz;
size_t second_cp = len * type_sz - first_cp;
memcpy(buf, data + read_start, first_cp);
memcpy((char*) buf + first_cp, data, second_cp);
}
data_start = (read_start + len) % buf_len;
time_start = timestamp + len;
if (time_start > time_end)
return ERROR_READ;
else
return num_smpls;
}
ssize_t smpl_buf::read(void *buf, size_t len, uhd::time_spec_t ts)
{
return read(buf, len, ts.to_ticks(clk_rt));
}
ssize_t smpl_buf::write(void *buf, size_t len, TIMESTAMP timestamp)
{
int type_sz = 2 * sizeof(short);
// Check for valid write
if ((len == 0) || (len >= buf_len))
return ERROR_WRITE;
if ((timestamp + len) <= time_end)
return ERROR_TIMESTAMP;
if (timestamp < time_end) {
LOG(ERR) << "Overwriting old buffer data: timestamp="<<timestamp<<" time_end="<<time_end;
uhd::time_spec_t ts = uhd::time_spec_t::from_ticks(timestamp, clk_rt);
LOG(DEBUG) << "Requested timestamp = " << timestamp << " (real_sec=" << std::fixed << ts.get_real_secs() << " = " << ts.to_ticks(clk_rt) << ") rate=" << clk_rt;
// Do not return error here, because it's a rounding error and is not fatal
}
if (timestamp > time_end && time_end != 0) {
LOG(ERR) << "Skipping buffer data: timestamp="<<timestamp<<" time_end="<<time_end;
uhd::time_spec_t ts = uhd::time_spec_t::from_ticks(timestamp, clk_rt);
LOG(DEBUG) << "Requested timestamp = " << timestamp << " (real_sec=" << std::fixed << ts.get_real_secs() << " = " << ts.to_ticks(clk_rt) << ") rate=" << clk_rt;
// Do not return error here, because it's a rounding error and is not fatal
}
// Starting index
size_t write_start = (data_start + (timestamp - time_start)) % buf_len;
// Write it
if ((write_start + len) < buf_len) {
size_t numBytes = len * type_sz;
memcpy(data + write_start, buf, numBytes);
} else {
size_t first_cp = (buf_len - write_start) * type_sz;
size_t second_cp = len * type_sz - first_cp;
memcpy(data + write_start, buf, first_cp);
memcpy(data, (char*) buf + first_cp, second_cp);
}
data_end = (write_start + len) % buf_len;
time_end = timestamp + len;
if (!data_start)
data_start = write_start;
if (((write_start + len) > buf_len) && (data_end > data_start))
return ERROR_OVERFLOW;
else if (time_end <= time_start)
return ERROR_WRITE;
else
return len;
}
ssize_t smpl_buf::write(void *buf, size_t len, uhd::time_spec_t ts)
{
return write(buf, len, ts.to_ticks(clk_rt));
}
std::string smpl_buf::str_status(size_t ts) const
{
std::ostringstream ost("Sample buffer: ");
ost << "timestamp = " << ts;
ost << ", length = " << buf_len;
ost << ", time_start = " << time_start;
ost << ", time_end = " << time_end;
ost << ", data_start = " << data_start;
ost << ", data_end = " << data_end;
return ost.str();
}
std::string smpl_buf::str_code(ssize_t code)
{
switch (code) {
case ERROR_TIMESTAMP:
return "Sample buffer: Requested timestamp is not valid";
case ERROR_READ:
return "Sample buffer: Read error";
case ERROR_WRITE:
return "Sample buffer: Write error";
case ERROR_OVERFLOW:
return "Sample buffer: Overrun";
default:
return "Sample buffer: Unknown error";
}
}
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,

View File

@@ -1,175 +0,0 @@
/*
* Device support for Ettus Research UHD driver
*
* Copyright 2010,2011 Free Software Foundation, Inc.
* Copyright (C) 2015 Ettus Research LLC
* Copyright 2019 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* Author: Tom Tsou <tom.tsou@ettus.com>
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* See the COPYING file in the main directory for details.
*/
#pragma once
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "radioDevice.h"
#include "smpl_buf.h"
#include <uhd/version.hpp>
#include <uhd/property_tree.hpp>
#include <uhd/usrp/multi_usrp.hpp>
enum uhd_dev_type {
USRP1,
USRP2,
B100,
B200,
B210,
B2XX_MCBTS,
E1XX,
E3XX,
X3XX,
UMTRX,
LIMESDR,
};
/*
uhd_device - UHD implementation of the Device interface. Timestamped samples
are sent to and received from the device. An intermediate buffer
on the receive side collects and aligns packets of samples.
Events and errors such as underruns are reported asynchronously
by the device and received in a separate thread.
*/
class uhd_device : public RadioDevice {
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();
void setPriority(float prio);
enum TxWindowType getWindowType() { return tx_window; }
int readSamples(std::vector<short *> &bufs, int len, bool *overrun,
TIMESTAMP timestamp, bool *underrun, unsigned *RSSI);
int writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
TIMESTAMP timestamp, bool isControl);
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 setTxGain(double db, size_t chan);
double maxTxGain(void) { return tx_gain_max; }
double minTxGain(void) { return tx_gain_min; }
double getTxFreq(size_t chan);
double getRxFreq(size_t chan);
double getRxFreq();
bool setRxAntenna(const std::string &ant, size_t chan);
std::string getRxAntenna(size_t chan);
bool setTxAntenna(const std::string &ant, size_t chan);
std::string getTxAntenna(size_t chan);
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,
};
private:
uhd::usrp::multi_usrp::sptr usrp_dev;
uhd::tx_streamer::sptr tx_stream;
uhd::rx_streamer::sptr rx_stream;
enum TxWindowType tx_window;
enum uhd_dev_type dev_type;
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;
std::vector<double> tx_freqs, rx_freqs;
size_t tx_spp, rx_spp;
bool started;
bool aligned;
size_t drop_cnt;
uhd::time_spec_t prev_ts;
TIMESTAMP ts_initial, ts_offset;
std::vector<smpl_buf *> rx_buffers;
/* Sample buffers used to receive samples from UHD: */
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 parse_dev_type();
bool flush_recv(size_t num_pkts);
int check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls);
std::string str_code(uhd::rx_metadata_t metadata);
std::string str_code(uhd::async_metadata_t metadata);
uhd::tune_request_t select_freq(double wFreq, size_t chan, bool tx);
bool set_freq(double freq, size_t chan, bool tx);
Thread *async_event_thrd;
Mutex tune_lock;
};

View File

@@ -1,14 +1,8 @@
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/..
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(USRP_CFLAGS)
rev2dir = $(datadir)/usrp/rev2
rev4dir = $(datadir)/usrp/rev4
dist_rev2_DATA = std_inband.rbf
dist_rev4_DATA = std_inband.rbf
noinst_HEADERS = USRPDevice.h
noinst_LTLIBRARIES = libdevice.la

View File

@@ -1,8 +1,6 @@
/*
* Copyright 2008, 2009 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
@@ -66,7 +64,7 @@ USRPDevice::USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface,
const std::vector<std::string>& rx_paths):
RadioDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths)
{
LOGC(DDEV, INFO) << "creating USRP device...";
LOG(INFO) << "creating USRP device...";
decimRate = (unsigned int) round(masterClockRate/((GSMRATE) * (double) tx_sps));
actualSampleRate = masterClockRate/decimRate;
@@ -97,11 +95,12 @@ int USRPDevice::open(const std::string &, int, bool)
{
writeLock.unlock();
LOGC(DDEV, INFO) << "opening USRP device..";
LOG(INFO) << "opening USRP device..";
#ifndef SWLOOPBACK
string rbf = "std_inband.rbf";
//string rbf = "inband_1rxhb_1tx.rbf";
m_uRx.reset();
if (!skipRx) {
try {
m_uRx = usrp_standard_rx_sptr(usrp_standard_rx::make(
0, decimRate * tx_sps, 1, -1,
@@ -111,18 +110,19 @@ int USRPDevice::open(const std::string &, int, bool)
}
catch(...) {
LOGC(DDEV, ALERT) << "make failed on Rx";
LOG(ALERT) << "make failed on Rx";
m_uRx.reset();
return -1;
}
if (m_uRx->fpga_master_clock_freq() != masterClockRate)
{
LOGC(DDEV, ALERT) << "WRONG FPGA clock freq = " << m_uRx->fpga_master_clock_freq()
LOG(ALERT) << "WRONG FPGA clock freq = " << m_uRx->fpga_master_clock_freq()
<< ", desired clock freq = " << masterClockRate;
m_uRx.reset();
return -1;
}
}
try {
m_uTx = usrp_standard_tx_sptr(usrp_standard_tx::make(
@@ -132,20 +132,20 @@ int USRPDevice::open(const std::string &, int, bool)
}
catch(...) {
LOGC(DDEV, ALERT) << "make failed on Tx";
LOG(ALERT) << "make failed on Tx";
m_uTx.reset();
return -1;
}
if (m_uTx->fpga_master_clock_freq() != masterClockRate)
{
LOGC(DDEV, ALERT) << "WRONG FPGA clock freq = " << m_uTx->fpga_master_clock_freq()
LOG(ALERT) << "WRONG FPGA clock freq = " << m_uTx->fpga_master_clock_freq()
<< ", desired clock freq = " << masterClockRate;
m_uTx.reset();
return -1;
}
m_uRx->stop();
if (!skipRx) m_uRx->stop();
m_uTx->stop();
#endif
@@ -175,6 +175,8 @@ int USRPDevice::open(const std::string &, int, bool)
m_dbTx = m_uTx->selected_subdev(txSubdevSpec);
m_dbRx = m_uRx->selected_subdev(rxSubdevSpec);
samplesRead = 0;
samplesWritten = 0;
started = false;
return NORMAL;
@@ -184,12 +186,12 @@ int USRPDevice::open(const std::string &, int, bool)
bool USRPDevice::start()
{
LOGC(DDEV, INFO) << "starting USRP...";
LOG(INFO) << "starting USRP...";
#ifndef SWLOOPBACK
if (!m_uRx) return false;
if (!m_uRx && !skipRx) return false;
if (!m_uTx) return false;
m_uRx->stop();
if (!skipRx) m_uRx->stop();
m_uTx->stop();
writeLock.lock();
@@ -219,7 +221,10 @@ bool USRPDevice::start()
isAligned = false;
if (!skipRx)
started = (m_uRx->start() && m_uTx->start());
else
started = m_uTx->start();
return started;
#else
gettimeofday(&lastReadTime,NULL);
@@ -265,7 +270,7 @@ double USRPDevice::minRxGain()
double USRPDevice::setTxGain(double dB, size_t chan)
{
if (chan) {
LOGC(DDEV, ALERT) << "Invalid channel " << chan;
LOG(ALERT) << "Invalid channel " << chan;
return 0.0;
}
@@ -275,10 +280,10 @@ double USRPDevice::setTxGain(double dB, size_t chan)
if (dB < minTxGain())
dB = minTxGain();
LOGC(DDEV, NOTICE) << "Setting TX gain to " << dB << " dB.";
LOG(NOTICE) << "Setting TX gain to " << dB << " dB.";
if (!m_dbTx->set_gain(dB))
LOGC(DDEV, ERR) << "Error setting TX gain";
LOG(ERR) << "Error setting TX gain";
writeLock.unlock();
@@ -289,7 +294,7 @@ double USRPDevice::setTxGain(double dB, size_t chan)
double USRPDevice::setRxGain(double dB, size_t chan)
{
if (chan) {
LOGC(DDEV, ALERT) << "Invalid channel " << chan;
LOG(ALERT) << "Invalid channel " << chan;
return 0.0;
}
@@ -301,10 +306,10 @@ double USRPDevice::setRxGain(double dB, size_t chan)
if (dB < minRxGain())
dB = minRxGain();
LOGC(DDEV, NOTICE) << "Setting RX gain to " << dB << " dB.";
LOG(NOTICE) << "Setting RX gain to " << dB << " dB.";
if (!m_dbRx->set_gain(dB))
LOGC(DDEV, ERR) << "Error setting RX gain";
LOG(ERR) << "Error setting RX gain";
writeLock.unlock();
@@ -314,40 +319,40 @@ double USRPDevice::setRxGain(double dB, size_t chan)
bool USRPDevice::setRxAntenna(const std::string &ant, size_t chan)
{
if (chan >= rx_paths.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return false;
}
LOGC(DDEV, ALERT) << "Not implemented";
LOG(ALERT) << "Not implemented";
return true;
}
std::string USRPDevice::getRxAntenna(size_t chan)
{
if (chan >= rx_paths.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return "";
}
LOGC(DDEV, ALERT) << "Not implemented";
LOG(ALERT) << "Not implemented";
return "";
}
bool USRPDevice::setTxAntenna(const std::string &ant, size_t chan)
{
if (chan >= tx_paths.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return false;
}
LOGC(DDEV, ALERT) << "Not implemented";
LOG(ALERT) << "Not implemented";
return true;
}
std::string USRPDevice::getTxAntenna(size_t chan)
{
if (chan >= tx_paths.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
LOG(ALERT) << "Requested non-existent channel " << chan;
return "";
}
LOGC(DDEV, ALERT) << "Not implemented";
LOG(ALERT) << "Not implemented";
return "";
}
@@ -377,7 +382,7 @@ int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
return len;
}
*underrun = false;
if (underrun) *underrun = false;
uint32_t readBuf[2000];
@@ -393,18 +398,18 @@ int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
// read USRP packets, parse and save A/D data as needed
readLen = m_uRx->read((void *)readBuf,readLen,overrun);
for (int pktNum = 0; pktNum < (readLen/512); pktNum++) {
for(int pktNum = 0; pktNum < (readLen/512); pktNum++) {
// tmpBuf points to start of a USB packet
uint32_t* tmpBuf = (uint32_t *) (readBuf+pktNum*512/4);
TIMESTAMP pktTimestamp = usrp_to_host_u32(tmpBuf[1]);
uint32_t word0 = usrp_to_host_u32(tmpBuf[0]);
uint32_t chan = (word0 >> 16) & 0x1f;
unsigned payloadSz = word0 & 0x1ff;
LOGC(DDEV, DEBUG) << "first two bytes: " << hex << word0 << " " << dec << pktTimestamp;
LOG(DEBUG) << "first two bytes: " << hex << word0 << " " << dec << pktTimestamp;
bool incrementHi32 = ((lastPktTimestamp & 0x0ffffffffll) > pktTimestamp);
if (incrementHi32 && (timeStart!=0)) {
LOGC(DDEV, DEBUG) << "high 32 increment!!!";
LOG(DEBUG) << "high 32 increment!!!";
hi32Timestamp++;
}
pktTimestamp = (((TIMESTAMP) hi32Timestamp) << 32) | pktTimestamp;
@@ -416,19 +421,19 @@ int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
if ((word2 >> 16) == ((0x01 << 8) | 0x02)) {
timestamp -= timestampOffset;
timestampOffset = pktTimestamp - pingTimestamp + pingOffset;
LOGC(DDEV, DEBUG) << "updating timestamp offset to: " << timestampOffset;
LOG(DEBUG) << "updating timestamp offset to: " << timestampOffset;
timestamp += timestampOffset;
isAligned = true;
}
continue;
}
if (chan != 0) {
LOGC(DDEV, DEBUG) << "chan: " << chan << ", timestamp: " << pktTimestamp << ", sz:" << payloadSz;
LOG(DEBUG) << "chan: " << chan << ", timestamp: " << pktTimestamp << ", sz:" << payloadSz;
continue;
}
if ((word0 >> 28) & 0x04) {
*underrun = true;
LOGC(DDEV, DEBUG) << "UNDERRUN in TRX->USRP interface";
if (underrun) *underrun = true;
LOG(DEBUG) << "UNDERRUN in TRX->USRP interface";
}
if (RSSI) *RSSI = (word0 >> 21) & 0x3f;
@@ -449,7 +454,7 @@ int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
if (pktTimestamp + payloadSz/2/sizeof(short) > timeEnd)
timeEnd = pktTimestamp+payloadSz/2/sizeof(short);
LOGC(DDEV, DEBUG) << "timeStart: " << timeStart << ", timeEnd: " << timeEnd << ", pktTimestamp: " << pktTimestamp;
LOG(DEBUG) << "timeStart: " << timeStart << ", timeEnd: " << timeEnd << ", pktTimestamp: " << pktTimestamp;
}
}
@@ -457,14 +462,14 @@ int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
// copy desired data to buf
unsigned bufStart = dataStart+(timestamp-timeStart);
if (bufStart + len < currDataSize/2) {
LOGC(DDEV, DEBUG) << "bufStart: " << bufStart;
LOG(DEBUG) << "bufStart: " << bufStart;
memcpy(buf,data+bufStart*2,len*2*sizeof(short));
memset(data+bufStart*2,0,len*2*sizeof(short));
}
else {
LOGC(DDEV, DEBUG) << "len: " << len << ", currDataSize/2: " << currDataSize/2 << ", bufStart: " << bufStart;
LOG(DEBUG) << "len: " << len << ", currDataSize/2: " << currDataSize/2 << ", bufStart: " << bufStart;
unsigned firstLength = (currDataSize/2-bufStart);
LOGC(DDEV, DEBUG) << "firstLength: " << firstLength;
LOG(DEBUG) << "firstLength: " << firstLength;
memcpy(buf,data+bufStart*2,firstLength*2*sizeof(short));
memset(data+bufStart*2,0,firstLength*2*sizeof(short));
memcpy(buf+firstLength*2,data,(len-firstLength)*2*sizeof(short));
@@ -505,6 +510,7 @@ int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
gettimeofday(&lastReadTime,NULL);
firstRead = true;
}
samplesRead += numSamples;
return numSamples;
#endif
@@ -554,12 +560,14 @@ int USRPDevice::writeSamples(std::vector<short *> &bufs, int len,
}
m_uTx->write((const void*) outPkt,sizeof(uint32_t)*128*numPkts,NULL);
samplesWritten += len/2/sizeof(short);
writeLock.unlock();
return len/2/sizeof(short);
#else
int retVal = len;
memcpy(loopbackBuffer+loopbackBufferSize,buf,sizeof(short)*2*len);
samplesWritten += retVal;
loopbackBufferSize += retVal*2;
return retVal;
@@ -591,19 +599,19 @@ bool USRPDevice::setTxFreq(double wFreq, size_t chan)
usrp_tune_result result;
if (chan) {
LOGC(DDEV, ALERT) << "Invalid channel " << chan;
LOG(ALERT) << "Invalid channel " << chan;
return false;
}
if (m_uTx->tune(txSubdevSpec.side, m_dbTx, wFreq, &result)) {
LOGC(DDEV, INFO) << "set TX: " << wFreq << std::endl
LOG(INFO) << "set TX: " << wFreq << std::endl
<< " baseband freq: " << result.baseband_freq << std::endl
<< " DDC freq: " << result.dxc_freq << std::endl
<< " residual freq: " << result.residual_freq;
return true;
}
else {
LOGC(DDEV, ALERT) << "set TX: " << wFreq << " failed" << std::endl
LOG(ALERT) << "set TX: " << wFreq << "failed" << std::endl
<< " baseband freq: " << result.baseband_freq << std::endl
<< " DDC freq: " << result.dxc_freq << std::endl
<< " residual freq: " << result.residual_freq;
@@ -616,19 +624,19 @@ bool USRPDevice::setRxFreq(double wFreq, size_t chan)
usrp_tune_result result;
if (chan) {
LOGC(DDEV, ALERT) << "Invalid channel " << chan;
LOG(ALERT) << "Invalid channel " << chan;
return false;
}
if (m_uRx->tune(0, m_dbRx, wFreq, &result)) {
LOGC(DDEV, INFO) << "set RX: " << wFreq << std::endl
LOG(INFO) << "set RX: " << wFreq << std::endl
<< " baseband freq: " << result.baseband_freq << std::endl
<< " DDC freq: " << result.dxc_freq << std::endl
<< " residual freq: " << result.residual_freq;
return true;
}
else {
LOGC(DDEV, ALERT) << "set RX: " << wFreq << " failed" << std::endl
LOG(ALERT) << "set RX: " << wFreq << "failed" << std::endl
<< " baseband freq: " << result.baseband_freq << std::endl
<< " DDC freq: " << result.dxc_freq << std::endl
<< " residual freq: " << result.residual_freq;
@@ -647,17 +655,5 @@ RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
const std::vector<std::string>& tx_paths,
const std::vector<std::string>& rx_paths)
{
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);
}

View File

@@ -1,10 +1,7 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
*
* 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.
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -54,7 +51,11 @@ private:
double actualSampleRate; ///< the actual USRP sampling rate
unsigned int decimRate; ///< the USRP decimation rate
unsigned long long samplesRead; ///< number of samples read from USRP
unsigned long long samplesWritten; ///< number of samples sent to USRP
bool started; ///< flag indicates USRP has started
bool skipRx; ///< set if USRP is transmit-only.
static const unsigned int currDataSize_log2 = 21;
static const unsigned long currDataSize = (1 << currDataSize_log2);
@@ -201,6 +202,8 @@ private:
inline double getTxFreq(size_t chan = 0) { return 0; }
inline double getRxFreq(size_t chan = 0) { return 0; }
inline double getSampleRate() { return actualSampleRate; }
inline double numberRead() { return samplesRead; }
inline double numberWritten() { return samplesWritten; }
};
#endif // _USRP_DEVICE_H_

View File

@@ -0,0 +1,314 @@
This file specifies the format of USB packets used for in-band data
transmission and signaling on the USRP. All packets are 512-byte long,
and are transfered using USB "bulk" transfers.
IN packets are sent towards the host.
OUT packets are sent away from the host.
The layout is 32-bits wide. All data is transmitted in little-endian
format across the USB.
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|O|U|D|S|E| RSSI | Chan | mbz | Tag | Payload Len |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| Payload |
. .
. .
. .
| |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ... | .
+-+-+-+-+-+-+-+ .
. .
. Padding .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
mbz Must be Zero: these bits must be zero in both IN and OUT packets.
O Overrun Flag: set in an IN packet if an overrun condition was
detected. Must be zero in OUT packets. Overrun occurs when
the FPGA has data to transmit to the host and there is no
buffer space available. This generally indicates a problem on
the host. Either it is not keeping up, or it has configured
the FPGA to transmit data at a higher rate than the transport
(USB) can support.
U Underrun Flag: set in an IN packet if an underrun condition
was detected. Must be zero in OUT packets. Underrun occurs
when the FPGA runs out of samples, and it's not between
bursts. See the "End of Burst flag" below.
D Dropped Packet Flag: Set in an IN packet if the FPGA
discarded an OUT packet because its timestamp had already
passed.
S Start of Burst Flag: Set in an OUT packet if the data is the
first segment of what is logically a continuous burst of data.
Must be zero in IN packets.
E End of Burst Flag: Set in an OUT packet if the data is the
last segment of what is logically a continuous burst of data.
Must be zero in IN packets. Underruns are not reported
when the FPGA runs out of samples between bursts.
RSSI 6-bit Received Strength Signal Indicator: Must be zero in OUT
packets. In IN packets, indicates RSSI as reported by front end.
FIXME The format and interpretation are to be determined.
Chan 5-bit logical channel number. Channel number 0x1f is reserved
for control information. See "Control Channel" below. Other
channels are "data channels." Each data channel is logically
independent of the others. A data channel payload field
contains a sequence of homogeneous samples. The format of the
samples is determined by the configuration associated with the
given channel. It is often the case that the payload field
contains 32-bit complex samples, each containing 16-bit real
and imaginary components.
Tag 4-bit tag for matching IN packets with OUT packets.
[FIXME, write more...]
Payload Len: 9-bit field that specifies the length of the payload
field in bytes. Must be in the range 0 to 504 inclusive.
Timestamp: 32-bit timestamp.
On IN packets, the timestamp indicates the time at which the
first sample of the packet was produced by the A/D converter(s)
for that channel. On OUT packets, the timestamp specifies the
time at which the first sample in the packet should go out the
D/A converter(s) for that channel. If a packet reaches the
head of the transmit queue, and the current time is later than
the timestamp, an error is assumed to have occurred and the
packet is discarded. As a special case, the timestamp
0xffffffff is interpreted as "Now".
The time base is a free running 32-bit counter that is
incremented by the A/D sample-clock.
Payload: Variable length field. Length is specified by the
Payload Len field.
Padding: This field is 504 - Payload Len bytes long, and its content
is unspecified. This field pads the packet out to a constant
512 bytes.
"Data Channel" payload format:
-------------------------------
If Chan != 0x1f, the packet is a "data packet" and the payload is a
sequence of homogeneous samples. The format of the samples is
determined by the configuration associated with the given channel.
It is often the case that the payload field contains 32-bit complex
samples, each containing 16-bit real and imaginary components.
"Control Channel" payload format:
---------------------------------
If Chan == 0x1f, the packet is a "control packet". The control channel
payload consists of a sequence of 0 or more sub-packets.
Each sub-packet starts on a 32-bit boundary, and consists of an 8-bit
Opcode field, an 8-bit Length field, Length bytes of arguments, and 0,
1, 2 or 3 bytes of padding to align the tail of the sub-packet to
a 32-bit boundary.
Control channel packets shall be processed at the head of the queue,
and shall observe the timestamp semantics described above.
General sub-packet format:
--------------------------
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-//-+-+-+-+-+-+-+-+
| Opcode | Length | <length bytes> ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-//-+-+-+-+-+-+-+-+
Specific sub-packet formats:
----------------------------
RID: 6-bit Request-ID. Copied from request sub-packet into corresponding
reply sub-packet. RID allows the host to match requests and replies.
Reg Number: 10-bit Register Number.
Ping Fixed Length:
Opcode: OP_PING_FIXED
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode | 2 | RID | Ping Value |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Ping Fixed Length Reply:
Opcode: OP_PING_FIXED_REPLY
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode | 2 | RID | Ping Value |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Write Register:
Opcode: OP_WRITE_REG
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode | 6 | mbz | Reg Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Register Value |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Write Register Masked:
Opcode: OP_WRITE_REG_MASKED
REG[Num] = (REG[Num] & ~Mask) | (Value & Mask)
That is, only the register bits that correspond to 1's in the
mask are written with the new value.
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode | 10 | mbz | Reg Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Register Value |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Mask Value |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Read Register:
Opcode: OP_READ_REG
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode | 2 | RID | Reg Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Read Register Reply:
Opcode: OP_READ_REG_REPLY
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode | 6 | RID | Reg Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Register Value |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
I2C Write:
Opcode: OP_I2C_WRITE
I2C Addr: 7-bit I2C address
Data: The bytes to write to the I2C bus
Length: Length of Data + 2
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode | Length | mbz | I2C Addr |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data ... .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
I2C Read:
Opcode: OP_I2C_READ
I2C Addr: 7-bit I2C address
Nbytes: Number of bytes to read from I2C bus
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode | 3 | RID | mbz | I2C Addr |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Nbytes | unspecified padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
I2C Read Reply:
Opcode: OP_I2C_READ_REPLY
I2C Addr: 7-bit I2C address
Data: Length - 2 bytes of data read from I2C bus.
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode | Length | RID | mbz | I2C Addr |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data ... .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
SPI Write:
Opcode: OP_SPI_WRITE
Enables: Which SPI enables to assert (mask)
Format: Specifies format of SPI data and Opt Header Bytes
Opt Header Bytes: 2-byte field containing optional Tx bytes; see Format
Data: The bytes to write to the SPI bus
Length: Length of Data + 6
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode | Length | mbz |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Enables | Format | Opt Header Bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data ... .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
SPI Read:
Opcode: OP_SPI_READ
Enables: Which SPI enables to assert (mask)
Format: Specifies format of SPI data and Opt Header Bytes
Opt Header Bytes: 2-byte field containing optional Tx bytes; see Format
Nbytes: Number of bytes to read from SPI bus.
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode | 7 | RID | mbz |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Enables | Format | Opt Header Bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Nbytes | unspecified padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
SPI Read Reply:
Opcode: OP_SPI_READ_REPLY
Data: Length - 2 bytes of data read from SPI bus.
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode | Length | RID | mbz |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data ... .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Delay:
Opcode: OP_DELAY
Ticks: 16-bit unsigned delay count
Delay Ticks clock ticks before executing next operation.
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode | 2 | Ticks |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

View File

@@ -1,8 +1,6 @@
/*
* Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>
*
* SPDX-License-Identifier: LGPL-2.1+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
@@ -24,19 +22,16 @@
#include "Transceiver.h"
#include "radioDevice.h"
#include "Utils.h"
#include <time.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <sched.h>
#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <sys/signalfd.h>
#include <GSMCommon.h>
#include <Logger.h>
@@ -54,14 +49,10 @@ extern "C" {
#include <osmocom/ctrl/ports.h>
#include <osmocom/ctrl/control_if.h>
#include <osmocom/vty/stats.h>
#include <osmocom/vty/command.h>
#include "convolve.h"
#include "convert.h"
#include "trx_vty.h"
#include "debug.h"
#include "osmo_signal.h"
#include "trx_rate_ctr.h"
}
#define DEFAULT_CONFIG_FILE "osmo-trx.cfg"
@@ -70,7 +61,6 @@ extern "C" {
static char* config_file = (char*)DEFAULT_CONFIG_FILE;
struct osmo_fd signal_ofd;
volatile bool gshutdown = false;
static void *tall_trx_ctx;
@@ -120,20 +110,6 @@ RadioInterface *makeRadioInterface(struct trx_ctx *trx,
return radio;
}
/* Callback function to be called every time we receive a signal from TRANSC */
static int transc_sig_cb(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
{
switch (signal) {
case S_MAIN_STOP_REQUIRED:
gshutdown = true;
break;
default:
break;
}
return 0;
}
/* Create transceiver core
* The multi-threaded modem core operates at multiples of the GSM rate of
* 270.8333 ksps and consists of GSM specific modulation, demodulation,
@@ -147,9 +123,9 @@ int makeTransceiver(struct trx_ctx *trx, RadioInterface *radio)
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);
radio, trx->cfg.rssi_offset);
if (!transceiver->init(trx->cfg.filler, trx->cfg.rtsc,
trx->cfg.rach_delay, trx->cfg.egprs, trx->cfg.ext_rach)) {
trx->cfg.rach_delay, trx->cfg.egprs)) {
LOG(ALERT) << "Failed to initialize transceiver";
return -1;
}
@@ -167,12 +143,6 @@ int makeTransceiver(struct trx_ctx *trx, RadioInterface *radio)
static void sig_handler(int signo)
{
if (gshutdown)
/* We are in the middle of shutdown process, avoid any kind of extra
action like printing */
return;
fprintf(stdout, "signal %d received\n", signo);
switch (signo) {
case SIGINT:
@@ -188,68 +158,42 @@ static void sig_handler(int signo)
case SIGUSR2:
talloc_report_full(tall_trx_ctx, stderr);
break;
case SIGHUP:
log_targets_reopen();
default:
break;
}
}
static int signalfd_callback(struct osmo_fd *ofd, unsigned int what)
{
struct signalfd_siginfo fdsi;
ssize_t s;
s = read(ofd->fd, &fdsi, sizeof(struct signalfd_siginfo));
if (s < 0) {
LOG(FATAL) << "Failed to read from signalfd ("<< ofd->fd << "): " << errno;
gshutdown = true;
return 0;
}
sig_handler(fdsi.ssi_signo);
return 0;
}
static void setup_signal_handlers()
{
sigset_t set;
int sfd;
/* Handle keyboard interrupt SIGINT */
signal(SIGINT, &sig_handler);
signal(SIGTERM, &sig_handler);
signal(SIGABRT, &sig_handler);
signal(SIGUSR1, &sig_handler);
signal(SIGUSR2, &sig_handler);
osmo_init_ignore_signals();
}
/* Other threads created by this thread (main) will inherit a copy of the
signal mask. */
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGTERM);
sigaddset(&set, SIGUSR1);
sigaddset(&set, SIGUSR2);
sigaddset(&set, SIGHUP);
if (pthread_sigmask(SIG_BLOCK, &set, NULL)) {
fprintf(stderr, "pthread_sigmask() failed.\n");
exit(EXIT_FAILURE);
}
static std::vector<std::string> comma_delimited_to_vector(char* opt)
{
std::string str = std::string(opt);
std::vector<std::string> result;
std::stringstream ss(str);
if ((sfd = signalfd(-1, &set, 0)) == -1) {
fprintf(stderr, "signalfd() failed (%d).\n", errno);
exit(EXIT_FAILURE);
}
osmo_fd_setup(&signal_ofd, sfd, BSC_FD_READ, signalfd_callback, NULL, 0);
if (osmo_fd_register(&signal_ofd) < 0) {
fprintf(stderr, "osmo_fd_register() failed.\n");
exit(EXIT_FAILURE);
while( ss.good() )
{
std::string substr;
getline(ss, substr, ',');
result.push_back(substr);
}
return result;
}
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"
" -h This text\n"
" -C Filename The config file to use\n"
);
}
@@ -257,7 +201,7 @@ static void print_deprecated(char opt)
{
LOG(WARNING) << "Cmd line option '" << opt << "' is deprecated and will be soon removed."
<< " Please use VTY cfg option instead."
<< " All cmd line options are already being overridden by VTY options if set.";
<< " All cmd line options are already being overriden by VTY options if set.";
}
static void handle_options(int argc, char **argv, struct trx_ctx* trx)
@@ -266,15 +210,8 @@ static void handle_options(int argc, char **argv, struct trx_ctx* trx)
unsigned int i;
std::vector<std::string> rx_paths, tx_paths;
bool rx_paths_set = false, tx_paths_set = false;
static struct option long_options[] = {
{"help", 0, 0, 'h'},
{"config", 1, 0, 'C'},
{"version", 0, 0, 'V'},
{NULL, 0, 0, 0}
};
while ((option = getopt_long(argc, argv, "ha:l:i:j:p:c:dmxgfo:s:b:r:A:R:Set:y:z:C:V", long_options,
NULL)) != -1) {
while ((option = getopt(argc, argv, "ha:l:i:j:p:c:dmxgfo:s:b:r:A:R:Set:y:z:C:")) != -1) {
switch (option) {
case 'h':
print_help();
@@ -374,10 +311,6 @@ static void handle_options(int argc, char **argv, struct trx_ctx* trx)
case 'C':
config_file = optarg;
break;
case 'V':
print_version(1);
exit(0);
break;
default:
goto bad_config;
}
@@ -412,14 +345,14 @@ bad_config:
int trx_validate_config(struct trx_ctx *trx)
{
if (trx->cfg.multi_arfcn && trx->cfg.num_chans > TRX_MCHAN_MAX) {
if (trx->cfg.multi_arfcn && trx->cfg.num_chans > 5) {
LOG(ERROR) << "Unsupported number of channels";
return -1;
}
/* Force 4 SPS for EDGE or multi-ARFCN configurations */
if ((trx->cfg.egprs || trx->cfg.multi_arfcn) &&
(trx->cfg.tx_sps!=4 || trx->cfg.rx_sps!=4)) {
(trx->cfg.tx_sps!=4 || trx->cfg.tx_sps!=4)) {
LOG(ERROR) << "EDGE and Multi-Carrier options require 4 tx and rx sps. Check you config.";
return -1;
}
@@ -433,7 +366,7 @@ static int set_sched_rr(unsigned int prio)
int rc;
memset(&param, 0, sizeof(param));
param.sched_priority = prio;
LOG(INFO) << "Setting SCHED_RR priority " << param.sched_priority;
printf("Setting SCHED_RR priority(%d)\n", param.sched_priority);
rc = sched_setscheduler(getpid(), SCHED_RR, &param);
if (rc != 0) {
LOG(ERROR) << "Config: Setting SCHED_RR failed";
@@ -457,7 +390,6 @@ static void print_config(struct trx_ctx *trx)
ost << " Tx Samples-per-Symbol... " << trx->cfg.tx_sps << std::endl;
ost << " Rx Samples-per-Symbol... " << trx->cfg.rx_sps << std::endl;
ost << " EDGE support............ " << trx->cfg.egprs << std::endl;
ost << " Extended RACH support... " << trx->cfg.ext_rach << std::endl;
ost << " Reference............... " << trx->cfg.clock_ref << std::endl;
ost << " C0 Filler Table......... " << trx->cfg.filler << std::endl;
ost << " Multi-Carrier........... " << trx->cfg.multi_arfcn << std::endl;
@@ -477,12 +409,12 @@ static void print_config(struct trx_ctx *trx)
}
ost << std::endl;
LOG(INFO) << ost << std::endl;
std::cout << ost << std::endl;
}
static void trx_stop()
{
LOG(NOTICE) << "Shutting down transceiver..." << std::endl;
std::cout << "Shutting down transceiver..." << std::endl;
delete transceiver;
delete radio;
@@ -525,7 +457,7 @@ static int trx_start(struct trx_ctx *trx)
goto shutdown;
chans = transceiver->numChans();
LOG(NOTICE) << "-- Transceiver active with "
std::cout << "-- Transceiver active with "
<< chans << " channel(s)" << std::endl;
return 0;
@@ -571,10 +503,6 @@ int main(int argc, char *argv[])
#endif
#endif
if (!log_mutex_init()) {
fprintf(stderr, "Failed to initialize log mutex!\n");
exit(2);
}
convolve_init();
convert_init();
@@ -602,9 +530,9 @@ int main(int argc, char *argv[])
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";
fprintf(stderr, "Failed to create CTRL interface.\n");
exit(1);
}
@@ -631,9 +559,6 @@ int main(int argc, char *argv[])
return EXIT_FAILURE;
}
osmo_signal_register_handler(SS_MAIN, transc_sig_cb, NULL);
trx_rate_ctr_init(tall_trx_ctx, g_trx_ctx);
srandom(time(NULL));
if(trx_start(g_trx_ctx) < 0)
@@ -644,8 +569,5 @@ int main(int argc, char *argv[])
trx_stop();
osmo_fd_unregister(&signal_ofd);
osmo_fd_close(&signal_ofd);
osmo_signal_unregister_handler(SS_MAIN, transc_sig_cb, NULL);
return 0;
}

View File

@@ -1,117 +0,0 @@
/*
* Copyright (C) 2019 sysmocom - s.f.m.c. GmbH
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Pau Espin Pedrol <pespin@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/>.
* See the COPYING file in the main directory for details.
*/
#include "proto_trxd.h"
#include <osmocom/core/bits.h>
static void trxd_fill_common(struct trxd_hdr_common *common, const struct trx_ul_burst_ind *bi, uint8_t version)
{
common->version = version & 0b1111;
common->reserved = 0;
common->tn = bi->tn;
osmo_store32be(bi->fn, &common->fn);
}
static void trxd_fill_v0_specific(struct trxd_hdr_v0_specific *v0, const struct trx_ul_burst_ind *bi)
{
int toa_int;
/* in 1/256 symbols, round to closest integer */
toa_int = (int) (bi->toa * 256.0 + 0.5);
v0->rssi = bi->rssi;
osmo_store16be(toa_int, &v0->toa);
}
static void trxd_fill_v1_specific(struct trxd_hdr_v1_specific *v1, const struct trx_ul_burst_ind *bi)
{
int16_t ci_int_cB;
/* deciBels->centiBels, round to closest integer */
ci_int_cB = (int16_t)((bi->ci * 10) + 0.5);
v1->idle = !!bi->idle;
v1->modulation = (bi->modulation == MODULATION_GMSK) ?
TRXD_MODULATION_GMSK(bi->tss) :
TRXD_MODULATION_8PSK(bi->tss);
v1->tsc = bi->tsc;
osmo_store16be(ci_int_cB, &v1->ci);
}
static void trxd_fill_burst_normalized255(uint8_t* soft_bits, const struct trx_ul_burst_ind *bi)
{
unsigned i;
for (i = 0; i < bi->nbits; i++)
soft_bits[i] = (char) 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)
return true;
/* +2: Historically (OpenBTS times), two extra non-used bytes are sent appeneded to each burst */
char buf[sizeof(struct trxd_hdr_v0) + bi->nbits + 2];
struct trxd_hdr_v0* pkt = (struct trxd_hdr_v0*)buf;
trxd_fill_common(&pkt->common, bi, 0);
trxd_fill_v0_specific(&pkt->v0, bi);
trxd_fill_burst_normalized255(&pkt->soft_bits[0], bi);
/* +1: Historical reason. There's an uninitizalied byte in there: pkt->soft_bits[bi->nbits] */
pkt->soft_bits[bi->nbits + 1] = '\0';
rc = write(fd, buf, sizeof(struct trxd_hdr_v0) + bi->nbits + 2);
if (rc <= 0) {
CLOGCHAN(chan, DMAIN, LOGL_NOTICE, "mDataSockets write(%d) failed: %d\n", fd, rc);
return false;
}
return true;
}
bool trxd_send_burst_ind_v1(size_t chan, int fd, const struct trx_ul_burst_ind *bi) {
int rc;
size_t buf_len;
buf_len = sizeof(struct trxd_hdr_v1);
if (!bi->idle)
buf_len += bi->nbits;
char buf[buf_len];
struct trxd_hdr_v1* pkt = (struct trxd_hdr_v1*)buf;
trxd_fill_common(&pkt->common, bi, 1);
trxd_fill_v0_specific(&pkt->v0, bi);
trxd_fill_v1_specific(&pkt->v1, bi);
if (!bi->idle)
trxd_fill_burst_normalized255(&pkt->soft_bits[0], bi);
rc = write(fd, buf, buf_len);
if (rc <= 0) {
CLOGCHAN(chan, DMAIN, LOGL_NOTICE, "mDataSockets write(%d) failed: %d\n", fd, rc);
return false;
}
return true;
}

View File

@@ -1,94 +0,0 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <math.h>
#include <osmocom/core/endian.h>
#include "debug.h"
#define MAX_RX_BURST_BUF_SIZE 444 /* 444 = EDGE_BURST_NBITS */
enum Modulation {
MODULATION_GMSK,
MODULATION_8PSK,
/* Not supported yet:
MODULATION_AQPSK,
MODULATION_16QAM,
MODULATION_32QAM
*/
};
struct trx_ul_burst_ind {
float rx_burst[MAX_RX_BURST_BUF_SIZE]; /* soft bits normalized 0..1 */
unsigned nbits; // number of symbols per slot in rxBurst, not counting guard periods
uint32_t fn; // TDMA frame number
uint8_t tn; // TDMA time-slot number
double rssi; // in dBFS
double toa; // in symbols
double noise; // noise level in dBFS
bool idle; // true if no valid burst is included
enum Modulation modulation; // modulation type
uint8_t tss; // training sequence set
uint8_t tsc; // training sequence code
float ci; // Carrier-to-Interference ratio, in dB
};
bool trxd_send_burst_ind_v0(size_t chan, int fd, const struct trx_ul_burst_ind *bi);
bool trxd_send_burst_ind_v1(size_t chan, int fd, const struct trx_ul_burst_ind *bi);
/* The latest supported TRXD header format version */
#define TRX_DATA_FORMAT_VER 1
struct trxd_hdr_common {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t tn:3,
reserved:1,
version:4;
#elif OSMO_IS_BIG_ENDIAN
uint8_t version:4,
reserved:1,
tn:3;
#endif
uint32_t fn; /* big endian */
} __attribute__ ((packed));
struct trxd_hdr_v0_specific {
uint8_t rssi;
uint16_t toa; /* big endian */
} __attribute__ ((packed));
struct trxd_hdr_v0 {
struct trxd_hdr_common common;
struct trxd_hdr_v0_specific v0;
uint8_t soft_bits[0];
} __attribute__ ((packed));
#define TRXD_MODULATION_GMSK(ts_set) (0b0000 | (ts_set & 0b0011))
#define TRXD_MODULATION_8PSK(ts_set) (0b0100 | (ts_set & 0b0001))
#define TRXD_MODULATION_AQPSK(ts_set) (0b0110 | (ts_set & 0b0001))
#define TRXD_MODULATION_16QAM(ts_set) (0b1000 | (ts_set & 0b0001))
#define TRXD_MODULATION_32QAM(ts_set) (0b1010 | (ts_set & 0b0001))
struct trxd_hdr_v1_specific {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t tsc:3,
modulation:4,
idle:1;
#elif OSMO_IS_BIG_ENDIAN
uint8_t idle:1,
modulation:4,
tsc:3;
#endif
int16_t ci; /* big endian, in centiBels */
} __attribute__ ((packed));
struct trxd_hdr_v1 {
struct trxd_hdr_common common;
struct trxd_hdr_v0_specific v0;
struct trxd_hdr_v1_specific v1;
uint8_t soft_bits[0];
} __attribute__ ((packed));

View File

@@ -5,8 +5,6 @@
*
* Author: Tom Tsou <tom@tsou.cc>
*
* 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
@@ -98,7 +96,7 @@ const float *RadioBuffer::getReadSegment()
/*
* Output direction
*
* Write a non-segment length of samples to the buffer.
* Write a non-segment length of samples to the buffer.
*/
bool RadioBuffer::write(const float *wr, size_t len)
{

View File

@@ -4,8 +4,6 @@
*
* Copyright 2011 Free Software Foundation, Inc.
*
* 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

View File

@@ -4,8 +4,6 @@
*
* Copyright 2011 Free Software Foundation, Inc.
*
* 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

View File

@@ -4,8 +4,6 @@
* Copyright (C) 2008-2014 Free Software Foundation, Inc.
* Copyright (C) 2015 Ettus Research LLC
*
* 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
@@ -36,8 +34,7 @@ RadioInterface::RadioInterface(RadioDevice *wRadio, 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)
underrun(false), overrun(false), receiveOffset(wReceiveOffset), mOn(false)
{
mClock.set(wStartTime);
}
@@ -78,14 +75,6 @@ bool RadioInterface::init(int type)
void RadioInterface::close()
{
for (std::vector<RadioBuffer*>::iterator it = sendBuffer.begin(); it != sendBuffer.end(); ++it)
delete *it;
for (std::vector<RadioBuffer*>::iterator it = recvBuffer.begin(); it != recvBuffer.end(); ++it)
delete *it;
for (std::vector<short*>::iterator it = convertSendBuffer.begin(); it != convertSendBuffer.end(); ++it)
delete[] *it;
for (std::vector<short*>::iterator it = convertRecvBuffer.begin(); it != convertRecvBuffer.end(); ++it)
delete[] *it;
sendBuffer.resize(0);
recvBuffer.resize(0);
convertSendBuffer.resize(0);
@@ -159,7 +148,6 @@ bool RadioInterface::tuneRx(double freq, size_t chan)
/** synchronization thread loop */
void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface)
{
set_selfthread_name("AlignRadio");
while (1) {
sleep(60);
radioInterface->alignRadio();
@@ -230,15 +218,14 @@ void RadioInterface::driveTransmitRadio(std::vector<signalVector *> &bursts,
while (pushBuffer());
}
int RadioInterface::driveReceiveRadio()
bool RadioInterface::driveReceiveRadio()
{
radioVector *burst = NULL;
if (!mOn)
return 0;
return false;
if (pullBuffer() < 0)
return -1;
pullBuffer();
GSM::Time rcvClock = mClock.get();
rcvClock.decTN(receiveOffset);
@@ -252,11 +239,11 @@ int RadioInterface::driveReceiveRadio()
else
burstSize = symbolsPerSlot + (tN % 4 == 0);
/*
/*
* Pre-allocate head room for the largest correlation size
* so we can later avoid a re-allocation and copy
* */
size_t head = GSM::gRACHSynchSequenceTS0.size();
size_t head = GSM::gRACHSynchSequence.size();
/*
* Form receive bursts and pass up to transceiver. Use repeating
@@ -283,7 +270,7 @@ int RadioInterface::driveReceiveRadio()
burstSize = (symbolsPerSlot + (tN % 4 == 0)) * mSPSRx;
}
return 1;
return true;
}
bool RadioInterface::isUnderrun()
@@ -313,14 +300,13 @@ double RadioInterface::getRxGain(size_t chan)
}
/* Receive a timestamped chunk from the device */
int RadioInterface::pullBuffer()
void RadioInterface::pullBuffer()
{
bool local_underrun;
int numRecv;
size_t segmentLen = recvBuffer[0]->getSegmentLen();
size_t numRecv, segmentLen = recvBuffer[0]->getSegmentLen();
if (recvBuffer[0]->getFreeSegments() <= 0)
return -1;
return;
/* Outer buffer access size is fixed */
numRecv = mRadio->readSamples(convertRecvBuffer,
@@ -329,9 +315,9 @@ int RadioInterface::pullBuffer()
readTimestamp,
&local_underrun);
if ((size_t) numRecv != segmentLen) {
if (numRecv != segmentLen) {
LOG(ALERT) << "Receive error " << numRecv;
return -1;
return;
}
for (size_t i = 0; i < mChans; i++) {
@@ -342,13 +328,11 @@ int RadioInterface::pullBuffer()
underrun |= local_underrun;
readTimestamp += numRecv;
return 0;
}
/* Send timestamped chunk to the device with arbitrary size */
bool RadioInterface::pushBuffer()
{
bool local_underrun;
size_t numSent, segmentLen = sendBuffer[0]->getSegmentLen();
if (sendBuffer[0]->getAvailSegments() < 1)
@@ -364,9 +348,8 @@ bool RadioInterface::pushBuffer()
/* Send the all samples in the send buffer */
numSent = mRadio->writeSamples(convertSendBuffer,
segmentLen,
&local_underrun,
&underrun,
writeTimestamp);
underrun |= local_underrun;
writeTimestamp += numSent;
return true;

View File

@@ -14,7 +14,7 @@
#include "sigProcLib.h"
#include "sigProcLib.h"
#include "GSMCommon.h"
#include "LinkedLists.h"
#include "radioDevice.h"
@@ -71,7 +71,7 @@ private:
virtual bool pushBuffer(void);
/** pull GSM bursts from the receive buffer */
virtual int pullBuffer(void);
virtual void pullBuffer(void);
public:
@@ -116,8 +116,8 @@ public:
void driveTransmitRadio(std::vector<signalVector *> &bursts,
std::vector<bool> &zeros);
/** drive reception of GSM bursts. -1: Error. 0: Radio off. 1: Received something. */
int driveReceiveRadio();
/** drive reception of GSM bursts */
bool driveReceiveRadio();
int setPowerAttenuation(int atten, size_t chan = 0);
@@ -130,7 +130,7 @@ public:
/** set thread priority on current thread */
void setPriority(float prio = 0.5) { mRadio->setPriority(prio); }
/** get transport window type of attached device */
/** get transport window type of attached device */
enum RadioDevice::TxWindowType getWindowType() { return mRadio->getWindowType(); }
/** Minimum latency that the device can achieve */
@@ -149,7 +149,7 @@ private:
signalVector *outerRecvBuffer;
bool pushBuffer();
int pullBuffer();
void pullBuffer();
public:
RadioInterfaceResamp(RadioDevice* wRadio, size_t tx_sps, size_t rx_sps);
@@ -162,7 +162,7 @@ public:
class RadioInterfaceMulti : public RadioInterface {
private:
bool pushBuffer();
int pullBuffer();
void pullBuffer();
signalVector *outerSendBuffer;
signalVector *outerRecvBuffer;

View File

@@ -1,12 +1,10 @@
/*
* Multi-carrier radio interface
*
* Copyright (C) 2016 Ettus Research LLC
* Copyright (C) 2016 Ettus Research LLC
*
* Author: Tom Tsou <tom.tsou@ettus.com>
*
* 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
@@ -227,15 +225,14 @@ bool RadioInterfaceMulti::init(int type)
}
/* Receive a timestamped chunk from the device */
int RadioInterfaceMulti::pullBuffer()
void RadioInterfaceMulti::pullBuffer()
{
bool local_underrun;
size_t num;
float *buf;
unsigned int i;
if (recvBuffer[0]->getFreeSegments() <= 0)
return -1;
return;
/* Outer buffer access size is fixed */
num = mRadio->readSamples(convertRecvBuffer,
@@ -245,7 +242,7 @@ int RadioInterfaceMulti::pullBuffer()
&local_underrun);
if (num != channelizer->inputLen()) {
LOG(ALERT) << "Receive error " << num << ", " << channelizer->inputLen();
return -1;
return;
}
convert_short_float((float *) outerRecvBuffer->begin(),
@@ -276,22 +273,10 @@ int RadioInterfaceMulti::pullBuffer()
buf = channelizer->outputBuffer(pchan);
size_t cLen = channelizer->outputLen();
size_t hLen = dnsampler->len();
size_t hSize = 2 * hLen * sizeof(float);
float *fdst = &buf[2 * -hLen];
complex *src = history[lchan]->begin();
for (i = 0; i < hLen; i++) {
fdst[0] = src->real();
fdst[1] = src->imag();
src++;
fdst += 2;
}
complex *dst = history[lchan]->begin();
float *fsrc = &buf[2 * (cLen - hLen)];
for (i = 0; i < hLen; i++) {
*dst = complex(fdst[0], fdst[1]);
fsrc += 2;
dst++;
}
memcpy(&buf[2 * -hLen], history[lchan]->begin(), hSize);
memcpy(history[lchan]->begin(), &buf[2 * (cLen - hLen)], hSize);
float *wr_segment = recvBuffer[lchan]->getWriteSegment();
@@ -303,7 +288,6 @@ int RadioInterfaceMulti::pullBuffer()
LOG(ALERT) << "Sample rate upsampling error";
}
}
return 0;
}
/* Send a timestamped chunk to the device */

View File

@@ -6,8 +6,6 @@
*
* Author: Tom Tsou <tom@tsou.cc>
*
* 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
@@ -162,13 +160,13 @@ bool RadioInterfaceResamp::init(int type)
}
/* Receive a timestamped chunk from the device */
int RadioInterfaceResamp::pullBuffer()
void RadioInterfaceResamp::pullBuffer()
{
bool local_underrun;
int rc, num_recv;
if (recvBuffer[0]->getFreeSegments() <= 0)
return -1;
return;
/* Outer buffer access size is fixed */
num_recv = mRadio->readSamples(convertRecvBuffer,
@@ -178,7 +176,7 @@ int RadioInterfaceResamp::pullBuffer()
&local_underrun);
if (num_recv != (int) resamp_outchunk) {
LOG(ALERT) << "Receive error " << num_recv;
return -1;
return;
}
convert_short_float((float *) outerRecvBuffer->begin(),
@@ -198,7 +196,6 @@ int RadioInterfaceResamp::pullBuffer()
/* Set history for the next chunk */
outerRecvBuffer->updateHistory();
return 0;
}
/* Send a timestamped chunk to the device */

View File

@@ -4,8 +4,6 @@
*
* Copyright 2011 Free Software Foundation, Inc.
*
* 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

View File

@@ -4,8 +4,6 @@
*
* Copyright 2011 Free Software Foundation, Inc.
*
* 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

View File

@@ -1,8 +1,6 @@
/*
* Copyright 2008, 2011 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
@@ -86,13 +84,14 @@ static Resampler *dnsampler = NULL;
* perform 16-byte memory alignment required by many SSE instructions.
*/
struct CorrelationSequence {
CorrelationSequence() : sequence(NULL), buffer(NULL), toa(0.0)
CorrelationSequence() : sequence(NULL), buffer(NULL)
{
}
~CorrelationSequence()
{
delete sequence;
free(buffer);
}
signalVector *sequence;
@@ -107,7 +106,8 @@ struct CorrelationSequence {
* for SSE instructions.
*/
struct PulseSequence {
PulseSequence() : c0(NULL), c1(NULL), c0_inv(NULL), empty(NULL)
PulseSequence() : c0(NULL), c1(NULL), c0_inv(NULL), empty(NULL),
c0_buffer(NULL), c1_buffer(NULL), c0_inv_buffer(NULL)
{
}
@@ -117,17 +117,22 @@ struct PulseSequence {
delete c1;
delete c0_inv;
delete empty;
free(c0_buffer);
free(c1_buffer);
}
signalVector *c0;
signalVector *c1;
signalVector *c0_inv;
signalVector *empty;
void *c0_buffer;
void *c1_buffer;
void *c0_inv_buffer;
};
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 *gRACHSequence = NULL;
static PulseSequence *GSMPulse1 = NULL;
static PulseSequence *GSMPulse4 = NULL;
@@ -145,15 +150,11 @@ void sigProcLibDestroy()
delayFilters[i] = NULL;
}
for (int i = 0; i < 3; i++) {
delete gRACHSequences[i];
gRACHSequences[i] = NULL;
}
delete GMSKRotation1;
delete GMSKReverseRotation1;
delete GMSKRotation4;
delete GMSKReverseRotation4;
delete gRACHSequence;
delete GSMPulse1;
delete GSMPulse4;
delete dnsampler;
@@ -162,6 +163,7 @@ void sigProcLibDestroy()
GMSKRotation4 = NULL;
GMSKReverseRotation4 = NULL;
GMSKReverseRotation1 = NULL;
gRACHSequence = NULL;
GSMPulse1 = NULL;
GSMPulse4 = NULL;
}
@@ -287,7 +289,8 @@ enum ConvType {
static signalVector *convolve(const signalVector *x, const signalVector *h,
signalVector *y, ConvType spanType,
size_t start = 0, size_t len = 0)
size_t start = 0, size_t len = 0,
size_t step = 1, int offset = 0)
{
int rc;
size_t head = 0, tail = 0;
@@ -334,7 +337,7 @@ static signalVector *convolve(const signalVector *x, const signalVector *h,
if (y && (len > y->size()))
return NULL;
if (!y) {
y = new signalVector(len, convolve_h_alloc, free);
y = new signalVector(len);
alloc = true;
}
@@ -355,22 +358,22 @@ static signalVector *convolve(const signalVector *x, const signalVector *h,
rc = convolve_real((float *) _x->begin(), _x->size(),
(float *) h->begin(), h->size(),
(float *) y->begin(), y->size(),
start, len);
start, len, step, offset);
} else if (!h->isReal() && h->isAligned()) {
rc = convolve_complex((float *) _x->begin(), _x->size(),
(float *) h->begin(), h->size(),
(float *) y->begin(), y->size(),
start, len);
start, len, step, offset);
} else if (h->isReal() && !h->isAligned()) {
rc = base_convolve_real((float *) _x->begin(), _x->size(),
(float *) h->begin(), h->size(),
(float *) y->begin(), y->size(),
start, len);
start, len, step, offset);
} else if (!h->isReal() && !h->isAligned()) {
rc = base_convolve_complex((float *) _x->begin(), _x->size(),
(float *) h->begin(), h->size(),
(float *) y->begin(), y->size(),
start, len);
start, len, step, offset);
} else {
rc = -1;
}
@@ -397,7 +400,8 @@ static bool generateInvertC0Pulse(PulseSequence *pulse)
if (!pulse)
return false;
pulse->c0_inv = new signalVector((complex *) convolve_h_alloc(5), 0, 5, convolve_h_alloc, free);
pulse->c0_inv_buffer = convolve_h_alloc(5);
pulse->c0_inv = new signalVector((complex *) pulse->c0_inv_buffer, 0, 5);
pulse->c0_inv->isReal(true);
pulse->c0_inv->setAligned(false);
@@ -426,7 +430,9 @@ static bool generateC1Pulse(int sps, PulseSequence *pulse)
return false;
}
pulse->c1 = new signalVector((complex *) convolve_h_alloc(len), 0, len, convolve_h_alloc, free);
pulse->c1_buffer = convolve_h_alloc(len);
pulse->c1 = new signalVector((complex *)
pulse->c1_buffer, 0, len);
pulse->c1->isReal(true);
/* Enable alignment for SSE usage */
@@ -480,7 +486,8 @@ static PulseSequence *generateGSMPulse(int sps)
len = 4;
}
pulse->c0 = new signalVector((complex *) convolve_h_alloc(len), 0, len, convolve_h_alloc, free);
pulse->c0_buffer = convolve_h_alloc(len);
pulse->c0 = new signalVector((complex *) pulse->c0_buffer, 0, len);
pulse->c0->isReal(true);
/* Enable alingnment for SSE usage */
@@ -532,17 +539,19 @@ static PulseSequence *generateGSMPulse(int sps)
return pulse;
}
/* Convert -1..+1 soft bits to 0..1 soft bits */
void vectorSlicer(float *dest, const float *src, size_t len)
bool vectorSlicer(SoftVector *x)
{
size_t i;
for (i = 0; i < len; i++) {
dest[i] = 0.5 * (src[i] + 1.0f);
if (dest[i] > 1.0)
dest[i] = 1.0;
else if (dest[i] < 0.0)
dest[i] = 0.0;
}
SoftVector::iterator xP = x->begin();
SoftVector::iterator xPEnd = x->end();
while (xP < xPEnd) {
*xP = 0.5 * (*xP + 1.0f);
if (*xP > 1.0)
*xP = 1.0;
if (*xP < 0.0)
*xP = 0.0;
xP++;
}
return true;
}
static signalVector *rotateBurst(const BitVector &wBurst,
@@ -1007,7 +1016,7 @@ static void generateDelayFilters()
for (int i = 0; i < DELAYFILTS; i++) {
data = (complex *) convolve_h_alloc(h_len);
h = new signalVector(data, 0, h_len, convolve_h_alloc, free);
h = new signalVector(data, 0, h_len);
h->setAligned(true);
h->isReal(true);
@@ -1093,17 +1102,17 @@ static complex interpolatePoint(const signalVector &inSig, float ix)
if (start < 0) start = 0;
int end = (int) (floor(ix) + 11);
if ((unsigned) end > inSig.size()-1) end = inSig.size()-1;
complex pVal = 0.0;
if (!inSig.isReal()) {
for (int i = start; i < end; i++)
for (int i = start; i < end; i++)
pVal += inSig[i] * sinc(M_PI_F*(i-ix));
}
else {
for (int i = start; i < end; i++)
for (int i = start; i < end; i++)
pVal += inSig[i].real() * sinc(M_PI_F*(i-ix));
}
return pVal;
}
@@ -1148,12 +1157,12 @@ static complex peakDetect(const signalVector &rxBurst,
// to save computation, we'll use early-late balancing
float earlyIndex = maxIndex-1;
float lateIndex = maxIndex+1;
float incr = 0.5;
while (incr > 1.0/1024.0) {
complex earlyP = interpolatePoint(rxBurst,earlyIndex);
complex lateP = interpolatePoint(rxBurst,lateIndex);
if (earlyP < lateP)
if (earlyP < lateP)
earlyIndex += incr;
else if (earlyP > lateP)
earlyIndex -= incr;
@@ -1241,7 +1250,7 @@ static bool generateMidamble(int sps, int tsc)
// 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
// 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));
@@ -1251,9 +1260,10 @@ static bool generateMidamble(int sps, int tsc)
/* 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 = new signalVector(data, 0, midMidamble->size());
_midMidamble->setAligned(true);
midMidamble->copyTo(*_midMidamble);
memcpy(_midMidamble->begin(), midMidamble->begin(),
midMidamble->size() * sizeof(complex));
autocorr = convolve(midamble, _midMidamble, NULL, NO_DELAY);
if (!autocorr) {
@@ -1262,6 +1272,7 @@ static bool generateMidamble(int sps, int tsc)
}
gMidambles[tsc] = new CorrelationSequence;
gMidambles[tsc]->buffer = data;
gMidambles[tsc]->sequence = _midMidamble;
gMidambles[tsc]->gain = peakDetect(*autocorr, &toa, NULL);
@@ -1306,12 +1317,14 @@ static CorrelationSequence *generateEdgeMidamble(int tsc)
conjugateVector(*midamble);
data = (complex *) convolve_h_alloc(midamble->size());
_midamble = new signalVector(data, 0, midamble->size(), convolve_h_alloc, free);
_midamble = new signalVector(data, 0, midamble->size());
_midamble->setAligned(true);
midamble->copyTo(*_midamble);
memcpy(_midamble->begin(), midamble->begin(),
midamble->size() * sizeof(complex));
/* Channel gain is an empirically measured value */
seq = new CorrelationSequence;
seq->buffer = data;
seq->sequence = _midamble;
seq->gain = Complex<float>(-19.6432, 19.5006) / 1.18;
seq->toa = 0;
@@ -1321,7 +1334,7 @@ static CorrelationSequence *generateEdgeMidamble(int tsc)
return seq;
}
static bool generateRACHSequence(CorrelationSequence **seq, const BitVector &bv, int sps)
static bool generateRACHSequence(int sps)
{
bool status = true;
float toa;
@@ -1329,14 +1342,13 @@ static bool generateRACHSequence(CorrelationSequence **seq, const BitVector &bv,
signalVector *autocorr = NULL;
signalVector *seq0 = NULL, *seq1 = NULL, *_seq1 = NULL;
if (*seq != NULL)
delete *seq;
delete gRACHSequence;
seq0 = modulateBurst(bv, 0, sps, false);
seq0 = modulateBurst(gRACHSynchSequence, 0, sps, false);
if (!seq0)
return false;
seq1 = modulateBurst(bv.segment(0, 40), 0, sps, true);
seq1 = modulateBurst(gRACHSynchSequence.segment(0, 40), 0, sps, true);
if (!seq1) {
status = false;
goto release;
@@ -1346,9 +1358,9 @@ static bool generateRACHSequence(CorrelationSequence **seq, const BitVector &bv,
/* 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 = new signalVector(data, 0, seq1->size());
_seq1->setAligned(true);
seq1->copyTo(*_seq1);
memcpy(_seq1->begin(), seq1->begin(), seq1->size() * sizeof(complex));
autocorr = convolve(seq0, _seq1, autocorr, NO_DELAY);
if (!autocorr) {
@@ -1356,18 +1368,19 @@ static bool generateRACHSequence(CorrelationSequence **seq, const BitVector &bv,
goto release;
}
*seq = new CorrelationSequence;
(*seq)->sequence = _seq1;
(*seq)->gain = peakDetect(*autocorr, &toa, NULL);
gRACHSequence = new CorrelationSequence;
gRACHSequence->sequence = _seq1;
gRACHSequence->buffer = data;
gRACHSequence->gain = peakDetect(*autocorr, &toa, NULL);
/* For 1 sps only
* (Half of correlation length - 1) + midpoint of pulse shaping filer
* 20.5 = (40 / 2 - 1) + 1.5
*/
if (sps == 1)
(*seq)->toa = toa - 20.5;
gRACHSequence->toa = toa - 20.5;
else
(*seq)->toa = 0.0;
gRACHSequence->toa = 0.0;
release:
delete autocorr;
@@ -1377,7 +1390,7 @@ release:
if (!status) {
delete _seq1;
free(data);
*seq = NULL;
gRACHSequence = NULL;
}
return status;
@@ -1444,7 +1457,7 @@ static signalVector *downsampleBurst(const signalVector &burst)
{
signalVector in(DOWNSAMPLE_IN_LEN, dnsampler->len());
signalVector *out = new signalVector(DOWNSAMPLE_OUT_LEN);
burst.copyToSegment(in, 0, DOWNSAMPLE_IN_LEN);
memcpy(in.begin(), burst.begin(), DOWNSAMPLE_IN_LEN * 2 * sizeof(float));
if (dnsampler->rotate((float *) in.begin(), DOWNSAMPLE_IN_LEN,
(float *) out->begin(), DOWNSAMPLE_OUT_LEN) < 0) {
@@ -1455,33 +1468,6 @@ static signalVector *downsampleBurst(const signalVector &burst)
return out;
};
/*
* Computes C/I (Carrier-to-Interference ratio) in dB (deciBels).
* 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)
{
float S, C;
int ps;
/* Integer position where the sequence starts */
ps = start + 1 - sync->sequence->size() + (int)roundf(toa);
/* Estimate Signal power */
S = 0.0f;
for (int i=0, j=ps; i<(int)sync->sequence->size(); i++,j++)
S += (*burst)[j].norm2();
S /= sync->sequence->size();
/* Esimate Carrier power */
C = xcorr.norm2() / ((sync->sequence->size() - 1) * sync->gain.abs());
/* Interference = Signal - Carrier, so C/I = C / (S - C) */
return 3.0103f * log2f(C / (S - C));
}
/*
* Detect a burst based on correlation and peak-to-average ratio
*
@@ -1492,13 +1478,11 @@ static float computeCI(const signalVector *burst, CorrelationSequence *sync,
*/
static int detectBurst(const signalVector &burst,
signalVector &corr, CorrelationSequence *sync,
float thresh, int sps, int start, int len,
struct estim_burst_params *ebp)
float thresh, int sps, complex *amp, float *toa,
int start, int len)
{
const signalVector *corr_in;
signalVector *dec = NULL;
complex xcorr;
int rc = 1;
if (sps == 4) {
dec = downsampleBurst(burst);
@@ -1510,43 +1494,36 @@ static int detectBurst(const signalVector &burst,
/* Correlate */
if (!convolve(corr_in, sync->sequence, &corr,
CUSTOM, start, len)) {
rc = -1;
goto del_ret;
CUSTOM, start, len, 1, 0)) {
delete dec;
return -1;
}
delete dec;
/* Running at the downsampled rate at this point */
sps = 1;
/* Peak detection - place restrictions at correlation edges */
ebp->amp = fastPeakDetect(corr, &ebp->toa);
*amp = fastPeakDetect(corr, toa);
if ((ebp->toa < 3 * sps) || (ebp->toa > len - 3 * sps)) {
rc = 0;
goto del_ret;
}
if ((*toa < 3 * sps) || (*toa > len - 3 * sps))
return 0;
/* Peak-to-average ratio */
if (computePeakRatio(&corr, sps, ebp->toa, ebp->amp) < thresh) {
rc = 0;
goto del_ret;
}
/* Peak -to-average ratio */
if (computePeakRatio(&corr, sps, *toa, *amp) < thresh)
return 0;
/* Refine TOA and correlation value */
xcorr = peakDetect(corr, &ebp->toa, NULL);
/* Compute C/I */
ebp->ci = computeCI(corr_in, sync, ebp->toa, start, xcorr);
/* Compute peak-to-average ratio. Reject if we don't have enough values */
*amp = peakDetect(corr, toa, NULL);
/* Normalize our channel gain */
ebp->amp = xcorr / sync->gain;
*amp = *amp / sync->gain;
/* Compensate for residuate time lag */
ebp->toa = ebp->toa - sync->toa;
*toa = *toa - sync->toa;
del_ret:
delete dec;
return rc;
return 1;
}
static float maxAmplitude(const signalVector &burst)
@@ -1570,10 +1547,13 @@ static float maxAmplitude(const signalVector &burst)
* head: Search symbols before target
* tail: Search symbols after target
*/
static int detectGeneralBurst(const signalVector &rxBurst, float thresh, int sps,
static int detectGeneralBurst(const signalVector &rxBurst,
float thresh,
int sps,
complex &amp,
float &toa,
int target, int head, int tail,
CorrelationSequence *sync,
struct estim_burst_params *ebp)
CorrelationSequence *sync)
{
int rc, start, len;
bool clipping = false;
@@ -1595,24 +1575,23 @@ static int detectGeneralBurst(const signalVector &rxBurst, float thresh, int sps
signalVector corr(len);
rc = detectBurst(rxBurst, corr, sync,
thresh, sps, start, len, ebp);
thresh, sps, &amp, &toa, start, len);
if (rc < 0) {
return -SIGERR_INTERNAL;
} else if (!rc) {
ebp->amp = 0.0f;
ebp->toa = 0.0f;
ebp->ci = 0.0f;
amp = 0.0f;
toa = 0.0f;
return clipping?-SIGERR_CLIP:SIGERR_NONE;
}
/* Subtract forward search bits from delay */
ebp->toa -= head;
toa -= head;
return 1;
}
/*
/*
* RACH burst detection
*
* Correlation window parameters:
@@ -1621,29 +1600,23 @@ static int detectGeneralBurst(const signalVector &rxBurst, float thresh, int sps
* tail: Search 8 symbols + maximum expected delay
*/
static int detectRACHBurst(const signalVector &burst, float threshold, int sps,
unsigned max_toa, bool ext, struct estim_burst_params *ebp)
complex &amplitude, float &toa, unsigned max_toa)
{
int rc, target, head, tail;
int i, num_seq;
CorrelationSequence *sync;
target = 8 + 40;
head = 8;
tail = 8 + max_toa;
num_seq = ext ? 3 : 1;
sync = gRACHSequence;
for (i = 0; i < num_seq; i++) {
rc = detectGeneralBurst(burst, threshold, sps, target, head, tail,
gRACHSequences[i], ebp);
if (rc > 0) {
ebp->tsc = i;
break;
}
}
rc = detectGeneralBurst(burst, threshold, sps, amplitude, toa,
target, head, tail, sync);
return rc;
}
/*
/*
* Normal burst detection
*
* Correlation window parameters:
@@ -1652,7 +1625,7 @@ static int detectRACHBurst(const signalVector &burst, float threshold, int sps,
* tail: Search 6 symbols + maximum expected delay
*/
static int analyzeTrafficBurst(const signalVector &burst, unsigned tsc, float threshold,
int sps, unsigned max_toa, struct estim_burst_params *ebp)
int sps, complex &amplitude, float &toa, unsigned max_toa)
{
int rc, target, head, tail;
CorrelationSequence *sync;
@@ -1665,13 +1638,13 @@ static int analyzeTrafficBurst(const signalVector &burst, unsigned tsc, float th
tail = 6 + max_toa;
sync = gMidambles[tsc];
ebp->tsc = tsc;
rc = detectGeneralBurst(burst, threshold, sps, target, head, tail, sync, ebp);
rc = detectGeneralBurst(burst, threshold, sps, amplitude, toa,
target, head, tail, sync);
return rc;
}
static int detectEdgeBurst(const signalVector &burst, unsigned tsc, float threshold,
int sps, unsigned max_toa, struct estim_burst_params *ebp)
int sps, complex &amplitude, float &toa, unsigned max_toa)
{
int rc, target, head, tail;
CorrelationSequence *sync;
@@ -1684,31 +1657,32 @@ static int detectEdgeBurst(const signalVector &burst, unsigned tsc, float thresh
tail = 6 + max_toa;
sync = gEdgeMidambles[tsc];
ebp->tsc = tsc;
rc = detectGeneralBurst(burst, threshold, sps,
target, head, tail, sync, ebp);
rc = detectGeneralBurst(burst, threshold, sps, amplitude, toa,
target, head, tail, sync);
return rc;
}
int detectAnyBurst(const signalVector &burst, unsigned tsc, float threshold,
int sps, CorrType type, unsigned max_toa,
struct estim_burst_params *ebp)
int sps, CorrType type, complex &amp, float &toa,
unsigned max_toa)
{
int rc = 0;
switch (type) {
case EDGE:
rc = detectEdgeBurst(burst, tsc, threshold, sps, max_toa, ebp);
rc = detectEdgeBurst(burst, tsc, threshold, sps,
amp, toa, max_toa);
if (rc > 0)
break;
else
type = TSC;
case TSC:
rc = analyzeTrafficBurst(burst, tsc, threshold, sps, max_toa, ebp);
rc = analyzeTrafficBurst(burst, tsc, threshold, sps,
amp, toa, max_toa);
break;
case EXT_RACH:
case RACH:
rc = detectRACHBurst(burst, threshold, sps, max_toa, type == EXT_RACH, ebp);
rc = detectRACHBurst(burst, threshold, sps, amp, toa,
max_toa);
break;
default:
LOG(ERR) << "Invalid correlation type";
@@ -1886,10 +1860,7 @@ bool sigProcLibSetup()
GSMPulse1 = generateGSMPulse(1);
GSMPulse4 = generateGSMPulse(4);
generateRACHSequence(&gRACHSequences[0], gRACHSynchSequenceTS0, 1);
generateRACHSequence(&gRACHSequences[1], gRACHSynchSequenceTS1, 1);
generateRACHSequence(&gRACHSequences[2], gRACHSynchSequenceTS2, 1);
generateRACHSequence(1);
for (int tsc = 0; tsc < 8; tsc++) {
generateMidamble(1, tsc);
gEdgeMidambles[tsc] = generateEdgeMidamble(tsc);

View File

@@ -29,7 +29,6 @@
enum CorrType{
OFF, ///< timeslot is off
TSC, ///< timeslot should contain a normal burst
EXT_RACH, ///< timeslot should contain an extended access burst
RACH, ///< timeslot should contain an access burst
EDGE, ///< timeslot should contain an EDGE burst
IDLE ///< timeslot is an idle (or dummy) burst
@@ -59,7 +58,7 @@ bool sigProcLibSetup();
void sigProcLibDestroy(void);
/** Operate soft slicer on a soft-bit vector */
void vectorSlicer(float *dest, const float *src, size_t len);
bool vectorSlicer(SoftVector *x);
/** GMSK modulate a GSM burst of bits */
signalVector *modulateBurst(const BitVector &wBurst,
@@ -101,26 +100,15 @@ void scaleVector(signalVector &x,
*/
float energyDetect(const signalVector &rxBurst,
unsigned windowLength);
/** Struct used to fill out parameters in detectAnyBurst(): estimated burst parameters
@param amplitude The estimated amplitude of received TSC burst.
@param toa The estimated time-of-arrival of received TSC burst (in symbols).
@param tsc The TSC used to detect the burst.
*/
struct estim_burst_params {
complex amp;
float toa;
uint8_t tsc;
float ci;
};
/**
8-PSK/GMSK/RACH burst detector
@param burst The received GSM burst of interest
@param tsc Midamble type (0..7) also known as TSC
@param threshold The threshold that the received burst's post-correlator SNR is compared against to determine validity.
@param sps The number of samples per GSM symbol.
@param amplitude The estimated amplitude of received TSC burst.
@param toa The estimate time-of-arrival of received TSC burst (in symbols).
@param max_toa The maximum expected time-of-arrival (in symbols).
@param ebp The estimated parameters of the detected burst.
@return positive value (CorrType) if threshold value is reached,
negative value (-SignalError) on error,
zero (SIGERR_NONE) if no burst is detected
@@ -130,8 +118,9 @@ int detectAnyBurst(const signalVector &burst,
float threshold,
int sps,
CorrType type,
unsigned max_toa,
struct estim_burst_params *ebp);
complex &amp,
float &toa,
unsigned max_toa);
/** Demodulate burst basde on type and output soft bits */
SoftVector *demodAnyBurst(const signalVector &burst, int sps,

View File

@@ -1,20 +1,20 @@
#include "signalVector.h"
signalVector::signalVector(size_t size, vector_alloc_func wAllocFunc, vector_free_func wFreeFunc)
: Vector<complex>(size, wAllocFunc, wFreeFunc),
signalVector::signalVector(size_t size)
: Vector<complex>(size),
real(false), aligned(false), symmetry(NONE)
{
}
signalVector::signalVector(size_t size, size_t start, vector_alloc_func wAllocFunc, vector_free_func wFreeFunc)
: Vector<complex>(size + start, wAllocFunc, wFreeFunc),
signalVector::signalVector(size_t size, size_t start)
: Vector<complex>(size + start),
real(false), aligned(false), symmetry(NONE)
{
mStart = mData + start;
}
signalVector::signalVector(complex *data, size_t start, size_t span, vector_alloc_func wAllocFunc, vector_free_func wFreeFunc)
: Vector<complex>(data, data + start, data + start + span, wAllocFunc, wFreeFunc),
signalVector::signalVector(complex *data, size_t start, size_t span)
: Vector<complex>(NULL, data + start, data + start + span),
real(false), aligned(false), symmetry(NONE)
{
}
@@ -41,14 +41,7 @@ signalVector::signalVector(const signalVector &vector,
void signalVector::operator=(const signalVector& vector)
{
resize(vector.size() + vector.getStart());
unsigned int i;
complex *dst = mData;
complex *src = vector.mData;
for (i = 0; i < size(); i++, src++, dst++)
*dst = *src;
/* TODO: optimize for non non-trivially copyable types: */
/*memcpy(mData, vector.mData, bytes()); */
memcpy(mData, vector.mData, bytes());
mStart = mData + vector.getStart();
}
@@ -65,13 +58,8 @@ size_t signalVector::getStart() const
size_t signalVector::updateHistory()
{
size_t num = getStart();
unsigned int i;
complex *dst = mData;
complex *src = mStart + this->size() - num;
for (i = 0; i < num; i++, src++, dst++)
*dst = *src;
/* TODO: optimize for non non-trivially copyable types: */
/*memmove(mData, mStart + this->size() - num, num * sizeof(complex)); */
memmove(mData, mStart + this->size() - num, num * sizeof(complex));
return num;
}

View File

@@ -13,13 +13,13 @@ enum Symmetry {
class signalVector: public Vector<complex> {
public:
/** Default constructor */
signalVector(size_t size = 0, vector_alloc_func wAllocFunc = NULL, vector_free_func wFreeFunc = NULL);
signalVector(size_t size = 0);
/** Construct with head room */
signalVector(size_t size, size_t start, vector_alloc_func wAllocFunc = NULL, vector_free_func wFreeFunc = NULL);
signalVector(size_t size, size_t start);
/** Construct from existing buffer data (buffer not managed) */
signalVector(complex *data, size_t start, size_t span, vector_alloc_func wAllocFunc = NULL, vector_free_func wFreeFunc = NULL);
signalVector(complex *data, size_t start, size_t span);
/** Construct by from existing vector */
signalVector(const signalVector &vector);

1578
autogen.sh Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,7 @@ AC_CANONICAL_BUILD
AC_CANONICAL_HOST
AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE([foreign subdir-objects])
AM_INIT_AUTOMAKE([subdir-objects])
dnl Linux kernel KBuild style compile messages
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
@@ -49,7 +49,6 @@ AC_PROG_LN_S
AC_PROG_MAKE_SET
AC_PROG_INSTALL
AC_PATH_PROG([RM_PROG], [rm])
AC_LANG([C++])
dnl check for pkg-config (explained in detail in libosmocore/configure.ac)
AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no)
@@ -75,9 +74,9 @@ AC_TYPE_SIZE_T
AC_HEADER_TIME
AC_C_BIGENDIAN
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.12.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.12.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 0.12.0)
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.11.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.11.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 0.11.0)
AC_ARG_ENABLE(sanitize,
[AS_HELP_STRING(
@@ -91,25 +90,6 @@ then
LDFLAGS="$LDFLAGS -fsanitize=address -fsanitize=undefined"
fi
AC_ARG_ENABLE(werror,
[AS_HELP_STRING(
[--enable-werror],
[Turn all compiler warnings into errors, with exceptions:
a) deprecation (allow upstream to mark deprecation without breaking builds);
b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds)
]
)],
[werror=$enableval], [werror="no"])
if test x"$werror" = x"yes"
then
WERROR_FLAGS="-Werror"
WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations"
WERROR_FLAGS+=" -Wno-error=cpp" # "#warning"
CFLAGS="$CFLAGS $WERROR_FLAGS"
CPPFLAGS="$CPPFLAGS $WERROR_FLAGS"
fi
AC_ARG_WITH(uhd, [
AS_HELP_STRING([--with-uhd],
[enable UHD based transceiver])
@@ -122,7 +102,7 @@ AC_ARG_WITH(usrp1, [
AC_ARG_WITH(lms, [
AS_HELP_STRING([--with-lms],
[enable LimeSuite based transceiver])
[enable LimeSuite gnuradio based transceiver])
])
AC_ARG_WITH(singledb, [
@@ -155,8 +135,6 @@ AS_IF([test "x$with_neon_vfpv4" = "xyes"], [
])
AS_IF([test "x$with_usrp1" = "xyes"], [
AC_CHECK_HEADER([boost/config.hpp],[],
[AC_MSG_ERROR([boost/config.hpp not found, install e.g. libboost-dev])])
PKG_CHECK_MODULES(USRP, usrp >= 3.3)
])
@@ -222,63 +200,8 @@ AM_CONDITIONAL(ARCH_ARM_A15, [test "x$with_neon_vfpv4" = "xyes"])
PKG_CHECK_MODULES(LIBUSB, libusb-1.0)
PKG_CHECK_MODULES(FFTWF, fftw3f)
# Generate manuals
AC_ARG_ENABLE(manuals,
[AS_HELP_STRING(
[--enable-manuals],
[Generate manual PDFs [default=no]],
)],
[osmo_ac_build_manuals=$enableval], [osmo_ac_build_manuals="no"])
AM_CONDITIONAL([BUILD_MANUALS], [test x"$osmo_ac_build_manuals" = x"yes"])
AC_ARG_VAR(OSMO_GSM_MANUALS_DIR, [path to common osmo-gsm-manuals files, overriding pkg-config and "../osmo-gsm-manuals"
fallback])
if test x"$osmo_ac_build_manuals" = x"yes"
then
# Find OSMO_GSM_MANUALS_DIR (env, pkg-conf, fallback)
if test -n "$OSMO_GSM_MANUALS_DIR"; then
echo "checking for OSMO_GSM_MANUALS_DIR... $OSMO_GSM_MANUALS_DIR (from env)"
else
OSMO_GSM_MANUALS_DIR="$($PKG_CONFIG osmo-gsm-manuals --variable=osmogsmmanualsdir 2>/dev/null)"
if test -n "$OSMO_GSM_MANUALS_DIR"; then
echo "checking for OSMO_GSM_MANUALS_DIR... $OSMO_GSM_MANUALS_DIR (from pkg-conf)"
else
OSMO_GSM_MANUALS_DIR="../osmo-gsm-manuals"
echo "checking for OSMO_GSM_MANUALS_DIR... $OSMO_GSM_MANUALS_DIR (fallback)"
fi
fi
if ! test -d "$OSMO_GSM_MANUALS_DIR"; then
AC_MSG_ERROR("OSMO_GSM_MANUALS_DIR does not exist! Install osmo-gsm-manuals or set OSMO_GSM_MANUALS_DIR.")
fi
# Find and run check-depends
CHECK_DEPENDS="$OSMO_GSM_MANUALS_DIR/check-depends.sh"
if ! test -x "$CHECK_DEPENDS"; then
CHECK_DEPENDS="osmo-gsm-manuals-check-depends"
fi
if ! $CHECK_DEPENDS; then
AC_MSG_ERROR("missing dependencies for --enable-manuals")
fi
# Put in Makefile with absolute path
OSMO_GSM_MANUALS_DIR="$(realpath "$OSMO_GSM_MANUALS_DIR")"
AC_SUBST([OSMO_GSM_MANUALS_DIR])
fi
# https://www.freedesktop.org/software/systemd/man/daemon.html
AC_ARG_WITH([systemdsystemunitdir],
[AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],,
[with_systemdsystemunitdir=auto])
AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [
def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
AS_IF([test "x$def_systemdsystemunitdir" = "x"],
[AS_IF([test "x$with_systemdsystemunitdir" = "xyes"],
[AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])])
with_systemdsystemunitdir=no],
[with_systemdsystemunitdir="$def_systemdsystemunitdir"])])
AS_IF([test "x$with_systemdsystemunitdir" != "xno"],
[AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])])
AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"])
AC_CHECK_HEADER([boost/config.hpp],[],
[AC_MSG_ERROR([boost/config.hpp not found, install e.g. libboost-dev])])
AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"])
AC_MSG_RESULT([CFLAGS="$CFLAGS"])
@@ -296,18 +219,12 @@ AC_CONFIG_FILES([\
Transceiver52M/arch/arm/Makefile \
Transceiver52M/arch/x86/Makefile \
Transceiver52M/device/Makefile \
Transceiver52M/device/common/Makefile \
Transceiver52M/device/uhd/Makefile \
Transceiver52M/device/usrp1/Makefile \
Transceiver52M/device/lms/Makefile \
tests/Makefile \
tests/CommonLibs/Makefile \
tests/Transceiver52M/Makefile \
doc/Makefile \
doc/examples/Makefile \
contrib/Makefile \
contrib/systemd/Makefile \
])
AC_OUTPUT(
doc/manuals/Makefile)
AC_OUTPUT

View File

@@ -1 +0,0 @@
SUBDIRS = systemd

View File

@@ -1,12 +1,4 @@
#!/bin/sh
# jenkins build helper script for osmo-trx. This is how we build on jenkins.osmocom.org
#
# environment variables:
# * INSTR: configure the CPU instruction set ("--with-sse", "--with-neon" or "--with-neon-vfpv4")
# * WITH_MANUALS: build manual PDFs if set to "1"
# * PUBLISH: upload manuals after building if set to "1" (ignored without WITH_MANUALS = "1")
# * INSIDE_CHROOT: (used internally) set to "1" when the script runs with QEMU in an ARM chroot
#
set -ex
substr() { [ -z "${2##*$1*}" ]; }
@@ -72,19 +64,10 @@ fi
mkdir "$deps" || true
osmo-build-dep.sh libosmocore "" "--enable-sanitize --disable-doxygen --disable-pcsc"
PARALLEL_MAKE="" osmo-build-dep.sh libusrp
osmo-build-dep.sh libusrp
export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
export LD_LIBRARY_PATH="$inst/lib"
export PATH="$inst/bin:$PATH"
CONFIG="--enable-sanitize --enable-werror --with-uhd --with-usrp1 --with-lms $INSTR"
# Additional configure options and depends
if [ "$WITH_MANUALS" = "1" ]; then
osmo-build-dep.sh osmo-gsm-manuals
CONFIG="$CONFIG --enable-manuals"
fi
set +x
echo
@@ -96,16 +79,9 @@ set -x
cd "$base"
autoreconf --install --force
./configure $CONFIG
./configure --enable-sanitize --with-uhd --with-usrp1 $INSTR
$MAKE $PARALLEL_MAKE
$MAKE check \
|| cat-testlogs.sh
DISTCHECK_CONFIGURE_FLAGS="$CONFIG" $MAKE distcheck \
|| cat-testlogs.sh
if [ "$WITH_MANUALS" = "1" ] && [ "$PUBLISH" = "1" ]; then
make -C "$base/doc/manuals" publish
fi
$MAKE maintainer-clean
osmo-clean-workspace.sh

View File

@@ -1,22 +0,0 @@
EXTRA_DIST = \
osmo-trx-lms.service \
osmo-trx-uhd.service \
osmo-trx-usrp1.service
if HAVE_SYSTEMD
SYSTEMD_SERVICES =
if DEVICE_UHD
SYSTEMD_SERVICES += osmo-trx-uhd.service
endif
if DEVICE_USRP1
SYSTEMD_SERVICES += osmo-trx-usrp1.service
endif
if DEVICE_LMS
SYSTEMD_SERVICES += osmo-trx-lms.service
endif
systemdsystemunit_DATA = $(SYSTEMD_SERVICES)
endif # HAVE_SYSTEMD

View File

@@ -1,11 +0,0 @@
[Unit]
Description=Osmocom SDR BTS L1 Transceiver (LimeSuite backend)
[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/osmo-trx-lms -C /etc/osmocom/osmo-trx-lms.cfg
RestartSec=2
[Install]
WantedBy=multi-user.target

Some files were not shown because too many files have changed in this diff Show More