mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-trx.git
synced 2025-11-02 21:23:16 +00:00
The reason is to allow them to be started from any TRX. I.e. they are started when the first TRX starts and stopped when the last TRX stops.
367 lines
8.7 KiB
C++
367 lines
8.7 KiB
C++
/*
|
|
* Copyright 2008, 2009, 2012 Free Software Foundation, Inc.
|
|
*
|
|
* This software is distributed under the terms of the GNU Affero Public License.
|
|
* See the COPYING file in the main directory for details.
|
|
*
|
|
* This use of this software may be subject to additional restrictions.
|
|
* See the LEGAL file in the main directory for details.
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include "radioInterface.h"
|
|
#include <Logger.h>
|
|
|
|
bool started = false;
|
|
|
|
/* Device side buffers */
|
|
static short *rx_buf[CHAN_MAX];
|
|
static short *tx_buf[CHAN_MAX];
|
|
|
|
/* Complex float to short conversion */
|
|
static void floatToShort(short *out, float *in, int num)
|
|
{
|
|
for (int i = 0; i < num; i++) {
|
|
out[2 * i + 0] = (short) in[2 * i + 0];
|
|
out[2 * i + 1] = (short) in[2 * i + 1];
|
|
}
|
|
}
|
|
|
|
/* Complex short to float conversion */
|
|
static void shortToFloat(float *out, short *in, int num)
|
|
{
|
|
for (int i = 0; i < num; i++) {
|
|
out[2 * i + 0] = (float) in[2 * i + 0];
|
|
out[2 * i + 1] = (float) in[2 * i + 1];
|
|
}
|
|
}
|
|
|
|
RadioInterface::RadioInterface(RadioDevice *wRadio,
|
|
int wChanM,
|
|
int wSPS,
|
|
int wReceiveOffset,
|
|
GSM::Time wStartTime)
|
|
: mChanM(wChanM), underrun(false), sendCursor(0), rcvCursor(0), mOn(false),
|
|
mUseCount(0),
|
|
mRadio(wRadio), receiveOffset(wReceiveOffset), samplesPerSymbol(wSPS),
|
|
powerScaling(1.0), loadTest(false)
|
|
{
|
|
mClock.set(wStartTime);
|
|
}
|
|
|
|
RadioInterface::~RadioInterface(void)
|
|
{
|
|
if (mOn) {
|
|
mRadio->stop();
|
|
close();
|
|
|
|
for (int i = 0; i < mChanM; i++) {
|
|
if (rcvBuffer[i] != NULL)
|
|
delete rcvBuffer[i];
|
|
if (sendBuffer[i] != NULL)
|
|
delete sendBuffer[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
double RadioInterface::fullScaleInputValue(void) {
|
|
return mRadio->fullScaleInputValue();
|
|
}
|
|
|
|
double RadioInterface::fullScaleOutputValue(void) {
|
|
return mRadio->fullScaleOutputValue();
|
|
}
|
|
|
|
|
|
void RadioInterface::setPowerAttenuation(double atten, int chan)
|
|
{
|
|
double rfGain, digAtten;
|
|
|
|
rfGain = mRadio->setTxGain(mRadio->maxTxGain() - atten, chan);
|
|
digAtten = atten - mRadio->maxTxGain() + rfGain;
|
|
|
|
if (digAtten < 1.0)
|
|
powerScaling = 1.0;
|
|
else
|
|
powerScaling = 1.0/sqrt(pow(10, (digAtten/10.0)));
|
|
}
|
|
|
|
int RadioInterface::radioifyVector(signalVector &wVector,
|
|
float *retVector,
|
|
float scale,
|
|
bool zero)
|
|
{
|
|
int i;
|
|
signalVector::iterator itr = wVector.begin();
|
|
|
|
if (zero) {
|
|
memset(retVector, 0, wVector.size() * 2 * sizeof(float));
|
|
return wVector.size();
|
|
}
|
|
|
|
for (i = 0; i < wVector.size(); i++) {
|
|
retVector[2 * i + 0] = itr->real() * scale;
|
|
retVector[2 * i + 1] = itr->imag() * scale;
|
|
itr++;
|
|
}
|
|
|
|
return wVector.size();
|
|
}
|
|
|
|
int RadioInterface::unRadioifyVector(float *floatVector, int offset,
|
|
signalVector &newVector)
|
|
{
|
|
int i;
|
|
signalVector::iterator itr = newVector.begin();
|
|
|
|
for (i = 0; i < newVector.size(); i++) {
|
|
*itr++ = Complex<float>(floatVector[offset + 2 * i + 0],
|
|
floatVector[offset + 2 * i + 1]);
|
|
}
|
|
|
|
return newVector.size();
|
|
}
|
|
|
|
bool RadioInterface::tuneTx(double freq, int chan)
|
|
{
|
|
return mRadio->setTxFreq(freq, chan);
|
|
}
|
|
|
|
bool RadioInterface::tuneRx(double freq, int chan)
|
|
{
|
|
return mRadio->setRxFreq(freq, chan);
|
|
}
|
|
|
|
|
|
bool RadioInterface::start()
|
|
{
|
|
// Use count must not be negative
|
|
assert(mUseCount>=0);
|
|
// Being on while mUseCount is 0 is a wrong condition
|
|
assert(!(mOn && mUseCount==0));
|
|
|
|
mUseCount++;
|
|
if (mOn || mUseCount>1)
|
|
return false;
|
|
|
|
mOn = true;
|
|
#ifdef USRP1
|
|
mAlignRadioServiceLoopThread = new Thread(32768);
|
|
mAlignRadioServiceLoopThread->start((void * (*)(void*))AlignRadioServiceLoopAdapter,
|
|
(void*)this);
|
|
#endif
|
|
writeTimestamp = mRadio->initialWriteTimestamp();
|
|
readTimestamp = mRadio->initialReadTimestamp();
|
|
for (int i = 0; i < mChanM; i++) {
|
|
sendBuffer[i] = new float[8*2*INCHUNK];
|
|
rcvBuffer[i] = new float[8*2*OUTCHUNK];
|
|
}
|
|
|
|
/* Init I/O specific variables if applicable */
|
|
init();
|
|
|
|
mRadio->start();
|
|
LOG(DEBUG) << "Radio started";
|
|
mRadio->updateAlignment(writeTimestamp-10000);
|
|
mRadio->updateAlignment(writeTimestamp-10000);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool RadioInterface::stop()
|
|
{
|
|
// Use count must not be negative or zero
|
|
assert(mUseCount>0);
|
|
// Must be on while stopping
|
|
assert(mOn);
|
|
|
|
mUseCount--;
|
|
if (mUseCount>0)
|
|
return false;
|
|
|
|
mOn = false;
|
|
mRadio->stop();
|
|
return true;
|
|
}
|
|
|
|
#ifdef USRP1
|
|
void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface)
|
|
{
|
|
while (radioInterface->on()) {
|
|
radioInterface->alignRadio();
|
|
pthread_testcancel();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void RadioInterface::alignRadio() {
|
|
sleep(60);
|
|
mRadio->updateAlignment(writeTimestamp+ (TIMESTAMP) 10000);
|
|
}
|
|
#endif
|
|
|
|
void RadioInterface::driveTransmitRadio(signalVector **radioBurst, bool *zeroBurst)
|
|
{
|
|
int i;
|
|
|
|
if (!mOn)
|
|
return;
|
|
|
|
for (i = 0; i < mChanM; i++) {
|
|
radioifyVector(*radioBurst[i], sendBuffer[i] + 2 * sendCursor,
|
|
powerScaling, zeroBurst[i]);
|
|
}
|
|
|
|
/*
|
|
* All bursts should be the same size since all transceivers are
|
|
* tied with a single clock in the radio interface.
|
|
*/
|
|
sendCursor += radioBurst[0]->size();
|
|
|
|
pushBuffer();
|
|
}
|
|
|
|
static inline void shiftRxBuffers(float **buf, int offset, int len, int chanM)
|
|
{
|
|
for (int i = 0; i < chanM; i++)
|
|
memmove(buf[i], buf[i] + offset, sizeof(float) * len);
|
|
}
|
|
|
|
void RadioInterface::loadVectors(unsigned tN, int samplesPerBurst,
|
|
int idx, GSM::Time rxClock)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < mChanM; i++) {
|
|
signalVector rxVector(samplesPerBurst);
|
|
unRadioifyVector(rcvBuffer[i], idx * 2, rxVector);
|
|
radioVector *rxBurst = new radioVector(rxVector, rxClock);
|
|
mReceiveFIFO[i].write(rxBurst);
|
|
}
|
|
}
|
|
|
|
void RadioInterface::driveReceiveRadio()
|
|
{
|
|
if (!mOn)
|
|
return;
|
|
|
|
if (mReceiveFIFO[0].size() > 8)
|
|
return;
|
|
|
|
pullBuffer();
|
|
|
|
GSM::Time rcvClock = mClock.get();
|
|
rcvClock.decTN(receiveOffset);
|
|
unsigned tN = rcvClock.TN();
|
|
int rcvSz = rcvCursor;
|
|
int readSz = 0;
|
|
const int symbolsPerSlot = gSlotLen + 8;
|
|
int samplesPerBurst = (symbolsPerSlot + (tN % 4 == 0)) * samplesPerSymbol;
|
|
|
|
// while there's enough data in receive buffer, form received
|
|
// GSM bursts and pass up to Transceiver
|
|
// Using the 157-156-156-156 symbols per timeslot format.
|
|
while (rcvSz >= samplesPerBurst) {
|
|
if (rcvClock.FN() >= 0) {
|
|
loadVectors(tN, samplesPerBurst, readSz, rcvClock);
|
|
}
|
|
|
|
mClock.incTN();
|
|
rcvClock.incTN();
|
|
|
|
readSz += samplesPerBurst;
|
|
rcvSz -= samplesPerBurst;
|
|
|
|
tN = rcvClock.TN();
|
|
samplesPerBurst = (symbolsPerSlot + (tN % 4 == 0)) * samplesPerSymbol;
|
|
}
|
|
|
|
if (readSz > 0) {
|
|
rcvCursor -= readSz;
|
|
shiftRxBuffers(rcvBuffer, 2 * readSz, 2 * rcvCursor, mChanM);
|
|
}
|
|
}
|
|
|
|
double RadioInterface::setRxGain(double dB, int chan)
|
|
{
|
|
if (mRadio)
|
|
return mRadio->setRxGain(dB, chan);
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
double RadioInterface::getRxGain(int chan)
|
|
{
|
|
if (mRadio)
|
|
return mRadio->getRxGain(chan);
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
bool RadioInterface::init()
|
|
{
|
|
for (int i = 0; i < CHAN_MAX; i++) {
|
|
rx_buf[i] = new short[2 * OUTCHUNK];
|
|
tx_buf[i] = new short[4 * 2 * INCHUNK];
|
|
}
|
|
}
|
|
|
|
void RadioInterface::close()
|
|
{
|
|
for (int i = 0; i < CHAN_MAX; i++) {
|
|
delete rx_buf[i];
|
|
delete tx_buf[i];
|
|
}
|
|
}
|
|
|
|
/* Receive a timestamped chunk from the device */
|
|
void RadioInterface::pullBuffer()
|
|
{
|
|
bool local_underrun;
|
|
|
|
/* Read samples. Fail if we don't get what we want. */
|
|
int num_rd = mRadio->readSamples(rx_buf, mChanM, OUTCHUNK, readTimestamp);
|
|
|
|
LOG(DEBUG) << "Rx read " << num_rd << " samples from device";
|
|
assert(num_rd == OUTCHUNK);
|
|
|
|
underrun |= local_underrun;
|
|
readTimestamp += (TIMESTAMP) num_rd;
|
|
|
|
for (int i = 0; i < mChanM; i++)
|
|
shortToFloat(rcvBuffer[i] + 2 * rcvCursor, rx_buf[i], num_rd);
|
|
|
|
rcvCursor += num_rd;
|
|
}
|
|
|
|
/* Send timestamped chunk to the device with arbitrary size */
|
|
void RadioInterface::pushBuffer()
|
|
{
|
|
if (sendCursor < INCHUNK)
|
|
return;
|
|
|
|
for (int i = 0; i < mChanM; i++)
|
|
floatToShort(tx_buf[i], sendBuffer[i], sendCursor);
|
|
|
|
/* Write samples. Fail if we don't get what we want. */
|
|
int num_smpls = mRadio->writeSamples(tx_buf, mChanM, sendCursor,
|
|
writeTimestamp, &underrun);
|
|
assert(num_smpls == sendCursor);
|
|
|
|
writeTimestamp += (TIMESTAMP) num_smpls;
|
|
sendCursor = 0;
|
|
}
|