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 info@wavelet-lab.com +* +* 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 info@wavelet-lab.com +* +* 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.