/*
* Radio device interface
*
* Copyright (C) 2008-2014 Free Software Foundation, Inc.
* Copyright (C) 2015 Ettus Research LLC
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
* See the COPYING file in the main directory for details.
*/
#include "radioInterface.h"
#include "Resampler.h"
#include
#include
extern "C" {
#include
#include
#include "convert.h"
}
#define CHUNK 625
#define NUMCHUNKS 4
RadioInterface::RadioInterface(RadioDevice *wDevice, size_t tx_sps,
size_t rx_sps, size_t chans,
int wReceiveOffset, GSM::Time wStartTime)
: mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mReceiveFIFO(mChans), mDevice(wDevice),
sendBuffer(mChans), recvBuffer(mChans), convertRecvBuffer(mChans),
convertSendBuffer(mChans), powerScaling(mChans), underrun(false), overrun(false),
writeTimestamp(0), readTimestamp(0), receiveOffset(wReceiveOffset), mOn(false)
{
mClock.set(wStartTime);
}
RadioInterface::~RadioInterface(void)
{
close();
}
bool RadioInterface::init(int type)
{
if ((type != RadioDevice::NORMAL) || !mChans) {
LOG(ALERT) << "Invalid configuration";
return false;
}
for (size_t i = 0; i < mChans; i++) {
sendBuffer[i] = new RadioBuffer(NUMCHUNKS, CHUNK * mSPSTx, 0, true);
recvBuffer[i] = new RadioBuffer(NUMCHUNKS, CHUNK * mSPSRx, 0, false);
convertSendBuffer[i] = new short[CHUNK * mSPSTx * 2];
convertRecvBuffer[i] = new short[CHUNK * mSPSRx * 2];
powerScaling[i] = 1.0;
}
return true;
}
void RadioInterface::close()
{
for (std::vector::iterator it = sendBuffer.begin(); it != sendBuffer.end(); ++it)
delete *it;
for (std::vector::iterator it = recvBuffer.begin(); it != recvBuffer.end(); ++it)
delete *it;
for (std::vector::iterator it = convertSendBuffer.begin(); it != convertSendBuffer.end(); ++it)
delete[] *it;
for (std::vector::iterator it = convertRecvBuffer.begin(); it != convertRecvBuffer.end(); ++it)
delete[] *it;
sendBuffer.resize(0);
recvBuffer.resize(0);
convertSendBuffer.resize(0);
convertRecvBuffer.resize(0);
}
double RadioInterface::fullScaleInputValue(void) {
return mDevice->fullScaleInputValue();
}
double RadioInterface::fullScaleOutputValue(void) {
return mDevice->fullScaleOutputValue();
}
int RadioInterface::setPowerAttenuation(int atten, size_t chan)
{
double rfAtten, digAtten;
if (chan >= mChans) {
LOG(ALERT) << "Invalid channel requested";
return -1;
}
if (atten < 0.0)
atten = 0.0;
rfAtten = mDevice->setPowerAttenuation((double) atten, chan);
digAtten = (double) atten - rfAtten;
if (digAtten < 1.0)
powerScaling[chan] = 1.0;
else
powerScaling[chan] = 1.0 / sqrt(pow(10, digAtten / 10.0));
return atten;
}
int RadioInterface::getNominalTxPower(size_t chan)
{
if (chan >= mChans) {
LOG(ALERT) << "Invalid channel requested";
return -1;
}
return mDevice->getNominalTxPower(chan);
}
int RadioInterface::radioifyVector(signalVector &wVector,
size_t chan, bool zero)
{
if (zero)
sendBuffer[chan]->zero(wVector.size());
else
sendBuffer[chan]->write((float *) wVector.begin(), wVector.size());
return wVector.size();
}
int RadioInterface::unRadioifyVector(signalVector *newVector, size_t chan)
{
if (newVector->size() > recvBuffer[chan]->getAvailSamples()) {
LOG(ALERT) << "Insufficient number of samples in receive buffer";
return -1;
}
recvBuffer[chan]->read((float *) newVector->begin(), newVector->size());
return newVector->size();
}
bool RadioInterface::tuneTx(double freq, size_t chan)
{
return mDevice->setTxFreq(freq, chan);
}
bool RadioInterface::tuneRx(double freq, size_t chan)
{
return mDevice->setRxFreq(freq, chan);
}
/** synchronization thread loop */
void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface)
{
set_selfthread_name("AlignRadio");
OSMO_ASSERT(osmo_cpu_sched_vty_apply_localthread() == 0);
while (1) {
sleep(60);
radioInterface->alignRadio();
pthread_testcancel();
}
return NULL;
}
void RadioInterface::alignRadio() {
mDevice->updateAlignment(writeTimestamp+ (TIMESTAMP) 10000);
}
bool RadioInterface::start()
{
if (mOn)
return true;
LOG(INFO) << "Starting radio device";
if (mDevice->requiresRadioAlign())
mAlignRadioServiceLoopThread.start(
(void * (*)(void*))AlignRadioServiceLoopAdapter,
(void*)this);
if (!mDevice->start())
return false;
for (size_t i = 0; i < mChans; i++) {
sendBuffer[i]->reset();
recvBuffer[i]->reset();
}
writeTimestamp = mDevice->initialWriteTimestamp();
readTimestamp = mDevice->initialReadTimestamp();
mDevice->updateAlignment(writeTimestamp-10000);
mDevice->updateAlignment(writeTimestamp-10000);
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 || !mDevice->stop())
return false;
mOn = false;
return true;
}
void RadioInterface::driveTransmitRadio(std::vector &bursts,
std::vector &zeros)
{
if (!mOn)
return;
for (size_t i = 0; i < mChans; i++)
radioifyVector(*bursts[i], i, zeros[i]);
while (pushBuffer());
}
int RadioInterface::driveReceiveRadio()
{
radioVector *burst = NULL;
if (!mOn)
return 0;
if (pullBuffer() < 0)
return -1;
GSM::Time rcvClock = mClock.get();
rcvClock.decTN(receiveOffset);
unsigned tN = rcvClock.TN();
int recvSz = recvBuffer[0]->getAvailSamples();
const int symbolsPerSlot = gSlotLen + 8;
int burstSize;
if (mSPSRx == 4)
burstSize = 625;
else
burstSize = symbolsPerSlot + (tN % 4 == 0);
/*
* Pre-allocate head room for the largest correlation size
* so we can later avoid a re-allocation and copy
* */
size_t head = GSM::gRACHSynchSequenceTS0.size();
/*
* Form receive bursts and pass up to transceiver. Use repeating
* pattern of 157-156-156-156 symbols per timeslot
*/
while (recvSz > burstSize) {
for (size_t i = 0; i < mChans; i++) {
burst = new radioVector(rcvClock, burstSize, head);
unRadioifyVector(burst->getVector(), i);
if (mReceiveFIFO[i].size() < 32)
mReceiveFIFO[i].write(burst);
else
delete burst;
}
mClock.incTN();
rcvClock.incTN();
recvSz -= burstSize;
tN = rcvClock.TN();
if (mSPSRx != 4)
burstSize = (symbolsPerSlot + (tN % 4 == 0)) * mSPSRx;
}
return 1;
}
bool RadioInterface::isUnderrun()
{
bool retVal;
/* atomically get previous value of "underrun" and set the var to false */
retVal = osmo_trx_sync_fetch_and_and(&underrun, false);
return retVal;
}
VectorFIFO* RadioInterface::receiveFIFO(size_t chan)
{
if (chan >= mReceiveFIFO.size())
return NULL;
return &mReceiveFIFO[chan];
}
double RadioInterface::setRxGain(double dB, size_t chan)
{
return mDevice->setRxGain(dB, chan);
}
double RadioInterface::rssiOffset(size_t chan)
{
return mDevice->rssiOffset(chan);
}
/* Receive a timestamped chunk from the device */
int RadioInterface::pullBuffer()
{
bool local_underrun;
int numRecv;
size_t segmentLen = recvBuffer[0]->getSegmentLen();
if (recvBuffer[0]->getFreeSegments() <= 0)
return -1;
/* Outer buffer access size is fixed */
numRecv = mDevice->readSamples(convertRecvBuffer,
segmentLen,
&overrun,
readTimestamp,
&local_underrun);
if ((size_t) numRecv != segmentLen) {
LOG(ALERT) << "Receive error " << numRecv;
return -1;
}
for (size_t i = 0; i < mChans; i++) {
convert_short_float(recvBuffer[i]->getWriteSegment(),
convertRecvBuffer[i],
segmentLen * 2);
}
osmo_trx_sync_or_and_fetch(&underrun, local_underrun);
readTimestamp += numRecv;
return 0;
}
/* Send timestamped chunk to the device with arbitrary size */
bool RadioInterface::pushBuffer()
{
bool local_underrun;
size_t numSent, segmentLen = sendBuffer[0]->getSegmentLen();
if (sendBuffer[0]->getAvailSegments() < 1)
return false;
for (size_t i = 0; i < mChans; i++) {
convert_float_short(convertSendBuffer[i],
(float *) sendBuffer[i]->getReadSegment(),
powerScaling[i],
segmentLen * 2);
}
/* Send the all samples in the send buffer */
numSent = mDevice->writeSamples(convertSendBuffer,
segmentLen,
&local_underrun,
writeTimestamp);
osmo_trx_sync_or_and_fetch(&underrun, local_underrun);
writeTimestamp += numSent;
return true;
}