Compare commits

...

55 Commits

Author SHA1 Message Date
Alexander Chemeris
0ffe815bbc Build: Remove dependency on SVN since we don't use it anymore anywhere. 2013-08-29 01:58:20 +04:00
Alexander Chemeris
19ae715e71 Transceiver52M: fixup for c5da660: We don't need to check for NULL with delete operator. 2013-07-16 07:33:57 +04:00
Alexander Chemeris
c4038ef54c CommonLibs: Adding a new ThreadsTest testsuite.
It's very basic at this moment. We should add a stress-test for thread
start/stop at least.
2013-07-14 23:11:54 +04:00
Alexander Chemeris
604f65e69f CommonLibs: Port InterthreadTest and SocketsTest to the new Thread API. 2013-07-14 23:11:54 +04:00
Alexander Chemeris
129ad76b15 Transceiver52M: Delay is no longer needed at the transceiver exit, threads should shut down cleanly. 2013-07-14 23:11:54 +04:00
Alexander Chemeris
de202e3435 Transceiver52M: Port all threads to the new Thread interface.
Also note that we introduced shutdown() method for Transceiver threads which
implement proper shutdown of threads when they are in blocking read state.
This involves using shutdown() on sockets and pushing NULL to queues.

With this change we should be able to start/stop transceiver channels at
arbitrary moments.
2013-07-14 23:11:54 +04:00
Alexander Chemeris
e279124660 CommonLibs: Rewrite Threads class to be predictable during start/stop.
Current implementation strictly synchronize thread startup and shutdown
to make sure a thread is actually started on startThread() exit and
that it's stopped on stopThread() exit (or an error is returned).

I also changed the Thread class to be used as a parent class for
an actual thread. This way we could provide much better logical
separation between variables used by different threads. Which will
(hopefully) reduce number of issues with inter-thread communications.
2013-07-14 23:11:54 +04:00
Alexander Chemeris
302a9198df CommonLibs: Signal::wait() should return the value which pthread_cond_timedwait() returns. 2013-07-14 23:11:54 +04:00
Alexander Chemeris
d4a8c1360e CommonLibs: Add shutdown() method to the DatagramSocket class.
This method is useful to abort a blocking operations on a socket during shutdown.
2013-07-14 23:11:54 +04:00
Alexander Chemeris
69b6a6dfcd CommonLibs: Allow NULLs to be retrieved from InterthreadQueue.
We need a way to stop InterthreadQueue blocking read to be able to shutdown
a thread. The easiest way to do that is to push NULL to the queue, but the
original implementation will just ignore that and continue blocking. After
the change the blocking read() will exit with NULL result which is perfectly
fine with us.

Ideally we should change all methods of InterthreadQueue to return a status
value to indicate normal exits, timeouts, etc. Right now the only way to
indicate an error is returning NULL, which could be a valid operation.
2013-07-14 23:11:54 +04:00
Alexander Chemeris
b864694652 CommonLibs: DEBUG: Mark interthread classes which are not used in OsmoTRX. 2013-07-14 23:11:54 +04:00
Alexander Chemeris
8eaa40dd6c Transceiver52M: Remove unused thread mAlignRadioServiceLoopThread; 2013-07-14 23:11:54 +04:00
Alexander Chemeris
dbd27a60b6 CommonLibs: Fix compile time warnings. 2013-07-14 14:59:00 +04:00
Alexander Chemeris
c5da6607b4 Transceiver52M: Fix crash in uhd_device destructor due to deleting statically allocated memory. 2013-07-14 14:59:00 +04:00
Thomas Tsou
42ade041d7 Transceiver52M: Setup independent gain and tune elements for dual channel 2013-07-14 13:37:03 +04:00
Thomas Tsou
5e18001bb0 Transceiver52M: Disable TSC check on slot setting 2013-07-14 13:36:55 +04:00
Thomas Tsou
621e52ab4a Transceiver52M: Disable dynamic filler table setting and fix deallocation 2013-07-14 13:36:42 +04:00
Alexander Chemeris
f86aa2c923 Transceiver52M: Fix build in the new repository. 2013-06-24 01:57:31 +04:00
Thomas Tsou
48f8fb34aa Transceiver52M: Setup UmTRX dual carrier support 2013-06-24 01:51:03 +04:00
Thomas Tsou
40c3d0a6d9 transceiver: mcbts: remove unused call in drive loop
Inside the drive loop addRadioVector() is duplicate call that was
not removed from the previous separation of the main loop and
transceiver instances.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:03 +04:00
Thomas Tsou
41c6657938 multi-arfcn: refactor to match upstream GSM core
This patch aligns the multicarrier (MC) USRP code with
released GSM core changes that accommodate the MC RAD1.
Primary changes are:

     1. Runtime setting of number of channelizer paths
     2. Matching channelizer path to ARFCN mapping of GSM core
     3. Use a single clock update socket on the drive loop
     4. Match transceiver data and control socket ports

Setting of channelizer paths (or width) was previously fixed
at compile time. In either case, channelizer width is limited
by the sample rate of the device and channel spacing of the
maximally decimated filterbank. Available settings are 1, 5,
and 10 channels, which accommodate any number of ARFCN's in
between. Also add the frequency offsets to handle the effective
shift in setting RF frequency.

Previous assumption was to place C0 at the center frequency,
but RAD1 assumes C0 at the leftmost carrier, so adjust
accordingly.

The rest is general consolidation to mostly match the RAD1
interaction with GSM core. There is some loss of flexibility to
run, say, multiple independent instances of OpenBTS through a
single bank of channelized transceivers. But, the better
compatibility and reduction in code is the appropriate tradeoff.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:02 +04:00
Thomas Tsou
cd576c9636 uhd: fix local overflow handling of buffer reads
This patches fixes the hypothetical bug in the read out of the
intermediate sample buffer after a local overflow condition.

Local overflows - occurring in the intermediate storage buffer
and not the UHD transport - should never occur; the existence
of a local overflow indicates a significant error elsewhere in
the system. For example, if timestamps or timing offsets are
ridiculously off, such errors may occur.

Nonetheless, handle errors anyways by taking the modulo value
of the calculated read index to stay within the buffer and avoid
seg faulting.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:02 +04:00
Thomas Tsou
3eeda4841d multi-arfcn, trx: create transceivers based on command line arg
Move from the hard coded case of 3 transceiver instances to a
command line determined value. 'M' potential channels will be
compiled into the build depending on preprocessor selections
in radioParams.h. The command line argument must be less M.

Channels are selected starting from 0, which is centered on the
RF tuning frequency. Subsequent channels are selected by shifting
outward from 0 (center) in a left before right pattern. Default
channel spacing is 400 kHz. The ordering for supported cases of
1, 5, and 10 path channelizers is as follows.

CHAN_M = 1

    { 0 }

CHAN_M = 5

    { 0, 1, 4, 2, 3 }

CHAN_M = 10

    { 0, 1, 9, 2, 8, 3, 7, 4, 6, 5}

Note: Channel 5 for the 10 channel case sits on the Nyquist
frequency and is therefor unusable.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:02 +04:00
Thomas Tsou
5d64491f9b transceiver, uhd: dynamically allocate async event thread
Similar to the previous commit titled,

"multi-arfcn, trx: allocate threads on heap and fix thread release"

there is the potential for a segfault on exit if the event thread
is never started. As before, address the issue by initializing
the Thread pointer with NULL and later allocating the object
immediately prior to use.

On stop or exit, allow the thread to exit by checking a condition
variable. If device is stopped or never started, the same variable
can be checked for state, which avoids attempts to deallocate an
empty pointer.

If there is a better method to shutdown / deallocate using the
OpenBTS thread library, please let me know.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:02 +04:00
Thomas Tsou
b5c450dfdf multi-arfcn, trx: attach FIFO's internally in transceiver
The original split-transceiver abstraction did not maintain
internal instances of the radio interface or drive loop.
The FIFO's were attached through external calls. The control
loop, however, made such an approach overly difficult, so
the transceiver now maintains pointers to the aforementioned
objects. In doing so, we no longer need external attachment
calls to setup the FIFO's.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:02 +04:00
Thomas Tsou
afb04f8b63 multi-arfcn, trx: fix infinite energy threshold bug
This fixes a bug where the energy threshold may reach infinity.

The transceiver energy detection threshold increase is
dependent on elapsed frames and the previous false detection
time. If we assume a (0,0) start time with the actual start
time - randomly determined - it's possible to get very
large elapsed frame counts at start. Once the threshold hits
'inf' further calculations are impossible and transceiver
is locked out from use.

Use the actual start time for initializing variables so
we avoid this scenario.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:02 +04:00
Thomas Tsou
5f13377b83 multi-arfcn, trx: handle thread exiting on shutdown
Previous approach was to allow stack unwinding to take care
shutdown and thread ending, which was unpredictable and
occasionally segfault. Attempt to shutdown more gracefully.

There are thread cancellation points in the transceiver code
using pthread_testcancel(), but the thread abstraction library
does not allow direct access to the pthread variables. This
prevents thread shutdown through pthread_cancel(). To get
around this, use boolean status values in the receive socket
service loops and main drive loop.

The socket read calls will block indefinitly, so shutdown
may cause the socket implementation to throw a SocketError
exception. Use of timeout values with reads does not seem to
work correctly or reliably, so catch the exception and ignore
if it occurs on shutdown.

The following error may appear as the socket is shutdown while
the Transceiver is blocking on read().

  DatagramSocket::read() failed: Bad file descriptor

So be it; the API doesn't allow us to do any more.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:02 +04:00
Thomas Tsou
a6ca73ca67 multi-arfcn, trx: allocate threads on heap and fix thread release
The underlying pthread of the Thread object isn't created until
Thread::start(). If the Thread object is contructed, but not
started, then the destructor will fail with a variety of
unpredictable errors such as the following or double free() in
certain cases.

Program received signal SIGSEGV, Segmentation fault.
__GI___libc_free (mem=0x3811abed3e946178) at malloc.c:2972
2972      if (chunk_is_mmapped(p))

If the Thread object is stack allocated, but start() isn't called,
destructor is guaranteed to run and will fail. The previous
approach was to dynamically allocate threads, but not free them,
thus avoiding memory errors, but creating memory leaks.

To get around this limitation, dynamically allocate Thread objects
and initialize with NULL. Then allocate immediately prior to start
such that pthread allocation is tied to the Thread object
constructor. Deallocation can check that the Thread pointer is
valid through NULL or other tracking methods.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:02 +04:00
Thomas Tsou
5a37840dfa multi-arfcn, trx: remove unused reset() call in drive loop
This call is a remnant of the Transceiver / DriveLoop split. The
empty call is never used.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:02 +04:00
Thomas Tsou
ca5d35cce8 multi-arfcn, trx: remove unused attach call()
At one point an attach() call was used to connect
multiple transceivers to the radio interface. The
current approach is to pass the radio interface to
the transceiver instances through the constructor.
Remove the unused and deprecated call.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:02 +04:00
Thomas Tsou
507e6d4e12 multi-arfcn, trx: only deallocate radio resouces if started
Certain notable variables - sample buffers - are not
allocated until start(), which causes a segfault if the
transceiver is shutdown without the radio starting. Check
that the radio is 'started' before releasing in the
destructor.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:02 +04:00
Thomas Tsou
1f330a9801 transceiver: define virtual destructor for base device
Lack of an explicitly defined virtual destructor was
causing the empty default interface destructor to be
called, which created a memory leak at shutdown.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:02 +04:00
Thomas Tsou
7c6f58af7a multi-arfcn, trx: add and modify transceiver main for new interfaces
Add a transceiver main() for multi-arfcn use and modify single
channel transceiver for use with updated interfaces.

Setup multiTRX with 3 channels for default case.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:51:02 +04:00
Thomas Tsou
996f426c16 multi-arfcn, trx: split transceiver to handle multiple channels
This patch separates the 'Transceiver' into a multi-channel
I/O component and single channel component. The latter may
may have multiple instances. The receive FIFO is converted to
a thread-safe queue.

The 'TransceiverIO' continuously drives the receive and transmit
loops. In this process, bursts are driven into thread-safe FIFO's
and read from the priority queues. Filler bursts are inserted if
no transmit data is available.

Each 'Transceiver' instance attaches to the I/O object and creates
its own threads and sockets, which include blocking on the receive
FIFO for the attached channel. Each instance also handles its own
control loop and clock indications.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:50:59 +04:00
Thomas Tsou
711e6afddf multi-arfcn, trx: modify radio interface for multi-channel use
The radio interface needs to feed the device I/O buffers
synchronously in order to drive the channelizer. Asynchronous
channel access occurs opposite the radio interface through
a bank of thread-safe FIFO's or priority queue's on receive
and transmit sides respectively.

Setup 'M' channels and allow only a subset to be active at a
given time. When a channel is unused, there is no need to
feed the particular receive FIFO or pull data from the
channel priority queue.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:46:34 +04:00
Thomas Tsou
222688d3dc multi-arfcn, trx: add header wrappers for radio interface
Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:46:34 +04:00
Thomas Tsou
20bc24d367 Transceiver52M: Add UmTRX support 2013-06-24 01:46:34 +04:00
Thomas Tsou
59796e1e3e Transceiver52M: Explicitly check for USRP2 device type
Before, we assumed that non-B100 device was implicitly a USRP2
(inclusive of N200/N210). Make the check explicit so that any
unknown device will causes an error and exit.

Signed-off-by: Thomas Tsou <tom@tsou.cc>
2013-06-24 01:46:34 +04:00
Thomas Tsou
19a506dffa Transceiver52M: Use exception blocks for rate changes
UHD will throw if something goes awry in these sensitive sections,
so we should catch and shutdown gracefully. There is no recovery
if we can't set rates.

Signed-off-by: Thomas Tsou <tom@tsou.cc>
2013-06-24 01:46:34 +04:00
Thomas Tsou
fbd6e1c985 Transceiver52M: Allow tolerance in UHD sample rate selection
We're performance floating point comparisons so allow a
10 Hz offset when UHD does not return an exact sample rate;

Signed-off-by: Thomas Tsou <tom@tsou.cc>
2013-06-24 01:46:34 +04:00
Thomas Tsou
03669856b7 Transceiver52M: Set resampling option automatically based on device
Remove the built time resampling selection and link both options.
Move the normal push/pullBuffer() calls back to the base class and
overload them in the inherited resampling class.

USRP2/N2xx devices are the only devices that require resampling so
return that resampling is necessary on the device open(), which is
the point at which the device type will be known.

Signed-off-by: Thomas Tsou <tom@tsou.cc>
2013-06-24 01:46:34 +04:00
Thomas Tsou
5d0e392b21 Transceiver52M: Set sample rate from within the radio device
The GSM transceiver only operates at a whole number multiple of
the GSM rate and doesn't care about the actual device rate and
if resampling is used. Therefore GSM specific portion of the
transceiver should only need to submit the samples-per-symbol
value to the device interface.

Then, the device should be able to determine the appropriate
sample rate (400 ksps or 270.833 ksps) and if resampling is
appropriate.

Signed-off-by: Thomas Tsou <tom@tsou.cc>
2013-06-24 01:46:34 +04:00
Thomas Tsou
801ce60d4a Transceiver52M: Remove and rename oversampling variables
The transceiver only uses a single integer oversampling value,
which is more simply referred to as samples-per-symbol.

mRadioOversampling --> mSPS
mTransceiverOversampling (removed)

Signed-off-by: Thomas Tsou <tom@tsou.cc>
2013-06-24 01:46:34 +04:00
Thomas Tsou
ed64e799bc Transceiver52M: Remove periodic alignment update from UHD build
Periodic timing alignment should never be required for UHD devices,
though the mechanism was used as a fallback mechanism should UHD
not properly recover after an underrun - as may occur in old
003.003.000 based revisions. This issue is not a concern in more
recent UHD releases and deprecates this code for legacy USRP1
use only.

Signed-off-by: Thomas Tsou <tom@tsou.cc>
2013-06-24 01:46:34 +04:00
Thomas Tsou
d1bcab2731 Transceiver52M: Add device offset correction table
Previously, two timing correction values were used for UHD devices
depending on the sample rate of 270.833e3 or 400e3 for native GSM or
resampled device rate respectively. The correction values compensate
for residual timing effects due to analog component delays, filters
lag times, and general fudge factors. These values are device
specific and over-generalized by the two value configuration.

This patch adds the following struct to store these correction
values by device type and sample rate - through samples-per-symbol.

struct uhd_dev_offset {
        enum uhd_dev_type type;
        int sps;
        double offset;
};

Signed-off-by: Thomas Tsou <tom@tsou.cc>
2013-06-24 01:46:34 +04:00
Thomas Tsou
00493f1a41 Transceiver52M: Add UHD device type checking
UHD device type was previously detected, but only categorized in
terms of bus type, USB or Ethernet, and sample rate capability.
With the number of supported device increasing, we can no longer
easily group devices since we need to handle more and more
device-specific peculiarities. Some of these factors are managed
internally by the UHD driver, but other factors (e.g. timing
offsets) are specific to a single device.

Start by maintaining an enumerated list of relevant device types
that we can use for applying device specific operations. Also
rename the USB/Ethernet grouping to transmit window type because
that's what it is.

enum uhd_dev_type {
        USRP1,
        USRP2,
        B100,
        NUM_USRP_TYPES,
};

Signed-off-by: Thomas Tsou <tom@tsou.cc>
2013-06-24 01:46:33 +04:00
Thomas Tsou
d565556b4b Transceiver52M: Setup test case for second UmTRX channel
Feed the second channel with the same data buffer as channel one.
The two channel send maintains the same UHD interface, so we use
the same metadata for both channels. Hard code the second channel
RF frequency as an offset relative to first channel for now.

Signed-off-by: Thomas Tsou <tom@tsou.cc>
2013-06-24 01:46:33 +04:00
Thomas Tsou
2b48784c61 Transceiver52M: Update to UHD streamer interface
This patch is long overdue and can now be merged after better understanding
of timestamp stability issues. UHD tick / timespec conversions were
generally used with the streamer interface, though these calls are actually
independent change sets. The combination would lead to internal rounding
errors and a timing drift most notably on B100 running at GSM symbol
rate multiples. There are no known issues, however, with the streamer code
itself.

The aforementioned issue was discovered in test code only, which was never
merged to mainline.

Signed-off-by: Thomas Tsou <tom@tsou.cc>
2013-06-24 01:46:33 +04:00
Thomas Tsou
635e34239c umtrx: set timing offset and clocking frequency
Measured offset and set to zero based on Nokia 3120 handset.

Signed-off-by: Thomas Tsou <tom@tsou.cc>
2013-06-24 01:46:33 +04:00
Alexander Chemeris
9e5e208b6e Transceier52M: Make error response to an unknown command on UDP command interface more understandable.
Previously we just repeated the last response which could confuse a command sender.
2013-06-24 01:46:33 +04:00
Alexander Chemeris
8f47387777 Transceiver52M: Check for correctly set TSC before setting timeslot types. 2013-06-24 01:46:33 +04:00
Ivan Kluchnikov
c707f42396 Modified fillerTable usage in pushRadioVector function.
Now we put to fillerTable only frames of BEACON channels, all others frames in fillerTable are dummy bursts.
2013-06-24 01:46:33 +04:00
Thomas Tsou
00ed1441a1 umtrx: flush any possible garbage bursts at start
In certain cases (higher sample-per-symbol counts), there is
some sensitivity to either timeouts or bad metadata on the
first packet. The first packet sets the transceiver clock, so
this is essential. As a workaround, drop the first 50 packets
to guarantee that we get a packet with a valid timestamp

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:46:10 +04:00
Thomas Tsou
8d804a4cd8 transceiver: workaround for transmit testing with no clock reset
Non-functional clock reset causes huge initial timing offset
between expected and received timestamps. Receive an initial
packet to 'set' the expected starting timestamp value for
both transmit and receive.

Signed-off-by: Thomas Tsou <ttsou@vt.edu>
2013-06-24 01:46:10 +04:00
Thomas Tsou
82ede3e810 Transceiver52M: add antenna selection from configuration
Set optional transmit and receive antennas from database configuration
file. Use default antenna values on empty string or if option does not
exist.

Signed-off-by: Thomas Tsou <tom@tsou.cc>
2013-06-24 01:45:04 +04:00
30 changed files with 2166 additions and 1462 deletions

View File

@@ -35,7 +35,7 @@
#ifdef DEBUG_CONFIG
#define debugLogEarly gLogEarly
#else
#define debugLogEarly
#define debugLogEarly(x,y,z)
#endif

View File

