├── .gitignore ├── debian ├── compat ├── docs ├── source │ └── format ├── soapysdr0.7-module-uhd.install ├── uhd-soapysdr.install ├── copyright ├── rules ├── control └── changelog ├── README.md ├── FindUHD.cmake ├── cmake └── cmake_uninstall.cmake.in ├── .github └── workflows │ └── build.yml ├── Changelog.txt ├── TypeHelpers.hpp ├── CMakeLists.txt ├── COPYING ├── UHDSoapyDevice.cpp └── SoapyUHDDevice.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | COPYING 2 | README.md 3 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/soapysdr0.7-module-uhd.install: -------------------------------------------------------------------------------- 1 | usr/lib/*/SoapySDR/modules* 2 | -------------------------------------------------------------------------------- /debian/uhd-soapysdr.install: -------------------------------------------------------------------------------- 1 | usr/lib/*/uhd/modules/ 2 | 3 | #Note: uhd 3.10 broke the multi-arch module support. 4 | #The rules file creates a symlink to work around this. 5 | usr/lib/uhd/modules/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Soapy SDR plugins for UHD devices 2 | 3 | ## Dependencies 4 | 5 | * UHD - https://github.com/EttusResearch/uhd/wiki 6 | * SoapySDR - https://github.com/pothosware/SoapySDR/wiki 7 | * boost libraries - http://www.boost.org/ 8 | 9 | ## Documentation 10 | 11 | * https://github.com/pothosware/SoapyUHD/wiki 12 | 13 | ## Licensing information 14 | 15 | * GPLv3: http://www.gnu.org/licenses/gpl-3.0.html 16 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: soapyuhd 3 | Source: https://github.com/pothosware/SoapyUHD/wiki 4 | 5 | Files: * 6 | Copyright: 2014-2016 Josh Blum 7 | License: GPL-3 8 | On Debian systems, the full text of the GNU General Public 9 | License version 3 can be found in the file 10 | `/usr/share/common-licenses/GPL-3'. 11 | -------------------------------------------------------------------------------- /FindUHD.cmake: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | # Find the library for the USRP Hardware Driver 3 | ######################################################################## 4 | 5 | INCLUDE(FindPkgConfig) 6 | PKG_CHECK_MODULES(PC_UHD uhd) 7 | 8 | FIND_PATH( 9 | UHD_INCLUDE_DIRS 10 | NAMES uhd/config.hpp 11 | HINTS $ENV{UHD_DIR}/include 12 | ${PC_UHD_INCLUDEDIR} 13 | PATHS /usr/local/include 14 | /usr/include 15 | ) 16 | 17 | FIND_LIBRARY( 18 | UHD_LIBRARIES 19 | NAMES uhd 20 | HINTS $ENV{UHD_DIR}/lib 21 | ${PC_UHD_LIBDIR} 22 | PATHS /usr/local/lib 23 | /usr/lib 24 | ) 25 | 26 | INCLUDE(FindPackageHandleStandardArgs) 27 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(UHD DEFAULT_MSG UHD_LIBRARIES UHD_INCLUDE_DIRS) 28 | MARK_AS_ADVANCED(UHD_LIBRARIES UHD_INCLUDE_DIRS) 29 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) 5 | export DEB_HOST_MULTIARCH 6 | 7 | # Uncomment this to turn on verbose mode. 8 | #export DH_VERBOSE=1 9 | 10 | %: 11 | dh $@ --buildsystem=cmake --parallel 12 | 13 | override_dh_auto_configure: 14 | dh_auto_configure -- -DLIB_SUFFIX="/$(DEB_HOST_MULTIARCH)" 15 | 16 | override_dh_installchangelogs: 17 | dh_installchangelogs Changelog.txt 18 | 19 | #Note: uhd 3.10 broke the multi-arch module support. 20 | #The rules file creates a symlink to work around this. 21 | override_dh_install: 22 | mkdir -p $(CURDIR)/debian/tmp/usr/lib/uhd/modules/$(DEB_HOST_MULTIARCH) 23 | ln -s /usr/lib/$(DEB_HOST_MULTIARCH)/uhd/modules/libsoapySupport.so \ 24 | $(CURDIR)/debian/tmp/usr/lib/uhd/modules/$(DEB_HOST_MULTIARCH)/libsoapySupport.so 25 | dh_install 26 | -------------------------------------------------------------------------------- /cmake/cmake_uninstall.cmake.in: -------------------------------------------------------------------------------- 1 | if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 2 | message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 3 | endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 4 | 5 | file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) 6 | string(REGEX REPLACE "\n" ";" files "${files}") 7 | foreach(file ${files}) 8 | message(STATUS "Uninstalling $ENV{DESTDIR}${file}") 9 | if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 10 | exec_program( 11 | "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" 12 | OUTPUT_VARIABLE rm_out 13 | RETURN_VALUE rm_retval 14 | ) 15 | if(NOT "${rm_retval}" STREQUAL 0) 16 | message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") 17 | endif(NOT "${rm_retval}" STREQUAL 0) 18 | else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 19 | message(STATUS "File $ENV{DESTDIR}${file} does not exist.") 20 | endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 21 | endforeach(file) 22 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: soapyuhd 2 | Section: libs 3 | Priority: optional 4 | Maintainer: Josh Blum 5 | Build-Depends: 6 | debhelper (>= 9.0.0), 7 | cmake, 8 | libboost-all-dev, 9 | libuhd-dev, 10 | libsoapysdr-dev 11 | Standards-Version: 4.1.4 12 | Homepage: https://github.com/pothosware/SoapyUHD/wiki 13 | Vcs-Git: https://github.com/pothosware/SoapyUHD.git 14 | Vcs-Browser: https://github.com/pothosware/SoapyUHD 15 | 16 | Package: soapysdr0.7-module-uhd 17 | Architecture: any 18 | Multi-Arch: same 19 | Depends: ${shlibs:Depends}, ${misc:Depends} 20 | Description: Soapy UHD - UHD devices for Soapy SDR. 21 | A Soapy module that supports UHD devices within the Soapy API. 22 | 23 | Package: soapysdr-module-uhd 24 | Architecture: all 25 | Depends: soapysdr0.7-module-uhd, ${misc:Depends} 26 | Description: Soapy UHD - UHD devices for Soapy SDR. 27 | A Soapy module that supports UHD devices within the Soapy API. 28 | . 29 | This is an empty dependency package that pulls in the USRP module 30 | for the default version of libsoapysdr. 31 | 32 | Package: uhd-soapysdr 33 | Architecture: any 34 | Multi-Arch: same 35 | Depends: ${shlibs:Depends}, ${misc:Depends} 36 | Description: Soapy UHD - Soapy SDR devices for UHD. 37 | A UHD module that supports Soapy devices within the UHD API. 38 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build check 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | macos_macports_build_job: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: [macos-14, macos-15] 13 | runs-on: ${{ matrix.os }} 14 | name: Build on ${{ matrix.os }} (macports) 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: melusina-org/setup-macports@v1 18 | - name: Install ports 19 | run: port install SoapySDR py310-setuptools uhd 20 | # Note: MacPorts uhd depends on non-default boost171 21 | - name: Configure 22 | run: BOOST_ROOT=/opt/local/libexec/boost/1.71 cmake -B build 23 | - name: Build 24 | run: cmake --build build 25 | 26 | macos_homebrew_build_job: 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | os: [macos-14, macos-15] 31 | runs-on: ${{ matrix.os }} 32 | name: Build on ${{ matrix.os }} (homebrew) 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Setup tools 36 | run: brew install soapysdr uhd 37 | - name: Configure 38 | run: cmake -B build 39 | - name: Build 40 | run: cmake --build build 41 | 42 | linux_build_job: 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | os: [ubuntu-22.04, ubuntu-24.04] 47 | runs-on: ${{ matrix.os }} 48 | name: Build on ${{ matrix.os }} 49 | steps: 50 | - uses: actions/checkout@v4 51 | - name: Setup tools 52 | run: | 53 | sudo apt-get update -q -y 54 | sudo apt-get install -y --no-install-recommends cmake ninja-build 55 | sudo apt-get install -q -y libsoapysdr-dev libuhd-dev 56 | - name: Configure 57 | run: cmake -GNinja -B build 58 | - name: Build 59 | run: cmake --build build 60 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | soapyuhd (0.4.1-1) unstable; urgency=low 2 | 3 | * Release 0.4.1 (2020-09-20) 4 | 5 | -- Josh Blum Sun, 20 Sep 2020 17:40:02 -0000 6 | 7 | soapyuhd (0.4.0-1) unstable; urgency=low 8 | 9 | * Release 0.4.0 (2020-09-17) 10 | 11 | -- Josh Blum Thu, 17 Sep 2020 10:06:00 -0000 12 | 13 | soapyuhd (0.3.6-1) unstable; urgency=low 14 | 15 | * Release 0.3.6 (2019-06-22) 16 | 17 | -- Josh Blum Sat, 22 Jun 2019 07:51:53 -0000 18 | 19 | soapyuhd (0.3.5-1) unstable; urgency=low 20 | 21 | * Release 0.3.5 (2018-12-07) 22 | 23 | -- Josh Blum Fri, 07 Dec 2018 21:13:46 -0000 24 | 25 | soapyuhd (0.3.4-1) unstable; urgency=low 26 | 27 | * Release 0.3.4 (2017-12-14) 28 | 29 | -- Josh Blum Thu, 14 Dec 2017 19:43:38 -0000 30 | 31 | soapyuhd (0.3.3-1) unstable; urgency=low 32 | 33 | * Release 0.3.3 (2017-04-29) 34 | 35 | -- Josh Blum Sat, 29 Apr 2017 15:11:18 -0000 36 | 37 | soapyuhd (0.3.2-1) unstable; urgency=low 38 | 39 | * Release 0.3.2 (2017-01-22) 40 | 41 | -- Josh Blum Sun, 22 Jan 2017 14:54:04 -0800 42 | 43 | soapyuhd (0.3.1) unstable; urgency=low 44 | 45 | * Release 0.3.1 (2016-08-13) 46 | 47 | -- Josh Blum Sat, 13 Aug 2016 09:28:14 -0700 48 | 49 | soapyuhd (0.3.0) unstable; urgency=low 50 | 51 | * Release 0.3.0 (2015-11-20) 52 | 53 | -- Josh Blum Fri, 23 Oct 2015 21:01:40 -0700 54 | 55 | soapyuhd (0.2.0) unstable; urgency=low 56 | 57 | * Release 0.2.0 (2015-10-10) 58 | 59 | -- Josh Blum Sat, 10 Oct 2015 11:16:37 -0700 60 | 61 | soapyuhd (0.1.2) unstable; urgency=low 62 | 63 | * Release 0.1.2 (2015-09-13) 64 | 65 | -- Josh Blum Sun, 13 Sep 2015 00:01:42 -0700 66 | 67 | soapyuhd (0.1.1) unstable; urgency=low 68 | 69 | * Release 0.1.1 (2015-08-15) 70 | 71 | -- Josh Blum Sat, 15 Aug 2015 11:32:27 -0700 72 | 73 | soapyuhd (0.1.0) unstable; urgency=low 74 | 75 | * Release 0.1.0 (2015-06-15) 76 | 77 | -- Josh Blum Mon, 15 Jun 2015 12:29:52 -0400 78 | -------------------------------------------------------------------------------- /Changelog.txt: -------------------------------------------------------------------------------- 1 | Release 0.4.1 (pending) 2 | ========================== 3 | 4 | - Fix get_tree() for X300 on UHD 4.0 release 5 | - Fix for getBandwidthRange() for SOAPY_SDR_RX 6 | - Set C++14 which is required in UHD headers 7 | 8 | Release 0.4.1 (2020-09-20) 9 | ========================== 10 | 11 | - Fix for UHD_VERSION define and 4.0 release compilation 12 | 13 | Release 0.4.0 (2020-09-17) 14 | ========================== 15 | 16 | - Support for compilation with UHD 4.0 release 17 | 18 | Release 0.3.6 (2019-06-22) 19 | ========================== 20 | 21 | - Support tuning on boards without a CORDIC in the DSP 22 | by registering a dummy second tuning element 23 | 24 | Release 0.3.5 (2018-12-07) 25 | ========================== 26 | 27 | - Create fake channels if the number of TX and RX channels 28 | are not equal to fix segmentation faults in UHD based tools 29 | 30 | Release 0.3.4 (2017-12-14) 31 | ========================== 32 | 33 | - Optional check for dsp freq range in property tree 34 | - Tx de/activateStream() return 0 for NOP, not an error 35 | - Support timestamp for deactivateStream() stream command 36 | - Conditional support for new logging API (replaces msg.hpp) 37 | - Tx stream activation hooks based on start and end of burst 38 | 39 | Release 0.3.3 (2017-04-29) 40 | ========================== 41 | 42 | - Results for frequency component with no tune result 43 | - Fix arg for set_rx_subdev_spec() in UHDSoapyDevice 44 | - Support getBandwidthRange()/getSampleRateRange() 45 | - UHDSoapyDevice supports zero length buffer send() 46 | - Implement timestamp interpolation for uhd rx streams 47 | - Added label convention to soapy uhd discovery routine 48 | - Support for optional gain range step in type conversions 49 | 50 | Release 0.3.2 (2017-01-22) 51 | ========================== 52 | 53 | - Added tx/rx_subdev device argument for uhd device 54 | - Added corrections hooks for soapy devices in uhd 55 | - Symlinks to workaround uhd 3.10 multi-arch bug 56 | - Minor corrections for license and copyright text 57 | - Update debian files for SoapySDR module ABI format 58 | 59 | Release 0.3.1 (2016-08-13) 60 | ========================== 61 | 62 | - support setHardwareTime("CMD"), deprecated setCommandTime() 63 | - support changes to property tree API (backwards compatible) 64 | - support property tree API changes for uhd v3.10.0 65 | 66 | Release 0.3.0 (2015-11-20) 67 | ========================== 68 | 69 | - SoapyUHDDevice - implement getSensorInfo() for SoapySDR v0.4 70 | - SoapyUHDDevice - implement getStreamArgsInfo() for SoapySDR v0.4 71 | - SoapyUHDDevice - implement getNativeStreamFormat() for SoapySDR v0.4 72 | - SoapyUHDDevice - implement getStreamFormats() for SoapySDR v0.4 73 | - UHDSoapyDevice - use getSensorInfo() for sensors 74 | 75 | Release 0.2.0 (2015-10-10) 76 | ========================== 77 | 78 | - Hooks for 'has' DC offset mode, DC offset, and IQ balance 79 | - Switched to using per-channel and global sensors API 80 | - Added GPIO access support for Soapy UHD and vice-versa 81 | 82 | Release 0.1.2 (2015-09-13) 83 | ========================== 84 | 85 | - Fix metaRangeToNumericList for single element entries 86 | 87 | Release 0.1.1 (2015-08-15) 88 | ========================== 89 | 90 | - Modifications for ubuntu 12.04 w/ cmake 2.8.7 91 | 92 | Release 0.1.0 (2015-06-15) 93 | ========================== 94 | 95 | - Initial release of Soapy UHD support module 96 | -------------------------------------------------------------------------------- /TypeHelpers.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 Josh Blum 2 | // SPDX-License-Identifier: GPL-3.0 3 | 4 | #pragma once 5 | #include 6 | #include //feature constants 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define SOAPY_UHD_NO_DEEPER "soapy_uhd_no_deeper" 13 | 14 | /*********************************************************************** 15 | * Helpful type conversions 16 | **********************************************************************/ 17 | static inline SoapySDR::Kwargs dictToKwargs(const uhd::device_addr_t &addr) 18 | { 19 | SoapySDR::Kwargs kwargs; 20 | const std::vector keys = addr.keys(); 21 | for (size_t i = 0; i < keys.size(); i++) 22 | { 23 | kwargs[keys[i]] = addr[keys[i]]; 24 | } 25 | return kwargs; 26 | } 27 | 28 | static inline uhd::device_addr_t kwargsToDict(const SoapySDR::Kwargs &kwargs) 29 | { 30 | uhd::device_addr_t addr; 31 | for (SoapySDR::Kwargs::const_iterator it = kwargs.begin(); it != kwargs.end(); ++it) 32 | { 33 | addr[it->first] = it->second; 34 | } 35 | return addr; 36 | } 37 | 38 | static inline SoapySDR::RangeList metaRangeToRangeList(const uhd::meta_range_t &metaRange) 39 | { 40 | SoapySDR::RangeList out; 41 | for (size_t i = 0; i < metaRange.size(); i++) 42 | { 43 | #ifdef SOAPY_SDR_API_HAS_RANGE_TYPE_STEP 44 | out.push_back(SoapySDR::Range(metaRange[i].start(), metaRange[i].stop(), metaRange[i].step())); 45 | #else 46 | out.push_back(SoapySDR::Range(metaRange[i].start(), metaRange[i].stop())); 47 | #endif 48 | } 49 | return out; 50 | } 51 | 52 | static inline uhd::meta_range_t rangeListToMetaRange(const SoapySDR::RangeList &ranges) 53 | { 54 | uhd::meta_range_t out; 55 | for (size_t i = 0; i < ranges.size(); i++) 56 | { 57 | #ifdef SOAPY_SDR_API_HAS_RANGE_TYPE_STEP 58 | out.push_back(uhd::range_t(ranges[i].minimum(), ranges[i].maximum(), ranges[i].step())); 59 | #else 60 | out.push_back(uhd::range_t(ranges[i].minimum(), ranges[i].maximum())); 61 | #endif 62 | } 63 | if (out.empty()) out.push_back(uhd::range_t(0.0)); 64 | return out; 65 | } 66 | 67 | static inline SoapySDR::Range metaRangeToRange(const uhd::meta_range_t &metaRange) 68 | { 69 | #ifdef SOAPY_SDR_API_HAS_RANGE_TYPE_STEP 70 | return SoapySDR::Range(metaRange.start(), metaRange.stop(), metaRange.step()); 71 | #else 72 | return SoapySDR::Range(metaRange.start(), metaRange.stop()); 73 | #endif 74 | } 75 | 76 | static inline uhd::meta_range_t numberListToMetaRange(const std::vector &nums) 77 | { 78 | uhd::meta_range_t out; 79 | for (size_t i = 0; i < nums.size(); i++) 80 | { 81 | out.push_back(uhd::range_t(nums[i])); 82 | } 83 | if (out.empty()) out.push_back(uhd::range_t(0.0)); 84 | return out; 85 | } 86 | 87 | static inline std::vector metaRangeToNumericList(const uhd::meta_range_t &metaRange) 88 | { 89 | std::vector out; 90 | 91 | //in this case, the bounds are in element 0 92 | if (metaRange.size() == 1) 93 | { 94 | out.push_back(metaRange[0].start()); 95 | out.push_back(metaRange[0].stop()); 96 | return out; 97 | } 98 | 99 | for (size_t i = 0; i < metaRange.size(); i++) 100 | { 101 | //in these cases start == stop 102 | out.push_back(metaRange[i].start()); 103 | } 104 | return out; 105 | } 106 | 107 | static inline uhd::meta_range_t rangeToMetaRange(const SoapySDR::Range &range, double step = 0.0) 108 | { 109 | //when range step is supported, use it only if initialized to non-zero 110 | #ifdef SOAPY_SDR_API_HAS_RANGE_TYPE_STEP 111 | if (range.step() != 0.0) step = range.step(); 112 | #endif 113 | 114 | return uhd::meta_range_t(range.minimum(), range.maximum(), step); 115 | } 116 | 117 | static inline SoapySDR::ArgInfo sensorToArgInfo(const uhd::sensor_value_t &sensor, const std::string &key) 118 | { 119 | SoapySDR::ArgInfo argInfo; 120 | argInfo.key = key; 121 | argInfo.value = sensor.value; 122 | argInfo.name = sensor.name; 123 | argInfo.units = sensor.unit; 124 | switch (sensor.type) 125 | { 126 | case uhd::sensor_value_t::BOOLEAN: argInfo.type = SoapySDR::ArgInfo::BOOL; break; 127 | case uhd::sensor_value_t::INTEGER: argInfo.type = SoapySDR::ArgInfo::INT; break; 128 | case uhd::sensor_value_t::REALNUM: argInfo.type = SoapySDR::ArgInfo::FLOAT; break; 129 | case uhd::sensor_value_t::STRING: argInfo.type = SoapySDR::ArgInfo::STRING; break; 130 | } 131 | return argInfo; 132 | } 133 | 134 | static inline uhd::sensor_value_t argInfoToSensor(const SoapySDR::ArgInfo &argInfo, const std::string &value) 135 | { 136 | switch (argInfo.type) 137 | { 138 | case SoapySDR::ArgInfo::BOOL: return uhd::sensor_value_t(argInfo.name, value == "true", argInfo.units, argInfo.units); 139 | case SoapySDR::ArgInfo::INT: return uhd::sensor_value_t(argInfo.name, atoi(value.c_str()), argInfo.units); 140 | case SoapySDR::ArgInfo::FLOAT: return uhd::sensor_value_t(argInfo.name, atof(value.c_str()), argInfo.units); 141 | case SoapySDR::ArgInfo::STRING: return uhd::sensor_value_t(argInfo.name, value, argInfo.units); 142 | } 143 | return uhd::sensor_value_t(argInfo.name, value, argInfo.units); 144 | } 145 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2012-2015 Free Software Foundation, Inc. 2 | # Copyright 2015-2016 Josh Blum 3 | # 4 | # This file is part of SoapyUHD support modules 5 | # 6 | # SoapyUHD is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 3, or (at your option) 9 | # any later version. 10 | # 11 | # SoapyUHD is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with GNU Radio; see the file COPYING. If not, write to 18 | # the Free Software Foundation, Inc., 51 Franklin Street, 19 | # Boston, MA 02110-1301, USA. 20 | 21 | ######################################################################## 22 | # Project setup 23 | ######################################################################## 24 | cmake_minimum_required(VERSION 2.8.12...3.10) 25 | project(SoapyUHD CXX C) 26 | enable_testing() 27 | 28 | set(CMAKE_CXX_STANDARD 14) 29 | 30 | #select the release build type by default to get optimization flags 31 | if(NOT CMAKE_BUILD_TYPE) 32 | set(CMAKE_BUILD_TYPE "Release") 33 | message(STATUS "Build type not specified: defaulting to release.") 34 | endif(NOT CMAKE_BUILD_TYPE) 35 | set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "") 36 | 37 | ######################################################################## 38 | # Dependencies 39 | ######################################################################## 40 | find_package(SoapySDR "0.6" NO_MODULE REQUIRED) 41 | find_package(UHD NO_MODULE) 42 | 43 | #try old-style find script 44 | if (NOT UHD_FOUND) 45 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 46 | find_package(UHD) 47 | endif() 48 | 49 | if (NOT UHD_FOUND) 50 | message(FATAL_ERROR "UHD not found -- required for Soapy UHD support") 51 | endif() 52 | 53 | if (NOT UHD_ROOT) 54 | get_filename_component(UHD_ROOT "${UHD_INCLUDE_DIRS}/.." ABSOLUTE) 55 | endif() 56 | message(STATUS "UHD root directory: ${UHD_ROOT}") 57 | message(STATUS "UHD include directories: ${UHD_INCLUDE_DIRS}") 58 | message(STATUS "UHD libraries: ${UHD_LIBRARIES}") 59 | 60 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 61 | include_directories(${SoapySDR_INCLUDE_DIRS}) 62 | include_directories(${UHD_INCLUDE_DIRS}) 63 | 64 | message(STATUS "Checking uhd::device::register_device() API...") 65 | message(STATUS " Reading ${UHD_INCLUDE_DIRS}/uhd/device.hpp...") 66 | file(READ ${UHD_INCLUDE_DIRS}/uhd/device.hpp device_hpp) 67 | string(FIND "${device_hpp}" "device_filter_t" has_device_filter) 68 | if ("${has_device_filter}" STREQUAL "-1") 69 | message(STATUS " has original API") 70 | else() 71 | add_definitions(-DUHD_HAS_DEVICE_FILTER) 72 | message(STATUS " has filter API") 73 | endif() 74 | 75 | message(STATUS "Checking uhd::usrp::multi_usrp::set_rx_agc() API...") 76 | message(STATUS " Reading ${UHD_INCLUDE_DIRS}/uhd/usrp/multi_usrp.hpp...") 77 | file(READ ${UHD_INCLUDE_DIRS}/uhd/usrp/multi_usrp.hpp multi_usrp_hpp) 78 | string(FIND "${multi_usrp_hpp}" "set_rx_agc" has_set_rx_agc) 79 | if ("${has_set_rx_agc}" STREQUAL "-1") 80 | message(STATUS " missing set_rx_agc() API") 81 | else() 82 | add_definitions(-DUHD_HAS_SET_RX_AGC) 83 | message(STATUS " has set_rx_agc() API") 84 | endif() 85 | 86 | message(STATUS "Checking uhd::property::set_publisher() API...") 87 | message(STATUS " Reading ${UHD_INCLUDE_DIRS}/uhd/property_tree.hpp...") 88 | file(READ ${UHD_INCLUDE_DIRS}/uhd/property_tree.hpp property_tree_hpp) 89 | string(FIND "${property_tree_hpp}" "set_publisher" has_set_publisher) 90 | if ("${has_set_publisher}" STREQUAL "-1") 91 | message(STATUS " missing set_publisher() API") 92 | else() 93 | add_definitions(-DUHD_HAS_SET_PUBLISHER) 94 | message(STATUS " has set_publisher() API") 95 | endif() 96 | 97 | if (EXISTS "${UHD_INCLUDE_DIRS}/uhd/utils/msg.hpp") 98 | add_definitions(-DUHD_HAS_MSG_HPP) 99 | message(STATUS " use msg.hpp for logging") 100 | else() 101 | message(STATUS " use log.hpp for logging") 102 | endif() 103 | 104 | ######################################################################## 105 | # Setup boost 106 | ######################################################################## 107 | MESSAGE(STATUS "Configuring Boost C++ Libraries...") 108 | 109 | if(UNIX AND NOT BOOST_ROOT AND EXISTS "/usr/lib64") 110 | list(APPEND BOOST_LIBRARYDIR "/usr/lib64") #fedora 64-bit fix 111 | endif(UNIX AND NOT BOOST_ROOT AND EXISTS "/usr/lib64") 112 | 113 | find_package(Boost) 114 | 115 | if(NOT Boost_FOUND) 116 | message(FATAL_ERROR "Boost not found -- required for Soapy UHD support") 117 | endif() 118 | 119 | ADD_DEFINITIONS(-DBOOST_ALL_DYN_LINK) 120 | 121 | include_directories(${Boost_INCLUDE_DIRS}) 122 | link_directories(${Boost_LIBRARY_DIRS}) 123 | 124 | message(STATUS "Boost include directories: ${Boost_INCLUDE_DIRS}") 125 | message(STATUS "Boost library directories: ${Boost_LIBRARY_DIRS}") 126 | message(STATUS "Boost libraries: ${Boost_LIBRARIES}") 127 | 128 | ######################################################################## 129 | # Build a Soapy module to support UHD devices 130 | ######################################################################## 131 | SOAPY_SDR_MODULE_UTIL( 132 | TARGET uhdSupport 133 | SOURCES SoapyUHDDevice.cpp 134 | LIBRARIES 135 | ${UHD_LIBRARIES} 136 | ${Boost_LIBRARIES} 137 | ) 138 | 139 | ######################################################################## 140 | # Build a UHD module to support Soapy devices 141 | ######################################################################## 142 | add_library(soapySupport MODULE UHDSoapyDevice.cpp) 143 | target_link_libraries(soapySupport 144 | ${UHD_LIBRARIES} 145 | ${SoapySDR_LIBRARIES} 146 | ${Boost_LIBRARIES}) 147 | install(TARGETS soapySupport 148 | DESTINATION ${UHD_ROOT}/lib${LIB_SUFFIX}/uhd/modules 149 | ) 150 | 151 | ######################################################################## 152 | # rpath setup - http://www.cmake.org/Wiki/CMake_RPATH_handling 153 | ######################################################################## 154 | # use, i.e. don't skip the full RPATH for the build tree 155 | option(CMAKE_SKIP_BUILD_RPATH "skip rpath build" FALSE) 156 | 157 | # when building, don't use the install RPATH already 158 | # (but later on when installing) 159 | option(CMAKE_BUILD_WITH_INSTALL_RPATH "build with install rpath" FALSE) 160 | 161 | # the RPATH to be used when installing, but only if it's not a system directory 162 | option(CMAKE_AUTOSET_INSTALL_RPATH TRUE) 163 | if(CMAKE_AUTOSET_INSTALL_RPATH) 164 | LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" isSystemDir) 165 | IF("${isSystemDir}" STREQUAL "-1") 166 | SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}") 167 | ENDIF("${isSystemDir}" STREQUAL "-1") 168 | endif(CMAKE_AUTOSET_INSTALL_RPATH) 169 | 170 | # add the automatically determined parts of the RPATH 171 | # which point to directories outside the build tree to the install RPATH 172 | option(CMAKE_INSTALL_RPATH_USE_LINK_PATH "build with automatic rpath" TRUE) 173 | 174 | if(APPLE) 175 | set(CMAKE_MACOSX_RPATH ON) 176 | endif() 177 | 178 | ######################################################################## 179 | # Print Summary 180 | ######################################################################## 181 | MESSAGE(STATUS "Using install prefix: ${CMAKE_INSTALL_PREFIX}") 182 | 183 | ######################################################################## 184 | # uninstall target 185 | ######################################################################## 186 | add_custom_target(uninstall 187 | "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") 188 | configure_file( 189 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" 190 | "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" 191 | IMMEDIATE @ONLY) 192 | 193 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /UHDSoapyDevice.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2020 Josh Blum 2 | // Copyright (c) 2018 Deepwave Digital, Inc. 3 | // SPDX-License-Identifier: GPL-3.0 4 | 5 | #ifdef UHD_HAS_SET_PUBLISHER 6 | #define publish set_publisher 7 | #define subscribe add_desired_subscriber 8 | #endif 9 | 10 | /*********************************************************************** 11 | * A UHD module that supports Soapy devices within the UHD API. 12 | **********************************************************************/ 13 | 14 | #include "TypeHelpers.hpp" 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #ifdef UHD_HAS_MSG_HPP 21 | #include 22 | #else 23 | #include 24 | #endif 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include 42 | #include 43 | 44 | //Report a positive gain step value for UHD's automatic distribution algorithm. 45 | //This prevents the gain group rounding algorithm from producing zero values. 46 | static const double MIN_GAIN_STEP = 0.1; 47 | 48 | /*********************************************************************** 49 | * Custom UHD Device to support Soapy 50 | **********************************************************************/ 51 | class UHDSoapyDevice : public uhd::device 52 | { 53 | public: 54 | UHDSoapyDevice(const uhd::device_addr_t &args); 55 | 56 | ~UHDSoapyDevice(void); 57 | 58 | uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args); 59 | uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); 60 | bool recv_async_msg(uhd::async_metadata_t &, double); 61 | 62 | uhd::time_spec_t get_hardware_time(const std::string &what) 63 | { 64 | return uhd::time_spec_t::from_ticks(_device->getHardwareTime(what), 1e9); 65 | } 66 | 67 | void set_hardware_time(const std::string &what, const uhd::time_spec_t &time) 68 | { 69 | _device->setHardwareTime(time.to_ticks(1e9), what); 70 | } 71 | 72 | uhd::usrp::subdev_spec_t get_frontend_mapping(const int dir) 73 | { 74 | //return uhd::usrp::subdev_spec_t(_device->getFrontendMapping(dir)); 75 | uhd::usrp::subdev_spec_t spec; 76 | for (size_t ch = 0; ch < _device->getNumChannels(dir); ch++) 77 | { 78 | const std::string chName(boost::lexical_cast(ch)); 79 | spec.push_back(uhd::usrp::subdev_spec_pair_t(chName, chName)); 80 | } 81 | 82 | //spec cant be empty, we make a fake spec for apps work 83 | if (spec.empty()) spec.push_back(uhd::usrp::subdev_spec_pair_t("0", "0")); 84 | 85 | return spec; 86 | } 87 | 88 | void set_frontend_mapping(const int, const uhd::usrp::subdev_spec_t &) 89 | { 90 | //there is no translation from spec to frontend map 91 | //however, frontend map can be set by device args 92 | //_device->setFrontendMapping(dir, spec.to_string()); 93 | } 94 | 95 | uhd::meta_range_t get_freq_range(const int dir, const size_t chan, const std::string &name) 96 | { 97 | return rangeListToMetaRange(_device->getFrequencyRange(dir, chan, name)); 98 | } 99 | 100 | void stash_tune_args(const int dir, const size_t chan, const uhd::device_addr_t &args) 101 | { 102 | _tuneArgsStash[dir][chan] = dictToKwargs(args); 103 | } 104 | 105 | std::map > _tuneArgsStash; 106 | 107 | void set_frequency(const int dir, const size_t chan, const std::string &name, const double freq) 108 | { 109 | _device->setFrequency(dir, chan, name, freq, _tuneArgsStash[dir][chan]); 110 | } 111 | 112 | uhd::meta_range_t get_bw_range(const int dir, const size_t chan) 113 | { 114 | #ifdef SOAPY_SDR_API_HAS_GET_BANDWIDTH_RANGE 115 | return rangeListToMetaRange(_device->getBandwidthRange(dir, chan)); 116 | #else 117 | return numberListToMetaRange(_device->listBandwidths(dir, chan)); 118 | #endif 119 | } 120 | 121 | uhd::meta_range_t get_rate_range(const int dir, const size_t chan) 122 | { 123 | #ifdef SOAPY_SDR_API_HAS_GET_SAMPLE_RATE_RANGE 124 | return rangeListToMetaRange(_device->getSampleRateRange(dir, chan)); 125 | #else 126 | return numberListToMetaRange(_device->listSampleRates(dir, chan)); 127 | #endif 128 | } 129 | 130 | void set_sample_rate(const int dir, const size_t chan, const double rate) 131 | { 132 | _device->setSampleRate(dir, chan, rate); 133 | 134 | //cache the sample rate for the streamer to use 135 | _sampleRates[dir][chan] = _device->getSampleRate(dir, chan); 136 | } 137 | 138 | uhd::meta_range_t get_gain_range(const int dir, const size_t chan, const std::string &name) 139 | { 140 | return rangeToMetaRange(_device->getGainRange(dir, chan, name), MIN_GAIN_STEP); 141 | } 142 | 143 | uhd::sensor_value_t get_mboard_sensor(const std::string &name) 144 | { 145 | return argInfoToSensor(_device->getSensorInfo(name), _device->readSensor(name)); 146 | } 147 | 148 | uhd::sensor_value_t get_channel_sensor(const int dir, const size_t chan, const std::string &name) 149 | { 150 | return argInfoToSensor(_device->getSensorInfo(dir, chan, name), _device->readSensor(dir, chan, name)); 151 | } 152 | 153 | void old_issue_stream_cmd(const size_t chan, const uhd::stream_cmd_t &cmd) 154 | { 155 | auto stream = _rx_streamers[chan].lock(); 156 | if (stream) stream->issue_stream_cmd(cmd); 157 | } 158 | 159 | void setupChannelHooks(); 160 | void setupChannelHooks(const int dir, const size_t chan, const std::string &dirName, const std::string &chName); 161 | void setupFakeChannelHooks(const int dir, const size_t chan, const std::string &dirName, const std::string &chName); 162 | 163 | void set_gpio_attr(const std::string &bank, const std::string &attr, const boost::uint32_t value) 164 | { 165 | if (attr == "READBACK") return; //readback is never written 166 | if (attr == "OUT") return _device->writeGPIO(bank, value); 167 | if (attr == "DDR") return _device->writeGPIODir(bank, value); 168 | return _device->writeGPIO(bank+":"+attr, value); 169 | } 170 | 171 | boost::uint32_t get_gpio_attr(const std::string &bank, const std::string &attr) 172 | { 173 | if (attr == "READBACK") return _device->readGPIO(bank); 174 | if (attr == "OUT") return _device->readGPIO(bank); //usually OUT is cached output setting 175 | if (attr == "DDR") return _device->readGPIODir(bank); 176 | return _device->readGPIO(bank+":"+attr); 177 | } 178 | 179 | private: 180 | SoapySDR::Device *_device; 181 | std::map> _sampleRates; 182 | 183 | //stash streamers to implement old-style issue stream cmd and async message 184 | #if UHD_VERSION >= 4000000 185 | std::map > _rx_streamers; 186 | std::map > _tx_streamers; 187 | #else 188 | std::map > _rx_streamers; 189 | std::map > _tx_streamers; 190 | #endif 191 | }; 192 | 193 | /*********************************************************************** 194 | * Factory and initialization 195 | **********************************************************************/ 196 | static boost::mutex &suMutexMaker(void) 197 | { 198 | static boost::mutex m; 199 | return m; 200 | } 201 | 202 | UHDSoapyDevice::UHDSoapyDevice(const uhd::device_addr_t &args) 203 | { 204 | { 205 | boost::mutex::scoped_lock l(suMutexMaker()); 206 | _device = SoapySDR::Device::make(dictToKwargs(args)); 207 | } 208 | 209 | //optional frontend map args 210 | if (args.has_key("rx_map")) _device->setFrontendMapping(SOAPY_SDR_RX, args.get("rx_map")); 211 | if (args.has_key("tx_map")) _device->setFrontendMapping(SOAPY_SDR_TX, args.get("tx_map")); 212 | 213 | //setup property tree 214 | _tree = uhd::property_tree::make(); 215 | const uhd::fs_path mb_path = "/mboards/0"; 216 | _tree->create("/name").set(_device->getDriverKey()); 217 | _tree->create(mb_path / "name").set(_device->getHardwareKey()); 218 | 219 | //mb eeprom filled with hardware info 220 | uhd::usrp::mboard_eeprom_t mb_eeprom; 221 | const uhd::device_addr_t hardware_info(kwargsToDict(_device->getHardwareInfo())); 222 | for(const std::string &key : hardware_info.keys()) mb_eeprom[key] = hardware_info[key]; 223 | _tree->create(mb_path / "eeprom").set(mb_eeprom); 224 | 225 | //the frontend mapping 226 | _tree->create(mb_path / "rx_subdev_spec") 227 | .publish(boost::bind(&UHDSoapyDevice::get_frontend_mapping, this, SOAPY_SDR_RX)) 228 | .subscribe(boost::bind(&UHDSoapyDevice::set_frontend_mapping, this, SOAPY_SDR_RX, _1)); 229 | _tree->create(mb_path / "tx_subdev_spec") 230 | .publish(boost::bind(&UHDSoapyDevice::get_frontend_mapping, this, SOAPY_SDR_TX)) 231 | .subscribe(boost::bind(&UHDSoapyDevice::set_frontend_mapping, this, SOAPY_SDR_TX, _1)); 232 | 233 | //timed command support 234 | _tree->create(mb_path / "time" / "cmd") 235 | .subscribe(boost::bind(&UHDSoapyDevice::set_hardware_time, this, "CMD", _1)); 236 | _tree->create(mb_path / "tick_rate") 237 | .publish(boost::bind(&SoapySDR::Device::getMasterClockRate, _device)) 238 | .subscribe(boost::bind(&SoapySDR::Device::setMasterClockRate, _device, _1)); 239 | 240 | //hardware time support 241 | _tree->create(mb_path / "time" / "now") 242 | .publish(boost::bind(&UHDSoapyDevice::get_hardware_time, this, "")) 243 | .subscribe(boost::bind(&UHDSoapyDevice::set_hardware_time, this, "", _1)); 244 | _tree->create(mb_path / "time" / "pps") 245 | .publish(boost::bind(&UHDSoapyDevice::get_hardware_time, this, "PPS")) 246 | .subscribe(boost::bind(&UHDSoapyDevice::set_hardware_time, this, "PPS", _1)); 247 | 248 | //clock and time sources 249 | _tree->create >(mb_path / "clock_source"/ "options") 250 | .publish(boost::bind(&SoapySDR::Device::listClockSources, _device)); 251 | _tree->create(mb_path / "clock_source" / "value") 252 | .publish(boost::bind(&SoapySDR::Device::getClockSource, _device)) 253 | .subscribe(boost::bind(&SoapySDR::Device::setClockSource, _device, _1)); 254 | _tree->create >(mb_path / "time_source"/ "options") 255 | .publish(boost::bind(&SoapySDR::Device::listTimeSources, _device)); 256 | _tree->create(mb_path / "time_source" / "value") 257 | .publish(boost::bind(&SoapySDR::Device::getTimeSource, _device)) 258 | .subscribe(boost::bind(&SoapySDR::Device::setTimeSource, _device, _1)); 259 | 260 | //mboard sensors 261 | _tree->create(mb_path / "sensors"); //ensure this path exists 262 | for(const std::string &name : _device->listSensors()) 263 | { 264 | _tree->create(mb_path / "sensors" / name) 265 | .publish(boost::bind(&UHDSoapyDevice::get_mboard_sensor, this, name)); 266 | } 267 | 268 | //gpio banks 269 | for(const std::string &bank : _device->listGPIOBanks()) 270 | { 271 | std::vector attrs; 272 | attrs.push_back("CTRL"); 273 | attrs.push_back("DDR"); 274 | attrs.push_back("OUT"); 275 | attrs.push_back("ATR_0X"); 276 | attrs.push_back("ATR_RX"); 277 | attrs.push_back("ATR_TX"); 278 | attrs.push_back("ATR_XX"); 279 | attrs.push_back("READBACK"); 280 | for(const std::string &attr : attrs) 281 | { 282 | _tree->create(mb_path / "gpio" / bank / attr) 283 | .subscribe(boost::bind(&UHDSoapyDevice::set_gpio_attr, this, bank, attr, _1)) 284 | .publish(boost::bind(&UHDSoapyDevice::get_gpio_attr, this, bank, attr)); 285 | } 286 | } 287 | 288 | //setup channel and frontend hooks 289 | this->setupChannelHooks(); 290 | } 291 | 292 | UHDSoapyDevice::~UHDSoapyDevice(void) 293 | { 294 | boost::mutex::scoped_lock l(suMutexMaker()); 295 | SoapySDR::Device::unmake(_device); 296 | } 297 | 298 | void UHDSoapyDevice::setupChannelHooks() 299 | { 300 | static const std::string kRxDirName = "rx"; 301 | static const std::string kTxDirName = "tx"; 302 | const size_t numRxChannels = _device->getNumChannels(SOAPY_SDR_RX); 303 | const size_t numTxChannels = _device->getNumChannels(SOAPY_SDR_TX); 304 | 305 | //We have to build up the same number of TX and RX channels to make UHD 306 | //happy. If there are less channels in one direction than another, we fill 307 | //in the direction with dummy channels. 308 | const size_t numChannels = std::max(numRxChannels, numTxChannels); 309 | 310 | for (size_t ch = 0; ch < numChannels; ch++) 311 | { 312 | const std::string chName(boost::lexical_cast(ch)); 313 | if (ch < numRxChannels) 314 | this->setupChannelHooks(SOAPY_SDR_RX, ch, kRxDirName, chName); 315 | else 316 | this->setupFakeChannelHooks(SOAPY_SDR_RX, ch, kRxDirName, chName); 317 | 318 | if (ch < numTxChannels) 319 | this->setupChannelHooks(SOAPY_SDR_TX, ch, kTxDirName, chName); 320 | else 321 | this->setupFakeChannelHooks(SOAPY_SDR_TX, ch, kTxDirName, chName); 322 | } 323 | } 324 | 325 | void UHDSoapyDevice::setupChannelHooks(const int dir, const size_t chan, const std::string &dirName, const std::string &chName) 326 | { 327 | const uhd::fs_path mb_path = "/mboards/0"; 328 | const uhd::fs_path rf_fe_path = mb_path / "dboards" / chName / (dirName+"_frontends") / chName; 329 | const uhd::fs_path dsp_path = mb_path / (dirName+"_dsps") / chName; 330 | const uhd::fs_path codec_path = mb_path / (dirName+"_codecs") / chName; 331 | 332 | _tree->create(codec_path / "name").set("Soapy"+std::string((dir==SOAPY_SDR_RX)?"ADC":"DAC")); 333 | _tree->create(codec_path / "gains"); //empty, gains in frontend 334 | _tree->create(rf_fe_path / "gains"); //in case its empty 335 | _tree->create(rf_fe_path / "name").set("SoapyRF"); 336 | _tree->create(rf_fe_path / "connection").set("IQ"); 337 | 338 | //names of the tunable components 339 | const std::vector comps = _device->listFrequencies(dir, chan); 340 | const std::string rfCompName = (comps.size()>0)?comps.at(0):"RF"; 341 | const std::string bbCompName = (comps.size()>1)?comps.at(1):""; 342 | 343 | //samp rate 344 | _tree->create(dsp_path / "rate" / "range") 345 | .publish(boost::bind(&UHDSoapyDevice::get_rate_range, this, dir, chan)); 346 | _tree->create(dsp_path / "rate" / "value") 347 | .publish(boost::bind(&SoapySDR::Device::getSampleRate, _device, dir, chan)) 348 | .subscribe(boost::bind(&UHDSoapyDevice::set_sample_rate, this, dir, chan, _1)); 349 | 350 | //dsp freq (when there is no tunable cordic) 351 | if (bbCompName.empty()) 352 | { 353 | _tree->create(dsp_path / "freq" / "value").set(0.0); 354 | _tree->create(dsp_path / "freq" / "range").set(uhd::meta_range_t(0.0, 0.0)); 355 | } 356 | //dsp freq 357 | else 358 | { 359 | _tree->create(dsp_path / "freq" / "value") 360 | .publish(boost::bind(&SoapySDR::Device::getFrequency, _device, dir, chan, bbCompName)) 361 | .subscribe(boost::bind(&UHDSoapyDevice::set_frequency, this, dir, chan, bbCompName, _1)); 362 | _tree->create(dsp_path / "freq" / "range") 363 | .publish(boost::bind(&UHDSoapyDevice::get_freq_range, this, dir, chan, bbCompName)); 364 | } 365 | 366 | //old style stream cmd 367 | if (dir == SOAPY_SDR_RX) 368 | { 369 | _tree->create(dsp_path / "stream_cmd") 370 | .subscribe(boost::bind(&UHDSoapyDevice::old_issue_stream_cmd, this, chan, _1)); 371 | } 372 | 373 | //frontend sensors 374 | _tree->create(rf_fe_path / "sensors"); //ensure this path exists 375 | for(const std::string &name : _device->listSensors(dir, chan)) 376 | { 377 | //install the sensor 378 | _tree->create(rf_fe_path / "sensors" / name) 379 | .publish(boost::bind(&UHDSoapyDevice::get_channel_sensor, this, dir, chan, name)); 380 | } 381 | 382 | //dummy eeprom values 383 | if (dir == SOAPY_SDR_RX) 384 | { 385 | _tree->create(mb_path / "dboards" / chName / "rx_eeprom") 386 | .set(uhd::usrp::dboard_eeprom_t()); 387 | } 388 | else 389 | { 390 | _tree->create(mb_path / "dboards" / chName / "tx_eeprom") 391 | .set(uhd::usrp::dboard_eeprom_t()); 392 | _tree->create(mb_path / "dboards" / chName / "gdb_eeprom") 393 | .set(uhd::usrp::dboard_eeprom_t()); 394 | } 395 | 396 | //gains 397 | for(const std::string &name : _device->listGains(dir, chan)) 398 | { 399 | _tree->create(rf_fe_path / "gains" / name / "range") 400 | .publish(boost::bind(&UHDSoapyDevice::get_gain_range, this, dir, chan, name)); 401 | _tree->create(rf_fe_path / "gains" / name / "value") 402 | .publish(boost::bind(&SoapySDR::Device::getGain, _device, dir, chan, name)) 403 | .subscribe(boost::bind(&SoapySDR::Device::setGain, _device, dir, chan, name, _1)); 404 | } 405 | 406 | //agc 407 | _tree->create(rf_fe_path / "gain" / "agc" / "enable") 408 | .publish(boost::bind(&SoapySDR::Device::getGainMode, _device, dir, chan)) 409 | .subscribe(boost::bind(&SoapySDR::Device::setGainMode, _device, dir, chan, _1)); 410 | 411 | //freq 412 | _tree->create(rf_fe_path / "freq" / "value") 413 | .publish(boost::bind(&SoapySDR::Device::getFrequency, _device, dir, chan, rfCompName)) 414 | .subscribe(boost::bind(&UHDSoapyDevice::set_frequency, this, dir, chan, rfCompName, _1)); 415 | _tree->create(rf_fe_path / "freq" / "range") 416 | .publish(boost::bind(&UHDSoapyDevice::get_freq_range, this, dir, chan, rfCompName)); 417 | _tree->create(rf_fe_path / "use_lo_offset").set(false); 418 | _tree->create(rf_fe_path / "tune_args") 419 | .subscribe(boost::bind(&UHDSoapyDevice::stash_tune_args, this, dir, chan, _1)); 420 | 421 | //ant 422 | _tree->create >(rf_fe_path / "antenna" / "options") 423 | .publish(boost::bind(&SoapySDR::Device::listAntennas, _device, dir, chan)); 424 | _tree->create(rf_fe_path / "antenna" / "value") 425 | .publish(boost::bind(&SoapySDR::Device::getAntenna, _device, dir, chan)) 426 | .subscribe(boost::bind(&SoapySDR::Device::setAntenna, _device, dir, chan, _1)); 427 | 428 | //bw 429 | _tree->create(rf_fe_path / "bandwidth" / "value") 430 | .publish(boost::bind(&SoapySDR::Device::getBandwidth, _device, dir, chan)) 431 | .subscribe(boost::bind(&SoapySDR::Device::setBandwidth, _device, dir, chan, _1)); 432 | _tree->create(rf_fe_path / "bandwidth" / "range") 433 | .publish(boost::bind(&UHDSoapyDevice::get_bw_range, this, dir, chan)); 434 | 435 | //corrections 436 | if (_device->hasDCOffsetMode(dir, chan)) 437 | { 438 | _tree->create(rf_fe_path / "dc_offset" / "enable") 439 | .publish(boost::bind(&SoapySDR::Device::getDCOffsetMode, _device, dir, chan)) 440 | .subscribe(boost::bind(&SoapySDR::Device::setDCOffsetMode, _device, dir, chan, _1)); 441 | } 442 | 443 | if (_device->hasDCOffset(dir, chan)) 444 | { 445 | _tree->create>(rf_fe_path / "dc_offset" / "value") 446 | .publish(boost::bind(&SoapySDR::Device::getDCOffset, _device, dir, chan)) 447 | .subscribe(boost::bind(&SoapySDR::Device::setDCOffset, _device, dir, chan, _1)); 448 | } 449 | 450 | if (_device->hasIQBalance(dir, chan)) 451 | { 452 | _tree->create>(rf_fe_path / "iq_balance" / "value") 453 | .publish(boost::bind(&SoapySDR::Device::getIQBalance, _device, dir, chan)) 454 | .subscribe(boost::bind(&SoapySDR::Device::setIQBalance, _device, dir, chan, _1)); 455 | } 456 | 457 | #ifdef SOAPY_SDR_API_HAS_IQ_BALANCE_MODE 458 | if (_device->hasIQBalanceMode(dir, chan)) 459 | { 460 | _tree->create(rf_fe_path / "iq_balance" / "enable") 461 | .publish(boost::bind(&SoapySDR::Device::getIQBalanceMode, _device, dir, chan)) 462 | .subscribe(boost::bind(&SoapySDR::Device::setIQBalanceMode, _device, dir, chan, _1)); 463 | } 464 | #endif 465 | } 466 | 467 | void UHDSoapyDevice::setupFakeChannelHooks(const int dir, const size_t /*chan*/, const std::string &dirName, const std::string &chName) 468 | { 469 | const uhd::fs_path mb_path = "/mboards/0"; 470 | const uhd::fs_path rf_fe_path = mb_path / "dboards" / chName / (dirName+"_frontends") / chName; 471 | const uhd::fs_path dsp_path = mb_path / (dirName+"_dsps") / chName; 472 | const uhd::fs_path codec_path = mb_path / (dirName+"_codecs") / chName; 473 | 474 | _tree->create(codec_path / "name").set("None"); 475 | _tree->create(codec_path / "gains"); //empty, gains in frontend 476 | _tree->create(rf_fe_path / "gains"); //in case its empty 477 | _tree->create(rf_fe_path / "name").set("None"); 478 | _tree->create(rf_fe_path / "connection").set("IQ"); 479 | 480 | //samp rate 481 | _tree->create(dsp_path / "rate" / "range").set(uhd::meta_range_t(0.0, 0.0)); 482 | _tree->create(dsp_path / "rate" / "value").set(0.0); 483 | 484 | //dsp freq 485 | _tree->create(dsp_path / "freq" / "value").set(0.0); 486 | _tree->create(dsp_path / "freq" / "range").set(uhd::meta_range_t(0.0, 0.0)); 487 | 488 | //frontend sensors 489 | _tree->create(rf_fe_path / "sensors"); //ensure this path exists 490 | 491 | //dummy eeprom values 492 | if (dir == SOAPY_SDR_RX) 493 | { 494 | _tree->create(mb_path / "dboards" / chName / "rx_eeprom") 495 | .set(uhd::usrp::dboard_eeprom_t()); 496 | } 497 | else 498 | { 499 | _tree->create(mb_path / "dboards" / chName / "tx_eeprom") 500 | .set(uhd::usrp::dboard_eeprom_t()); 501 | _tree->create(mb_path / "dboards" / chName / "gdb_eeprom") 502 | .set(uhd::usrp::dboard_eeprom_t()); 503 | } 504 | 505 | //freq 506 | _tree->create(rf_fe_path / "freq" / "value").set(0.0); 507 | _tree->create(rf_fe_path / "freq" / "range").set(uhd::meta_range_t(0.0, 0.0)); 508 | _tree->create(rf_fe_path / "use_lo_offset").set(false); 509 | 510 | //ant 511 | _tree->create(rf_fe_path / "antenna" / "value").set(""); 512 | _tree->create >(rf_fe_path / "antenna" / "options").set(std::vector()); 513 | 514 | //bw 515 | _tree->create(rf_fe_path / "bandwidth" / "value").set(0.0); 516 | _tree->create(rf_fe_path / "bandwidth" / "range").set(uhd::meta_range_t(0.0, 0.0)); 517 | } 518 | 519 | /*********************************************************************** 520 | * RX Streaming 521 | **********************************************************************/ 522 | static SoapySDR::Stream *make_stream(SoapySDR::Device *d, const int direction, const uhd::stream_args_t &args) 523 | { 524 | //ensure at least one channel selected 525 | std::vector chans = args.channels; 526 | if (chans.empty()) chans.push_back(0); 527 | 528 | //load keyword args from stream args 529 | SoapySDR::Kwargs kwargs(dictToKwargs(args.args)); 530 | 531 | //fill in WIRE keyword when specified 532 | if (not args.otw_format.empty() and kwargs.count("WIRE") == 0) 533 | { 534 | kwargs["WIRE"] = args.otw_format; 535 | } 536 | 537 | //the format string 538 | std::string hostFormat; 539 | for(const char ch : args.cpu_format) 540 | { 541 | if (ch == 'c') hostFormat = "C" + hostFormat; 542 | else if (ch == 'f') hostFormat += "F"; 543 | else if (ch == 's') hostFormat += "S"; 544 | else if (std::isdigit(ch)) hostFormat += ch; 545 | else throw std::runtime_error("UHDSoapyDevice::setupStream("+args.cpu_format+") unknown format"); 546 | } 547 | 548 | return d->setupStream(direction, hostFormat, chans, kwargs); 549 | } 550 | 551 | class UHDSoapyRxStream : public uhd::rx_streamer 552 | { 553 | public: 554 | UHDSoapyRxStream(SoapySDR::Device *d, const uhd::stream_args_t &args, const double &sampRate): 555 | _device(d), 556 | _stream(make_stream(d, SOAPY_SDR_RX, args)), 557 | _nchan(std::max(1, args.channels.size())), 558 | _elemSize(uhd::convert::get_bytes_per_item(args.cpu_format)), 559 | _nextTimeValid(false), 560 | _sampRate(sampRate) 561 | { 562 | _offsetBuffs.resize(_nchan); 563 | } 564 | 565 | ~UHDSoapyRxStream(void) 566 | { 567 | _device->deactivateStream(_stream); 568 | _device->closeStream(_stream); 569 | } 570 | 571 | size_t get_num_channels(void) const 572 | { 573 | return _nchan; 574 | } 575 | 576 | size_t get_max_num_samps(void) const 577 | { 578 | return _device->getStreamMTU(_stream); 579 | } 580 | 581 | size_t recv( 582 | const buffs_type &buffs, 583 | const size_t nsamps_per_buff, 584 | uhd::rx_metadata_t &md, 585 | const double timeout = 0.1, 586 | const bool one_packet = false 587 | ) 588 | { 589 | size_t total = 0; 590 | md.reset(); 591 | 592 | while (total < nsamps_per_buff) 593 | { 594 | int flags = 0; 595 | if (one_packet) flags |= SOAPY_SDR_ONE_PACKET; 596 | long long timeNs = 0; 597 | size_t numElems = (nsamps_per_buff-total); 598 | for (size_t i = 0; i < _nchan; i++) _offsetBuffs[i] = ((char *)buffs[i]) + total*_elemSize; 599 | int ret = _device->readStream(_stream, &(_offsetBuffs[0]), numElems, flags, timeNs, long(timeout*1e6)); 600 | 601 | //deal with return error codes 602 | switch (ret) 603 | { 604 | case SOAPY_SDR_TIMEOUT: 605 | md.error_code = uhd::rx_metadata_t::ERROR_CODE_TIMEOUT; 606 | break; 607 | 608 | case SOAPY_SDR_STREAM_ERROR: 609 | md.error_code = uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN; 610 | break; 611 | 612 | case SOAPY_SDR_CORRUPTION: 613 | md.error_code = uhd::rx_metadata_t::ERROR_CODE_BAD_PACKET; 614 | break; 615 | 616 | case SOAPY_SDR_OVERFLOW: 617 | md.error_code = uhd::rx_metadata_t::ERROR_CODE_OVERFLOW; 618 | break; 619 | 620 | case SOAPY_SDR_TIME_ERROR: 621 | md.error_code = uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND; 622 | break; 623 | } 624 | if (ret < 0) break; 625 | total += ret; 626 | 627 | //more fragments always over written by last recv 628 | md.more_fragments = (flags & SOAPY_SDR_MORE_FRAGMENTS) != 0; 629 | 630 | //apply time if this is the first recv 631 | if (total == size_t(ret)) 632 | { 633 | md.has_time_spec = (flags & SOAPY_SDR_HAS_TIME) != 0; 634 | md.time_spec = uhd::time_spec_t::from_ticks(timeNs, 1e9); 635 | if (md.has_time_spec) 636 | { 637 | _nextTimeValid = true; 638 | _nextTime = md.time_spec; 639 | } 640 | } 641 | 642 | //mark end of burst and exit call 643 | if ((flags & SOAPY_SDR_END_BURST) != 0) 644 | { 645 | md.end_of_burst = true; 646 | break; 647 | } 648 | 649 | //inline overflow indication 650 | if ((flags & SOAPY_SDR_END_ABRUPT) != 0) 651 | { 652 | md.error_code = uhd::rx_metadata_t::ERROR_CODE_OVERFLOW; 653 | break; 654 | } 655 | 656 | //one pkt mode, end loop 657 | if (one_packet) break; 658 | } 659 | 660 | //time interpolation support 661 | if (_sampRate != 0.0 and _nextTimeValid) 662 | { 663 | //if the metadata does not have a time, use the incremented time 664 | if (not md.has_time_spec) 665 | { 666 | md.has_time_spec = true; 667 | md.time_spec = _nextTime; 668 | } 669 | 670 | //increment for the next call 671 | _nextTime += uhd::time_spec_t::from_ticks(total, _sampRate); 672 | } 673 | 674 | return total; 675 | } 676 | 677 | void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd) 678 | { 679 | int flags = 0; 680 | if (not stream_cmd.stream_now) flags |= SOAPY_SDR_HAS_TIME; 681 | long long timeNs = stream_cmd.time_spec.to_ticks(1e9); 682 | size_t numElems = 0; 683 | bool activate = true; 684 | 685 | switch(stream_cmd.stream_mode) 686 | { 687 | case uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS: 688 | break; 689 | 690 | case uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS: 691 | activate = false; 692 | break; 693 | 694 | case uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE: 695 | flags |= SOAPY_SDR_END_BURST; 696 | numElems = stream_cmd.num_samps; 697 | break; 698 | 699 | case uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE: 700 | numElems = stream_cmd.num_samps; 701 | break; 702 | } 703 | 704 | int ret = 0; 705 | if (activate) ret = _device->activateStream(_stream, flags, timeNs, numElems); 706 | else ret = _device->deactivateStream(_stream, flags, timeNs); 707 | if (ret != 0) throw std::runtime_error(str(boost::format("UHDSoapyRxStream::issue_stream_cmd() = %d") % ret)); 708 | } 709 | 710 | #if UHD_VERSION >= 4080000 711 | void post_input_action( 712 | const std::shared_ptr&, const size_t) override 713 | { 714 | throw uhd::not_implemented_error("post_input_action is not implemented here!"); 715 | } 716 | #endif 717 | 718 | private: 719 | SoapySDR::Device *_device; 720 | SoapySDR::Stream *_stream; 721 | const size_t _nchan; 722 | const size_t _elemSize; 723 | std::vector _offsetBuffs; 724 | bool _doErrorOnNextRecv; 725 | bool _nextTimeValid; 726 | uhd::time_spec_t _nextTime; 727 | const double &_sampRate; 728 | }; 729 | 730 | uhd::rx_streamer::sptr UHDSoapyDevice::get_rx_stream(const uhd::stream_args_t &args) 731 | { 732 | size_t ch = 0; if (not args.channels.empty()) ch = args.channels.front(); 733 | uhd::rx_streamer::sptr stream(new UHDSoapyRxStream(_device, args, _sampleRates[SOAPY_SDR_RX][ch])); 734 | for(const size_t ch : args.channels) _rx_streamers[ch] = stream; 735 | if (args.channels.empty()) _rx_streamers[0] = stream; 736 | return stream; 737 | } 738 | 739 | /*********************************************************************** 740 | * TX Streaming 741 | **********************************************************************/ 742 | class UHDSoapyTxStream : public uhd::tx_streamer 743 | { 744 | public: 745 | UHDSoapyTxStream(SoapySDR::Device *d, const uhd::stream_args_t &args): 746 | _active(false), 747 | _device(d), 748 | _stream(make_stream(d, SOAPY_SDR_TX, args)), 749 | _nchan(std::max(1, args.channels.size())), 750 | _elemSize(uhd::convert::get_bytes_per_item(args.cpu_format)) 751 | { 752 | _offsetBuffs.resize(_nchan); 753 | } 754 | 755 | ~UHDSoapyTxStream(void) 756 | { 757 | if (_active) _device->deactivateStream(_stream); 758 | _device->closeStream(_stream); 759 | } 760 | 761 | size_t get_num_channels(void) const 762 | { 763 | return _nchan; 764 | } 765 | 766 | size_t get_max_num_samps(void) const 767 | { 768 | return _device->getStreamMTU(_stream); 769 | } 770 | 771 | size_t send( 772 | const buffs_type &buffs, 773 | size_t nsamps_per_buff, 774 | const uhd::tx_metadata_t &md, 775 | const double timeout = 0.1 776 | ) 777 | { 778 | //perform activation at the latest/on the first call to send 779 | if (not _active) 780 | { 781 | _device->activateStream(_stream); 782 | _active = true; 783 | } 784 | 785 | size_t total = 0; 786 | const long long timeNs(md.time_spec.to_ticks(1e9)); 787 | 788 | while (total < nsamps_per_buff) 789 | { 790 | int flags = 0; 791 | size_t numElems = (nsamps_per_buff-total); 792 | if (md.has_time_spec and total == 0) flags |= SOAPY_SDR_HAS_TIME; 793 | if (md.end_of_burst) flags |= SOAPY_SDR_END_BURST; 794 | for (size_t i = 0; i < _nchan; i++) _offsetBuffs[i] = ((char *)buffs[i]) + total*_elemSize; 795 | int ret = _device->writeStream(_stream, &(_offsetBuffs[0]), numElems, flags, timeNs, long(timeout*1e6)); 796 | if (ret == SOAPY_SDR_TIMEOUT) break; 797 | if (ret < 0) throw std::runtime_error(str(boost::format("UHDSoapyTxStream::send() = %d") % ret)); 798 | total += ret; 799 | } 800 | 801 | //implement deactivation hook for very last sample consumed on end of burst 802 | if (_active and md.end_of_burst and total == nsamps_per_buff) 803 | { 804 | _device->deactivateStream(_stream); 805 | _active = false; 806 | } 807 | 808 | return total; 809 | } 810 | 811 | bool recv_async_msg(uhd::async_metadata_t &md, double timeout = 0.1) 812 | { 813 | size_t chanMask = 0; 814 | int flags = 0; 815 | long long timeNs = 0; 816 | int ret = _device->readStreamStatus(_stream, chanMask, flags, timeNs, long(timeout*1e6)); 817 | 818 | //save the first channel found in the mask 819 | md.channel = 0; 820 | for (size_t i = 0; i < _nchan; i++) 821 | { 822 | if ((chanMask & (1 << i)) == 0) continue; 823 | md.channel = i; 824 | break; 825 | } 826 | 827 | //convert the time 828 | md.has_time_spec = (flags & SOAPY_SDR_HAS_TIME) != 0; 829 | md.time_spec = uhd::time_spec_t::from_ticks(timeNs, 1e9); 830 | 831 | //check flags 832 | if ((flags & SOAPY_SDR_END_BURST) != 0) 833 | { 834 | md.event_code = uhd::async_metadata_t::EVENT_CODE_BURST_ACK; 835 | } 836 | 837 | //set event code based on ret 838 | switch (ret) 839 | { 840 | case SOAPY_SDR_TIMEOUT: return false; 841 | 842 | case SOAPY_SDR_STREAM_ERROR: 843 | md.event_code = uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR; 844 | break; 845 | 846 | case SOAPY_SDR_NOT_SUPPORTED: 847 | md.event_code = uhd::async_metadata_t::EVENT_CODE_USER_PAYLOAD; 848 | break; 849 | 850 | case SOAPY_SDR_TIME_ERROR: 851 | md.event_code = uhd::async_metadata_t::EVENT_CODE_TIME_ERROR; 852 | break; 853 | 854 | case SOAPY_SDR_UNDERFLOW: 855 | md.event_code = uhd::async_metadata_t::EVENT_CODE_UNDERFLOW; 856 | break; 857 | } 858 | 859 | return true; 860 | } 861 | 862 | #if UHD_VERSION >= 4080000 863 | void post_output_action( 864 | const std::shared_ptr&, const size_t) override 865 | { 866 | throw uhd::not_implemented_error("post_output_action is not implemented here!"); 867 | } 868 | #endif 869 | 870 | private: 871 | bool _active; 872 | SoapySDR::Device *_device; 873 | SoapySDR::Stream *_stream; 874 | const size_t _nchan; 875 | const size_t _elemSize; 876 | std::vector _offsetBuffs; 877 | }; 878 | 879 | uhd::tx_streamer::sptr UHDSoapyDevice::get_tx_stream(const uhd::stream_args_t &args) 880 | { 881 | uhd::tx_streamer::sptr stream(new UHDSoapyTxStream(_device, args)); 882 | for(const size_t ch : args.channels) _tx_streamers[ch] = stream; 883 | if (args.channels.empty()) _tx_streamers[0] = stream; 884 | return stream; 885 | } 886 | 887 | bool UHDSoapyDevice::recv_async_msg(uhd::async_metadata_t &md, double timeout) 888 | { 889 | auto stream = _tx_streamers[0].lock(); 890 | if (not stream) return false; 891 | return stream->recv_async_msg(md, timeout); 892 | } 893 | 894 | /*********************************************************************** 895 | * Soapy Logger handle forward to UHD 896 | **********************************************************************/ 897 | static void UHDSoapyLogger(const SoapySDR::LogLevel logLevel, const char *message) 898 | { 899 | #define component "UHDSoapyDevice" 900 | switch(logLevel) 901 | { 902 | #ifdef UHD_HAS_MSG_HPP 903 | case SOAPY_SDR_FATAL: 904 | case SOAPY_SDR_CRITICAL: 905 | case SOAPY_SDR_ERROR: UHD_MSG(error) << message << std::endl; break; 906 | case SOAPY_SDR_WARNING: UHD_MSG(warning) << message << std::endl; break; 907 | case SOAPY_SDR_NOTICE: 908 | case SOAPY_SDR_INFO: 909 | case SOAPY_SDR_DEBUG: 910 | case SOAPY_SDR_TRACE: UHD_MSG(status) << message << std::endl; break; 911 | case SOAPY_SDR_SSI: UHD_MSG(fastpath) << message << std::flush; break; 912 | #else 913 | case SOAPY_SDR_FATAL: 914 | case SOAPY_SDR_CRITICAL: UHD_LOG_FATAL(component, message); break; 915 | case SOAPY_SDR_ERROR: UHD_LOG_FATAL(component, message); break; 916 | case SOAPY_SDR_WARNING: UHD_LOG_WARNING(component, message); break; 917 | case SOAPY_SDR_NOTICE: 918 | case SOAPY_SDR_INFO: UHD_LOG_INFO(component, message); break; 919 | case SOAPY_SDR_DEBUG: 920 | case SOAPY_SDR_TRACE: UHD_LOG_TRACE(component, message); break; 921 | case SOAPY_SDR_SSI: UHD_LOG_FASTPATH(message); break; 922 | #endif 923 | } 924 | } 925 | 926 | /*********************************************************************** 927 | * Registration 928 | **********************************************************************/ 929 | static uhd::device::sptr makeUHDSoapyDevice(const uhd::device_addr_t &device_addr) 930 | { 931 | SoapySDR::registerLogHandler(&UHDSoapyLogger); 932 | return uhd::device::sptr(new UHDSoapyDevice(device_addr)); 933 | } 934 | 935 | static uhd::device_addrs_t findUHDSoapyDevice(const uhd::device_addr_t &args_) 936 | { 937 | //prevent going into the the SoapyUHDDevice 938 | SoapySDR::Kwargs args(dictToKwargs(args_)); 939 | if (args.count(SOAPY_UHD_NO_DEEPER) != 0) return uhd::device_addrs_t(); 940 | //when driver is specified and its not uhd, we can go deeper... 941 | if (args.count("driver") != 0 and args.at("driver") != "uhd"){} 942 | else args[SOAPY_UHD_NO_DEEPER] = ""; //otherwise no-deeper 943 | 944 | //type filter for soapy devices 945 | if (args.count("type") != 0 and args.at("type") != "soapy") return uhd::device_addrs_t(); 946 | 947 | uhd::device_addrs_t result; 948 | for(SoapySDR::Kwargs found : SoapySDR::Device::enumerate(args)) 949 | { 950 | found.erase(SOAPY_UHD_NO_DEEPER); 951 | result.push_back(kwargsToDict(found)); 952 | result.back()["type"] = "soapy"; 953 | if (found.count("serial") == 0) 954 | { 955 | auto serial = std::hash()(SoapySDR::KwargsToString(found)); 956 | result.back()["serial"] = std::to_string(serial); 957 | } 958 | } 959 | return result; 960 | } 961 | 962 | UHD_STATIC_BLOCK(registerUHDSoapyDevice) 963 | { 964 | #ifdef UHD_HAS_DEVICE_FILTER 965 | uhd::device::register_device(&findUHDSoapyDevice, &makeUHDSoapyDevice, uhd::device::USRP); 966 | #else 967 | uhd::device::register_device(&findUHDSoapyDevice, &makeUHDSoapyDevice); 968 | #endif 969 | } 970 | -------------------------------------------------------------------------------- /SoapyUHDDevice.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Josh Blum 2 | // 2019-2021 Nicholas Corgan 3 | // SPDX-License-Identifier: GPL-3.0 4 | 5 | /*********************************************************************** 6 | * A Soapy module that supports UHD devices within the Soapy API. 7 | **********************************************************************/ 8 | 9 | #include "TypeHelpers.hpp" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #ifdef UHD_HAS_MSG_HPP 16 | #include 17 | #else 18 | #include 19 | #endif 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | /*********************************************************************** 27 | * Stream wrapper 28 | **********************************************************************/ 29 | struct SoapyUHDStream 30 | { 31 | uhd::rx_streamer::sptr rx; 32 | uhd::tx_streamer::sptr tx; 33 | }; 34 | 35 | /*********************************************************************** 36 | * Device interface 37 | **********************************************************************/ 38 | class SoapyUHDDevice : public SoapySDR::Device 39 | { 40 | public: 41 | SoapyUHDDevice(uhd::usrp::multi_usrp::sptr dev, const SoapySDR::Kwargs &args): 42 | _dev(dev), 43 | _type(args.at("type")), 44 | _isNetworkDevice(args.count("addr") != 0) 45 | { 46 | if (args.count("rx_subdev") != 0) _dev->set_rx_subdev_spec(args.at("rx_subdev")); 47 | if (args.count("tx_subdev") != 0) _dev->set_tx_subdev_spec(args.at("tx_subdev")); 48 | } 49 | 50 | /******************************************************************* 51 | * Identification API 52 | ******************************************************************/ 53 | std::string getDriverKey(void) const 54 | { 55 | return _type; 56 | } 57 | 58 | std::string getHardwareKey(void) const 59 | { 60 | return _dev->get_mboard_name(); 61 | } 62 | 63 | SoapySDR::Kwargs getHardwareInfo(void) const 64 | { 65 | SoapySDR::Kwargs out; 66 | for (size_t i = 0; i < this->getNumChannels(SOAPY_SDR_TX); i++) 67 | { 68 | const uhd::dict info = _dev->get_usrp_tx_info(i); 69 | for (const std::string &key : info.keys()) 70 | { 71 | if (key.size() > 3 and key.substr(0, 3) == "tx_") 72 | out[str(boost::format("tx%d_%s") % i % key.substr(3))] = info[key]; 73 | else out[key] = info[key]; 74 | } 75 | } 76 | for (size_t i = 0; i < this->getNumChannels(SOAPY_SDR_RX); i++) 77 | { 78 | const uhd::dict info = _dev->get_usrp_rx_info(i); 79 | for (const std::string &key : info.keys()) 80 | { 81 | if (key.size() > 3 and key.substr(0, 3) == "rx_") 82 | out[str(boost::format("rx%d_%s") % i % key.substr(3))] = info[key]; 83 | else out[key] = info[key]; 84 | } 85 | } 86 | 87 | uhd::property_tree::sptr tree = _get_tree(); 88 | if (tree->exists("/mboards/0/fw_version")) out["fw_version"] = tree->access("/mboards/0/fw_version").get(); 89 | if (tree->exists("/mboards/0/fpga_version")) out["fpga_version"] = tree->access("/mboards/0/fpga_version").get(); 90 | 91 | return out; 92 | } 93 | 94 | /******************************************************************* 95 | * Channels support 96 | ******************************************************************/ 97 | void setFrontendMapping(const int dir, const std::string &mapping) 98 | { 99 | if (dir == SOAPY_SDR_TX) return _dev->set_tx_subdev_spec(mapping); 100 | if (dir == SOAPY_SDR_RX) return _dev->set_rx_subdev_spec(mapping); 101 | } 102 | 103 | std::string getFrontendMapping(const int dir) const 104 | { 105 | if (dir == SOAPY_SDR_TX) return _dev->get_tx_subdev_spec().to_string(); 106 | if (dir == SOAPY_SDR_RX) return _dev->get_rx_subdev_spec().to_string(); 107 | return SoapySDR::Device::getFrontendMapping(dir); 108 | } 109 | 110 | size_t getNumChannels(const int dir) const 111 | { 112 | if (dir == SOAPY_SDR_TX) return _dev->get_tx_num_channels(); 113 | if (dir == SOAPY_SDR_RX) return _dev->get_rx_num_channels(); 114 | return SoapySDR::Device::getNumChannels(dir); 115 | } 116 | 117 | /******************************************************************* 118 | * Stream support 119 | ******************************************************************/ 120 | std::vector getStreamFormats(const int, const size_t) const 121 | { 122 | std::vector formats; 123 | formats.push_back("CS8"); 124 | formats.push_back("CS12"); 125 | formats.push_back("CS16"); 126 | formats.push_back("CF32"); 127 | formats.push_back("CF64"); 128 | return formats; 129 | } 130 | 131 | std::string getNativeStreamFormat(const int, const size_t, double &fullScale) const 132 | { 133 | fullScale = (1 << 15); 134 | return "CS16"; 135 | } 136 | 137 | SoapySDR::ArgInfoList getStreamArgsInfo(const int direction, const size_t) const 138 | { 139 | SoapySDR::ArgInfoList streamArgs; 140 | 141 | SoapySDR::ArgInfo sppArg; 142 | sppArg.key = "spp"; 143 | sppArg.value = "0"; 144 | sppArg.name = "Samples per packet"; 145 | sppArg.description = "The number of samples per packet."; 146 | sppArg.units = "samples"; 147 | sppArg.type = SoapySDR::ArgInfo::INT; 148 | streamArgs.push_back(sppArg); 149 | 150 | SoapySDR::ArgInfo wireFormatArg; 151 | wireFormatArg.key = "WIRE"; 152 | wireFormatArg.value = ""; 153 | wireFormatArg.name = "Bus format"; 154 | wireFormatArg.description = "The format of samples over the bus."; 155 | wireFormatArg.type = SoapySDR::ArgInfo::STRING; 156 | wireFormatArg.options.push_back("sc8"); 157 | wireFormatArg.options.push_back("sc16"); 158 | wireFormatArg.optionNames.push_back("Complex bytes"); 159 | wireFormatArg.optionNames.push_back("Complex shorts"); 160 | streamArgs.push_back(wireFormatArg); 161 | 162 | SoapySDR::ArgInfo peakArgs; 163 | peakArgs.key = "peak"; 164 | peakArgs.value = "1.0"; 165 | peakArgs.name = "Peak value"; 166 | peakArgs.description = "The peak value for scaling in complex byte mode."; 167 | peakArgs.type = SoapySDR::ArgInfo::FLOAT; 168 | streamArgs.push_back(peakArgs); 169 | 170 | const std::string key = (direction == SOAPY_SDR_RX)?"recv":"send"; 171 | const std::string name = (direction == SOAPY_SDR_RX)?"Receive":"Send"; 172 | 173 | SoapySDR::ArgInfo bufSizeArgs; 174 | bufSizeArgs.key = key+"_buff_size"; 175 | bufSizeArgs.value = "0"; 176 | bufSizeArgs.name = name + " socket buffer size"; 177 | bufSizeArgs.description = "The size of the kernel socket buffer in bytes. Use 0 for automatic."; 178 | bufSizeArgs.units = "bytes"; 179 | bufSizeArgs.type = SoapySDR::ArgInfo::INT; 180 | if (_isNetworkDevice) streamArgs.push_back(bufSizeArgs); 181 | 182 | SoapySDR::ArgInfo frameSizeArgs; 183 | frameSizeArgs.key = key+"_frame_size"; 184 | frameSizeArgs.value = ""; 185 | frameSizeArgs.name = name + " frame buffer size"; 186 | frameSizeArgs.description = "The size an individual datagram or frame in bytes."; 187 | frameSizeArgs.units = "bytes"; 188 | frameSizeArgs.type = SoapySDR::ArgInfo::INT; 189 | streamArgs.push_back(frameSizeArgs); 190 | 191 | SoapySDR::ArgInfo numFrameArgs; 192 | numFrameArgs.key = "num_"+key+"_frames"; 193 | numFrameArgs.value = ""; 194 | numFrameArgs.name = name + " number of buffers"; 195 | numFrameArgs.description = "The number of available buffers."; 196 | numFrameArgs.units = "buffers"; 197 | numFrameArgs.type = SoapySDR::ArgInfo::INT; 198 | streamArgs.push_back(numFrameArgs); 199 | 200 | SoapySDR::ArgInfo fullscaleArgs; 201 | fullscaleArgs.key = "fullscale"; 202 | fullscaleArgs.value = "1.0"; 203 | fullscaleArgs.name = "Full-scale amplitude"; 204 | fullscaleArgs.description = "Specifies the full-scale amplitude when using floats (not supported for all devices)."; 205 | fullscaleArgs.type = SoapySDR::ArgInfo::FLOAT; 206 | streamArgs.push_back(fullscaleArgs); 207 | 208 | if(direction == SOAPY_SDR_TX) 209 | { 210 | SoapySDR::ArgInfo underflowPolicyArg; 211 | underflowPolicyArg.key = "underflow_policy"; 212 | underflowPolicyArg.name = "Underflow policy"; 213 | underflowPolicyArg.description = "How the TX DSP should recover from underflow (not supported for all devices)."; 214 | underflowPolicyArg.type = SoapySDR::ArgInfo::STRING; 215 | underflowPolicyArg.options.push_back("next_burst"); 216 | underflowPolicyArg.options.push_back("next_packet"); 217 | underflowPolicyArg.optionNames.push_back("Next burst"); 218 | underflowPolicyArg.optionNames.push_back("Next packet"); 219 | streamArgs.push_back(underflowPolicyArg); 220 | } 221 | 222 | return streamArgs; 223 | } 224 | 225 | SoapySDR::Stream *setupStream(const int dir, const std::string &format, const std::vector &channels, const SoapySDR::Kwargs &args) 226 | { 227 | std::string hostFormat; 228 | for(const char ch : format) 229 | { 230 | if (ch == 'C') hostFormat += "c"; 231 | else if (ch == 'F') hostFormat = "f" + hostFormat; 232 | else if (ch == 'S') hostFormat = "s" + hostFormat; 233 | else if (std::isdigit(ch)) hostFormat += ch; 234 | else throw std::runtime_error("SoapyUHDDevice::setupStream("+format+") unknown format"); 235 | } 236 | 237 | //convert input to stream args 238 | uhd::stream_args_t stream_args(hostFormat); 239 | stream_args.channels = channels; 240 | stream_args.args = kwargsToDict(args); 241 | if (args.count("WIRE") != 0) stream_args.otw_format = args.at("WIRE"); 242 | 243 | //create streamers 244 | SoapyUHDStream *stream = new SoapyUHDStream(); 245 | if (dir == SOAPY_SDR_TX) stream->tx = _dev->get_tx_stream(stream_args); 246 | if (dir == SOAPY_SDR_RX) stream->rx = _dev->get_rx_stream(stream_args); 247 | return reinterpret_cast(stream); 248 | } 249 | 250 | void closeStream(SoapySDR::Stream *handle) 251 | { 252 | SoapyUHDStream *stream = reinterpret_cast(handle); 253 | delete stream; 254 | } 255 | 256 | size_t getStreamMTU(SoapySDR::Stream *handle) const 257 | { 258 | SoapyUHDStream *stream = reinterpret_cast(handle); 259 | if (stream->rx) return stream->rx->get_max_num_samps(); 260 | if (stream->tx) return stream->tx->get_max_num_samps(); 261 | return SoapySDR::Device::getStreamMTU(handle); 262 | } 263 | 264 | int activateStream(SoapySDR::Stream *handle, const int flags, const long long timeNs, const size_t numElems) 265 | { 266 | SoapyUHDStream *stream = reinterpret_cast(handle); 267 | if (not stream->rx) return 0; //NOP, does nothing, but not an error 268 | 269 | //determine stream mode 270 | uhd::stream_cmd_t::stream_mode_t mode; 271 | if (numElems == 0) mode = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS; 272 | else if ((flags & SOAPY_SDR_END_BURST) != 0) mode = uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE; 273 | else mode = uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE; 274 | 275 | //fill in the command 276 | uhd::stream_cmd_t cmd(mode); 277 | cmd.stream_now = (flags & SOAPY_SDR_HAS_TIME) == 0; 278 | cmd.time_spec = uhd::time_spec_t::from_ticks(timeNs, 1e9); 279 | cmd.num_samps = numElems; 280 | 281 | //issue command 282 | stream->rx->issue_stream_cmd(cmd); 283 | return 0; 284 | } 285 | 286 | int deactivateStream(SoapySDR::Stream *handle, const int flags, const long long timeNs) 287 | { 288 | SoapyUHDStream *stream = reinterpret_cast(handle); 289 | if (not stream->rx) return 0; //NOP, does nothing, but not an error 290 | 291 | //stop the stream (stop mode might support a timestamp) 292 | uhd::stream_cmd_t cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); 293 | cmd.stream_now = (flags & SOAPY_SDR_HAS_TIME) == 0; 294 | cmd.time_spec = uhd::time_spec_t::from_ticks(timeNs, 1e9); 295 | 296 | //issue command 297 | stream->rx->issue_stream_cmd(cmd); 298 | return 0; 299 | } 300 | 301 | int readStream(SoapySDR::Stream *handle, void * const *buffs, const size_t numElems, int &flags, long long &timeNs, const long timeoutUs) 302 | { 303 | uhd::rx_streamer::sptr &stream = reinterpret_cast(handle)->rx; 304 | 305 | //receive into buffers and metadata 306 | uhd::rx_metadata_t md; 307 | uhd::rx_streamer::buffs_type stream_buffs(buffs, stream->get_num_channels()); 308 | int ret = stream->recv(stream_buffs, numElems, md, timeoutUs/1e6, (flags & SOAPY_SDR_ONE_PACKET) != 0); 309 | 310 | //parse the metadata 311 | flags = 0; 312 | if (md.has_time_spec) flags |= SOAPY_SDR_HAS_TIME; 313 | if (md.end_of_burst) flags |= SOAPY_SDR_END_BURST; 314 | if (md.more_fragments) flags |= SOAPY_SDR_MORE_FRAGMENTS; 315 | timeNs = md.time_spec.to_ticks(1e9); 316 | switch (md.error_code) 317 | { 318 | case uhd::rx_metadata_t::ERROR_CODE_NONE: return ret; 319 | case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW: return SOAPY_SDR_OVERFLOW; 320 | case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: return SOAPY_SDR_TIMEOUT; 321 | case uhd::rx_metadata_t::ERROR_CODE_BAD_PACKET: return SOAPY_SDR_CORRUPTION; 322 | case uhd::rx_metadata_t::ERROR_CODE_ALIGNMENT: return SOAPY_SDR_CORRUPTION; 323 | case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND: return SOAPY_SDR_STREAM_ERROR; 324 | case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN: return SOAPY_SDR_STREAM_ERROR; 325 | } 326 | return ret; 327 | } 328 | 329 | int writeStream(SoapySDR::Stream *handle, const void * const *buffs, const size_t numElems, int &flags, const long long timeNs, const long timeoutUs) 330 | { 331 | uhd::tx_streamer::sptr &stream = reinterpret_cast(handle)->tx; 332 | 333 | //load metadata 334 | uhd::tx_metadata_t md; 335 | md.has_time_spec = (flags & SOAPY_SDR_HAS_TIME) != 0; 336 | md.end_of_burst = (flags & SOAPY_SDR_END_BURST) != 0; 337 | md.time_spec = uhd::time_spec_t::from_ticks(timeNs, 1e9); 338 | 339 | //send buffers and metadata 340 | uhd::tx_streamer::buffs_type stream_buffs(buffs, stream->get_num_channels()); 341 | int ret = stream->send(stream_buffs, numElems, md, timeoutUs/1e6); 342 | 343 | flags = 0; 344 | //consider a return of 0 to be a complete timeout 345 | if (ret == 0) return SOAPY_SDR_TIMEOUT; 346 | return ret; 347 | } 348 | 349 | int readStreamStatus(SoapySDR::Stream *handle, size_t &chanMask, int &flags, long long &timeNs, const long timeoutUs) 350 | { 351 | if (reinterpret_cast(handle)->rx) return SOAPY_SDR_NOT_SUPPORTED; 352 | uhd::tx_streamer::sptr &stream = reinterpret_cast(handle)->tx; 353 | 354 | uhd::async_metadata_t md; 355 | if (not stream->recv_async_msg(md, timeoutUs/1e6)) return SOAPY_SDR_TIMEOUT; 356 | 357 | chanMask = (1 << md.channel); 358 | flags = 0; 359 | if (md.has_time_spec) flags |= SOAPY_SDR_HAS_TIME; 360 | timeNs = md.time_spec.to_ticks(1e9); 361 | 362 | switch (md.event_code) 363 | { 364 | case uhd::async_metadata_t::EVENT_CODE_BURST_ACK: flags |= SOAPY_SDR_END_BURST; break; 365 | case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW: return SOAPY_SDR_UNDERFLOW; 366 | case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR: return SOAPY_SDR_CORRUPTION; 367 | case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR: return SOAPY_SDR_TIME_ERROR; 368 | case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET: return SOAPY_SDR_UNDERFLOW; 369 | case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST: return SOAPY_SDR_CORRUPTION; 370 | case uhd::async_metadata_t::EVENT_CODE_USER_PAYLOAD: break; 371 | } 372 | return 0; 373 | } 374 | 375 | /******************************************************************* 376 | * Antenna support 377 | ******************************************************************/ 378 | 379 | std::vector listAntennas(const int dir, const size_t channel) const 380 | { 381 | if (dir == SOAPY_SDR_TX) return _dev->get_tx_antennas(channel); 382 | if (dir == SOAPY_SDR_RX) return _dev->get_rx_antennas(channel); 383 | return SoapySDR::Device::listAntennas(dir, channel); 384 | } 385 | 386 | void setAntenna(const int dir, const size_t channel, const std::string &name) 387 | { 388 | if (dir == SOAPY_SDR_TX) _dev->set_tx_antenna(name, channel); 389 | if (dir == SOAPY_SDR_RX) _dev->set_rx_antenna(name, channel); 390 | } 391 | 392 | std::string getAntenna(const int dir, const size_t channel) const 393 | { 394 | if (dir == SOAPY_SDR_TX) return _dev->get_tx_antenna(channel); 395 | if (dir == SOAPY_SDR_RX) return _dev->get_rx_antenna(channel); 396 | return SoapySDR::Device::getAntenna(dir, channel); 397 | } 398 | 399 | /******************************************************************* 400 | * Frontend corrections support 401 | ******************************************************************/ 402 | 403 | bool hasDCOffsetMode(const int dir, const size_t channel) const 404 | { 405 | if (dir == SOAPY_SDR_TX) return false; 406 | if (dir == SOAPY_SDR_RX) 407 | { 408 | // This is usually on the motherboard's layer, but for some devices 409 | // (ex. the B2XX), it is done on the RF frontend. 410 | return __doesMBoardFEPropTreeEntryExist(dir, channel, "dc_offset/enable") || 411 | __doesDBoardFEPropTreeEntryExist(dir, channel, "dc_offset/enable"); 412 | } 413 | 414 | return SoapySDR::Device::hasDCOffsetMode(dir, channel); 415 | } 416 | 417 | void setDCOffsetMode(const int dir, const size_t channel, const bool automatic) 418 | { 419 | if (dir == SOAPY_SDR_RX) _dev->set_rx_dc_offset(automatic, channel); 420 | } 421 | 422 | bool getDCOffsetMode(const int dir, const size_t channel) const 423 | { 424 | // multi_usrp has no getter for this, so we need to query the 425 | // property tree itself. 426 | if (dir == SOAPY_SDR_TX) return false; 427 | if((dir == SOAPY_SDR_RX) && this->hasDCOffsetMode(dir, channel)) 428 | { 429 | auto tree = _get_tree(); 430 | const std::string subpath = "/dc_offset/enable"; 431 | 432 | auto mboardPath = __getMBoardFEPropTreePath(dir, channel) + subpath; 433 | if(tree->exists(mboardPath)) 434 | { 435 | return tree->access(mboardPath).get(); 436 | } 437 | 438 | auto dboardPath = __getDBoardFEPropTreePath(dir, channel) + subpath; 439 | if(tree->exists(dboardPath)) 440 | { 441 | return tree->access(dboardPath).get(); 442 | } 443 | } 444 | 445 | return SoapySDR::Device::getDCOffsetMode(dir, channel); 446 | } 447 | 448 | bool hasDCOffset(const int dir, const size_t channel) const 449 | { 450 | return __doesMBoardFEPropTreeEntryExist(dir, channel, "dc_offset/value"); 451 | } 452 | 453 | void setDCOffset(const int dir, const size_t channel, const std::complex &offset) 454 | { 455 | if (dir == SOAPY_SDR_TX) _dev->set_tx_dc_offset(offset, channel); 456 | if (dir == SOAPY_SDR_RX) _dev->set_rx_dc_offset(offset, channel); 457 | } 458 | 459 | std::complex getDCOffset(const int dir, const size_t channel) const 460 | { 461 | // multi_usrp has no getter for this, so we need to query the 462 | // property tree itself. 463 | if(this->hasDCOffset(dir, channel)) 464 | { 465 | auto tree = _get_tree(); 466 | const std::string subpath = "/dc_offset/value"; 467 | 468 | auto path = __getMBoardFEPropTreePath(dir, channel) + subpath; 469 | return tree->access>(path).get(); 470 | } 471 | 472 | return SoapySDR::Device::getDCOffset(dir, channel); 473 | } 474 | 475 | bool hasIQBalance(const int dir, const size_t channel) const 476 | { 477 | return __doesMBoardFEPropTreeEntryExist(dir, channel, "iq_balance/value"); 478 | } 479 | 480 | void setIQBalance(const int dir, const size_t channel, const std::complex &balance) 481 | { 482 | if (dir == SOAPY_SDR_TX) _dev->set_tx_iq_balance(balance, channel); 483 | if (dir == SOAPY_SDR_RX) _dev->set_rx_iq_balance(balance, channel); 484 | } 485 | 486 | std::complex getIQBalance(const int dir, const size_t channel) const 487 | { 488 | // multi_usrp has no getter for this, so we need to query the 489 | // property tree itself. 490 | if(this->hasIQBalance(dir, channel)) 491 | { 492 | auto tree = _get_tree(); 493 | const std::string subpath = "/iq_balance/value"; 494 | 495 | auto path = __getMBoardFEPropTreePath(dir, channel) + subpath; 496 | return tree->access>(path).get(); 497 | } 498 | 499 | return SoapySDR::Device::getIQBalance(dir, channel); 500 | } 501 | 502 | bool hasIQBalanceMode(const int dir, const size_t channel) const 503 | { 504 | if (dir == SOAPY_SDR_TX) return false; 505 | if (dir == SOAPY_SDR_RX) 506 | { 507 | return __doesMBoardFEPropTreeEntryExist(dir, channel, "iq_balance/enable"); 508 | } 509 | 510 | return SoapySDR::Device::hasDCOffsetMode(dir, channel); 511 | } 512 | 513 | void setIQBalanceMode(const int dir, const size_t channel, const bool automatic) 514 | { 515 | if (dir == SOAPY_SDR_RX) _dev->set_rx_iq_balance(automatic, channel); 516 | } 517 | 518 | bool getIQBalanceMode(const int dir, const size_t channel) const 519 | { 520 | // multi_usrp has no getter for this, so we need to query the 521 | // property tree itself. 522 | if (dir == SOAPY_SDR_TX) return false; 523 | if((dir == SOAPY_SDR_RX) && this->hasIQBalanceMode(dir, channel)) 524 | { 525 | auto tree = _get_tree(); 526 | const std::string subpath = "/iq_balance/enable"; 527 | 528 | auto path = __getMBoardFEPropTreePath(dir, channel) + subpath; 529 | return tree->access(path).get(); 530 | } 531 | 532 | return false; 533 | } 534 | 535 | /******************************************************************* 536 | * Gain support 537 | ******************************************************************/ 538 | 539 | std::vector listGains(const int dir, const size_t channel) const 540 | { 541 | if (dir == SOAPY_SDR_TX) return _dev->get_tx_gain_names(channel); 542 | if (dir == SOAPY_SDR_RX) return _dev->get_rx_gain_names(channel); 543 | return SoapySDR::Device::listGains(dir, channel); 544 | } 545 | 546 | bool hasGainMode(const int dir, const size_t channel) const 547 | { 548 | #ifdef UHD_HAS_SET_RX_AGC 549 | if (dir == SOAPY_SDR_TX) return false; 550 | if (dir == SOAPY_SDR_RX) 551 | { 552 | return __doesDBoardFEPropTreeEntryExist(dir, channel, "gain/agc/enable"); 553 | } 554 | #endif 555 | return SoapySDR::Device::hasGainMode(dir, channel); 556 | } 557 | 558 | void setGainMode(const int dir, const size_t channel, const bool automatic) 559 | { 560 | #ifdef UHD_HAS_SET_RX_AGC 561 | if (dir == SOAPY_SDR_RX) return _dev->set_rx_agc(automatic, channel); 562 | #endif 563 | return SoapySDR::Device::setGainMode(dir, channel, automatic); 564 | } 565 | 566 | void setGain(const int dir, const size_t channel, const double value) 567 | { 568 | if (dir == SOAPY_SDR_TX) _dev->set_tx_gain(value, channel); 569 | if (dir == SOAPY_SDR_RX) _dev->set_rx_gain(value, channel); 570 | } 571 | 572 | void setGain(const int dir, const size_t channel, const std::string &name, const double value) 573 | { 574 | if (dir == SOAPY_SDR_TX) _dev->set_tx_gain(value, name, channel); 575 | if (dir == SOAPY_SDR_RX) _dev->set_rx_gain(value, name, channel); 576 | } 577 | 578 | double getGain(const int dir, const size_t channel) const 579 | { 580 | if (dir == SOAPY_SDR_TX) return _dev->get_tx_gain(channel); 581 | if (dir == SOAPY_SDR_RX) return _dev->get_rx_gain(channel); 582 | return SoapySDR::Device::getGain(dir, channel); 583 | } 584 | 585 | double getGain(const int dir, const size_t channel, const std::string &name) const 586 | { 587 | if (dir == SOAPY_SDR_TX) return _dev->get_tx_gain(name, channel); 588 | if (dir == SOAPY_SDR_RX) return _dev->get_rx_gain(name, channel); 589 | return SoapySDR::Device::getGain(dir, channel, name); 590 | } 591 | 592 | SoapySDR::Range getGainRange(const int dir, const size_t channel, const std::string &name) const 593 | { 594 | if (dir == SOAPY_SDR_TX) return metaRangeToRange(_dev->get_tx_gain_range(name, channel)); 595 | if (dir == SOAPY_SDR_RX) return metaRangeToRange(_dev->get_rx_gain_range(name, channel)); 596 | return SoapySDR::Device::getGainRange(dir, channel, name); 597 | } 598 | 599 | SoapySDR::Range getGainRange(const int dir, const size_t channel) const 600 | { 601 | if (dir == SOAPY_SDR_TX) return metaRangeToRange(_dev->get_tx_gain_range(channel)); 602 | if (dir == SOAPY_SDR_RX) return metaRangeToRange(_dev->get_rx_gain_range(channel)); 603 | return SoapySDR::Device::getGainRange(dir, channel); 604 | } 605 | 606 | /******************************************************************* 607 | * Frequency support 608 | ******************************************************************/ 609 | 610 | void setFrequency(const int dir, const size_t channel, const double frequency, const SoapySDR::Kwargs &args) 611 | { 612 | uhd::tune_request_t tr(frequency); 613 | 614 | if (args.count("OFFSET") != 0) 615 | { 616 | tr = uhd::tune_request_t(frequency, boost::lexical_cast(args.at("OFFSET"))); 617 | } 618 | if (args.count("RF") != 0) 619 | { 620 | try 621 | { 622 | tr.rf_freq = boost::lexical_cast(args.at("RF")); 623 | tr.rf_freq_policy = uhd::tune_request_t::POLICY_MANUAL; 624 | } 625 | catch (...) 626 | { 627 | tr.rf_freq_policy = uhd::tune_request_t::POLICY_NONE; 628 | } 629 | } 630 | if (args.count("BB") != 0) 631 | { 632 | try 633 | { 634 | tr.dsp_freq = boost::lexical_cast(args.at("BB")); 635 | tr.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; 636 | } 637 | catch (...) 638 | { 639 | tr.dsp_freq_policy = uhd::tune_request_t::POLICY_NONE; 640 | } 641 | } 642 | tr.args = kwargsToDict(args); 643 | 644 | if (dir == SOAPY_SDR_TX) _trCache[dir][channel] = _dev->set_tx_freq(tr, channel); 645 | if (dir == SOAPY_SDR_RX) _trCache[dir][channel] = _dev->set_rx_freq(tr, channel); 646 | } 647 | 648 | void setFrequency(const int dir, const size_t channel, const std::string &name, const double frequency, const SoapySDR::Kwargs &args) 649 | { 650 | //use tune request to get individual elements -- could use property tree here 651 | uhd::tune_request_t tr(frequency); 652 | tr.rf_freq_policy = uhd::tune_request_t::POLICY_NONE; 653 | tr.dsp_freq_policy = uhd::tune_request_t::POLICY_NONE; 654 | tr.args = kwargsToDict(args); 655 | if (name == "RF") 656 | { 657 | tr.rf_freq = frequency; 658 | tr.rf_freq_policy = uhd::tune_request_t::POLICY_MANUAL; 659 | } 660 | if (name == "BB") 661 | { 662 | tr.dsp_freq = frequency; 663 | tr.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; 664 | } 665 | 666 | if (dir == SOAPY_SDR_TX) _trCache[dir][channel] = _dev->set_tx_freq(tr, channel); 667 | if (dir == SOAPY_SDR_RX) _trCache[dir][channel] = _dev->set_rx_freq(tr, channel); 668 | } 669 | 670 | std::map > _trCache; 671 | 672 | double getFrequency(const int dir, const size_t channel) const 673 | { 674 | if (dir == SOAPY_SDR_TX) return _dev->get_tx_freq(channel); 675 | if (dir == SOAPY_SDR_RX) return _dev->get_rx_freq(channel); 676 | return SoapySDR::Device::getFrequency(dir, channel); 677 | } 678 | 679 | double getFrequency(const int dir, const size_t channel, const std::string &name) const 680 | { 681 | //we have never tuned before, return the overall freq for RF, assume 0.0 for all else 682 | if (_trCache.count(dir) == 0 or _trCache.at(dir).count(channel) == 0) 683 | { 684 | if (name == "RF") return this->getFrequency(dir, channel); 685 | else return 0.0; 686 | } 687 | 688 | const uhd::tune_result_t tr = _trCache.at(dir).at(channel); 689 | if (name == "RF") return tr.actual_rf_freq; 690 | if (name == "BB") return tr.actual_dsp_freq; 691 | return SoapySDR::Device::getFrequency(dir, channel, name); 692 | } 693 | 694 | std::vector listFrequencies(const int, const size_t) const 695 | { 696 | std::vector comps; 697 | comps.push_back("RF"); 698 | comps.push_back("BB"); 699 | return comps; 700 | } 701 | 702 | SoapySDR::RangeList getFrequencyRange(const int dir, const size_t channel) const 703 | { 704 | if (dir == SOAPY_SDR_TX) return metaRangeToRangeList(_dev->get_tx_freq_range(channel)); 705 | if (dir == SOAPY_SDR_RX) return metaRangeToRangeList(_dev->get_rx_freq_range(channel)); 706 | return SoapySDR::Device::getFrequencyRange(dir, channel); 707 | } 708 | 709 | SoapySDR::RangeList getFrequencyRange(const int dir, const size_t channel, const std::string &name) const 710 | { 711 | if (name == "RF") 712 | { 713 | //use overall range - could use property tree, but close enough 714 | if (dir == SOAPY_SDR_TX) return metaRangeToRangeList(_dev->get_tx_freq_range(channel)); 715 | if (dir == SOAPY_SDR_RX) return metaRangeToRangeList(_dev->get_rx_freq_range(channel)); 716 | } 717 | if (name == "BB") 718 | { 719 | //read the range from the property tree 720 | uhd::property_tree::sptr tree = _get_tree(); 721 | const std::string path = str(boost::format("/mboards/0/%s_dsps/%u/freq/range") % ((dir == SOAPY_SDR_TX)?"tx":"rx") % channel); 722 | if (tree->exists(path)) return metaRangeToRangeList(tree->access(path).get()); 723 | else return SoapySDR::RangeList(1, SoapySDR::Range(-getSampleRate(dir, channel)/2, getSampleRate(dir, channel)/2)); 724 | } 725 | return SoapySDR::Device::getFrequencyRange(dir, channel, name); 726 | } 727 | 728 | SoapySDR::ArgInfoList getFrequencyArgsInfo(const int, const size_t) const 729 | { 730 | SoapySDR::ArgInfoList frequencyArgs; 731 | 732 | SoapySDR::ArgInfo modeNArg; 733 | modeNArg.key = "mode_n"; 734 | modeNArg.name = "N divider"; 735 | modeNArg.description = "Whether the daughterboard tune code should use an integer N divider or fractional N divider (not supported for all devices)."; 736 | modeNArg.type = SoapySDR::ArgInfo::STRING; 737 | modeNArg.options.push_back("integer"); 738 | modeNArg.options.push_back("fractional"); 739 | modeNArg.optionNames.push_back("Integer"); 740 | modeNArg.optionNames.push_back("Fractional"); 741 | frequencyArgs.emplace_back(std::move(modeNArg)); 742 | 743 | SoapySDR::ArgInfo intNStepArg; 744 | intNStepArg.key = "int_n_step"; 745 | intNStepArg.name = "Integer-N tuning step"; 746 | intNStepArg.description = "The step between valid tunable frequencies when using integer-N tuning (not supported for all devices)."; 747 | intNStepArg.type = SoapySDR::ArgInfo::FLOAT; 748 | frequencyArgs.emplace_back(std::move(intNStepArg)); 749 | 750 | return frequencyArgs; 751 | } 752 | 753 | /******************************************************************* 754 | * Sample Rate support 755 | ******************************************************************/ 756 | 757 | void setSampleRate(const int dir, const size_t channel, const double rate) 758 | { 759 | if (dir == SOAPY_SDR_TX) return _dev->set_tx_rate(rate, channel); 760 | if (dir == SOAPY_SDR_RX) return _dev->set_rx_rate(rate, channel); 761 | } 762 | 763 | double getSampleRate(const int dir, const size_t channel) const 764 | { 765 | if (dir == SOAPY_SDR_TX) return _dev->get_tx_rate(channel); 766 | if (dir == SOAPY_SDR_RX) return _dev->get_rx_rate(channel); 767 | return SoapySDR::Device::getSampleRate(dir, channel); 768 | } 769 | 770 | std::vector listSampleRates(const int dir, const size_t channel) const 771 | { 772 | if (dir == SOAPY_SDR_TX) return metaRangeToNumericList(_dev->get_tx_rates(channel)); 773 | if (dir == SOAPY_SDR_RX) return metaRangeToNumericList(_dev->get_rx_rates(channel)); 774 | return SoapySDR::Device::listSampleRates(dir, channel); 775 | } 776 | 777 | SoapySDR::RangeList getSampleRateRange(const int dir, const size_t channel) const 778 | { 779 | if (dir == SOAPY_SDR_TX) return metaRangeToRangeList(_dev->get_tx_rates(channel)); 780 | if (dir == SOAPY_SDR_RX) return metaRangeToRangeList(_dev->get_rx_rates(channel)); 781 | return SoapySDR::Device::getSampleRateRange(dir, channel); 782 | } 783 | 784 | void setBandwidth(const int dir, const size_t channel, const double bw) 785 | { 786 | if (dir == SOAPY_SDR_TX) return _dev->set_tx_bandwidth(bw, channel); 787 | if (dir == SOAPY_SDR_RX) return _dev->set_rx_bandwidth(bw, channel); 788 | } 789 | 790 | double getBandwidth(const int dir, const size_t channel) const 791 | { 792 | if (dir == SOAPY_SDR_TX) return _dev->get_tx_bandwidth(channel); 793 | if (dir == SOAPY_SDR_RX) return _dev->get_rx_bandwidth(channel); 794 | return SoapySDR::Device::getBandwidth(dir, channel); 795 | } 796 | 797 | std::vector listBandwidths(const int dir, const size_t channel) const 798 | { 799 | if (dir == SOAPY_SDR_TX) return metaRangeToNumericList(_dev->get_tx_bandwidth_range(channel)); 800 | if (dir == SOAPY_SDR_RX) return metaRangeToNumericList(_dev->get_rx_bandwidth_range(channel)); 801 | return SoapySDR::Device::listBandwidths(dir, channel); 802 | } 803 | 804 | SoapySDR::RangeList getBandwidthRange(const int dir, const size_t channel) const 805 | { 806 | if (dir == SOAPY_SDR_TX) return metaRangeToRangeList(_dev->get_tx_bandwidth_range(channel)); 807 | if (dir == SOAPY_SDR_RX) return metaRangeToRangeList(_dev->get_rx_bandwidth_range(channel)); 808 | return SoapySDR::Device::getBandwidthRange(dir, channel); 809 | } 810 | 811 | /******************************************************************* 812 | * Clocking support 813 | ******************************************************************/ 814 | 815 | void setMasterClockRate(const double rate) 816 | { 817 | _dev->set_master_clock_rate(rate); 818 | } 819 | 820 | double getMasterClockRate(void) const 821 | { 822 | return _dev->get_master_clock_rate(); 823 | } 824 | 825 | std::vector listClockSources(void) const 826 | { 827 | return _dev->get_clock_sources(0); 828 | } 829 | 830 | void setClockSource(const std::string &source) 831 | { 832 | _dev->set_clock_source(source, 0); 833 | } 834 | 835 | std::string getClockSource(void) const 836 | { 837 | return _dev->get_clock_source(0); 838 | } 839 | 840 | std::vector listTimeSources(void) const 841 | { 842 | return _dev->get_time_sources(0); 843 | } 844 | 845 | void setTimeSource(const std::string &source) 846 | { 847 | _dev->set_time_source(source, 0); 848 | } 849 | 850 | std::string getTimeSource(void) const 851 | { 852 | return _dev->get_time_source(0); 853 | } 854 | 855 | /******************************************************************* 856 | * Time support 857 | ******************************************************************/ 858 | 859 | bool hasHardwareTime(const std::string &what) const 860 | { 861 | return (what == "PPS" or what.empty()); 862 | } 863 | 864 | long long getHardwareTime(const std::string &what) const 865 | { 866 | if (what == "PPS") return _dev->get_time_last_pps().to_ticks(1e9); 867 | return _dev->get_time_now().to_ticks(1e9); 868 | } 869 | 870 | void setHardwareTime(const long long timeNs, const std::string &what) 871 | { 872 | uhd::time_spec_t time = uhd::time_spec_t::from_ticks(timeNs, 1e9); 873 | if (what == "PPS") return _dev->set_time_next_pps(time); 874 | if (what == "UNKNOWN_PPS") return _dev->set_time_unknown_pps(time); 875 | if (what == "CMD" and timeNs == 0) return _dev->clear_command_time(); 876 | if (what == "CMD") return _dev->set_command_time(time); 877 | return _dev->set_time_now(time); 878 | } 879 | 880 | //deprecated call, just forwards to setHardwareTime with CMD arg 881 | void setCommandTime(const long long timeNs, const std::string &) 882 | { 883 | this->setHardwareTime(timeNs, "CMD"); 884 | } 885 | 886 | /******************************************************************* 887 | * Sensor support 888 | ******************************************************************/ 889 | 890 | std::vector listSensors(void) const 891 | { 892 | return _dev->get_mboard_sensor_names(); 893 | } 894 | 895 | SoapySDR::ArgInfo getSensorInfo(const std::string &name) const 896 | { 897 | return sensorToArgInfo(_dev->get_mboard_sensor(name), name); 898 | } 899 | 900 | std::string readSensor(const std::string &name) const 901 | { 902 | return _dev->get_mboard_sensor(name).value; 903 | } 904 | 905 | std::vector listSensors(const int dir, const size_t channel) const 906 | { 907 | if (dir == SOAPY_SDR_TX) return _dev->get_tx_sensor_names(channel); 908 | if (dir == SOAPY_SDR_RX) return _dev->get_rx_sensor_names(channel); 909 | return SoapySDR::Device::listSensors(dir, channel); 910 | } 911 | 912 | SoapySDR::ArgInfo getSensorInfo(const int dir, const size_t channel, const std::string &name) const 913 | { 914 | if (dir == SOAPY_SDR_TX) return sensorToArgInfo(_dev->get_tx_sensor(name, channel), name); 915 | if (dir == SOAPY_SDR_RX) return sensorToArgInfo(_dev->get_rx_sensor(name, channel), name); 916 | return SoapySDR::Device::getSensorInfo(dir, channel, name); 917 | } 918 | 919 | std::string readSensor(const int dir, const size_t channel, const std::string &name) const 920 | { 921 | if (dir == SOAPY_SDR_TX) return _dev->get_tx_sensor(name, channel).value; 922 | if (dir == SOAPY_SDR_RX) return _dev->get_rx_sensor(name, channel).value; 923 | return SoapySDR::Device::readSensor(dir, channel, name); 924 | } 925 | 926 | /******************************************************************* 927 | * GPIO API 928 | ******************************************************************/ 929 | 930 | /*! 931 | * Helpful markup to set the ATRs and CTRL from the writeGPIO API. 932 | * dev->writeGPIO("BANKFOO:ATR_TX", 0xffff); 933 | */ 934 | void __splitBankName(const std::string &name, std::string &bank, std::string &attr) 935 | { 936 | const size_t sepPos = name.find(':'); 937 | if (sepPos == std::string::npos) 938 | { 939 | bank = name; 940 | attr = "OUT"; 941 | return; 942 | } 943 | bank = name.substr(0, sepPos); 944 | attr = name.substr(sepPos+1); 945 | } 946 | 947 | std::vector listGPIOBanks(void) const 948 | { 949 | return _dev->get_gpio_banks(0); 950 | } 951 | 952 | void writeGPIO(const std::string &name, const unsigned value) 953 | { 954 | std::string bank, attr; __splitBankName(name, bank, attr); 955 | _dev->set_gpio_attr(bank, attr, value); 956 | } 957 | 958 | void writeGPIO(const std::string &name, const unsigned value, const unsigned mask) 959 | { 960 | std::string bank, attr; __splitBankName(name, bank, attr); 961 | _dev->set_gpio_attr(bank, attr, value, mask); 962 | } 963 | 964 | unsigned readGPIO(const std::string &bank) const 965 | { 966 | return _dev->get_gpio_attr(bank, "READBACK"); 967 | } 968 | 969 | void writeGPIODir(const std::string &bank, const unsigned dir) 970 | { 971 | _dev->set_gpio_attr(bank, "DDR", dir); 972 | } 973 | 974 | void writeGPIODir(const std::string &bank, const unsigned dir, const unsigned mask) 975 | { 976 | _dev->set_gpio_attr(bank, "DDR", dir, mask); 977 | } 978 | 979 | unsigned readGPIODir(const std::string &bank) const 980 | { 981 | return _dev->get_gpio_attr(bank, "DDR"); 982 | } 983 | 984 | /******************************************************************* 985 | * Get handle to underlying device 986 | ******************************************************************/ 987 | 988 | void* getNativeDeviceHandle(void) const 989 | { 990 | return _dev.get(); 991 | } 992 | 993 | /******************************************************************* 994 | * Helpers for searching property tree 995 | ******************************************************************/ 996 | 997 | std::string __getMBoardFEPropTreePath(const int dir, const size_t channel) const 998 | { 999 | auto tree = _get_tree(); 1000 | const std::string directionName = (dir == SOAPY_SDR_TX) ? "tx" : "rx"; 1001 | auto subdevSpec = (dir == SOAPY_SDR_TX) ? _dev->get_tx_subdev_spec(0).at(channel) 1002 | : _dev->get_rx_subdev_spec(0).at(channel); 1003 | 1004 | const std::string path = 1005 | str(boost::format("/mboards/0/%s_frontends/%s") 1006 | % directionName 1007 | % subdevSpec.db_name); 1008 | 1009 | return path; 1010 | } 1011 | 1012 | std::string __getDBoardFEPropTreePath(const int dir, const size_t channel) const 1013 | { 1014 | auto tree = _get_tree(); 1015 | const std::string directionName = (dir == SOAPY_SDR_TX) ? "tx" : "rx"; 1016 | auto subdevSpec = (dir == SOAPY_SDR_TX) ? _dev->get_tx_subdev_spec(0).at(channel) 1017 | : _dev->get_rx_subdev_spec(0).at(channel); 1018 | 1019 | const std::string path = 1020 | str(boost::format("/mboards/0/dboards/%s/%s_frontends/%s") 1021 | % subdevSpec.db_name 1022 | % directionName 1023 | % subdevSpec.sd_name); 1024 | 1025 | return path; 1026 | } 1027 | 1028 | bool __doesMBoardFEPropTreeEntryExist(const int dir, const size_t channel, const std::string &subpath) const 1029 | { 1030 | auto path = __getMBoardFEPropTreePath(dir, channel) + "/" + subpath; 1031 | 1032 | return _get_tree()->exists(path); 1033 | } 1034 | 1035 | bool __doesDBoardFEPropTreeEntryExist(const int dir, const size_t channel, const std::string &subpath) const 1036 | { 1037 | auto path = __getDBoardFEPropTreePath(dir, channel) + "/" + subpath; 1038 | 1039 | return _get_tree()->exists(path); 1040 | } 1041 | 1042 | private: 1043 | 1044 | uhd::property_tree::sptr _get_tree(void) const 1045 | { 1046 | #if UHD_VERSION >= 4000000 1047 | return _dev->get_tree(); 1048 | #else 1049 | return _dev->get_device()->get_tree(); 1050 | #endif 1051 | } 1052 | 1053 | uhd::usrp::multi_usrp::sptr _dev; 1054 | const std::string _type; 1055 | const bool _isNetworkDevice; 1056 | }; 1057 | 1058 | /*********************************************************************** 1059 | * Register into logger 1060 | **********************************************************************/ 1061 | #ifdef UHD_HAS_MSG_HPP 1062 | static void SoapyUHDLogger(uhd::msg::type_t t, const std::string &s) 1063 | { 1064 | if (s.empty()) return; 1065 | if (s[s.size()-1] == '\n') return SoapyUHDLogger(t, s.substr(0, s.size()-1)); 1066 | switch (t) 1067 | { 1068 | case uhd::msg::status: SoapySDR::log(SOAPY_SDR_INFO, s); break; 1069 | case uhd::msg::warning: SoapySDR::log(SOAPY_SDR_WARNING, s); break; 1070 | case uhd::msg::error: SoapySDR::log(SOAPY_SDR_ERROR, s); break; 1071 | case uhd::msg::fastpath: SoapySDR::log(SOAPY_SDR_SSI, s); break; 1072 | } 1073 | } 1074 | #else 1075 | static void SoapyUHDLogger(const uhd::log::logging_info &info) 1076 | { 1077 | //build a log message formatted from the information 1078 | std::string message; 1079 | 1080 | if (not info.file.empty()) 1081 | { 1082 | std::string shortfile = info.file.substr(info.file.find_last_of("/\\") + 1); 1083 | message += "[" + shortfile + ":" + std::to_string(info.line) + "] "; 1084 | } 1085 | 1086 | if (not info.component.empty()) 1087 | { 1088 | message += "[" + info.component + "] "; 1089 | } 1090 | 1091 | message += info.message; 1092 | 1093 | switch(info.verbosity) 1094 | { 1095 | case uhd::log::trace: SoapySDR::log(SOAPY_SDR_TRACE, message); break; 1096 | case uhd::log::debug: SoapySDR::log(SOAPY_SDR_DEBUG, message); break; 1097 | case uhd::log::info: SoapySDR::log(SOAPY_SDR_INFO, message); break; 1098 | case uhd::log::warning: SoapySDR::log(SOAPY_SDR_WARNING, message); break; 1099 | case uhd::log::error: SoapySDR::log(SOAPY_SDR_ERROR, message); break; 1100 | case uhd::log::fatal: SoapySDR::log(SOAPY_SDR_FATAL, message); break; 1101 | default: break; 1102 | } 1103 | } 1104 | #endif 1105 | 1106 | /*********************************************************************** 1107 | * Registration 1108 | **********************************************************************/ 1109 | std::vector find_uhd(const SoapySDR::Kwargs &args_) 1110 | { 1111 | //prevent going into the the UHDSoapyDevice 1112 | SoapySDR::Kwargs args(args_); 1113 | if (args.count(SOAPY_UHD_NO_DEEPER) != 0) return std::vector(); 1114 | args[SOAPY_UHD_NO_DEEPER] = ""; 1115 | 1116 | //perform the discovery 1117 | #ifdef UHD_HAS_DEVICE_FILTER 1118 | const uhd::device_addrs_t addrs = uhd::device::find(kwargsToDict(args), uhd::device::USRP); 1119 | #else 1120 | const uhd::device_addrs_t addrs = uhd::device::find(kwargsToDict(args)); 1121 | #endif 1122 | 1123 | //convert addrs to results 1124 | std::vector results; 1125 | for (size_t i = 0; i < addrs.size(); i++) 1126 | { 1127 | SoapySDR::Kwargs result(dictToKwargs(addrs[i])); 1128 | 1129 | //create displayable label if not present 1130 | if (result.count("label") == 0) 1131 | { 1132 | if (result.count("product") != 0) 1133 | result["label"] = result.at("product"); 1134 | 1135 | if (result.count("serial") != 0) 1136 | result["label"] += " " + result.at("serial"); 1137 | } 1138 | 1139 | result.erase(SOAPY_UHD_NO_DEEPER); 1140 | results.push_back(result); 1141 | } 1142 | return results; 1143 | } 1144 | 1145 | SoapySDR::Device *make_uhd(const SoapySDR::Kwargs &args) 1146 | { 1147 | if(std::string(UHD_VERSION_ABI_STRING) != uhd::get_abi_string()) throw std::runtime_error(str(boost::format( 1148 | "SoapySDR detected ABI compatibility mismatch with UHD library.\n" 1149 | "SoapySDR UHD support was build against ABI: %s,\n" 1150 | "but UHD library reports ABI: %s\n" 1151 | "Suggestion: install an ABI compatible version of UHD,\n" 1152 | "or rebuild SoapySDR UHD support against this ABI version.\n" 1153 | ) % UHD_VERSION_ABI_STRING % uhd::get_abi_string())); 1154 | #ifdef UHD_HAS_MSG_HPP 1155 | uhd::msg::register_handler(&SoapyUHDLogger); 1156 | #else 1157 | uhd::log::add_logger("SoapyUHDDevice", &SoapyUHDLogger); 1158 | #endif 1159 | return new SoapyUHDDevice(uhd::usrp::multi_usrp::make(kwargsToDict(args)), args); 1160 | } 1161 | 1162 | static SoapySDR::Registry register__uhd("uhd", &find_uhd, &make_uhd, SOAPY_SDR_ABI_VERSION); 1163 | --------------------------------------------------------------------------------