From c8ae32253b649faea27dcf0d98fd0395924a2eec Mon Sep 17 00:00:00 2001
From: Andrej Rode <andrej.rode@ettus.com>
Date: Mon, 27 Mar 2017 18:05:23 -0700
Subject: usrp: add netd find and impl, add uhd rpc client wrapper

---
 host/lib/CMakeLists.txt             |   8 +-
 host/lib/deps/CMakeLists.txt        |   5 +-
 host/lib/deps/import_rpclib.py      |   5 +-
 host/lib/deps/rpclib/CMakeLists.txt |   1 -
 host/lib/usrp/CMakeLists.txt        |   1 +
 host/lib/usrp/netd/CMakeLists.txt   |  29 ++++
 host/lib/usrp/netd/netd_impl.cpp    | 330 ++++++++++++++++++++++++++++++++++++
 host/lib/usrp/netd/netd_impl.hpp    |  82 +++++++++
 host/lib/utils/rpc.hpp              |  44 +++++
 9 files changed, 498 insertions(+), 7 deletions(-)
 create mode 100644 host/lib/usrp/netd/CMakeLists.txt
 create mode 100644 host/lib/usrp/netd/netd_impl.cpp
 create mode 100644 host/lib/usrp/netd/netd_impl.hpp
 create mode 100644 host/lib/utils/rpc.hpp

(limited to 'host')

diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt
index 2679ce302..a1c5648ad 100644
--- a/host/lib/CMakeLists.txt
+++ b/host/lib/CMakeLists.txt
@@ -61,6 +61,7 @@ MESSAGE(STATUS "")
 FIND_PACKAGE(USB1)
 FIND_PACKAGE(GPSD)
 FIND_PACKAGE(LIBERIO)
+LIBUHD_REGISTER_COMPONENT("LIBERIO" ENABLE_LIBERIO ON "ENABLE_LIBUHD;LIBERIO_FOUND" OFF OFF)
 LIBUHD_REGISTER_COMPONENT("USB" ENABLE_USB ON "ENABLE_LIBUHD;LIBUSB_FOUND" OFF OFF)
 LIBUHD_REGISTER_COMPONENT("GPSD" ENABLE_GPSD OFF "ENABLE_LIBUHD;ENABLE_GPSD;LIBGPS_FOUND" OFF OFF)
 # Devices
@@ -71,7 +72,7 @@ LIBUHD_REGISTER_COMPONENT("USRP1" ENABLE_USRP1 ON "ENABLE_LIBUHD;ENABLE_USB" OFF
 LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 ON "ENABLE_LIBUHD" OFF OFF)
 LIBUHD_REGISTER_COMPONENT("X300" ENABLE_X300 ON "ENABLE_LIBUHD" OFF OFF)
 LIBUHD_REGISTER_COMPONENT("N230" ENABLE_N230 ON "ENABLE_LIBUHD" OFF OFF)
-LIBUHD_REGISTER_COMPONENT("LIBERIO" ENABLE_LIBERIO ON "ENABLE_LIBUHD;LIBERIO_FOUND" OFF OFF)
+LIBUHD_REGISTER_COMPONENT("NETD" ENABLE_NETD ON "ENABLE_LIBUHD" OFF OFF)
 LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OFF)
 
 ########################################################################
@@ -164,6 +165,11 @@ ENDIF(MSVC)
 ########################################################################
 # Setup libuhd library
 ########################################################################
+IF(ENABLE_NETD)
+    INCLUDE_DIRECTORIES(deps/rpclib/include)
+    ADD_SUBDIRECTORY(deps)
+    LIST(APPEND libuhd_sources $<TARGET_OBJECTS:uhd_rpclib>)
+ENDIF()
 ADD_LIBRARY(uhd SHARED ${libuhd_sources})
 TARGET_LINK_LIBRARIES(uhd ${Boost_LIBRARIES} ${libuhd_libs})
 SET_TARGET_PROPERTIES(uhd PROPERTIES DEFINE_SYMBOL "UHD_DLL_EXPORTS")
