diff options
Diffstat (limited to 'host/lib/rfnoc')
-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 |
5 files changed, 462 insertions, 0 deletions
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); +} |