aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib
diff options
context:
space:
mode:
authorAshish Chaudhari <ashish@ettus.com>2019-05-28 13:21:08 -0700
committerMartin Braun <martin.braun@ettus.com>2019-11-26 11:49:20 -0800
commitb99d0f348eada0500ea5d668d5eba283afa6c4a4 (patch)
treebed87ff733aab37c7d5d3851f5cc5cbcffe056d4 /host/lib
parent374b30cc0eb0842e567d01486d9be8155e361d9f (diff)
downloaduhd-b99d0f348eada0500ea5d668d5eba283afa6c4a4.tar.gz
uhd-b99d0f348eada0500ea5d668d5eba283afa6c4a4.tar.bz2
uhd-b99d0f348eada0500ea5d668d5eba283afa6c4a4.zip
rfnoc: Added link/graph specific stream managers
- Fleshed out mb_iface - Managers currently only export ctrl APIs. Data APIs TBD
Diffstat (limited to 'host/lib')
-rw-r--r--host/lib/include/uhdlib/rfnoc/client_zero.hpp4
-rw-r--r--host/lib/include/uhdlib/rfnoc/epid_allocator.hpp58
-rw-r--r--host/lib/include/uhdlib/rfnoc/graph_stream_manager.hpp85
-rw-r--r--host/lib/include/uhdlib/rfnoc/link_stream_manager.hpp88
-rw-r--r--host/lib/include/uhdlib/rfnoc/mb_iface.hpp52
-rw-r--r--host/lib/rfnoc/CMakeLists.txt3
-rw-r--r--host/lib/rfnoc/client_zero.cpp18
-rw-r--r--host/lib/rfnoc/epid_allocator.cpp46
-rw-r--r--host/lib/rfnoc/graph_stream_manager.cpp129
-rw-r--r--host/lib/rfnoc/link_stream_manager.cpp266
10 files changed, 722 insertions, 27 deletions
diff --git a/host/lib/include/uhdlib/rfnoc/client_zero.hpp b/host/lib/include/uhdlib/rfnoc/client_zero.hpp
index c6d3e879b..52330bffa 100644
--- a/host/lib/include/uhdlib/rfnoc/client_zero.hpp
+++ b/host/lib/include/uhdlib/rfnoc/client_zero.hpp
@@ -25,6 +25,10 @@ namespace uhd { namespace rfnoc { namespace detail {
class client_zero : public uhd::rfnoc::register_iface_holder
{
public:
+ using sptr = std::shared_ptr<client_zero>;
+
+ static sptr make(chdr_ctrl_endpoint& chdr_ctrl_ep, sep_id_t dst_epid);
+
client_zero(register_iface::sptr reg);
//! Definition of an edge in the static router
diff --git a/host/lib/include/uhdlib/rfnoc/epid_allocator.hpp b/host/lib/include/uhdlib/rfnoc/epid_allocator.hpp
new file mode 100644
index 000000000..c92ca013d
--- /dev/null
+++ b/host/lib/include/uhdlib/rfnoc/epid_allocator.hpp
@@ -0,0 +1,58 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#ifndef INCLUDED_LIBUHD_EPID_ALLOCATOR_HPP
+#define INCLUDED_LIBUHD_EPID_ALLOCATOR_HPP
+
+#include <uhdlib/rfnoc/rfnoc_common.hpp>
+#include <map>
+#include <memory>
+#include <mutex>
+
+namespace uhd { namespace rfnoc {
+
+/*! A class that is responsible for allocating and keeping track of endpoint
+ * IDs. There shall be one instance of this class per rfnoc_graph
+ */
+class epid_allocator
+{
+public:
+ using sptr = std::shared_ptr<epid_allocator>;
+
+ epid_allocator(sep_id_t start_epid = 1);
+ epid_allocator(const epid_allocator& rhs) = delete;
+ epid_allocator(epid_allocator&& rhs) = delete;
+
+ /*! \brief Allocate an EPID for the specified endpoint.
+ *
+ * \param addr The physical address (device, instance) of the stream endpoint
+ * \return The allocated EPID
+ */
+ sep_id_t allocate_epid(const sep_addr_t& addr);
+
+ /*! \brief Lookup an EPID and return the address associated with it.
+ *
+ * \param epid The allocated EPID
+ * \return The physical address (device, instance) of the stream endpoint
+ */
+ sep_addr_t lookup_epid(const sep_id_t& epid) const;
+
+ /*! \brief Deallocate the specified EPID.
+ *
+ * \param epid The EPID to deallocate
+ */
+ void deallocate_epid(sep_id_t epid);
+
+private:
+ std::map<sep_addr_t, sep_id_t> _epid_map;
+ std::map<sep_id_t, sep_addr_t> _addr_map;
+ sep_id_t _next_epid;
+ mutable std::mutex _mutex;
+};
+
+}} /* namespace uhd::rfnoc */
+
+#endif /* INCLUDED_LIBUHD_EPID_ALLOCATOR_HPP */
diff --git a/host/lib/include/uhdlib/rfnoc/graph_stream_manager.hpp b/host/lib/include/uhdlib/rfnoc/graph_stream_manager.hpp
new file mode 100644
index 000000000..81657453d
--- /dev/null
+++ b/host/lib/include/uhdlib/rfnoc/graph_stream_manager.hpp
@@ -0,0 +1,85 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#ifndef INCLUDED_LIBUHD_RFNOC_GRAPH_STREAM_MANAGER_HPP
+#define INCLUDED_LIBUHD_RFNOC_GRAPH_STREAM_MANAGER_HPP
+
+#include <uhdlib/rfnoc/chdr_packet.hpp>
+#include <uhdlib/rfnoc/client_zero.hpp>
+#include <uhdlib/rfnoc/ctrlport_endpoint.hpp>
+#include <uhdlib/rfnoc/epid_allocator.hpp>
+#include <uhdlib/rfnoc/mb_iface.hpp>
+#include <functional>
+#include <memory>
+#include <set>
+
+namespace uhd { namespace rfnoc {
+
+/*! A class that is responsible managing all data endpoints, control endpoints and client
+ * zero instances accessible through this graph. There must be one instance of this
+ * class per graph
+ */
+class graph_stream_manager
+{
+public:
+ using uptr = std::unique_ptr<graph_stream_manager>;
+
+ virtual ~graph_stream_manager() = 0;
+
+ /*! \brief Get all the local devices that can be taken from this graph
+ *
+ * \return A vector of IDs of all local devices
+ */
+ virtual std::vector<device_id_t> get_local_devices() const = 0;
+
+ /*! \brief Get all the endpoints reachable from this graph
+ *
+ * \return A set of addresses for all reachable endpoints
+ */
+ virtual const std::set<sep_addr_t>& get_reachable_endpoints() const = 0;
+
+ /*! \brief Initialize a control endpoint to the specified destination
+ *
+ * \param dst_addr The physical address of the destination endpoint
+ * \param via_device The preference for the device to take to get to the destination
+ * \return A pair (source, destination) endpoint IDs for the control stream
+ */
+ virtual sep_id_pair_t init_ctrl_stream(
+ sep_addr_t dst_addr, device_id_t via_device = NULL_DEVICE_ID) = 0;
+
+ /*! \brief Get a register iface (ctrlport endpoint) to a particular block
+ *
+ * \param dst_epid The endpoint ID of the destination
+ * \param block_index The index of the block in the device
+ * \param client_clk The clock that is driving the ctrlport slave
+ * \param timebase_clk The clock that is driving the timebase
+ * \param via_device The preference for the device to take to get to the destination
+ * \return An interface to the ctrlport endpoint
+ */
+ virtual ctrlport_endpoint::sptr get_block_register_iface(sep_id_t dst_epid,
+ uint16_t block_index,
+ const clock_iface& client_clk,
+ const clock_iface& timebase_clk,
+ device_id_t via_device = NULL_DEVICE_ID) = 0;
+
+ /*! \brief Get a pointer to the client zero instance for the specified EPID
+ *
+ * \param dst_epid The endpoint ID of the destination
+ * \param via_device The preference for the device to take to get to the destination
+ * \return An interface to the client zero instance
+ */
+ virtual detail::client_zero::sptr get_client_zero(
+ sep_id_t dst_epid, device_id_t via_device = NULL_DEVICE_ID) const = 0;
+
+ static uptr make(const chdr::chdr_packet_factory& pkt_factory,
+ const epid_allocator::sptr& epid_alloc,
+ const std::vector<std::pair<device_id_t, mb_iface*>>& links);
+
+}; // class graph_stream_manager
+
+}} /* namespace uhd::rfnoc */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_GRAPH_STREAM_MANAGER_HPP */
diff --git a/host/lib/include/uhdlib/rfnoc/link_stream_manager.hpp b/host/lib/include/uhdlib/rfnoc/link_stream_manager.hpp
new file mode 100644
index 000000000..c31bf8987
--- /dev/null
+++ b/host/lib/include/uhdlib/rfnoc/link_stream_manager.hpp
@@ -0,0 +1,88 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#ifndef INCLUDED_LIBUHD_RFNOC_LINK_STREAM_MANAGER_HPP
+#define INCLUDED_LIBUHD_RFNOC_LINK_STREAM_MANAGER_HPP
+
+#include <uhdlib/rfnoc/client_zero.hpp>
+#include <uhdlib/rfnoc/ctrlport_endpoint.hpp>
+#include <uhdlib/rfnoc/epid_allocator.hpp>
+#include <uhdlib/rfnoc/mb_iface.hpp>
+#include <functional>
+#include <memory>
+
+namespace uhd { namespace rfnoc {
+
+/*! A class that is responsible managing all data endpoints, control endpoints and client
+ * zero instances accessible via a physical link. There must be one instance this this
+ * class per physical link (Ethernet cable, PCIe connection, etc)
+ */
+class link_stream_manager
+{
+public:
+ using uptr = std::unique_ptr<link_stream_manager>;
+
+ virtual ~link_stream_manager() = 0;
+
+ /*! \brief Get the software device ID associated with this instance
+ *
+ * \return A vector of addresses for all reachable endpoints
+ */
+ virtual device_id_t get_self_device_id() const = 0;
+
+ /*! \brief Get all the endpoints reachable from this link
+ *
+ * \return A vector of addresses for all reachable endpoints
+ */
+ virtual const std::set<sep_addr_t>& get_reachable_endpoints() const = 0;
+
+ /*! \brief Initialize a control endpoint to the specified destination
+ *
+ * \param dst_addr The physical address of the destination endpoint
+ * \return A pair (source, destination) endpoint IDs for the control stream
+ */
+ virtual sep_id_pair_t init_ctrl_stream(sep_addr_t dst_addr) = 0;
+
+ /*! \brief Get a register iface (ctrlport endpoint) to a particular block
+ *
+ * \param dst_epid The endpoint ID of the destination
+ * \param block_index The index of the block in the device
+ * \param client_clk The clock that is driving the ctrlport slave
+ * \param timebase_clk The clock that is driving the timebase
+ * \return An interface to the ctrlport endpoint
+ */
+ virtual ctrlport_endpoint::sptr get_block_register_iface(sep_id_t dst_epid,
+ uint16_t block_index,
+ const clock_iface& client_clk,
+ const clock_iface& timebase_clk) = 0;
+
+ /*! \brief Get a pointer to the client zero instance for the specified EPID
+ *
+ * \param dst_epid The endpoint ID of the destination
+ * \return An interface to the client zero instance
+ */
+ virtual detail::client_zero::sptr get_client_zero(sep_id_t dst_epid) const = 0;
+
+ /*! \brief Create a data stream
+ *
+ * \param dst_epid The endpoint ID of the destination
+ * \param vc The virtual channel
+ * \param xport_args The transport argument
+ * \return An transport instance
+ */
+ virtual chdr_data_xport_t create_data_stream(
+ sep_addr_t dst_addr, sep_vc_t vc, const device_addr_t& xport_args) = 0;
+
+ static uptr make(const chdr::chdr_packet_factory& pkt_factory,
+ mb_iface& mb_if,
+ const epid_allocator::sptr& epid_alloc,
+ device_id_t device_id);
+
+}; // class link_stream_manager
+
+}} /* namespace uhd::rfnoc */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_LINK_STREAM_MANAGER_HPP */
diff --git a/host/lib/include/uhdlib/rfnoc/mb_iface.hpp b/host/lib/include/uhdlib/rfnoc/mb_iface.hpp
index 60afa92fa..ce1106c4c 100644
--- a/host/lib/include/uhdlib/rfnoc/mb_iface.hpp
+++ b/host/lib/include/uhdlib/rfnoc/mb_iface.hpp
@@ -7,7 +7,7 @@
#ifndef INCLUDED_LIBUHD_MB_IFACE_HPP
#define INCLUDED_LIBUHD_MB_IFACE_HPP
-#include <uhdlib/rfnoc/chdr_types.hpp>
+#include <uhdlib/rfnoc/rfnoc_common.hpp>
#include <memory>
namespace uhd { namespace rfnoc {
@@ -22,54 +22,52 @@ namespace uhd { namespace rfnoc {
* It's up to the various device implementations (e.g., x300_impl) to implement
* this interface.
*/
-class mb_iface {
+class mb_iface
+{
public:
using uptr = std::unique_ptr<mb_iface>;
- virtual ~mb_iface() = 0;
+ virtual ~mb_iface() = default;
- /*! Return the RFNoC protocol version for this motherboard
+ /*! Return the RFNoC protocol version of the firmware running on this motherboard
*/
virtual uint16_t get_proto_ver() = 0;
- /*! Return the CHDR width for this motherboard
+ /*! Return the CHDR width of the firmware running on this motherboard
*/
- virtual chdr_w_t get_chdr_width() = 0;
+ virtual chdr_w_t get_chdr_w() = 0;
- /*! Set the device ID of this motherboard
+ /*! Get the device ID assigned to the motherboard
*
- * Every motherboard in a multi-USRP setup needs a unique device ID. It is
- * up to the rfnoc_graph to choose the various IDs, and then call this
- * function to set it.
+ * A freshly reset motherboard should return 0.
+ *
+ * \returns the motherboard's device ID
*/
- virtual void set_device_id(const uint16_t id) = 0;
+ virtual device_id_t get_remote_device_id() = 0;
- /*! Get device ID
- *
- * Returns the value previously written by set_device_id(). A freshly
- * resetted motherboard which has not been assigned a device ID should
- * return 0xFFFF.
+ /*! Get the local (software) device IDs on this motherboard that can actively
+ * communicate with the sea of RFNoC FPGAs. The number of local devices returned
+ * should be equal to the number of physical links on the motherboard that are
+ * actively connected.
*
- * \returns the motherboard's device ID
+ * \returns The active software device IDs
*/
- virtual uint16_t get_device_id() = 0;
+ virtual std::vector<device_id_t> get_local_device_ids() = 0;
/*! Reset the device
*/
virtual void reset_network() = 0;
- /*! Return a list of all physical links available from the current UHD
- * session to the motherboard.
- *
- * FIXME determine appropriate return type
+ /*! Create a control transport
*/
- //virtual <link info> enumerate_links() = 0;
+ virtual chdr_ctrl_xport_t make_ctrl_transport(
+ device_id_t local_device_id, const sep_id_t& src_epid) = 0;
- /*!
- *
- * FIXME determine appropriate return type and arg types
+ /*! Create a data transport
*/
- //virtual link_iface::uptr create_link(<link_params>) = 0;
+ virtual chdr_data_xport_t make_data_transport(device_id_t local_device_id,
+ const sep_id_t& src_epid,
+ const device_addr_t& xport_args) = 0;
};
}} /* namespace uhd::rfnoc */
diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt
index 45904a572..366e2d062 100644
--- a/host/lib/rfnoc/CMakeLists.txt
+++ b/host/lib/rfnoc/CMakeLists.txt
@@ -24,9 +24,12 @@ LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/chdr_packet.cpp
${CMAKE_CURRENT_SOURCE_DIR}/client_zero.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ctrl_iface.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/epid_allocator.cpp
${CMAKE_CURRENT_SOURCE_DIR}/graph_impl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/graph.cpp
${CMAKE_CURRENT_SOURCE_DIR}/legacy_compat.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/link_stream_manager.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/graph_stream_manager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/noc_block_base.cpp
${CMAKE_CURRENT_SOURCE_DIR}/node_ctrl_base.cpp
${CMAKE_CURRENT_SOURCE_DIR}/node.cpp
diff --git a/host/lib/rfnoc/client_zero.cpp b/host/lib/rfnoc/client_zero.cpp
index 82c44eaf4..aaecdcbf5 100644
--- a/host/lib/rfnoc/client_zero.cpp
+++ b/host/lib/rfnoc/client_zero.cpp
@@ -213,3 +213,21 @@ uint32_t client_zero::_get_flush_status_flags(uint16_t portno)
// The flush status flags are in the third register of the port
return regs().peek32(_get_port_base_addr(portno) + 8);
}
+
+client_zero::sptr client_zero::make(chdr_ctrl_endpoint& chdr_ctrl_ep, sep_id_t dst_epid)
+{
+ // Create a control port endpoint for client zero
+ static constexpr uint16_t CLIENT_ZERO_PORT = 0;
+ static constexpr size_t CLIENT_ZERO_BUFF_CAPACITY = 32;
+ static constexpr size_t CLIENT_ZERO_MAX_ASYNC_MSGS = 0;
+ static clock_iface client_zero_clk{"client_zero"};
+ client_zero_clk.set_running(true); // Client zero clock must be always-on.
+ client_zero_clk.set_freq(100e6); // The freq is unused. No timed ops or sleeps.
+
+ return std::make_shared<client_zero>(chdr_ctrl_ep.get_ctrlport_ep(dst_epid,
+ CLIENT_ZERO_PORT,
+ CLIENT_ZERO_BUFF_CAPACITY,
+ CLIENT_ZERO_MAX_ASYNC_MSGS,
+ client_zero_clk,
+ client_zero_clk));
+}
diff --git a/host/lib/rfnoc/epid_allocator.cpp b/host/lib/rfnoc/epid_allocator.cpp
new file mode 100644
index 000000000..984b1716e
--- /dev/null
+++ b/host/lib/rfnoc/epid_allocator.cpp
@@ -0,0 +1,46 @@
+//
+// Copyright 2018 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/exception.hpp>
+#include <uhdlib/rfnoc/epid_allocator.hpp>
+
+using namespace uhd;
+using namespace uhd::rfnoc;
+
+epid_allocator::epid_allocator(sep_id_t start_epid) : _next_epid(start_epid) {}
+
+
+sep_id_t epid_allocator::allocate_epid(const sep_addr_t& addr)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+
+ if (_epid_map.count(addr) == 0) {
+ sep_id_t new_epid = _next_epid++;
+ _epid_map[addr] = new_epid;
+ _addr_map[new_epid] = addr;
+ return new_epid;
+ } else {
+ return _epid_map.at(addr);
+ }
+}
+
+sep_addr_t epid_allocator::lookup_epid(const sep_id_t& epid) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+
+ if (_addr_map.count(epid) > 0) {
+ return _addr_map.at(epid);
+ } else {
+ throw uhd::lookup_error("The specified EPID has not been allocated");
+ }
+}
+
+void epid_allocator::deallocate_epid(sep_id_t)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ // TODO: Nothing to do for deallocate.
+ // With the current counter approach we assume that we will not run out of EPIDs
+}
diff --git a/host/lib/rfnoc/graph_stream_manager.cpp b/host/lib/rfnoc/graph_stream_manager.cpp
new file mode 100644
index 000000000..06f78dad4
--- /dev/null
+++ b/host/lib/rfnoc/graph_stream_manager.cpp
@@ -0,0 +1,129 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/exception.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhdlib/rfnoc/graph_stream_manager.hpp>
+#include <uhdlib/rfnoc/link_stream_manager.hpp>
+#include <boost/format.hpp>
+#include <map>
+#include <set>
+
+using namespace uhd;
+using namespace uhd::rfnoc;
+using namespace uhd::rfnoc::chdr;
+
+graph_stream_manager::~graph_stream_manager() = default;
+
+class graph_stream_manager_impl : public graph_stream_manager
+{
+public:
+ graph_stream_manager_impl(const chdr::chdr_packet_factory& pkt_factory,
+ const epid_allocator::sptr& epid_alloc,
+ const std::vector<std::pair<device_id_t, mb_iface*>>& links)
+ : _epid_alloc(epid_alloc)
+ {
+ for (const auto& lnk : links) {
+ UHD_ASSERT_THROW(lnk.second);
+ _link_mgrs.emplace(lnk.first,
+ std::move(link_stream_manager::make(
+ pkt_factory, *lnk.second, epid_alloc, lnk.first)));
+ }
+ for (const auto& mgr_pair : _link_mgrs) {
+ for (const auto& ep : mgr_pair.second->get_reachable_endpoints()) {
+ // Add the (potential) destinations to the
+ _reachable_endpoints.insert(ep);
+ // Add entry to source map
+ if (_src_map.count(ep) == 0) {
+ _src_map[ep] = std::vector<device_id_t>();
+ }
+ _src_map[ep].push_back(mgr_pair.first);
+ }
+ }
+ }
+
+ virtual ~graph_stream_manager_impl() = default;
+
+ virtual const std::set<sep_addr_t>& get_reachable_endpoints() const
+ {
+ return _reachable_endpoints;
+ }
+
+ virtual std::vector<device_id_t> get_local_devices() const
+ {
+ std::vector<device_id_t> retval;
+ for (const auto& mgr_pair : _link_mgrs) {
+ retval.push_back(mgr_pair.first);
+ }
+ return retval;
+ }
+
+ virtual sep_id_pair_t init_ctrl_stream(
+ sep_addr_t dst_addr, device_id_t via_device = NULL_DEVICE_ID)
+ {
+ return _link_mgrs.at(_check_dst_and_find_src(dst_addr, via_device))
+ ->init_ctrl_stream(dst_addr);
+ }
+
+ virtual ctrlport_endpoint::sptr get_block_register_iface(sep_id_t dst_epid,
+ uint16_t block_index,
+ const clock_iface& client_clk,
+ const clock_iface& timebase_clk,
+ device_id_t via_device = NULL_DEVICE_ID)
+ {
+ sep_addr_t dst_addr = _epid_alloc->lookup_epid(dst_epid);
+ return _link_mgrs.at(_check_dst_and_find_src(dst_addr, via_device))
+ ->get_block_register_iface(dst_epid, block_index, client_clk, timebase_clk);
+ }
+
+ virtual detail::client_zero::sptr get_client_zero(
+ sep_id_t dst_epid, device_id_t via_device = NULL_DEVICE_ID) const
+ {
+ sep_addr_t dst_addr = _epid_alloc->lookup_epid(dst_epid);
+ return _link_mgrs.at(_check_dst_and_find_src(dst_addr, via_device))
+ ->get_client_zero(dst_epid);
+ }
+
+private:
+ device_id_t _check_dst_and_find_src(sep_addr_t dst_addr, device_id_t via_device) const
+ {
+ if (_src_map.count(dst_addr) > 0) {
+ const auto& src_devs = _src_map.at(dst_addr);
+ if (via_device == NULL_DEVICE_ID) {
+ // TODO: Maybe we can come up with a better heuristic for when the user
+ // gives no preference
+ return src_devs[0];
+ } else {
+ for (const auto& src : src_devs) {
+ if (src == via_device) {
+ return src;
+ }
+ }
+ throw uhd::rfnoc_error("Specified destination address is unreachable "
+ "from the via device");
+ }
+ } else {
+ throw uhd::rfnoc_error("Specified destination address is unreachable");
+ }
+ }
+
+ // The cached EPID allocator object
+ epid_allocator::sptr _epid_alloc;
+ // A map the contains all link manager indexed by the device ID
+ std::map<device_id_t, link_stream_manager::uptr> _link_mgrs;
+ // A set of the addresses of all devices reachable from this graph
+ std::set<sep_addr_t> _reachable_endpoints;
+ // A map of addresses that can be taken to reach a particular destination
+ std::map<sep_addr_t, std::vector<device_id_t>> _src_map;
+};
+
+graph_stream_manager::uptr graph_stream_manager::make(
+ const chdr::chdr_packet_factory& pkt_factory,
+ const epid_allocator::sptr& epid_alloc,
+ const std::vector<std::pair<device_id_t, mb_iface*>>& links)
+{
+ return std::make_unique<graph_stream_manager_impl>(pkt_factory, epid_alloc, links);
+}
diff --git a/host/lib/rfnoc/link_stream_manager.cpp b/host/lib/rfnoc/link_stream_manager.cpp
new file mode 100644
index 000000000..0faec94e3
--- /dev/null
+++ b/host/lib/rfnoc/link_stream_manager.cpp
@@ -0,0 +1,266 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/exception.hpp>
+#include <uhd/transport/muxed_zero_copy_if.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhdlib/rfnoc/chdr_ctrl_endpoint.hpp>
+#include <uhdlib/rfnoc/link_stream_manager.hpp>
+#include <uhdlib/rfnoc/mgmt_portal.hpp>
+#include <boost/format.hpp>
+
+using namespace uhd;
+using namespace uhd::rfnoc;
+using namespace uhd::transport;
+using namespace uhd::rfnoc::chdr;
+using namespace uhd::rfnoc::mgmt;
+using namespace uhd::rfnoc::detail;
+
+constexpr sep_inst_t SEP_INST_MGMT_CTRL = 0;
+constexpr sep_inst_t SEP_INST_DATA_BASE = 1;
+
+link_stream_manager::~link_stream_manager() = default;
+
+class link_stream_manager_impl : public link_stream_manager
+{
+public:
+ link_stream_manager_impl(const chdr::chdr_packet_factory& pkt_factory,
+ mb_iface& mb_if,
+ const epid_allocator::sptr& epid_alloc,
+ device_id_t device_id)
+ : _pkt_factory(pkt_factory)
+ , _my_device_id(device_id)
+ , _mb_iface(mb_if)
+ , _epid_alloc(epid_alloc)
+ , _data_ep_inst(0)
+ {
+ // Sanity check if we can access our device ID from this motherboard
+ const auto& mb_devs = _mb_iface.get_local_device_ids();
+ if (std::find(mb_devs.begin(), mb_devs.end(), _my_device_id) == mb_devs.end()) {
+ throw uhd::rfnoc_error("The device bound to this link manager cannot be "
+ "accessed from this motherboard");
+ }
+
+ // Sanity check the protocol version and CHDR width
+ if ((_pkt_factory.get_protover() & 0xFF00)
+ != (_mb_iface.get_proto_ver() & 0xFF00)) {
+ throw uhd::rfnoc_error("RFNoC protocol mismatch between SW and HW");
+ }
+ if (_pkt_factory.get_chdr_w() != _mb_iface.get_chdr_w()) {
+ throw uhd::rfnoc_error("RFNoC CHDR width mismatch between SW and HW");
+ }
+
+ // Create a transport and EPID for management and control traffic
+ _my_mgmt_ctrl_epid =
+ epid_alloc->allocate_epid(sep_addr_t(_my_device_id, SEP_INST_MGMT_CTRL));
+ _allocated_epids.insert(_my_mgmt_ctrl_epid);
+
+ // Create a muxed transport to share between the mgmt_portal and
+ // chdr_ctrl_endpoint. We have to use the same base transport here to ensure that
+ // the route setup logic in the FPGA transport works correctly.
+ // TODO: This needs to be cleaned up. A muxed_zero_copy_if is excessive here
+ chdr_ctrl_xport_t base_xport =
+ _mb_iface.make_ctrl_transport(_my_device_id, _my_mgmt_ctrl_epid);
+ UHD_ASSERT_THROW(base_xport.send.get() == base_xport.recv.get())
+
+ auto classify_fn = [&pkt_factory](void* buff, size_t) -> uint32_t {
+ if (buff) {
+ chdr_packet::cuptr pkt = pkt_factory.make_generic();
+ pkt->refresh(buff);
+ return (pkt->get_chdr_header().get_pkt_type() == PKT_TYPE_MGMT) ? 0 : 1;
+ } else {
+ throw uhd::assertion_error("null pointer");
+ }
+ };
+ _muxed_xport = muxed_zero_copy_if::make(base_xport.send, classify_fn, 2);
+
+ // Create child transports
+ chdr_ctrl_xport_t mgmt_xport = base_xport;
+ mgmt_xport.send = _muxed_xport->make_stream(0);
+ mgmt_xport.recv = mgmt_xport.send;
+ _ctrl_xport = base_xport;
+ _ctrl_xport.send = _muxed_xport->make_stream(1);
+ _ctrl_xport.recv = _ctrl_xport.send;
+
+ // Create management portal using one of the child transports
+ _mgmt_portal = mgmt_portal::make(mgmt_xport,
+ _pkt_factory,
+ sep_addr_t(_my_device_id, SEP_INST_MGMT_CTRL),
+ _my_mgmt_ctrl_epid);
+ }
+
+ virtual ~link_stream_manager_impl()
+ {
+ for (const auto& epid : _allocated_epids) {
+ _epid_alloc->deallocate_epid(epid);
+ }
+ }
+
+ virtual device_id_t get_self_device_id() const
+ {
+ return _my_device_id;
+ }
+
+ virtual const std::set<sep_addr_t>& get_reachable_endpoints() const
+ {
+ return _mgmt_portal->get_reachable_endpoints();
+ }
+
+ virtual sep_id_pair_t init_ctrl_stream(sep_addr_t dst_addr)
+ {
+ // Allocate EPIDs
+ sep_id_t dst_epid = _epid_alloc->allocate_epid(dst_addr);
+ UHD_LOGGER_DEBUG("RFNOC::LINK_MGR")
+ << boost::format("Initializing control stream to Endpoint %d:%d with EPID "
+ "%d...")
+ % dst_addr.first % dst_addr.second % dst_epid;
+ _ensure_ep_is_reachable(dst_addr);
+
+ // Make sure that the software side of the endpoint is initialized and reachable
+ if (_ctrl_ep == nullptr) {
+ // Create a control endpoint with that xport
+ _ctrl_ep =
+ chdr_ctrl_endpoint::make(_ctrl_xport, _pkt_factory, _my_mgmt_ctrl_epid);
+ }
+
+ // Setup a route to the EPID
+ _mgmt_portal->initialize_endpoint(dst_addr, dst_epid);
+ _mgmt_portal->setup_local_route(dst_epid);
+ if (!_mgmt_portal->get_endpoint_info(dst_epid).has_ctrl) {
+ throw uhd::rfnoc_error(
+ "Downstream endpoint does not support control traffic");
+ }
+
+ // Create a client zero instance
+ if (_client_zero_map.count(dst_epid) == 0) {
+ _client_zero_map.insert(
+ std::make_pair(dst_epid, client_zero::make(*_ctrl_ep, dst_epid)));
+ }
+ return sep_id_pair_t(_my_mgmt_ctrl_epid, dst_epid);
+ }
+
+ virtual ctrlport_endpoint::sptr get_block_register_iface(sep_id_t dst_epid,
+ uint16_t block_index,
+ const clock_iface& client_clk,
+ const clock_iface& timebase_clk)
+ {
+ // Ensure that the endpoint is initialized for control at the specified EPID
+ if (_ctrl_ep == nullptr) {
+ throw uhd::runtime_error("Software endpoint not initialized for control");
+ }
+ if (_client_zero_map.count(dst_epid) == 0) {
+ throw uhd::runtime_error(
+ "Control for the specified EPID was not initialized");
+ }
+ const client_zero::sptr& c0_ctrl = _client_zero_map.at(dst_epid);
+ uint16_t dst_port = 1 + c0_ctrl->get_num_stream_endpoints() + block_index;
+ if (block_index >= c0_ctrl->get_num_blocks()) {
+ throw uhd::value_error("Requested block index out of range");
+ }
+
+ // Create control endpoint
+ return _ctrl_ep->get_ctrlport_ep(dst_epid,
+ dst_port,
+ (size_t(1) << c0_ctrl->get_block_info(dst_port).ctrl_fifo_size),
+ c0_ctrl->get_block_info(dst_port).ctrl_max_async_msgs,
+ client_clk,
+ timebase_clk);
+ }
+
+ virtual client_zero::sptr get_client_zero(sep_id_t dst_epid) const
+ {
+ if (_client_zero_map.count(dst_epid) == 0) {
+ throw uhd::runtime_error(
+ "Control for the specified EPID was not initialized");
+ }
+ return _client_zero_map.at(dst_epid);
+ }
+
+ virtual chdr_data_xport_t create_data_stream(
+ sep_addr_t dst_addr, sep_vc_t /*vc*/, const device_addr_t& xport_args)
+ {
+ // Create a new source endpoint and EPID
+ sep_addr_t sw_epid_addr(_my_device_id, SEP_INST_DATA_BASE + (_data_ep_inst++));
+ sep_id_t src_epid = _epid_alloc->allocate_epid(sw_epid_addr);
+ _allocated_epids.insert(src_epid);
+ _ensure_ep_is_reachable(dst_addr);
+
+ // Generate a new destination EPID instance
+ sep_id_t dst_epid = _epid_alloc->allocate_epid(dst_addr);
+
+ // Create the data transport that we will return to the client
+ chdr_data_xport_t xport =
+ _mb_iface.make_data_transport(_my_device_id, src_epid, xport_args);
+ xport.src_epid = src_epid;
+ xport.dst_epid = dst_epid;
+
+ // Create new temporary management portal with the transports used for this stream
+ // TODO: This is a bit excessive. Maybe we can pair down the functionality of the
+ // portal just for route setup purposes. Whatever we do, we *must* use xport in it
+ // though otherwise the transport will not behave correctly.
+ mgmt_portal::uptr data_mgmt_portal =
+ mgmt_portal::make(xport, _pkt_factory, sw_epid_addr, src_epid);
+
+ // Setup a route to the EPID
+ data_mgmt_portal->initialize_endpoint(dst_addr, dst_epid);
+ data_mgmt_portal->setup_local_route(dst_epid);
+ if (!_mgmt_portal->get_endpoint_info(dst_epid).has_data) {
+ throw uhd::rfnoc_error("Downstream endpoint does not support data traffic");
+ }
+
+ // TODO: Implement data transport setup logic here
+
+
+ // Delete the portal when done
+ data_mgmt_portal.reset();
+ return xport;
+ }
+
+private:
+ void _ensure_ep_is_reachable(const sep_addr_t& ep_addr_)
+ {
+ for (const auto& ep_addr : _mgmt_portal->get_reachable_endpoints()) {
+ if (ep_addr == ep_addr_)
+ return;
+ }
+ throw uhd::routing_error("Specified endpoint is not reachable");
+ }
+
+ // A reference to the packet factor
+ const chdr::chdr_packet_factory& _pkt_factory;
+ // The device address of this software endpoint
+ const device_id_t _my_device_id;
+
+ // Motherboard interface
+ mb_iface& _mb_iface;
+ // A pointer to the EPID allocator
+ epid_allocator::sptr _epid_alloc;
+ // A set of all allocated EPIDs
+ std::set<sep_id_t> _allocated_epids;
+ // The software EPID for all management and control traffic
+ sep_id_t _my_mgmt_ctrl_epid;
+ // Transports
+ muxed_zero_copy_if::sptr _muxed_xport;
+ chdr_ctrl_xport_t _ctrl_xport;
+ // Management portal for control endpoints
+ mgmt_portal::uptr _mgmt_portal;
+ // The CHDR control endpoint
+ chdr_ctrl_endpoint::uptr _ctrl_ep;
+ // A map of all client zero instances indexed by the destination
+ std::map<sep_id_t, client_zero::sptr> _client_zero_map;
+ // Data endpoint instance
+ sep_inst_t _data_ep_inst;
+};
+
+link_stream_manager::uptr link_stream_manager::make(
+ const chdr::chdr_packet_factory& pkt_factory,
+ mb_iface& mb_if,
+ const epid_allocator::sptr& epid_alloc,
+ device_id_t device_id)
+{
+ return std::make_unique<link_stream_manager_impl>(
+ pkt_factory, mb_if, epid_alloc, device_id);
+}