diff --git a/host/lib/deps/CMakeLists.txt b/host/lib/deps/CMakeLists.txt
index 106f2cd19..bcb89d29a 100644
--- a/host/lib/deps/CMakeLists.txt
+++ b/host/lib/deps/CMakeLists.txt
@@ -14,5 +14,6 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
-
-ADD_SUBDIRECTORY(rpclib)
+IF(ENABLE_NETD)
+    ADD_SUBDIRECTORY(rpclib)
+ENDIF()
diff --git a/host/lib/deps/import_rpclib.py b/host/lib/deps/import_rpclib.py
index 2efd29e60..2baccab1a 100755
--- a/host/lib/deps/import_rpclib.py
+++ b/host/lib/deps/import_rpclib.py
@@ -46,7 +46,7 @@ REPLACE_EXPR = [
 
 def fix_naming(workdir):
     """
-    replace all the things
+    Applies all the REPLACE_EXPR in all the files
     """
     for root, dirs, files in os.walk(workdir):
         for cur_file in files:
@@ -78,7 +78,7 @@ def extract_rpclib_version(rpclib_root):
 
 def copy_important_files(target_root, rpclib_root):
     """
-    copy files/subdirs we consider important
+    copy files/subdirs we consider important (see IMPORTANT_FILES)
     """
     for rpc_file in os.listdir(rpclib_root):
         if rpc_file in IMPORTANT_FILES:
@@ -110,7 +110,6 @@ def extract_rpclib(args):
     """
     THE extraction function
     """
-
     workdir = tempfile.mkdtemp()
     rpclib_root = args.rpclib_root
     new_rpclib_root = tempfile.mkdtemp()
diff --git a/host/lib/deps/rpclib/CMakeLists.txt b/host/lib/deps/rpclib/CMakeLists.txt
index a1298a464..271a49854 100644
--- a/host/lib/deps/rpclib/CMakeLists.txt
+++ b/host/lib/deps/rpclib/CMakeLists.txt
@@ -1,4 +1,3 @@
-CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0)
 PROJECT(UHD_RPCLIB CXX)
 
 SET(RPCLIB_DEFAULT_BUFFER_SIZE "1024 << 10" CACHE STRING "Default buffer size")
diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt
index c3a1e6533..0f8172d41 100644
--- a/host/lib/usrp/CMakeLists.txt
+++ b/host/lib/usrp/CMakeLists.txt
@@ -41,6 +41,7 @@ INCLUDE_SUBDIRECTORY(cores)
 INCLUDE_SUBDIRECTORY(dboard)
 INCLUDE_SUBDIRECTORY(common)
 INCLUDE_SUBDIRECTORY(device3)
+INCLUDE_SUBDIRECTORY(netd)
 INCLUDE_SUBDIRECTORY(usrp1)
 INCLUDE_SUBDIRECTORY(usrp2)
 INCLUDE_SUBDIRECTORY(b100)
