460 lines
11 KiB
C++
460 lines
11 KiB
C++
/*
|
|
* Copyright 2018 Sergey Kostanbaev <sergey.kostanbaev@fairwaves.co>
|
|
* Copyright 2019 Alexander Chemeris <alexander.chemeris@fairwaves.co>
|
|
*
|
|
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 <stdint.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "Threads.h"
|
|
#include "XTRXDevice.h"
|
|
|
|
#include <Logger.h>
|
|
#include <errno.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
const double defaultRXBandwidth = 0.5e6;
|
|
const double defaultTXBandwidth = 1.5e6;
|
|
|
|
// Rx/Tx timestamp difference in samples.
|
|
// Transmitted IQ stream is sent this many samples ahead of the Rx stream
|
|
// to compensate for the frontend delay between Rx and Tx
|
|
static int time_tx_corr = 60;
|
|
|
|
XTRXDevice::XTRXDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset,
|
|
const std::vector<std::string>& tx_paths,
|
|
const std::vector<std::string>& rx_paths)
|
|
: RadioDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths)
|
|
{
|
|
LOG(INFO) << "creating XTRX device:"
|
|
<< " RXSPS: " << rx_sps
|
|
<< " TXSPS: " << tx_sps
|
|
<< " chans: " << chans
|
|
<< " lo_off: " << lo_offset
|
|
<< " rx_path(0): " << (rx_paths.size() ? rx_paths[0] : "<>")
|
|
<< " tx_path(0): " << (tx_paths.size() ? tx_paths[0] : "<>");
|
|
|
|
txsps = tx_sps;
|
|
rxsps = rx_sps;
|
|
|
|
rxGain = 0;
|
|
txGain = 0;
|
|
|
|
loopback = false;
|
|
device = NULL;
|
|
}
|
|
|
|
static int parse_config(const char* line, const char* argument, int default_value)
|
|
{
|
|
const char* arg_found = strstr(line, argument);
|
|
if (!arg_found)
|
|
return default_value;
|
|
|
|
const char* qe_pos = strchr(arg_found, '=');
|
|
if (!qe_pos)
|
|
return default_value;
|
|
|
|
int res = strtol(qe_pos + 1, NULL, 10);
|
|
if (res == 0 && errno) {
|
|
return default_value;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int XTRXDevice::open(const std::string &args, int ref, bool swap_channels)
|
|
{
|
|
LOG(INFO) << "Opening XTRX device '" << args << "'..";
|
|
|
|
int loglevel = parse_config(args.c_str(), "loglevel", 3);
|
|
int lb_param = parse_config(args.c_str(), "loopback", 0);
|
|
time_tx_corr = parse_config(args.c_str(), "tcorr", time_tx_corr);
|
|
int fref = parse_config(args.c_str(), "refclk", 26000000);
|
|
int rxdec = parse_config(args.c_str(), "rxdec", 0);
|
|
|
|
char xtrx_name[500];
|
|
const char* lend = strchr(args.c_str(), ',');
|
|
int len = (lend) ? (lend - args.c_str()) : sizeof(xtrx_name) - 1;
|
|
strncpy(xtrx_name, args.c_str(), len);
|
|
xtrx_name[len] = 0;
|
|
|
|
if ((txsps % 2) || (rxsps % 2)) {
|
|
LOG(ALERT) << "XTRX TxSPS/RxSPS must be even!";
|
|
return -1;
|
|
}
|
|
|
|
if (lb_param) {
|
|
LOG(ALERT) << "XTRX LOOPBACK mode is set!";
|
|
loopback = true;
|
|
}
|
|
|
|
int res = xtrx_open(xtrx_name, loglevel, &device);
|
|
if (res) {
|
|
LOG(ALERT) << "Failed openning XTRX device \"" << xtrx_name << "\", code " << res;
|
|
return -1;
|
|
}
|
|
double actualMasterClock = 0;
|
|
|
|
if (fref > 0) {
|
|
xtrx_set_ref_clk(device, fref, XTRX_CLKSRC_INT);
|
|
}
|
|
|
|
res = xtrx_set_samplerate(device,
|
|
GSMRATE * (double) std::min(txsps, rxsps) * 32 * 4 * ((rxdec) ? 2 : 1),
|
|
GSMRATE * (double) rxsps,
|
|
GSMRATE * (double) txsps,
|
|
(rxdec) ? XTRX_SAMPLERATE_FORCE_RX_DECIM : 0,
|
|
&actualMasterClock,
|
|
&actualRXSampleRate,
|
|
&actualTXSampleRate);
|
|
if (res) {
|
|
LOG(ALERT) << "XTRX failed to set samplerate RX: " << GSMRATE * (double) rxsps
|
|
<< " TX: " << GSMRATE * (double) txsps
|
|
<< " res: " << res;
|
|
return -1;
|
|
} else {
|
|
LOG(INFO) << "XTRX set samplerate Master: " << actualMasterClock
|
|
<< " RX: " << actualRXSampleRate
|
|
<< " TX: " << actualTXSampleRate;
|
|
}
|
|
|
|
double bw;
|
|
double actualbw;
|
|
|
|
actualbw = 0;
|
|
bw = defaultRXBandwidth;
|
|
res = xtrx_tune_rx_bandwidth(device, XTRX_CH_AB, bw, &actualbw);
|
|
if (res) {
|
|
LOG(ALERT) << "XTRX failed to set RX bandwidth: " << bw
|
|
<< " res: " << res;
|
|
} else {
|
|
LOG(INFO) << "XTRX set RX bandwidth: " << actualbw;
|
|
}
|
|
|
|
actualbw = 0;
|
|
bw = defaultTXBandwidth;
|
|
res = xtrx_tune_tx_bandwidth(device, XTRX_CH_AB, bw, &actualbw);
|
|
if (res) {
|
|
LOG(ALERT) << "XTRX failed to set TX bandwidth: " << bw
|
|
<< " res: " << res;
|
|
} else {
|
|
LOG(INFO) << "XTRX set TX bandwidth: " << actualbw;
|
|
}
|
|
|
|
samplesRead = 0;
|
|
samplesWritten = 0;
|
|
started = false;
|
|
|
|
return NORMAL;
|
|
}
|
|
|
|
XTRXDevice::~XTRXDevice()
|
|
{
|
|
if (device) {
|
|
xtrx_close(device);
|
|
}
|
|
}
|
|
|
|
bool XTRXDevice::start()
|
|
{
|
|
LOG(INFO) << "starting XTRX...";
|
|
if (started) {
|
|
LOGC(DDEV, ERROR) << "Device already started";
|
|
return false;
|
|
}
|
|
|
|
dataStart = 0;
|
|
dataEnd = 0;
|
|
timeStart = 0;
|
|
timeEnd = 0;
|
|
timeRx = initialReadTimestamp();
|
|
timestampOffset = 0;
|
|
latestWriteTimestamp = 0;
|
|
lastPktTimestamp = 0;
|
|
hi32Timestamp = 0;
|
|
isAligned = false;
|
|
|
|
xtrx_stop(device, XTRX_TX);
|
|
xtrx_stop(device, XTRX_RX);
|
|
|
|
xtrx_set_antenna(device, XTRX_TX_AUTO);
|
|
xtrx_set_antenna(device, XTRX_RX_AUTO);
|
|
|
|
xtrx_run_params_t params;
|
|
params.dir = XTRX_TRX;
|
|
params.nflags = (loopback) ? XTRX_RUN_DIGLOOPBACK : 0;
|
|
|
|
params.rx.chs = XTRX_CH_AB;
|
|
params.rx.flags = XTRX_RSP_SISO_MODE;
|
|
params.rx.hfmt = XTRX_IQ_INT16;
|
|
params.rx.wfmt = XTRX_WF_16;
|
|
params.rx.paketsize = 625 * rxsps;
|
|
|
|
params.tx.chs = XTRX_CH_AB;
|
|
params.tx.flags = XTRX_RSP_SISO_MODE;
|
|
params.tx.hfmt = XTRX_IQ_INT16;
|
|
params.tx.wfmt = XTRX_WF_16;
|
|
params.tx.paketsize = 625 * txsps;
|
|
|
|
if (loopback) {
|
|
params.tx.flags |= XTRX_RSP_SWAP_AB | XTRX_RSP_SWAP_IQ;
|
|
}
|
|
|
|
params.tx_repeat_buf = NULL;
|
|
params.rx_stream_start = initialReadTimestamp();
|
|
|
|
int res = xtrx_run_ex(device, ¶ms);
|
|
if (res) {
|
|
LOG(ALERT) << "XTRX start failed res: " << res;
|
|
} else {
|
|
LOG(INFO) << "XTRX started";
|
|
started = true;
|
|
}
|
|
return started;
|
|
}
|
|
|
|
bool XTRXDevice::stop()
|
|
{
|
|
if (started) {
|
|
int res = xtrx_stop(device, XTRX_TRX);
|
|
if (res) {
|
|
LOG(ALERT) << "XTRX stop failed res: " << res;
|
|
} else {
|
|
LOG(INFO) << "XTRX stopped";
|
|
started = false;
|
|
}
|
|
}
|
|
return !started;
|
|
}
|
|
|
|
TIMESTAMP XTRXDevice::initialWriteTimestamp()
|
|
{
|
|
if (/*(iface == MULTI_ARFCN) || */(rxsps == txsps))
|
|
return initialReadTimestamp();
|
|
else
|
|
return initialReadTimestamp() * txsps;
|
|
}
|
|
|
|
double XTRXDevice::maxTxGain()
|
|
{
|
|
return 30;
|
|
}
|
|
|
|
double XTRXDevice::minTxGain()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
double XTRXDevice::maxRxGain()
|
|
{
|
|
return 30;
|
|
}
|
|
|
|
double XTRXDevice::minRxGain()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
double XTRXDevice::setTxGain(double dB, size_t chan)
|
|
{
|
|
if (chan) {
|
|
LOG(ALERT) << "Invalid channel " << chan;
|
|
return 0.0;
|
|
}
|
|
LOG(NOTICE) << "Setting TX gain to " << dB << " dB.";
|
|
|
|
int res = xtrx_set_gain(device, XTRX_CH_AB, XTRX_TX_PAD_GAIN, dB - 30, &txGain);
|
|
if (res) {
|
|
LOG(ERR) << "Error setting TX gain res: " << res;
|
|
} else {
|
|
LOG(NOTICE) << "Actual TX gain: " << txGain << " dB.";
|
|
}
|
|
|
|
return txGain;
|
|
}
|
|
|
|
|
|
double XTRXDevice::setRxGain(double dB, size_t chan)
|
|
{
|
|
if (chan) {
|
|
LOG(ALERT) << "Invalid channel " << chan;
|
|
return 0.0;
|
|
}
|
|
LOG(NOTICE) << "Setting RX gain to " << dB << " dB.";
|
|
|
|
int res = xtrx_set_gain(device, XTRX_CH_AB, XTRX_RX_LNA_GAIN, dB, &rxGain);
|
|
if (res) {
|
|
LOG(ERR) << "Error setting RX gain res: " << res;
|
|
} else {
|
|
LOG(NOTICE) << "Actual RX gain: " << rxGain << " dB.";
|
|
}
|
|
|
|
return rxGain;
|
|
}
|
|
|
|
// NOTE: Assumes sequential reads
|
|
int XTRXDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
|
|
TIMESTAMP timestamp, bool *underrun, unsigned *RSSI)
|
|
{
|
|
if (!started)
|
|
return -1;
|
|
|
|
struct xtrx_recv_ex_info ri;
|
|
ri.samples = len;
|
|
ri.buffer_count = bufs.size();
|
|
ri.buffers = (void* const*)&bufs[0];
|
|
ri.flags = 0;
|
|
|
|
int res = xtrx_recv_sync_ex(device, &ri);
|
|
if (res) {
|
|
LOG(ALERT) << "xtrx_recv_sync failed res " << res << " current TS " << timeRx << " req TS" << timestamp;
|
|
return -1;
|
|
}
|
|
timeRx += len;
|
|
|
|
// TODO get rid of it!
|
|
int i;
|
|
for (i = 0; i < len * 2; i++)
|
|
bufs[0][i] <<= 4;
|
|
|
|
if (underrun)
|
|
*underrun = (ri.out_events & RCVEX_EVENT_FILLED_ZERO);
|
|
|
|
return len;
|
|
}
|
|
|
|
int XTRXDevice::writeSamples(std::vector<short *> &bufs, int len,
|
|
bool *underrun, unsigned long long timestamp,
|
|
bool isControl)
|
|
{
|
|
if (!started)
|
|
return 0;
|
|
|
|
xtrx_send_ex_info_t nfo;
|
|
nfo.buffers = (const void* const*)&bufs[0];
|
|
nfo.buffer_count = bufs.size();
|
|
nfo.flags = XTRX_TX_DONT_BUFFER;
|
|
nfo.samples = len;
|
|
nfo.ts = timestamp - time_tx_corr;
|
|
|
|
int res = xtrx_send_sync_ex(device, &nfo);
|
|
if (res != 0) {
|
|
LOG(ALERT) << "xtrx_send_sync_ex returned " << res << " len=" << len << " ts=" << timestamp;
|
|
return 0;
|
|
}
|
|
|
|
if (*underrun) {
|
|
*underrun = (nfo.out_flags & XTRX_TX_DISCARDED_TO);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
bool XTRXDevice::setRxAntenna(const std::string & ant, size_t chan)
|
|
{
|
|
LOG(ALERT) << "CH" << chan << ": RX ANTENNA: " << ant.c_str() << " (SETTING RX ANTENNA IS NOT IMPLEMENTED)";
|
|
return true;
|
|
}
|
|
|
|
std::string XTRXDevice::getRxAntenna(size_t chan)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
bool XTRXDevice::setTxAntenna(const std::string & ant, size_t chan)
|
|
{
|
|
LOG(ALERT) << "CH" << chan << ": TX ANTENNA: " << ant.c_str() << " (SETTING TX ANTENNA IS NOT IMPLEMENTED)";
|
|
return true;
|
|
}
|
|
|
|
std::string XTRXDevice::getTxAntenna(size_t chan )
|
|
{
|
|
return "";
|
|
}
|
|
|
|
|
|
bool XTRXDevice::requiresRadioAlign()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
GSM::Time XTRXDevice::minLatency()
|
|
{
|
|
return GSM::Time(6,7);
|
|
}
|
|
|
|
bool XTRXDevice::updateAlignment(TIMESTAMP timestamp)
|
|
{
|
|
LOG(ALERT) << "Update Aligment " << timestamp;
|
|
return true;
|
|
}
|
|
|
|
bool XTRXDevice::setTxFreq(double wFreq, size_t chan)
|
|
{
|
|
int res;
|
|
double actual = 0;
|
|
|
|
if (chan) {
|
|
LOG(ALERT) << "Invalid channel " << chan;
|
|
return false;
|
|
}
|
|
|
|
if ((res = xtrx_tune(device, XTRX_TUNE_TX_FDD, wFreq, &actual)) == 0) {
|
|
LOG(INFO) << "set RX: " << wFreq << std::endl
|
|
<< " actual freq: " << actual << std::endl;
|
|
return true;
|
|
}
|
|
else {
|
|
LOG(ALERT) << "set RX: " << wFreq << "failed (code: " << res << ")" << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool XTRXDevice::setRxFreq(double wFreq, size_t chan)
|
|
{
|
|
int res;
|
|
double actual = 0;
|
|
|
|
if (chan) {
|
|
LOG(ALERT) << "Invalid channel " << chan;
|
|
return false;
|
|
}
|
|
|
|
if ((res = xtrx_tune(device, XTRX_TUNE_RX_FDD, wFreq, &actual)) == 0) {
|
|
LOG(INFO) << "set RX: " << wFreq << std::endl
|
|
<< " actual freq: " << actual << std::endl;
|
|
return true;
|
|
}
|
|
else {
|
|
LOG(ALERT) << "set RX: " << wFreq << "failed (code: " << res << ")" << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
|
|
InterfaceType iface, size_t chans, double lo_offset,
|
|
const std::vector < std::string > &tx_paths,
|
|
const std::vector < std::string > &rx_paths)
|
|
{
|
|
return new XTRXDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
|
|
}
|