Timur Davydov has uploaded this change for review. (
https://gerrit.osmocom.org/c/osmo-trx/+/42198?usp=email )
Change subject: feat(usdr): add USDR backend support (osmo-trx-usdr)
......................................................................
feat(usdr): add USDR backend support (osmo-trx-usdr)
- Implement USDRDevice backend for Transceiver52M
- Add --with-usdr configure option and build integration
- Add systemd service unit (osmo-trx-usdr.service)
- Add Debian packaging files for osmo-trx-usdr
- Provide example configuration file
- Extend manuals and device documentation with USDR backend section
Change-Id: I5d1d25921514954c4929ae6e7352168b3ceb05df
---
M .gitignore
M Transceiver52M/Makefile.am
M Transceiver52M/device/Makefile.am
A Transceiver52M/device/usdr/Makefile.am
A Transceiver52M/device/usdr/USDRDevice.cpp
A Transceiver52M/device/usdr/USDRDevice.h
M configure.ac
M contrib/systemd/Makefile.am
A contrib/systemd/osmo-trx-usdr.service
A debian/osmo-trx-usdr.install
A debian/osmo-trx-usdr.postinst
M doc/examples/Makefile.am
A doc/examples/osmo-trx-usdr/osmo-trx-usdr.cfg
M doc/manuals/Makefile.am
M doc/manuals/chapters/trx-backends.adoc
M doc/manuals/chapters/trx-devices.adoc
16 files changed, 946 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-trx refs/changes/98/42198/1
diff --git a/.gitignore b/.gitignore
index fc567db..d43dd9c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
*.lo
*.la
Transceiver52M/osmo-trx-uhd
+Transceiver52M/osmo-trx-usdr
Transceiver52M/osmo-trx-usrp1
Transceiver52M/osmo-trx-lms
Transceiver52M/osmo-trx-ipc
diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am
index 0b63b16..1317de7 100644
--- a/Transceiver52M/Makefile.am
+++ b/Transceiver52M/Makefile.am
@@ -139,6 +139,16 @@
#endif
endif
+if DEVICE_USDR
+bin_PROGRAMS += osmo-trx-usdr
+osmo_trx_usdr_SOURCES = osmo-trx.cpp
+osmo_trx_usdr_LDADD = \
+ $(builddir)/device/usdr/libdevice.la \
+ $(COMMON_LDADD) \
+ $(USDR_LIBS)
+osmo_trx_usdr_CPPFLAGS = $(AM_CPPFLAGS) $(USDR_CFLAGS)
+endif
+
if DEVICE_USRP1
bin_PROGRAMS += osmo-trx-usrp1
osmo_trx_usrp1_SOURCES = osmo-trx.cpp
diff --git a/Transceiver52M/device/Makefile.am
b/Transceiver52M/device/Makefile.am
index 9af18f7..69c8fd4 100644
--- a/Transceiver52M/device/Makefile.am
+++ b/Transceiver52M/device/Makefile.am
@@ -6,6 +6,10 @@
SUBDIRS += ipc
endif
+if DEVICE_USDR
+SUBDIRS += usdr
+endif
+
if DEVICE_USRP1
SUBDIRS += usrp1
endif
diff --git a/Transceiver52M/device/usdr/Makefile.am
b/Transceiver52M/device/usdr/Makefile.am
new file mode 100644
index 0000000..f679283
--- /dev/null
+++ b/Transceiver52M/device/usdr/Makefile.am
@@ -0,0 +1,12 @@
+include $(top_srcdir)/Makefile.common
+
+AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(USDR_CFLAGS)
+#AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS)
$(LIBOSMOVTY_CFLAGS) $(USDR_CFLAGS)
+
+noinst_HEADERS = USDRDevice.h
+
+noinst_LTLIBRARIES = libdevice.la
+
+libdevice_la_SOURCES = USDRDevice.cpp
+libdevice_la_LIBADD =
$(top_builddir)/Transceiver52M/device/common/libdevice_common.la
diff --git a/Transceiver52M/device/usdr/USDRDevice.cpp
b/Transceiver52M/device/usdr/USDRDevice.cpp
new file mode 100644
index 0000000..8210d5a
--- /dev/null
+++ b/Transceiver52M/device/usdr/USDRDevice.cpp
@@ -0,0 +1,566 @@
+/*
+* Copyright 2026 Wavelet Lab <[email protected]>
+*
+* SPDX-License-Identifier: AGPL-3.0+
+*
+* 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 <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <string>
+#include <array>
+#include <cmath>
+
+#include "Threads.h"
+#include "USDRDevice.h"
+
+#include <Logger.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+using namespace std::chrono_literals;
+
+static const DeviceParametersMap deviceParametersMap = {
+ {"usdr", {tx_offset: 14.769us}}, /* 16 samples at 1083333.33333 sps
corresponds to 14.769 us */
+ {"ssdr", {tx_offset: 70.154us}}, /* 76 samples at 1083333.33333
sps corresponds to 70.154 us */
+ {"limemini", {tx_offset: 86.769us}}, /* 94 samples at 1083333.33333 sps
corresponds to 86.769 us */
+};
+
+enum {
+ /* Number of initial samples to skip after starting the device, to
allow it to stabilize and avoid sending
+ corrupted samples to the air. The value of 20000 samples corresponds to
approximately 18.5 ms
+ at a sample rate of 1083333.33333 sps, which should be sufficient for
the device to stabilize. */
+ USDR_SKIP_TX_SAMPLES = 20000,
+ /* Timeout for sending samples to USDR device, in milliseconds */
+ USDR_SEND_TIMEOUT_MS = 5000,
+};
+
+/* Implementation of helper functions for USDR API calls with exception
handling */
+
+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;
+}
+
+static void usdr_dmd_create_string_exception(const char* connection_string,
pdm_dev_t* odev)
+{
+ int res = usdr_dmd_create_string(connection_string, odev);
+ if (res) {
+ std::string msg = std::string("Error creating USDR device with
connection string '") + connection_string + "' res: " + std::to_string(res) +
"(" + strerror(res) + ")";
+ throw std::runtime_error(msg);
+ }
+}
+
+static void usdr_dmr_rate_set_exception(pdm_dev_t dev, const char* rate_name,
unsigned rate)
+{
+ int res = usdr_dmr_rate_set(dev, rate_name, rate);
+ if (res) {
+ const char *rate_name_safe = rate_name ? rate_name : "(null)";
+ std::string msg = std::string("Error setting USDR rate for '")
+ rate_name_safe + "' to " + std::to_string(rate) + " res: " +
std::to_string(res) + "(" + strerror(res) + ")";
+ throw std::runtime_error(msg);
+ }
+}
+
+static void usdr_dme_set_uint_exception(pdm_dev_t dev, const char* path,
uint64_t val)
+{
+ const int res = usdr_dme_set_uint(dev, path, val);
+ if (res) {
+ const char *path_safe = path ? path : "(null)";
+ std::string msg = std::string("Error setting ") + path_safe + "
to " + std::to_string(val) + " res: " + std::to_string(res) + "(" +
strerror(res) + ")";
+ throw std::runtime_error(msg);
+ }
+}
+
+static void usdr_dme_get_uint_exception(pdm_dev_t dev, const char* path,
uint64_t* val)
+{
+ const int res = usdr_dme_get_uint(dev, path, val);
+ if (res) {
+ const char *path_safe = path ? path : "(null)";
+ std::string msg = std::string("Error getting ") + path_safe + "
res: " + std::to_string(res) + "(" + strerror(res) + ")";
+ throw std::runtime_error(msg);
+ }
+}
+
+static void usdr_dms_create_exception(
+ pdm_dev_t device, const char* sobj, const char* dformat,
+ logical_ch_msk_t channels, unsigned pktsyms, pusdr_dms_t* outu
+)
+{
+ const int res = usdr_dms_create(device, sobj, dformat, channels,
pktsyms, outu);
+ if (res) {
+ const char *sobj_safe = sobj ? sobj : "(null)";
+ std::string msg = std::string("Error creating DMS for ") +
sobj_safe + " res: " + std::to_string(res) + "(" + strerror(res) + ")";
+ throw std::runtime_error(msg);
+ }
+}
+
+static void usdr_dms_info_exception(pusdr_dms_t stream, usdr_dms_nfo_t* nfo)
+{
+ const int res = usdr_dms_info(stream, nfo);
+ if (res) {
+ std::string msg = std::string("Error getting DMS info res: ") +
std::to_string(res) + "(" + strerror(res) + ")";
+ throw std::runtime_error(msg);
+ }
+}
+
+static void usdr_dms_op_exception(pusdr_dms_t stream, unsigned command,
dm_time_t tm)
+{
+ const int res = usdr_dms_op(stream, command, tm);
+ if (res) {
+ std::string msg = std::string("Error doing DMS op command: ") +
std::to_string(command) + " res: " + std::to_string(res) + "(" + strerror(res)
+ ")";
+ throw std::runtime_error(msg);
+ }
+}
+
+static void usdr_dms_sync_exception(pdm_dev_t device, const char* synctype,
unsigned scount, pusdr_dms_t *pstream)
+{
+ const int res = usdr_dms_sync(device, synctype, scount, pstream);
+ if (res) {
+ const char *synctype_safe = synctype ? synctype : "(null)";
+ std::string msg = std::string("Error syncing DMS for synctype
") + synctype_safe + " res: " + std::to_string(res) + "(" + strerror(res) + ")";
+ throw std::runtime_error(msg);
+ }
+}
+
+static bool usdr_check_channel(size_t chan)
+{
+ if (chan) {
+ LOGC(DDEV, ERROR) << "Invalid channel " << chan;
+ return false;
+ }
+ return true;
+}
+
+/* Implementation of USDRDevice class methods */
+
+USDRDevice::USDRDevice(InterfaceType iface, const struct trx_cfg *cfg) :
+ RadioDevice(iface, cfg)
+{
+ LOGC(DDEV, INFO) << "Creating USDR device... SPS=" << cfg->tx_sps << "/"
<< cfg->rx_sps;
+
+ this->txsps = cfg->tx_sps;
+ this->rxsps = cfg->rx_sps;
+
+ dev = NULL;
+}
+
+int USDRDevice::open()
+{
+ const char* args = cfg->dev_args ? cfg->dev_args : "";
+ LOGC(DDEV, INFO) << "Opening USDR device ARGS: " << args;
+
+ const int loglevel = parse_config(args, "loglevel", 3);
+ usdrlog_setlevel(NULL, loglevel);
+
+ try {
+ /* Create the USDR device */
+ usdr_dmd_create_string_exception(args, &dev);
+
+ /* Set the sample rate */
+ const double rate = 0.27083333333e6 * txsps;
+ usdr_dmr_rate_set_exception(dev, NULL,
static_cast<unsigned>(std::lround(rate)));
+
+ /* Get custom device time_tx_corr if available */
+ uint64_t val;
+ const int res = usdr_dme_get_uint(dev, "/ll/device/name", &val);
+ if (res == 0 && val != 0) {
+ /* Some USDR APIs return a pointer encoded as an
integer. Cast via uintptr_t to be explicit.
+ This is legacy and depends on the underlying
library; guard usage. */
+ const char* device = reinterpret_cast<const
char*>(static_cast<uintptr_t>(val));
+ LOGC(DDEV, INFO) << "Device name is \"" << device <<
"\"";
+
+ const auto it = deviceParametersMap.find(device);
+ if (it != deviceParametersMap.end()) {
+ /* convert time offset in seconds to samples
using the actual sample rate */
+ timeTxCorr =
static_cast<int>(std::lround(it->second.tx_offset.count() * rate));
+ LOGC(DDEV, INFO) << "Time TX correction for
device \"" << device << "\" is " << timeTxCorr << " samples";
+ }
+ }
+
+ /* Set TX and RX gains */
+ usdr_dme_set_uint_exception(dev, "/dm/sdr/0/tx/gain", 0);
+ usdr_dme_set_uint_exception(dev, "/dm/sdr/0/rx/gain", 15);
+
+ /* Set TX and RX bandwidth */
+ usdr_dme_set_uint_exception(dev, "/dm/sdr/0/tx/bandwidth",
1'000'000ULL);
+ usdr_dme_set_uint_exception(dev, "/dm/sdr/0/rx/bandwidth",
500'000ULL);
+
+ const char* fmt = "ci16";
+ const unsigned chmsk = 0x1;
+ const unsigned samples = 625 * txsps;
+
+ /* Create DMS streams for Tx */
+ usdr_dms_create_exception(dev, "/ll/stx/0", fmt, chmsk,
samples, &usds_tx);
+ usdr_dms_info_exception(usds_tx, &snfo_tx);
+
+ /* Create DMS streams for Rx */
+ usdr_dms_create_exception(dev, "/ll/srx/0", fmt, chmsk,
samples, &usds_rx);
+ usdr_dms_info_exception(usds_rx, &snfo_rx);
+
+ /* Stop the DMS streams to prevent them from running before
start() is called */
+ usdr_dms_op_exception(usds_rx, USDR_DMS_STOP, 0);
+ usdr_dms_op_exception(usds_tx, USDR_DMS_STOP, 0);
+
+ /* Sync the DMS streams */
+ std::array<pusdr_dms_t, 2> ped = { usds_rx, usds_tx };
+ usdr_dms_sync_exception(dev, "rx", ped.size(), ped.data());
+
+ actualSampleRate = rate;
+ started = false;
+
+ return NORMAL;
+ } catch (const std::exception& e) {
+ LOGC(DDEV, ERROR) << e.what();
+ return -1;
+ }
+}
+
+USDRDevice::~USDRDevice()
+{
+ if (dev) {
+ usdr_dms_op(usds_rx, USDR_DMS_STOP, 0);
+ usdr_dms_op(usds_tx, USDR_DMS_STOP, 0);
+ }
+}
+bool USDRDevice::start()
+{
+ LOGC(DDEV, INFO) << "Starting USDR...";
+ if (started)
+ return false;
+
+ try {
+ usdr_dms_op_exception(usds_tx, USDR_DMS_START, 0);
+ usdr_dms_op_exception(usds_rx, USDR_DMS_START, 0);
+ } catch (const std::exception& e) {
+ LOGC(DDEV, ERROR) << e.what();
+ return false;
+ }
+
+ started = true;
+ return started;
+}
+
+bool USDRDevice::stop()
+{
+ if (started) {
+ try {
+ usdr_dms_op_exception(usds_rx, USDR_DMS_STOP, 0);
+ usdr_dms_op_exception(usds_tx, USDR_DMS_STOP, 0);
+
+ started = false;
+ } catch(const std::exception& e) {
+ LOGC(DDEV, ERROR) << e.what();
+ return false;
+ }
+ }
+ return !started;
+}
+
+TIMESTAMP USDRDevice::initialWriteTimestamp()
+{
+ if (rxsps == txsps)
+ return initialReadTimestamp();
+ else
+ return initialReadTimestamp() * txsps;
+}
+
+double USDRDevice::maxTxGain()
+{
+ return 30;
+}
+
+double USDRDevice::minTxGain()
+{
+ return 0;
+}
+
+double USDRDevice::maxRxGain()
+{
+ return 30;
+}
+
+double USDRDevice::minRxGain()
+{
+ return 0;
+}
+
+double USDRDevice::setTxGain(double dB, size_t chan)
+{
+ if (!usdr_check_channel(chan))
+ return 0.0;
+
+ uint64_t actual = 0;
+
+ LOGC(DDEV, INFO) << "Setting TX gain to " << dB << " dB";
+
+ try {
+ usdr_dme_set_uint_exception(dev, "/dm/sdr/0/tx/gain", dB);
+ usdr_dme_get_uint_exception(dev, "/dm/sdr/0/tx/gain", &actual);
+ } catch (const std::exception& e) {
+ LOGC(DDEV, ERROR) << e.what();
+ }
+
+ return static_cast<double>(actual);
+}
+
+
+double USDRDevice::setRxGain(double dB, size_t chan)
+{
+ if (!usdr_check_channel(chan))
+ return 0.0;
+
+ uint64_t actual = 0;
+
+ LOGC(DDEV, INFO) << "Setting RX gain to " << dB << " dB";
+
+ try {
+ usdr_dme_set_uint_exception(dev, "/dm/sdr/0/rx/gain", dB);
+ usdr_dme_get_uint_exception(dev, "/dm/sdr/0/rx/gain", &actual);
+ } catch (const std::exception& e) {
+ LOGC(DDEV, ERROR) << e.what();
+ }
+
+ return static_cast<double>(actual);
+}
+
+int USDRDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
+ TIMESTAMP timestamp, bool *underrun)
+{
+ if (!started)
+ return -1;
+
+ struct usdr_dms_recv_nfo ri;
+ int res = usdr_dms_recv(usds_rx, (void**)&bufs[0], len, &ri);
+ if (res) {
+ LOGC(DDEV, ERROR) << "usdr_dms_recv failed res " << res << " dev TS "
<< ri.fsymtime << " req TS" << timestamp;
+ return -1;
+ }
+
+ unsigned diff = ri.fsymtime - ts_last_recv;
+ ts_last_recv = ri.fsymtime + len;
+ if (ts_initial == 0)
+ ts_initial = ri.fsymtime;
+
+ LOGC(DDEV, DEBUG) << "usdr_dms_recv TS=" << ri.fsymtime << " diff=" <<
diff;
+
+ if (underrun)
+ *underrun = (diff != 0) ? true : false;
+ if (overrun)
+ *overrun = false;
+
+ return len;
+
+}
+
+int USDRDevice::writeSamples(std::vector<short *> &bufs, int len,
+ bool *underrun,
TIMESTAMP timestamp)
+{
+ if (!started)
+ return 0;
+
+ /* skip USDR_SKIP_TX_SAMPLES samples after start */
+ if (timestamp < USDR_SKIP_TX_SAMPLES)
+ return len;
+
+ LOGC(DDEV, DEBUG) << "writeSamples TS=" << timestamp - timeTxCorr << "
len=" << len;
+
+ int res = usdr_dms_send(usds_tx, (const void**)&bufs[0], len, timestamp -
timeTxCorr, USDR_SEND_TIMEOUT_MS);
+ if (res) {
+ LOGC(DDEV, ERROR) << "USDR_send_sync_ex returned " << res << " len="
<< len << " ts=" << timestamp;
+ return 0;
+ }
+
+ return len;
+}
+
+bool USDRDevice::setRxAntenna(const std::string & ant, size_t chan)
+{
+ if (!usdr_check_channel(chan))
+ return false;
+
+ LOGC(DDEV, INFO) << "Setting RX antenna at channel " << chan << " to "
<< ant;
+
+ return true;
+}
+
+std::string USDRDevice::getRxAntenna(size_t chan)
+{
+ if (!usdr_check_channel(chan))
+ return "";
+
+ return "";
+}
+
+bool USDRDevice::setTxAntenna(const std::string & ant, size_t chan)
+{
+ if (!usdr_check_channel(chan))
+ return false;
+
+ LOGC(DDEV, INFO) << "Setting TX antenna at channel " << chan << " to "
<< ant;
+
+ return true;
+}
+
+std::string USDRDevice::getTxAntenna(size_t chan )
+{
+ if (!usdr_check_channel(chan))
+ return "";
+
+ return "";
+}
+
+bool USDRDevice::requiresRadioAlign()
+{
+ return false;
+}
+
+GSM::Time USDRDevice::minLatency()
+{
+ return GSM::Time(6,7);
+}
+
+bool USDRDevice::updateAlignment(TIMESTAMP timestamp)
+{
+ return true;
+}
+
+bool USDRDevice::setTxFreq(double freq, size_t chan)
+{
+ if (!usdr_check_channel(chan))
+ return false;
+
+ LOGC(DDEV, INFO) << "Setting TX frequency at channel " << chan << " to
" << freq * 1e-6 << " MHz";
+
+ try {
+ usdr_dme_set_uint_exception(dev, "/dm/sdr/0/tx/frequency",
static_cast<uint64_t>(freq));
+ } catch (const std::exception& e) {
+ LOGC(DDEV, ERROR) << e.what();
+ return false;
+ }
+
+ return true;
+}
+
+double USDRDevice::getTxFreq(size_t chan)
+{
+ uint64_t freq;
+
+ if (!usdr_check_channel(chan))
+ return 0;
+
+ try {
+ usdr_dme_get_uint_exception(dev, "/dm/sdr/0/tx/frequency",
&freq);
+ } catch (const std::exception& e) {
+ LOGC(DDEV, ERROR) << e.what();
+ return 0;
+ }
+
+ return static_cast<double>(freq);
+}
+
+bool USDRDevice::setRxFreq(double freq, size_t chan)
+{
+ if (!usdr_check_channel(chan))
+ return false;
+
+ LOGC(DDEV, INFO) << "Setting RX frequency at channel " << chan << " to
" << freq * 1e-6 << " MHz";
+
+ try {
+ usdr_dme_set_uint_exception(dev, "/dm/sdr/0/rx/frequency",
static_cast<uint64_t>(freq));
+ } catch (const std::exception& e) {
+ LOGC(DDEV, ERROR) << e.what();
+ return false;
+ }
+
+ return true;
+}
+
+double USDRDevice::getRxFreq(size_t chan)
+{
+ uint64_t freq;
+
+ if (!usdr_check_channel(chan))
+ return 0;
+
+ try {
+ usdr_dme_get_uint_exception(dev, "/dm/sdr/0/rx/frequency",
&freq);
+ } catch (const std::exception& e) {
+ LOGC(DDEV, ERROR) << e.what();
+ return 0;
+ }
+
+ return static_cast<double>(freq);
+}
+
+double USDRDevice::rssiOffset(size_t chan)
+{
+ if (!usdr_check_channel(chan))
+ return 0;
+
+ return -20;
+}
+
+double USDRDevice::setPowerAttenuation(int atten, size_t chan)
+{
+ if (!usdr_check_channel(chan))
+ return 0;
+
+ return 0;
+}
+
+double USDRDevice::getPowerAttenuation(size_t chan)
+{
+ if (!usdr_check_channel(chan))
+ return 0;
+
+ return 0;
+}
+
+int USDRDevice::getNominalTxPower(size_t chan)
+{
+ if (!usdr_check_channel(chan))
+ return 0;
+
+ return 0;
+}
+
+RadioDevice *RadioDevice::make(InterfaceType iface, const struct trx_cfg *cfg)
+{
+ return new USDRDevice(iface, cfg);
+}
diff --git a/Transceiver52M/device/usdr/USDRDevice.h
b/Transceiver52M/device/usdr/USDRDevice.h
new file mode 100644
index 0000000..2e6fa0a
--- /dev/null
+++ b/Transceiver52M/device/usdr/USDRDevice.h
@@ -0,0 +1,191 @@
+/*
+* Copyright 2026 Wavelet Lab <[email protected]>
+*
+* SPDX-License-Identifier: AGPL-3.0+
+*
+* This software is distributed under multiple licenses; see the COPYING file in
+* the main directory for licensing information for this specific distribution.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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.
+
+*/
+
+#ifndef _USDR_DEVICE_H_
+#define _USDR_DEVICE_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "radioDevice.h"
+
+#include <stdint.h>
+#include <sys/time.h>
+#include <string>
+#include <iostream>
+#include <chrono>
+#include <map>
+#include <vector>
+
+#include "Threads.h"
+
+extern "C" {
+#include <dm_dev.h>
+#include <dm_rate.h>
+#include <dm_stream.h>
+
+#include <usdr_logging.h>
+}
+
+
+struct DeviceParameters {
+ std::chrono::duration<double> tx_offset; ///< time correction
for TX timestamp, in seconds, to align it with RX timestamp, which is needed to
achieve correct timing of the transmitted signal. This is a device-specific
parameter that can be configured via dev_args and defaults to 0 if not
specified.
+};
+
+using DeviceParametersMap = std::map<std::string, DeviceParameters>;
+
+class USDRDevice: public RadioDevice {
+private:
+ int txsps; ///< samples
count per GSM symbol for Tx
+ int rxsps; ///< samples
count per GSM symbol for Rx
+
+ bool started; ///< flag indicates
USDR has started
+
+ double actualSampleRate = 0; ///< actual sample rate of the USDR
device, which may differ from the configured sample rate due to hardware
limitations
+ int timeTxCorr = 0; ///< time correction
for TX timestamp, in samples, to align it with RX timestamp, which is needed to
achieve correct timing of the transmitted signal. This is a device-specific
parameter that can be configured via dev_args and defaults to 0 if not
specified.
+
+ TIMESTAMP ts_initial = 0;
+ TIMESTAMP ts_last_recv = 0;
+
+ pdm_dev_t dev;
+ pusdr_dms_t usds_rx;
+ pusdr_dms_t usds_tx;
+ usdr_dms_nfo_t snfo_rx;
+ usdr_dms_nfo_t snfo_tx;
+
+public:
+
+ /** Object constructor */
+ USDRDevice(InterfaceType iface, const struct trx_cfg *cfg);
+
+ ~USDRDevice();
+
+ /** Instantiate the USDR */
+ int open();
+
+ /** Start the USDR */
+ bool start();
+
+ /** Stop the USDR */
+ bool stop();
+
+ enum TxWindowType getWindowType() { return TX_WINDOW_FIXED; }
+
+ /**
+ Read samples from the USDR.
+ @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 USDR 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
+ */
+ int readSamples(std::vector<short *> &buf, int len, bool *overrun,
+ TIMESTAMP timestamp = 0xffffffff, bool
*underrun = NULL);
+ /**
+ Write samples to the USDR.
+ @param buf Contains the data to be written.
+ @param len number of samples to write.
+ @param underrun Set if USDR 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
+ */
+ int writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
+ TIMESTAMP timestamp = 0xffffffff);
+
+ /** Update the alignment between the read and write timestamps */
+ bool updateAlignment(TIMESTAMP timestamp);
+
+ /** Set the transmitter frequency */
+ bool setTxFreq(double wFreq, size_t chan = 0);
+
+ /** Get the transmitter frequency */
+ double getTxFreq(size_t chan = 0);
+
+ /** Set the receiver frequency */
+ bool setRxFreq(double wFreq, size_t chan = 0);
+
+ /** Get the receiver frequency */
+ double getRxFreq(size_t chan = 0);
+
+ /** Returns the starting write Timestamp*/
+ TIMESTAMP initialWriteTimestamp(void);
+
+ /** Returns the starting read Timestamp*/
+ TIMESTAMP initialReadTimestamp(void) { return ts_initial;}
+
+ /** returns the full-scale transmit amplitude **/
+ double fullScaleInputValue() {return (double) 32767*0.707;}
+
+ /** returns the full-scale receive amplitude **/
+ double fullScaleOutputValue() {return (double) 32767;}
+
+ /** sets the receive chan gain, returns the gain setting **/
+ double setRxGain(double dB, size_t chan = 0);
+
+ /** get the current receive gain */
+ double getRxGain(size_t chan = 0) { return 0; }
+
+ /** return maximum Rx Gain **/
+ double maxRxGain(void);
+
+ /** return minimum Rx Gain **/
+ double minRxGain(void);
+
+ double rssiOffset(size_t chan);
+
+ double setPowerAttenuation(int atten, size_t chan);
+ double getPowerAttenuation(size_t chan = 0);
+
+ int getNominalTxPower(size_t chan = 0);
+
+ /** sets the transmit chan gain, returns the gain setting **/
+ double setTxGain(double dB, size_t chan = 0);
+
+ /** return maximum Tx Gain **/
+ double maxTxGain(void);
+
+ /** return minimum Rx Gain **/
+ double minTxGain(void);
+
+ /** sets the RX path to use, returns true if successful and false
otherwise */
+ bool setRxAntenna(const std::string & ant, size_t chan = 0);
+
+ /** return the used RX path */
+ std::string getRxAntenna(size_t chan = 0);
+
+ /** sets the RX path to use, returns true if successful and false
otherwise */
+ bool setTxAntenna(const std::string & ant, size_t chan = 0);
+
+ /** return the used RX path */
+ std::string getTxAntenna(size_t chan = 0);
+
+ /** return whether user drives synchronization of Tx/Rx of USRP */
+ bool requiresRadioAlign();
+
+ /** return whether user drives synchronization of Tx/Rx of USRP */
+ virtual GSM::Time minLatency();
+
+ /** Return internal status values */
+ inline double getSampleRate() { return actualSampleRate; }
+};
+
+#endif // _USDR_DEVICE_H_
+
diff --git a/configure.ac b/configure.ac
index ff73d09..10c1ac2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -123,6 +123,11 @@
[enable UHD based transceiver])
])
+AC_ARG_WITH(usdr, [
+ AS_HELP_STRING([--with-usdr],
+ [enable USDR based transceiver])
+])
+
AC_ARG_WITH(usrp1, [
AS_HELP_STRING([--with-usrp1],
[enable USRP1 gnuradio based transceiver])
@@ -177,6 +182,10 @@
AC_DEFINE(HAVE_NEON_FMA, 1, Support ARM NEON with FMA)
])
+AS_IF([test "x$with_usdr" = "xyes"], [
+ PKG_CHECK_MODULES(USDR, usdr >= 0.9.9)
+])
+
AS_IF([test "x$with_usrp1" = "xyes"], [
AC_CHECK_HEADER([boost/config.hpp],[],
[AC_MSG_ERROR([boost/config.hpp not found, install e.g.
libboost-dev])])
@@ -277,6 +286,7 @@
])
AM_CONDITIONAL(DEVICE_UHD, [test "x$with_uhd" = "xyes"])
+AM_CONDITIONAL(DEVICE_USDR, [test "x$with_usdr" = "xyes"])
AM_CONDITIONAL(DEVICE_USRP1, [test "x$with_usrp1" = "xyes"])
AM_CONDITIONAL(DEVICE_LMS, [test "x$with_lms" = "xyes"])
AM_CONDITIONAL(DEVICE_IPC, [test "x$with_ipc" = "xyes"])
@@ -365,6 +375,7 @@
Transceiver52M/device/Makefile \
Transceiver52M/device/common/Makefile \
Transceiver52M/device/uhd/Makefile \
+ Transceiver52M/device/usdr/Makefile \
Transceiver52M/device/usrp1/Makefile \
Transceiver52M/device/lms/Makefile \
Transceiver52M/device/ipc/Makefile \
diff --git a/contrib/systemd/Makefile.am b/contrib/systemd/Makefile.am
index 6566b97..55b4e82 100644
--- a/contrib/systemd/Makefile.am
+++ b/contrib/systemd/Makefile.am
@@ -1,6 +1,7 @@
EXTRA_DIST = \
osmo-trx-lms.service \
osmo-trx-uhd.service \
+ osmo-trx-usdr.service \
osmo-trx-usrp1.service \
osmo-trx-ipc.service
@@ -11,6 +12,10 @@
SYSTEMD_SERVICES += osmo-trx-uhd.service
endif
+if DEVICE_USDR
+SYSTEMD_SERVICES += osmo-trx-usdr.service
+endif
+
if DEVICE_USRP1
SYSTEMD_SERVICES += osmo-trx-usrp1.service
endif
diff --git a/contrib/systemd/osmo-trx-usdr.service
b/contrib/systemd/osmo-trx-usdr.service
new file mode 100644
index 0000000..4e26e2c
--- /dev/null
+++ b/contrib/systemd/osmo-trx-usdr.service
@@ -0,0 +1,23 @@
+[Unit]
+Description=Osmocom SDR BTS L1 Transceiver (USDR backend)
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=simple
+Restart=always
+StateDirectory=osmocom
+WorkingDirectory=%S/osmocom
+User=osmocom
+Group=osmocom
+ExecStart=/usr/bin/osmo-trx-usdr -C /etc/osmocom/osmo-trx-usdr.cfg
+RestartSec=2
+AmbientCapabilities=CAP_SYS_NICE
+# CPU scheduling policy:
+CPUSchedulingPolicy=rr
+# For real-time scheduling policies an integer between 1 (lowest priority) and
99 (highest priority):
+CPUSchedulingPriority=21
+# See sched(7) for further details on real-time policies and priorities
+
+[Install]
+WantedBy=multi-user.target
diff --git a/debian/osmo-trx-usdr.install b/debian/osmo-trx-usdr.install
new file mode 100644
index 0000000..d595589
--- /dev/null
+++ b/debian/osmo-trx-usdr.install
@@ -0,0 +1,4 @@
+etc/osmocom/osmo-trx-usdr.cfg
+lib/systemd/system/osmo-trx-usdr.service
+/usr/bin/osmo-trx-usdr
+/usr/share/doc/osmo-trx/examples/osmo-trx-usdr/osmo-trx-usdr.cfg
/usr/share/doc/osmo-trx/examples/osmo-trx-usdr/
diff --git a/debian/osmo-trx-usdr.postinst b/debian/osmo-trx-usdr.postinst
new file mode 100755
index 0000000..9bf9fc7
--- /dev/null
+++ b/debian/osmo-trx-usdr.postinst
@@ -0,0 +1,38 @@
+#!/bin/sh -e
+case "$1" in
+ configure)
+ # Create the osmocom group and user (if it doesn't exist yet)
+ if ! getent group osmocom >/dev/null; then
+ groupadd --system osmocom
+ fi
+ if ! getent passwd osmocom >/dev/null; then
+ useradd \
+ --system \
+ --gid osmocom \
+ --home-dir /var/lib/osmocom \
+ --shell /sbin/nologin \
+ --comment "Open Source Mobile Communications" \
+ osmocom
+ fi
+
+ # Fix permissions of previous (root-owned) install (OS#4107)
+ if dpkg --compare-versions "$2" le "1.13.0"; then
+ if [ -e /etc/osmocom/osmo-trx-usdr.cfg ]; then
+ chown -v osmocom:osmocom
/etc/osmocom/osmo-trx-usdr.cfg
+ chmod -v 0660 /etc/osmocom/osmo-trx-usdr.cfg
+ fi
+
+ if [ -d /etc/osmocom ]; then
+ chown -v root:osmocom /etc/osmocom
+ chmod -v 2775 /etc/osmocom
+ fi
+
+ mkdir -p /var/lib/osmocom
+ chown -R -v osmocom:osmocom /var/lib/osmocom
+ fi
+ ;;
+esac
+
+# dh_installdeb(1) will replace this with shell code automatically
+# generated by other debhelper scripts.
+#DEBHELPER#
diff --git a/doc/examples/Makefile.am b/doc/examples/Makefile.am
index ac87457..717097c 100644
--- a/doc/examples/Makefile.am
+++ b/doc/examples/Makefile.am
@@ -8,6 +8,7 @@
osmo-trx-lms/osmo-trx-limesdr.cfg \
osmo-trx-lms/osmo-trx-lms.cfg \
osmo-trx-ipc/osmo-trx-ipc.cfg \
+ osmo-trx-ipc/osmo-trx-usdr.cfg \
$(NULL)
OSMOCONF_FILES =
@@ -17,6 +18,10 @@
OSMOCONF_FILES += osmo-trx-uhd/osmo-trx-uhd.cfg
endif
+if DEVICE_USDR
+OSMOCONF_FILES += osmo-trx-usdr/osmo-trx-usdr.cfg
+endif
+
# if DEVICE_USRP1
# TODO: no usrp1 sample file yet
# OSMOCONF_FILES += osmo-trx-usrp1/osmo-trx-usrp1.cfg
diff --git a/doc/examples/osmo-trx-usdr/osmo-trx-usdr.cfg
b/doc/examples/osmo-trx-usdr/osmo-trx-usdr.cfg
new file mode 100644
index 0000000..c892892
--- /dev/null
+++ b/doc/examples/osmo-trx-usdr/osmo-trx-usdr.cfg
@@ -0,0 +1,24 @@
+log stderr
+ logging filter all 1
+ logging color 1
+ logging print category-hex 0
+ logging print category 1
+ logging timestamp 0
+ logging print file basename last
+ logging print level 1
+ logging level set-all notice
+!
+line vty
+ no login
+!
+cpu-sched
+ policy rr 18
+trx
+ bind-ip 127.0.0.1
+ remote-ip 127.0.0.1
+ egprs disable
+ tx-sps 4
+ rx-sps 4
+ clock-ref external
+ chan 0
+ dev-args loglevel=3
diff --git a/doc/manuals/Makefile.am b/doc/manuals/Makefile.am
index ed7353d..642ffae 100644
--- a/doc/manuals/Makefile.am
+++ b/doc/manuals/Makefile.am
@@ -13,6 +13,9 @@
if DEVICE_UHD
VARIANTS += uhd
endif
+if DEVICE_USDR
+ VARIANTS += usdr
+endif
if DEVICE_USRP1
VARIANTS += usrp1
endif
diff --git a/doc/manuals/chapters/trx-backends.adoc
b/doc/manuals/chapters/trx-backends.adoc
index 021c6f4..eef8a22 100644
--- a/doc/manuals/chapters/trx-backends.adoc
+++ b/doc/manuals/chapters/trx-backends.adoc
@@ -73,6 +73,25 @@
Transceiver and radioInterface functionality.
+[[backend_usdr]]
+=== `osmo-trx-usdr` for USDR based Transceivers
+
+This OsmoTRX model uses the USDR API to drive a family of SDR devices
+such as uSDR, xSDR and sSDR, allowing a single OsmoTRX binary to control one
+or more attached SDR units. The backend implements device discovery, per-unit
+configuration and sample I/O integration with the OsmoTRX burst processing
+pipeline.
+
+The backend supports single-channel SDR hardware and is designed for setups
+where multiple small SDRs are combined to present one or more TRX instances
+to the upper GSM stack. For details about supported hardware and
platform-specific
+instructions, refer to the USDR documentation at https://docs.wsdr.io/.
+
+Related code can be found in the _Transceiver52M/device/usdr/_ directory in
+_osmo-trx.git_. Example configuration files shipped with the backend show
+how to list multiple USDR devices and tune device-specific parameters.
+
+
[[backend_ipc]]
=== `osmo-trx-ipc` Inter Process Communication backend
diff --git a/doc/manuals/chapters/trx-devices.adoc
b/doc/manuals/chapters/trx-devices.adoc
index 3c21e59..629bd5d 100644
--- a/doc/manuals/chapters/trx-devices.adoc
+++ b/doc/manuals/chapters/trx-devices.adoc
@@ -90,3 +90,33 @@
As a smaller brother of the [[dev_limesdr_usb]], this device comes only with 1
RF channel. As a result, it can only hold 1 TRX as of today.
+
+
+[[dev_usdr_usdr]]
+=== uSDR
+
+The binary _osmo-trx-usdr_ is used to drive this device, see <<backend_usdr>>.
+
+The uSDR devices are compact single-RF-channel SDRs commonly used for low-cost
+or portable TRX deployments. They are often combined in multi-unit setups to
+scale the number of TRX instances.
+
+
+[[dev_usdr_xsdr]]
+=== xSDR
+
+The binary _osmo-trx-usdr_ is used to drive this device, see <<backend_usdr>>.
+
+xSDR-class devices typically provide multiple RF channels and are suitable for
+multi-TRX BTS configurations where a single physical board exposes multiple
+transceiver channels.
+
+
+[[dev_usdr_ssdr]]
+=== sSDR
+
+The binary _osmo-trx-usdr_ is used to drive this device, see <<backend_usdr>>.
+
+sSDR devices are small, low-power SDR modules targeted at constrained or
+embedded use-cases. They generally expose a single RF channel and are useful
+for testbeds or distributed deployments.
--
To view, visit https://gerrit.osmocom.org/c/osmo-trx/+/42198?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmo-trx
Gerrit-Branch: master
Gerrit-Change-Id: I5d1d25921514954c4929ae6e7352168b3ceb05df
Gerrit-Change-Number: 42198
Gerrit-PatchSet: 1
Gerrit-Owner: Timur Davydov <[email protected]>