diff --git a/TransceiverUHD/Makefile.am b/TransceiverUHD/Makefile.am new file mode 100644 index 0000000..f7716b7 --- /dev/null +++ b/TransceiverUHD/Makefile.am @@ -0,0 +1,34 @@ +if HAVE_UHD +include $(top_srcdir)/Makefile.common + +noinst_LTLIBRARIES = libumtstransceiver.la +noinst_PROGRAMS = transceiver +noinst_HEADERS = \ + RadioInterface.h \ + RadioDevice.h \ + Transceiver.h \ + SampleBuffer.h \ + UHDDevice.h \ + Resampler.h \ + convolve.h \ + convert.h + +libumtstransceiver_la_CFLAGS = -Wall $(AM_CFLAGS) -std=gnu99 -march=native +libumtstransceiver_la_CPPFLAGS = -Wall $(AM_CPPFLAGS) $(UHD_CPPFLAGS) +libumtstransceiver_la_CXXFLAGS = -Wall $(AM_CXXFLAGS) $(UHD_CXXFLAGS) +libumtstransceiver_la_SOURCES = \ + RadioInterface.cpp \ + Transceiver.cpp \ + UHDDevice.cpp \ + SampleBuffer.cpp \ + Resampler.cpp \ + convolve.c \ + convert.c + +transceiver_SOURCES = runTransceiver.cpp ../apps/GetConfigurationKeys.cpp +transceiver_LDADD = libumtstransceiver.la $(UHD_LIBS) $(UMTS_LA) $(GSM_LA) $(COMMON_LA) $(SQLITE_LA) + +install: transceiver + mkdir -p "$(DESTDIR)/OpenBTS/" + install transceiver "$(DESTDIR)/OpenBTS/" +endif diff --git a/TransceiverUHD/RadioDevice.h b/TransceiverUHD/RadioDevice.h new file mode 100644 index 0000000..2c6e396 --- /dev/null +++ b/TransceiverUHD/RadioDevice.h @@ -0,0 +1,100 @@ +#ifndef __RADIO_DEVICE_H__ +#define __RADIO_DEVICE_H__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/** A class to handle a USRP rev 4, with a two RFX900 daughterboards */ +class RadioDevice { + public: + virtual ~RadioDevice() { } + + /* Available transport bus types */ + enum TxWindowType { TX_WINDOW_USRP1, TX_WINDOW_FIXED }; + + /** Start the USRP */ + virtual bool start()=0; + + /** Stop the USRP */ + virtual bool stop()=0; + + /** Get the Tx window type */ + virtual enum TxWindowType getWindowType()=0; + + /** Enable thread priority */ + virtual void setPriority()=0; + + /** + Read samples from the radio. + @param buf preallocated buf to contain read result + @param len number of samples desired + @param overrun Set if read buffer has been overrun, e.g. data not being read fast enough + @param timestamp The timestamp of the first samples to be read + @param underrun Set if radio does not have data to transmit, e.g. data not being sent fast enough + @param RSSI The received signal strength of the read result + @return The number of samples actually read + */ + virtual int readSamples(short *buf, int len, bool *overrun, + long long timestamp, bool *underrun, + unsigned *RSSI=NULL)=0; + /** + Write samples to the radio. + @param buf Contains the data to be written. + @param len number of samples to write. + @param underrun Set if radio does not have data to transmit, e.g. data not being sent fast enough + @param timestamp The timestamp of the first sample of the data buffer. + @param isControl Set if data is a control packet, e.g. a ping command + @return The number of samples actually written + */ + virtual int writeSamples(short *buf, int len, bool *underrun, + long long timestamp)=0; + + /** Set the transmitter frequency */ + virtual bool setTxFreq(double wFreq)=0; + + /** Set the receiver frequency */ + virtual bool setRxFreq(double wFreq)=0; + + /** Returns the starting write Timestamp*/ + virtual long long initialWriteTimestamp(void)=0; + + /** Returns the starting read Timestamp*/ + virtual long long initialReadTimestamp(void)=0; + + /** returns the full-scale transmit amplitude **/ + virtual double fullScaleInputValue()=0; + + /** returns the full-scale receive amplitude **/ + virtual double fullScaleOutputValue()=0; + + /** sets the receive chan gain, returns the gain setting **/ + virtual double setRxGain(double dB)=0; + + /** gets the current receive gain **/ + virtual double getRxGain(void)=0; + + /** return maximum Rx Gain **/ + virtual double maxRxGain(void) = 0; + + /** return minimum Rx Gain **/ + virtual double minRxGain(void) = 0; + + /** sets the transmit chan gain, returns the gain setting **/ + virtual double setTxGain(double dB)=0; + + /** return maximum Tx Gain **/ + virtual double maxTxGain(void) = 0; + + /** return minimum Tx Gain **/ + virtual double minTxGain(void) = 0; + + /** Return internal status values */ + virtual double getTxFreq()=0; + virtual double getRxFreq()=0; + virtual double getSampleRate()=0; + virtual double numberRead()=0; + virtual double numberWritten()=0; +}; + +#endif /* __RADIO_DEVICE_H__ */ diff --git a/TransceiverUHD/RadioInterface.cpp b/TransceiverUHD/RadioInterface.cpp new file mode 100644 index 0000000..4b219ce --- /dev/null +++ b/TransceiverUHD/RadioInterface.cpp @@ -0,0 +1,410 @@ +/* + * OpenBTS provides an open source alternative to legacy telco protocols and + * traditionally complex, proprietary hardware systems. + * + * Copyright 2008, 2009 Free Software Foundation, Inc. + * Copyright 2014 Range Networks, Inc. + * Copyright 2014 Ettus Research LLC + * + * This software is distributed under the terms of the GNU General Public + * License version 3. See the COPYING and NOTICE files in the current + * directory for licensing information. + * + * This use of this software may be subject to additional restrictions. + * See the LEGAL file in the main directory for details. + */ + +#include "RadioInterface.h" +#include + +extern "C" { +#include "convert.h" +} + +/* + * Downlink digital gain scaling + * + * This value scales floating point transmit values prior fixed-point + * conversion. The current value provides a significant amount of backoff to + * avoid reaching compression across general device-daughterboard combinations. + * More optimal values to target peak gain or maximum dynamic range will be + * device and daughterboard combination specific. + * */ +#define TX_BASE_SCALING 150.0f + +/* + * Resampling ratio for 25 MHz base clocking + * + * This resampling factor absorbs the sample rate differences between UMTS chip + * rate of 3.84 Mcps and the device clocking rate. USRP N200 clock operates at a + * rate of 100 MHz with factor of 16 downsampling. B200 uses 25 MHz FPGA + * clocking with downsampling by 4. + */ +#define RESAMP_INRATE 384 +#define RESAMP_OUTRATE 625 + +/* + * Number of taps per resampler-RRC filter partitions + * + * The number of taps per polyphase partition filter can be adjusted to suit + * processing or spectrum requirements. Note that the filter length, and + * associated group delay, affects timing. + */ +#define RESAMP_TAP_LEN 20 + +/* Chunk multiplier with resampler rates determine the chunk sizes */ +#define CHUNK_MUL 2 + +/* Universal resampling parameters */ +#define NUMCHUNKS 32 + +UMTS::Time VectorQueue::nextTime() const +{ + UMTS::Time retVal; + ScopedLock lock(mLock); + + while (!mQ.size()) + mWriteSignal.wait(mLock); + + return mQ.top()->time(); +} + +radioVector* VectorQueue::getStaleBurst(const UMTS::Time& targTime) +{ + ScopedLock lock(mLock); + if (!mQ.size()) + return NULL; + + if (mQ.top()->time() < targTime) { + radioVector* retVal = mQ.top(); + mQ.pop(); + return retVal; + } + return NULL; +} + +radioVector* VectorQueue::getCurrentBurst(const UMTS::Time& targTime) +{ + ScopedLock lock(mLock); + if (!mQ.size()) + return NULL; + + if (mQ.top()->time() == targTime) { + radioVector* retVal = mQ.top(); + mQ.pop(); + return retVal; + } + return NULL; +} + +RadioInterface::RadioInterface(RadioDevice *wRadio, int wReceiveOffset, + UMTS::Time wStartTime) + : mRadio(wRadio), sendCursor(0), recvCursor(0), + underrun(false), receiveOffset(wReceiveOffset), + mOn(false), powerScaling(TX_BASE_SCALING), + finalVec(NULL), upsampler(NULL), dnsampler(NULL), + innerSendBuffer(NULL), innerRecvBuffer(NULL), + outerSendBuffer(NULL), outerRecvBuffer(NULL), + convertSendBuffer(NULL), convertRecvBuffer(NULL) +{ + mClock.set(wStartTime); + inchunk = RESAMP_INRATE * CHUNK_MUL; + outchunk = RESAMP_OUTRATE * CHUNK_MUL; +} + +RadioInterface::~RadioInterface(void) +{ + delete convertSendBuffer; + delete convertRecvBuffer; + + delete innerSendBuffer; + delete outerSendBuffer; + delete innerRecvBuffer; + delete outerRecvBuffer; + + delete dnsampler; + delete upsampler; +} + +/* + * Allocate resamplers and I/O buffers + * + * Setup multiple chunks on buffers except for the outer receive buffer, which + * always receives a fixed number of samples. Conversion buffers are fixed point + * used directly by the device interface. + */ +bool RadioInterface::init() +{ + dnsampler = new Resampler(RESAMP_INRATE, RESAMP_OUTRATE, RESAMP_TAP_LEN); + if (!dnsampler->init(Resampler::FILTER_TYPE_RRC)) { + LOG(ALERT) << "Rx resampler failed to initialize"; + return false; + } + + upsampler = new Resampler(RESAMP_OUTRATE, RESAMP_INRATE, RESAMP_TAP_LEN); + if (!upsampler->init(Resampler::FILTER_TYPE_RRC)) { + LOG(ALERT) << "Tx resampler failed to initialize"; + return false; + } + + outerSendBuffer = new signalVector(NUMCHUNKS * outchunk); + innerRecvBuffer = new signalVector(NUMCHUNKS * inchunk); + + /* Buffers that feed the resampler require filter length headroom */ + innerSendBuffer = new signalVector(NUMCHUNKS * inchunk + upsampler->len()); + outerRecvBuffer = new signalVector(outchunk + dnsampler->len()); + + convertSendBuffer = new short[outerSendBuffer->size() * 2]; + convertRecvBuffer = new short[outerRecvBuffer->size() * 2]; + + return true; +} + +/* + * Start the radio device + * + * When the device start returns, initialize the timestamp counters with values + * reported by the device. This avoids having to reset the device clock (if even + * possible) and guessing at latency and corresponding initial values. + * + * Note that we do not advance the initial timestamp relative to receive here + * so the initial data chunk will submit with a late timestamp. Some timing + * compensation will occur with the TX_PACKET_SYNC alignment, which removes + * packets from the downlink stream, while the remaining timing adjustment will + * be absorbed by UHD and on the device itself. + */ +bool RadioInterface::start() +{ + if (mOn) + return true; + + LOG(INFO) << "Starting radio device"; + + if (!mRadio->start()) + return false; + + sendCursor = 0; + recvCursor = 0; + writeTimestamp = mRadio->initialWriteTimestamp(); + readTimestamp = mRadio->initialReadTimestamp(); + mOn = true; + + LOG(INFO) << "Radio started"; + return true; +} + +/* + * Stop the radio device + * + * This is a pass-through call to the device interface. Because the underlying + * stop command issuance generally doesn't return confirmation on device status, + * this call will only return false if the device is already stopped. + */ +bool RadioInterface::stop() +{ + if (!mOn || !mRadio->stop()) + return false; + + mOn = false; + return true; +} + +double RadioInterface::fullScaleInputValue(void) +{ + return mRadio->fullScaleInputValue(); +} + +double RadioInterface::fullScaleOutputValue(void) +{ + return mRadio->fullScaleOutputValue(); +} + +int RadioInterface::setPowerAttenuation(int atten) +{ + double rfGain, digAtten; + + if (atten < 0) + atten = 0; + + rfGain = mRadio->setTxGain(mRadio->maxTxGain() - (double) atten); + digAtten = (double) atten - mRadio->maxTxGain() + rfGain; + + if (digAtten < 1.0) + powerScaling = TX_BASE_SCALING; + else + powerScaling = TX_BASE_SCALING / sqrt(pow(10, (digAtten / 10.0))); + + return atten; +} + +int RadioInterface::radioifyVector(signalVector &wVector, + float *retVector, bool zero) +{ + if (zero) + memset(retVector, 0, wVector.size() * sizeof(Complex)); + else + memcpy(retVector, wVector.begin(), wVector.size() * sizeof(Complex)); + + return wVector.size(); +} + +void RadioInterface::unRadioifyVector(float *floatVector, + signalVector& newVector) +{ + memcpy(newVector.begin(), floatVector, + newVector.size() * sizeof(Complex)); +} + +bool RadioInterface::pushBuffer(void) +{ + int rc, chunks; + int inner_len, outer_len; + + if (sendCursor < inchunk) + return true; + if (sendCursor > innerSendBuffer->size()) { + LOG(ALERT) << "Send buffer overflow"; + } + + chunks = sendCursor / inchunk; + inner_len = chunks * inchunk; + outer_len = chunks * outchunk; + + /* Input from the buffer with number of taps length headroom */ + float *resamp_in = (float *) (innerSendBuffer->begin() + upsampler->len()); + float *resamp_out = (float *) outerSendBuffer->begin(); + + rc = upsampler->rotate(resamp_in, inner_len, resamp_out, outer_len); + if (rc < 0) { + LOG(ALERT) << "Sample rate downsampling error"; + return false; + } + + convert_float_short(convertSendBuffer, + (float *) outerSendBuffer->begin(), + powerScaling, outer_len * 2); + + mRadio->writeSamples(convertSendBuffer, outer_len, + &underrun, writeTimestamp); + + /* Shift remaining samples to beginning of buffer */ + memmove(innerSendBuffer->begin() + upsampler->len(), + innerSendBuffer->begin() + upsampler->len() + inner_len, + (sendCursor - inner_len) * 2 * sizeof(float)); + + writeTimestamp += outer_len; + sendCursor -= inner_len; + + return true; +} + +bool RadioInterface::pullBuffer(void) +{ + bool localUnderrun; + + if (recvCursor > innerRecvBuffer->size() - inchunk) + return true; + + /* Outer buffer access size is fixed */ + size_t num_recv = mRadio->readSamples(convertRecvBuffer, + outchunk, + &overrun, + readTimestamp, + &localUnderrun); + if (num_recv != outchunk) { + LOG(ALERT) << "Receive error " << num_recv; + return false; + } + + short *convert_in = convertRecvBuffer; + float *convert_out = (float *) (outerRecvBuffer->begin() + dnsampler->len()); + + convert_short_float(convert_out, convert_in, outchunk * 2); + + underrun |= localUnderrun; + readTimestamp += outchunk; + + /* Write to the end of the inner receive buffer */ + float *resamp_in = (float *) (outerRecvBuffer->begin() + dnsampler->len()); + float *resamp_out = (float *) (innerRecvBuffer->begin() + recvCursor); + + int rc = dnsampler->rotate(resamp_in, outchunk, + resamp_out, inchunk); + if (rc < 0) { + LOG(ALERT) << "Sample rate upsampling error"; + return false; + } + + recvCursor += inchunk; + + return true; +} + +bool RadioInterface::tuneTx(double freq) +{ + return mRadio->setTxFreq(freq); +} + +bool RadioInterface::tuneRx(double freq) +{ + return mRadio->setRxFreq(freq); +} + +void RadioInterface::driveTransmitRadio(signalVector &radioBurst, + bool zeroBurst) +{ + if (!mOn) + return; + + /* Buffer write position */ + float *pos = (float *) (innerSendBuffer->begin() + + upsampler->len() + sendCursor); + + radioifyVector(radioBurst, pos, zeroBurst); + + sendCursor += radioBurst.size(); + pushBuffer(); +} + +void RadioInterface::driveReceiveRadio(int guardPeriod) +{ + if (!mOn) + return; + + pullBuffer(); + + UMTS::Time recvClock = mClock.get(); + int recvSz = recvCursor; + int readSz = 0; + const int symbolsPerSlot = UMTS::gSlotLen; + + // while there's enough data in receive buffer, form received + // UMTS bursts and pass up to Transceiver + int vecSz = symbolsPerSlot + guardPeriod; + while (recvSz > vecSz) { + UMTS::Time tmpTime = recvClock; + mClock.incTN(); + recvClock.incTN(); + + if (recvClock.FN() >= 0) { + radioVector *rxBurst = new radioVector(vecSz, tmpTime); + memcpy(rxBurst->begin(), + innerRecvBuffer->begin() + readSz, + rxBurst->size() * sizeof(Complex)); + + mReceiveFIFO.put(rxBurst); + } + + readSz += symbolsPerSlot; + recvSz -= symbolsPerSlot; + } + + if (!readSz) + return; + + memmove(innerRecvBuffer->begin(), + innerRecvBuffer->begin() + readSz, + (recvCursor - readSz) * sizeof(Complex)); + + recvCursor -= readSz; +} diff --git a/TransceiverUHD/RadioInterface.h b/TransceiverUHD/RadioInterface.h new file mode 100644 index 0000000..26a84f7 --- /dev/null +++ b/TransceiverUHD/RadioInterface.h @@ -0,0 +1,261 @@ +#ifndef __RADIOINTERFACE_H__ +#define __RADIOINTERFACE_H__ + +#include "signalVector.h" +#include "LinkedLists.h" +#include "RadioDevice.h" +#include "Resampler.h" + +#include +#include + +/** class used to organize UMTS bursts by UMTS timestamps */ +class radioVector : public signalVector { +private: + UMTS::Time mTime; ///< the burst's UMTS timestamp + +public: + /** constructor */ + radioVector(const signalVector& wVector, + UMTS::Time& wTime): signalVector(wVector),mTime(wTime) { }; + + radioVector(unsigned wSz, UMTS::Time& wTime) + : signalVector(wSz), mTime(wTime) { }; + + /** timestamp read and write operators */ + UMTS::Time time() const { return mTime; } + void time(const UMTS::Time& wTime) { mTime = wTime; } + + /** comparison operator, used for sorting */ + bool operator>(const radioVector& other) const + { + return mTime > other.mTime; + } +}; + +/** a priority queue of radioVectors, i.e. UMTS bursts, sorted so that earliest element is at top */ +class VectorQueue : public InterthreadPriorityQueue { +public: + /** the top element of the queue */ + UMTS::Time nextTime() const; + + /** + Get stale burst, if any. + @param targTime The target time. + @return Pointer to burst older than target time, removed from queue, or NULL. + */ + radioVector* getStaleBurst(const UMTS::Time& targTime); + + /** + Get current burst, if any. + @param targTime The target time. + @return Pointer to burst at the target time, removed from queue, or NULL. + */ + radioVector* getCurrentBurst(const UMTS::Time& targTime); +}; + +/** a FIFO of radioVectors */ +class VectorFIFO { +private: + PointerFIFO mQ; + Mutex mLock; + +public: + unsigned size() { return mQ.size(); } + + void put(radioVector *ptr) + { + ScopedLock lock(mLock); + mQ.put((void*) ptr); + } + + radioVector *get() + { + ScopedLock lock(mLock); + return (radioVector*) mQ.get(); + } +}; + +/** FIFO to store */ +class RadioBurstFIFO : public InterthreadQueueWithWait { }; + +/** the basestation clock class */ +class RadioClock { +private: + UMTS::Time mClock; + Mutex mLock; + Signal updateSignal; + +public: + /** Set clock */ + void set(const UMTS::Time& wTime) + { + ScopedLock lock(mLock); + mClock = wTime; + updateSignal.signal(); + } + + /** Increment clock */ + void incTN() + { + ScopedLock lock(mLock); + mClock.incTN(); + updateSignal.signal(); + } + + /** Get clock value */ + UMTS::Time get() + { + ScopedLock lock(mLock); + return mClock; + } + + void wait() + { + ScopedLock lock(mLock); + updateSignal.wait(mLock); + } + + void signal() + { + ScopedLock lock(mLock); + updateSignal.signal(); + } +}; + +/** class to interface the transceiver with the USRP */ +class RadioInterface { +private: + VectorFIFO mReceiveFIFO; + RadioDevice *mRadio; + + size_t sendCursor; + size_t recvCursor; + size_t inchunk, outchunk; + + bool underrun; + bool overrun; + long long writeTimestamp; + long long readTimestamp; + + RadioClock mClock; + int receiveOffset; + + bool mOn; + + /** base scaling assumes no digital attenuation */ + double baseScaling; + + /** digital scaling attenuation factor */ + double powerScaling; + + signalVector *finalVec; + + Resampler *upsampler; + Resampler *dnsampler; + + signalVector *innerSendBuffer; + signalVector *innerRecvBuffer; + signalVector *outerSendBuffer; + signalVector *outerRecvBuffer; + + short *convertSendBuffer; + short *convertRecvBuffer; + + /** format samples to USRP */ + int radioifyVector(signalVector &wVector, float *shortVector, bool zero); + + /** format samples from USRP */ + void unRadioifyVector(float *floatVector, signalVector &wVector); + + /** push UMTS bursts into the transmit buffer */ + bool pushBuffer(void); + + /** pull UMTS bursts from the receive buffer */ + bool pullBuffer(void); + +public: + /** constructor */ + RadioInterface(RadioDevice* wRadio = NULL, int receiveOffset = 3, + UMTS::Time wStartTime = UMTS::Time(0)); + + /** destructor */ + ~RadioInterface(); + + bool init(); + + /** start the interface */ + bool start(); + bool stop(); + + /** check for underrun, resets underrun value */ + bool isUnderrun() + { + bool retVal = underrun; + underrun = false; + return retVal; + } + + /** return the receive FIFO */ + VectorFIFO* receiveFIFO() { return &mReceiveFIFO;} + + /** return the basestation clock */ + RadioClock* getClock(void) { return &mClock;}; + + /** set receive gain */ + double setRxGain(double dB) + { + if (mRadio) + return mRadio->setRxGain(dB); + else + return -1; + } + + /** get receive gain */ + double getRxGain(void) + { + if (mRadio) + return mRadio->getRxGain(); + else + return -1; + } + + /** set transmit frequency */ + bool tuneTx(double freq); + + /** set receive frequency */ + bool tuneRx(double freq); + + /** drive transmission of UMTS bursts */ + void driveTransmitRadio(signalVector &radioBurst, bool zeroBurst); + + /** drive reception of UMTS bursts */ + void driveReceiveRadio(int guardPeriod = 0); + + int setPowerAttenuation(int atten); + + /** returns the full-scale transmit amplitude **/ + double fullScaleInputValue(); + + /** returns the full-scale receive amplitude **/ + double fullScaleOutputValue(); + + /** set thread priority on current thread */ + void setPriority() { mRadio->setPriority(); } + + /** get transport window type of attached device */ + enum RadioDevice::TxWindowType getWindowType() + { + return mRadio->getWindowType(); + } + +protected: + /** drive synchronization of Tx/Rx of USRP */ + void alignRadio(); + + /** reset the interface */ + void reset(); +}; + +#endif /* __RADIOINTERFACE_H__ */ + diff --git a/TransceiverUHD/Resampler.cpp b/TransceiverUHD/Resampler.cpp new file mode 100644 index 0000000..0366aa1 --- /dev/null +++ b/TransceiverUHD/Resampler.cpp @@ -0,0 +1,302 @@ +/* + * Rational Sample Rate Conversion + * Copyright (C) 2012, 2013 Thomas Tsou + * Copyright (C) 2014 Ettus Research LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include + +#include "Resampler.h" + +extern "C" { +#include "convolve.h" +} + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327f +#endif + +#define MAX_OUTPUT_LEN 8192 + +static float sinc(float x) +{ + if (x == 0.0) + return 0.9999999999; + + return sin(M_PI * x) / (M_PI * x); +} + +/* + * Generate sinc prototype filter with a Blackman-harris window. + * Scale coefficients with DC filter gain set to unity divided + * by the number of filter partitions. + */ +static float gen_windowed_sinc(float *buf, size_t len, int p, int q, float bw) +{ + float sum = 0.0f; + float a0 = 0.35875f; + float a1 = 0.48829f; + float a2 = 0.14128f; + float a3 = 0.01168f; + float cutoff; + float midpt = ((float) len - 1.0f) / 2.0f; + + if (p > q) + cutoff = (float) p; + else + cutoff = (float) q; + + for (size_t i = 0; i < len; i++) { + buf[i] = sinc(((float) i - midpt) / cutoff * bw); + buf[i] *= a0 - + a1 * cos(2.0f * M_PI * i / (float) (len - 1)) + + a2 * cos(4.0f * M_PI * i / (float) (len - 1)) - + a3 * cos(6.0f * M_PI * i / (float) (len - 1)); + sum += buf[i]; + } + + return (float) p / sum; +} + +/* + * Generate UMTS root raised cosine prototype filter. + */ +static float gen_umts_rrc_pulse(float *buf, size_t len, int p, int q, float bw) +{ + float alpha = 0.22f; + float Tc = 1.0f; + float sum = 0.0f; + float midpt = ((float) len - 1.0f) / 2.0f; + + for (size_t i = 0; i < len; i++) { + float t, a, b, c; + + t = ((float) i - midpt) / (float) p * bw; + a = sinf(M_PI * t / Tc * (1.0f - alpha)); + b = 4.0f * alpha * t / Tc * cosf(M_PI * t / Tc * (1 + alpha)); + c = 4.0f * alpha * t / Tc; + + buf[i] = (a + b) / (M_PI * t / Tc * (1.0f - c * c)); + sum += buf[i]; + } + + return (float) p / sum; +} + +bool Resampler::initFilters(int type, float bw) +{ + size_t proto_len = p * filt_len; + float *proto, scale; + + /* + * Allocate partition filters and the temporary prototype filter + * according to numerator of the rational rate. Coefficients are + * real only and must be 16-byte memory aligned for SSE usage. + */ + proto = new float[proto_len]; + if (!proto) + return false; + + partitions = (float **) malloc(sizeof(float *) * p); + if (!partitions) { + delete proto; + return false; + } + + for (size_t i = 0; i < p; i++) { + partitions[i] = (float *) + memalign(16, filt_len * 2 * sizeof(float)); + } + + /* Filter type selection */ + if (type == FILTER_TYPE_RRC) { + scale = gen_umts_rrc_pulse(proto, proto_len, p, q, bw); + } else if (type == FILTER_TYPE_SINC) { + scale = gen_windowed_sinc(proto, proto_len, p, q, bw); + } else { + delete proto; + return false; + } + + /* Populate filter partitions from the prototype filter */ + for (size_t i = 0; i < filt_len; i++) { + for (size_t n = 0; n < p; n++) { + partitions[n][2 * i + 0] = proto[i * p + n] * scale; + partitions[n][2 * i + 1] = 0.0f; + } + } + + /* Reverse filter taps for convolution */ + for (size_t n = 0; n < p; n++) { + for (size_t i = 0; i < filt_len / 2; i++) { + float a = partitions[n][2 * i]; + float b = partitions[n][2 * (filt_len - 1 - i)]; + + partitions[n][2 * i] = b; + partitions[n][2 * (filt_len - 1 - i)] = a; + } + } + + delete proto; + return true; +} + +void Resampler::releaseFilters() +{ + if (partitions) { + for (size_t i = 0; i < p; i++) + free(partitions[i]); + } + + free(partitions); + partitions = NULL; +} + +bool Resampler::checkLen(size_t in_len, size_t out_len) +{ + if (in_len % q) { +#ifdef DEBUG + std::cerr << "Invalid input length " << in_len + << " is not multiple of " << q << std::endl; +#endif + return false; + } + + if (out_len % p) { +#ifdef DEBUG + std::cerr << "Invalid output length " << out_len + << " is not multiple of " << p << std::endl; +#endif + return false; + } + + if ((in_len / q) != (out_len / p)) { +#ifdef DEBUG + std::cerr << "Input/output block length mismatch" << std::endl; + std::cerr << "P = " << p << ", Q = " << q << std::endl; + std::cerr << "Input len: " << in_len << std::endl; + std::cerr << "Output len: " << out_len << std::endl; +#endif + return false; + } + + if (out_len > path_len) { +#ifdef DEBUG + std::cerr << "Block length of " << out_len + << " exceeds max of " << path_len << std::endl; + std::cerr << "Resizing" << std::endl; +#endif + computePaths(out_len * 2); + } + + return true; +} + +int Resampler::rotate(float *in, size_t in_len, float *out, size_t out_len) +{ + int n, path; + int hist_len = filt_len - 1; + + if (!checkLen(in_len, out_len)) + return -1; + + /* Insert history */ + memcpy(&in[-2 * hist_len], history, hist_len * 2 * sizeof(float)); + + /* Generate output from precomputed input/output paths */ + for (size_t i = 0; i < out_len; i++) { + n = in_index[i]; + path = out_path[i]; + + convolve_real(in, in_len, + partitions[path], filt_len, + &out[2 * i], out_len - i, + n, 1, 1, 0); + } + + /* Save history */ + memcpy(history, &in[2 * (in_len - hist_len)], + hist_len * 2 * sizeof(float)); + + return out_len; +} + +bool Resampler::init(int type, float bw) +{ + size_t hist_len = filt_len - 1; + + /* Filterbank filter internals */ + if (!initFilters(type, bw)) + return false; + + /* History buffer */ + history = new float[2 * hist_len]; + memset(history, 0, 2 * hist_len * sizeof(float)); + + computePaths(path_len); + + return true; +} + +bool Resampler::computePaths(int len) +{ + if (len <= 0) + return false; + + delete in_index; + delete out_path; + + /* Precompute filterbank paths */ + in_index = new size_t[len]; + out_path = new size_t[len]; + + for (int i = 0; i < len; i++) { + in_index[i] = (q * i) / p; + out_path[i] = (q * i) % p; + } + + path_len = len; + + return true; +} + +size_t Resampler::len() +{ + return filt_len; +} + +Resampler::Resampler(size_t p, size_t q, size_t filt_len) + : in_index(NULL), out_path(NULL), partitions(NULL), history(NULL) +{ + this->p = p; + this->q = q; + this->filt_len = filt_len; + this->path_len = MAX_OUTPUT_LEN; +} + +Resampler::~Resampler() +{ + releaseFilters(); + + delete history; + delete in_index; + delete out_path; +} diff --git a/TransceiverUHD/Resampler.h b/TransceiverUHD/Resampler.h new file mode 100644 index 0000000..54ed7f5 --- /dev/null +++ b/TransceiverUHD/Resampler.h @@ -0,0 +1,65 @@ +#ifndef _RESAMPLER_H_ +#define _RESAMPLER_H_ + +class Resampler { +public: + /* Constructor for rational sample rate conversion + * @param p numerator of resampling ratio + * @param q denominator of resampling ratio + * @param filt_len length of each polyphase subfilter + */ + Resampler(size_t p, size_t q, size_t filt_len = 16); + ~Resampler(); + + /* Initilize resampler filterbank. + * @param bw bandwidth factor on filter generation (pre-window) + * @return false on error, zero otherwise + * + * Automatic setting is to compute the filter to prevent aliasing with + * a Blackman-Harris window. Adjustment is made through a bandwith + * factor to shift the cutoff and/or the constituent filter lengths. + * Calculation of specific rolloff factors or 3-dB cutoff points is + * left as an excersize for the reader. + */ + bool init(int type = FILTER_TYPE_SINC, float bw = 1.0f); + + /* Rotate "commutator" and drive samples through filterbank + * @param in continuous buffer of input complex float values + * @param in_len input buffer length + * @param out continuous buffer of output complex float values + * @param out_len output buffer length + * @return number of samples outputted, negative on error + * + * Input and output vector lengths must of be equal multiples of the + * rational conversion rate denominator and numerator respectively. + */ + int rotate(float *in, size_t in_len, float *out, size_t out_len); + + /* Get filter length + * @return number of taps in each filter partition + */ + size_t len(); + + enum { + FILTER_TYPE_SINC, + FILTER_TYPE_RRC, + }; + +private: + size_t p; + size_t q; + size_t filt_len; + size_t path_len; + size_t *in_index; + size_t *out_path; + + float **partitions; + float *history; + + bool initFilters(int type, float bw); + void releaseFilters(); + bool computePaths(int len); + bool checkLen(size_t in_len, size_t out_len); +}; + +#endif /* _RESAMPLER_H_ */ diff --git a/TransceiverUHD/SampleBuffer.cpp b/TransceiverUHD/SampleBuffer.cpp new file mode 100644 index 0000000..241c88f --- /dev/null +++ b/TransceiverUHD/SampleBuffer.cpp @@ -0,0 +1,169 @@ +/* + * Timestamped ring buffer implementation + * Written by Tom Tsou + * + * Copyright 2010-2011 Free Software Foundation, Inc. + * Copyright 2014 Ettus Research LLC + * + * 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 . + * See the COPYING file in the main directory for details. + */ + +#include +#include "SampleBuffer.h" + +SampleBuffer::SampleBuffer(int len, double rate) + : clock_rate(rate), time_start(0), time_end(0), + data_start(0), data_end(0) +{ + this->len = len; + data = new std::complex[len]; +} + +SampleBuffer::~SampleBuffer() +{ + delete[] data; +} + +int SampleBuffer::avail_smpls(long long ts) const +{ + if (ts < time_start) + return ERROR_TIMESTAMP; + else if (ts >= time_end) + return 0; + else + return time_end - ts; +} + +int SampleBuffer::avail_smpls(uhd::time_spec_t ts) const +{ + return avail_smpls(ts.to_ticks(clock_rate)); +} + +int SampleBuffer::read(void *buf, int len, long long ts) +{ + int type_size = sizeof(std::complex); + + /* Check for valid read */ + if (ts < time_start) + return ERROR_TIMESTAMP; + if (ts >= time_end) + return 0; + if (len >= this->len) + return ERROR_READ; + + /* How many samples should be copied */ + int num_smpls = time_end - ts; + if (num_smpls > len) + num_smpls = len; + + /* Starting index */ + int read_start = (data_start + (ts - time_start)) % this->len; + + /* Read it */ + if (read_start + num_smpls < this->len) { + int numBytes = len * type_size; + memcpy(buf, data + read_start, numBytes); + } else { + int first_cp = (this->len - read_start) * type_size; + int second_cp = len * type_size - first_cp; + + memcpy(buf, data + read_start, first_cp); + memcpy((char*) buf + first_cp, data, second_cp); + } + + data_start = (read_start + len) % this->len; + time_start = ts + len; + + if (time_start > time_end) + return ERROR_READ; + else + return num_smpls; +} + +int SampleBuffer::read(void *buf, int len, uhd::time_spec_t ts) +{ + return read(buf, len, ts.to_ticks(clock_rate)); +} + +int SampleBuffer::write(void *buf, int len, long long ts) +{ + int type_size = sizeof(std::complex); + + /* Check for valid write */ + if ((len == 0) || (len >= this->len)) + return ERROR_WRITE; + if ((ts + len) <= time_end) + return ERROR_TIMESTAMP; + + /* Starting index */ + int write_start = (data_start + (ts - time_start)) % this->len; + + /* Write it */ + if ((write_start + len) < this->len) { + int numBytes = len * type_size; + memcpy(data + write_start, buf, numBytes); + } else { + int first_cp = (this->len - write_start) * type_size; + int second_cp = len * type_size - first_cp; + + memcpy(data + write_start, buf, first_cp); + memcpy(data, (char*) buf + first_cp, second_cp); + } + + data_end = (write_start + len) % this->len; + time_end = ts + len; + + if (((write_start + len) > this->len) && (data_end > data_start)) + return ERROR_OVERFLOW; + else if (time_end <= time_start) + return ERROR_WRITE; + else + return len; +} + +int SampleBuffer::write(void *buf, int len, uhd::time_spec_t ts) +{ + return write(buf, len, ts.to_ticks(clock_rate)); +} + +std::string SampleBuffer::str_status(long long ts) const +{ + std::ostringstream ost("Sample buffer: "); + + ost << "ts = " << ts; + ost << ", len = " << this->len; + ost << ", time_start = " << time_start; + ost << ", time_end = " << time_end; + ost << ", data_start = " << data_start; + ost << ", data_end = " << data_end; + + return ost.str(); +} + +std::string SampleBuffer::str_code(int code) +{ + switch (code) { + case ERROR_TIMESTAMP: + return "Sample buffer: Requested timestamp not valid"; + case ERROR_READ: + return "Sample buffer: Read error"; + case ERROR_WRITE: + return "Sample buffer: Write error"; + case ERROR_OVERFLOW: + return "Sample buffer: Overrun"; + default: + return "Sample buffer: Unknown error"; + } +} diff --git a/TransceiverUHD/SampleBuffer.h b/TransceiverUHD/SampleBuffer.h new file mode 100644 index 0000000..e3852f7 --- /dev/null +++ b/TransceiverUHD/SampleBuffer.h @@ -0,0 +1,53 @@ +#ifndef UHD_BUFFER_H +#define UHD_BUFFER_H + +#include +#include +#include + +/* + * Sample Buffer + * + * Allows reading and writing of timed samples using sample ticks or UHD + * timespec values. + */ + +class SampleBuffer { +public: + SampleBuffer(int len, double rate); + ~SampleBuffer(); + + /* Return number of samples available for a given ts */ + int avail_smpls(long long ts) const; + int avail_smpls(uhd::time_spec_t ts) const; + + int read(void *buf, int len, long long ts); + int read(void *buf, int len, uhd::time_spec_t ts); + int write(void *buf, int len, long long ts); + int write(void *buf, int len, uhd::time_spec_t ts); + + /* Return formatted string describing internal buffer state */ + std::string str_status(long long ts) const; + + /* Formatted error code string */ + static std::string str_code(int code); + + enum err_code { + ERROR_TIMESTAMP = -1, + ERROR_READ = -2, + ERROR_WRITE = -3, + ERROR_OVERFLOW = -4 + }; + +private: + std::complex *data; + int len; + double clock_rate; + + long long time_start; + long long time_end; + long long data_start; + long long data_end; +}; + +#endif /* UHD_BUFFER_H */ diff --git a/TransceiverUHD/Transceiver.cpp b/TransceiverUHD/Transceiver.cpp new file mode 100644 index 0000000..f807eab --- /dev/null +++ b/TransceiverUHD/Transceiver.cpp @@ -0,0 +1,487 @@ +/* + * OpenBTS provides an open source alternative to legacy telco protocols and + * traditionally complex, proprietary hardware systems. + * + * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. + * Copyright 2014 Range Networks, Inc. + * Copyright 2014 Ettus Research LLC + * + * This software is distributed under the terms of the GNU General Public + * License version 3. See the COPYING and NOTICE files in the current + * directory for licensing information. + * + * This use of this software may be subject to additional restrictions. + * See the LEGAL file in the main directory for details. + */ + +#include +#include +#include "Transceiver.h" + +/* Clock indication reporting interval in frames */ +#define CLK_IND_INTERVAL 100 + +/* Default attenuation value in dB */ +#define DEFAULT_ATTEN 20 + +Transceiver::Transceiver(int wBasePort, + const char *wTRXAddress, + UMTS::Time wTransmitLatency, + RadioInterface *wRadioInterface) + : mDataSocket(wBasePort + 2, wTRXAddress, wBasePort + 102), + mControlSocket(wBasePort + 1, wTRXAddress, wBasePort + 101), + mClockSocket(wBasePort, wTRXAddress, wBasePort + 100), + mTxServiceLoopThread(NULL), mRxServiceLoopThread(NULL), + mTransmitPriorityQueueServiceLoopThread(NULL), + mControlServiceLoopThread(NULL), mOn(false), mPower(DEFAULT_ATTEN), + mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface) +{ + signalVector emptyVector(UMTS::gSlotLen); + UMTS::Time emptyTime(0, 0); + mEmptyTransmitBurst = new radioVector((const signalVector&) emptyVector, + (UMTS::Time&) emptyTime); +} + +Transceiver::~Transceiver() +{ + stop(); + + if (mControlServiceLoopThread) { + mControlServiceLoopThread->cancel(); + mControlServiceLoopThread->join(); + delete mControlServiceLoopThread; + } + + delete mEmptyTransmitBurst; +} + +/* + * Initialize transceiver + * + * Start or restart the control loop. Any further control is handled through the + * socket API. Randomize the central radio clock set the downlink burst + * counters. Note that the clock will not update until the radio starts, but we + * are still expected to report clock indications through control channel + * activity. + */ +void Transceiver::init(int wDelaySpread) +{ + if (wDelaySpread < 0) + wDelaySpread = 0; + + stop(); + + if (mControlServiceLoopThread) { + mControlServiceLoopThread->cancel(); + mControlServiceLoopThread->join(); + delete mControlServiceLoopThread; + } + + UMTS::Time time(random() % 1024, 0); + mRadioInterface->getClock()->set(time); + mTransmitDeadlineClock = time; + mLastClockUpdateTime = time; + mLatencyUpdateTime = time; + + mDelaySpread = wDelaySpread; + mPower = mRadioInterface->setPowerAttenuation(mPower); + + mControlServiceLoopThread = new Thread(32768); + mControlServiceLoopThread->start((void * (*)(void*)) + ControlServiceLoopAdapter, (void*) this); +} + +/* + * Start the transceiver + * + * Submit command(s) to the radio device to commence streaming samples and + * launch threads to handle sample I/O. Re-synchronize the transmit burst + * counters to the central radio clock here as well. Perform locking because the + * stop request can occur simultaneously (i.e. shutdown). + */ +bool Transceiver::start() +{ + ScopedLock lock(mLock); + + if (mOn) { + LOG(ERR) << "Transceiver already running"; + return false; + } + + LOG(NOTICE) << "Starting the transceiver"; + + UMTS::Time time = mRadioInterface->getClock()->get(); + mTransmitDeadlineClock = time; + mLastClockUpdateTime = time; + mLatencyUpdateTime = time; + + if (!mRadioInterface->start()) { + LOG(ALERT) << "Device failed to start"; + return false; + } + + /* Device is running - launch I/O threads */ + mTxServiceLoopThread = new Thread(8 * 32768); + mRxServiceLoopThread = new Thread(8 * 32768); + mTransmitPriorityQueueServiceLoopThread = new Thread(8 * 32768); + + mTxServiceLoopThread->start((void * (*)(void*)) + TxServiceLoopAdapter, (void*) this); + mRxServiceLoopThread->start((void * (*)(void*)) + RxServiceLoopAdapter, (void*) this); + mTransmitPriorityQueueServiceLoopThread->start((void * (*)(void*)) + TransmitPriorityQueueServiceLoopAdapter, (void*) this); + writeClockInterface(); + + mOn = true; + LOG(NOTICE) << "Transceiver running"; + return true; +} + +/* + * Stop the transceiver + * + * Perform stopping by disabling receive streaming and issuing cancellation + * requests to running threads. Threads will timeout and terminate at the + * cancellation points once the device is disabled. Perform locking since the + * stop request can come from the destructor (i.e. shutdown) in addition to the + * control thread. + */ +void Transceiver::stop() +{ + ScopedLock lock(mLock); + + if (!mOn) + return; + + LOG(NOTICE) << "Stopping the transceiver"; + mTxServiceLoopThread->cancel(); + mRxServiceLoopThread->cancel(); + mTransmitPriorityQueueServiceLoopThread->cancel(); + + LOG(INFO) << "Stopping the device"; + mRadioInterface->stop(); + + LOG(INFO) << "Terminating threads"; + mRxServiceLoopThread->join(); + mTxServiceLoopThread->join(); + mTransmitPriorityQueueServiceLoopThread->join(); + + delete mTxServiceLoopThread; + delete mRxServiceLoopThread; + delete mTransmitPriorityQueueServiceLoopThread; + + mTransmitPriorityQueue.clear(); + + mOn = false; + LOG(NOTICE) << "Transceiver stopped"; +} + +void Transceiver::addRadioVector(signalVector &burst, UMTS::Time &wTime) +{ + // modulate and stick into queue + radioVector *vec = new radioVector(burst, wTime); + RN_MEMLOG(radioVector, vec); + mTransmitPriorityQueue.write(vec); +} + +void Transceiver::pushRadioVector(UMTS::Time &now) +{ + radioVector *stale, *next; + + // dump stale bursts, if any + while ((stale = mTransmitPriorityQueue.getStaleBurst(now))) { + LOG(NOTICE) << "dumping STALE burst in TRX->USRP interface burst:" + << stale->time() << " now:" << now; + writeClockInterface(); + delete stale; + } + + // if queue contains data at the desired timestamp, stick it into FIFO + if ((next = (radioVector*) mTransmitPriorityQueue.getCurrentBurst(now))) { + mRadioInterface->driveTransmitRadio(*(next), false); + delete next; + return; + } + + // Extremely rare that we get here. We need to send a blank burst to the + // radio interface to update the timestamp. + LOG(INFO) << "Sending empty burst at " << now; + mRadioInterface->driveTransmitRadio(*(mEmptyTransmitBurst), true); +} + +void Transceiver::reset() +{ + mTransmitPriorityQueue.clear(); +} + +void Transceiver::driveControl() +{ + int MAX_PACKET_LENGTH = 100; + + // check control socket + char buffer[MAX_PACKET_LENGTH]; + int msgLen = -1; + buffer[0] = '\0'; + + msgLen = mControlSocket.read(buffer, 1000); + + if (msgLen < 1) + return; + + char cmdcheck[4]; + char command[MAX_PACKET_LENGTH]; + char response[MAX_PACKET_LENGTH]; + + sscanf(buffer, "%3s %s", cmdcheck, command); + + writeClockInterface(); + + if (strcmp(cmdcheck, "CMD") != 0) { + LOG(WARNING) << "bogus message on control interface"; + return; + } + LOG(NOTICE) << "command is " << buffer; + + if (!strcmp(command, "POWEROFF")) { + stop(); + sprintf(response, "RSP POWEROFF 0"); + } + else if (!strcmp(command, "POWERON")) { + if (!start()) + sprintf(response, "RSP POWERON 1"); + else + sprintf(response, "RSP POWERON 0"); + } + else if (!strcmp(command, "SETRXGAIN")) { + int newGain; + sscanf(buffer, "%3s %s %d", cmdcheck, command, &newGain); + newGain = mRadioInterface->setRxGain(newGain); + sprintf(response, "RSP SETRXGAIN 0 %d", newGain); + } + else if (!strcmp(command, "SETTXATTEN")) { + int power; + sscanf(buffer, "%3s %s %d", cmdcheck, command, &power); + mPower = mRadioInterface->setPowerAttenuation(power); + sprintf(response, "RSP SETTXATTEN 0 %d", mPower); + } + else if (!strcmp(command, "SETFREQOFFSET")) { + sprintf(response, "RSP SETFREQOFFSET 1"); + } + else if (!strcmp(command, "SETPOWER")) { + int power; + sscanf(buffer, "%3s %s %d", cmdcheck, command, &power); + mPower = mRadioInterface->setPowerAttenuation(power); + sprintf(response, "RSP SETPOWER 0 %d", mPower); + } + else if (!strcmp(command, "ADJPOWER")) { + int step; + sscanf(buffer, "%3s %s %d", cmdcheck, command, &step); + mPower = mRadioInterface->setPowerAttenuation(mPower + step); + sprintf(response, "RSP ADJPOWER 0 %d", mPower); + } + else if (!strcmp(command, "RXTUNE")) { + int freqkhz; + sscanf(buffer, "%3s %s %d", cmdcheck, command, &freqkhz); + if (!mRadioInterface->tuneRx(freqkhz * 1e3)) { + LOG(ALERT) << "RX failed to tune"; + sprintf(response, "RSP RXTUNE 1 %d", freqkhz); + } + else + sprintf(response, "RSP RXTUNE 0 %d", freqkhz); + } + else if (!strcmp(command, "TXTUNE")) { + int freqkhz; + sscanf(buffer, "%3s %s %d", cmdcheck, command, &freqkhz); + if (!mRadioInterface->tuneTx(freqkhz * 1e3)) { + LOG(ALERT) << "TX failed to tune"; + sprintf(response, "RSP TXTUNE 1 %d", freqkhz); + } + else + sprintf(response, "RSP TXTUNE 0 %d", freqkhz); + } + else if (!strcmp(command, "SETFREQOFFSET")) { + sprintf(response, "RSP SETFREQOFFSET 1"); + } + else { + LOG(WARNING) << "bogus command " << command << " on control interface."; + } + + LOG(DEBUG) << "response: " << response; + mControlSocket.write(response, strlen(response) + 1); +} + +bool Transceiver::driveTransmitPriorityQueue() +{ + char buffer[MAX_UDP_LENGTH]; + + // check data socket + int msgLen = mDataSocket.read(buffer, 1000); + if (msgLen < 0) + return false; + + if (msgLen != 2 * UMTS::gSlotLen + 4) { + LOG(ERR) << "badly formatted packet on UMTS->TRX interface"; + return false; + } + + int timeSlot = (int) buffer[0]; + uint16_t frameNum = 0; + for (int i = 0; i < 2; i++) + frameNum = (frameNum << 8) | (0x0ff & buffer[i + 1]); + + static signalVector newBurst(UMTS::gSlotLen); + signalVector::iterator itr = newBurst.begin(); + signed char *bufferItr = (signed char *) (buffer + 3); + + while (itr < newBurst.end()) { + *itr++ = complex((float) *(bufferItr + 0), + (float) *(bufferItr + 1)); + bufferItr++; + bufferItr++; + } + + UMTS::Time currTime = UMTS::Time(frameNum, timeSlot); + addRadioVector(newBurst, currTime); + + return true; +} + +void Transceiver::driveReceiveFIFO() +{ + radioVector *rxBurst = NULL; + int RSSI = 0; + UMTS::Time burstTime; + + mRadioInterface->driveReceiveRadio(1024 + mDelaySpread); + + rxBurst = (radioVector *) mReceiveFIFO->get(); + if (!rxBurst) + return; + + burstTime = rxBurst->time(); + size_t burstSize = 2 * rxBurst->size() + 3 + 1 + 1; + char burstString[burstSize]; + + burstString[0] = burstTime.TN(); + for (size_t i = 0; i < 2; i++) + burstString[1 + i] = (burstTime.FN() >> ((1 - i) * 8)) & 0x0ff; + + burstString[3] = RSSI; + radioVector::iterator burstItr = rxBurst->begin(); + char *burstPtr = burstString + 4; + + for (size_t i = 0; i < UMTS::gSlotLen + 1024 + mDelaySpread; i++) { + *burstPtr++ = (char) (int8_t) burstItr->real(); + *burstPtr++ = (char) -(int8_t) burstItr->imag(); + burstItr++; + } + + burstString[burstSize - 1] = '\0'; + delete rxBurst; + + if (!burstTime.TN() && !(burstTime.FN() % CLK_IND_INTERVAL)) + writeClockInterface(); + + mDataSocket.write(burstString, burstSize); +} + +/* + * 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 Transceiver::driveTransmitFIFO() +{ + RadioClock *radioClock = mRadioInterface->getClock(); + + if (!mOn) + return; + + radioClock->wait(); + + while (radioClock->get() + mTransmitLatency > mTransmitDeadlineClock) { + // if underrun, then we're not providing bursts to radio/USRP fast + // enough. Need to increase latency by one UMTS frame. + if (mRadioInterface->getWindowType() == RadioDevice::TX_WINDOW_USRP1) { + if (mRadioInterface->isUnderrun()) { + // only do latency update every 10 frames, so we don't over update + if (radioClock->get() > mLatencyUpdateTime + UMTS::Time(10, 0)) { + mTransmitLatency = mTransmitLatency + UMTS::Time(1, 0); + if (mTransmitLatency > UMTS::Time(15, 0)) + mTransmitLatency = UMTS::Time(15, 0); + + LOG(INFO) << "new latency: " << mTransmitLatency; + mLatencyUpdateTime = radioClock->get(); + } + } else { + // if underrun hasn't occurred in the last sec (100 frames) drop + // transmit latency by a timeslot + if (mTransmitLatency > UMTS::Time(1, 0)) { + if (radioClock->get() > mLatencyUpdateTime + UMTS::Time(100, 0)) { + mTransmitLatency.decTN(); + LOG(INFO) << "reduced latency: " << mTransmitLatency; + mLatencyUpdateTime = radioClock->get(); + } + } + } + } + + // time to push burst to transmit FIFO + pushRadioVector(mTransmitDeadlineClock); + mTransmitDeadlineClock.incTN(); + } +} + +void Transceiver::writeClockInterface() +{ + char command[50]; + + sprintf(command, "IND CLOCK %llu", + (unsigned long long) (mTransmitDeadlineClock.FN() + 8)); + + LOG(INFO) << "ClockInterface: sending " << command; + + mClockSocket.write(command, strlen(command) + 1); + mLastClockUpdateTime = mTransmitDeadlineClock; +} + +void *RxServiceLoopAdapter(Transceiver *transceiver) +{ + while (1) { + transceiver->driveReceiveFIFO(); + pthread_testcancel(); + } + return NULL; +} + +void *TxServiceLoopAdapter(Transceiver *transceiver) +{ + while (1) { + transceiver->driveTransmitFIFO(); + pthread_testcancel(); + } + return NULL; +} + +void *ControlServiceLoopAdapter(Transceiver *transceiver) +{ + while (1) { + transceiver->driveControl(); + pthread_testcancel(); + } + return NULL; +} + +void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *transceiver) +{ + while (1) { + transceiver->driveTransmitPriorityQueue(); + pthread_testcancel(); + } + return NULL; +} diff --git a/TransceiverUHD/Transceiver.h b/TransceiverUHD/Transceiver.h new file mode 100644 index 0000000..55e925e --- /dev/null +++ b/TransceiverUHD/Transceiver.h @@ -0,0 +1,121 @@ +#ifndef TRANSCEIVER_H +#define TRANSCEIVER_H + +#include "RadioInterface.h" +#include "Interthread.h" +#include "UMTSCommon.h" +#include "Sockets.h" + +#include +#include + +/** The Transceiver class, responsible for physical layer of basestation */ +class Transceiver { +private: + UDPSocket mDataSocket; ///< socket for writing to/reading from UMTS core + UDPSocket mControlSocket; ///< socket for writing/reading control commands from UMTS core + UDPSocket mClockSocket; ///< socket for writing clock updates to UMTS core + + VectorQueue mTransmitPriorityQueue; ///< priority queue of transmit bursts received from UMTS core + VectorFIFO* mTransmitFIFO; ///< radioInterface FIFO of transmit bursts + VectorFIFO* mReceiveFIFO; ///< radioInterface FIFO of receive bursts + + RadioBurstFIFO mT; + RadioBurstFIFO mR; + + Thread *mTxServiceLoopThread; ///< thread to push/pull bursts into transmit/receive FIFO + Thread *mRxServiceLoopThread; ///< thread to push/pull bursts into transmit/receive FIFO + Thread *mTransmitPriorityQueueServiceLoopThread;///< thread to process transmit bursts from UMTS core + Thread *mControlServiceLoopThread; ///< thread to process control messages from UMTS core + + bool mOn; ///< flag to indicate that transceiver is powered on + int mPower; ///< the transmit power in dB + int mDelaySpread; ///< maximum expected delay spread, i.e. extend buffer when sending upstream + + UMTS::Time mTransmitLatency; ///< latency between basestation clock and transmit deadline clock + UMTS::Time mLatencyUpdateTime; ///< last time latency was updated + + UMTS::Time mTransmitDeadlineClock; ///< deadline for pushing bursts into transmit FIFO + UMTS::Time mLastClockUpdateTime; ///< last time clock update was sent up to core + radioVector *mEmptyTransmitBurst; + + RadioInterface *mRadioInterface; ///< associated radioInterface object + + /** modulate and add a burst to the transmit queue */ + void addRadioVector(signalVector &burst, UMTS::Time &wTime); + + /** Push modulated burst into transmit FIFO corresponding to a particular timestamp */ + void pushRadioVector(UMTS::Time &nowTime); + + /** send messages over the clock socket */ + void writeClockInterface(void); + + /** Start and stop I/O threads through the control socket API */ + bool start(); + void stop(); + + /** Protect destructor accessable stop call */ + Mutex mLock; + +public: + /** Transceiver constructor + @param wBasePort base port number of UDP sockets + @param TRXAddress IP address of the TRX manager, as a string + @param wTransmitLatency initial setting of transmit latency + @param radioInterface associated radioInterface object + */ + Transceiver(int wBasePort, + const char *TRXAddress, + UMTS::Time wTransmitLatency, + RadioInterface *wRadioInterface); + + /** Destructor */ + ~Transceiver(); + + /** Start the control loop */ + void init(int wDelaySpread); + + /** attach the radioInterface receive FIFO */ + void receiveFIFO(VectorFIFO *wFIFO) { mReceiveFIFO = wFIFO; } + + /** attach the radioInterface transmit FIFO */ + void transmitFIFO(VectorFIFO *wFIFO) { mTransmitFIFO = wFIFO; } + + RadioBurstFIFO* highSideTransmitFIFO(void) { return &mT; } + RadioBurstFIFO* highSideReceiveFIFO(void) { return &mR; } + +protected: + /** drive reception and demodulation of UMTS bursts */ + void driveReceiveFIFO(); + + /** drive transmission of UMTS bursts */ + void driveTransmitFIFO(); + + /** drive handling of control messages from UMTS core */ + void driveControl(); + + /** + drive modulation and sorting of UMTS bursts from UMTS core + @return true if a burst was transferred successfully + */ + bool driveTransmitPriorityQueue(); + + friend void *TxServiceLoopAdapter(Transceiver *); + friend void *RxServiceLoopAdapter(Transceiver *); + friend void *ControlServiceLoopAdapter(Transceiver *); + friend void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *); + + void reset(); +}; + +/** FIFO thread loop */ +void *TxServiceLoopAdapter(Transceiver *); +void *RxServiceLoopAdapter(Transceiver *); + +/** control message handler thread loop */ +void *ControlServiceLoopAdapter(Transceiver *); + +/** transmit queueing thread loop */ +void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *); + +#endif /* TRANSCEIVER_H */ diff --git a/TransceiverUHD/UHDDevice.cpp b/TransceiverUHD/UHDDevice.cpp new file mode 100644 index 0000000..454d036 --- /dev/null +++ b/TransceiverUHD/UHDDevice.cpp @@ -0,0 +1,729 @@ +/* + * Device support for Ettus Research UHD driver + * Written by Tom Tsou + * + * Copyright 2010-2011 Free Software Foundation, Inc. + * Copyright 2014 Ettus Research LLC + * + * 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 . + * See the COPYING file in the main directory for details. + */ + +#include +#include +#include +#include + +#include "Threads.h" +#include "Logger.h" +#include "UHDDevice.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* + * Transmit packet synchronization count + * + * During device start and periods of underrun recovery, we need to prevent + * flooding the device with buffered late packets. This parameter is the number + * of packets to remove from the beginning of a transmit stream so that we get + * close to an on-time packet arrival. + */ +#define TX_PACKET_SYNC 30 + +/* + * B200/B210 FPGA clocking rate + * + * Rate derived from the fixed 100 MHz N200 clocking rate. We use a device + * sample rate of 6.25 Msps, or approximately 1.63 samples-per-chip based on the + * 3.84 Mcps UMTS rate. This provides a balance of sufficient filtering and + * device compatibility. + */ +#define B2XX_CLK_RT 25e6 + +/* + * Device clocking offset limit + * + * Error if the actual device clocking rate differs from the requested rate by + * more than this limit. + */ +#define MASTER_CLK_LIMIT 10.0 + +/* Timestamped ring buffer size */ +#define SAMPLE_BUF_SZ (1 << 20) + +struct uhd_dev_offset { + enum uhd_dev_type type; + int offset; +}; + +/* + * Tx/Rx sample offset values + * + * Timing adjustment in samples. This value accounts is empiracally measured + * and accounts for overall group delay digital filters and analog components. + */ +static struct uhd_dev_offset uhd_offsets[NUM_USRP_TYPES] = { + { USRP1, 0 }, + { USRP2, 61 }, + { B100, 0 }, + { B2XX, 99 }, + { X300, 73 }, + { UMTRX, 0 }, +}; + +static int get_dev_offset(enum uhd_dev_type type) +{ + if ((type != B2XX) && (type != USRP2) && (type != X300)) { + LOG(ALERT) << "Unsupported device type"; + return 0; + } + + for (int i = 0; i < NUM_USRP_TYPES; i++) { + if (type == uhd_offsets[i].type) + return uhd_offsets[i].offset; + } + + return 0; +} + +static void *async_event_loop(UHDDevice *dev) +{ + while (1) { + dev->recv_async_msg(); + pthread_testcancel(); + } + + return NULL; +} + +/* + * Catch and drop underrun 'U' and overrun 'O' messages from stdout + * since we already report using the logging facility. Direct + * everything else appropriately. + */ +void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg) +{ + switch (type) { + case uhd::msg::status: + LOG(INFO) << msg; + break; + case uhd::msg::warning: + LOG(WARNING) << msg; + break; + case uhd::msg::error: + LOG(ERR) << msg; + break; + case uhd::msg::fastpath: + break; + } +} + +static void thread_enable_cancel(bool cancel) +{ + cancel ? pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) : + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); +} + +UHDDevice::UHDDevice(double rate) + : tx_gain_min(0.0), tx_gain_max(0.0), + rx_gain_min(0.0), rx_gain_max(0.0), + tx_rate(rate), rx_rate(rate), tx_freq(0.0), rx_freq(0.0), + started(false), aligned(false), rx_pkt_cnt(0), drop_cnt(0), + prev_ts(0), ts_offset(0), rx_buffer(NULL) +{ +} + +UHDDevice::~UHDDevice() +{ + stop(); + delete rx_buffer; +} + +void UHDDevice::init_gains() +{ + uhd::gain_range_t range; + + range = usrp_dev->get_tx_gain_range(); + tx_gain_min = range.start(); + tx_gain_max = range.stop(); + + range = usrp_dev->get_rx_gain_range(); + 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); + + return; +} + +int UHDDevice::set_master_clk(double clk_rate) +{ + double actual, offset, limit = 10.0; + + try { + usrp_dev->set_master_clock_rate(clk_rate); + } catch (const std::exception &ex) { + LOG(ALERT) << "UHD clock rate setting failed: " << clk_rate; + LOG(ALERT) << ex.what(); + return -1; + } + + actual = usrp_dev->get_master_clock_rate(); + offset = fabs(clk_rate - actual); + + if (offset > limit) { + LOG(ALERT) << "Failed to set master clock rate"; + LOG(ALERT) << "Requested clock rate " << clk_rate; + LOG(ALERT) << "Actual clock rate " << actual; + return -1; + } + + return 0; +} + +int UHDDevice::set_rates(double tx_rate, double rx_rate) +{ + double offset_limit = 1.0; + double tx_offset, rx_offset; + + /* B2XX is the only device where we set FPGA clocking */ + if (dev_type == B2XX) { + if (set_master_clk(B2XX_CLK_RT) < 0) + return -1; + } + + try { + usrp_dev->set_tx_rate(tx_rate); + usrp_dev->set_rx_rate(rx_rate); + } catch (const std::exception &ex) { + LOG(ALERT) << "UHD rate setting failed"; + LOG(ALERT) << ex.what(); + return -1; + } + this->tx_rate = usrp_dev->get_tx_rate(); + this->rx_rate = usrp_dev->get_rx_rate(); + + tx_offset = fabs(this->tx_rate - tx_rate); + rx_offset = fabs(this->rx_rate - rx_rate); + if ((tx_offset > offset_limit) || (rx_offset > offset_limit)) { + LOG(ALERT) << "Actual sample rate differs from desired rate"; + LOG(ALERT) << "Tx/Rx (" << this->tx_rate << "/" + << this->rx_rate << ")" << std::endl; + return -1; + } + + return 0; +} + +double UHDDevice::setTxGain(double db) +{ + usrp_dev->set_tx_gain(db); + double tx_gain = usrp_dev->get_tx_gain(); + + LOG(INFO) << "Set TX gain to " << tx_gain << "dB"; + + return tx_gain; +} + +double UHDDevice::setRxGain(double db) +{ + usrp_dev->set_rx_gain(db); + double rx_gain = usrp_dev->get_rx_gain(); + + LOG(INFO) << "Set RX gain to " << rx_gain << "dB"; + + return rx_gain; +} + +/* + * Parse the UHD device tree and mboard name to find out what device we're + * 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. + */ +bool UHDDevice::parse_dev_type() +{ + std::string mboard_str, dev_str; + uhd::property_tree::sptr prop_tree; + size_t usrp2_str, b200_str, b210_str, x300_str, x310_str; + + prop_tree = usrp_dev->get_device()->get_tree(); + dev_str = prop_tree->access("/name").get(); + mboard_str = usrp_dev->get_mboard_name(); + + usrp2_str = dev_str.find("USRP2"); + b200_str = mboard_str.find("B200"); + b210_str = mboard_str.find("B210"); + x300_str = mboard_str.find("X300"); + x310_str = mboard_str.find("X310"); + + if (b200_str != std::string::npos) { + dev_type = B2XX; + } else if (b210_str != std::string::npos) { + dev_type = B2XX; + } else if (usrp2_str != std::string::npos) { + dev_type = USRP2; + } else if (x300_str != std::string::npos) { + dev_type = X300; + } else if (x310_str != std::string::npos) { + dev_type = X300; + } else { + goto nosupport; + } + + tx_window = TX_WINDOW_FIXED; + LOG(INFO) << "Using fixed transmit window for " + << dev_str << " " << mboard_str; + return true; + +nosupport: + LOG(ALERT) << "Device not supported by OpenBTS-UMTS"; + return false; +} + +bool UHDDevice::open(const std::string &args, bool extref) +{ + /* Find UHD devices */ + uhd::device_addr_t addr(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; + } + + /* Use the first found device */ + LOG(INFO) << "Using discovered UHD device " << dev_addrs[0].to_string(); + try { + usrp_dev = uhd::usrp::multi_usrp::make(dev_addrs[0]); + } catch(...) { + LOG(ALERT) << "UHD make failed, device " << dev_addrs[0].to_string(); + return false; + } + + /* Check for a valid device type and set bus type */ + if (!parse_dev_type()) + return false; + + if (extref) + usrp_dev->set_clock_source("external"); + + /* Create TX and RX streamers */ + uhd::stream_args_t stream_args("sc16"); + 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(); + + set_rates(tx_rate, rx_rate); + + /* Create receive buffer */ + size_t buf_len = SAMPLE_BUF_SZ / sizeof(uint32_t); + rx_buffer = new SampleBuffer(buf_len, rx_rate); + + /* Set receive chain sample offset */ + ts_offset = get_dev_offset(dev_type); + + /* Initialize and shadow gain values */ + init_gains(); + + LOG(INFO) << "\n" << usrp_dev->get_pp_string(); + + return true; +} + +bool UHDDevice::flush_recv(size_t num_pkts) +{ + uhd::rx_metadata_t md; + size_t num_smpls, chans = 1; + float timeout = 0.5f; + + std::vector > + pkt_bufs(chans, std::vector(2 * rx_spp)); + + std::vector pkt_ptrs; + for (size_t i = 0; i < pkt_bufs.size(); i++) + pkt_ptrs.push_back(&pkt_bufs[i].front()); + + ts_initial = 0; + while (!ts_initial || (num_pkts-- > 0)) { + num_smpls = rx_stream->recv(pkt_ptrs, rx_spp, md, + timeout, true); + if (!num_smpls) { + switch (md.error_code) { + case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: + LOG(ALERT) << "Device timed out"; + return false; + default: + continue; + } + } + + ts_initial = md.time_spec.to_ticks(rx_rate); + } + + LOG(INFO) << "Initial timestamp " << ts_initial << std::endl; + + return true; +} + +bool UHDDevice::restart() +{ + double delay = 0.1; + + aligned = false; + + uhd::time_spec_t current = usrp_dev->get_time_now(); + + uhd::stream_cmd_t cmd = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS; + cmd.stream_now = false; + cmd.time_spec = uhd::time_spec_t(current.get_real_secs() + delay); + + usrp_dev->issue_stream_cmd(cmd); + + return flush_recv(10); +} + +bool UHDDevice::start() +{ + LOG(INFO) << "Starting USRP..."; + + if (started) { + LOG(ERR) << "Device already running"; + return false; + } + + setPriority(); + + /* Register msg handler */ + uhd::msg::register_handler(&uhd_msg_handler); + + /* Start receive streaming */ + if (!restart()) + return false; + + /* Start asynchronous event (underrun check) loop */ + async_event_thrd = new Thread(); + async_event_thrd->start((void * (*)(void*))async_event_loop, (void*) this); + + /* Display USRP time */ + double time_now = usrp_dev->get_time_now().get_real_secs(); + LOG(INFO) << "The current time is " << time_now << " seconds"; + + started = true; + return true; +} + +/* + * Stop the UHD device + * + * Issue a stop streaming command and cancel the asynchronous message loop. + * Deallocate the asynchronous thread object since we can't reuse it anyways. + * A new thread object is allocated on device start. + */ +bool UHDDevice::stop() +{ + if (!started) { + LOG(ERR) << "Device not running"; + return false; + } + + uhd::stream_cmd_t stream_cmd = + uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS; + + usrp_dev->issue_stream_cmd(stream_cmd); + + async_event_thrd->cancel(); + async_event_thrd->join(); + delete async_event_thrd; + + started = false; + return true; +} + +void UHDDevice::setPriority() +{ + uhd::set_thread_priority_safe(); + return; +} + +int UHDDevice::check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls) +{ + long long ts; + + if (!num_smpls) { + LOG(ERR) << str_code(md); + + switch (md.error_code) { + case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: + LOG(ALERT) << "UHD: Receive timed out"; + case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW: + case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND: + case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN: + case uhd::rx_metadata_t::ERROR_CODE_BAD_PACKET: + default: + return ERROR_UNHANDLED; + } + } + + /* Missing timestamp */ + if (!md.has_time_spec) { + LOG(ALERT) << "UHD: Received packet missing timestamp"; + return ERROR_UNRECOVERABLE; + } + + ts = md.time_spec.to_ticks(rx_rate); + + /* Monotonicity check */ + if (ts < prev_ts) { + LOG(ALERT) << "UHD: Loss of monotonic time"; + LOG(ALERT) << "Current time: " << ts << ", " + << "Previous time: " << prev_ts; + return ERROR_TIMING; + } else { + prev_ts = ts; + } + + return 0; +} + +int UHDDevice::readSamples(short *buf, int len, bool *overrun, + long long timestamp, bool *underrun, unsigned *RSSI) +{ + ssize_t rc; + uhd::time_spec_t timespec; + uhd::rx_metadata_t metadata; + uint32_t pkt_buf[rx_spp]; + + *overrun = false; + *underrun = false; + + /* Align read time sample timing with respect to transmit clock */ + timestamp += ts_offset; + + timespec = uhd::time_spec_t::from_ticks(timestamp, rx_rate); + LOG(DEBUG) << "Requested timestamp = " << timestamp; + + /* Check that timestamp is valid */ + rc = rx_buffer->avail_smpls(timestamp); + if (rc < 0) { + LOG(ERR) << rx_buffer->str_code(rc); + LOG(ERR) << rx_buffer->str_status(timestamp); + return 0; + } + + /* Receive samples from the usrp until we have enough */ + while (rx_buffer->avail_smpls(timestamp) < len) { + thread_enable_cancel(false); + size_t num_smpls = rx_stream->recv( + (void *) pkt_buf, + rx_spp, + metadata, + 0.1, + true); + thread_enable_cancel(true); + + rx_pkt_cnt++; + + /* Check for errors */ + rc = check_rx_md_err(metadata, num_smpls); + switch (rc) { + case ERROR_UNRECOVERABLE: + LOG(ALERT) << "UHD: Version " << uhd::get_version_string(); + LOG(ALERT) << "UHD: Unrecoverable error, exiting..."; + exit(-1); + case ERROR_TIMING: + case ERROR_UNHANDLED: + continue; + } + + + timespec = metadata.time_spec; + LOG(DEBUG) << "Received timestamp = " << timespec.to_ticks(rx_rate); + + rc = rx_buffer->write(pkt_buf, + num_smpls, + metadata.time_spec); + + /* Continue on local overrun, exit on other errors */ + if ((rc < 0)) { + LOG(ERR) << rx_buffer->str_code(rc); + LOG(ERR) << rx_buffer->str_status(timestamp); + if (rc != SampleBuffer::ERROR_OVERFLOW) + return 0; + } + } + + /* We have enough samples */ + rc = rx_buffer->read(buf, len, timestamp); + if ((rc < 0) || (rc != len)) { + LOG(ERR) << rx_buffer->str_code(rc); + LOG(ERR) << rx_buffer->str_status(timestamp); + return 0; + } + + return len; +} + +int UHDDevice::writeSamples(short *buf, int len, + bool *underrun, long long ts) +{ + uhd::tx_metadata_t metadata; + metadata.has_time_spec = true; + metadata.start_of_burst = false; + metadata.end_of_burst = false; + metadata.time_spec = uhd::time_spec_t::from_ticks(ts, tx_rate); + + *underrun = false; + + /* Drop a fixed number of packets */ + if (!aligned) { + drop_cnt++; + + if (drop_cnt == 1) { + LOG(DEBUG) << "Aligning transmitter: stop burst"; + *underrun = true; + metadata.end_of_burst = true; + } else if (drop_cnt < TX_PACKET_SYNC) { + LOG(DEBUG) << "Aligning transmitter: packet advance"; + return len; + } else { + LOG(DEBUG) << "Aligning transmitter: start burst"; + metadata.start_of_burst = true; + aligned = true; + drop_cnt = 0; + } + } + + thread_enable_cancel(false); + size_t num_smpls = tx_stream->send(buf, len, metadata); + thread_enable_cancel(true); + + if (num_smpls != (unsigned) len) { + LOG(ALERT) << "UHD: Device send timed out"; + } + + return num_smpls; +} + +bool UHDDevice::setTxFreq(double wFreq) +{ + uhd::tune_result_t tr = usrp_dev->set_tx_freq(wFreq); + LOG(INFO) << "\n" << tr.to_pp_string(); + tx_freq = usrp_dev->get_tx_freq(); + + return true; +} + +bool UHDDevice::setRxFreq(double wFreq) +{ + uhd::tune_result_t tr = usrp_dev->set_rx_freq(wFreq); + LOG(INFO) << "\n" << tr.to_pp_string(); + rx_freq = usrp_dev->get_rx_freq(); + + return true; +} + +bool UHDDevice::recv_async_msg() +{ + uhd::async_metadata_t md; + + thread_enable_cancel(false); + bool rc = tx_stream->recv_async_msg(md, 0.1); + thread_enable_cancel(true); + if (!rc) + return false; + + /* Assume that any error requires resynchronization */ + if (md.event_code != uhd::async_metadata_t::EVENT_CODE_BURST_ACK) { + aligned = false; + + if ((md.event_code != uhd::async_metadata_t::EVENT_CODE_UNDERFLOW) && + (md.event_code != uhd::async_metadata_t::EVENT_CODE_TIME_ERROR)) { + LOG(ERR) << str_code(md); + } + } + + return true; +} + +std::string UHDDevice::str_code(uhd::rx_metadata_t metadata) +{ + std::ostringstream ost("UHD: "); + + switch (metadata.error_code) { + case uhd::rx_metadata_t::ERROR_CODE_NONE: + ost << "No error"; + break; + case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: + ost << "No packet received, implementation timed-out"; + break; + case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND: + ost << "A stream command was issued in the past"; + break; + case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN: + ost << "Expected another stream command"; + break; + case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW: + ost << "An internal receive buffer has filled"; + break; + case uhd::rx_metadata_t::ERROR_CODE_BAD_PACKET: + ost << "The packet could not be parsed"; + break; + default: + ost << "Unknown error " << metadata.error_code; + } + + if (metadata.has_time_spec) + ost << " at " << metadata.time_spec.get_real_secs() << " sec."; + + return ost.str(); +} + +std::string UHDDevice::str_code(uhd::async_metadata_t metadata) +{ + std::ostringstream ost("UHD: "); + + switch (metadata.event_code) { + case uhd::async_metadata_t::EVENT_CODE_BURST_ACK: + ost << "A packet was successfully transmitted"; + break; + case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW: + ost << "An internal send buffer has emptied"; + break; + case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR: + ost << "Packet loss between host and device"; + break; + case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR: + ost << "Packet time was too late or too early"; + break; + case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET: + ost << "Underflow occurred inside a packet"; + break; + case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST: + ost << "Packet loss within a burst"; + break; + default: + ost << "Unknown error " << metadata.event_code; + } + + if (metadata.has_time_spec) + ost << " at " << metadata.time_spec.get_real_secs() << " sec."; + + return ost.str(); +} diff --git a/TransceiverUHD/UHDDevice.h b/TransceiverUHD/UHDDevice.h new file mode 100644 index 0000000..e01172d --- /dev/null +++ b/TransceiverUHD/UHDDevice.h @@ -0,0 +1,116 @@ +#ifndef UHD_DEVICE_H +#define UHD_DEVICE_H + +#include +#include "RadioDevice.h" +#include "SampleBuffer.h" + +#define TX_AMPL 0.3 + +enum uhd_dev_type { + USRP1, + USRP2, + B100, + B2XX, + X300, + UMTRX, + NUM_USRP_TYPES, +}; + +class UHDDevice : public RadioDevice { +public: + UHDDevice(double rate); + ~UHDDevice(); + + bool open(const std::string &args, bool extref); + bool start(); + bool stop(); + bool restart(); + void setPriority(); + enum TxWindowType getWindowType() { return tx_window; } + + int readSamples(short *buf, int len, bool *overrun, + long long timestamp, bool *underrun, unsigned *RSSI); + + int writeSamples(short *buf, int len, + bool *underrun, long long timestamp); + + bool setVCTCXO(unsigned int) { return true; }; + + bool setTxFreq(double wFreq); + bool setRxFreq(double wFreq); + + inline long long initialWriteTimestamp() { return ts_initial; } + inline long long initialReadTimestamp() { return ts_initial; } + + inline double fullScaleInputValue() { return 32000 * TX_AMPL; } + inline double fullScaleOutputValue() { return 32000; } + + double setRxGain(double db); + double getRxGain(void) { return rx_gain; } + double maxRxGain(void) { return rx_gain_max; } + double minRxGain(void) { return rx_gain_min; } + + double setTxGain(double db); + double maxTxGain(void) { return tx_gain_max; } + double minTxGain(void) { return tx_gain_min; } + + double getTxFreq() { return tx_freq; } + double getRxFreq() { return rx_freq; } + + inline double getSampleRate() { return tx_rate; } + inline double numberRead() { return rx_pkt_cnt; } + inline double numberWritten() { return 0; } + + /** Receive and process asynchronous message + @return true if message received or false on timeout or error + */ + bool recv_async_msg(); + + enum err_code { + ERROR_TIMING = -1, + ERROR_UNRECOVERABLE = -2, + ERROR_UNHANDLED = -3, + }; + +private: + uhd::usrp::multi_usrp::sptr usrp_dev; + uhd::tx_streamer::sptr tx_stream; + uhd::rx_streamer::sptr rx_stream; + enum TxWindowType tx_window; + enum uhd_dev_type dev_type; + + int sps; + int tx_spp, rx_spp; + + double tx_gain, tx_gain_min, tx_gain_max; + double rx_gain, rx_gain_min, rx_gain_max; + + double tx_rate, rx_rate; + double tx_freq, rx_freq; + + bool started; + bool aligned; + + size_t rx_pkt_cnt; + size_t drop_cnt; + long long prev_ts; + + long long ts_initial, ts_offset; + SampleBuffer *rx_buffer; + + void init_gains(); + void set_ref_clk(bool ext_clk); + int set_master_clk(double rate); + int set_rates(double tx_rate, double rx_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); + + std::string str_code(uhd::rx_metadata_t metadata); + std::string str_code(uhd::async_metadata_t metadata); + + Thread *async_event_thrd; +}; + +#endif /* UHD_DEVICE_H */ diff --git a/TransceiverUHD/convert.c b/TransceiverUHD/convert.c new file mode 100644 index 0000000..3ab670c --- /dev/null +++ b/TransceiverUHD/convert.c @@ -0,0 +1,199 @@ +/* + * SSE type conversions + * Copyright (C) 2013 Thomas Tsou + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_SSE3 +#include +#include + +#ifdef HAVE_SSE4_1 +#include + +/* 16*N 16-bit signed integer converted to single precision floats */ +static void _sse_convert_si16_ps_16n(float *restrict out, + short *restrict in, + int len) +{ + __m128i m0, m1, m2, m3, m4, m5; + __m128 m6, m7, m8, m9; + + for (int i = 0; i < len / 16; i++) { + /* Load (unaligned) packed floats */ + m0 = _mm_loadu_si128((__m128i *) &in[16 * i + 0]); + m1 = _mm_loadu_si128((__m128i *) &in[16 * i + 8]); + + /* Unpack */ + m2 = _mm_cvtepi16_epi32(m0); + m4 = _mm_cvtepi16_epi32(m1); + m0 = _mm_shuffle_epi32(m0, _MM_SHUFFLE(1, 0, 3, 2)); + m1 = _mm_shuffle_epi32(m1, _MM_SHUFFLE(1, 0, 3, 2)); + m3 = _mm_cvtepi16_epi32(m0); + m5 = _mm_cvtepi16_epi32(m1); + + /* Convert */ + m6 = _mm_cvtepi32_ps(m2); + m7 = _mm_cvtepi32_ps(m3); + m8 = _mm_cvtepi32_ps(m4); + m9 = _mm_cvtepi32_ps(m5); + + /* Store */ + _mm_storeu_ps(&out[16 * i + 0], m6); + _mm_storeu_ps(&out[16 * i + 4], m7); + _mm_storeu_ps(&out[16 * i + 8], m8); + _mm_storeu_ps(&out[16 * i + 12], m9); + } +} + +/* 16*N 16-bit signed integer conversion with remainder */ +static void _sse_convert_si16_ps(float *restrict out, + short *restrict in, + int len) +{ + int start = len / 16 * 16; + + _sse_convert_si16_ps_16n(out, in, len); + + for (int i = 0; i < len % 16; i++) + out[start + i] = in[start + i]; +} +#endif /* HAVE_SSE4_1 */ + +/* 8*N single precision floats scaled and converted to 16-bit signed integer */ +static void _sse_convert_scale_ps_si16_8n(short *restrict out, + float *restrict in, + float scale, int len) +{ + __m128 m0, m1, m2; + __m128i m4, m5; + + for (int i = 0; i < len / 8; i++) { + /* Load (unaligned) packed floats */ + m0 = _mm_loadu_ps(&in[8 * i + 0]); + m1 = _mm_loadu_ps(&in[8 * i + 4]); + m2 = _mm_load1_ps(&scale); + + /* Scale */ + m0 = _mm_mul_ps(m0, m2); + m1 = _mm_mul_ps(m1, m2); + + /* Convert */ + m4 = _mm_cvtps_epi32(m0); + m5 = _mm_cvtps_epi32(m1); + + /* Pack and store */ + m5 = _mm_packs_epi32(m4, m5); + _mm_storeu_si128((__m128i *) &out[8 * i], m5); + } +} + +/* 8*N single precision floats scaled and converted with remainder */ +static void _sse_convert_scale_ps_si16(short *restrict out, + float *restrict in, + float scale, int len) +{ + int start = len / 8 * 8; + + _sse_convert_scale_ps_si16_8n(out, in, scale, len); + + for (int i = 0; i < len % 8; i++) + out[start + i] = in[start + i] * scale; +} + +/* 16*N single precision floats scaled and converted to 16-bit signed integer */ +static void _sse_convert_scale_ps_si16_16n(short *restrict out, + float *restrict in, + float scale, int len) +{ + __m128 m0, m1, m2, m3, m4; + __m128i m5, m6, m7, m8; + + for (int i = 0; i < len / 16; i++) { + /* Load (unaligned) packed floats */ + m0 = _mm_loadu_ps(&in[16 * i + 0]); + m1 = _mm_loadu_ps(&in[16 * i + 4]); + m2 = _mm_loadu_ps(&in[16 * i + 8]); + m3 = _mm_loadu_ps(&in[16 * i + 12]); + m4 = _mm_load1_ps(&scale); + + /* Scale */ + m0 = _mm_mul_ps(m0, m4); + m1 = _mm_mul_ps(m1, m4); + m2 = _mm_mul_ps(m2, m4); + m3 = _mm_mul_ps(m3, m4); + + /* Convert */ + m5 = _mm_cvtps_epi32(m0); + m6 = _mm_cvtps_epi32(m1); + m7 = _mm_cvtps_epi32(m2); + m8 = _mm_cvtps_epi32(m3); + + /* Pack and store */ + m5 = _mm_packs_epi32(m5, m6); + m7 = _mm_packs_epi32(m7, m8); + _mm_storeu_si128((__m128i *) &out[16 * i + 0], m5); + _mm_storeu_si128((__m128i *) &out[16 * i + 8], m7); + } +} +#else /* HAVE_SSE3 */ +static void convert_scale_ps_si16(short *out, float *in, float scale, int len) +{ + for (int i = 0; i < len; i++) + out[i] = in[i] * scale; +} +#endif + +#ifndef HAVE_SSE4_1 +static void convert_si16_ps(float *out, short *in, int len) +{ + for (int i = 0; i < len; i++) + out[i] = in[i]; +} +#endif + +void convert_float_short(short *out, float *in, float scale, int len) +{ +#ifdef HAVE_SSE3 + if (!(len % 16)) + _sse_convert_scale_ps_si16_16n(out, in, scale, len); + else if (!(len % 8)) + _sse_convert_scale_ps_si16_8n(out, in, scale, len); + else + _sse_convert_scale_ps_si16(out, in, scale, len); +#else + convert_scale_ps_si16(out, in, scale, len); +#endif +} + +void convert_short_float(float *out, short *in, int len) +{ +#ifdef HAVE_SSE4_1 + if (!(len % 16)) + _sse_convert_si16_ps_16n(out, in, len); + else + _sse_convert_si16_ps(out, in, len); +#else + convert_si16_ps(out, in, len); +#endif +} diff --git a/TransceiverUHD/convert.h b/TransceiverUHD/convert.h new file mode 100644 index 0000000..5b557bf --- /dev/null +++ b/TransceiverUHD/convert.h @@ -0,0 +1,7 @@ +#ifndef _CONVERT_H_ +#define _CONVERT_H_ + +void convert_float_short(short *out, float *in, float scale, int len); +void convert_short_float(float *out, short *in, int len); + +#endif /* _CONVERT_H_ */ diff --git a/TransceiverUHD/convolve.c b/TransceiverUHD/convolve.c new file mode 100644 index 0000000..646f9fd --- /dev/null +++ b/TransceiverUHD/convolve.c @@ -0,0 +1,714 @@ +/* + * SSE Convolution + * Copyright (C) 2012, 2013 Thomas Tsou + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_SSE3 +#include +#include + +/* 4-tap SSE complex-real convolution */ +static void sse_conv_real4(float *restrict x, + float *restrict h, + float *restrict y, + int len) +{ + __m128 m0, m1, m2, m3, m4, m5, m6, m7; + + /* Load (aligned) filter taps */ + m0 = _mm_load_ps(&h[0]); + m1 = _mm_load_ps(&h[4]); + m7 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + + for (int i = 0; i < len; i++) { + /* Load (unaligned) input data */ + m0 = _mm_loadu_ps(&x[2 * i + 0]); + m1 = _mm_loadu_ps(&x[2 * i + 4]); + m2 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m3 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3)); + + /* Quad multiply */ + m4 = _mm_mul_ps(m2, m7); + m5 = _mm_mul_ps(m3, m7); + + /* Sum and store */ + m6 = _mm_hadd_ps(m4, m5); + m0 = _mm_hadd_ps(m6, m6); + + _mm_store_ss(&y[2 * i + 0], m0); + m0 = _mm_shuffle_ps(m0, m0, _MM_SHUFFLE(0, 3, 2, 1)); + _mm_store_ss(&y[2 * i + 1], m0); + } +} + +/* 8-tap SSE complex-real convolution */ +static void sse_conv_real8(float *restrict x, + float *restrict h, + float *restrict y, + int len) +{ + __m128 m0, m1, m2, m3, m4, m5, m6, m7, m8, m9; + + /* Load (aligned) filter taps */ + m0 = _mm_load_ps(&h[0]); + m1 = _mm_load_ps(&h[4]); + m2 = _mm_load_ps(&h[8]); + m3 = _mm_load_ps(&h[12]); + + m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m5 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2)); + + for (int i = 0; i < len; i++) { + /* Load (unaligned) input data */ + m0 = _mm_loadu_ps(&x[2 * i + 0]); + m1 = _mm_loadu_ps(&x[2 * i + 4]); + m2 = _mm_loadu_ps(&x[2 * i + 8]); + m3 = _mm_loadu_ps(&x[2 * i + 12]); + + m6 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m7 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3)); + m8 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2)); + m9 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3)); + + /* Quad multiply */ + m6 = _mm_mul_ps(m6, m4); + m7 = _mm_mul_ps(m7, m4); + m8 = _mm_mul_ps(m8, m5); + m9 = _mm_mul_ps(m9, m5); + + /* Sum and store */ + m6 = _mm_add_ps(m6, m8); + m7 = _mm_add_ps(m7, m9); + m6 = _mm_hadd_ps(m6, m7); + m6 = _mm_hadd_ps(m6, m6); + + _mm_store_ss(&y[2 * i + 0], m6); + m6 = _mm_shuffle_ps(m6, m6, _MM_SHUFFLE(0, 3, 2, 1)); + _mm_store_ss(&y[2 * i + 1], m6); + } +} + +/* 12-tap SSE complex-real convolution */ +static void sse_conv_real12(float *restrict x, + float *restrict h, + float *restrict y, + int len) +{ + __m128 m0, m1, m2, m3, m4, m5, m6, m7; + __m128 m8, m9, m10, m11, m12, m13, m14; + + /* Load (aligned) filter taps */ + m0 = _mm_load_ps(&h[0]); + m1 = _mm_load_ps(&h[4]); + m2 = _mm_load_ps(&h[8]); + m3 = _mm_load_ps(&h[12]); + m4 = _mm_load_ps(&h[16]); + m5 = _mm_load_ps(&h[20]); + + m12 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m13 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2)); + m14 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(0, 2, 0, 2)); + + for (int i = 0; i < len; i++) { + /* Load (unaligned) input data */ + m0 = _mm_loadu_ps(&x[2 * i + 0]); + m1 = _mm_loadu_ps(&x[2 * i + 4]); + m2 = _mm_loadu_ps(&x[2 * i + 8]); + m3 = _mm_loadu_ps(&x[2 * i + 12]); + + m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3)); + m6 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2)); + m7 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3)); + + m0 = _mm_loadu_ps(&x[2 * i + 16]); + m1 = _mm_loadu_ps(&x[2 * i + 20]); + + m8 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m9 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3)); + + /* Quad multiply */ + m0 = _mm_mul_ps(m4, m12); + m1 = _mm_mul_ps(m5, m12); + m2 = _mm_mul_ps(m6, m13); + m3 = _mm_mul_ps(m7, m13); + m4 = _mm_mul_ps(m8, m14); + m5 = _mm_mul_ps(m9, m14); + + /* Sum and store */ + m8 = _mm_add_ps(m0, m2); + m9 = _mm_add_ps(m1, m3); + m10 = _mm_add_ps(m8, m4); + m11 = _mm_add_ps(m9, m5); + + m2 = _mm_hadd_ps(m10, m11); + m3 = _mm_hadd_ps(m2, m2); + + _mm_store_ss(&y[2 * i + 0], m3); + m3 = _mm_shuffle_ps(m3, m3, _MM_SHUFFLE(0, 3, 2, 1)); + _mm_store_ss(&y[2 * i + 1], m3); + } +} + +/* 16-tap SSE complex-real convolution */ +static void sse_conv_real16(float *restrict x, + float *restrict h, + float *restrict y, + int len) +{ + __m128 m0, m1, m2, m3, m4, m5, m6, m7; + __m128 m8, m9, m10, m11, m12, m13, m14, m15; + + /* Load (aligned) filter taps */ + m0 = _mm_load_ps(&h[0]); + m1 = _mm_load_ps(&h[4]); + m2 = _mm_load_ps(&h[8]); + m3 = _mm_load_ps(&h[12]); + + m4 = _mm_load_ps(&h[16]); + m5 = _mm_load_ps(&h[20]); + m6 = _mm_load_ps(&h[24]); + m7 = _mm_load_ps(&h[28]); + + m12 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m13 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2)); + m14 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(0, 2, 0, 2)); + m15 = _mm_shuffle_ps(m6, m7, _MM_SHUFFLE(0, 2, 0, 2)); + + for (int i = 0; i < len; i++) { + /* Load (unaligned) input data */ + m0 = _mm_loadu_ps(&x[2 * i + 0]); + m1 = _mm_loadu_ps(&x[2 * i + 4]); + m2 = _mm_loadu_ps(&x[2 * i + 8]); + m3 = _mm_loadu_ps(&x[2 * i + 12]); + + m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3)); + m6 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2)); + m7 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3)); + + m0 = _mm_loadu_ps(&x[2 * i + 16]); + m1 = _mm_loadu_ps(&x[2 * i + 20]); + m2 = _mm_loadu_ps(&x[2 * i + 24]); + m3 = _mm_loadu_ps(&x[2 * i + 28]); + + m8 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m9 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3)); + m10 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2)); + m11 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3)); + + /* Quad multiply */ + m0 = _mm_mul_ps(m4, m12); + m1 = _mm_mul_ps(m5, m12); + m2 = _mm_mul_ps(m6, m13); + m3 = _mm_mul_ps(m7, m13); + + m4 = _mm_mul_ps(m8, m14); + m5 = _mm_mul_ps(m9, m14); + m6 = _mm_mul_ps(m10, m15); + m7 = _mm_mul_ps(m11, m15); + + /* Sum and store */ + m8 = _mm_add_ps(m0, m2); + m9 = _mm_add_ps(m1, m3); + m10 = _mm_add_ps(m4, m6); + m11 = _mm_add_ps(m5, m7); + + m0 = _mm_add_ps(m8, m10); + m1 = _mm_add_ps(m9, m11); + m2 = _mm_hadd_ps(m0, m1); + m3 = _mm_hadd_ps(m2, m2); + + _mm_store_ss(&y[2 * i + 0], m3); + m3 = _mm_shuffle_ps(m3, m3, _MM_SHUFFLE(0, 3, 2, 1)); + _mm_store_ss(&y[2 * i + 1], m3); + } +} + +/* 20-tap SSE complex-real convolution */ +static void sse_conv_real20(float *restrict x, + float *restrict h, + float *restrict y, + int len) +{ + __m128 m0, m1, m2, m3, m4, m5, m6, m7; + __m128 m8, m9, m11, m12, m13, m14, m15; + + /* Load (aligned) filter taps */ + m0 = _mm_load_ps(&h[0]); + m1 = _mm_load_ps(&h[4]); + m2 = _mm_load_ps(&h[8]); + m3 = _mm_load_ps(&h[12]); + m4 = _mm_load_ps(&h[16]); + m5 = _mm_load_ps(&h[20]); + m6 = _mm_load_ps(&h[24]); + m7 = _mm_load_ps(&h[28]); + m8 = _mm_load_ps(&h[32]); + m9 = _mm_load_ps(&h[36]); + + m11 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m12 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2)); + m13 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(0, 2, 0, 2)); + m14 = _mm_shuffle_ps(m6, m7, _MM_SHUFFLE(0, 2, 0, 2)); + m15 = _mm_shuffle_ps(m8, m9, _MM_SHUFFLE(0, 2, 0, 2)); + + for (int i = 0; i < len; i++) { + /* Multiply-accumulate first 12 taps */ + m0 = _mm_loadu_ps(&x[2 * i + 0]); + m1 = _mm_loadu_ps(&x[2 * i + 4]); + m2 = _mm_loadu_ps(&x[2 * i + 8]); + m3 = _mm_loadu_ps(&x[2 * i + 12]); + m4 = _mm_loadu_ps(&x[2 * i + 16]); + m5 = _mm_loadu_ps(&x[2 * i + 20]); + + m6 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m7 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3)); + m8 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2)); + m9 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3)); + m0 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(0, 2, 0, 2)); + m1 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(1, 3, 1, 3)); + + m2 = _mm_mul_ps(m6, m11); + m3 = _mm_mul_ps(m7, m11); + m4 = _mm_mul_ps(m8, m12); + m5 = _mm_mul_ps(m9, m12); + m6 = _mm_mul_ps(m0, m13); + m7 = _mm_mul_ps(m1, m13); + + m0 = _mm_add_ps(m2, m4); + m1 = _mm_add_ps(m3, m5); + m8 = _mm_add_ps(m0, m6); + m9 = _mm_add_ps(m1, m7); + + /* Multiply-accumulate last 8 taps */ + m0 = _mm_loadu_ps(&x[2 * i + 24]); + m1 = _mm_loadu_ps(&x[2 * i + 28]); + m2 = _mm_loadu_ps(&x[2 * i + 32]); + m3 = _mm_loadu_ps(&x[2 * i + 36]); + + m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3)); + m6 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2)); + m7 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3)); + + m0 = _mm_mul_ps(m4, m14); + m1 = _mm_mul_ps(m5, m14); + m2 = _mm_mul_ps(m6, m15); + m3 = _mm_mul_ps(m7, m15); + + m4 = _mm_add_ps(m0, m2); + m5 = _mm_add_ps(m1, m3); + + /* Final sum and store */ + m0 = _mm_add_ps(m8, m4); + m1 = _mm_add_ps(m9, m5); + m2 = _mm_hadd_ps(m0, m1); + m3 = _mm_hadd_ps(m2, m2); + + _mm_store_ss(&y[2 * i + 0], m3); + m3 = _mm_shuffle_ps(m3, m3, _MM_SHUFFLE(0, 3, 2, 1)); + _mm_store_ss(&y[2 * i + 1], m3); + } +} + +/* 4*N-tap SSE complex-real convolution */ +static void sse_conv_real4n(float *x, float *h, float *y, int h_len, int len) +{ + __m128 m0, m1, m2, m4, m5, m6, m7; + + for (int i = 0; i < len; i++) { + /* Zero */ + m6 = _mm_setzero_ps(); + m7 = _mm_setzero_ps(); + + for (int n = 0; n < h_len / 4; n++) { + /* Load (aligned) filter taps */ + m0 = _mm_load_ps(&h[8 * n + 0]); + m1 = _mm_load_ps(&h[8 * n + 4]); + m2 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + + /* Load (unaligned) input data */ + m0 = _mm_loadu_ps(&x[2 * i + 8 * n + 0]); + m1 = _mm_loadu_ps(&x[2 * i + 8 * n + 4]); + m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3)); + + /* Quad multiply */ + m0 = _mm_mul_ps(m2, m4); + m1 = _mm_mul_ps(m2, m5); + + /* Accumulate */ + m6 = _mm_add_ps(m6, m0); + m7 = _mm_add_ps(m7, m1); + } + + m0 = _mm_hadd_ps(m6, m7); + m0 = _mm_hadd_ps(m0, m0); + + _mm_store_ss(&y[2 * i + 0], m0); + m0 = _mm_shuffle_ps(m0, m0, _MM_SHUFFLE(0, 3, 2, 1)); + _mm_store_ss(&y[2 * i + 1], m0); + } +} + +/* 4*N-tap SSE complex-complex convolution */ +static void sse_conv_cmplx_4n(float *x, float *h, float *y, int h_len, int len) +{ + __m128 m0, m1, m2, m3, m4, m5, m6, m7; + + for (int i = 0; i < len; i++) { + /* Zero */ + m6 = _mm_setzero_ps(); + m7 = _mm_setzero_ps(); + + for (int n = 0; n < h_len / 4; n++) { + /* Load (aligned) filter taps */ + m0 = _mm_load_ps(&h[8 * n + 0]); + m1 = _mm_load_ps(&h[8 * n + 4]); + m2 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m3 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3)); + + /* Load (unaligned) input data */ + m0 = _mm_loadu_ps(&x[2 * i + 8 * n + 0]); + m1 = _mm_loadu_ps(&x[2 * i + 8 * n + 4]); + m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3)); + + /* Quad multiply */ + m0 = _mm_mul_ps(m2, m4); + m1 = _mm_mul_ps(m3, m5); + + m2 = _mm_mul_ps(m2, m5); + m3 = _mm_mul_ps(m3, m4); + + /* Sum */ + m0 = _mm_sub_ps(m0, m1); + m2 = _mm_add_ps(m2, m3); + + /* Accumulate */ + m6 = _mm_add_ps(m6, m0); + m7 = _mm_add_ps(m7, m2); + } + + m0 = _mm_hadd_ps(m6, m7); + m0 = _mm_hadd_ps(m0, m0); + + _mm_store_ss(&y[2 * i + 0], m0); + m0 = _mm_shuffle_ps(m0, m0, _MM_SHUFFLE(0, 3, 2, 1)); + _mm_store_ss(&y[2 * i + 1], m0); + } +} + +/* 8*N-tap SSE complex-complex convolution */ +static void sse_conv_cmplx_8n(float *x, float *h, float *y, int h_len, int len) +{ + __m128 m0, m1, m2, m3, m4, m5, m6, m7; + __m128 m8, m9, m10, m11, m12, m13, m14, m15; + + for (int i = 0; i < len; i++) { + /* Zero */ + m12 = _mm_setzero_ps(); + m13 = _mm_setzero_ps(); + m14 = _mm_setzero_ps(); + m15 = _mm_setzero_ps(); + + for (int n = 0; n < h_len / 8; n++) { + /* Load (aligned) filter taps */ + m0 = _mm_load_ps(&h[16 * n + 0]); + m1 = _mm_load_ps(&h[16 * n + 4]); + m2 = _mm_load_ps(&h[16 * n + 8]); + m3 = _mm_load_ps(&h[16 * n + 12]); + + m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3)); + m6 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2)); + m7 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3)); + + /* Load (unaligned) input data */ + m0 = _mm_loadu_ps(&x[2 * i + 16 * n + 0]); + m1 = _mm_loadu_ps(&x[2 * i + 16 * n + 4]); + m2 = _mm_loadu_ps(&x[2 * i + 16 * n + 8]); + m3 = _mm_loadu_ps(&x[2 * i + 16 * n + 12]); + + m8 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2)); + m9 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3)); + m10 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2)); + m11 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3)); + + /* Quad multiply */ + m0 = _mm_mul_ps(m4, m8); + m1 = _mm_mul_ps(m5, m9); + m2 = _mm_mul_ps(m6, m10); + m3 = _mm_mul_ps(m7, m11); + + m4 = _mm_mul_ps(m4, m9); + m5 = _mm_mul_ps(m5, m8); + m6 = _mm_mul_ps(m6, m11); + m7 = _mm_mul_ps(m7, m10); + + /* Sum */ + m0 = _mm_sub_ps(m0, m1); + m2 = _mm_sub_ps(m2, m3); + m4 = _mm_add_ps(m4, m5); + m6 = _mm_add_ps(m6, m7); + + /* Accumulate */ + m12 = _mm_add_ps(m12, m0); + m13 = _mm_add_ps(m13, m2); + m14 = _mm_add_ps(m14, m4); + m15 = _mm_add_ps(m15, m6); + } + + m0 = _mm_add_ps(m12, m13); + m1 = _mm_add_ps(m14, m15); + m2 = _mm_hadd_ps(m0, m1); + m2 = _mm_hadd_ps(m2, m2); + + _mm_store_ss(&y[2 * i + 0], m2); + m2 = _mm_shuffle_ps(m2, m2, _MM_SHUFFLE(0, 3, 2, 1)); + _mm_store_ss(&y[2 * i + 1], m2); + } +} +#endif + +/* Base multiply and accumulate complex-real */ +static void mac_real(float *x, float *h, float *y) +{ + y[0] += x[0] * h[0]; + y[1] += x[1] * h[0]; +} + +/* Base multiply and accumulate complex-complex */ +static void mac_cmplx(float *x, float *h, float *y) +{ + y[0] += x[0] * h[0] - x[1] * h[1]; + y[1] += x[0] * h[1] + x[1] * h[0]; +} + +/* Base vector complex-complex multiply and accumulate */ +static void mac_real_vec_n(float *x, float *h, float *y, + int len, int step, int offset) +{ + for (int i = offset; i < len; i += step) + mac_real(&x[2 * i], &h[2 * i], y); +} + +/* Base vector complex-complex multiply and accumulate */ +static void mac_cmplx_vec_n(float *x, float *h, float *y, + int len, int step, int offset) +{ + for (int i = offset; i < len; i += step) + mac_cmplx(&x[2 * i], &h[2 * i], y); +} + +/* Base complex-real convolution */ +static int _base_convolve_real(float *x, int x_len, + float *h, int h_len, + float *y, int y_len, + int start, int len, + int step, int offset) +{ + for (int i = 0; i < len; i++) { + mac_real_vec_n(&x[2 * (i - (h_len - 1) + start)], + h, + &y[2 * i], h_len, + step, offset); + } + + return len; +} + +/* Base complex-complex convolution */ +static int _base_convolve_complex(float *x, int x_len, + float *h, int h_len, + float *y, int y_len, + int start, int len, + int step, int offset) +{ + for (int i = 0; i < len; i++) { + mac_cmplx_vec_n(&x[2 * (i - (h_len - 1) + start)], + h, + &y[2 * i], + h_len, step, offset); + } + + return len; +} + +/* Buffer validity checks */ +static int bounds_check(int x_len, int h_len, int y_len, + int start, int len, int step) +{ + if ((x_len < 1) || (h_len < 1) || + (y_len < 1) || (len < 1) || (step < 1)) { + fprintf(stderr, "Convolve: Invalid input\n"); + return -1; + } + + if ((start + len > x_len) || (len > y_len) || (x_len < h_len)) { + fprintf(stderr, "Convolve: Boundary exception\n"); + fprintf(stderr, "start: %i, len: %i, x: %i, h: %i, y: %i\n", + start, len, x_len, h_len, y_len); + return -1; + } + + return 0; +} + +/* API: Aligned complex-real */ +int convolve_real(float *x, int x_len, + float *h, int h_len, + float *y, int y_len, + int start, int len, + int step, int offset) +{ + void (*conv_func)(float *, float *, float *, int) = NULL; + void (*conv_func_n)(float *, float *, float *, int, int) = NULL; + + if (bounds_check(x_len, h_len, y_len, start, len, step) < 0) + return -1; + + memset(y, 0, len * 2 * sizeof(float)); + +#ifdef HAVE_SSE3 + if (step <= 4) { + switch (h_len) { + case 4: + conv_func = sse_conv_real4; + break; + case 8: + conv_func = sse_conv_real8; + break; + case 12: + conv_func = sse_conv_real12; + break; + case 16: + conv_func = sse_conv_real16; + break; + case 20: + conv_func = sse_conv_real20; + break; + default: + if (!(h_len % 4)) + conv_func_n = sse_conv_real4n; + } + } +#endif + if (conv_func) { + conv_func(&x[2 * (-(h_len - 1) + start)], + h, y, len); + } else if (conv_func_n) { + conv_func_n(&x[2 * (-(h_len - 1) + start)], + h, y, h_len, len); + } else { + _base_convolve_real(x, x_len, + h, h_len, + y, y_len, + start, len, step, offset); + } + + return len; +} + +/* API: Aligned complex-complex */ +int convolve_complex(float *x, int x_len, + float *h, int h_len, + float *y, int y_len, + int start, int len, + int step, int offset) +{ + void (*conv_func)(float *, float *, float *, int, int) = NULL; + + if (bounds_check(x_len, h_len, y_len, start, len, step) < 0) + return -1; + + memset(y, 0, len * 2 * sizeof(float)); + +#ifdef HAVE_SSE3 + if (step <= 4) { + if (!(h_len % 8)) + conv_func = sse_conv_cmplx_8n; + else if (!(h_len % 4)) + conv_func = sse_conv_cmplx_4n; + } +#endif + if (conv_func) { + conv_func(&x[2 * (-(h_len - 1) + start)], + h, y, h_len, len); + } else { + _base_convolve_complex(x, x_len, + h, h_len, + y, y_len, + start, len, step, offset); + } + + return len; +} + +/* API: Non-aligned (no SSE) complex-real */ +int base_convolve_real(float *x, int x_len, + float *h, int h_len, + float *y, int y_len, + int start, int len, + int step, int offset) +{ + if (bounds_check(x_len, h_len, y_len, start, len, step) < 0) + return -1; + + memset(y, 0, len * 2 * sizeof(float)); + + return _base_convolve_real(x, x_len, + h, h_len, + y, y_len, + start, len, step, offset); +} + +/* API: Non-aligned (no SSE) complex-complex */ +int base_convolve_complex(float *x, int x_len, + float *h, int h_len, + float *y, int y_len, + int start, int len, + int step, int offset) +{ + if (bounds_check(x_len, h_len, y_len, start, len, step) < 0) + return -1; + + memset(y, 0, len * 2 * sizeof(float)); + + return _base_convolve_complex(x, x_len, + h, h_len, + y, y_len, + start, len, step, offset); +} + +/* Aligned filter tap allocation */ +void *convolve_h_alloc(int len) +{ +#ifdef HAVE_SSE3 + return memalign(16, len * 2 * sizeof(float)); +#else + return malloc(len * 2 * sizeof(float)); +#endif +} diff --git a/TransceiverUHD/convolve.h b/TransceiverUHD/convolve.h new file mode 100644 index 0000000..aef9953 --- /dev/null +++ b/TransceiverUHD/convolve.h @@ -0,0 +1,30 @@ +#ifndef _CONVOLVE_H_ +#define _CONVOLVE_H_ + +void *convolve_h_alloc(int num); + +int convolve_real(float *x, int x_len, + float *h, int h_len, + float *y, int y_len, + int start, int len, + int step, int offset); + +int convolve_complex(float *x, int x_len, + float *h, int h_len, + float *y, int y_len, + int start, int len, + int step, int offset); + +int base_convolve_real(float *x, int x_len, + float *h, int h_len, + float *y, int y_len, + int start, int len, + int step, int offset); + +int base_convolve_complex(float *x, int x_len, + float *h, int h_len, + float *y, int y_len, + int start, int len, + int step, int offset); + +#endif /* _CONVOLVE_H_ */ diff --git a/TransceiverUHD/runTransceiver.cpp b/TransceiverUHD/runTransceiver.cpp new file mode 100644 index 0000000..2b73300 --- /dev/null +++ b/TransceiverUHD/runTransceiver.cpp @@ -0,0 +1,223 @@ +/* + * OpenBTS provides an open source alternative to legacy telco protocols and + * traditionally complex, proprietary hardware systems. + * + * Copyright 2008, 2009 Free Software Foundation, Inc. + * Copyright 2010 Kestrel Signal Processing, Inc. + * Copyright 2014 Range Networks, Inc. + * + * This software is distributed under the terms of the GNU General Public + * License version 3. See the COPYING and NOTICE files in the current + * directory for licensing information. + * + * This use of this software may be subject to additional restrictions. + * See the LEGAL file in the main directory for details. + */ + +#include +#include +#include +#include +#include +#include + +#include "Transceiver.h" +#include "UHDDevice.h" + +/* Default maximum expected delay spread in symbols */ +#define DEFAULT_MAX_DELAY 50 + +/* Sample rate for all devices */ +#define DEVICE_RATE 6.25e6 + +ConfigurationKeyMap getConfigurationKeys2(); +ConfigurationTable gConfig("/etc/OpenBTS/OpenBTS-UMTS.db", + "transceiver", getConfigurationKeys2()); + +volatile bool gbShutdown = false; + +static void shutdown_handler(int signo) +{ + std::cout << std::endl << "** Received shutdown signal" << std::endl; + gbShutdown = true; +} + +static void register_signal_handlers() +{ + if (signal(SIGINT, shutdown_handler) == SIG_ERR) { + std::cerr << "** Failed to install SIGINT signal handler" << std::endl; + exit(1); + } + + if (signal(SIGTERM, shutdown_handler) == SIG_ERR) { + std::cerr << "** Failed to install SIGTERM signal handler" << std::endl; + exit(1); + } +} + +/* Logging check in the configuration is mandatory */ +static bool init_config() +{ + try { + std::cout << "** Configuring logger" << std::endl; + gLogInit("transceiver", gConfig.getStr("Log.Level").c_str(), LOG_LOCAL7); + } catch (ConfigurationTableKeyNotFound e) { + LOG(EMERG) << "** Required configuration parameter " << e.key() + << " not defined, aborting"; + return false; + } + + return true; +} + +/* Optional expected delay spread (default 50 symbols) */ +static int init_max_delay() +{ + int max_delay; + + try { + max_delay = gConfig.getNum("UMTS.Radio.MaxExpectedDelaySpread"); + } catch (ConfigurationTableKeyNotFound e) { + max_delay = DEFAULT_MAX_DELAY; + } + + return max_delay; +} + +/* Optional external reference enable (default off) */ +static bool init_extref() +{ + int enable; + + try { + enable = gConfig.getNum("TRX.Reference"); + } catch (ConfigurationTableKeyNotFound e) { + enable = 0; + } + + return enable != 0; +} + +/* Optional device hint (default none) */ +static std::string init_devaddr() +{ + std::string addr; + + try { + addr = gConfig.getNum("UMTS.Radio.UHD.DeviceAddress"); + } catch (ConfigurationTableKeyNotFound e) { + addr = ""; + } + + return addr; +} + +int main(int argc, char *argv[]) +{ + UHDDevice *usrp = NULL; + RadioDevice *dev = NULL; + Transceiver *trx = NULL; + RadioInterface *radio = NULL; + + int max_delay; + bool found, extref; + std::string devaddr; + + /* Capture termination signals */ + register_signal_handlers(); + + /* Fail if we don't have logging */ + if (!init_config()) + goto shutdown; + + /* Optional parameters */ + max_delay = init_max_delay(); + extref = init_extref(); + devaddr = init_devaddr(); + + srandom(time(NULL)); + + if (extref) + std::cout << "** Using external clock reference" << std::endl; + else + std::cout << "** Using internal clock reference" << std::endl; + + std::cout << "** Searching for USRP device " << devaddr << std::endl; + usrp = new UHDDevice(DEVICE_RATE); + found = usrp->open(devaddr, extref); + + if (found) { + std::cout << "** Device ready" << std::endl; + dev = (RadioDevice *) usrp; + } else { + std::cout << "** Device not available" << std::endl; + goto shutdown; + } + + radio = new RadioInterface(dev, 0); + if (!radio->init()) { + std::cout << "** Radio failed to initialize" << std::endl; + goto shutdown; + } + + trx = new Transceiver(5700, "127.0.0.1", UMTS::Time(4, 0), radio); + trx->receiveFIFO(radio->receiveFIFO()); + trx->init(max_delay); + + while (!gbShutdown) + sleep(1); + +shutdown: + delete trx; + delete radio; + delete usrp; + + return 0; +} + +ConfigurationKeyMap getConfigurationKeys2() +{ + extern ConfigurationKeyMap getConfigurationKeys(); + ConfigurationKeyMap map = getConfigurationKeys(); + ConfigurationKey *tmp; + + tmp = new ConfigurationKey("TRX.RadioFrequencyOffset","128", + "~170Hz steps", + ConfigurationKey::FACTORY, + ConfigurationKey::VALRANGE, + "96:160",// educated guess + true, + "Fine-tuning adjustment for the transceiver master clock. " + "Roughly 170 Hz/step. " + "Set at the factory. " + "Do not adjust without proper calibration." + ); + map[tmp->getName()] = *tmp; + delete tmp; + + tmp = new ConfigurationKey("TRX.TxAttenOffset","0", + "dB of attenuation", + ConfigurationKey::FACTORY, + ConfigurationKey::VALRANGE, + "0:100",// educated guess + true, + "Hardware-specific gain adjustment for transmitter, matched to the power amplifier, expessed as an attenuationi in dB. " + "Set at the factory. " + "Do not adjust without proper calibration." + ); + map[tmp->getName()] = *tmp; + delete tmp; + + tmp = new ConfigurationKey("TRX.RadioNumber","0", + "", + ConfigurationKey::FACTORY, + ConfigurationKey::VALRANGE, + "0:9", // Not likely to have 10 radios on the same computer. Not likely to have >1 + true, + "If non-0, use multiple radios on the same cpu, numbered 1-9. Must change TRX.Port also. Provide a separate config file for each OpenBTS+Radio combination using the environment variable or --config command line option." + ); + map[tmp->getName()] = *tmp; + delete(tmp); + + return map; +}