@@ -1,5 +1,6 @@
/*
* Copyright 2008, 2011 Free Software Foundation, Inc.
* Copyright 2013 Alexander Chemeris <Alexander.Chemeris@fairwaves.ru>
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
@@ -41,6 +42,7 @@
//@{
// UNUSED in osmo-trx
/** Pointer FIFO for interthread operations. */
// (pat) The elements in the queue are type T*, and
// the Fifo class implements the underlying queue.
@@ -98,7 +100,7 @@ template <class T, class Fifo=PointerFIFO> class InterthreadQueue {
{
ScopedLock lock(mLock);
T* retVal = (T*)mQ.get();
while (retVal==NULL) {
if (retVal==NULL) {
mWriteSignal.wait(mLock);
retVal = (T*)mQ.get();
}
@@ -155,6 +157,7 @@ template <class T, class Fifo=PointerFIFO> class InterthreadQueue {
}
};
// UNUSED in osmo-trx
// (pat) Identical to above but with the threading problem fixed.
template <class T, class Fifo=PointerFIFO> class InterthreadQueue2 {
@@ -276,6 +279,7 @@ template <class T, class Fifo=PointerFIFO> class InterthreadQueue2 {
// UNUSED in osmo-trx
/** Pointer FIFO for interthread operations. */
template <class T> class InterthreadQueueWithWait {
@@ -380,7 +384,7 @@ template <class T> class InterthreadQueueWithWait {
// UNUSED in osmo-trx
/** Thread-safe map of pointers to class D, keyed by class K. */
template <class K, class D > class InterthreadMap {
@@ -644,7 +648,7 @@ template <class T, class C = std::vector<T*>, class Cmp = PointerCompare<T> > cl
// UNUSED in osmo-trx
class Semaphore {
private:

View File

@@ -1,5 +1,6 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
* Copyright 2013 Alexander Chemeris <Alexander.Chemeris@fairwaves.ru>
*
*
* This software is distributed under the terms of the GNU Affero Public License.
@@ -27,66 +28,91 @@
#include "Threads.h"
#include "Interthread.h"
#include "Configuration.h"
#include <iostream>
using namespace std;
ConfigurationTable gConfig;
InterthreadQueue<int> gQ;
InterthreadMap<int,int> gMap;
void* qWriter(void*)
class QueueWriter : public Thread
{
int *p;
for (int i=0; i<20; i++) {
public:
QueueWriter() : Thread("QueueWriter") {}
protected:
virtual void runThread()
{
int *p;
for (int i=0; i<20; i++) {
p = new int;
*p = i;
COUT("queue write " << *p);
gQ.write(p);
msleep(1);
}
p = new int;
*p = i;
COUT("queue write " << *p);
*p = -1;
gQ.write(p);
if (random()%2) sleep(1);
}
p = new int;
*p = -1;
gQ.write(p);
return NULL;
}
};
void* qReader(void*)
class QueueReader : public Thread
{
bool done = false;
while (!done) {
int *p = gQ.read();
COUT("queue read " << *p);
if (*p<0) done=true;
delete p;
public:
QueueReader() : Thread("QueueReader") {}
protected:
virtual void runThread()
{
bool done = false;
while (!done) {
int *p = gQ.read();
COUT("queue read " << *p);
if (*p<0) done=true;
delete p;
}
}
return NULL;
}
};
void* mapWriter(void*)
class MapWriter : public Thread
{
int *p;
for (int i=0; i<20; i++) {
p = new int;
*p = i;
COUT("map write " << *p);
gMap.write(i,p);
if (random()%2) sleep(1);
}
return NULL;
}
public:
MapWriter() : Thread("MapWriter") {}
void* mapReader(void*)
{
for (int i=0; i<20; i++) {
int *p = gMap.read(i);
COUT("map read " << *p);
// InterthreadMap will delete the pointers
// delete p;
protected:
virtual void runThread()
{
int *p;
for (int i=0; i<20; i++) {
p = new int;
*p = i;
COUT("map write " << *p);
gMap.write(i,p);
msleep(1);
}
}
return NULL;
}
};
class MapReader : public Thread
{
public:
MapReader() : Thread("MapReader") {}
protected:
virtual void runThread()
{
for (int i=0; i<20; i++) {
int *p = gMap.read(i);
COUT("map read " << *p);
// InterthreadMap will delete the pointers
}
}
};
@@ -95,20 +121,25 @@ void* mapReader(void*)
int main(int argc, char *argv[])
{
Thread qReaderThread;
qReaderThread.start(qReader,NULL);
Thread mapReaderThread;
mapReaderThread.start(mapReader,NULL);
COUT("TEST 1: InterthreadQueue")
QueueReader qReaderThread;
QueueWriter qWriterThread;
qReaderThread.startThread();
qWriterThread.startThread();
// stopThread() will wait for a thread to stop for 5 seconds, which
// is more than enough for this test to finish.
qReaderThread.stopThread();
qWriterThread.stopThread();
Thread qWriterThread;
qWriterThread.start(qWriter,NULL);
Thread mapWriterThread;
mapWriterThread.start(mapWriter,NULL);
qReaderThread.join();
qWriterThread.join();
mapReaderThread.join();
mapWriterThread.join();
COUT("TEST 2: InterthreadMap")
MapReader mapReaderThread;
mapReaderThread.startThread();
MapWriter mapWriterThread;
mapWriterThread.startThread();
// stopThread() will wait for a thread to stop for 5 seconds, which
// is more than enough for this test to finish.
mapReaderThread.stopThread();
mapWriterThread.stopThread();
}

View File

@@ -44,6 +44,7 @@ libcommon_la_SOURCES = \
Utils.cpp
noinst_PROGRAMS = \
ThreadsTest \
BitVectorTest \
InterthreadTest \
SocketsTest \
@@ -80,12 +81,16 @@ URLEncodeTest_LDADD = libcommon.la
BitVectorTest_SOURCES = BitVectorTest.cpp
BitVectorTest_LDADD = libcommon.la $(SQLITE_LA)
ThreadsTest_SOURCES = ThreadsTest.cpp
ThreadsTest_LDADD = libcommon.la $(SQLITE_LA)
ThreadsTest_LDFLAGS = -lpthread
InterthreadTest_SOURCES = InterthreadTest.cpp
InterthreadTest_LDADD = libcommon.la
InterthreadTest_LDADD = libcommon.la $(SQLITE_LA)
InterthreadTest_LDFLAGS = -lpthread
SocketsTest_SOURCES = SocketsTest.cpp
SocketsTest_LDADD = libcommon.la
SocketsTest_LDADD = libcommon.la $(SQLITE_LA)
SocketsTest_LDFLAGS = -lpthread
TimevalTest_SOURCES = TimevalTest.cpp

View File

@@ -1,5 +1,6 @@
/*
* Copyright 2008, 2010 Free Software Foundation, Inc.
* Copyright 2013 Alexander Chemeris <Alexander.Chemeris@fairwaves.ru>
*
*
* This software is distributed under the terms of the GNU Affero Public License.
@@ -129,6 +130,11 @@ void DatagramSocket::close()
::close(mSocketFD);
}
void DatagramSocket::shutdown()
{
::shutdown(mSocketFD, SHUT_RDWR);
}
DatagramSocket::~DatagramSocket()
{

View File

@@ -1,5 +1,6 @@
/*
* Copyright 2008, 2010 Free Software Foundation, Inc.
* Copyright 2013 Alexander Chemeris <Alexander.Chemeris@fairwaves.ru>
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
@@ -134,6 +135,11 @@ public:
/** Close the socket. */
void close();
/** Shutdown the socket without destroying the descriptor
* Use this to interrupt blocking read()
*/
void shutdown();
};

View File

@@ -1,5 +1,6 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
* Copyright 2013 Alexander Chemeris <Alexander.Chemeris@fairwaves.ru>
*
*
* This software is distributed under the terms of the GNU Affero Public License.
@@ -28,59 +29,73 @@
#include "Sockets.h"
#include "Threads.h"
#include <stdio.h>
#include "Configuration.h"
#include "Timeval.h"
#include <stdlib.h>
ConfigurationTable gConfig;
static const int gNumToSend = 10;
void *testReaderIP(void *)
class TestReaderIP : public Thread
{
UDPSocket readSocket(5934, "localhost", 5061);
readSocket.nonblocking();
int rc = 0;
while (rc<gNumToSend) {
char buf[MAX_UDP_LENGTH];
int count = readSocket.read(buf);
if (count>0) {
COUT("read: " << buf);
rc++;
} else {
sleep(2);
public:
TestReaderIP() : Thread("TestReaderIP") {}
protected:
virtual void runThread()
{
UDPSocket readSocket(5934, "localhost", 5061);
readSocket.nonblocking();
int rc = 0;
while (rc<gNumToSend) {
char buf[MAX_UDP_LENGTH];
int count = readSocket.read(buf);
if (count>0) {
COUT("IP read: " << buf);
rc++;
} else {
COUT("IP sleeping...");
sleep(2);
}
}
}
return NULL;
}
};
void *testReaderUnix(void *)
class TestReaderUnix : public Thread
{
UDDSocket readSocket("testDestination");
readSocket.nonblocking();
int rc = 0;
while (rc<gNumToSend) {
char buf[MAX_UDP_LENGTH];
int count = readSocket.read(buf);
if (count>0) {
COUT("read: " << buf);
rc++;
} else {
sleep(2);
public:
TestReaderUnix() : Thread("TestReaderUnix") {}
protected:
virtual void runThread()
{
UDDSocket readSocket("testDestination");
readSocket.nonblocking();
int rc = 0;
while (rc<gNumToSend) {
char buf[MAX_UDP_LENGTH];
int count = readSocket.read(buf);
if (count>0) {
COUT("UNIX read: " << buf);
rc++;
} else {
COUT("UNIX sleeping...");
sleep(2);
}
}
}
return NULL;
}
};
int main(int argc, char * argv[] )
{
Thread readerThreadIP;
readerThreadIP.start(testReaderIP,NULL);
Thread readerThreadUnix;
readerThreadUnix.start(testReaderUnix,NULL);
TestReaderIP readerThreadIP;
TestReaderUnix readerThreadUnix;
readerThreadIP.startThread();
readerThreadUnix.startThread();
UDPSocket socket1(5061, "127.0.0.1",5934);
UDDSocket socket1U("testSource","testDestination");
@@ -92,12 +107,10 @@ int main(int argc, char * argv[] )
for (int i=0; i<gNumToSend; i++) {
socket1.write("Hello IP land");
socket1U.write("Hello Unix domain");
sleep(1);
socket1U.write("Hello Unix domain");
msleep(1);
}
readerThreadIP.join();
readerThreadUnix.join();
}
// vim: ts=4 sw=4

View File

@@ -1,5 +1,6 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
* Copyright 2013 Alexander Chemeris <Alexander.Chemeris@fairwaves.ru>
*
*
* This software is distributed under the terms of the GNU Affero Public License.
@@ -29,11 +30,26 @@
#include "Threads.h"
#include "Timeval.h"
#include "Logger.h"
#include <pthread.h>
#include <sys/types.h>
#include <errno.h> // for ETIMEDOUT
#include <sys/syscall.h> // for SYS_gettid
#include <sys/prctl.h> // Linux specific, for prctl(PR_SET_NAME)
// Make sure we get MCL_CURRENT and MCL_FUTURE (for mlockall) on OS X 10.3
#define _P1003_1B_VISIBLE
#include <sys/mman.h>
#undef _P1003_1B_VISIBLE
using namespace std;
#define POSIX_OK 0
#define POSIX_NO_WAIT 0
#define POSIX_WAIT_FOREVER (-1)
static inline int gettid() {return syscall(SYS_gettid);}
Mutex gStreamLock; ///< Global lock to control access to cout and cerr.
@@ -95,27 +111,235 @@ Mutex::~Mutex()
/** Block for the signal up to the cancellation timeout. */
void Signal::wait(Mutex& wMutex, unsigned timeout) const
int Signal::wait(Mutex& wMutex, unsigned timeout) const
{
Timeval then(timeout);
struct timespec waitTime = then.timespec();
pthread_cond_timedwait(&mSignal,&wMutex.mMutex,&waitTime);
return pthread_cond_timedwait(&mSignal,&wMutex.mMutex,&waitTime);
}
void Thread::start(void *(*task)(void*), void *arg)
Thread::Thread(const string &name, size_t stackSize)
: mThreadId((pthread_t)0)
, mThreadName(name)
, mStackSize(stackSize)
, mThreadState(THREAD_STATE_IDLE)
, mThreadData(NULL)
{
assert(mThread==((pthread_t)0));
bool res;
// (pat) Moved initialization to constructor to avoid crash in destructor.
//res = pthread_attr_init(&mAttrib);
//assert(!res);
res = pthread_attr_setstacksize(&mAttrib, mStackSize);
assert(!res);
res = pthread_create(&mThread, &mAttrib, task, arg);
assert(!res);
}
Thread::~Thread()
{
stopThread();
}
void *Thread::threadAdaptor(void *data)
{
Thread *pThread = (Thread*)data;
// If we ever receive a thread cancel request, it means that the Thread
// object is in the process of being destroyed. To avoid the situation
// where a thread attempts to run after its containing Thread object has
// been freed, we set the thread up so that the cancel takes effect
// immediately (as opposed to waiting until the next thread cancellation
// point).
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
// =====================================================================
// Synchronize with the start() in the parent thread.
{
// 1. Lock synchronization mutex.
ScopedLock lock(pThread->mThreadStartupMutex);
// 2. Btw, set the thread name, while we're inside the mutex.
// FIXME: This works on Linux with glibc >= 2.12. Under *BSD and MacOS X
// this function has different arguments.
// pthread_setname_np(pThread->mThreadId, pThread->mThreadName.c_str());
// FIXME: For some reason the previous call doesn't work on my Ubuntu 12.04,
// so we use this one which works.
prctl(PR_SET_NAME, pThread->mThreadName.c_str());
// 3. Signal that we've started.
pThread->mThreadStartStopEvent.signal();
// 4. Wait until start() finishes its initialization.
//
// The actual thread is created and started with pthread_create(), then
// start() does its housekeeping and sets mThreadState=THREAD_STATE_RUNNING.
// If we allow Thread::run() to start before this initialization completes,
// callers might think (among other things) that the thread is not started
// while it's actually started.
pThread->mThreadInitializedEvent.wait(pThread->mThreadStartupMutex);
}
// Synchronization with the parent thread is finished.
// =====================================================================
// Log Thread ID for debugging purposes
LOG(INFO) << "Thread started: " << pThread->mThreadName
<< " with lwp=" << gettid() << ", pid=" << getpid();
// Keep all memory locked into physical mem, to guarantee realtime-behaviour
int res = mlockall(MCL_CURRENT|MCL_FUTURE);
if (res != POSIX_OK) {
LOG(WARNING) << "Failed to lock memory for thread: " << pThread->mThreadName;
}
// Run the actual code
pThread->runThread();
// Huh, we're done. Signal to a (potentially) waiting stop()'s.
{
ScopedLock lock(pThread->mThreadStateMutex);
pThread->mThreadState = THREAD_STATE_IDLE;
pThread->mThreadStartStopEvent.broadcast();
}
return NULL;
}
Thread::ReturnStatus Thread::startThread(void *data)
{
pthread_attr_t attrib;
// timeval threadStartTime;
// timespec threadStartTimeout;
bool res;
// Lock startup synchronization mutex. It will be used in conjunction with
// mThreadInitializedEvent and mThreadStartStopEvent conditional variables.
ScopedLock lock(mThreadStartupMutex);
{
ScopedLock lock(mThreadStateMutex);
if (mThreadState != THREAD_STATE_IDLE)
return ALREADY_STARTED;
mThreadState = THREAD_STATE_STARTING;
}
// Save thread data pointer
mThreadData = data;
LOG(DEBUG) << "Starting thread " << mThreadName << " (" << this << ")";
// construct thread attribute
res = pthread_attr_init(&attrib);
if (res != POSIX_OK) {
LOG(ALERT) << "pthread_attr_init failed, returned " << res
<< " in " << mThreadName << " (" << this << ")";
}
// Set the thread stack size
res = pthread_attr_setstacksize(&attrib, mStackSize);
if (res != POSIX_OK)
{
LOG(ALERT) << "pthread_attr_setstacksize failed, returned " << res
<< " in " << mThreadName << " (" << this << ")";
}
// Create the thread detached
res = pthread_attr_setdetachstate(&attrib, PTHREAD_CREATE_DETACHED);
if (res != POSIX_OK)
{
LOG(ALERT) << "pthread_attr_setdetachstate failed, returned " << res
<< " in " << mThreadName << " (" << this << ")";
}
// =====================================================================
// Start the thread and synchronize with it
// Start the thread!
res = pthread_create(&mThreadId, &attrib, threadAdaptor, (void *)this);
// Attributes are no longer needed.
pthread_attr_destroy(&attrib);
if (res != POSIX_OK)
{
LOG(ALERT) << "pthread_create failed, returned " << res
<< " in " << mThreadName << " (" << this << ")";
return PTHREAD_ERROR;
}
// Wait for the thread to startup.
res = mThreadStartStopEvent.wait(mThreadStartupMutex, THREAD_STARTUP_TIMEOUT*1000);
// If the thread does not start in THREAD_STARTUP_TIMEOUT seconds,
// then something is terribly wrong here.
if (res == ETIMEDOUT)
{
LOG(ALERT) << "thread " << mThreadName << " (" << this << ") hasn't started up in "
<< THREAD_STARTUP_TIMEOUT << " seconds. Bailing out.";
return RETURN_TIMEOUT;
}
// We're done with the initialization.
ackThreadStart();
// ToDo: Add other initialization here, e.g. adding this thread to a list of all threads.
// Startup initialization finished. Signal this to started thread, so
// it could go on.
mThreadInitializedEvent.signal();
return RETURN_OK;
}
Thread::ReturnStatus Thread::stopThread()
{
int res;
LOG(DEBUG) << "Stopping thread " << mThreadName << " (" << this << ")";
while (1) {
ScopedLock lock(mThreadStateMutex);
switch (mThreadState) {
case THREAD_STATE_IDLE:
// Nothing to do.
return RETURN_OK;
case THREAD_STATE_STARTING:
// Something is wrong in thi world.
assert(mThreadState != THREAD_STATE_STARTING);
LOG(ALERT) << "Trying to stop thread " << mThreadName
<< " (" << this << ") while it's trying to start.";
return WRONG_STATE;
case THREAD_STATE_RUNNING:
// Request shudown
mThreadState = THREAD_STATE_STOPPING;
// no "break" here to fall through to the next case
case THREAD_STATE_STOPPING:
// Wait for the thread to stop.
LOG(DEBUG) << "Waiting for thread " << mThreadName << " (" << this << ") to stop.";
res = mThreadStartStopEvent.wait(mThreadStateMutex, THREAD_STOP_TIMEOUT*1000);
LOG(DEBUG) << "Thread " << mThreadName << " (" << this << ") signalled stop "
<< "with res=" << res << " and mThreadState=" << mThreadState;
// If the thread does not stop in THREAD_STOP_TIMEOUT seconds,
// return error. It may be waiting for something.
if (res == ETIMEDOUT)
{
LOG(ALERT) << "thread " << mThreadName << " (" << this << ") hasn't stopped in "
<< THREAD_STARTUP_TIMEOUT << " seconds. Bailing out.";
return RETURN_TIMEOUT;
}
// Conditional variable could return in case of a signal, so we should
// double check that the thread has indeed stopped.
if (mThreadState == THREAD_STATE_IDLE)
return RETURN_OK;
else
// Try again...
break;
}
}
// We should never reach this line
assert(false);
return RETURN_OK;
}
// vim: ts=4 sw=4

View File

@@ -1,5 +1,6 @@
/*
* Copyright 2008, 2011 Free Software Foundation, Inc.
* Copyright 2013 Alexander Chemeris <Alexander.Chemeris@fairwaves.ru>
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
@@ -121,14 +122,14 @@ class Signal {
Block for the signal up to the cancellation timeout.
Under Linux, spurious returns are possible.
*/
void wait(Mutex& wMutex, unsigned timeout) const;
int wait(Mutex& wMutex, unsigned timeout) const;
/**
Block for the signal.
Under Linux, spurious returns are possible.
*/
void wait(Mutex& wMutex) const
{ pthread_cond_wait(&mSignal,&wMutex.mMutex); }
int wait(Mutex& wMutex) const
{ return pthread_cond_wait(&mSignal,&wMutex.mMutex); }
void signal() { pthread_cond_signal(&mSignal); }
@@ -137,47 +138,105 @@ class Signal {
};
#define START_THREAD(thread,function,argument) \
thread.start((void *(*)(void*))function, (void*)argument);
/** A C++ wrapper for pthread threads. */
class Thread {
private:
public:
pthread_t mThread;
pthread_attr_t mAttrib;
// FIXME -- Can this be reduced now?
size_t mStackSize;
typedef void *(*Adaptor)(void*);
enum ReturnStatus {
RETURN_OK = 0,
ALREADY_STARTED,
ALREADY_IDLE,
PTHREAD_ERROR,
WRONG_STATE,
RETURN_TIMEOUT
};
enum ThreadState {
THREAD_STATE_IDLE, ///< Thread is not started. On start() => STARTING
THREAD_STATE_STARTING, ///< Thread is about to start. When actually started => RUNNING
THREAD_STATE_RUNNING, ///< Thread is active. On stop() => STOPPING
THREAD_STATE_STOPPING ///< Thread is about to stop. When actually stopped => IDLE
};
enum {
THREAD_STARTUP_TIMEOUT=5, ///< Time to wait for thread startup (in seconds).
THREAD_STOP_TIMEOUT=5 ///< Time to wait for thread stop (in seconds).
};
public:
/** Create a thread in a non-running state. */
Thread(const std::string &name, size_t stackSize = (65536*4));
/** Create a thread in a non-running state. */
Thread(size_t wStackSize = (65536*4)):mThread((pthread_t)0) {
pthread_attr_init(&mAttrib); // (pat) moved this here.
mStackSize=wStackSize;
}
/** Destroy the Thread. */
virtual ~Thread();
/**
Destroy the Thread.
It should be stopped and joined.
*/
// (pat) If the Thread is destroyed without being started, then mAttrib is undefined. Oops.
~Thread() { pthread_attr_destroy(&mAttrib); }
/** Start the thread. */
ReturnStatus startThread(void *data=NULL);
/** Stop the thread. */
ReturnStatus stopThread();
/** Start the thread on a task. */
void start(void *(*task)(void*), void *arg);
ThreadState getThreadState() const
{
ScopedLock lock(mThreadStateMutex);
return mThreadState;
}
/** Join a thread that will stop on its own. */
void join() { int s = pthread_join(mThread,NULL); assert(!s); mThread = 0; }
bool isThreadRunning() const
{
ScopedLock lock(mThreadStateMutex);
return mThreadState == THREAD_STATE_RUNNING;
}
void requestThreadStop()
{
ScopedLock lock(mThreadStateMutex);
if (mThreadState == THREAD_STATE_RUNNING)
mThreadState = THREAD_STATE_STOPPING;
}
bool isThreadStopping() const
{
ScopedLock lock(mThreadStateMutex);
return mThreadState == THREAD_STATE_STOPPING;
}
const std::string &getThreadName() const {return mThreadName;}
protected:
pthread_t mThreadId; ///< OS id of the thread.
const std::string mThreadName; ///< Name of the thread.
size_t mStackSize; ///< Requested stack size for the thread.
ThreadState mThreadState; ///< The current state of the thread.
mutable Mutex mThreadStateMutex; ///< Mutex to protect ThreadState variable
void *mThreadData; ///< Data to be passed to the thread loop.
Mutex mThreadStartupMutex; ///< Mutex, used with the next two conditional
///< variables to synchronize thread startup.
Signal mThreadInitializedEvent; ///< Conditional variable, signaling
///< that this thread object initialization is completed
///< and the thread could go on.
Signal mThreadStartStopEvent; ///< Conditional variable, signaling
///< that the thread is started and start() method could
///< return to caller.
/** Function with the actual thread loop.
* Override this function in child classes to do real work.
*/
virtual void runThread() =0;
// Static funciton which actually starts the run() method.
static void *threadAdaptor(void *data);
void ackThreadStart() {
ScopedLock lock(mThreadStateMutex);
assert(mThreadState == THREAD_STATE_STARTING);
mThreadState = THREAD_STATE_RUNNING;
}
void ackThreadStop() {
ScopedLock lock(mThreadStateMutex);
assert(mThreadState == THREAD_STATE_STOPPING);
mThreadState = THREAD_STATE_IDLE;
}
};
#endif
// vim: ts=4 sw=4

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2013 Alexander Chemeris <Alexander.Chemeris@fairwaves.ru>
*
*
* 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 "Threads.h"
#include "Timeval.h"
#include "Configuration.h"
#include <iostream>
ConfigurationTable gConfig;
class SimpleThreadTest : public Thread
{
public:
SimpleThreadTest() : Thread("SimpleThreadTest") {}
void runThread()
{
COUT(getThreadName() << ": Started thread");
while (isThreadRunning()) {
COUT(getThreadName() << ": Sleeping...");
msleep(50);
}
COUT(getThreadName() << ": Stopped thread");
}
};
void testSimpleStartStop()
{
SimpleThreadTest simpleThreadTest;
COUT("Main: Starting thread " << simpleThreadTest.getThreadName());
simpleThreadTest.startThread();
COUT("Main: Started thread " << simpleThreadTest.getThreadName());
msleep(30);
COUT("Main: Stopping thread " << simpleThreadTest.getThreadName());
simpleThreadTest.stopThread();
COUT("Main: Stopped thread " << simpleThreadTest.getThreadName());
}
void testDoubleRequestStop()
{
SimpleThreadTest simpleThreadTest;
COUT("Main: Starting thread " << simpleThreadTest.getThreadName());
simpleThreadTest.startThread();
COUT("Main: Started thread " << simpleThreadTest.getThreadName());
msleep(30);
COUT("Main: Requesting stop for thread " << simpleThreadTest.getThreadName());
simpleThreadTest.requestThreadStop();
msleep(30);
COUT("Main: Requesting stop for thread " << simpleThreadTest.getThreadName());
simpleThreadTest.requestThreadStop();
msleep(30);
COUT("Main: Stopping thread " << simpleThreadTest.getThreadName());
simpleThreadTest.stopThread();
COUT("Main: Stopped thread " << simpleThreadTest.getThreadName());
}
int main(int argc, char *argv[])
{
std::cout<< std::endl << "Simple start/stop test" << std::endl << std::endl ;
testSimpleStartStop();
std::cout << std::endl << "Double requestThreadStop() test" << std::endl << std::endl ;
testDoubleRequestStop();
}
// vim: ts=4 sw=4

View File

@@ -18,18 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#hack to get around symlink svn:externals in git -kurtis
top_srcdir = $(abs_top_srcdir)
top_builddir = $(abs_top_builddir)
COMMON_INCLUDEDIR = $(top_srcdir)/CommonLibs
GSM_INCLUDEDIR = $(top_srcdir)/GSM
SQLITE_INCLUDEDIR = $(top_srcdir)/sqlite3
SVNDEV = -D'SVN_REV="$(shell svnversion -n $(top_builddir))"'
STD_DEFINES_AND_INCLUDES = \
$(SVNDEV) \
-I$(COMMON_INCLUDEDIR) \
-I$(GSM_INCLUDEDIR) \
-I$(SQLITE_INCLUDEDIR)

View File

@@ -0,0 +1,268 @@
/*
* Copyright 2008, 2009, 2010, 2012 Free Software Foundation, Inc.
* Copyright 2013 Alexander Chemeris <Alexander.Chemeris@fairwaves.ru>
*
* This software is distributed under the terms of the GNU 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 General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include "DriveLoop.h"
#include <Logger.h>
using namespace GSM;
DriveLoop::DriveLoop(int wBasePort, const char *TRXAddress,
RadioInterface *wRadioInterface,
int wChanM, int wC0, int wSamplesPerSymbol,
GSM::Time wTransmitLatency)
: Thread("DriveLoop")
, mClockSocket(wBasePort, TRXAddress, wBasePort + 100)
, mC0(wC0)
{
mChanM = wChanM;
mSamplesPerSymbol = wSamplesPerSymbol;
mRadioInterface = wRadioInterface;
mStartTime = (random() % gHyperframe, 0);
mTransmitDeadlineClock = mStartTime;
mLatencyUpdateTime = mStartTime;
mTransmitLatency = wTransmitLatency;
mLastClockUpdateTime = mStartTime;
mRadioInterface->getClock()->set(mStartTime);
// generate pulse and setup up signal processing library
gsmPulse = generateGSMPulse(2, mSamplesPerSymbol);
LOG(DEBUG) << "gsmPulse: " << *gsmPulse;
sigProcLibSetup(mSamplesPerSymbol);
txFullScale = mRadioInterface->fullScaleInputValue();
// initialize filler tables with dummy bursts on C0, empty bursts otherwise
for (int i = 0; i < 8; i++) {
signalVector* modBurst = modulateBurst(gDummyBurst, *gsmPulse,
8 + (i % 4 == 0), mSamplesPerSymbol);
scaleVector(*modBurst, txFullScale);
for (int j = 0; j < 102; j++) {
for (int n = 0; n < mChanM; n++) {
if (n == mC0)
fillerTable[n][j][i] = new signalVector(*modBurst);
else
fillerTable[n][j][i] = new signalVector(modBurst->size());
}
}
delete modBurst;
for (int n = 0; n < mChanM; n++) {
fillerModulus[n][i] = 26;
mChanType[n][i] = NONE;
}
}
}
DriveLoop::~DriveLoop()
{
stopThread();
delete gsmPulse;
sigProcLibDestroy();
}
void DriveLoop::pushRadioVector(GSM::Time &nowTime)
{
int i;
radioVector *staleBurst;
radioVector *next;
for (i = 0; i < mChanM; i++) {
// dump stale bursts, if any
while (staleBurst = mTransmitPriorityQueue[i].getStaleBurst(nowTime)) {
// Even if the burst is stale, put it in the fillter table.
// (It might be an idle pattern.)
LOG(NOTICE) << "dumping STALE burst in TRX->USRP interface";
}
int TN = nowTime.TN();
int modFN = nowTime.FN() % fillerModulus[i][nowTime.TN()];
mTxBursts[i] = fillerTable[i][modFN][TN];
mIsFiller[i] = true;
mIsZero[i] = (mChanType[i][TN] == NONE);
// if queue contains data at the desired timestamp, stick it into FIFO
if (next = (radioVector*) mTransmitPriorityQueue[i].getCurrentBurst(nowTime)) {
LOG(DEBUG) << "transmitFIFO: wrote burst " << next << " at time: " << nowTime;
mTxBursts[i] = next;
mIsFiller[i] = false;
mIsZero[i] = false;
}
}
mRadioInterface->driveTransmitRadio(mTxBursts, mIsZero);
for (i = 0; i < mChanM; i++) {
if (!mIsFiller[i])
delete mTxBursts[i];
}
}
void DriveLoop::setModulus(int channel, int timeslot)
{
switch (mChanType[channel][timeslot]) {
case NONE:
case I:
case II:
case III:
case FILL:
fillerModulus[channel][timeslot] = 26;
break;
case IV:
case VI:
case V:
fillerModulus[channel][timeslot] = 51;
break;
//case V:
case VII:
fillerModulus[channel][timeslot] = 102;
break;
default:
break;
}
}
DriveLoop::CorrType DriveLoop::expectedCorrType(int channel, GSM::Time currTime)
{
unsigned burstTN = currTime.TN();
unsigned burstFN = currTime.FN();
switch (mChanType[channel][burstTN]) {
case NONE:
return OFF;
break;
case FILL:
return IDLE;
break;
case I:
return TSC;
/*if (burstFN % 26 == 25)
return IDLE;
else
return TSC;*/
break;
case II:
if (burstFN % 2 == 1)
return IDLE;
else
return TSC;
break;
case III:
return TSC;
break;
case IV:
case VI:
return RACH;
break;
case V: {
int mod51 = burstFN % 51;
if ((mod51 <= 36) && (mod51 >= 14))
return RACH;
else if ((mod51 == 4) || (mod51 == 5))
return RACH;
else if ((mod51 == 45) || (mod51 == 46))
return RACH;
else
return TSC;
break;
}
case VII:
if ((burstFN % 51 <= 14) && (burstFN % 51 >= 12))
return IDLE;
else
return TSC;
break;
case LOOPBACK:
if ((burstFN % 51 <= 50) && (burstFN % 51 >=48))
return IDLE;
else
return TSC;
break;
default:
return OFF;
break;
}
}
void DriveLoop::driveReceiveFIFO()
{
SoftVector *rxBurst = NULL;
int RSSI;
int TOA; // in 1/256 of a symbol
GSM::Time burstTime;
mRadioInterface->driveReceiveRadio();
}
/*
* Features a carefully controlled latency mechanism, to
* assure that transmit packets arrive at the radio/USRP
* before they need to be transmitted.
*
* Deadline clock indicates the burst that needs to be
* pushed into the FIFO right NOW. If transmit queue does
* not have a burst, stick in filler data.
*/
void DriveLoop::driveTransmitFIFO()
{
int i;
RadioClock *radioClock = (mRadioInterface->getClock());
while (radioClock->get() + mTransmitLatency > mTransmitDeadlineClock) {
pushRadioVector(mTransmitDeadlineClock);
mTransmitDeadlineClock.incTN();
}
// FIXME -- This should not be a hard spin.
// But any delay here causes us to throw omni_thread_fatal.
//else radioClock->wait();
}
void DriveLoop::writeClockInterface()
{
char command[50];
// FIXME -- This should be adaptive.
sprintf(command,"IND CLOCK %llu",
(unsigned long long) (mTransmitDeadlineClock.FN() + 2));
LOG(INFO) << "ClockInterface: sending " << command;
mClockSocket.write(command,strlen(command)+1);
mLastClockUpdateTime = mTransmitDeadlineClock;
}
void DriveLoop::runThread()
{
setPriority();
while (isThreadRunning()) {
driveReceiveFIFO();
driveTransmitFIFO();
}
}

188
Transceiver52M/DriveLoop.h Normal file
View File

@@ -0,0 +1,188 @@
/*
* Copyright 2008, 2012 Free Software Foundation, Inc.
* Copyright 2013 Alexander Chemeris <Alexander.Chemeris@fairwaves.ru>
*
* This software is distributed under the terms of the GNU 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 General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Compilation switches
TRANSMIT_LOGGING write every burst on the given slot to a log
*/
#ifndef _DRIVELOOP_H_
#define _DRIVELOOP_H_
#include "radioInterface.h"
#include "Interthread.h"
#include "GSMCommon.h"
#include "Sockets.h"
#include <sys/types.h>
#include <sys/socket.h>
/** Define this to be the slot number to be logged. */
//#define TRANSMIT_LOGGING 1
/** The Transceiver class, responsible for physical layer of basestation */
class DriveLoop : public Thread {
private:
GSM::Time mTransmitLatency; ///< latency between basestation clock and transmit deadline clock
GSM::Time mLatencyUpdateTime; ///< last time latency was updated
GSM::Time mLastClockUpdateTime; ///< last time clock update was sent up to core
UDPSocket mClockSocket; ///< socket for writing clock updates to GSM core
VectorQueue mTransmitPriorityQueue[CHAN_MAX]; ///< priority queue of transmit bursts received from GSM core
GSM::Time mTransmitDeadlineClock; ///< deadline for pushing bursts into transmit FIFO
GSM::Time mStartTime; ///< random start time of the radio clock
RadioInterface *mRadioInterface; ///< associated radioInterface object
double txFullScale; ///< full scale input to radio
double rxFullScale; ///< full scale output to radio
/** Number of channels supported by the channelizer */
int mChanM;
/** unmodulate a modulated burst */
#ifdef TRANSMIT_LOGGING
void unModulateVector(signalVector wVector);
#endif
/** Push modulated burst into transmit FIFO corresponding to a particular timestamp */
void pushRadioVector(GSM::Time &nowTime);
/** Pull and demodulate a burst from the receive FIFO */
SoftVector *pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset);
signalVector *gsmPulse; ///< the GSM shaping pulse for modulation
int mSamplesPerSymbol; ///< number of samples per GSM symbol
int fillerModulus[CHAN_MAX][8]; ///< modulus values of all timeslots, in frames
signalVector *fillerTable[CHAN_MAX][102][8]; ///< table of modulated filler waveforms for all timeslots
/** Channelizer path for primary ARFCN */
int mC0;
signalVector *mTxBursts[CHAN_MAX];
bool mIsFiller[CHAN_MAX];
bool mIsZero[CHAN_MAX];
public:
/** Transceiver constructor
@param wBasePort base port number of UDP sockets
@param TRXAddress IP address of the TRX manager, as a string
@param wSamplesPerSymbol number of samples per GSM symbol
@param wTransmitLatency initial setting of transmit latency
@param radioInterface associated radioInterface object
*/
DriveLoop(int wBasePort, const char *TRXAddress,
RadioInterface *wRadioInterface,
int wChanM = 1, int wC0 = 0,
int wSamplesPerSymbol = SAMPSPERSYM,
GSM::Time wTransmitLatency = GSM::Time(3, 0));
/** Destructor */
~DriveLoop();
VectorQueue *priorityQueue(int m) { return &mTransmitPriorityQueue[m]; }
/** Codes for burst types of received bursts*/
typedef enum {
OFF, ///< timeslot is off
TSC, ///< timeslot should contain a normal burst
RACH, ///< timeslot should contain an access burst
IDLE ///< timeslot is an idle (or dummy) burst
} CorrType;
/** Codes for channel combinations */
typedef enum {
FILL, ///< Channel is transmitted, but unused
I, ///< TCH/FS
II, ///< TCH/HS, idle every other slot
III, ///< TCH/HS
IV, ///< FCCH+SCH+CCCH+BCCH, uplink RACH
V, ///< FCCH+SCH+CCCH+BCCH+SDCCH/4+SACCH/4, uplink RACH+SDCCH/4
VI, ///< CCCH+BCCH, uplink RACH
VII, ///< SDCCH/8 + SACCH/8
NONE, ///< Channel is inactive, default
LOOPBACK ///< similar go VII, used in loopback testing
} ChannelCombination;
/** Set modulus for specific timeslot */
void setModulus(int channel, int timeslot);
/** return the expected burst type for the specified timestamp */
CorrType expectedCorrType(int channel, GSM::Time currTime);
void setTimeslot(int m, int timeslot, ChannelCombination comb)
{
mChanType[m][timeslot] = comb;
}
GSM::Time getStartTime() { return mStartTime; }
GSM::Time getLastClockUpdate() { return mLastClockUpdateTime; }
GSM::Time getDeadlineClock() { return mTransmitDeadlineClock; }
/** send messages over the clock socket */
void writeClockInterface(void);
private:
ChannelCombination mChanType[CHAN_MAX][8]; ///< channel types for all timeslots
protected:
/** drive reception and demodulation of GSM bursts */
void driveReceiveFIFO();
/** drive transmission of GSM bursts */
void driveTransmitFIFO();
/** drive handling of control messages from GSM core */
void driveControl();
/**
drive modulation and sorting of GSM bursts from GSM core
@return true if a burst was transferred successfully
*/
bool driveTransmitPriorityQueue();
virtual void runThread();
void reset();
/** set priority on current thread */
void setPriority() { mRadioInterface->setPriority(); }
};
/** FIFO thread loop */
void *RadioDriveLoopAdapter(DriveLoop *);
#endif /* _DRIVELOOP_H_ */

View File

@@ -52,21 +52,14 @@ COMMON_SOURCES = \
radioVector.cpp \
radioClock.cpp \
sigProcLib.cpp \
DriveLoop.cpp \
Transceiver.cpp \
DummyLoad.cpp
if RESAMPLE
libtransceiver_la_SOURCES = \
$(COMMON_SOURCES) \
radioIOResamp.cpp
else
libtransceiver_la_SOURCES = \
$(COMMON_SOURCES) \
radioIO.cpp
endif
$(COMMON_SOURCES)
noinst_PROGRAMS = \
USRPping \
transceiver \
sigProcLibTest
@@ -83,12 +76,7 @@ noinst_HEADERS = \
rcvLPF_651.h \
sendLPF_961.h
USRPping_SOURCES = USRPping.cpp
USRPping_LDADD = \
libtransceiver.la \
$(COMMON_LA) $(SQLITE_LA)
transceiver_SOURCES = runTransceiver.cpp
transceiver_SOURCES = multiTRX.cpp
transceiver_LDADD = \
libtransceiver.la \
$(GSM_LA) \
@@ -104,13 +92,11 @@ sigProcLibTest_LDADD = \
if UHD
libtransceiver_la_SOURCES += UHDDevice.cpp
transceiver_LDADD += $(UHD_LIBS)
USRPping_LDADD += $(UHD_LIBS)
sigProcLibTest_LDADD += $(UHD_LIBS)
else
if USRP1
libtransceiver_la_SOURCES += USRPDevice.cpp
transceiver_LDADD += $(USRP_LIBS)
USRPping_LDADD += $(USRP_LIBS)
sigProcLibTest_LDADD += $(USRP_LIBS)
else
#we should never be here, as one of the above mustbe defined for us to build

View File

@@ -1,5 +1,6 @@
/*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2008, 2009, 2010, 2012 Free Software Foundation, Inc.
* Copyright 2013 Alexander Chemeris <Alexander.Chemeris@fairwaves.ru>
*
* This software is distributed under the terms of the GNU Public License.
* See the COPYING file in the main directory for details.
@@ -48,72 +49,57 @@ using namespace GSM;
#define INIT_ENERGY_THRSHD 5.0f
Transceiver::Transceiver(int wBasePort,
const char *TRXAddress,
int wSamplesPerSymbol,
GSM::Time wTransmitLatency,
RadioInterface *wRadioInterface)
:mDataSocket(wBasePort+2,TRXAddress,wBasePort+102),
Transceiver::Transceiver(int wBasePort, const char *TRXAddress,
DriveLoop *wDriveLoop, RadioInterface *wRadioInterface,
int wSamplesPerSymbol, int wChannel, bool wPrimary)
:mBasePort(wBasePort), mTRXAddress(TRXAddress),
mDataSocket(wBasePort+2,TRXAddress,wBasePort+102),
mControlSocket(wBasePort+1,TRXAddress,wBasePort+101),
mClockSocket(wBasePort,TRXAddress,wBasePort+100)
mDriveLoop(wDriveLoop), mRadioInterface(wRadioInterface),
mSamplesPerSymbol(wSamplesPerSymbol), mTransmitPriorityQueue(NULL),
mChannel(wChannel), mPrimary(wPrimary)
{
//GSM::Time startTime(0,0);
//GSM::Time startTime(gHyperframe/2 - 4*216*60,0);
GSM::Time startTime(random() % gHyperframe,0);
mFIFOServiceLoopThread = new Thread(32768); ///< thread to push bursts into transmit FIFO
mControlServiceLoopThread = new Thread(32768); ///< thread to process control messages from GSM core
mTransmitPriorityQueueServiceLoopThread = new Thread(32768);///< thread to process transmit bursts from GSM core
mSamplesPerSymbol = wSamplesPerSymbol;
mRadioInterface = wRadioInterface;
mTransmitLatency = wTransmitLatency;
mTransmitDeadlineClock = startTime;
mLastClockUpdateTime = startTime;
mLatencyUpdateTime = startTime;
mRadioInterface->getClock()->set(startTime);
mMaxExpectedDelay = 0;
// generate pulse and setup up signal processing library
gsmPulse = generateGSMPulse(2,mSamplesPerSymbol);
LOG(DEBUG) << "gsmPulse: " << *gsmPulse;
sigProcLibSetup(mSamplesPerSymbol);
mTransmitPriorityQueue = mDriveLoop->priorityQueue(mChannel);
mReceiveFIFO = mRadioInterface->receiveFIFO(mChannel);
txFullScale = mRadioInterface->fullScaleInputValue();
rxFullScale = mRadioInterface->fullScaleOutputValue();
// initialize filler tables with dummy bursts, initialize other per-timeslot variables
// initialize per-timeslot variables
for (int i = 0; i < 8; i++) {
signalVector* modBurst = modulateBurst(gDummyBurst,*gsmPulse,
8 + (i % 4 == 0),
mSamplesPerSymbol);
scaleVector(*modBurst,txFullScale);
fillerModulus[i]=26;
for (int j = 0; j < 102; j++) {
fillerTable[j][i] = new signalVector(*modBurst);
}
delete modBurst;
mChanType[i] = NONE;
channelResponse[i] = NULL;
DFEForward[i] = NULL;
DFEFeedback[i] = NULL;
channelEstimateTime[i] = startTime;
channelEstimateTime[i] = mDriveLoop->getStartTime();
}
mOn = false;
mRunning = false;
mTxFreq = 0.0;
mRxFreq = 0.0;
mFreqOffset = 0.0;
mPower = -10;
mEnergyThreshold = INIT_ENERGY_THRSHD;
prevFalseDetectionTime = startTime;
prevFalseDetectionTime = mDriveLoop->getStartTime();
}
Transceiver::~Transceiver()
{
// Stop all threads before freeing up
mFIFOServiceLoop.shutdown();
mControlServiceLoop.shutdown();
mTransmitPriorityQueueServiceLoop.shutdown();
// Now free all allocated data
delete gsmPulse;
sigProcLibDestroy();
mTransmitPriorityQueue.clear();
mTransmitPriorityQueue->clear();
}
@@ -127,182 +113,18 @@ void Transceiver::addRadioVector(BitVector &burst,
mSamplesPerSymbol);
scaleVector(*modBurst,txFullScale * pow(10,-RSSI/10));
radioVector *newVec = new radioVector(*modBurst,wTime);
mTransmitPriorityQueue.write(newVec);
mTransmitPriorityQueue->write(newVec);
delete modBurst;
}
#ifdef TRANSMIT_LOGGING
void Transceiver::unModulateVector(signalVector wVector)
{
SoftVector *burst = demodulateBurst(wVector,
*gsmPulse,
mSamplesPerSymbol,
1.0,0.0);
LOG(DEBUG) << "LOGGED BURST: " << *burst;
/*
unsigned char burstStr[gSlotLen+1];
SoftVector::iterator burstItr = burst->begin();
for (int i = 0; i < gSlotLen; i++) {
// FIXME: Demod bits are inverted!
burstStr[i] = (unsigned char) ((*burstItr++)*255.0);
}
burstStr[gSlotLen]='\0';
LOG(DEBUG) << "LOGGED BURST: " << burstStr;
*/
delete burst;
}
#endif
void Transceiver::pushRadioVector(GSM::Time &nowTime)
{
// dump stale bursts, if any
while (radioVector* staleBurst = mTransmitPriorityQueue.getStaleBurst(nowTime)) {
// Even if the burst is stale, put it in the fillter table.
// (It might be an idle pattern.)
LOG(NOTICE) << "dumping STALE burst in TRX->USRP interface";
const GSM::Time& nextTime = staleBurst->getTime();
int TN = nextTime.TN();
int modFN = nextTime.FN() % fillerModulus[TN];
delete fillerTable[modFN][TN];
fillerTable[modFN][TN] = staleBurst;
}
int TN = nowTime.TN();
int modFN = nowTime.FN() % fillerModulus[nowTime.TN()];
// if queue contains data at the desired timestamp, stick it into FIFO
if (radioVector *next = (radioVector*) mTransmitPriorityQueue.getCurrentBurst(nowTime)) {
LOG(DEBUG) << "transmitFIFO: wrote burst " << next << " at time: " << nowTime;
delete fillerTable[modFN][TN];
fillerTable[modFN][TN] = new signalVector(*(next));
mRadioInterface->driveTransmitRadio(*(next),(mChanType[TN]==NONE)); //fillerTable[modFN][TN]));
delete next;
#ifdef TRANSMIT_LOGGING
if (nowTime.TN()==TRANSMIT_LOGGING) {
unModulateVector(*(fillerTable[modFN][TN]));
}
#endif
return;
}
// otherwise, pull filler data, and push to radio FIFO
mRadioInterface->driveTransmitRadio(*(fillerTable[modFN][TN]),(mChanType[TN]==NONE));
#ifdef TRANSMIT_LOGGING
if (nowTime.TN()==TRANSMIT_LOGGING)
unModulateVector(*fillerTable[modFN][TN]);
#endif
}
void Transceiver::setModulus(int timeslot)
{
switch (mChanType[timeslot]) {
case NONE:
case I:
case II:
case III:
case FILL:
fillerModulus[timeslot] = 26;
break;
case IV:
case VI:
case V:
fillerModulus[timeslot] = 51;
break;
//case V:
case VII:
fillerModulus[timeslot] = 102;
break;
case XIII:
fillerModulus[timeslot] = 52;
break;
default:
break;
}
}
Transceiver::CorrType Transceiver::expectedCorrType(GSM::Time currTime)
{
unsigned burstTN = currTime.TN();
unsigned burstFN = currTime.FN();
switch (mChanType[burstTN]) {
case NONE:
return OFF;
break;
case FILL:
return IDLE;
break;
case I:
return TSC;
/*if (burstFN % 26 == 25)
return IDLE;
else
return TSC;*/
break;
case II:
return TSC;
break;
case III:
return TSC;
break;
case IV:
case VI:
return RACH;
break;
case V: {
int mod51 = burstFN % 51;
if ((mod51 <= 36) && (mod51 >= 14))
return RACH;
else if ((mod51 == 4) || (mod51 == 5))
return RACH;
else if ((mod51 == 45) || (mod51 == 46))
return RACH;
else
return TSC;
break;
}
case VII:
if ((burstFN % 51 <= 14) && (burstFN % 51 >= 12))
return IDLE;
else
return TSC;
break;
case XIII: {
int mod52 = burstFN % 52;
if ((mod52 == 12) || (mod52 == 38))
return RACH;
else if ((mod52 == 25) || (mod52 == 51))
return IDLE;
else
return TSC;
break;
}
case LOOPBACK:
if ((burstFN % 51 <= 50) && (burstFN % 51 >=48))
return IDLE;
else
return TSC;
break;
default:
return OFF;
break;
}
}
SoftVector *Transceiver::pullRadioVector(GSM::Time &wTime,
int &RSSI,
int &timingOffset)
{
bool needDFE = (mMaxExpectedDelay > 1);
radioVector *rxBurst = (radioVector *) mReceiveFIFO->get();
radioVector *rxBurst = (radioVector *) mReceiveFIFO->read();
if (!rxBurst) return NULL;
@@ -310,9 +132,9 @@ SoftVector *Transceiver::pullRadioVector(GSM::Time &wTime,
int timeslot = rxBurst->getTime().TN();
CorrType corrType = expectedCorrType(rxBurst->getTime());
DriveLoop::CorrType corrType = mDriveLoop->expectedCorrType(mChannel, rxBurst->getTime());
if ((corrType==OFF) || (corrType==IDLE)) {
if ((corrType == DriveLoop::OFF) || (corrType == DriveLoop::IDLE)) {
delete rxBurst;
return NULL;
}
@@ -322,6 +144,7 @@ SoftVector *Transceiver::pullRadioVector(GSM::Time &wTime,
complex amplitude = 0.0;
float TOA = 0.0;
float avgPwr = 0.0;
if (!energyDetect(*vectorBurst,20*mSamplesPerSymbol,mEnergyThreshold,&avgPwr)) {
LOG(DEBUG) << "Estimated Energy: " << sqrt(avgPwr) << ", at time " << rxBurst->getTime();
double framesElapsed = rxBurst->getTime()-prevFalseDetectionTime;
@@ -339,8 +162,9 @@ SoftVector *Transceiver::pullRadioVector(GSM::Time &wTime,
// run the proper correlator
bool success = false;
if (corrType==TSC) {
if (corrType == DriveLoop::TSC) {
LOG(DEBUG) << "looking for TSC at time: " << rxBurst->getTime();
signalVector *channelResp;
double framesElapsed = rxBurst->getTime()-channelEstimateTime[timeslot];
bool estimateChannel = false;
@@ -413,7 +237,7 @@ SoftVector *Transceiver::pullRadioVector(GSM::Time &wTime,
// demodulate burst
SoftVector *burst = NULL;
if ((rxBurst) && (success)) {
if ((corrType==RACH) || (!needDFE)) {
if ((corrType == DriveLoop::RACH) || (!needDFE)) {
burst = demodulateBurst(*vectorBurst,
*gsmPulse,
mSamplesPerSymbol,
@@ -440,16 +264,68 @@ SoftVector *Transceiver::pullRadioVector(GSM::Time &wTime,
return burst;
}
void Transceiver::pullFIFO()
{
SoftVector *rxBurst = NULL;
int RSSI;
int TOA; // in 1/256 of a symbol
GSM::Time burstTime;
rxBurst = pullRadioVector(burstTime,RSSI,TOA);
if (rxBurst) {
LOG(DEBUG) << "burst parameters: "
<< " time: " << burstTime
<< " RSSI: " << RSSI
<< " TOA: " << TOA
<< " bits: " << *rxBurst;
char burstString[gSlotLen+10];
burstString[0] = burstTime.TN();
for (int i = 0; i < 4; i++) {
burstString[1+i] = (burstTime.FN() >> ((3-i)*8)) & 0x0ff;
}
burstString[5] = RSSI;
burstString[6] = (TOA >> 8) & 0x0ff;
burstString[7] = TOA & 0x0ff;
SoftVector::iterator burstItr = rxBurst->begin();
for (unsigned int i = 0; i < gSlotLen; i++) {
burstString[8+i] =(char) round((*burstItr++)*255.0);
}
burstString[gSlotLen+9] = '\0';
delete rxBurst;
mDataSocket.write(burstString,gSlotLen+10);
}
}
void Transceiver::start()
{
mControlServiceLoopThread->start((void * (*)(void*))ControlServiceLoopAdapter,(void*) this);
mRunning = true;
mControlServiceLoop.startThread((void*) this);
if (!mPrimary) {
mOn = true;
mFIFOServiceLoop.startThread((void*) this);
mTransmitPriorityQueueServiceLoop.startThread((void*) this);
}
}
void Transceiver::shutdown()
{
mOn = false;
mRunning = false;
mControlServiceLoop.shutdown();
mFIFOServiceLoop.shutdown();
mTransmitPriorityQueueServiceLoop.shutdown();
}
void Transceiver::reset()
{
mTransmitPriorityQueue.clear();
//mTransmitFIFO->clear();
//mReceiveFIFO->clear();
mTransmitPriorityQueue->clear();
}
@@ -462,10 +338,19 @@ void Transceiver::driveControl()
char buffer[MAX_PACKET_LENGTH];
int msgLen = -1;
buffer[0] = '\0';
msgLen = mControlSocket.read(buffer);
if (msgLen < 1) {
try {
msgLen = mControlSocket.read(buffer);
if (msgLen < 1) {
return;
}
} catch (...) {
/* Ignore the read exception on shutdown */
if (!mRunning) {
return;
}
LOG(ALERT) << "Caught UHD socket exception";
return;
}
@@ -474,8 +359,8 @@ void Transceiver::driveControl()
char response[MAX_PACKET_LENGTH];
sscanf(buffer,"%3s %s",cmdcheck,command);
writeClockInterface();
mDriveLoop->writeClockInterface();
if (strcmp(cmdcheck,"CMD")!=0) {
LOG(WARNING) << "bogus message on control interface";
@@ -489,22 +374,23 @@ void Transceiver::driveControl()
}
else if (strcmp(command,"POWERON")==0) {
// turn on transmitter/demod
if (!mTxFreq || !mRxFreq)
if (!mTxFreq || !mRxFreq || (mTSC<0))
sprintf(response,"RSP POWERON 1");
else {
sprintf(response,"RSP POWERON 0");
if (!mOn) {
if (mPrimary && !mOn) {
// Prepare for thread start
mPower = -20;
mRadioInterface->start();
mDriveLoop->startThread();
mDriveLoop->writeClockInterface();
generateRACHSequence(*gsmPulse,mSamplesPerSymbol);
// Start radio interface threads.
mFIFOServiceLoopThread->start((void * (*)(void*))FIFOServiceLoopAdapter,(void*) this);
mTransmitPriorityQueueServiceLoopThread->start((void * (*)(void*))TransmitPriorityQueueServiceLoopAdapter,(void*) this);
writeClockInterface();
mOn = true;
mFIFOServiceLoop.startThread((void*) this);
mTransmitPriorityQueueServiceLoop.startThread((void*) this);
}
}
}
@@ -519,8 +405,8 @@ void Transceiver::driveControl()
//set expected maximum time-of-arrival
int newGain;
sscanf(buffer,"%3s %s %d",cmdcheck,command,&newGain);
newGain = mRadioInterface->setRxGain(newGain);
mEnergyThreshold = INIT_ENERGY_THRSHD;
newGain = mRadioInterface->setRxGain(newGain, mChannel);
sprintf(response,"RSP SETRXGAIN 0 %d",newGain);
}
else if (strcmp(command,"NOISELEV")==0) {
@@ -540,7 +426,7 @@ void Transceiver::driveControl()
sprintf(response,"RSP SETPOWER 1 %d",dbPwr);
else {
mPower = dbPwr;
mRadioInterface->setPowerAttenuation(dbPwr);
mRadioInterface->setPowerAttenuation(dbPwr, mChannel);
sprintf(response,"RSP SETPOWER 0 %d",dbPwr);
}
}
@@ -555,37 +441,36 @@ void Transceiver::driveControl()
sprintf(response,"RSP ADJPOWER 0 %d",mPower);
}
}
#define FREQOFFSET 0//11.2e3
else if (strcmp(command,"RXTUNE")==0) {
// tune receiver
int freqKhz;
sscanf(buffer,"%3s %s %d",cmdcheck,command,&freqKhz);
mRxFreq = freqKhz*1.0e3+FREQOFFSET;
if (!mRadioInterface->tuneRx(mRxFreq)) {
LOG(ALERT) << "RX failed to tune";
sprintf(response,"RSP RXTUNE 1 %d",freqKhz);
mRxFreq = freqKhz * 1.0e3 + mFreqOffset;
if (!mRadioInterface->tuneRx(mRxFreq, mChannel)) {
LOG(ALERT) << "RX failed to tune";
sprintf(response,"RSP RXTUNE 1 %d",freqKhz);
} else {
sprintf(response,"RSP RXTUNE 0 %d",freqKhz);
}
else
sprintf(response,"RSP RXTUNE 0 %d",freqKhz);
}
else if (strcmp(command,"TXTUNE")==0) {
// tune txmtr
int freqKhz;
sscanf(buffer,"%3s %s %d",cmdcheck,command,&freqKhz);
//freqKhz = 890e3;
mTxFreq = freqKhz*1.0e3+FREQOFFSET;
if (!mRadioInterface->tuneTx(mTxFreq)) {
LOG(ALERT) << "TX failed to tune";
sprintf(response,"RSP TXTUNE 1 %d",freqKhz);
mTxFreq = freqKhz * 1.0e3 + mFreqOffset;
if (!mRadioInterface->tuneTx(mTxFreq, mChannel)) {
LOG(ALERT) << "TX failed to tune";
sprintf(response,"RSP TXTUNE 1 %d",freqKhz);
} else {
sprintf(response,"RSP TXTUNE 0 %d",freqKhz);
}
else
sprintf(response,"RSP TXTUNE 0 %d",freqKhz);
}
else if (strcmp(command,"SETTSC")==0) {
// set TSC
int TSC;
sscanf(buffer,"%3s %s %d",cmdcheck,command,&TSC);
if (mOn)
if (mOn || (TSC<0) || (TSC>7))
sprintf(response,"RSP SETTSC 1 %d",TSC);
else {
mTSC = TSC;
@@ -594,7 +479,7 @@ void Transceiver::driveControl()
}
}
else if (strcmp(command,"SETSLOT")==0) {
// set TSC
// set slot type
int corrCode;
int timeslot;
sscanf(buffer,"%3s %s %d %d",cmdcheck,command,&timeslot,&corrCode);
@@ -603,13 +488,14 @@ void Transceiver::driveControl()
sprintf(response,"RSP SETSLOT 1 %d %d",timeslot,corrCode);
return;
}
mChanType[timeslot] = (ChannelCombination) corrCode;
setModulus(timeslot);
mDriveLoop->setTimeslot(mChannel, timeslot, (DriveLoop::ChannelCombination) corrCode);
mDriveLoop->setModulus(mChannel, timeslot);
sprintf(response,"RSP SETSLOT 0 %d %d",timeslot,corrCode);
}
else {
LOG(WARNING) << "bogus command " << command << " on control interface.";
sprintf(response,"RSP ERR 1");
}
mControlSocket.write(response,strlen(response)+1);
@@ -618,14 +504,24 @@ void Transceiver::driveControl()
bool Transceiver::driveTransmitPriorityQueue()
{
char buffer[gSlotLen+50];
// check data socket
size_t msgLen = mDataSocket.read(buffer);
if (!mOn)
return true;
if (msgLen!=gSlotLen+1+4+1) {
LOG(ERR) << "badly formatted packet on GSM->TRX interface";
try {
size_t msgLen = mDataSocket.read(buffer);
if (msgLen!=gSlotLen+1+4+1) {
LOG(ERR) << "badly formatted packet on GSM->TRX interface";
return false;
}
} catch (...) {
if (!mOn) {
/* Shutdown condition. End the thread. */
return true;
}
LOG(ALERT) << "Caught UHD socket exception";
return false;
}
@@ -634,28 +530,12 @@ bool Transceiver::driveTransmitPriorityQueue()
for (int i = 0; i < 4; i++)
frameNum = (frameNum << 8) | (0x0ff & buffer[i+1]);
/*
if (GSM::Time(frameNum,timeSlot) > mTransmitDeadlineClock + GSM::Time(51,0)) {
// stale burst
//LOG(DEBUG) << "FAST! "<< GSM::Time(frameNum,timeSlot);
//writeClockInterface();
}*/
/*
DAB -- Just let these go through the demod.
if (GSM::Time(frameNum,timeSlot) < mTransmitDeadlineClock) {
// stale burst from GSM core
LOG(NOTICE) << "STALE packet on GSM->TRX interface at time "<< GSM::Time(frameNum,timeSlot);
return false;
}
*/
// periodically update GSM core clock
LOG(DEBUG) << "mTransmitDeadlineClock " << mTransmitDeadlineClock
<< " mLastClockUpdateTime " << mLastClockUpdateTime;
if (mTransmitDeadlineClock > mLastClockUpdateTime + GSM::Time(216,0))
writeClockInterface();
LOG(DEBUG) << "mTransmitDeadlineClock " << mDriveLoop->getDeadlineClock()
<< " mLastClockUpdateTime " << mDriveLoop->getLastClockUpdate();
if (mDriveLoop->getDeadlineClock() > mDriveLoop->getLastClockUpdate() + GSM::Time(216,0)) {
mDriveLoop->writeClockInterface();
}
LOG(DEBUG) << "rcvd. burst at: " << GSM::Time(frameNum,timeSlot);
@@ -676,154 +556,106 @@ bool Transceiver::driveTransmitPriorityQueue()
}
void Transceiver::driveReceiveFIFO()
Thread::ReturnStatus FIFOServiceLoopThread::shutdown()
{
Transceiver *transceiver = (Transceiver *)mThreadData;
SoftVector *rxBurst = NULL;
int RSSI;
int TOA; // in 1/256 of a symbol
GSM::Time burstTime;
if (transceiver == NULL)
// Nothing to do
return ALREADY_IDLE;
mRadioInterface->driveReceiveRadio();
rxBurst = pullRadioVector(burstTime,RSSI,TOA);
if (rxBurst) {
LOG(DEBUG) << "burst parameters: "
<< " time: " << burstTime
<< " RSSI: " << RSSI
<< " TOA: " << TOA
<< " bits: " << *rxBurst;
char burstString[gSlotLen+10];
burstString[0] = burstTime.TN();
for (int i = 0; i < 4; i++)
burstString[1+i] = (burstTime.FN() >> ((3-i)*8)) & 0x0ff;
burstString[5] = RSSI;
burstString[6] = (TOA >> 8) & 0x0ff;
burstString[7] = TOA & 0x0ff;
SoftVector::iterator burstItr = rxBurst->begin();
for (unsigned int i = 0; i < gSlotLen; i++) {
burstString[8+i] =(char) round((*burstItr++)*255.0);
}
burstString[gSlotLen+9] = '\0';
delete rxBurst;
mDataSocket.write(burstString,gSlotLen+10);
transceiver->mFIFOServiceLoop.requestThreadStop();
if (transceiver->mReceiveFIFO != NULL) {
// Write twice, because read() function may read twice in case of NULL.
transceiver->mReceiveFIFO->write(NULL);
transceiver->mReceiveFIFO->write(NULL);
}
return transceiver->mFIFOServiceLoop.stopThread();
}
void Transceiver::driveTransmitFIFO()
void FIFOServiceLoopThread::runThread()
{
Transceiver *transceiver = (Transceiver *)mThreadData;
/**
Features a carefully controlled latency mechanism, to
assure that transmit packets arrive at the radio/USRP
before they need to be transmitted.
while (isThreadRunning()) {
transceiver->pullFIFO();
}
Deadline clock indicates the burst that needs to be
pushed into the FIFO right NOW. If transmit queue does
not have a burst, stick in filler data.
*/
LOG(DEBUG) << "FIFOServiceLoopThread has finished operations";
}
RadioClock *radioClock = (mRadioInterface->getClock());
Thread::ReturnStatus ControlServiceLoopThread::shutdown()
{
Transceiver *transceiver = (Transceiver *)mThreadData;
if (mOn) {
//radioClock->wait(); // wait until clock updates
LOG(DEBUG) << "radio clock " << radioClock->get();
while (radioClock->get() + mTransmitLatency > mTransmitDeadlineClock) {
// if underrun, then we're not providing bursts to radio/USRP fast
// enough. Need to increase latency by one GSM frame.
if (mRadioInterface->getBus() == RadioDevice::USB) {
if (mRadioInterface->isUnderrun()) {
// 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;
mLatencyUpdateTime = radioClock->get();
}
}
else {
// if underrun hasn't occurred in the last sec (216 frames) drop
// transmit latency by a timeslot
if (mTransmitLatency > GSM::Time(USB_LATENCY_MIN)) {
if (radioClock->get() > mLatencyUpdateTime + GSM::Time(216,0)) {
mTransmitLatency.decTN();
LOG(INFO) << "reduced latency: " << mTransmitLatency;
mLatencyUpdateTime = radioClock->get();
}
}
}
}
// time to push burst to transmit FIFO
pushRadioVector(mTransmitDeadlineClock);
mTransmitDeadlineClock.incTN();
}
if (transceiver == NULL)
// Nothing to do
return ALREADY_IDLE;
transceiver->mControlServiceLoop.requestThreadStop();
// FIXME: We should use shutdown() here, but the socket should be
// re-openned on the next start() then. Righ now the socket
// is created in the constructor and if we shutdown() it here,
// we'll get errors when we try to use it on the next start.
// transceiver->mControlSocket.shutdown();
{
// mBasePort+1 is mControlSocket port
UDPSocket tmpSock(0, "127.0.0.1", transceiver->mBasePort+1);
tmpSock.write(NULL, 0);
}
// FIXME -- This should not be a hard spin.
// But any delay here causes us to throw omni_thread_fatal.
//else radioClock->wait();
return transceiver->mControlServiceLoop.stopThread();
}
void Transceiver::writeClockInterface()
void ControlServiceLoopThread::runThread()
{
char command[50];
// FIXME -- This should be adaptive.
sprintf(command,"IND CLOCK %llu",(unsigned long long) (mTransmitDeadlineClock.FN()+2));
Transceiver *transceiver = (Transceiver *)mThreadData;
LOG(INFO) << "ClockInterface: sending " << command;
mClockSocket.write(command,strlen(command)+1);
mLastClockUpdateTime = mTransmitDeadlineClock;
}
void *FIFOServiceLoopAdapter(Transceiver *transceiver)
{
transceiver->setPriority();
while (1) {
transceiver->driveReceiveFIFO();
transceiver->driveTransmitFIFO();
pthread_testcancel();
}
return NULL;
}
void *ControlServiceLoopAdapter(Transceiver *transceiver)
{
while (1) {
while (isThreadRunning()) {
transceiver->driveControl();
pthread_testcancel();
}
return NULL;
LOG(DEBUG) << "ControlServiceLoopThread has finished operations";
}
void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *transceiver)
Thread::ReturnStatus TransmitPriorityQueueServiceLoopThread::shutdown()
{
while (1) {
Transceiver *transceiver = (Transceiver *)mThreadData;
if (transceiver == NULL)
// Nothing to do
return ALREADY_IDLE;
transceiver->mTransmitPriorityQueueServiceLoop.requestThreadStop();
// FIXME: We should use shutdown() here, but the socket should be
// re-openned on the next start() then. Righ now the socket
// is created in the constructor and if we shutdown() it here,
// we'll get errors when we try to use it on the next start.
// transceiver->mDataSocket.shutdown();
{
// mBasePort+2 is mDataSocket port
UDPSocket tmpSock(0, "127.0.0.1", transceiver->mBasePort+2);
tmpSock.write(NULL, 0);
}
return transceiver->mTransmitPriorityQueueServiceLoop.stopThread();
}
void TransmitPriorityQueueServiceLoopThread::runThread()
{
Transceiver *transceiver = (Transceiver *)mThreadData;
while (isThreadRunning()) {
bool stale = false;
// Flush the UDP packets until a successful transfer.
while (!transceiver->driveTransmitPriorityQueue()) {
stale = true;
}
if (stale) {
// If a packet was stale, remind the GSM stack of the clock.
transceiver->writeClockInterface();
transceiver->getDriveLoop()->writeClockInterface();
}
pthread_testcancel();
}
return NULL;
LOG(DEBUG) << "TransmitPriorityQueueServiceLoopThread has finished operations";
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
* Copyright 2008, 2012 Free Software Foundation, Inc.
* Copyright 2013 Alexander Chemeris <Alexander.Chemeris@fairwaves.ru>
*
* This software is distributed under the terms of the GNU Public License.
* See the COPYING file in the main directory for details.
@@ -29,6 +30,7 @@
TRANSMIT_LOGGING write every burst on the given slot to a log
*/
#include "DriveLoop.h"
#include "radioInterface.h"
#include "Interthread.h"
#include "GSMCommon.h"
@@ -40,63 +42,63 @@
/** Define this to be the slot number to be logged. */
//#define TRANSMIT_LOGGING 1
/** FIFO thread loop */
class FIFOServiceLoopThread : public Thread {
public:
FIFOServiceLoopThread() : Thread("FIFOServiceLoopThread") {}
Thread::ReturnStatus shutdown();
protected:
virtual void runThread();
};
/** control message handler thread loop */
class ControlServiceLoopThread : public Thread {
public:
ControlServiceLoopThread() : Thread("ControlServiceLoopThread") {}
Thread::ReturnStatus shutdown();
protected:
virtual void runThread();
};
/** transmit queueing thread loop */
class TransmitPriorityQueueServiceLoopThread : public Thread {
public:
TransmitPriorityQueueServiceLoopThread() : Thread("TransmitPriorityQueueServiceLoopThread") {}
Thread::ReturnStatus shutdown();
protected:
virtual void runThread();
};
/** The Transceiver class, responsible for physical layer of basestation */
class Transceiver {
private:
DriveLoop *mDriveLoop;
GSM::Time mTransmitLatency; ///< latency between basestation clock and transmit deadline clock
GSM::Time mLatencyUpdateTime; ///< last time latency was updated
int mBasePort; ///< Base port address for all our ports
std::string mTRXAddress; ///< Address of the BTS TRX control interface
UDPSocket mDataSocket; ///< socket for writing to/reading from GSM core
UDPSocket mControlSocket; ///< socket for writing/reading control commands from GSM core
UDPSocket mClockSocket; ///< socket for writing clock updates to GSM core
VectorQueue mTransmitPriorityQueue; ///< priority queue of transmit bursts received from GSM core
VectorFIFO* mTransmitFIFO; ///< radioInterface FIFO of transmit bursts
VectorQueue *mTransmitPriorityQueue; ///< priority queue of transmit bursts received from GSM core
VectorFIFO* mReceiveFIFO; ///< radioInterface FIFO of receive bursts
Thread *mFIFOServiceLoopThread; ///< thread to push/pull bursts into transmit/receive FIFO
Thread *mControlServiceLoopThread; ///< thread to process control messages from GSM core
Thread *mTransmitPriorityQueueServiceLoopThread;///< thread to process transmit bursts from GSM core
friend class FIFOServiceLoopThread;
FIFOServiceLoopThread mFIFOServiceLoop; ///< thread to push/pull bursts into transmit/receive FIFO
friend class ControlServiceLoopThread;
ControlServiceLoopThread mControlServiceLoop; ///< thread to process control messages from GSM core
friend class TransmitPriorityQueueServiceLoopThread;
TransmitPriorityQueueServiceLoopThread mTransmitPriorityQueueServiceLoop;///< thread to process transmit bursts from GSM core
GSM::Time mTransmitDeadlineClock; ///< deadline for pushing bursts into transmit FIFO
GSM::Time mLastClockUpdateTime; ///< last time clock update was sent up to core
int mChannel; ///< channelizer attach number between 0 and 'M-1'
RadioInterface *mRadioInterface; ///< associated radioInterface object
double txFullScale; ///< full scale input to radio
double rxFullScale; ///< full scale output to radio
/** Codes for burst types of received bursts*/
typedef enum {
OFF, ///< timeslot is off
TSC, ///< timeslot should contain a normal burst
RACH, ///< timeslot should contain an access burst
IDLE ///< timeslot is an idle (or dummy) burst
} CorrType;
/** Codes for channel combinations */
typedef enum {
FILL, ///< Channel is transmitted, but unused
I, ///< TCH/FS
II, ///< TCH/HS, idle every other slot
III, ///< TCH/HS
IV, ///< FCCH+SCH+CCCH+BCCH, uplink RACH
V, ///< FCCH+SCH+CCCH+BCCH+SDCCH/4+SACCH/4, uplink RACH+SDCCH/4
VI, ///< CCCH+BCCH, uplink RACH
VII, ///< SDCCH/8 + SACCH/8
VIII, ///< TCH/F + FACCH/F + SACCH/M
IX, ///< TCH/F + SACCH/M
X, ///< TCH/FD + SACCH/MD
XI, ///< PBCCH+PCCCH+PDTCH+PACCH+PTCCH
XII, ///< PCCCH+PDTCH+PACCH+PTCCH
XIII, ///< PDTCH+PACCH+PTCCH
NONE, ///< Channel is inactive, default
LOOPBACK ///< similar go VII, used in loopback testing
} ChannelCombination;
/** unmodulate a modulated burst */
#ifdef TRANSMIT_LOGGING
void unModulateVector(signalVector wVector);
@@ -115,29 +117,24 @@ private:
int &RSSI,
int &timingOffset);
/** Set modulus for specific timeslot */
void setModulus(int timeslot);
/** return the expected burst type for the specified timestamp */
CorrType expectedCorrType(GSM::Time currTime);
/** send messages over the clock socket */
void writeClockInterface(void);
void pullFIFO(void); ///< blocking call on receive FIFO
signalVector *gsmPulse; ///< the GSM shaping pulse for modulation
int mSamplesPerSymbol; ///< number of samples per GSM symbol
bool mOn; ///< flag to indicate that transceiver is powered on
ChannelCombination mChanType[8]; ///< channel types for all timeslots
bool mRunning; ///< flag to indicate control loop is running
bool mPrimary; ///< flag to indicate C0 channel
double mTxFreq; ///< the transmit frequency
double mRxFreq; ///< the receive frequency
double mFreqOffset; ///< RF frequency offset
int mPower; ///< the transmit power in dB
unsigned mTSC; ///< the midamble sequence code
double mEnergyThreshold; ///< threshold to determine if received data is potentially a GSM burst
GSM::Time prevFalseDetectionTime; ///< last timestamp of a false energy detection
int fillerModulus[8]; ///< modulus values of all timeslots, in frames
signalVector *fillerTable[102][8]; ///< table of modulated filler waveforms for all timeslots
unsigned mMaxExpectedDelay; ///< maximum expected time-of-arrival offset in GSM symbols
GSM::Time channelEstimateTime[8]; ///< last timestamp of each timeslot's channel estimate
@@ -148,6 +145,8 @@ private:
float chanRespOffset[8]; ///< most recent timing offset, e.g. TOA, of all timeslots
complex chanRespAmplitude[8]; ///< most recent channel amplitude of all timeslots
static int mTSC; ///< the midamble sequence code
public:
/** Transceiver constructor
@@ -157,23 +156,19 @@ public:
@param wTransmitLatency initial setting of transmit latency
@param radioInterface associated radioInterface object
*/
Transceiver(int wBasePort,
const char *TRXAddress,
int wSamplesPerSymbol,
GSM::Time wTransmitLatency,
RadioInterface *wRadioInterface);
Transceiver(int wBasePort, const char *TRXAddress,
DriveLoop *wDriveLoop, RadioInterface *wRadioInterface,
int wSamplesPerSymbol = SAMPSPERSYM,
int wChannel = 0, bool wPrimary = true);
/** Destructor */
~Transceiver();
/** start the Transceiver */
void start();
/** attach the radioInterface receive FIFO */
void receiveFIFO(VectorFIFO *wFIFO) { mReceiveFIFO = wFIFO;}
/** attach the radioInterface transmit FIFO */
void transmitFIFO(VectorFIFO *wFIFO) { mTransmitFIFO = wFIFO;}
/** shutdown (teardown threads) the Transceiver */
void shutdown();
protected:
@@ -192,25 +187,17 @@ protected:
*/
bool driveTransmitPriorityQueue();
friend void *FIFOServiceLoopAdapter(Transceiver *);
friend void *ControlServiceLoopAdapter(Transceiver *);
friend void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *);
void reset();
/** return transceiver on/off status */
bool on() { return mOn; }
/** return control loop operational status */
bool running() { return mRunning; }
/** return the drive loop pointer */
DriveLoop *getDriveLoop() { return mDriveLoop; }
/** set priority on current thread */
void setPriority() { mRadioInterface->setPriority(); }
};
/** FIFO thread loop */
void *FIFOServiceLoopAdapter(Transceiver *);
/** control message handler thread loop */
void *ControlServiceLoopAdapter(Transceiver *);
/** transmit queueing thread loop */
void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *);

View File

@@ -3,6 +3,7 @@
* Written by Thomas Tsou <ttsou@vt.edu>
*
* Copyright 2010,2011 Free Software Foundation, Inc.
* Copyright 2013 Alexander Chemeris <Alexander.Chemeris@fairwaves.ru>
*
* 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
@@ -32,31 +33,98 @@
#include "config.h"
#endif
#define U1_DEFAULT_CLK_RT 64e6
#define U2_DEFAULT_CLK_RT 100e6
#define NUM_TX_CHANS 2
#define NUM_RX_CHANS NUM_TX_CHANS
#define TX_CHAN_OFFSET 2e6
#define B100_CLK_RT 52e6
#define USRP2_BASE_RT 400e3
#define TX_AMPL 0.3;
#define SAMPLE_BUF_SZ (1 << 20)
enum uhd_dev_type {
USRP1,
USRP2,
B100,
UMTRX,
NUM_USRP_TYPES,
};
struct uhd_dev_offset {
enum uhd_dev_type type;
int sps;
double offset;
};
static TIMESTAMP init_rd_ts = 0;
/*
master_clk_rt - Master clock frequency - ignored if host resampling is
enabled
* Tx / Rx sample offset values. In a perfect world, there is no group delay
* though analog components, and behaviour through digital filters exactly
* matches calculated values. In reality, there are unaccounted factors,
* which are captured in these empirically measured (using a loopback test)
* timing correction values.
*
* Notes:
* USRP1 with timestamps is not supported by UHD.
*/
static struct uhd_dev_offset uhd_offsets[NUM_USRP_TYPES * 3] = {
{ USRP1, 1, 0.0 },
{ USRP1, 2, 0.0 },
{ USRP1, 4, 0.0 },
{ USRP2, 1, 5.4394e-5 },
{ USRP2, 2, 0.0 },
{ USRP2, 4, 0.0 },
{ B100, 1, 9.4778e-5 },
{ B100, 2, 5.1100e-5 },
{ B100, 4, 2.9418e-5 },
{ UMTRX, 1, 9.1738e-5 },
{ UMTRX, 2, 0.0 },
{ UMTRX, 4, 0.0 },
};
rx_smpl_offset - Timing correction in seconds between receive and
transmit timestamps. This value corrects for delays on
on the RF side of the timestamping point of the device.
This value is generally empirically measured.
static double get_dev_offset(enum uhd_dev_type type, int sps)
{
if (type == USRP1) {
LOG(ERR) << "Invalid device type";
return 0.0;
}
smpl_buf_sz - The receive sample buffer size in bytes.
switch (sps) {
case 1:
return uhd_offsets[3 * type + 0].offset;
case 2:
return uhd_offsets[3 * type + 1].offset;
case 4:
return uhd_offsets[3 * type + 2].offset;
}
tx_ampl - Transmit amplitude must be between 0 and 1.0
*/
const double master_clk_rt = 52e6;
const size_t smpl_buf_sz = (1 << 20);
const float tx_ampl = .3;
LOG(ERR) << "Unsupported samples-per-symbols: " << sps;
return 0.0;
}
#ifdef RESAMPLE
const double rx_smpl_offset = .00005;
#else
const double rx_smpl_offset = .0000869;
#endif
/*
* Select sample rate based on device type and requested samples-per-symbol.
* The base rate is either GSM symbol rate, 270.833 kHz, or the minimum
* usable channel spacing of 400 kHz.
*/
static double select_rate(uhd_dev_type type, int sps)
{
if ((sps != 4) && (sps != 2) && (sps != 1))
return -9999.99;
switch (type) {
case USRP2:
return USRP2_BASE_RT * sps;
break;
case B100:
case UMTRX:
return GSMRATE * sps;
break;
}
LOG(ALERT) << "Unknown device type " << type;
return -9999.99;
}
/** Timestamp conversion
@param timestamp a UHD or OpenBTS timestamp
@@ -139,6 +207,15 @@ private:
size_t data_end;
};
/** transmit queueing thread loop */
class UHDAsyncEventThread : public Thread {
public:
UHDAsyncEventThread() : Thread("UHDAsyncEventThread") {}
protected:
virtual void runThread();
};
/*
uhd_device - UHD implementation of the Device interface. Timestamped samples
are sent to and received from the device. An intermediate buffer
@@ -148,44 +225,48 @@ private:
*/
class uhd_device : public RadioDevice {
public:
uhd_device(double rate, bool skip_rx);
uhd_device(int sps, bool skip_rx);
~uhd_device();
bool open(const std::string &args);
int open(const std::string &args);
bool start();
bool stop();
void restart(uhd::time_spec_t ts);
void setPriority();
enum busType getBus() { return bus; }
enum TxWindowType getWindowType() { return tx_window; }
int readSamples(short *buf, int len, bool *overrun,
TIMESTAMP timestamp, bool *underrun, unsigned *RSSI);
int readSamples(short **buf, int chans, int len, TIMESTAMP timestamp,
bool *overrun, bool *underrun, unsigned *RSSI);
int writeSamples(short *buf, int len, bool *underrun,
TIMESTAMP timestamp, bool isControl);
int writeSamples(short **buf, int chans, int len, TIMESTAMP timestamp,
bool *underrun, bool isControl);
bool updateAlignment(TIMESTAMP timestamp);
bool setTxFreq(double wFreq);
bool setRxFreq(double wFreq);
bool setTxFreq(double wFreq, int chan);
bool setRxFreq(double wFreq, int chan);
inline TIMESTAMP initialWriteTimestamp() { return 0; }
inline TIMESTAMP initialReadTimestamp() { return 0; }
inline TIMESTAMP initialWriteTimestamp() { return init_rd_ts; }
inline TIMESTAMP initialReadTimestamp() { return init_rd_ts; }
inline double fullScaleInputValue() { return 32000 * tx_ampl; }
inline double fullScaleInputValue() { return 32000 * TX_AMPL; }
inline double fullScaleOutputValue() { return 32000; }
double setRxGain(double db);
double getRxGain(void) { return rx_gain; }
double setRxGain(double db, int chan);
double getRxGain(int chan) { return !chan ? rx_gain[0] : tx_gain[1]; }
double maxRxGain(void) { return rx_gain_max; }
double minRxGain(void) { return rx_gain_min; }
double setTxGain(double db);
double setTxGain(double db, int chan);
double maxTxGain(void) { return tx_gain_max; }
double minTxGain(void) { return tx_gain_min; }
void setTxAntenna(std::string &name);
void setRxAntenna(std::string &name);
std::string getRxAntenna();
std::string getTxAntenna();
double getTxFreq() { return tx_freq; }
double getRxFreq() { return rx_freq; }
double getTxFreq(int chan) { return !chan ? tx_freq[0] : tx_freq[1]; }
double getRxFreq(int chan) { return !chan ? rx_freq[0] : rx_freq[1]; }
inline double getSampleRate() { return actual_smpl_rt; }
inline double numberRead() { return rx_pkt_cnt; }
@@ -195,6 +276,7 @@ public:
@return true if message received or false on timeout or error
*/
bool recv_async_msg();
bool running() { return started; }
enum err_code {
ERROR_TIMING = -1,
@@ -204,14 +286,18 @@ public:
private:
uhd::usrp::multi_usrp::sptr usrp_dev;
enum busType bus;
uhd::tx_streamer::sptr tx_stream;
uhd::rx_streamer::sptr rx_stream;
enum TxWindowType tx_window;
enum uhd_dev_type dev_type;
int sps;
double desired_smpl_rt, actual_smpl_rt;
double tx_gain, tx_gain_min, tx_gain_max;
double rx_gain, rx_gain_min, rx_gain_max;
double tx_gain[NUM_TX_CHANS], tx_gain_min, tx_gain_max;
double rx_gain[NUM_RX_CHANS], rx_gain_min, rx_gain_max;
double tx_freq, rx_freq;
double tx_freq[NUM_TX_CHANS], rx_freq[NUM_RX_CHANS];
size_t tx_spp, rx_spp;
bool started;
@@ -223,11 +309,12 @@ private:
uhd::time_spec_t prev_ts;
TIMESTAMP ts_offset;
smpl_buf *rx_smpl_buf;
smpl_buf *rx_smpl_buf[NUM_RX_CHANS];
void init_gains();
void set_ref_clk(bool ext_clk);
double set_rates(double rate);
int set_master_clk(double rate);
int set_rates(double rate);
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);
@@ -235,14 +322,14 @@ private:
std::string str_code(uhd::rx_metadata_t metadata);
std::string str_code(uhd::async_metadata_t metadata);
Thread async_event_thrd;
UHDAsyncEventThread async_event_thrd;
};
void *async_event_loop(uhd_device *dev)
void UHDAsyncEventThread::runThread()
{
while (1) {
uhd_device *dev = (uhd_device *)mThreadData;
while (isThreadRunning()) {
dev->recv_async_msg();
pthread_testcancel();
}
}
@@ -268,23 +355,34 @@ void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg)
}
}
uhd_device::uhd_device(double rate, bool skip_rx)
: desired_smpl_rt(rate), actual_smpl_rt(0),
tx_gain(0.0), tx_gain_min(0.0), tx_gain_max(0.0),
rx_gain(0.0), rx_gain_min(0.0), rx_gain_max(0.0),
tx_freq(0.0), rx_freq(0.0), tx_spp(0), rx_spp(0),
started(false), aligned(false), rx_pkt_cnt(0), drop_cnt(0),
prev_ts(0,0), ts_offset(0), rx_smpl_buf(NULL)
uhd_device::uhd_device(int sps, bool skip_rx)
: 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),
rx_pkt_cnt(0), drop_cnt(0), prev_ts(0,0), ts_offset(0)
{
this->sps = sps;
this->skip_rx = skip_rx;
for (int i = 0; i < NUM_TX_CHANS; i++) {
tx_freq[i] = 0.0f;
tx_gain[i] = 0.0f;
}
for (int i = 0; i < NUM_RX_CHANS; i++) {
rx_smpl_buf[i] = NULL;
rx_freq[i] = 0.0f;
rx_gain[i] = 0.0f;
}
}
uhd_device::~uhd_device()
{
stop();
if (rx_smpl_buf)
delete rx_smpl_buf;
for (int i = 0; i < NUM_RX_CHANS; i++) {
delete rx_smpl_buf[i];
}
}
void uhd_device::init_gains()
@@ -299,95 +397,135 @@ void uhd_device::init_gains()
rx_gain_min = range.start();
rx_gain_max = range.stop();
usrp_dev->set_tx_gain((tx_gain_min + tx_gain_max) / 2);
usrp_dev->set_rx_gain((rx_gain_min + rx_gain_max) / 2);
for (int i = 0; i < NUM_TX_CHANS; i++) {
usrp_dev->set_tx_gain((tx_gain_min + tx_gain_max) / 2, i);
tx_gain[i] = usrp_dev->get_tx_gain(i);
}
tx_gain = usrp_dev->get_tx_gain();
rx_gain = usrp_dev->get_rx_gain();
for (int i = 0; i < NUM_RX_CHANS; i++) {
usrp_dev->set_rx_gain((rx_gain_min + rx_gain_max) / 2, i);
rx_gain[i] = usrp_dev->get_rx_gain(i);
}
return;
}
void uhd_device::set_ref_clk(bool ext_clk)
{
uhd::clock_config_t clk_cfg;
clk_cfg.pps_source = uhd::clock_config_t::PPS_SMA;
if (ext_clk)
clk_cfg.ref_source = uhd::clock_config_t::REF_SMA;
else
clk_cfg.ref_source = uhd::clock_config_t::REF_INT;
usrp_dev->set_clock_config(clk_cfg);
usrp_dev->set_clock_source("external");
return;
}
double uhd_device::set_rates(double rate)
int uhd_device::set_master_clk(double clk_rate)
{
double actual_rt, actual_clk_rt;
double actual_clk_rt;
#ifndef RESAMPLE
// Make sure we can set the master clock rate on this device
actual_clk_rt = usrp_dev->get_master_clock_rate();
if (actual_clk_rt > U1_DEFAULT_CLK_RT) {
LOG(ALERT) << "Cannot set clock rate on this device";
LOG(ALERT) << "Please compile with host resampling support";
return -1.0;
try {
usrp_dev->set_master_clock_rate(clk_rate);
actual_clk_rt = usrp_dev->get_master_clock_rate();
} catch (const std::exception &ex) {
LOG(ALERT) << "UHD clock rate setting failed: " << clk_rate;
LOG(ALERT) << ex.what();
return -1;
}
// Set master clock rate
usrp_dev->set_master_clock_rate(master_clk_rt);
actual_clk_rt = usrp_dev->get_master_clock_rate();
if (actual_clk_rt != master_clk_rt) {
if (actual_clk_rt != clk_rate) {
LOG(ALERT) << "Failed to set master clock rate";
LOG(ALERT) << "Requested clock rate " << clk_rate;
LOG(ALERT) << "Actual clock rate " << actual_clk_rt;
return -1.0;
return -1;
}
return 0;
}
int uhd_device::set_rates(double rate)
{
double offset_limit = 10.0;
double tx_offset, rx_offset;
// B100 is the only device where we set FPGA clocking
if (dev_type == B100) {
if (set_master_clk(B100_CLK_RT) < 0)
return -1;
}
#endif
// Set sample rates
usrp_dev->set_tx_rate(rate);
usrp_dev->set_rx_rate(rate);
actual_rt = usrp_dev->get_tx_rate();
try {
usrp_dev->set_tx_rate(rate);
usrp_dev->set_rx_rate(rate);
} catch (const std::exception &ex) {
LOG(ALERT) << "UHD rate setting failed: " << rate;
LOG(ALERT) << ex.what();
return -1;
}
actual_smpl_rt = usrp_dev->get_tx_rate();
if (actual_rt != rate) {
tx_offset = actual_smpl_rt - rate;
rx_offset = usrp_dev->get_rx_rate() - rate;
if ((tx_offset > offset_limit) || (rx_offset > offset_limit)) {
LOG(ALERT) << "Actual sample rate differs from desired rate";
return -1.0;
}
if (usrp_dev->get_rx_rate() != actual_rt) {
LOG(ALERT) << "Transmit and receive sample rates do not match";
return -1.0;
LOG(ALERT) << "Tx/Rx (" << actual_smpl_rt << "/"
<< usrp_dev->get_rx_rate() << ")";
return -1;
}
return actual_rt;
return 0;
}
double uhd_device::setTxGain(double db)
double uhd_device::setTxGain(double db, int chan)
{
usrp_dev->set_tx_gain(db);
tx_gain = usrp_dev->get_tx_gain();
if (chan >= NUM_TX_CHANS) {
LOG(ALERT) << "Attempting to set gain on non-existent channel";
return 0.0f;
}
LOG(INFO) << "Set TX gain to " << tx_gain << "dB";
usrp_dev->set_tx_gain(db, chan);
tx_gain[chan] = usrp_dev->get_tx_gain(chan);
LOG(INFO) << "Set TX gain to " << tx_gain[chan] << "dB";
return tx_gain;
return tx_gain[chan];
}
double uhd_device::setRxGain(double db)
double uhd_device::setRxGain(double db, int chan)
{
usrp_dev->set_rx_gain(db);
rx_gain = usrp_dev->get_rx_gain();
if (chan >= NUM_RX_CHANS) {
LOG(ALERT) << "Attempting to read gain non-existent channel";
return 0.0f;
}
LOG(INFO) << "Set RX gain to " << rx_gain << "dB";
usrp_dev->set_rx_gain(db, chan);
rx_gain[chan] = usrp_dev->get_rx_gain(chan);
LOG(INFO) << "Set RX gain to " << rx_gain[chan] << "dB";
return rx_gain;
return rx_gain[chan];
}
void uhd_device::setTxAntenna(std::string &name)
{
usrp_dev->set_tx_antenna(name);
}
void uhd_device::setRxAntenna(std::string &name)
{
usrp_dev->set_rx_antenna(name);
}
std::string uhd_device::getTxAntenna()
{
return usrp_dev->get_tx_antenna();
}
std::string uhd_device::getRxAntenna()
{
return usrp_dev->get_rx_antenna();
}
/*
Parse the UHD device tree and mboard name to find out what device we're
dealing with. We need the bus type so that the transceiver knows how to
dealing with. We need the window type so that the transceiver knows how to
deal with the transport latency. Reject the USRP1 because UHD doesn't
support timestamped samples with it.
*/
@@ -395,34 +533,46 @@ bool uhd_device::parse_dev_type()
{
std::string mboard_str, dev_str;
uhd::property_tree::sptr prop_tree;
size_t usrp1_str, usrp2_str, b100_str1, b100_str2;
size_t usrp1_str, usrp2_str, b100_str, umtrx_str;
prop_tree = usrp_dev->get_device()->get_tree();
dev_str = prop_tree->access<std::string>("/name").get();
mboard_str = usrp_dev->get_mboard_name();
usrp1_str = dev_str.find("USRP1");
b100_str1 = dev_str.find("B-Series");
b100_str2 = mboard_str.find("B100");
usrp2_str = dev_str.find("USRP2");
umtrx_str = dev_str.find("UmTRX");
b100_str = mboard_str.find("B100");
if (usrp1_str != std::string::npos) {
LOG(ALERT) << "USRP1 is not supported using the UHD driver";
LOG(ALERT) << "Please compile with GNU Radio libusrp support";
dev_type = USRP1;
return false;
}
if ((b100_str1 != std::string::npos) || (b100_str2 != std::string::npos)) {
bus = USB;
LOG(INFO) << "Using USB bus for " << dev_str;
if (b100_str != std::string::npos) {
tx_window = TX_WINDOW_USRP1;
LOG(INFO) << "Using USRP1 type transmit window for "
<< dev_str << " " << mboard_str;
dev_type = B100;
return true;
} else if (usrp2_str != std::string::npos) {
dev_type = USRP2;
} else if (umtrx_str != std::string::npos) {
dev_type = UMTRX;
} else {
bus = NET;
LOG(INFO) << "Using network bus for " << dev_str;
LOG(ALERT) << "Unknown UHD device type";
return false;
}
tx_window = TX_WINDOW_FIXED;
LOG(INFO) << "Using fixed transmit window for "
<< dev_str << " " << mboard_str;
return true;
}
bool uhd_device::open(const std::string &args)
int uhd_device::open(const std::string &args)
{
// Register msg handler
uhd::msg::register_handler(&uhd_msg_handler);
@@ -432,7 +582,7 @@ bool uhd_device::open(const std::string &args)
uhd::device_addrs_t dev_addrs = uhd::device::find(addr);
if (dev_addrs.size() == 0) {
LOG(ALERT) << "No UHD devices found with address '" << args << "'";
return false;
return -1;
}
// Use the first found device
@@ -441,32 +591,51 @@ bool uhd_device::open(const std::string &args)
usrp_dev = uhd::usrp::multi_usrp::make(dev_addrs[0]);
} catch(...) {
LOG(ALERT) << "UHD make failed, device " << dev_addrs[0].to_string();
return false;
return -1;
}
// Check for a valid device type and set bus type
if (!parse_dev_type())
return false;
return -1;
#ifdef EXTREF
set_ref_clk(true);
#endif
// Number of samples per over-the-wire packet
tx_spp = usrp_dev->get_device()->get_max_send_samps_per_packet();
rx_spp = usrp_dev->get_device()->get_max_recv_samps_per_packet();
if (NUM_TX_CHANS == 2) {
uhd::usrp::subdev_spec_t subdev_spec("A:0 B:0");
usrp_dev->set_tx_subdev_spec(subdev_spec);
usrp_dev->set_rx_subdev_spec(subdev_spec);
}
// Set rates
actual_smpl_rt = set_rates(desired_smpl_rt);
if (actual_smpl_rt < 0)
return false;
desired_smpl_rt = select_rate(dev_type, sps);
if (set_rates(desired_smpl_rt) < 0)
return -1;
// Create TX and RX streamers
uhd::stream_args_t stream_args("sc16");
for (int i = 0; i < NUM_TX_CHANS; i++)
stream_args.channels.push_back(i);
tx_stream = usrp_dev->get_tx_stream(stream_args);
rx_stream = usrp_dev->get_rx_stream(stream_args);
// Number of samples per over-the-wire packet
tx_spp = tx_stream->get_max_num_samps();
rx_spp = rx_stream->get_max_num_samps();
// Create receive buffer
size_t buf_len = smpl_buf_sz / sizeof(uint32_t);
rx_smpl_buf = new smpl_buf(buf_len, actual_smpl_rt);
size_t buf_len = SAMPLE_BUF_SZ / sizeof(uint32_t);
for (int i = 0; i < NUM_RX_CHANS; i++)
rx_smpl_buf[i] = new smpl_buf(buf_len, actual_smpl_rt);
// Set receive chain sample offset
ts_offset = (TIMESTAMP)(rx_smpl_offset * actual_smpl_rt);
double offset = get_dev_offset(dev_type, sps);
if (offset == 0.0) {
LOG(ERR) << "Unsupported configuration, no correction applied";
ts_offset = 0;
} else {
ts_offset = (TIMESTAMP) (offset * actual_smpl_rt);
}
// Initialize and shadow gain values
init_gains();
@@ -474,28 +643,30 @@ bool uhd_device::open(const std::string &args)
// Print configuration
LOG(INFO) << "\n" << usrp_dev->get_pp_string();
return true;
if (dev_type == USRP2)
return RESAMP;
return NORMAL;
}
bool uhd_device::flush_recv(size_t num_pkts)
{
uhd::rx_metadata_t md;
size_t num_smpls;
uint32_t buff[rx_spp];
float timeout;
// Use .01 sec instead of the default .1 sec
timeout = .01;
for (size_t i = 0; i < num_pkts; i++) {
num_smpls = usrp_dev->get_device()->recv(
buff,
rx_spp,
md,
uhd::io_type_t::COMPLEX_INT16,
uhd::device::RECV_MODE_ONE_PACKET,
timeout);
std::vector<std::vector<std::complex<short> > > bufs(
NUM_RX_CHANS, std::vector<std::complex<short> >(rx_spp));
std::vector<std::complex<short> *> buf_ptrs;
for (int i = 0; i < bufs.size(); i++)
buf_ptrs.push_back(&bufs[i].front());
for (size_t i = 0; i < num_pkts; i++) {
num_smpls = rx_stream->recv(buf_ptrs, rx_spp, md,
timeout, true);
if (!num_smpls) {
switch (md.error_code) {
case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT:
@@ -511,17 +682,15 @@ bool uhd_device::flush_recv(size_t num_pkts)
void uhd_device::restart(uhd::time_spec_t ts)
{
uhd::stream_cmd_t cmd = uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS;
usrp_dev->issue_stream_cmd(cmd);
flush_recv(50);
usrp_dev->set_time_now(ts);
aligned = false;
cmd = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS;
cmd.stream_now = true;
uhd::stream_cmd_t cmd = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS;
cmd.time_spec = uhd::time_spec_t(0.1);
cmd.stream_now = false;
usrp_dev->issue_stream_cmd(cmd);
uhd::rx_metadata_t md;
}
bool uhd_device::start()
@@ -533,10 +702,11 @@ bool uhd_device::start()
return false;
}
started = true;
setPriority();
// Start asynchronous event (underrun check) loop
async_event_thrd.start((void * (*)(void*))async_event_loop, (void*)this);
async_event_thrd.startThread((void*)this);
// Start streaming
restart(uhd::time_spec_t(0.0));
@@ -545,18 +715,22 @@ bool uhd_device::start()
double time_now = usrp_dev->get_time_now().get_real_secs();
LOG(INFO) << "The current time is " << time_now << " seconds";
started = true;
return true;
}
bool uhd_device::stop()
{
if (!started)
return false;
async_event_thrd.stopThread();
started = false;
uhd::stream_cmd_t stream_cmd =
uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS;
usrp_dev->issue_stream_cmd(stream_cmd);
started = false;
return true;
}
@@ -607,40 +781,48 @@ int uhd_device::check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls)
return 0;
}
int uhd_device::readSamples(short *buf, int len, bool *overrun,
TIMESTAMP timestamp, bool *underrun, unsigned *RSSI)
int uhd_device::readSamples(short **buf, int chans, int len,
TIMESTAMP timestamp, bool *overrun,
bool *underrun, unsigned *RSSI)
{
ssize_t rc;
uhd::time_spec_t ts;
uhd::rx_metadata_t metadata;
uint32_t pkt_buf[rx_spp];
if (skip_rx)
if (skip_rx) {
LOG(INFO) << "Skipping Rx";
return 0;
}
if (chans != NUM_RX_CHANS) {
LOG(ERR) << "Number of requested channels does not match build";
return -1;
}
// Shift read time with respect to transmit clock
timestamp += ts_offset;
ts = convert_time(timestamp, actual_smpl_rt);
LOG(DEBUG) << "Requested timestamp = " << ts.get_real_secs();
LOG(DEBUG) << "Requested UHD timestamp = " << ts.get_real_secs();
// Check that timestamp is valid
rc = rx_smpl_buf->avail_smpls(timestamp);
rc = rx_smpl_buf[0]->avail_smpls(timestamp);
if (rc < 0) {
LOG(ERR) << rx_smpl_buf->str_code(rc);
LOG(ERR) << rx_smpl_buf->str_status();
LOG(ERR) << rx_smpl_buf[0]->str_code(rc);
LOG(ERR) << rx_smpl_buf[0]->str_status();
return 0;
}
// Receive samples from the usrp until we have enough
while (rx_smpl_buf->avail_smpls(timestamp) < len) {
size_t num_smpls = usrp_dev->get_device()->recv(
(void*)pkt_buf,
rx_spp,
metadata,
uhd::io_type_t::COMPLEX_INT16,
uhd::device::RECV_MODE_ONE_PACKET);
std::vector<std::vector<std::complex<short> > > bufs(
NUM_RX_CHANS, std::vector<std::complex<short> >(rx_spp));
std::vector<std::complex<short> *> buf_ptrs;
for (int i = 0; i < bufs.size(); i++)
buf_ptrs.push_back(&bufs[i].front());
// Receive samples from the usrp until we have enough
while (rx_smpl_buf[0]->avail_smpls(timestamp) < len) {
size_t num_smpls = rx_stream->recv(buf_ptrs, rx_spp,
metadata, 0.1, true);
rx_pkt_cnt++;
// Check for errors
@@ -653,39 +835,45 @@ int uhd_device::readSamples(short *buf, int len, bool *overrun,
case ERROR_TIMING:
restart(prev_ts);
case ERROR_UNHANDLED:
LOG(ALERT) << "UHD: Unhandled error";
continue;
}
ts = metadata.time_spec;
LOG(DEBUG) << "Received timestamp = " << ts.get_real_secs();
LOG(DEBUG) << "Received " << num_smpls << " samples "
<< "with timestamp = " << ts.get_real_secs();
rc = rx_smpl_buf->write(pkt_buf,
num_smpls,
metadata.time_spec);
// Continue on local overrun, exit on other errors
if ((rc < 0)) {
LOG(ERR) << rx_smpl_buf->str_code(rc);
LOG(ERR) << rx_smpl_buf->str_status();
if (rc != smpl_buf::ERROR_OVERFLOW)
return 0;
for (int i = 0; i < NUM_RX_CHANS; i++) {
rc = rx_smpl_buf[i]->write((short *) &bufs[i].front(),
num_smpls,
metadata.time_spec);
// Continue on local overrun, exit on other errors
if ((rc < 0)) {
LOG(ERR) << rx_smpl_buf[i]->str_code(rc);
LOG(ERR) << rx_smpl_buf[i]->str_status();
if (rc != smpl_buf::ERROR_OVERFLOW)
return 0;
}
}
}
// We have enough samples
rc = rx_smpl_buf->read(buf, len, timestamp);
if ((rc < 0) || (rc != len)) {
LOG(ERR) << rx_smpl_buf->str_code(rc);
LOG(ERR) << rx_smpl_buf->str_status();
return 0;
for (int i = 0; i < NUM_RX_CHANS; i++) {
rc = rx_smpl_buf[i]->read(buf[i], len, timestamp);
if ((rc < 0) || (rc != len)) {
LOG(ERR) << rx_smpl_buf[i]->str_code(rc);
LOG(ERR) << rx_smpl_buf[i]->str_status();
return 0;
}
}
return len;
}
int uhd_device::writeSamples(short *buf, int len, bool *underrun,
unsigned long long timestamp,bool isControl)
int uhd_device::writeSamples(short **buf, int chans, int len,
TIMESTAMP timestamp, bool *underrun,
bool isControl)
{
uhd::tx_metadata_t metadata;
metadata.has_time_spec = true;
@@ -718,12 +906,11 @@ int uhd_device::writeSamples(short *buf, int len, bool *underrun,
}
}
size_t num_smpls = usrp_dev->get_device()->send(buf,
len,
metadata,
uhd::io_type_t::COMPLEX_INT16,
uhd::device::SEND_MODE_FULL_BUFF);
std::vector<short *> bufs(NUM_TX_CHANS);
for (int i = 0; i < NUM_TX_CHANS; i++)
bufs[i] = buf[i];
size_t num_smpls = tx_stream->send(bufs, len, metadata);
if (num_smpls != (unsigned) len) {
LOG(ALERT) << "UHD: Device send timed out";
LOG(ALERT) << "UHD: Version " << uhd::get_version_string();
@@ -736,24 +923,33 @@ int uhd_device::writeSamples(short *buf, int len, bool *underrun,
bool uhd_device::updateAlignment(TIMESTAMP timestamp)
{
aligned = false;
return true;
}
bool uhd_device::setTxFreq(double wFreq)
bool uhd_device::setTxFreq(double wFreq, int chan)
{
uhd::tune_result_t tr = usrp_dev->set_tx_freq(wFreq);
if (chan >= NUM_TX_CHANS) {
LOG(ALERT) << "Attempting to tune non-existent channel";
return false;
}
uhd::tune_result_t tr = usrp_dev->set_tx_freq(wFreq, chan);
LOG(INFO) << "\n" << tr.to_pp_string();
tx_freq = usrp_dev->get_tx_freq();
tx_freq[chan] = usrp_dev->get_tx_freq(chan);
return true;
}
bool uhd_device::setRxFreq(double wFreq)
bool uhd_device::setRxFreq(double wFreq, int chan)
{
uhd::tune_result_t tr = usrp_dev->set_rx_freq(wFreq);
if (chan >= NUM_RX_CHANS) {
LOG(ALERT) << "Attempting to tune non-existent channel";
return false;
}
uhd::tune_result_t tr = usrp_dev->set_rx_freq(wFreq, chan);
LOG(INFO) << "\n" << tr.to_pp_string();
rx_freq = usrp_dev->get_rx_freq();
rx_freq[chan] = usrp_dev->get_rx_freq(chan);
return true;
}
@@ -888,7 +1084,7 @@ ssize_t smpl_buf::read(void *buf, size_t len, TIMESTAMP timestamp)
num_smpls = len;
// Starting index
size_t read_start = data_start + (timestamp - time_start);
size_t read_start = (data_start + (timestamp - time_start)) % buf_len;
// Read it
if (read_start + num_smpls < buf_len) {
@@ -986,7 +1182,7 @@ std::string smpl_buf::str_code(ssize_t code)
}
}
RadioDevice *RadioDevice::make(double smpl_rt, bool skip_rx)
RadioDevice *RadioDevice::make(int sps, bool skip_rx)
{
return new uhd_device(smpl_rt, skip_rx);
return new uhd_device(sps, skip_rx);
}

View File

@@ -59,11 +59,11 @@ const dboardConfigType dboardConfig = TXA_RXB;
const double USRPDevice::masterClockRate = 52.0e6;
USRPDevice::USRPDevice (double _desiredSampleRate, bool skipRx)
USRPDevice::USRPDevice(int sps, bool skipRx)
: skipRx(skipRx)
{
LOG(INFO) << "creating USRP device...";
decimRate = (unsigned int) round(masterClockRate/_desiredSampleRate);
decimRate = (unsigned int) round(masterClockRate/((GSMRATE) * (double) sps));
actualSampleRate = masterClockRate/decimRate;
rxGain = 0;
@@ -75,7 +75,7 @@ USRPDevice::USRPDevice (double _desiredSampleRate, bool skipRx)
#endif
}
bool USRPDevice::open(const std::string &)
int USRPDevice::open(const std::string &)
{
writeLock.unlock();
@@ -97,7 +97,7 @@ bool USRPDevice::open(const std::string &)
catch(...) {
LOG(ALERT) << "make failed on Rx";
m_uRx.reset();
return false;
return -1;
}
if (m_uRx->fpga_master_clock_freq() != masterClockRate)
@@ -105,7 +105,7 @@ bool USRPDevice::open(const std::string &)
LOG(ALERT) << "WRONG FPGA clock freq = " << m_uRx->fpga_master_clock_freq()
<< ", desired clock freq = " << masterClockRate;
m_uRx.reset();
return false;
return -1;
}
}
@@ -120,7 +120,7 @@ bool USRPDevice::open(const std::string &)
catch(...) {
LOG(ALERT) << "make failed on Tx";
m_uTx.reset();
return false;
return -1;
}
if (m_uTx->fpga_master_clock_freq() != masterClockRate)
@@ -128,7 +128,7 @@ bool USRPDevice::open(const std::string &)
LOG(ALERT) << "WRONG FPGA clock freq = " << m_uTx->fpga_master_clock_freq()
<< ", desired clock freq = " << masterClockRate;
m_uTx.reset();
return false;
return -1;
}
if (!skipRx) m_uRx->stop();
@@ -165,7 +165,7 @@ bool USRPDevice::open(const std::string &)
samplesWritten = 0;
started = false;
return true;
return NORMAL;
}
@@ -556,7 +556,7 @@ bool USRPDevice::setTxFreq(double wFreq) { return true;};
bool USRPDevice::setRxFreq(double wFreq) { return true;};
#endif
RadioDevice *RadioDevice::make(double desiredSampleRate, bool skipRx)
RadioDevice *RadioDevice::make(int sps, bool skipRx)
{
return new USRPDevice(desiredSampleRate, skipRx);
return new USRPDevice(sps, skipRx);
}

View File

@@ -112,10 +112,10 @@ private:
public:
/** Object constructor */
USRPDevice (double _desiredSampleRate, bool skipRx);
USRPDevice(int sps, bool skipRx);
/** Instantiate the USRP */
bool open(const std::string &);
int open(const std::string &);
/** Start the USRP */
bool start();
@@ -126,8 +126,7 @@ private:
/** Set priority not supported */
void setPriority() { return; }
/** Only USB bus supported */
busType getBus() { return USB; }
enum TxWindowType getWindowType() { return TX_WINDOW_USRP1; }
/**
Read samples from the USRP.

View File

@@ -41,7 +41,7 @@ int main(int argc, char *argv[]) {
else gLogInit("DEBUG");
//if (argc>2) gSetLogFile(argv[2]);
RadioDevice *usrp = RadioDevice::make(52.0e6/192.0);
RadioDevice *usrp = RadioDevice::make(52.0e6/192.0, 1);
usrp->open("");

120
Transceiver52M/multiTRX.cpp Normal file
View File

@@ -0,0 +1,120 @@
/*
* Copyright 2012 Thomas Tsou <ttsou@vt.edu>
*
* 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 <time.h>
#include <signal.h>
#include <GSMCommon.h>
#include <Logger.h>
#include <Configuration.h>
#include "Transceiver.h"
#include "radioDevice.h"
ConfigurationTable gConfig("/etc/OpenBTS/OpenBTS.db");
volatile bool gbShutdown = false;
int Transceiver::mTSC = -1;
static void sigHandler(int signum)
{
LOG(NOTICE) << "Received shutdown signal";
gbShutdown = true;
}
static int setupSignals()
{
struct sigaction action;
action.sa_handler = sigHandler;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
if (sigaction(SIGINT, &action, NULL) < 0)
return -1;
if (sigaction(SIGTERM, &action, NULL) < 0)
return -1;
return 0;
}
int main(int argc, char *argv[])
{
int numARFCN = 1;
if (argc > 1) {
numARFCN = atoi(argv[1]);
if (numARFCN > CHAN_MAX) {
LOG(ALERT) << numARFCN << " channels not supported "
<< " with with current build";
exit(-1);
}
}
gLogInit("transceiver", gConfig.getStr("Log.Level").c_str(), LOG_LOCAL7);
srandom(time(NULL));
if (setupSignals() < 0) {
LOG(ERR) << "Failed to setup signal handlers, exiting...";
exit(-1);
}
RadioDevice *device = RadioDevice::make(SAMPSPERSYM);
int radioType = device->open("");
if (radioType < 0) {
LOG(ALERT) << "Failed to open device, exiting...";
return EXIT_FAILURE;
}
RadioInterface *radio;
switch (radioType) {
case RadioDevice::NORMAL:
radio = new RadioInterface(device, numARFCN);
break;
case RadioDevice::RESAMP:
default:
LOG(ALERT) << "Unsupported configuration";
return EXIT_FAILURE;
}
DriveLoop *drive = new DriveLoop(5700, "127.0.0.1", radio, numARFCN, 0);
Transceiver *trx[CHAN_MAX];
bool primary = true;
for (int i = 0; i < numARFCN; i++) {
trx[i] = new Transceiver(5700 + 2 * i, "127.0.0.1",
drive, radio, SAMPSPERSYM,
i, primary);
trx[i]->start();
primary = false;
}
while (!gbShutdown)
sleep(1);
LOG(NOTICE) << "Shutting down transceivers...";
for (int i = 0; i < numARFCN; i++)
trx[i]->shutdown();
for (int i = 0; i < numARFCN; i++)
delete trx[i];
delete drive;
delete radio;
delete device;
}

View File

@@ -21,6 +21,8 @@
#include "config.h"
#endif
#define GSMRATE 1625e3/6
/** a 64-bit virtual timestamp for radio data */
typedef unsigned long long TIMESTAMP;
@@ -29,12 +31,17 @@ class RadioDevice {
public:
/* Available transport bus types */
enum busType { USB, NET };
enum TxWindowType { TX_WINDOW_USRP1, TX_WINDOW_FIXED };
static RadioDevice *make(double desiredSampleRate, bool skipRx = false);
/* Radio interface types */
enum RadioInterfaceType { NORMAL, RESAMP };
static RadioDevice *make(int sps, bool skipRx = false);
virtual ~RadioDevice() {};
/** Initialize the USRP */
virtual bool open(const std::string &args)=0;
virtual int open(const std::string &args)=0;
/** Start the USRP */
virtual bool start()=0;
@@ -42,8 +49,8 @@ class RadioDevice {
/** Stop the USRP */
virtual bool stop()=0;
/** Get the bus type */
virtual enum busType getBus()=0;
/** Get the Tx window type */
virtual enum TxWindowType getWindowType()=0;
/** Enable thread priority */
virtual void setPriority()=0;
@@ -58,10 +65,9 @@ class RadioDevice {
@param RSSI The received signal strength of the read result
@return The number of samples actually read
*/
virtual int readSamples(short *buf, int len, bool *overrun,
TIMESTAMP timestamp = 0xffffffff,
bool *underrun = 0,
unsigned *RSSI = 0)=0;
virtual int readSamples(short **buf, int chans, int len, TIMESTAMP timestamp,
bool *overrun = NULL, bool *underrun = NULL,
unsigned *RSSI = NULL)=0;
/**
Write samples to the radio.
@param buf Contains the data to be written.
@@ -71,18 +77,17 @@ class RadioDevice {
@param isControl Set if data is a control packet, e.g. a ping command
@return The number of samples actually written
*/
virtual int writeSamples(short *buf, int len, bool *underrun,
TIMESTAMP timestamp,
bool isControl=false)=0;
virtual int writeSamples(short **buf, int chans, int len, TIMESTAMP timestamp,
bool *underrun = NULL, bool isControl = false)=0;
/** Update the alignment between the read and write timestamps */
virtual bool updateAlignment(TIMESTAMP timestamp)=0;
/** Set the transmitter frequency */
virtual bool setTxFreq(double wFreq)=0;
virtual bool setTxFreq(double wFreq, int chan = 0)=0;
/** Set the receiver frequency */
virtual bool setRxFreq(double wFreq)=0;
virtual bool setRxFreq(double wFreq, int chan = 0)=0;
/** Returns the starting write Timestamp*/
virtual TIMESTAMP initialWriteTimestamp(void)=0;
@@ -97,10 +102,10 @@ class RadioDevice {
virtual double fullScaleOutputValue()=0;
/** sets the receive chan gain, returns the gain setting **/
virtual double setRxGain(double dB)=0;
virtual double setRxGain(double dB, int chan = 0)=0;
/** gets the current receive gain **/
virtual double getRxGain(void)=0;
virtual double getRxGain(int chan = 0)=0;
/** return maximum Rx Gain **/
virtual double maxRxGain(void) = 0;
@@ -109,7 +114,7 @@ class RadioDevice {
virtual double minRxGain(void) = 0;
/** sets the transmit chan gain, returns the gain setting **/
virtual double setTxGain(double dB)=0;
virtual double setTxGain(double dB, int chan = 0)=0;
/** return maximum Tx Gain **/
virtual double maxTxGain(void) = 0;
@@ -117,13 +122,18 @@ class RadioDevice {
/** return minimum Tx Gain **/
virtual double minTxGain(void) = 0;
/** set and return antennas selection **/
virtual void setTxAntenna(std::string &name) = 0;
virtual void setRxAntenna(std::string &name) = 0;
virtual std::string getRxAntenna() = 0;
virtual std::string getTxAntenna() = 0;
/** Return internal status values */
virtual double getTxFreq()=0;
virtual double getRxFreq()=0;
virtual double getTxFreq(int chan = 0)=0;
virtual double getRxFreq(int chan = 0)=0;
virtual double getSampleRate()=0;
virtual double numberRead()=0;
virtual double numberWritten()=0;
};
#endif

View File

@@ -1,91 +0,0 @@
/*
* Radio device I/O interface
* Written by Thomas Tsou <ttsou@vt.edu>
*
* Copyright 2011 Free Software Foundation, Inc.
*
* 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 <radioInterface.h>
#include <Logger.h>
/* Device side buffers */
static short rx_buf[OUTCHUNK * 2 * 2];
static short tx_buf[INCHUNK * 2 * 2];
/* Complex float to short conversion */
static int float_to_short(short *shrt_out, float *flt_in, int num)
{
int i;
for (i = 0; i < num; i++) {
shrt_out[2 * i + 0] = flt_in[2 * i + 0];
shrt_out[2 * i + 1] = flt_in[2 * i + 1];
}
return i;
}
/* Comlpex short to float conversion */
static int short_to_float(float *flt_out, short *shrt_in, int num)
{
int i;
for (i = 0; i < num; i++) {
flt_out[2 * i + 0] = shrt_in[2 * i + 0];
flt_out[2 * i + 1] = shrt_in[2 * i + 1];
}
return i;
}
/* Receive a timestamped chunk from the device */
void RadioInterface::pullBuffer()
{
bool local_underrun;
/* Read samples. Fail if we don't get what we want. */
int num_rd = mRadio->readSamples(rx_buf, OUTCHUNK, &overrun,
readTimestamp, &local_underrun);
LOG(DEBUG) << "Rx read " << num_rd << " samples from device";
assert(num_rd == OUTCHUNK);
underrun |= local_underrun;
readTimestamp += (TIMESTAMP) num_rd;
short_to_float(rcvBuffer + 2 * rcvCursor, rx_buf, num_rd);
rcvCursor += num_rd;
}
/* Send timestamped chunk to the device with arbitrary size */
void RadioInterface::pushBuffer()
{
if (sendCursor < INCHUNK)
return;
float_to_short(tx_buf, sendBuffer, sendCursor);
/* Write samples. Fail if we don't get what we want. */
int num_smpls = mRadio->writeSamples(tx_buf,
sendCursor,
&underrun,
writeTimestamp);
assert(num_smpls == sendCursor);
writeTimestamp += (TIMESTAMP) num_smpls;
sendCursor = 0;
}

View File

@@ -1,324 +0,0 @@
/*
* Radio device interface with sample rate conversion
* Written by Thomas Tsou <ttsou@vt.edu>
*
* Copyright 2011 Free Software Foundation, Inc.
*
* 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 <radioInterface.h>
#include <Logger.h>
/* New chunk sizes for resampled rate */
#ifdef INCHUNK
#undef INCHUNK
#endif
#ifdef OUTCHUNK
#undef OUTCHUNK
#endif
/* Resampling parameters */
#define INRATE 65 * SAMPSPERSYM
#define INHISTORY INRATE * 2
#define INCHUNK INRATE * 9
#define OUTRATE 96 * SAMPSPERSYM
#define OUTHISTORY OUTRATE * 2
#define OUTCHUNK OUTRATE * 9
/* Resampler low pass filters */
signalVector *tx_lpf = 0;
signalVector *rx_lpf = 0;
/* Resampler history */
signalVector *tx_hist = 0;
signalVector *rx_hist = 0;
/* Resampler input buffer */
signalVector *tx_vec = 0;
signalVector *rx_vec = 0;
/*
* High rate (device facing) buffers
*
* Transmit side samples are pushed after each burst so accomodate
* a resampled burst plus up to a chunk left over from the previous
* resampling operation.
*
* Receive side samples always pulled with a fixed size.
*/
short tx_buf[INCHUNK * 2 * 4];
short rx_buf[OUTCHUNK * 2 * 2];
/*
* Utilities and Conversions
*
* Manipulate signal vectors dynamically for two reasons. For one,
* it's simpler. And two, it doesn't make any reasonable difference
* relative to the high overhead generated by the resampling.
*/
/* Concatenate signal vectors. Deallocate input vectors. */
signalVector *concat(signalVector *a, signalVector *b)
{
signalVector *vec = new signalVector(*a, *b);
delete a;
delete b;
return vec;
}
/* Segment a signal vector. Deallocate the input vector. */
signalVector *segment(signalVector *a, int indx, int sz)
{
signalVector *vec = new signalVector(sz);
a->segmentCopyTo(*vec, indx, sz);
delete a;
return vec;
}
/* Create a new signal vector from a short array. */
signalVector *short_to_sigvec(short *smpls, size_t sz)
{
int i;
signalVector *vec = new signalVector(sz);
signalVector::iterator itr = vec->begin();
for (i = 0; i < sz; i++) {
*itr++ = Complex<float>(smpls[2 * i + 0], smpls[2 * i + 1]);
}
return vec;
}
/* Convert and deallocate a signal vector into a short array. */
int sigvec_to_short(signalVector *vec, short *smpls)
{
int i;
signalVector::iterator itr = vec->begin();
for (i = 0; i < vec->size(); i++) {
smpls[2 * i + 0] = itr->real();
smpls[2 * i + 1] = itr->imag();
itr++;
}
delete vec;
return i;
}
/* Create a new signal vector from a float array. */
signalVector *float_to_sigvec(float *smpls, int sz)
{
int i;
signalVector *vec = new signalVector(sz);
signalVector::iterator itr = vec->begin();
for (i = 0; i < sz; i++) {
*itr++ = Complex<float>(smpls[2 * i + 0], smpls[2 * i + 1]);
}
return vec;
}
/* Convert and deallocate a signal vector into a float array. */
int sigvec_to_float(signalVector *vec, float *smpls)
{
int i;
signalVector::iterator itr = vec->begin();
for (i = 0; i < vec->size(); i++) {
smpls[2 * i + 0] = itr->real();
smpls[2 * i + 1] = itr->imag();
itr++;
}
delete vec;
return i;
}
/* Initialize resampling signal vectors */
void init_resampler(signalVector **lpf,
signalVector **buf,
signalVector **hist,
int tx)
{
int P, Q, taps, hist_len;
float cutoff_freq;
if (tx) {
LOG(INFO) << "Initializing Tx resampler";
P = OUTRATE;
Q = INRATE;
taps = 651;
hist_len = INHISTORY;
} else {
LOG(INFO) << "Initializing Rx resampler";
P = INRATE;
Q = OUTRATE;
taps = 961;
hist_len = OUTHISTORY;
}
if (!*lpf) {
cutoff_freq = (P < Q) ? (1.0/(float) Q) : (1.0/(float) P);
*lpf = createLPF(cutoff_freq, taps, P);
}
if (!*buf) {
*buf = new signalVector();
}
if (!*hist);
*hist = new signalVector(hist_len);
}
/* Resample a signal vector
*
* The input vector is deallocated and the pointer returned with a vector
* of any unconverted samples.
*/
signalVector *resmpl_sigvec(signalVector *hist, signalVector **vec,
signalVector *lpf, double in_rate,
double out_rate, int chunk_sz)
{
signalVector *resamp_vec;
int num_chunks = (*vec)->size() / chunk_sz;
/* Truncate to a chunk multiple */
signalVector trunc_vec(num_chunks * chunk_sz);
(*vec)->segmentCopyTo(trunc_vec, 0, num_chunks * chunk_sz);
/* Update sample buffer with remainder */
*vec = segment(*vec, trunc_vec.size(), (*vec)->size() - trunc_vec.size());
/* Add history and resample */
signalVector input_vec(*hist, trunc_vec);
resamp_vec = polyphaseResampleVector(input_vec, in_rate,
out_rate, lpf);
/* Update history */
trunc_vec.segmentCopyTo(*hist, trunc_vec.size() - hist->size(),
hist->size());
return resamp_vec;
}
/* Wrapper for receive-side integer-to-float array resampling */
int rx_resmpl_int_flt(float *smpls_out, short *smpls_in, int num_smpls)
{
int num_resmpld, num_chunks;
signalVector *convert_vec, *resamp_vec, *trunc_vec;
if (!rx_lpf || !rx_vec || !rx_hist)
init_resampler(&rx_lpf, &rx_vec, &rx_hist, false);
/* Convert and add samples to the receive buffer */
convert_vec = short_to_sigvec(smpls_in, num_smpls);
rx_vec = concat(rx_vec, convert_vec);
num_chunks = rx_vec->size() / OUTCHUNK;
if (num_chunks < 1)
return 0;
/* Resample */
resamp_vec = resmpl_sigvec(rx_hist, &rx_vec, rx_lpf,
INRATE, OUTRATE, OUTCHUNK);
/* Truncate */
trunc_vec = segment(resamp_vec, INHISTORY,
resamp_vec->size() - INHISTORY);
/* Convert */
num_resmpld = sigvec_to_float(trunc_vec, smpls_out);
return num_resmpld;
}
/* Wrapper for transmit-side float-to-int array resampling */
int tx_resmpl_flt_int(short *smpls_out, float *smpls_in, int num_smpls)
{
int num_resmpl, num_chunks;
signalVector *convert_vec, *resamp_vec;
if (!tx_lpf || !tx_vec || !tx_hist)
init_resampler(&tx_lpf, &tx_vec, &tx_hist, true);
/* Convert and add samples to the transmit buffer */
convert_vec = float_to_sigvec(smpls_in, num_smpls);
tx_vec = concat(tx_vec, convert_vec);
num_chunks = tx_vec->size() / INCHUNK;
if (num_chunks < 1)
return 0;
/* Resample and convert to an integer array */
resamp_vec = resmpl_sigvec(tx_hist, &tx_vec, tx_lpf,
OUTRATE, INRATE, INCHUNK);
num_resmpl = sigvec_to_short(resamp_vec, smpls_out);
return num_resmpl;
}
/* Receive a timestamped chunk from the device */
void RadioInterface::pullBuffer()
{
int num_cv, num_rd;
bool local_underrun;
/* Read samples. Fail if we don't get what we want. */
num_rd = mRadio->readSamples(rx_buf, OUTCHUNK, &overrun,
readTimestamp, &local_underrun);
LOG(DEBUG) << "Rx read " << num_rd << " samples from device";
assert(num_rd == OUTCHUNK);
underrun |= local_underrun;
readTimestamp += (TIMESTAMP) num_rd;
/* Convert and resample */
num_cv = rx_resmpl_int_flt(rcvBuffer + 2 * rcvCursor,
rx_buf, num_rd);
LOG(DEBUG) << "Rx read " << num_cv << " samples from resampler";
rcvCursor += num_cv;
}
/* Send a timestamped chunk to the device */
void RadioInterface::pushBuffer()
{
int num_cv, num_wr;
if (sendCursor < INCHUNK)
return;
LOG(DEBUG) << "Tx wrote " << sendCursor << " samples to resampler";
/* Resample and convert */
num_cv = tx_resmpl_flt_int(tx_buf, sendBuffer, sendCursor);
assert(num_cv > sendCursor);
/* Write samples. Fail if we don't get what we want. */
num_wr = mRadio->writeSamples(tx_buf + OUTHISTORY * 2,
num_cv - OUTHISTORY,
&underrun,
writeTimestamp);
LOG(DEBUG) << "Tx wrote " << num_wr << " samples to device";
assert(num_wr == num_wr);
writeTimestamp += (TIMESTAMP) num_wr;
sendCursor = 0;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008, 2009 Free Software Foundation, Inc.
* Copyright 2008, 2009, 2012 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.
@@ -27,23 +27,53 @@
bool started = false;
/* Device side buffers */
static short *rx_buf[CHAN_MAX];
static short *tx_buf[CHAN_MAX];
/* Complex float to short conversion */
static void floatToShort(short *out, float *in, int num)
{
for (int i = 0; i < num; i++) {
out[2 * i + 0] = (short) in[2 * i + 0];
out[2 * i + 1] = (short) in[2 * i + 1];
}
}
/* Complex short to float conversion */
static void shortToFloat(float *out, short *in, int num)
{
for (int i = 0; i < num; i++) {
out[2 * i + 0] = (float) in[2 * i + 0];
out[2 * i + 1] = (float) in[2 * i + 1];
}
}
RadioInterface::RadioInterface(RadioDevice *wRadio,
int wReceiveOffset,
int wRadioOversampling,
int wTransceiverOversampling,
int wChanM,
int wSPS,
int wReceiveOffset,
GSM::Time wStartTime)
: underrun(false), sendCursor(0), rcvCursor(0), mOn(false),
mRadio(wRadio), receiveOffset(wReceiveOffset),
samplesPerSymbol(wRadioOversampling), powerScaling(1.0),
loadTest(false)
: mChanM(wChanM), underrun(false), sendCursor(0), rcvCursor(0), mOn(false),
mRadio(wRadio), receiveOffset(wReceiveOffset), samplesPerSymbol(wSPS),
powerScaling(1.0), loadTest(false)
{
mClock.set(wStartTime);
}
RadioInterface::~RadioInterface(void)
{
if (mOn) {
mRadio->stop();
close();
RadioInterface::~RadioInterface(void) {
if (rcvBuffer!=NULL) delete rcvBuffer;
//mReceiveFIFO.clear();
for (int i = 0; i < mChanM; i++) {
if (rcvBuffer[i] != NULL)
delete rcvBuffer[i];
if (sendBuffer[i] != NULL)
delete sendBuffer[i];
}
}
}
double RadioInterface::fullScaleInputValue(void) {
@@ -55,11 +85,11 @@ double RadioInterface::fullScaleOutputValue(void) {
}
void RadioInterface::setPowerAttenuation(double atten)
void RadioInterface::setPowerAttenuation(double atten, int chan)
{
double rfGain, digAtten;
rfGain = mRadio->setTxGain(mRadio->maxTxGain() - atten);
rfGain = mRadio->setTxGain(mRadio->maxTxGain() - atten, chan);
digAtten = atten - mRadio->maxTxGain() + rfGain;
if (digAtten < 1.0)
@@ -90,53 +120,75 @@ int RadioInterface::radioifyVector(signalVector &wVector,
return wVector.size();
}
int RadioInterface::unRadioifyVector(float *floatVector,
signalVector& newVector)
int RadioInterface::unRadioifyVector(float *floatVector, int offset,
signalVector &newVector)
{
int i;
signalVector::iterator itr = newVector.begin();
for (i = 0; i < newVector.size(); i++) {
*itr++ = Complex<float>(floatVector[2 * i + 0],
floatVector[2 * i + 1]);
*itr++ = Complex<float>(floatVector[offset + 2 * i + 0],
floatVector[offset + 2 * i + 1]);
}
return newVector.size();
}
bool RadioInterface::tuneTx(double freq)
bool RadioInterface::tuneTx(double freq, int chan)
{
return mRadio->setTxFreq(freq);
return mRadio->setTxFreq(freq, chan);
}
bool RadioInterface::tuneRx(double freq)
bool RadioInterface::tuneRx(double freq, int chan)
{
return mRadio->setRxFreq(freq);
return mRadio->setRxFreq(freq, chan);
}
void RadioInterface::start()
bool RadioInterface::start()
{
LOG(INFO) << "starting radio interface...";
mAlignRadioServiceLoopThread.start((void * (*)(void*))AlignRadioServiceLoopAdapter,
(void*)this);
int i;
if (mOn)
return false;
mOn = true;
#ifdef USRP1
mAlignRadioServiceLoopThread = new Thread(32768);
mAlignRadioServiceLoopThread->start((void * (*)(void*))AlignRadioServiceLoopAdapter,
(void*)this);
#endif
writeTimestamp = mRadio->initialWriteTimestamp();
readTimestamp = mRadio->initialReadTimestamp();
for (i = 0; i < mChanM; i++) {
sendBuffer[i] = new float[8*2*INCHUNK];
rcvBuffer[i] = new float[8*2*OUTCHUNK];
}
/* Init I/O specific variables if applicable */
init();
mRadio->start();
LOG(DEBUG) << "Radio started";
mRadio->updateAlignment(writeTimestamp-10000);
mRadio->updateAlignment(writeTimestamp-10000);
sendBuffer = new float[2*2*INCHUNK*samplesPerSymbol];
rcvBuffer = new float[2*2*OUTCHUNK*samplesPerSymbol];
mOn = true;
return true;
}
bool RadioInterface::stop()
{
if (!mOn)
return false;
mOn = false;
mRadio->stop();
}
#ifdef USRP1
void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface)
{
while (1) {
while (radioInterface->on()) {
radioInterface->alignRadio();
pthread_testcancel();
}
@@ -147,23 +199,55 @@ void RadioInterface::alignRadio() {
sleep(60);
mRadio->updateAlignment(writeTimestamp+ (TIMESTAMP) 10000);
}
#endif
void RadioInterface::driveTransmitRadio(signalVector &radioBurst, bool zeroBurst) {
void RadioInterface::driveTransmitRadio(signalVector **radioBurst, bool *zeroBurst)
{
int i;
if (!mOn) return;
if (!mOn)
return;
radioifyVector(radioBurst, sendBuffer + 2 * sendCursor, powerScaling, zeroBurst);
for (i = 0; i < mChanM; i++) {
radioifyVector(*radioBurst[i], sendBuffer[i] + 2 * sendCursor,
powerScaling, zeroBurst[i]);
}
sendCursor += radioBurst.size();
/*
* All bursts should be the same size since all transceivers are
* tied with a single clock in the radio interface.
*/
sendCursor += radioBurst[0]->size();
pushBuffer();
}
void RadioInterface::driveReceiveRadio() {
static inline void shiftRxBuffers(float **buf, int offset, int len, int chanM)
{
for (int i = 0; i < chanM; i++)
memmove(buf[i], buf[i] + offset, sizeof(float) * len);
}
if (!mOn) return;
void RadioInterface::loadVectors(unsigned tN, int samplesPerBurst,
int idx, GSM::Time rxClock)
{
int i;
if (mReceiveFIFO.size() > 8) return;
for (i = 0; i < mChanM; i++) {
signalVector rxVector(samplesPerBurst);
unRadioifyVector(rcvBuffer[i], idx * 2, rxVector);
radioVector *rxBurst = new radioVector(rxVector, rxClock);
mReceiveFIFO[i].write(rxBurst);
}
}
void RadioInterface::driveReceiveRadio()
{
if (!mOn)
return;
if (mReceiveFIFO[0].size() > 8)
return;
pullBuffer();
@@ -173,71 +257,98 @@ void RadioInterface::driveReceiveRadio() {
int rcvSz = rcvCursor;
int readSz = 0;
const int symbolsPerSlot = gSlotLen + 8;
int samplesPerBurst = (symbolsPerSlot + (tN % 4 == 0)) * samplesPerSymbol;
// while there's enough data in receive buffer, form received
// GSM bursts and pass up to Transceiver
// Using the 157-156-156-156 symbols per timeslot format.
while (rcvSz > (symbolsPerSlot + (tN % 4 == 0))*samplesPerSymbol) {
signalVector rxVector((symbolsPerSlot + (tN % 4 == 0))*samplesPerSymbol);
unRadioifyVector(rcvBuffer+readSz*2,rxVector);
GSM::Time tmpTime = rcvClock;
while (rcvSz >= samplesPerBurst) {
if (rcvClock.FN() >= 0) {
//LOG(DEBUG) << "FN: " << rcvClock.FN();
radioVector *rxBurst = NULL;
if (!loadTest)
rxBurst = new radioVector(rxVector,tmpTime);
else {
if (tN % 4 == 0)
rxBurst = new radioVector(*finalVec9,tmpTime);
else
rxBurst = new radioVector(*finalVec,tmpTime);
}
mReceiveFIFO.put(rxBurst);
loadVectors(tN, samplesPerBurst, readSz, rcvClock);
}
mClock.incTN();
mClock.incTN();
rcvClock.incTN();
//if (mReceiveFIFO.size() >= 16) mReceiveFIFO.wait(8);
//LOG(DEBUG) << "receiveFIFO: wrote radio vector at time: " << mClock.get() << ", new size: " << mReceiveFIFO.size() ;
readSz += (symbolsPerSlot+(tN % 4 == 0))*samplesPerSymbol;
rcvSz -= (symbolsPerSlot+(tN % 4 == 0))*samplesPerSymbol;
readSz += samplesPerBurst;
rcvSz -= samplesPerBurst;
tN = rcvClock.TN();
samplesPerBurst = (symbolsPerSlot + (tN % 4 == 0)) * samplesPerSymbol;
}
if (readSz > 0) {
rcvCursor -= readSz;
memmove(rcvBuffer,rcvBuffer+2*readSz,sizeof(float) * 2 * rcvCursor);
shiftRxBuffers(rcvBuffer, 2 * readSz, 2 * rcvCursor, mChanM);
}
}
bool RadioInterface::isUnderrun()
{
bool retVal = underrun;
underrun = false;
return retVal;
}
void RadioInterface::attach(RadioDevice *wRadio, int wRadioOversampling)
{
if (!mOn) {
mRadio = wRadio;
mRadioOversampling = SAMPSPERSYM;
}
}
double RadioInterface::setRxGain(double dB)
double RadioInterface::setRxGain(double dB, int chan)
{
if (mRadio)
return mRadio->setRxGain(dB);
return mRadio->setRxGain(dB, chan);
else
return -1;
}
double RadioInterface::getRxGain()
double RadioInterface::getRxGain(int chan)
{
if (mRadio)
return mRadio->getRxGain();
return mRadio->getRxGain(chan);
else
return -1;
}
bool RadioInterface::init()
{
for (int i = 0; i < CHAN_MAX; i++) {
rx_buf[i] = new short[2 * OUTCHUNK];
tx_buf[i] = new short[4 * 2 * INCHUNK];
}
}
void RadioInterface::close()
{
for (int i = 0; i < CHAN_MAX; i++) {
delete rx_buf[i];
delete tx_buf[i];
}
}
/* Receive a timestamped chunk from the device */
void RadioInterface::pullBuffer()
{
bool local_underrun;
/* Read samples. Fail if we don't get what we want. */
int num_rd = mRadio->readSamples(rx_buf, mChanM, OUTCHUNK, readTimestamp);
LOG(DEBUG) << "Rx read " << num_rd << " samples from device";
assert(num_rd == OUTCHUNK);
underrun |= local_underrun;
readTimestamp += (TIMESTAMP) num_rd;
for (int i = 0; i < mChanM; i++)
shortToFloat(rcvBuffer[i] + 2 * rcvCursor, rx_buf[i], num_rd);
rcvCursor += num_rd;
}
/* Send timestamped chunk to the device with arbitrary size */
void RadioInterface::pushBuffer()
{
if (sendCursor < INCHUNK)
return;
for (int i = 0; i < mChanM; i++)
floatToShort(tx_buf[i], sendBuffer[i], sendCursor);
/* Write samples. Fail if we don't get what we want. */
int num_smpls = mRadio->writeSamples(tx_buf, mChanM, sendCursor,
writeTimestamp, &underrun);
assert(num_smpls == sendCursor);
writeTimestamp += (TIMESTAMP) num_smpls;
sendCursor = 0;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
* Copyright 2008, 2012 Free Software Foundation, Inc.
*
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
*
@@ -12,7 +12,8 @@
*/
#ifndef _RADIOINTEFACE_H_
#define _RADIOINTEFACE_H_
#include "sigProcLib.h"
#include "GSMCommon.h"
@@ -22,29 +23,31 @@
#include "radioClock.h"
/** samples per GSM symbol */
#define SAMPSPERSYM 1
#define SAMPSPERSYM 1
#define INCHUNK (625)
#define OUTCHUNK (625)
#define CHAN_MAX 2
static const unsigned gSlotLen = 148; ///< number of symbols per slot, not counting guard periods
/** class to interface the transceiver with the USRP */
class RadioInterface {
private:
protected:
Thread mAlignRadioServiceLoopThread; ///< thread that synchronizes transmit and receive sections
int mChanM; ///< channelizer width
VectorFIFO mReceiveFIFO; ///< FIFO that holds receive bursts
VectorFIFO mReceiveFIFO[CHAN_MAX]; ///< FIFO that holds receive bursts
RadioDevice *mRadio; ///< the USRP object
float *sendBuffer;
float *sendBuffer[CHAN_MAX];
unsigned sendCursor;
float *rcvBuffer;
float *rcvBuffer[CHAN_MAX];
unsigned rcvCursor;
bool underrun; ///< indicates writes to USRP are too slow
bool overrun; ///< indicates reads from USRP are too slow
TIMESTAMP writeTimestamp; ///< sample timestamp of next packet written to USRP
@@ -54,8 +57,6 @@ private:
int samplesPerSymbol; ///< samples per GSM symbol
int receiveOffset; ///< offset b/w transmit and receive GSM timestamps, in timeslots
int mRadioOversampling;
int mTransceiverOversampling;
bool mOn; ///< indicates radio is on
@@ -65,6 +66,10 @@ private:
int mNumARFCNs;
signalVector *finalVec, *finalVec9;
private:
/** initialize I/O internals */
bool init();
/** format samples to USRP */
int radioifyVector(signalVector &wVector,
float *floatVector,
@@ -72,25 +77,34 @@ private:
bool zero);
/** format samples from USRP */
int unRadioifyVector(float *floatVector, signalVector &wVector);
int unRadioifyVector(float *floatVector, int offset, signalVector &wVector);
/** push GSM bursts into the transmit buffer */
void pushBuffer(void);
virtual void pushBuffer(void);
/** pull GSM bursts from the receive buffer */
void pullBuffer(void);
virtual void pullBuffer(void);
/** load receive vectors into FIFO's */
void loadVectors(unsigned tN, int samplesPerBurst, int index, GSM::Time rxClock);
public:
/** start the interface */
void start();
bool start();
bool stop();
bool started() { return mOn; };
/** shutdown interface */
void close();
/** constructor */
RadioInterface(RadioDevice* wRadio = NULL,
int receiveOffset = 3,
int wRadioOversampling = SAMPSPERSYM,
int wTransceiverOversampling = SAMPSPERSYM,
GSM::Time wStartTime = GSM::Time(0));
RadioInterface(RadioDevice* wRadio,
int wChanM = 1,
int wSPS = SAMPSPERSYM,
int receiveOffset = 3,
GSM::Time wStartTime = GSM::Time(0, 0));
/** destructor */
~RadioInterface();
@@ -98,38 +112,32 @@ public:
void setSamplesPerSymbol(int wSamplesPerSymbol) {if (!mOn) samplesPerSymbol = wSamplesPerSymbol;}
int getSamplesPerSymbol() { return samplesPerSymbol;}
/** check for underrun, resets underrun value */
bool isUnderrun();
/** attach an existing USRP to this interface */
void attach(RadioDevice *wRadio, int wRadioOversampling);
/** return the receive FIFO */
VectorFIFO* receiveFIFO() { return &mReceiveFIFO;}
VectorFIFO* receiveFIFO(int num) { return &mReceiveFIFO[num];}
/** return the basestation clock */
RadioClock* getClock(void) { return &mClock;};
/** set transmit frequency */
bool tuneTx(double freq);
bool tuneTx(double freq, int chan = 0);
/** set receive frequency */
bool tuneRx(double freq);
bool tuneRx(double freq, int chan = 0);
/** set receive gain */
double setRxGain(double dB);
double setRxGain(double dB, int chan = 0);
/** get receive gain */
double getRxGain(void);
double getRxGain(int chan = 0);
/** drive transmission of GSM bursts */
void driveTransmitRadio(signalVector &radioBurst, bool zeroBurst);
void driveTransmitRadio(signalVector **radioBurst, bool *zeroBurst);
/** drive reception of GSM bursts */
void driveReceiveRadio();
void setPowerAttenuation(double atten);
void setPowerAttenuation(double atten, int chan = 0);
/** returns the full-scale transmit amplitude **/
double fullScaleInputValue();
@@ -140,20 +148,8 @@ public:
/** set thread priority on current thread */
void setPriority() { mRadio->setPriority(); }
/** get transport bus type of attached device */
enum RadioDevice::busType getBus() { return mRadio->getBus(); }
protected:
/** drive synchronization of Tx/Rx of USRP */
void alignRadio();
/** reset the interface */
void reset();
friend void *AlignRadioServiceLoopAdapter(RadioInterface*);
/** get transport window type of attached device */
enum RadioDevice::TxWindowType getWindowType() { return mRadio->getWindowType(); }
};
/** synchronization thread loop */
void *AlignRadioServiceLoopAdapter(RadioInterface*);
#endif /* _RADIOINTEFACE_H_ */

View File

@@ -2,7 +2,7 @@
* Written by Thomas Tsou <ttsou@vt.edu>
* Based on code by Harvind S Samra <hssamra@kestrelsp.com>
*
* Copyright 2011 Free Software Foundation, Inc.
* Copyright 2011, 2012 Free Software Foundation, Inc.
*
* 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
@@ -41,21 +41,6 @@ bool radioVector::operator>(const radioVector& other) const
return mTime > other.mTime;
}
unsigned VectorFIFO::size()
{
return mQ.size();
}
void VectorFIFO::put(radioVector *ptr)
{
mQ.put((void*) ptr);
}
radioVector *VectorFIFO::get()
{
return (radioVector*) mQ.get();
}
GSM::Time VectorQueue::nextTime() const
{
GSM::Time retVal;

View File

@@ -2,7 +2,7 @@
* Written by Thomas Tsou <ttsou@vt.edu>
* Based on code by Harvind S Samra <hssamra@kestrelsp.com>
*
* Copyright 2011 Free Software Foundation, Inc.
* Copyright 2011, 2012 Free Software Foundation, Inc.
*
* 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
@@ -37,14 +37,7 @@ private:
GSM::Time mTime;
};
class VectorFIFO {
public:
unsigned size();
void put(radioVector *ptr);
radioVector *get();
private:
PointerFIFO mQ;
class VectorFIFO : public InterthreadQueue<radioVector> {
};
class VectorQueue : public InterthreadPriorityQueue<radioVector> {

View File

@@ -36,12 +36,6 @@
#include <Logger.h>
#include <Configuration.h>
#ifdef RESAMPLE
#define DEVICERATE 400e3
#else
#define DEVICERATE 1625e3/6
#endif
using namespace std;
ConfigurationTable gConfig("/etc/OpenBTS/OpenBTS.db");
@@ -58,6 +52,7 @@ static void ctrlCHandler(int signo)
int main(int argc, char *argv[])
{
std::string deviceArgs;
std::string txAntenna, rxAntenna;
if (argc == 3)
{
@@ -88,16 +83,43 @@ int main(int argc, char *argv[])
srandom(time(NULL));
int mOversamplingRate = numARFCN/2 + numARFCN;
RadioDevice *usrp = RadioDevice::make(DEVICERATE * SAMPSPERSYM);
if (!usrp->open(deviceArgs)) {
RadioDevice *usrp = RadioDevice::make(SAMPSPERSYM);
int radioType = usrp->open(deviceArgs);
if (radioType < 0) {
LOG(ALERT) << "Transceiver exiting..." << std::endl;
return EXIT_FAILURE;
}
RadioInterface* radio = new RadioInterface(usrp,3,SAMPSPERSYM,mOversamplingRate,false);
Transceiver *trx = new Transceiver(gConfig.getNum("TRX.Port"),gConfig.getStr("TRX.IP").c_str(),SAMPSPERSYM,GSM::Time(3,0),radio);
trx->receiveFIFO(radio->receiveFIFO());
if (gConfig.defines("GSM.Radio.TxAntenna"))
txAntenna = gConfig.getStr("GSM.Radio.TxAntenna").c_str();
if (gConfig.defines("GSM.Radio.RxAntenna"))
rxAntenna = gConfig.getStr("GSM.Radio.RxAntenna").c_str();
if (txAntenna != "")
usrp->setTxAntenna(txAntenna);
if (rxAntenna != "")
usrp->setRxAntenna(rxAntenna);
LOG(INFO) << "transceiver using transmit antenna " << usrp->getRxAntenna();
LOG(INFO) << "transceiver using receive antenna " << usrp->getTxAntenna();
RadioInterface* radio;
switch (radioType) {
case RadioDevice::NORMAL:
radio = new RadioInterface(usrp, 3, SAMPSPERSYM, false);
break;
case RadioDevice::RESAMP:
default:
LOG(ALERT) << "Unsupported configuration";
return EXIT_FAILURE;
}
int port = gConfig.getNum("TRX.Port");
const char *addr = gConfig.getStr("TRX.IP").c_str();
DriveLoop *drive = new DriveLoop(SAMPSPERSYM,GSM::Time(3,0),radio);
Transceiver *trx = new Transceiver(port, addr, SAMPSPERSYM, radio, drive, 0);
radio->activateChan(0);
/*
signalVector *gsmPulse = generateGSMPulse(2,1);
BitVector normalBurstSeg = "0000101010100111110010101010010110101110011000111001101010000";
@@ -130,12 +152,13 @@ int main(int argc, char *argv[])
usrp->loadBurst(finalVecShort,finalVec.size());
*/
trx->start();
//int i = 0;
while(!gbShutdown) { sleep(1); }//i++; if (i==60) break;}
cout << "Shutting down transceiver..." << endl;
trx->shutdown();
// trx->stop();
delete trx;
// delete radio;
delete drive;
delete radio;
}

View File

@@ -72,11 +72,6 @@ AC_ARG_WITH(singledb, [
[enable single daughterboard use on USRP1])
])
AC_ARG_WITH(resamp, [
AS_HELP_STRING([--with-resamp],
[enable resampling for non-52MHz devices])
])
AC_ARG_WITH(extref, [
AS_HELP_STRING([--with-extref],
[enable external reference on UHD devices])
@@ -98,14 +93,10 @@ AS_IF([test "x$with_usrp1" = "xyes"], [
])
AS_IF([test "x$with_uhd" = "xyes"],[
PKG_CHECK_MODULES(UHD, uhd >= 003.002.000)
PKG_CHECK_MODULES(UHD, uhd >= 003.004.000)
AC_DEFINE(USE_UHD, 1, Define to 1 if using UHD)
])
AS_IF([test "x$with_resamp" = "xyes"], [
AC_DEFINE(RESAMPLE, 1, Define to 1 for resampling)
])
AS_IF([test "x$with_extref" = "xyes"], [
AC_DEFINE(EXTREF, 1, Define to 1 for external reference)
])
@@ -114,7 +105,6 @@ AS_IF([test "x$with_singledb" = "xyes"], [
AC_DEFINE(SINGLEDB, 1, Define to 1 for single daughterboard)
])
AM_CONDITIONAL(RESAMPLE, [test "x$with_resamp" = "xyes"])
AM_CONDITIONAL(UHD, [test "x$with_uhd" = "xyes"])
AM_CONDITIONAL(USRP1, [test "x$with_usrp1" = "xyes"])