diff --git a/host/lib/usrp/netd/CMakeLists.txt b/host/lib/usrp/netd/CMakeLists.txt
new file mode 100644
index 000000000..a33c978c8
--- /dev/null
+++ b/host/lib/usrp/netd/CMakeLists.txt
@@ -0,0 +1,29 @@
+#
+# Copyright 2017 Ettus Research (National Instruments)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+########################################################################
+# This file included, use CMake directory variables
+########################################################################
+
+########################################################################
+# Conditionally configure the X300 support
+########################################################################
+IF(ENABLE_NETD)
+    LIBUHD_APPEND_SOURCES(
+        ${CMAKE_CURRENT_SOURCE_DIR}/netd_impl.cpp
+    )
+ENDIF(ENABLE_NETD)
diff --git a/host/lib/usrp/netd/netd_impl.cpp b/host/lib/usrp/netd/netd_impl.cpp
new file mode 100644
index 000000000..0b4da86ef
--- /dev/null
+++ b/host/lib/usrp/netd/netd_impl.cpp
@@ -0,0 +1,330 @@
+//
+// Copyright 2017 Ettus Research (National Instruments)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "netd_impl.hpp"
+#include <../device3/device3_impl.hpp>
+#include <uhd/exception.hpp>
+#include <uhd/property_tree.hpp>
+#include <uhd/transport/if_addrs.hpp>
+#include <uhd/transport/udp_simple.hpp>
+#include <uhd/transport/udp_zero_copy.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/tasks.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/asio.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/thread.hpp>
+#include <memory>
+#include <mutex>
+#include <random>
+#include <string>
+
+using namespace uhd;
+
+netd_mboard_impl::netd_mboard_impl(const std::string& addr)
+    : rpc(addr, MPM_RPC_PORT)
+{
+    std::map<std::string, std::string> _dev_info =
+        rpc.call<dev_info>("get_device_info");
+    device_info =
+        dict<std::string, std::string>(_dev_info.begin(), _dev_info.end());
+    // Get initial claim on mboard
+    _rpc_token = rpc.call<std::string>("claim", "UHD - Session 01"); // make this configurable with device_addr?
+    if (_rpc_token.empty()){
+        throw uhd::value_error("netd device claiming failed!");
+    }
+    _claimer_task = task::make([this] {
+        if (not this->claim()) {
+            throw uhd::value_error("netd device reclaiming loop failed!");
+        };
+        boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
+    });
+    std::vector<std::string> data_ifaces =
+        rpc.call<std::vector<std::string>>("get_interfaces", _rpc_token);
+
+    // discover path to device and tell MPM our MAC address seen at the data
+    // interfaces
+    // move this into make_transport
+    //for (const auto& iface : data_ifaces) {
+        //std::vector<std::string> addrs = rpc.call<std::vector<std::string>>(
+            //"get_interface_addrs", _rpc_token, iface);
+        //for (const auto& iface_addr : addrs) {
+            //if (rpc_client(iface_addr, MPM_RPC_PORT)
+                    //.call<bool>("probe_interface", _rpc_token)) {
+                //data_interfaces.emplace(iface, iface_addr);
+                //break;
+            //}
+        //}
+    //}
+}
+netd_mboard_impl::~netd_mboard_impl() {}
+
+netd_mboard_impl::uptr netd_mboard_impl::make(const std::string& addr)
+{
+    netd_mboard_impl::uptr mb =
+        netd_mboard_impl::uptr(new netd_mboard_impl(addr));
+    // implicit move
+    return mb;
+}
+
+bool netd_mboard_impl::claim() { return rpc.call<bool>("claim", _rpc_token); }
+
+netd_impl::netd_impl(const device_addr_t& device_addr) : usrp::device3_impl()
+{
+    UHD_LOGGER_INFO("NETD") << "NETD initialization sequence...";
+    _tree->create<std::string>("/name").set("NETD - Series device");
+    const device_addrs_t device_args = separate_device_addr(device_addr);
+    _mb.reserve(device_args.size());
+    for (size_t mb_i = 0; mb_i < device_args.size(); ++mb_i) {
+        _mb.push_back(setup_mb(mb_i, device_args[mb_i]));
+    }
+}
+
+netd_impl::~netd_impl() {}
+
+netd_mboard_impl::uptr netd_impl::setup_mb(const size_t mb_i,
+                                           const uhd::device_addr_t& dev_addr)
+{
+    const fs_path mb_path = "/mboards/" + std::to_string(mb_i);
+    netd_mboard_impl::uptr mb = netd_mboard_impl::make(dev_addr["addr"]);
+    mb->initialization_done = false;
+    std::vector<std::string> addrs;
+    const std::string eth0_addr = dev_addr["addr"];
+    _tree->create<std::string>(mb_path / "name")
+        .set(mb->device_info.get("type", ""));
+    _tree->create<std::string>(mb_path / "serial")
+        .set(mb->device_info.get("serial", ""));
+    _tree->create<std::string>(mb_path / "connection")
+        .set(mb->device_info.get("connection", "remote"));
+
+    for (const std::string& key : dev_addr.keys()) {
+        if (key.find("recv") != std::string::npos)
+            mb->recv_args[key] = dev_addr[key];
+        if (key.find("send") != std::string::npos)
+            mb->send_args[key] = dev_addr[key];
+    }
+
+    // Do real MTU discovery (something similar like X300 but with MPM)
+
+    _tree->create<size_t>(mb_path / "mtu/recv").set(1500);
+    _tree->create<size_t>(mb_path / "mtu/send").set(1500);
+    _tree->create<size_t>(mb_path / "link_max_rate").set(1e9 / 8);
+
+    // query more information about FPGA/MPM
+
+    // Call init on periph_manager, this will init the dboards/mboard, maybe
+    // even selfcal and everything
+
+    // Query time/clock sources on mboards/dboards
+    // Throw rpc calls with boost bind into the property tree?
+
+    // Query rfnoc blocks on the device (MPM may know about them?)
+
+    // call enumerate rfnoc_blocks on the device
+
+    // configure radio?
+
+    // implicit move
+    return mb;
+}
+
+// frame_size_t determine_max_frame_size(const std::string &addr,
+//                                       const frame_size_t &user_frame_size){
+//     transport::udp_simple::sptr udp =
+//     transport::udp_simple::make_connected(addr,
+//                                                                             std::to_string(MPM_DISCOVERY_PORT));
+//     std::vector<uint8_t> buffer(std::max(user_frame_size.rec))
+// }
+// Everything fake below here
+
+both_xports_t netd_impl::make_transport(const sid_t&,
+                                        usrp::device3_impl::xport_type_t,
+                                        const uhd::device_addr_t&)
+{
+    //const size_t mb_index = address.get_dst_addr();
+    size_t mb_index = 0;
+
+    both_xports_t xports;
+    xports.endianness = uhd::ENDIANNESS_BIG;
+    const uhd::device_addr_t& xport_args = (xport_type == CTRL) ? uhd::device_addr_t() : args;
+    transport::zero_copy_xport_params default_buff_args;
+
+    /*
+    std::cout << address << std::endl;
+    std::cout << address.get_src_addr() << std::endl;
+    std::cout << address.get_dst_addr() << std::endl;
+    */
+
+    std::string interface_addr = "192.168.10.2";
+    const uint32_t xbar_src_addr = address.get_src_addr();
+    const uint32_t xbar_src_dst = 0;
+
+    default_buff_args.send_frame_size = 8000;
+    default_buff_args.recv_frame_size = 8000;
+    default_buff_args.num_recv_frames = 32;
+    default_buff_args.num_send_frames = 32;
+    // hardcode frame size for now
+
+    transport::udp_zero_copy::buff_params buff_params;
+    auto recv = transport::udp_zero_copy::make(
+        interface_addr,
+        BOOST_STRINGIZE(49153),
+        default_buff_args,
+        buff_params,
+        xport_args);
+    uint16_t port  = recv->get_local_port();
+
+    xports.send_sid = _mb[mb_index]->allocate_sid(port, address, xbar_src_addr, xbar_src_dst);
+    xports.recv_sid = xports.send_sid.reversed();
+
+    //std::cout << xports.send_sid << std::endl;
+    //std::cout << xports.recv_sid << std::endl;
+
+    xports.recv_buff_size = buff_params.recv_buff_size;
+    xports.send_buff_size = buff_params.send_buff_size;
+
+    xports.recv = recv;
+    xports.send = xports.recv;
+
+    return xports;
+}
+
+device_addrs_t netd_find_with_addr(const device_addr_t& hint_)
+{
+    transport::udp_simple::sptr comm = transport::udp_simple::make_broadcast(
+        hint_["addr"], std::to_string(MPM_DISCOVERY_PORT));
+    comm->send(
+        boost::asio::buffer(&MPM_DISCOVERY_CMD, sizeof(MPM_DISCOVERY_CMD)));
+    device_addrs_t addrs;
+    while (true) {
+        char buff[4096] = {};
+        const size_t nbytes = comm->recv(boost::asio::buffer(buff), 0.050);
+        if (nbytes == 0) {
+            break;
+        }
+        const char* reply = (const char*)buff;
+        std::string reply_string = std::string(reply);
+        std::vector<std::string> result;
+        boost::algorithm::split(result, reply_string,
+                                [](const char& in) { return in == ';'; },
+                                boost::token_compress_on);
+        if (result.empty()) {
+            continue;
+        }
+        // who else is reposending to our request !?
+        if (result[0] != "USRP-MPM") {
+            continue;
+        }
+        const std::string recv_addr = comm->get_recv_addr();
+
+        // remove external iface addrs if executed directly on device
+        bool external_iface = false;
+        for (const auto& addr : transport::get_if_addrs()) {
+            if ((addr.inet == comm->get_recv_addr()) &&
+                recv_addr !=
+                    boost::asio::ip::address_v4::loopback().to_string()) {
+                external_iface = true;
+            }
+        }
+        if (external_iface) {
+            continue;
+        }
+        device_addr_t new_addr;
+        new_addr["addr"] = recv_addr;
+        new_addr["type"] = "netd"; // hwd will overwrite this
+        // remove ident string and put other informations into device_addr dict
+        result.erase(result.begin());
+        // parse key-value pairs in the discovery string and add them to the
+        // device_addr
+        for (const auto& el : result) {
+            std::vector<std::string> value;
+            boost::algorithm::split(value, el,
+                                    [](const char& in) { return in == '='; },
+                                    boost::token_compress_on);
+            new_addr[value[0]] = value[1];
+        }
+        addrs.push_back(new_addr);
+    }
+    return addrs;
+};
+
+device_addrs_t netd_find(const device_addr_t& hint_)
+{
+    // handle cases:
+    //
+    //  - empty hint
+    //  - multiple addrs
+    //  - single addr
+
+    device_addrs_t hints = separate_device_addr(hint_);
+    // either hints has:
+    // multiple entries
+    //   -> search for multiple devices and join them back into one
+    //   device_addr_t
+    // one entry with addr:
+    //   -> search for one device with this addr
+    // one
+    // multiple addrs
+    if (hints.size() > 1) {
+        device_addrs_t found_devices;
+        found_devices.reserve(hints.size());
+        for (const auto& hint : hints) {
+            if (not hint.has_key("addr")) { // maybe allow other attributes as well
+                return device_addrs_t();
+            }
+            device_addrs_t reply_addrs = netd_find_with_addr(hint);
+            if (reply_addrs.size() > 1) {
+                throw uhd::value_error(
+                    str(boost::format("Could not resolve device hint \"%s\" to "
+                                      "a single device.") %
+                        hint.to_string()));
+            } else if (reply_addrs.empty()) {
+                return device_addrs_t();
+            }
+            found_devices.push_back(reply_addrs[0]);
+        }
+        return device_addrs_t(1, combine_device_addrs(found_devices));
+    }
+    hints.resize(1);
+    device_addr_t hint = hints[0];
+    device_addrs_t addrs;
+
+    if (hint.has_key("addr")) {
+        // is this safe?
+        return netd_find_with_addr(hint);
+    }
+
+    for (const transport::if_addrs_t& if_addr : transport::get_if_addrs()) {
+        device_addr_t new_hint = hint;
+        new_hint["addr"] = if_addr.bcast;
+
+        device_addrs_t reply_addrs = netd_find_with_addr(new_hint);
+        addrs.insert(addrs.begin(), reply_addrs.begin(), reply_addrs.end());
+    }
+    return addrs;
+}
+
+static device::sptr netd_make(const device_addr_t& device_addr)
+{
+    return device::sptr(boost::make_shared<netd_impl>(device_addr));
+}
+
+UHD_STATIC_BLOCK(register_netd_device)
+{
+    device::register_device(&netd_find, &netd_make, device::USRP);
+}
+// vim: sw=4 expandtab:
diff --git a/host/lib/usrp/netd/netd_impl.hpp b/host/lib/usrp/netd/netd_impl.hpp
new file mode 100644
index 000000000..418f3aef6
--- /dev/null
+++ b/host/lib/usrp/netd/netd_impl.hpp
@@ -0,0 +1,82 @@
+//
+// Copyright 2017 Ettus Research (National Instruments)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_NETD_IMPL_HPP
+#define INCLUDED_NETD_IMPL_HPP
+#include "../../utils/rpc.hpp"
+#include "../device3/device3_impl.hpp"
+#include <uhd/stream.hpp>
+#include <uhd/types/device_addr.hpp>
+#include <uhd/types/dict.hpp>
+#include <uhd/utils/tasks.hpp>
+#include <map>
+
+static const size_t MPM_DISCOVERY_PORT = 49600;
+static const size_t MPM_RPC_PORT = 49601;
+static const char MPM_DISCOVERY_CMD[] = "MPM-DISC";
+static const char MPM_ECHO_CMD[] = "MPM-ECHO";
+static const size_t NETD_10GE_DATA_FRAME_MAX_SIZE = 8000; // CHDR packet size in bytes
+
+struct frame_size_t
+{
+    size_t recv_frame_size;
+    size_t send_frame_size;
+};
+
+class netd_mboard_impl
+{
+  public:
+    using uptr = std::unique_ptr<netd_mboard_impl>;
+    using dev_info = std::map<std::string, std::string>;
+    netd_mboard_impl(const std::string& addr);
+    ~netd_mboard_impl();
+    static uptr make(const std::string& addr);
+
+    bool initialization_done = false;
+    uhd::dict<std::string, std::string> device_info;
+    uhd::dict<std::string, std::string> recv_args;
+    uhd::dict<std::string, std::string> send_args;
+    std::map<std::string, std::string> data_interfaces;
+    std::string loaded_fpga_image;
+    std::string xport_path;
+    uhd::rpc_client rpc;
+
+  private:
+    bool claim();
+    std::string generate_token() const;
+    std::string _rpc_token;
+    uhd::task::sptr _claimer_task;
+};
+
+class netd_impl : public uhd::usrp::device3_impl
+{
+  public:
+    netd_impl(const uhd::device_addr_t& device_addr);
+    ~netd_impl();
+
+    netd_mboard_impl::uptr setup_mb(const size_t mb_i,
+                                    const uhd::device_addr_t& dev_addr);
+    uhd::both_xports_t make_transport(const uhd::sid_t&,
+                                      uhd::usrp::device3_impl::xport_type_t,
+                                      const uhd::device_addr_t&);
+
+  private:
+    std::vector<netd_mboard_impl::uptr> _mb;
+};
+uhd::device_addrs_t netd_find(const uhd::device_addr_t& hint_);
+#endif /* INCLUDED_NETD_IMPL_HPP */
+// vim: sw=4 expandtab:
diff --git a/host/lib/utils/rpc.hpp b/host/lib/utils/rpc.hpp
new file mode 100644
index 000000000..946ce90ed
--- /dev/null
+++ b/host/lib/utils/rpc.hpp
@@ -0,0 +1,44 @@
+//
+// Copyright 2017 Ettus Research (National Instruments)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_UTILS_RPC_HPP
+#define INCLUDED_UTILS_RPC_HPP
+
+#include <rpc/client.h>
+
+namespace uhd
+{
+class rpc_client
+{
+  public:
+    rpc_client(std::string const& addr, uint16_t port) : _client(addr, port) {}
+    template <typename return_type, typename... Args>
+    return_type call(std::string const& func_name, Args&&... args)
+    {
+        std::lock_guard<std::mutex> lock(_mutex);
+        return _client.call(func_name, std::forward<Args>(args)...)
+            .template as<return_type>();
+    };
+
+  private:
+    std::mutex _mutex;
+    ::rpc::client _client;
+};
+
+}
+
+#endif /* INCLUDED_UTILS_RPC_HPP */
-- 
cgit v1.2.3