1 //
2 // Copyright 2019 Ettus Research, a National Instruments Brand
3 //
4 // SPDX-License-Identifier: GPL-3.0-or-later
5 //
6 
7 #include <uhd/exception.hpp>
8 #include <uhd/transport/muxed_zero_copy_if.hpp>
9 #include <uhdlib/rfnoc/chdr_ctrl_endpoint.hpp>
10 #include <uhdlib/rfnoc/link_stream_manager.hpp>
11 #include <uhdlib/rfnoc/mgmt_portal.hpp>
12 #include <boost/format.hpp>
13 
14 using namespace uhd;
15 using namespace uhd::rfnoc;
16 using namespace uhd::transport;
17 using namespace uhd::rfnoc::chdr;
18 using namespace uhd::rfnoc::mgmt;
19 using namespace uhd::rfnoc::detail;
20 
21 constexpr sep_inst_t SEP_INST_MGMT_CTRL = 0;
22 constexpr sep_inst_t SEP_INST_DATA_BASE = 1;
23 
24 constexpr double STREAM_SETUP_TIMEOUT = 0.2;
25 
26 link_stream_manager::~link_stream_manager() = default;
27 
28 class link_stream_manager_impl : public link_stream_manager
29 {
30 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)31     link_stream_manager_impl(const chdr::chdr_packet_factory& pkt_factory,
32         mb_iface& mb_if,
33         const epid_allocator::sptr& epid_alloc,
34         device_id_t device_id)
35         : _pkt_factory(pkt_factory)
36         , _my_device_id(device_id)
37         , _mb_iface(mb_if)
38         , _epid_alloc(epid_alloc)
39         , _data_ep_inst(0)
40     {
41         // Sanity check if we can access our device ID from this motherboard
42         const auto& mb_devs = _mb_iface.get_local_device_ids();
43         if (std::find(mb_devs.begin(), mb_devs.end(), _my_device_id) == mb_devs.end()) {
44             throw uhd::rfnoc_error("The device bound to this link manager cannot be "
45                                    "accessed from this motherboard");
46         }
47 
48         // Sanity check the protocol version and CHDR width
49         if ((_pkt_factory.get_protover() & 0xFF00)
50             != (_mb_iface.get_proto_ver() & 0xFF00)) {
51             throw uhd::rfnoc_error("RFNoC protocol mismatch between SW and HW");
52         }
53         if (_pkt_factory.get_chdr_w() != _mb_iface.get_chdr_w()) {
54             throw uhd::rfnoc_error("RFNoC CHDR width mismatch between SW and HW");
55         }
56 
57         // Create a transport and EPID for management and control traffic
58         _my_mgmt_ctrl_epid =
59             epid_alloc->allocate_epid(sep_addr_t(_my_device_id, SEP_INST_MGMT_CTRL));
60         _allocated_epids.insert(_my_mgmt_ctrl_epid);
61 
62         // Create a muxed transport to share between the mgmt_portal and
63         // chdr_ctrl_endpoint. We have to use the same base transport here to ensure that
64         // the route setup logic in the FPGA transport works correctly.
65         // TODO: This needs to be cleaned up. A muxed_zero_copy_if is excessive here
66         _ctrl_xport = _mb_iface.make_ctrl_transport(_my_device_id, _my_mgmt_ctrl_epid);
67 
68         _my_adapter_id = _mb_iface.get_adapter_id(_my_device_id);
69 
70         // Create management portal using one of the child transports
71         _mgmt_portal = mgmt_portal::make(
72             *_ctrl_xport, _pkt_factory, sep_addr_t(_my_device_id, SEP_INST_MGMT_CTRL));
73     }
74 
~link_stream_manager_impl()75     virtual ~link_stream_manager_impl()
76     {
77         for (const auto& epid : _allocated_epids) {
78             _epid_alloc->deallocate_epid(epid);
79         }
80     }
81 
get_self_device_id() const82     virtual device_id_t get_self_device_id() const
83     {
84         return _my_device_id;
85     }
86 
get_adapter_id() const87     virtual uhd::transport::adapter_id_t get_adapter_id() const
88     {
89         return _my_adapter_id;
90     }
91 
get_reachable_endpoints() const92     virtual const std::set<sep_addr_t>& get_reachable_endpoints() const
93     {
94         return _mgmt_portal->get_reachable_endpoints();
95     }
96 
can_connect_device_to_device(sep_addr_t dst_addr,sep_addr_t src_addr) const97     virtual bool can_connect_device_to_device(
98         sep_addr_t dst_addr, sep_addr_t src_addr) const
99     {
100         return _mgmt_portal->can_remote_route(dst_addr, src_addr);
101     }
102 
connect_host_to_device(sep_addr_t dst_addr)103     virtual sep_id_pair_t connect_host_to_device(sep_addr_t dst_addr)
104     {
105         _ensure_ep_is_reachable(dst_addr);
106 
107         // Allocate EPIDs
108         sep_id_t dst_epid =
109             _epid_alloc->allocate_epid(dst_addr, *_mgmt_portal, *_ctrl_xport);
110 
111         // Make sure that the software side of the endpoint is initialized and reachable
112         if (_ctrl_ep == nullptr) {
113             // Create a control endpoint with that xport
114             _ctrl_ep =
115                 chdr_ctrl_endpoint::make(_ctrl_xport, _pkt_factory, _my_mgmt_ctrl_epid);
116         }
117 
118         // Setup a route to the EPID
119         _mgmt_portal->setup_local_route(*_ctrl_xport, dst_epid);
120         if (!_mgmt_portal->get_endpoint_info(dst_epid).has_ctrl) {
121             throw uhd::rfnoc_error(
122                 "Downstream endpoint does not support control traffic");
123         }
124 
125         // Create a client zero instance
126         if (_client_zero_map.count(dst_epid) == 0) {
127             _client_zero_map.insert(
128                 std::make_pair(dst_epid, client_zero::make(*_ctrl_ep, dst_epid)));
129         }
130         return sep_id_pair_t(_my_mgmt_ctrl_epid, dst_epid);
131     }
132 
connect_device_to_device(sep_addr_t dst_addr,sep_addr_t src_addr)133     virtual sep_id_pair_t connect_device_to_device(
134         sep_addr_t dst_addr, sep_addr_t src_addr)
135     {
136         _ensure_ep_is_reachable(dst_addr);
137         _ensure_ep_is_reachable(src_addr);
138 
139         // Allocate EPIDs and initialize endpoints
140         sep_id_t dst_epid =
141             _epid_alloc->allocate_epid(dst_addr, *_mgmt_portal, *_ctrl_xport);
142         sep_id_t src_epid =
143             _epid_alloc->allocate_epid(src_addr, *_mgmt_portal, *_ctrl_xport);
144 
145         // Set up routes
146         _mgmt_portal->setup_remote_route(*_ctrl_xport, dst_epid, src_epid);
147 
148         return sep_id_pair_t(src_epid, dst_epid);
149     }
150 
get_block_register_iface(sep_id_t dst_epid,uint16_t block_index,const clock_iface & client_clk,const clock_iface & timebase_clk)151     virtual ctrlport_endpoint::sptr get_block_register_iface(sep_id_t dst_epid,
152         uint16_t block_index,
153         const clock_iface& client_clk,
154         const clock_iface& timebase_clk)
155     {
156         // Ensure that the endpoint is initialized for control at the specified EPID
157         if (_ctrl_ep == nullptr) {
158             throw uhd::runtime_error("Software endpoint not initialized for control");
159         }
160         if (_client_zero_map.count(dst_epid) == 0) {
161             throw uhd::runtime_error(
162                 "Control for the specified EPID was not initialized");
163         }
164         const client_zero::sptr& c0_ctrl = _client_zero_map.at(dst_epid);
165         const uint16_t block_slot = 1 + c0_ctrl->get_num_stream_endpoints() + block_index;
166         if (block_index >= c0_ctrl->get_num_blocks()) {
167             throw uhd::value_error("Requested block index out of range");
168         }
169 
170         // Create control endpoint
171         return _ctrl_ep->get_ctrlport_ep(dst_epid,
172             c0_ctrl->get_ctrl_xbar_port(block_index),
173             (size_t(1) << c0_ctrl->get_block_info(block_slot).ctrl_fifo_size),
174             c0_ctrl->get_block_info(block_slot).ctrl_max_async_msgs,
175             client_clk,
176             timebase_clk);
177     }
178 
get_client_zero(sep_id_t dst_epid) const179     virtual client_zero::sptr get_client_zero(sep_id_t dst_epid) const
180     {
181         if (_client_zero_map.count(dst_epid) == 0) {
182             throw uhd::runtime_error(
183                 "Control for the specified EPID was not initialized");
184         }
185         return _client_zero_map.at(dst_epid);
186     }
187 
create_device_to_device_data_stream(const sep_id_t & dst_epid,const sep_id_t & src_epid,const bool lossy_xport,const double fc_freq_ratio,const double fc_headroom_ratio,const bool reset=false)188     virtual stream_buff_params_t create_device_to_device_data_stream(
189         const sep_id_t& dst_epid,
190         const sep_id_t& src_epid,
191         const bool lossy_xport,
192         const double fc_freq_ratio,
193         const double fc_headroom_ratio,
194         const bool reset = false)
195     {
196         // We assume that the devices are already connected (because this API requires
197         // EPIDs)
198 
199         // Setup a stream
200         stream_buff_params_t buff_params =
201             _mgmt_portal->config_remote_stream(*_ctrl_xport,
202                 dst_epid,
203                 src_epid,
204                 lossy_xport,
205                 stream_buff_params_t{1, 1}, // Dummy frequency
206                 stream_buff_params_t{0, 0}, // Dummy headroom
207                 false,
208                 STREAM_SETUP_TIMEOUT);
209 
210         // Reconfigure flow control using the new frequency and headroom
211         return _mgmt_portal->config_remote_stream(*_ctrl_xport,
212             dst_epid,
213             src_epid,
214             lossy_xport,
215             _get_buff_params_ratio(buff_params, fc_freq_ratio),
216             _get_buff_params_ratio(buff_params, fc_headroom_ratio),
217             reset,
218             STREAM_SETUP_TIMEOUT);
219     }
220 
create_host_to_device_data_stream(const sep_addr_t dst_addr,const sw_buff_t pyld_buff_fmt,const sw_buff_t mdata_buff_fmt,const device_addr_t & xport_args,const std::string & streamer_id)221     virtual chdr_tx_data_xport::uptr create_host_to_device_data_stream(
222         const sep_addr_t dst_addr,
223         const sw_buff_t pyld_buff_fmt,
224         const sw_buff_t mdata_buff_fmt,
225         const device_addr_t& xport_args,
226         const std::string& streamer_id)
227     {
228         _ensure_ep_is_reachable(dst_addr);
229 
230         // Generate a new destination (device) EPID instance
231         sep_id_t dst_epid =
232             _epid_alloc->allocate_epid(dst_addr, *_mgmt_portal, *_ctrl_xport);
233 
234         if (!_mgmt_portal->get_endpoint_info(dst_epid).has_data) {
235             throw uhd::rfnoc_error("Downstream endpoint does not support data traffic");
236         }
237 
238         // Create a new destination (host) endpoint and EPID
239         sep_addr_t sw_epid_addr(_my_device_id, SEP_INST_DATA_BASE + (_data_ep_inst++));
240         sep_id_t src_epid = _epid_alloc->allocate_epid(sw_epid_addr);
241         _allocated_epids.insert(src_epid);
242 
243         return _mb_iface.make_tx_data_transport(*_mgmt_portal,
244             {sw_epid_addr, dst_addr},
245             {src_epid, dst_epid},
246             pyld_buff_fmt,
247             mdata_buff_fmt,
248             xport_args,
249             streamer_id);
250     }
251 
create_device_to_host_data_stream(sep_addr_t src_addr,const sw_buff_t pyld_buff_fmt,const sw_buff_t mdata_buff_fmt,const device_addr_t & xport_args,const std::string & streamer_id)252     virtual chdr_rx_data_xport::uptr create_device_to_host_data_stream(
253         sep_addr_t src_addr,
254         const sw_buff_t pyld_buff_fmt,
255         const sw_buff_t mdata_buff_fmt,
256         const device_addr_t& xport_args,
257         const std::string& streamer_id)
258     {
259         _ensure_ep_is_reachable(src_addr);
260 
261         // Generate a new source (device) EPID instance
262         sep_id_t src_epid =
263             _epid_alloc->allocate_epid(src_addr, *_mgmt_portal, *_ctrl_xport);
264 
265         if (!_mgmt_portal->get_endpoint_info(src_epid).has_data) {
266             throw uhd::rfnoc_error("Downstream endpoint does not support data traffic");
267         }
268 
269         // Create a new destination (host) endpoint and EPID
270         sep_addr_t sw_epid_addr(_my_device_id, SEP_INST_DATA_BASE + (_data_ep_inst++));
271         sep_id_t dst_epid = _epid_alloc->allocate_epid(sw_epid_addr);
272         _allocated_epids.insert(dst_epid);
273 
274         return _mb_iface.make_rx_data_transport(*_mgmt_portal,
275             {src_addr, sw_epid_addr},
276             {src_epid, dst_epid},
277             pyld_buff_fmt,
278             mdata_buff_fmt,
279             xport_args,
280             streamer_id);
281     }
282 
283 private:
_ensure_ep_is_reachable(const sep_addr_t & ep_addr_)284     void _ensure_ep_is_reachable(const sep_addr_t& ep_addr_)
285     {
286         for (const auto& ep_addr : _mgmt_portal->get_reachable_endpoints()) {
287             if (ep_addr == ep_addr_)
288                 return;
289         }
290         throw uhd::routing_error("Specified endpoint is not reachable");
291     }
292 
_get_buff_params_ratio(const stream_buff_params_t & buff_params,const double ratio)293     stream_buff_params_t _get_buff_params_ratio(
294         const stream_buff_params_t& buff_params, const double ratio)
295     {
296         return {static_cast<uint64_t>(std::ceil(double(buff_params.bytes) * ratio)),
297             static_cast<uint32_t>(std::ceil(double(buff_params.packets) * ratio))};
298     }
299 
300     // A reference to the packet factory
301     const chdr::chdr_packet_factory& _pkt_factory;
302     // The device address of this software endpoint
303     const device_id_t _my_device_id;
304     // The host adapter ID associated with this software endpoint
305     adapter_id_t _my_adapter_id;
306 
307     // Motherboard interface
308     mb_iface& _mb_iface;
309     // A pointer to the EPID allocator
310     epid_allocator::sptr _epid_alloc;
311     // A set of all allocated EPIDs
312     std::set<sep_id_t> _allocated_epids;
313     // The software EPID for all management and control traffic
314     sep_id_t _my_mgmt_ctrl_epid;
315     // Transports
316     chdr_ctrl_xport::sptr _ctrl_xport;
317     // Management portal for control endpoints
318     mgmt_portal::uptr _mgmt_portal;
319     // The CHDR control endpoint
320     chdr_ctrl_endpoint::uptr _ctrl_ep;
321     // A map of all client zero instances indexed by the destination
322     std::map<sep_id_t, client_zero::sptr> _client_zero_map;
323     // Data endpoint instance
324     sep_inst_t _data_ep_inst;
325 };
326 
make(const chdr::chdr_packet_factory & pkt_factory,mb_iface & mb_if,const epid_allocator::sptr & epid_alloc,device_id_t device_id)327 link_stream_manager::uptr link_stream_manager::make(
328     const chdr::chdr_packet_factory& pkt_factory,
329     mb_iface& mb_if,
330     const epid_allocator::sptr& epid_alloc,
331     device_id_t device_id)
332 {
333     return std::make_unique<link_stream_manager_impl>(
334         pkt_factory, mb_if, epid_alloc, device_id);
335 }
336