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