/* * 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) : mDevice(wDevice), mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), 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; } close(); sendBuffer.resize(mChans); recvBuffer.resize(mChans); convertSendBuffer.resize(mChans); convertRecvBuffer.resize(mChans); mReceiveFIFO.resize(mChans); powerScaling.resize(mChans); for (size_t i = 0; i < mChans; i++) { sendBuffer[i] = new RadioBuffer(NUMCHUNKS, CHUNK * mSPSTx, 0, true); recvBuffer[i] = new RadioBuffer(NUMCHUNKS, CHUNK * mSPSRx, 0, false); 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); } /* 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; }