diff options
author | Ashish Chaudhari <ashish@ettus.com> | 2019-05-28 13:21:08 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2019-11-26 11:49:20 -0800 |
commit | b99d0f348eada0500ea5d668d5eba283afa6c4a4 (patch) | |
tree | bed87ff733aab37c7d5d3851f5cc5cbcffe056d4 /host/lib | |
parent | 374b30cc0eb0842e567d01486d9be8155e361d9f (diff) | |
download | uhd-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.hpp | 4 | ||||
-rw-r--r-- | host/lib/include/uhdlib/rfnoc/epid_allocator.hpp | 58 | ||||
-rw-r--r-- | host/lib/include/uhdlib/rfnoc/graph_stream_manager.hpp | 85 | ||||
-rw-r--r-- | host/lib/include/uhdlib/rfnoc/link_stream_manager.hpp | 88 | ||||
-rw-r--r-- | host/lib/include/uhdlib/rfnoc/mb_iface.hpp | 52 | ||||
-rw-r--r-- | host/lib/rfnoc/CMakeLists.txt | 3 | ||||
-rw-r--r-- | host/lib/rfnoc/client_zero.cpp | 18 | ||||
-rw-r--r-- | host/lib/rfnoc/epid_allocator.cpp | 46 | ||||
-rw-r--r-- | host/lib/rfnoc/graph_stream_manager.cpp | 129 | ||||
-rw-r--r-- | host/lib/rfnoc/link_stream_manager.cpp | 266 |
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); +} |