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/rfnoc/ddc_block_control.hpp>
9 #include <uhd/rfnoc/duc_block_control.hpp>
10 #include <uhd/rfnoc/filter_node.hpp>
11 #include <uhd/rfnoc/graph_edge.hpp>
12 #include <uhd/rfnoc/radio_control.hpp>
13 #include <uhd/rfnoc_graph.hpp>
14 #include <uhd/types/device_addr.hpp>
15 #include <uhd/usrp/multi_usrp.hpp>
16 #include <uhd/utils/graph_utils.hpp>
17 #include <uhdlib/rfnoc/rfnoc_device.hpp>
18 #include <uhdlib/rfnoc/rfnoc_rx_streamer.hpp>
19 #include <uhdlib/rfnoc/rfnoc_tx_streamer.hpp>
20 #include <uhdlib/usrp/gpio_defs.hpp>
21 #include <uhdlib/utils/narrow.hpp>
22 #include <unordered_set>
23 #include <boost/format.hpp>
24 #include <algorithm>
25 #include <chrono>
26 #include <memory>
27 #include <mutex>
28 #include <thread>
29 #include <vector>
30 
31 using namespace std::chrono_literals;
32 using namespace uhd;
33 using namespace uhd::usrp;
34 using namespace uhd::rfnoc;
35 
36 //! Fan out (mux) an API call that is for all channels or all motherboards
37 #define MUX_API_CALL(max_index, api_call, mux_var, mux_cond, ...)  \
38     if (mux_var == mux_cond) {                                     \
39         for (size_t __index = 0; __index < max_index; ++__index) { \
40             api_call(__VA_ARGS__, __index);                        \
41         }                                                          \
42         return;                                                    \
43     }
44 
45 //! Fan out (mux) an RX-specific API call that is for all channels
46 #define MUX_RX_API_CALL(api_call, ...) \
47     MUX_API_CALL(get_rx_num_channels(), api_call, chan, ALL_CHANS, __VA_ARGS__)
48 //! Fan out (mux) an TX-specific API call that is for all channels
49 #define MUX_TX_API_CALL(api_call, ...) \
50     MUX_API_CALL(get_tx_num_channels(), api_call, chan, ALL_CHANS, __VA_ARGS__)
51 //! Fan out (mux) a motherboard-specific API call that is for all boards
52 #define MUX_MB_API_CALL(api_call, ...) \
53     MUX_API_CALL(get_num_mboards(), api_call, mboard, ALL_MBOARDS, __VA_ARGS__)
54 
55 
56 namespace {
57 constexpr char DEFAULT_CPU_FORMAT[] = "fc32";
58 constexpr char DEFAULT_OTW_FORMAT[] = "sc16";
59 constexpr double RX_SIGN            = +1.0;
60 constexpr double TX_SIGN            = -1.0;
61 constexpr char LOG_ID[]             = "MULTI_USRP";
62 
63 //! A faux container for a UHD device
64 //
65 // Note that multi_usrp_rfnoc no longer gives access to the underlying device
66 // class. Legacy code might use multi_usrp->get_device()->get_tree() or
67 // similar functionalities; these can be faked with this redirector class.
68 //
69 // The only exception is recv_async_msg(), which depends on the streamer. It
70 // will print a warning once, and will attempt to access a Tx streamer if it
71 // has access to a Tx streamer. If there is only ever one Tx streamer, this will
72 // work as expected. For multiple streamers, only the last streamer's async
73 // messages will make it through.
74 class redirector_device : public uhd::device
75 {
76 public:
redirector_device(multi_usrp * musrp_ptr)77     redirector_device(multi_usrp* musrp_ptr) : _musrp(musrp_ptr) {}
78 
get_rx_stream(const stream_args_t & args)79     rx_streamer::sptr get_rx_stream(const stream_args_t& args)
80     {
81         return _musrp->get_rx_stream(args);
82     }
83 
get_tx_stream(const stream_args_t & args)84     tx_streamer::sptr get_tx_stream(const stream_args_t& args)
85     {
86         auto streamer     = _musrp->get_tx_stream(args);
87         _last_tx_streamer = streamer;
88         return streamer;
89     }
90 
recv_async_msg(async_metadata_t & md,double timeout)91     bool recv_async_msg(async_metadata_t& md, double timeout)
92     {
93         std::call_once(_async_warning_flag, []() {
94             UHD_LOG_WARNING(LOG_ID,
95                 "Calling multi_usrp::recv_async_msg() is deprecated and can lead to "
96                 "unexpected behaviour. Prefer calling tx_stream::recv_async_msg().");
97         });
98         auto streamer = _last_tx_streamer.lock();
99         if (streamer) {
100             return streamer->recv_async_msg(md, timeout);
101         }
102         return false;
103     }
104 
get_tree(void) const105     uhd::property_tree::sptr get_tree(void) const
106     {
107         return _musrp->get_tree();
108     }
109 
get_device_type() const110     device_filter_t get_device_type() const
111     {
112         return USRP;
113     }
114 
set_tx_stream(tx_streamer::sptr streamer)115     void set_tx_stream(tx_streamer::sptr streamer)
116     {
117         _last_tx_streamer = streamer;
118     }
119 
120 private:
121     std::once_flag _async_warning_flag;
122     std::weak_ptr<tx_streamer> _last_tx_streamer;
123     multi_usrp* _musrp;
124 };
125 
126 /*! Make sure the stream args are valid and can be used by get_tx_stream()
127  * and get_rx_stream().
128  *
129  */
sanitize_stream_args(const stream_args_t args_)130 stream_args_t sanitize_stream_args(const stream_args_t args_)
131 {
132     stream_args_t args = args_;
133     if (args.cpu_format.empty()) {
134         UHD_LOG_DEBUG("MULTI_USRP",
135             "get_xx_stream(): cpu_format not specified, defaulting to "
136                 << DEFAULT_CPU_FORMAT);
137         args.cpu_format = DEFAULT_CPU_FORMAT;
138     }
139     if (args.otw_format.empty()) {
140         UHD_LOG_DEBUG("MULTI_USRP",
141             "get_xx_stream(): otw_format not specified, defaulting to "
142                 << DEFAULT_OTW_FORMAT);
143         args.otw_format = DEFAULT_OTW_FORMAT;
144     }
145     if (args.channels.empty()) {
146         UHD_LOG_DEBUG(
147             "MULTI_USRP", "get_xx_stream(): channels not specified, defaulting to [0]");
148         args.channels = {0};
149     }
150     return args;
151 }
152 
bytes_to_str(std::vector<uint8_t> str_b)153 std::string bytes_to_str(std::vector<uint8_t> str_b)
154 {
155     return std::string(str_b.cbegin(), str_b.cend());
156 }
157 
158 } // namespace
159 
160 class multi_usrp_rfnoc : public multi_usrp,
161                          public std::enable_shared_from_this<multi_usrp_rfnoc>
162 {
163 public:
164     struct rx_chan_t
165     {
166         radio_control::sptr radio;
167         ddc_block_control::sptr ddc; // can be nullptr
168         size_t block_chan;
169         std::vector<graph_edge_t> edge_list;
170     };
171 
172     struct tx_chan_t
173     {
174         radio_control::sptr radio;
175         duc_block_control::sptr duc; // can be nullptr
176         size_t block_chan;
177         std::vector<graph_edge_t> edge_list;
178     };
179 
180     /**************************************************************************
181      * Structors
182      *************************************************************************/
multi_usrp_rfnoc(rfnoc_graph::sptr graph,const device_addr_t & addr)183     multi_usrp_rfnoc(rfnoc_graph::sptr graph, const device_addr_t& addr)
184         : _args(addr)
185         , _graph(graph)
186         , _tree(_graph->get_tree())
187         , _device(std::make_shared<redirector_device>(this))
188     {
189         // Discover all of the radios on our devices and create a mapping between
190         // radio chains and channel numbers. The result is sorted.
191         auto radio_blk_ids = _graph->find_blocks("Radio");
192         // If we don't find any radios, we don't have a multi_usrp object
193         if (radio_blk_ids.empty()) {
194             throw uhd::runtime_error(
195                 "[multi_usrp] No radios found in connected devices.");
196         }
197         // Next, we assign block controllers to RX channels
198         // Note that we don't want to connect blocks now; we will wait until we create and
199         // connect a streamer. This gives us a little more time to figure out the desired
200         // values of our properties (such as master clock)
201         size_t musrp_rx_channel = 0;
202         size_t musrp_tx_channel = 0;
203         for (auto radio_id : radio_blk_ids) {
204             auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
205             for (size_t block_chan = 0; block_chan < radio_blk->get_num_output_ports();
206                  ++block_chan) {
207                 // Create the RX chan
208                 uhd::usrp::subdev_spec_t rx_radio_subdev;
209                 rx_radio_subdev.push_back(uhd::usrp::subdev_spec_pair_t(
210                     radio_blk->get_slot_name(),
211                     radio_blk->get_dboard_fe_from_chan(block_chan, uhd::RX_DIRECTION)));
212                 auto rx_chans =
213                     _generate_mboard_rx_chans(rx_radio_subdev, radio_id.get_device_no());
214                 // TODO: we're passing the same info around here; there has to be a
215                 // cleaner way
216                 for (auto rx_chan : rx_chans) {
217                     _rx_chans.emplace(musrp_rx_channel, rx_chan);
218                     ++musrp_rx_channel; // Increment after logging so we print the correct
219                                         // value
220                 }
221             }
222             for (size_t block_chan = 0; block_chan < radio_blk->get_num_input_ports();
223                  ++block_chan) {
224                 // Create the TX chan
225                 uhd::usrp::subdev_spec_t tx_radio_subdev;
226                 tx_radio_subdev.push_back(uhd::usrp::subdev_spec_pair_t(
227                     radio_blk->get_slot_name(),
228                     radio_blk->get_dboard_fe_from_chan(block_chan, uhd::TX_DIRECTION)));
229                 auto tx_chans =
230                     _generate_mboard_tx_chans(tx_radio_subdev, radio_id.get_device_no());
231                 // TODO: we're passing the same info around here; there has to be a
232                 // cleaner way
233                 for (auto tx_chan : tx_chans) {
234                     _tx_chans.emplace(musrp_tx_channel, tx_chan);
235                     ++musrp_tx_channel; // Increment after logging so we print the correct
236                                         // value
237                 }
238             }
239         }
240         // Manually propagate radio block sample rates to DDC/DUC blocks in order to allow
241         // DDC/DUC blocks to have valid internal state before graph is (later) connected
242         for (size_t rx_chan = 0; rx_chan < get_rx_num_channels(); ++rx_chan) {
243             auto& rx_chain = _get_rx_chan(rx_chan);
244             if (rx_chain.ddc) {
245                 rx_chain.ddc->set_input_rate(
246                     rx_chain.radio->get_rate(), rx_chain.block_chan);
247             }
248         }
249         for (size_t tx_chan = 0; tx_chan < get_tx_num_channels(); ++tx_chan) {
250             auto& tx_chain = _get_tx_chan(tx_chan);
251             if (tx_chain.duc) {
252                 tx_chain.duc->set_output_rate(
253                     tx_chain.radio->get_rate(), tx_chain.block_chan);
254             }
255         }
256         _graph->commit();
257     }
258 
~multi_usrp_rfnoc()259     ~multi_usrp_rfnoc()
260     {
261         // nop
262     }
263 
get_device(void)264     device::sptr get_device(void)
265     {
266         return _device;
267     }
268 
get_tree() const269     uhd::property_tree::sptr get_tree() const
270     {
271         return _tree;
272     }
273 
get_rx_stream(const stream_args_t & args_)274     rx_streamer::sptr get_rx_stream(const stream_args_t& args_)
275     {
276         std::lock_guard<std::recursive_mutex> l(_graph_mutex);
277         stream_args_t args = sanitize_stream_args(args_);
278         double rate        = 1.0;
279 
280         // Note that we don't release the graph, which means that property
281         // propagation is possible. This is necessary so we don't disrupt
282         // existing streamers. We use the _graph_mutex to try and avoid any
283         // property propagation where possible.
284 
285         // Connect the chains
286         _connect_rx_chains(args.channels);
287 
288         // Create the streamer
289         // The disconnect callback must disconnect the entire chain because the radio
290         // relies on the connections to determine what is enabled.
291         auto this_multi_usrp = shared_from_this();
292         auto rx_streamer     = std::make_shared<rfnoc_rx_streamer>(args.channels.size(),
293             args,
294             [channels = args.channels, this_multi_usrp](const std::string& id) {
295                 this_multi_usrp->_graph->disconnect(id);
296                 this_multi_usrp->_disconnect_rx_chains(channels);
297             });
298 
299         // Connect the streamer
300         for (size_t strm_port = 0; strm_port < args.channels.size(); ++strm_port) {
301             auto rx_channel = args.channels.at(strm_port);
302             auto rx_chain   = _get_rx_chan(rx_channel);
303             UHD_LOG_TRACE("MULTI_USRP",
304                 "Connecting " << rx_chain.edge_list.back().src_blockid << ":"
305                               << rx_chain.edge_list.back().src_port
306                               << " -> RxStreamer:" << strm_port);
307             _graph->connect(rx_chain.edge_list.back().src_blockid,
308                 rx_chain.edge_list.back().src_port,
309                 rx_streamer,
310                 strm_port);
311             const double chan_rate =
312                 _rx_rates.count(rx_channel) ? _rx_rates.at(rx_channel) : 1.0;
313             if (chan_rate > 1.0 && rate != chan_rate) {
314                 if (rate > 1.0) {
315                     UHD_LOG_DEBUG("MULTI_USRP",
316                         "Inconsistent RX rates when creating streamer! "
317                         "Harmonizing to "
318                             << chan_rate);
319                 }
320                 rate = chan_rate;
321             }
322         }
323 
324         // Now everything is connected, commit() again so we can have stream
325         // commands go through the graph
326         _graph->commit();
327 
328         // Before we return the streamer, we may need to reapply the rate. This
329         // is necessary whenever the blocks were configured before the streamer
330         // was created, because we don't know what state the graph is in after
331         // commit() was called in that case..
332         if (rate > 1.0) {
333             UHD_LOG_TRACE("MULTI_USRP",
334                 "Now reapplying RX rate " << (rate / 1e6)
335                                           << " MHz to all streamer channels");
336             for (auto rx_channel : args.channels) {
337                 auto rx_chain = _get_rx_chan(rx_channel);
338                 if (rx_chain.ddc) {
339                     rx_chain.ddc->set_output_rate(rate, rx_chain.block_chan);
340                 } else {
341                     rx_chain.radio->set_rate(rate);
342                 }
343             }
344         }
345 
346         return rx_streamer;
347     }
348 
get_tx_stream(const stream_args_t & args_)349     tx_streamer::sptr get_tx_stream(const stream_args_t& args_)
350     {
351         std::lock_guard<std::recursive_mutex> l(_graph_mutex);
352         stream_args_t args = sanitize_stream_args(args_);
353         double rate        = 1.0;
354 
355         // Note that we don't release the graph, which means that property
356         // propagation is possible. This is necessary so we don't disrupt
357         // existing streamers. We use the _graph_mutex to try and avoid any
358         // property propagation where possible.
359 
360         // Connect the chains
361         _connect_tx_chains(args.channels);
362 
363         // Create a streamer
364         // The disconnect callback must disconnect the entire chain because the radio
365         // relies on the connections to determine what is enabled.
366         auto this_multi_usrp = shared_from_this();
367         auto tx_streamer     = std::make_shared<rfnoc_tx_streamer>(args.channels.size(),
368             args,
369             [channels = args.channels, this_multi_usrp](const std::string& id) {
370                 this_multi_usrp->_graph->disconnect(id);
371                 this_multi_usrp->_disconnect_tx_chains(channels);
372             });
373 
374         // Connect the streamer
375         for (size_t strm_port = 0; strm_port < args.channels.size(); ++strm_port) {
376             auto tx_channel = args.channels.at(strm_port);
377             auto tx_chain   = _get_tx_chan(tx_channel);
378             UHD_LOG_TRACE("MULTI_USRP",
379                 "Connecting TxStreamer:" << strm_port << " -> "
380                                          << tx_chain.edge_list.back().dst_blockid << ":"
381                                          << tx_chain.edge_list.back().dst_port);
382             _graph->connect(tx_streamer,
383                 strm_port,
384                 tx_chain.edge_list.back().dst_blockid,
385                 tx_chain.edge_list.back().dst_port);
386             const double chan_rate =
387                 _tx_rates.count(tx_channel) ? _tx_rates.at(tx_channel) : 1.0;
388             if (chan_rate > 1.0 && rate != chan_rate) {
389                 UHD_LOG_DEBUG("MULTI_USRP",
390                     "Inconsistent TX rates when creating streamer! Harmonizing "
391                     "to "
392                         << chan_rate);
393                 rate = chan_rate;
394             }
395         }
396         // Now everything is connected, commit() again so we can have stream
397         // commands go through the graph
398         _graph->commit();
399 
400         // Before we return the streamer, we may need to reapply the rate. This
401         // is necessary whenever the blocks were configured before the streamer
402         // was created, because we don't know what state the graph is in after
403         // commit() was called in that case, or we could have configured blocks
404         // to run at different rates (see the warning above).
405         if (rate > 1.0) {
406             UHD_LOG_TRACE("MULTI_USRP",
407                 "Now reapplying TX rate " << (rate / 1e6)
408                                           << " MHz to all streamer channels");
409             for (auto tx_channel : args.channels) {
410                 auto tx_chain = _get_tx_chan(tx_channel);
411                 if (tx_chain.duc) {
412                     tx_chain.duc->set_input_rate(rate, tx_chain.block_chan);
413                 } else {
414                     tx_chain.radio->set_rate(rate);
415                 }
416             }
417         }
418 
419         // For legacy purposes: This enables recv_async_msg(), which is considered
420         // deprecated, but as long as it's there, we need this to approximate
421         // previous behaviour.
422         _device->set_tx_stream(tx_streamer);
423 
424         return tx_streamer;
425     }
426 
427 
428     /***********************************************************************
429      * Helper methods
430      **********************************************************************/
431     /*! The CORDIC can be used to shift the baseband below / past the tunable
432      * limits of the actual RF front-end. The baseband filter, located on the
433      * daughterboard, however, limits the useful instantaneous bandwidth. We
434      * allow the user to tune to the edge of the filter, where the roll-off
435      * begins.  This prevents the user from tuning past the point where less
436      * than half of the spectrum would be useful.
437      */
make_overall_tune_range(const meta_range_t & fe_range,const meta_range_t & dsp_range,const double bw)438     static meta_range_t make_overall_tune_range(
439         const meta_range_t& fe_range, const meta_range_t& dsp_range, const double bw)
440     {
441         meta_range_t range;
442         for (const range_t& sub_range : fe_range) {
443             range.push_back(
444                 range_t(sub_range.start() + std::max(dsp_range.start(), -bw / 2),
445                     sub_range.stop() + std::min(dsp_range.stop(), bw / 2),
446                     dsp_range.step()));
447         }
448         return range;
449     }
450 
get_usrp_rx_info(size_t chan)451     dict<std::string, std::string> get_usrp_rx_info(size_t chan)
452     {
453         auto& rx_chain      = _get_rx_chan(chan);
454         const size_t mb_idx = rx_chain.radio->get_block_id().get_device_no();
455         auto mbc            = get_mbc(mb_idx);
456         auto mb_eeprom      = mbc->get_eeprom();
457 
458         dict<std::string, std::string> usrp_info;
459         usrp_info["mboard_id"]      = mbc->get_mboard_name();
460         usrp_info["mboard_name"]    = mb_eeprom.get("name", "n/a");
461         usrp_info["mboard_serial"]  = mb_eeprom.get("serial", "n/a");
462         usrp_info["rx_subdev_name"] = get_rx_subdev_name(chan);
463         usrp_info["rx_subdev_spec"] = get_rx_subdev_spec(mb_idx).to_string();
464         usrp_info["rx_antenna"]     = get_rx_antenna(chan);
465 
466         const auto db_eeprom = rx_chain.radio->get_db_eeprom();
467         usrp_info["rx_serial"] =
468             db_eeprom.count("rx_serial")
469                 ? bytes_to_str(db_eeprom.at("rx_serial"))
470                 : db_eeprom.count("serial") ? bytes_to_str(db_eeprom.at("serial")) : "";
471         usrp_info["rx_id"] =
472             db_eeprom.count("rx_id")
473                 ? bytes_to_str(db_eeprom.at("rx_id"))
474                 : db_eeprom.count("pid") ? bytes_to_str(db_eeprom.at("pid")) : "";
475 
476         const auto rx_power_ref_keys = rx_chain.radio->get_rx_power_ref_keys();
477         if (!rx_power_ref_keys.empty() && rx_power_ref_keys.size() == 2) {
478             usrp_info["rx_ref_power_key"]    = rx_power_ref_keys.at(0);
479             usrp_info["rx_ref_power_serial"] = rx_power_ref_keys.at(1);
480         }
481 
482         return usrp_info;
483     }
484 
get_usrp_tx_info(size_t chan)485     dict<std::string, std::string> get_usrp_tx_info(size_t chan)
486     {
487         auto& tx_chain      = _get_tx_chan(chan);
488         const size_t mb_idx = tx_chain.radio->get_block_id().get_device_no();
489         auto mbc            = get_mbc(mb_idx);
490         auto mb_eeprom      = mbc->get_eeprom();
491 
492         dict<std::string, std::string> usrp_info;
493         usrp_info["mboard_id"]      = mbc->get_mboard_name();
494         usrp_info["mboard_name"]    = mb_eeprom.get("name", "n/a");
495         usrp_info["mboard_serial"]  = mb_eeprom.get("serial", "n/a");
496         usrp_info["tx_subdev_name"] = get_tx_subdev_name(chan);
497         usrp_info["tx_subdev_spec"] = get_tx_subdev_spec(mb_idx).to_string();
498         usrp_info["tx_antenna"]     = get_tx_antenna(chan);
499 
500         const auto db_eeprom = tx_chain.radio->get_db_eeprom();
501         usrp_info["tx_serial"] =
502             db_eeprom.count("tx_serial")
503                 ? bytes_to_str(db_eeprom.at("tx_serial"))
504                 : db_eeprom.count("serial") ? bytes_to_str(db_eeprom.at("serial")) : "";
505         usrp_info["tx_id"] =
506             db_eeprom.count("tx_id")
507                 ? bytes_to_str(db_eeprom.at("tx_id"))
508                 : db_eeprom.count("pid") ? bytes_to_str(db_eeprom.at("pid")) : "";
509 
510         const auto tx_power_ref_keys = tx_chain.radio->get_tx_power_ref_keys();
511         if (!tx_power_ref_keys.empty() && tx_power_ref_keys.size() == 2) {
512             usrp_info["tx_ref_power_key"]    = tx_power_ref_keys.at(0);
513             usrp_info["tx_ref_power_serial"] = tx_power_ref_keys.at(1);
514         }
515 
516         return usrp_info;
517     }
518 
519     /*! Tune the appropriate radio chain to the requested frequency.
520      *  The general algorithm is the same for RX and TX, so we can pass in lambdas to do
521      * the setting/getting for us.
522      */
tune_xx_subdev_and_dsp(const double xx_sign,freq_range_t tune_range,freq_range_t rf_freq_range,freq_range_t dsp_freq_range,std::function<void (double)> set_rf_freq,std::function<double ()> get_rf_freq,std::function<void (double)> set_dsp_freq,std::function<double ()> get_dsp_freq,const tune_request_t & tune_request)523     tune_result_t tune_xx_subdev_and_dsp(const double xx_sign,
524         freq_range_t tune_range,
525         freq_range_t rf_freq_range,
526         freq_range_t dsp_freq_range,
527         std::function<void(double)> set_rf_freq,
528         std::function<double()> get_rf_freq,
529         std::function<void(double)> set_dsp_freq,
530         std::function<double()> get_dsp_freq,
531         const tune_request_t& tune_request)
532     {
533         double clipped_requested_freq = tune_range.clip(tune_request.target_freq);
534         UHD_LOGGER_TRACE("MULTI_USRP")
535             << boost::format("Frequency Range %.3fMHz->%.3fMHz")
536                    % (tune_range.start() / 1e6) % (tune_range.stop() / 1e6);
537         UHD_LOGGER_TRACE("MULTI_USRP")
538             << "Clipped RX frequency requested: "
539                    + std::to_string(clipped_requested_freq / 1e6) + "MHz";
540 
541         //------------------------------------------------------------------
542         //-- set the RF frequency depending upon the policy
543         //------------------------------------------------------------------
544         double target_rf_freq = 0.0;
545         switch (tune_request.rf_freq_policy) {
546             case tune_request_t::POLICY_AUTO:
547                 target_rf_freq = clipped_requested_freq;
548                 break;
549 
550             case tune_request_t::POLICY_MANUAL:
551                 target_rf_freq = rf_freq_range.clip(tune_request.rf_freq);
552                 break;
553 
554             case tune_request_t::POLICY_NONE:
555                 break; // does not set
556         }
557         UHD_LOGGER_TRACE("MULTI_USRP")
558             << "Target RF Freq: " + std::to_string(target_rf_freq / 1e6) + "MHz";
559 
560         //------------------------------------------------------------------
561         //-- Tune the RF frontend
562         //------------------------------------------------------------------
563         if (tune_request.rf_freq_policy != tune_request_t::POLICY_NONE) {
564             set_rf_freq(target_rf_freq);
565         }
566         const double actual_rf_freq = get_rf_freq();
567 
568         //------------------------------------------------------------------
569         //-- Set the DSP frequency depending upon the DSP frequency policy.
570         //------------------------------------------------------------------
571         double target_dsp_freq = 0.0;
572         switch (tune_request.dsp_freq_policy) {
573             case tune_request_t::POLICY_AUTO:
574                 /* If we are using the AUTO tuning policy, then we prevent the
575                  * CORDIC from spinning us outside of the range of the baseband
576                  * filter, regardless of what the user requested. This could happen
577                  * if the user requested a center frequency so far outside of the
578                  * tunable range of the FE that the CORDIC would spin outside the
579                  * filtered baseband. */
580                 target_dsp_freq = actual_rf_freq - clipped_requested_freq;
581 
582                 // invert the sign on the dsp freq for transmit (spinning up vs down)
583                 target_dsp_freq *= xx_sign;
584 
585                 break;
586 
587             case tune_request_t::POLICY_MANUAL:
588                 /* If the user has specified a manual tune policy, we will allow
589                  * tuning outside of the baseband filter, but will still clip the
590                  * target DSP frequency to within the bounds of the CORDIC to
591                  * prevent undefined behavior (likely an overflow). */
592                 target_dsp_freq = dsp_freq_range.clip(tune_request.dsp_freq);
593                 break;
594 
595             case tune_request_t::POLICY_NONE:
596                 break; // does not set
597         }
598         UHD_LOGGER_TRACE("MULTI_USRP")
599             << "Target DSP Freq: " + std::to_string(target_dsp_freq / 1e6) + "MHz";
600 
601         //------------------------------------------------------------------
602         //-- Tune the DSP
603         //------------------------------------------------------------------
604         if (tune_request.dsp_freq_policy != tune_request_t::POLICY_NONE) {
605             set_dsp_freq(target_dsp_freq);
606         }
607         const double actual_dsp_freq = get_dsp_freq();
608 
609         //------------------------------------------------------------------
610         //-- Load and return the tune result
611         //------------------------------------------------------------------
612         tune_result_t tune_result;
613         tune_result.clipped_rf_freq = clipped_requested_freq;
614         tune_result.target_rf_freq  = target_rf_freq;
615         tune_result.actual_rf_freq  = actual_rf_freq;
616         tune_result.target_dsp_freq = target_dsp_freq;
617         tune_result.actual_dsp_freq = actual_dsp_freq;
618         return tune_result;
619     }
620 
621     /*******************************************************************
622      * Mboard methods
623      ******************************************************************/
set_master_clock_rate(double rate,size_t mboard)624     void set_master_clock_rate(double rate, size_t mboard)
625     {
626         for (auto& chain : _rx_chans) {
627             auto radio = chain.second.radio;
628             if (radio->get_block_id().get_device_no() == mboard
629                 || mboard == ALL_MBOARDS) {
630                 radio->set_rate(rate);
631             }
632         }
633         for (auto& chain : _tx_chans) {
634             auto radio = chain.second.radio;
635             if (radio->get_block_id().get_device_no() == mboard
636                 || mboard == ALL_MBOARDS) {
637                 radio->set_rate(rate);
638             }
639         }
640     }
641 
get_master_clock_rate(size_t mboard)642     double get_master_clock_rate(size_t mboard)
643     {
644         // We pick the first radio we can find on this mboard, and hope that all
645         // radios have the same range.
646         for (auto& chain : _rx_chans) {
647             auto radio = chain.second.radio;
648             if (radio->get_block_id().get_device_no() == mboard) {
649                 return radio->get_tick_rate();
650             }
651         }
652         for (auto& chain : _tx_chans) {
653             auto radio = chain.second.radio;
654             if (radio->get_block_id().get_device_no() == mboard) {
655                 return radio->get_tick_rate();
656             }
657         }
658         throw uhd::key_error("Invalid mboard index!");
659     }
660 
get_master_clock_rate_range(const size_t mboard)661     meta_range_t get_master_clock_rate_range(const size_t mboard)
662     {
663         // We pick the first radio we can find on this mboard, and hope that all
664         // radios have the same range.
665         for (auto& chain : _rx_chans) {
666             auto radio = chain.second.radio;
667             if (radio->get_block_id().get_device_no() == mboard) {
668                 return radio->get_rate_range();
669             }
670         }
671         for (auto& chain : _tx_chans) {
672             auto radio = chain.second.radio;
673             if (radio->get_block_id().get_device_no() == mboard) {
674                 return radio->get_rate_range();
675             }
676         }
677         throw uhd::key_error("Invalid mboard index!");
678     }
679 
get_pp_string(void)680     std::string get_pp_string(void)
681     {
682         std::string buff = str(boost::format("%s USRP:\n"
683                                              "  Device: %s\n")
684                                % ((get_num_mboards() > 1) ? "Multi" : "Single")
685                                % (_tree->access<std::string>("/name").get()));
686         for (size_t m = 0; m < get_num_mboards(); m++) {
687             buff += str(
688                 boost::format("  Mboard %d: %s\n") % m % get_mbc(m)->get_mboard_name());
689         }
690 
691 
692         //----------- rx side of life ----------------------------------
693         for (size_t rx_chan = 0; rx_chan < get_rx_num_channels(); rx_chan++) {
694             buff += str(boost::format("  RX Channel: %u\n"
695                                       "    RX DSP: %s\n"
696                                       "    RX Dboard: %s\n"
697                                       "    RX Subdev: %s\n")
698                         % rx_chan
699                         % (_rx_chans.at(rx_chan).ddc ? std::to_string(rx_chan) : "n/a")
700                         % _rx_chans.at(rx_chan).radio->get_slot_name()
701                         % get_rx_subdev_name(rx_chan));
702         }
703 
704         //----------- tx side of life ----------------------------------
705         for (size_t tx_chan = 0; tx_chan < get_tx_num_channels(); tx_chan++) {
706             buff += str(boost::format("  TX Channel: %u\n"
707                                       "    TX DSP: %s\n"
708                                       "    TX Dboard: %s\n"
709                                       "    TX Subdev: %s\n")
710                         % tx_chan
711                         % (_tx_chans.at(tx_chan).duc ? std::to_string(tx_chan) : "n/a")
712                         % _tx_chans.at(tx_chan).radio->get_slot_name()
713                         % get_tx_subdev_name(tx_chan));
714         }
715 
716         return buff;
717     }
718 
get_mboard_name(size_t mboard=0)719     std::string get_mboard_name(size_t mboard = 0)
720     {
721         return get_mbc(mboard)->get_mboard_name();
722     }
723 
get_time_now(size_t mboard=0)724     time_spec_t get_time_now(size_t mboard = 0)
725     {
726         return get_mbc(mboard)->get_timekeeper(0)->get_time_now();
727     }
728 
get_time_last_pps(size_t mboard=0)729     time_spec_t get_time_last_pps(size_t mboard = 0)
730     {
731         return get_mbc(mboard)->get_timekeeper(0)->get_time_last_pps();
732     }
733 
set_time_now(const time_spec_t & time_spec,size_t mboard=ALL_MBOARDS)734     void set_time_now(const time_spec_t& time_spec, size_t mboard = ALL_MBOARDS)
735     {
736         MUX_MB_API_CALL(set_time_now, time_spec);
737         get_mbc(mboard)->get_timekeeper(0)->set_time_now(time_spec);
738     }
739 
set_time_next_pps(const time_spec_t & time_spec,size_t mboard=ALL_MBOARDS)740     void set_time_next_pps(const time_spec_t& time_spec, size_t mboard = ALL_MBOARDS)
741     {
742         MUX_MB_API_CALL(set_time_next_pps, time_spec);
743         get_mbc(mboard)->get_timekeeper(0)->set_time_next_pps(time_spec);
744     }
745 
set_time_unknown_pps(const time_spec_t & time_spec)746     void set_time_unknown_pps(const time_spec_t& time_spec)
747     {
748         UHD_LOGGER_INFO("MULTI_USRP") << "    1) catch time transition at pps edge";
749         auto end_time                   = std::chrono::steady_clock::now() + 1100ms;
750         time_spec_t time_start_last_pps = get_time_last_pps();
751         while (time_start_last_pps == get_time_last_pps()) {
752             if (std::chrono::steady_clock::now() > end_time) {
753                 throw uhd::runtime_error("Board 0 may not be getting a PPS signal!\n"
754                                          "No PPS detected within the time interval.\n"
755                                          "See the application notes for your device.\n");
756             }
757             std::this_thread::sleep_for(1ms);
758         }
759 
760         UHD_LOGGER_INFO("MULTI_USRP") << "    2) set times next pps (synchronously)";
761         set_time_next_pps(time_spec, ALL_MBOARDS);
762         std::this_thread::sleep_for(1s);
763 
764         // verify that the time registers are read to be within a few RTT
765         for (size_t m = 1; m < get_num_mboards(); m++) {
766             time_spec_t time_0 = this->get_time_now(0);
767             time_spec_t time_i = this->get_time_now(m);
768             // 10 ms: greater than RTT but not too big
769             if (time_i < time_0 or (time_i - time_0) > time_spec_t(0.01)) {
770                 UHD_LOGGER_WARNING("MULTI_USRP")
771                     << boost::format(
772                            "Detected time deviation between board %d and board 0.\n"
773                            "Board 0 time is %f seconds.\n"
774                            "Board %d time is %f seconds.\n")
775                            % m % time_0.get_real_secs() % m % time_i.get_real_secs();
776             }
777         }
778     }
779 
get_time_synchronized(void)780     bool get_time_synchronized(void)
781     {
782         for (size_t m = 1; m < get_num_mboards(); m++) {
783             time_spec_t time_0 = this->get_time_now(0);
784             time_spec_t time_i = this->get_time_now(m);
785             if (time_i < time_0 or (time_i - time_0) > time_spec_t(0.01)) {
786                 return false;
787             }
788         }
789         return true;
790     }
791 
set_command_time(const uhd::time_spec_t & time_spec,size_t mboard=ALL_MBOARDS)792     void set_command_time(const uhd::time_spec_t& time_spec, size_t mboard = ALL_MBOARDS)
793     {
794         MUX_MB_API_CALL(set_command_time, time_spec);
795 
796         // Set command time on all the blocks that are connected
797         for (auto& chain : _rx_chans) {
798             if (chain.second.radio->get_block_id().get_device_no() != mboard) {
799                 continue;
800             }
801             chain.second.radio->set_command_time(time_spec, chain.second.block_chan);
802             if (chain.second.ddc) {
803                 chain.second.ddc->set_command_time(time_spec, chain.second.block_chan);
804             }
805         }
806         for (auto& chain : _tx_chans) {
807             if (chain.second.radio->get_block_id().get_device_no() != mboard) {
808                 continue;
809             }
810             chain.second.radio->set_command_time(time_spec, chain.second.block_chan);
811             if (chain.second.duc) {
812                 chain.second.duc->set_command_time(time_spec, chain.second.block_chan);
813             }
814         }
815     }
816 
clear_command_time(size_t mboard=ALL_MBOARDS)817     void clear_command_time(size_t mboard = ALL_MBOARDS)
818     {
819         if (mboard == ALL_MBOARDS) {
820             for (size_t i = 0; i < get_num_mboards(); ++i) {
821                 clear_command_time(i);
822             }
823             return;
824         }
825 
826         // Clear command time on all the blocks that are connected
827         for (auto& chain : _rx_chans) {
828             if (chain.second.radio->get_block_id().get_device_no() != mboard) {
829                 continue;
830             }
831             chain.second.radio->clear_command_time(chain.second.block_chan);
832             if (chain.second.ddc) {
833                 chain.second.ddc->clear_command_time(chain.second.block_chan);
834             }
835         }
836         for (auto& chain : _tx_chans) {
837             if (chain.second.radio->get_block_id().get_device_no() != mboard) {
838                 continue;
839             }
840             chain.second.radio->clear_command_time(chain.second.block_chan);
841             if (chain.second.duc) {
842                 chain.second.duc->clear_command_time(chain.second.block_chan);
843             }
844         }
845     }
846 
issue_stream_cmd(const stream_cmd_t & stream_cmd,size_t chan=ALL_CHANS)847     void issue_stream_cmd(const stream_cmd_t& stream_cmd, size_t chan = ALL_CHANS)
848     {
849         MUX_RX_API_CALL(issue_stream_cmd, stream_cmd);
850         auto& rx_chain = _get_rx_chan(chan);
851         if (rx_chain.ddc) {
852             rx_chain.ddc->issue_stream_cmd(stream_cmd, rx_chain.block_chan);
853         } else {
854             rx_chain.radio->issue_stream_cmd(stream_cmd, rx_chain.block_chan);
855         }
856     }
857 
set_time_source(const std::string & source,const size_t mboard=ALL_MBOARDS)858     void set_time_source(const std::string& source, const size_t mboard = ALL_MBOARDS)
859     {
860         MUX_MB_API_CALL(set_time_source, source);
861         get_mbc(mboard)->set_time_source(source);
862     }
863 
get_time_source(const size_t mboard)864     std::string get_time_source(const size_t mboard)
865     {
866         return get_mbc(mboard)->get_time_source();
867     }
868 
get_time_sources(const size_t mboard)869     std::vector<std::string> get_time_sources(const size_t mboard)
870     {
871         return get_mbc(mboard)->get_time_sources();
872     }
873 
set_clock_source(const std::string & source,const size_t mboard=ALL_MBOARDS)874     void set_clock_source(const std::string& source, const size_t mboard = ALL_MBOARDS)
875     {
876         MUX_MB_API_CALL(set_clock_source, source);
877         get_mbc(mboard)->set_clock_source(source);
878     }
879 
get_clock_source(const size_t mboard)880     std::string get_clock_source(const size_t mboard)
881     {
882         return get_mbc(mboard)->get_clock_source();
883     }
884 
get_clock_sources(const size_t mboard)885     std::vector<std::string> get_clock_sources(const size_t mboard)
886     {
887         return get_mbc(mboard)->get_clock_sources();
888     }
889 
set_sync_source(const std::string & clock_source,const std::string & time_source,const size_t mboard=ALL_MBOARDS)890     void set_sync_source(const std::string& clock_source,
891         const std::string& time_source,
892         const size_t mboard = ALL_MBOARDS)
893     {
894         MUX_MB_API_CALL(set_sync_source, clock_source, time_source);
895         get_mbc(mboard)->set_sync_source(clock_source, time_source);
896     }
897 
set_sync_source(const device_addr_t & sync_source,const size_t mboard=ALL_MBOARDS)898     void set_sync_source(
899         const device_addr_t& sync_source, const size_t mboard = ALL_MBOARDS)
900     {
901         MUX_MB_API_CALL(set_sync_source, sync_source);
902         get_mbc(mboard)->set_sync_source(sync_source);
903     }
904 
get_sync_source(const size_t mboard)905     device_addr_t get_sync_source(const size_t mboard)
906     {
907         return get_mbc(mboard)->get_sync_source();
908     }
909 
get_sync_sources(const size_t mboard)910     std::vector<device_addr_t> get_sync_sources(const size_t mboard)
911     {
912         return get_mbc(mboard)->get_sync_sources();
913     }
914 
set_clock_source_out(const bool enb,const size_t mboard=ALL_MBOARDS)915     void set_clock_source_out(const bool enb, const size_t mboard = ALL_MBOARDS)
916     {
917         MUX_MB_API_CALL(set_clock_source_out, enb);
918         get_mbc(mboard)->set_clock_source_out(enb);
919     }
920 
set_time_source_out(const bool enb,const size_t mboard=ALL_MBOARDS)921     void set_time_source_out(const bool enb, const size_t mboard = ALL_MBOARDS)
922     {
923         MUX_MB_API_CALL(set_time_source_out, enb);
924         get_mbc(mboard)->set_time_source_out(enb);
925     }
926 
get_num_mboards(void)927     size_t get_num_mboards(void)
928     {
929         return _graph->get_num_mboards();
930     }
931 
get_mboard_sensor(const std::string & name,size_t mboard=0)932     sensor_value_t get_mboard_sensor(const std::string& name, size_t mboard = 0)
933     {
934         return get_mbc(mboard)->get_sensor(name);
935     }
936 
get_mboard_sensor_names(size_t mboard=0)937     std::vector<std::string> get_mboard_sensor_names(size_t mboard = 0)
938     {
939         return get_mbc(mboard)->get_sensor_names();
940     }
941 
942     // This only works on the USRP2 and B100, both of which are not rfnoc_device
set_user_register(const uint8_t,const uint32_t,size_t)943     void set_user_register(const uint8_t, const uint32_t, size_t)
944     {
945         throw uhd::not_implemented_error(
946             "set_user_register(): Not implemented on this device!");
947     }
948 
949     // This only works on the B200, which is not an rfnoc_device
get_user_settings_iface(const size_t)950     uhd::wb_iface::sptr get_user_settings_iface(const size_t)
951     {
952         return nullptr;
953     }
954 
get_radio_control(const size_t chan=0)955     uhd::rfnoc::radio_control& get_radio_control(const size_t chan = 0)
956     {
957         return *_get_rx_chan(chan).radio;
958     }
959 
960     /*******************************************************************
961      * RX methods
962      ******************************************************************/
_generate_rx_radio_chan(block_id_t radio_id,size_t block_chan)963     rx_chan_t _generate_rx_radio_chan(block_id_t radio_id, size_t block_chan)
964     {
965         auto radio_blk          = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
966         auto radio_source_chain = get_block_chain(_graph, radio_id, block_chan, true);
967 
968         // Find out if we have a DDC in the radio block chain
969         auto ddc_port_def = [this, radio_source_chain, radio_id, block_chan]() {
970             try {
971                 for (auto edge : radio_source_chain) {
972                     if (block_id_t(edge.dst_blockid).match("DDC")) {
973                         if (edge.dst_port != block_chan) {
974                             /*  We don't expect this to happen very often. But in
975                              * the case that port numbers don't match, we need to
976                              * disable DDC control to ensure we're not controlling
977                              * another channel's DDC
978                              */
979                             UHD_LOGGER_WARNING("MULTI_USRP")
980                                 << "DDC in radio chain " << radio_id << ":"
981                                 << std::to_string(block_chan)
982                                 << " not connected to the same port number! "
983                                    "Disabling DDC control.";
984                             break;
985                         }
986                         auto ddc_blk = _graph->get_block<uhd::rfnoc::ddc_block_control>(
987                             edge.dst_blockid);
988                         return std::tuple<uhd::rfnoc::ddc_block_control::sptr, size_t>(
989                             ddc_blk, block_chan);
990                     }
991                 }
992             } catch (const uhd::exception&) {
993                 UHD_LOGGER_DEBUG("MULTI_USRP")
994                     << "No DDC found for radio block " << radio_id << ":"
995                     << std::to_string(block_chan);
996                 // Then just return a nullptr
997             }
998             return std::tuple<uhd::rfnoc::ddc_block_control::sptr, size_t>(nullptr, 0);
999         }();
1000 
1001         // Create the RX chan
1002         return rx_chan_t(
1003             {radio_blk, std::get<0>(ddc_port_def), block_chan, radio_source_chain});
1004     }
1005 
_generate_mboard_rx_chans(const uhd::usrp::subdev_spec_t & spec,size_t mboard)1006     std::vector<rx_chan_t> _generate_mboard_rx_chans(
1007         const uhd::usrp::subdev_spec_t& spec, size_t mboard)
1008     {
1009         // Discover all of the radios on our devices and create a mapping between radio
1010         // chains and channel numbers
1011         auto radio_blk_ids = _graph->find_blocks(std::to_string(mboard) + "/Radio");
1012         // If we don't find any radios, we don't have a multi_usrp object
1013         if (radio_blk_ids.empty()) {
1014             throw uhd::runtime_error(
1015                 "[multi_usrp] No radios found in the requested mboard: "
1016                 + std::to_string(mboard));
1017         }
1018 
1019         // Iterate through the subdev pairs, and try to find a radio that matches
1020         std::vector<rx_chan_t> new_chans;
1021         for (auto chan_subdev_pair : spec) {
1022             bool subdev_found = false;
1023             for (auto radio_id : radio_blk_ids) {
1024                 auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
1025                 size_t block_chan;
1026                 try {
1027                     block_chan = radio_blk->get_chan_from_dboard_fe(
1028                         chan_subdev_pair.sd_name, RX_DIRECTION);
1029                 } catch (const uhd::lookup_error&) {
1030                     // This is OK, since we're probing all radios, this
1031                     // particular radio may not have the requested frontend name
1032                     // so it's not one that we want in this list.
1033                     continue;
1034                 }
1035                 subdev_spec_pair_t radio_subdev(radio_blk->get_slot_name(),
1036                     radio_blk->get_dboard_fe_from_chan(block_chan, uhd::RX_DIRECTION));
1037                 if (chan_subdev_pair == radio_subdev) {
1038                     new_chans.push_back(_generate_rx_radio_chan(radio_id, block_chan));
1039                     subdev_found = true;
1040                 }
1041             }
1042             if (!subdev_found) {
1043                 std::string err_msg("Could not find radio on mboard "
1044                                     + std::to_string(mboard) + " that matches subdev "
1045                                     + chan_subdev_pair.db_name + ":"
1046                                     + chan_subdev_pair.sd_name);
1047                 UHD_LOG_ERROR("MULTI_USRP", err_msg);
1048                 throw uhd::lookup_error(err_msg);
1049             }
1050         }
1051         UHD_LOG_TRACE("MULTI_USRP",
1052             std::string("Using RX subdev " + spec.to_string() + ", found ")
1053                 + std::to_string(new_chans.size()) + " channels for mboard "
1054                 + std::to_string(mboard));
1055         return new_chans;
1056     }
1057 
set_rx_subdev_spec(const uhd::usrp::subdev_spec_t & spec,size_t mboard=ALL_MBOARDS)1058     void set_rx_subdev_spec(
1059         const uhd::usrp::subdev_spec_t& spec, size_t mboard = ALL_MBOARDS)
1060     {
1061         // First, generate a vector of the RX channels that we need to register
1062         auto new_rx_chans = [this, spec, mboard]() {
1063             /* When setting the subdev spec in multiple mboard scenarios, there are two
1064              * cases we need to handle:
1065              * 1. Setting all mboard to the same subdev spec. This is the easy case.
1066              * 2. Setting a single mboard's subdev spec. In this case, we need to update
1067              * the requested mboard's subdev spec, and keep the old subdev spec for the
1068              * other mboards.
1069              */
1070             std::vector<rx_chan_t> new_rx_chans;
1071             for (size_t current_mboard = 0; current_mboard < get_num_mboards();
1072                  ++current_mboard) {
1073                 auto current_spec = [this, spec, mboard, current_mboard]() {
1074                     if (mboard == ALL_MBOARDS || mboard == current_mboard) {
1075                         // Update all mboards to the same subdev spec OR
1076                         // only update this mboard to the new subdev spec
1077                         return spec;
1078                     } else {
1079                         // Keep the old subdev spec for this mboard
1080                         return get_rx_subdev_spec(current_mboard);
1081                     }
1082                 }();
1083                 auto new_mboard_chans =
1084                     _generate_mboard_rx_chans(current_spec, current_mboard);
1085                 new_rx_chans.insert(
1086                     new_rx_chans.end(), new_mboard_chans.begin(), new_mboard_chans.end());
1087             }
1088             return new_rx_chans;
1089         }();
1090 
1091         // Disconnect and clear the existing chains
1092         for (size_t i = 0; i < _rx_chans.size(); i++) {
1093             _disconnect_rx_chain(i);
1094         }
1095         _rx_chans.clear();
1096 
1097         // Register the new chains
1098         size_t musrp_rx_channel = 0;
1099         for (auto rx_chan : new_rx_chans) {
1100             _rx_chans.emplace(musrp_rx_channel++, rx_chan);
1101         }
1102     }
1103 
get_rx_subdev_spec(size_t mboard=0)1104     uhd::usrp::subdev_spec_t get_rx_subdev_spec(size_t mboard = 0)
1105     {
1106         uhd::usrp::subdev_spec_t result;
1107         for (size_t rx_chan = 0; rx_chan < get_rx_num_channels(); rx_chan++) {
1108             auto& rx_chain = _rx_chans.at(rx_chan);
1109             if (rx_chain.radio->get_block_id().get_device_no() == mboard) {
1110                 result.push_back(
1111                     uhd::usrp::subdev_spec_pair_t(rx_chain.radio->get_slot_name(),
1112                         rx_chain.radio->get_dboard_fe_from_chan(
1113                             rx_chain.block_chan, uhd::RX_DIRECTION)));
1114             }
1115         }
1116 
1117         return result;
1118     }
1119 
get_rx_num_channels(void)1120     size_t get_rx_num_channels(void)
1121     {
1122         return _rx_chans.size();
1123     }
1124 
get_rx_subdev_name(size_t chan=0)1125     std::string get_rx_subdev_name(size_t chan = 0)
1126     {
1127         auto rx_chain = _get_rx_chan(chan);
1128         return rx_chain.radio->get_fe_name(rx_chain.block_chan, uhd::RX_DIRECTION);
1129     }
1130 
set_rx_rate(double rate,size_t chan=ALL_CHANS)1131     void set_rx_rate(double rate, size_t chan = ALL_CHANS)
1132     {
1133         std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1134         MUX_RX_API_CALL(set_rx_rate, rate);
1135         const double actual_rate = [&]() {
1136             auto rx_chain = _get_rx_chan(chan);
1137             if (rx_chain.ddc) {
1138                 return rx_chain.ddc->set_output_rate(rate, rx_chain.block_chan);
1139             } else {
1140                 return rx_chain.radio->set_rate(rate);
1141             }
1142         }();
1143         if (actual_rate != rate) {
1144             UHD_LOGGER_WARNING("MULTI_USRP")
1145                 << boost::format(
1146                        "Could not set RX rate to %.3f MHz. Actual rate is %.3f MHz")
1147                        % (rate / 1.0e6) % (actual_rate / 1.0e6);
1148         }
1149         _rx_rates[chan] = actual_rate;
1150     }
1151 
set_rx_spp(const size_t spp,const size_t chan=ALL_CHANS)1152     void set_rx_spp(const size_t spp, const size_t chan = ALL_CHANS)
1153     {
1154         std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1155         MUX_RX_API_CALL(set_rx_spp, spp);
1156         auto rx_chain = _get_rx_chan(chan);
1157         rx_chain.radio->set_property<int>(
1158             "spp", narrow_cast<int>(spp), rx_chain.block_chan);
1159     }
1160 
get_rx_rate(size_t chan=0)1161     double get_rx_rate(size_t chan = 0)
1162     {
1163         std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1164         auto& rx_chain = _get_rx_chan(chan);
1165         if (rx_chain.ddc) {
1166             return rx_chain.ddc->get_output_rate(rx_chain.block_chan);
1167         }
1168         return rx_chain.radio->get_rate();
1169     }
1170 
get_rx_rates(size_t chan=0)1171     meta_range_t get_rx_rates(size_t chan = 0)
1172     {
1173         std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1174         auto rx_chain = _get_rx_chan(chan);
1175         if (rx_chain.ddc) {
1176             return rx_chain.ddc->get_output_rates(rx_chain.block_chan);
1177         }
1178         return rx_chain.radio->get_rate_range();
1179     }
1180 
set_rx_freq(const tune_request_t & tune_request,size_t chan=0)1181     tune_result_t set_rx_freq(const tune_request_t& tune_request, size_t chan = 0)
1182     {
1183         std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1184 
1185         // TODO: Add external LO warning
1186 
1187         auto rx_chain = _get_rx_chan(chan);
1188 
1189         rx_chain.radio->set_rx_tune_args(tune_request.args, rx_chain.block_chan);
1190         //------------------------------------------------------------------
1191         //-- calculate the tunable frequency ranges of the system
1192         //------------------------------------------------------------------
1193         freq_range_t tune_range =
1194             (rx_chain.ddc)
1195                 ? make_overall_tune_range(
1196                       rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan),
1197                       rx_chain.ddc->get_frequency_range(rx_chain.block_chan),
1198                       rx_chain.radio->get_rx_bandwidth(rx_chain.block_chan))
1199                 : rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan);
1200 
1201         freq_range_t rf_range =
1202             rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan);
1203         freq_range_t dsp_range =
1204             (rx_chain.ddc) ? rx_chain.ddc->get_frequency_range(rx_chain.block_chan)
1205                            : meta_range_t(0, 0);
1206         // Create lambdas to feed to tune_xx_subdev_and_dsp()
1207         // Note: If there is no DDC present, register empty lambdas for the DSP functions
1208         auto set_rf_freq = [rx_chain](double freq) {
1209             rx_chain.radio->set_rx_frequency(freq, rx_chain.block_chan);
1210         };
1211         auto get_rf_freq = [rx_chain](void) {
1212             return rx_chain.radio->get_rx_frequency(rx_chain.block_chan);
1213         };
1214         auto set_dsp_freq = [rx_chain](double freq) {
1215             (rx_chain.ddc) ? rx_chain.ddc->set_freq(freq, rx_chain.block_chan) : 0;
1216         };
1217         auto get_dsp_freq = [rx_chain](void) {
1218             return (rx_chain.ddc) ? rx_chain.ddc->get_freq(rx_chain.block_chan) : 0.0;
1219         };
1220         return tune_xx_subdev_and_dsp(RX_SIGN,
1221             tune_range,
1222             rf_range,
1223             dsp_range,
1224             set_rf_freq,
1225             get_rf_freq,
1226             set_dsp_freq,
1227             get_dsp_freq,
1228             tune_request);
1229     }
1230 
get_rx_freq(size_t chan=0)1231     double get_rx_freq(size_t chan = 0)
1232     {
1233         auto& rx_chain = _get_rx_chan(chan);
1234 
1235         // extract actual dsp and IF frequencies
1236         const double actual_rf_freq =
1237             rx_chain.radio->get_rx_frequency(rx_chain.block_chan);
1238         const double actual_dsp_freq =
1239             (rx_chain.ddc) ? rx_chain.ddc->get_freq(rx_chain.block_chan) : 0.0;
1240 
1241         return actual_rf_freq - actual_dsp_freq * RX_SIGN;
1242     }
1243 
get_rx_freq_range(size_t chan=0)1244     freq_range_t get_rx_freq_range(size_t chan = 0)
1245     {
1246         auto fe_freq_range = get_fe_rx_freq_range(chan);
1247 
1248         auto rx_chain = _get_rx_chan(chan);
1249         uhd::freq_range_t dsp_freq_range =
1250             (rx_chain.ddc) ? make_overall_tune_range(get_fe_rx_freq_range(chan),
1251                                  rx_chain.ddc->get_frequency_range(rx_chain.block_chan),
1252                                  rx_chain.radio->get_rx_bandwidth(rx_chain.block_chan))
1253                            : get_fe_rx_freq_range(chan);
1254         return dsp_freq_range;
1255     }
1256 
get_fe_rx_freq_range(size_t chan=0)1257     freq_range_t get_fe_rx_freq_range(size_t chan = 0)
1258     {
1259         auto rx_chain = _get_rx_chan(chan);
1260         return rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan);
1261     }
1262 
1263     /**************************************************************************
1264      * LO controls
1265      *************************************************************************/
get_rx_lo_names(size_t chan=0)1266     std::vector<std::string> get_rx_lo_names(size_t chan = 0)
1267     {
1268         auto rx_chain = _get_rx_chan(chan);
1269         return rx_chain.radio->get_rx_lo_names(rx_chain.block_chan);
1270     }
1271 
set_rx_lo_source(const std::string & src,const std::string & name=ALL_LOS,size_t chan=0)1272     void set_rx_lo_source(
1273         const std::string& src, const std::string& name = ALL_LOS, size_t chan = 0)
1274     {
1275         MUX_RX_API_CALL(set_rx_lo_source, src, name);
1276         auto rx_chain = _get_rx_chan(chan);
1277         rx_chain.radio->set_rx_lo_source(src, name, rx_chain.block_chan);
1278     }
1279 
get_rx_lo_source(const std::string & name=ALL_LOS,size_t chan=0)1280     const std::string get_rx_lo_source(const std::string& name = ALL_LOS, size_t chan = 0)
1281     {
1282         auto rx_chain = _get_rx_chan(chan);
1283         return rx_chain.radio->get_rx_lo_source(name, rx_chain.block_chan);
1284     }
1285 
get_rx_lo_sources(const std::string & name=ALL_LOS,size_t chan=0)1286     std::vector<std::string> get_rx_lo_sources(
1287         const std::string& name = ALL_LOS, size_t chan = 0)
1288     {
1289         auto rx_chain = _get_rx_chan(chan);
1290         return rx_chain.radio->get_rx_lo_sources(name, rx_chain.block_chan);
1291     }
1292 
set_rx_lo_export_enabled(bool enabled,const std::string & name=ALL_LOS,size_t chan=0)1293     void set_rx_lo_export_enabled(
1294         bool enabled, const std::string& name = ALL_LOS, size_t chan = 0)
1295     {
1296         MUX_RX_API_CALL(set_rx_lo_export_enabled, enabled, name);
1297         auto rx_chain = _get_rx_chan(chan);
1298         rx_chain.radio->set_rx_lo_export_enabled(enabled, name, rx_chain.block_chan);
1299     }
1300 
get_rx_lo_export_enabled(const std::string & name=ALL_LOS,size_t chan=0)1301     bool get_rx_lo_export_enabled(const std::string& name = ALL_LOS, size_t chan = 0)
1302     {
1303         auto rx_chain = _get_rx_chan(chan);
1304         return rx_chain.radio->get_rx_lo_export_enabled(name, rx_chain.block_chan);
1305     }
1306 
set_rx_lo_freq(double freq,const std::string & name,size_t chan=0)1307     double set_rx_lo_freq(double freq, const std::string& name, size_t chan = 0)
1308     {
1309         auto rx_chain = _get_rx_chan(chan);
1310         return rx_chain.radio->set_rx_lo_freq(freq, name, rx_chain.block_chan);
1311     }
1312 
get_rx_lo_freq(const std::string & name,size_t chan=0)1313     double get_rx_lo_freq(const std::string& name, size_t chan = 0)
1314     {
1315         auto rx_chain = _get_rx_chan(chan);
1316         return rx_chain.radio->get_rx_lo_freq(name, rx_chain.block_chan);
1317     }
1318 
get_rx_lo_freq_range(const std::string & name,size_t chan=0)1319     freq_range_t get_rx_lo_freq_range(const std::string& name, size_t chan = 0)
1320     {
1321         auto rx_chain = _get_rx_chan(chan);
1322         return rx_chain.radio->get_rx_lo_freq_range(name, rx_chain.block_chan);
1323     }
1324 
1325     /*** TX LO API ***/
get_tx_lo_names(size_t chan=0)1326     std::vector<std::string> get_tx_lo_names(size_t chan = 0)
1327     {
1328         auto tx_chain = _get_tx_chan(chan);
1329         return tx_chain.radio->get_tx_lo_names(tx_chain.block_chan);
1330     }
1331 
set_tx_lo_source(const std::string & src,const std::string & name=ALL_LOS,const size_t chan=0)1332     void set_tx_lo_source(
1333         const std::string& src, const std::string& name = ALL_LOS, const size_t chan = 0)
1334     {
1335         MUX_TX_API_CALL(set_tx_lo_source, src, name);
1336         auto tx_chain = _get_tx_chan(chan);
1337         tx_chain.radio->set_tx_lo_source(src, name, tx_chain.block_chan);
1338     }
1339 
get_tx_lo_source(const std::string & name=ALL_LOS,const size_t chan=0)1340     const std::string get_tx_lo_source(
1341         const std::string& name = ALL_LOS, const size_t chan = 0)
1342     {
1343         auto tx_chain = _get_tx_chan(chan);
1344         return tx_chain.radio->get_tx_lo_source(name, tx_chain.block_chan);
1345     }
1346 
get_tx_lo_sources(const std::string & name=ALL_LOS,const size_t chan=0)1347     std::vector<std::string> get_tx_lo_sources(
1348         const std::string& name = ALL_LOS, const size_t chan = 0)
1349     {
1350         auto tx_chain = _get_tx_chan(chan);
1351         return tx_chain.radio->get_tx_lo_sources(name, tx_chain.block_chan);
1352     }
1353 
set_tx_lo_export_enabled(const bool enabled,const std::string & name=ALL_LOS,const size_t chan=0)1354     void set_tx_lo_export_enabled(
1355         const bool enabled, const std::string& name = ALL_LOS, const size_t chan = 0)
1356     {
1357         MUX_TX_API_CALL(set_tx_lo_export_enabled, enabled, name);
1358         auto tx_chain = _get_tx_chan(chan);
1359         tx_chain.radio->set_tx_lo_export_enabled(enabled, name, tx_chain.block_chan);
1360     }
1361 
get_tx_lo_export_enabled(const std::string & name=ALL_LOS,const size_t chan=0)1362     bool get_tx_lo_export_enabled(
1363         const std::string& name = ALL_LOS, const size_t chan = 0)
1364     {
1365         auto tx_chain = _get_tx_chan(chan);
1366         return tx_chain.radio->get_tx_lo_export_enabled(name, tx_chain.block_chan);
1367     }
1368 
set_tx_lo_freq(const double freq,const std::string & name,const size_t chan=0)1369     double set_tx_lo_freq(
1370         const double freq, const std::string& name, const size_t chan = 0)
1371     {
1372         auto tx_chain = _get_tx_chan(chan);
1373         return tx_chain.radio->set_tx_lo_freq(freq, name, tx_chain.block_chan);
1374     }
1375 
get_tx_lo_freq(const std::string & name,const size_t chan=0)1376     double get_tx_lo_freq(const std::string& name, const size_t chan = 0)
1377     {
1378         auto tx_chain = _get_tx_chan(chan);
1379         return tx_chain.radio->get_tx_lo_freq(name, tx_chain.block_chan);
1380     }
1381 
get_tx_lo_freq_range(const std::string & name,const size_t chan=0)1382     freq_range_t get_tx_lo_freq_range(const std::string& name, const size_t chan = 0)
1383     {
1384         auto tx_chain = _get_tx_chan(chan);
1385         return tx_chain.radio->get_tx_lo_freq_range(name, tx_chain.block_chan);
1386     }
1387 
1388     /**************************************************************************
1389      * Gain controls
1390      *************************************************************************/
set_rx_gain(double gain,const std::string & name,size_t chan=0)1391     void set_rx_gain(double gain, const std::string& name, size_t chan = 0)
1392     {
1393         MUX_RX_API_CALL(set_rx_gain, gain, name);
1394         auto rx_chain = _get_rx_chan(chan);
1395         rx_chain.radio->set_rx_gain(gain, name, rx_chain.block_chan);
1396     }
1397 
get_rx_gain_profile_names(const size_t chan=0)1398     std::vector<std::string> get_rx_gain_profile_names(const size_t chan = 0)
1399     {
1400         auto rx_chain = _get_rx_chan(chan);
1401         return rx_chain.radio->get_rx_gain_profile_names(rx_chain.block_chan);
1402     }
1403 
set_rx_gain_profile(const std::string & profile,const size_t chan=0)1404     void set_rx_gain_profile(const std::string& profile, const size_t chan = 0)
1405     {
1406         MUX_RX_API_CALL(set_rx_gain_profile, profile);
1407         auto rx_chain = _get_rx_chan(chan);
1408         rx_chain.radio->set_rx_gain_profile(profile, rx_chain.block_chan);
1409     }
1410 
get_rx_gain_profile(const size_t chan=0)1411     std::string get_rx_gain_profile(const size_t chan = 0)
1412     {
1413         auto rx_chain = _get_rx_chan(chan);
1414         return rx_chain.radio->get_rx_gain_profile(rx_chain.block_chan);
1415     }
1416 
set_normalized_rx_gain(double gain,size_t chan=0)1417     void set_normalized_rx_gain(double gain, size_t chan = 0)
1418     {
1419         if (gain > 1.0 || gain < 0.0) {
1420             throw uhd::runtime_error("Normalized gain out of range, must be in [0, 1].");
1421         }
1422         MUX_RX_API_CALL(set_normalized_rx_gain, gain);
1423         gain_range_t gain_range = get_rx_gain_range(ALL_GAINS, chan);
1424         double abs_gain =
1425             (gain * (gain_range.stop() - gain_range.start())) + gain_range.start();
1426         set_rx_gain(abs_gain, ALL_GAINS, chan);
1427     }
1428 
set_rx_agc(bool enable,size_t chan=0)1429     void set_rx_agc(bool enable, size_t chan = 0)
1430     {
1431         MUX_RX_API_CALL(set_rx_agc, enable);
1432         auto& rx_chain = _get_rx_chan(chan);
1433         rx_chain.radio->set_rx_agc(enable, rx_chain.block_chan);
1434     }
1435 
get_rx_gain(const std::string & name,size_t chan=0)1436     double get_rx_gain(const std::string& name, size_t chan = 0)
1437     {
1438         auto& rx_chain = _get_rx_chan(chan);
1439         return rx_chain.radio->get_rx_gain(name, rx_chain.block_chan);
1440     }
1441 
get_normalized_rx_gain(size_t chan=0)1442     double get_normalized_rx_gain(size_t chan = 0)
1443     {
1444         gain_range_t gain_range       = get_rx_gain_range(ALL_GAINS, chan);
1445         const double gain_range_width = gain_range.stop() - gain_range.start();
1446         // In case we have a device without a range of gains:
1447         if (gain_range_width == 0.0) {
1448             return 0;
1449         }
1450         const double norm_gain =
1451             (get_rx_gain(ALL_GAINS, chan) - gain_range.start()) / gain_range_width;
1452         // Avoid rounding errors:
1453         return std::max(std::min(norm_gain, 1.0), 0.0);
1454     }
1455 
get_rx_gain_range(const std::string & name,size_t chan=0)1456     gain_range_t get_rx_gain_range(const std::string& name, size_t chan = 0)
1457     {
1458         auto& rx_chain = _get_rx_chan(chan);
1459         return rx_chain.radio->get_rx_gain_range(name, rx_chain.block_chan);
1460     }
1461 
get_rx_gain_names(size_t chan=0)1462     std::vector<std::string> get_rx_gain_names(size_t chan = 0)
1463     {
1464         auto& rx_chain = _get_rx_chan(chan);
1465         return rx_chain.radio->get_rx_gain_names(rx_chain.block_chan);
1466     }
1467 
has_rx_power_reference(const size_t chan=0)1468     bool has_rx_power_reference(const size_t chan = 0)
1469     {
1470         auto& rx_chain = _get_rx_chan(chan);
1471         return rx_chain.radio->has_rx_power_reference(rx_chain.block_chan);
1472     }
1473 
set_rx_power_reference(const double power_dbm,const size_t chan=0)1474     void set_rx_power_reference(const double power_dbm, const size_t chan = 0)
1475     {
1476         MUX_RX_API_CALL(set_rx_power_reference, power_dbm);
1477         auto& rx_chain = _get_rx_chan(chan);
1478         rx_chain.radio->set_rx_power_reference(power_dbm, rx_chain.block_chan);
1479     }
1480 
get_rx_power_reference(const size_t chan=0)1481     double get_rx_power_reference(const size_t chan = 0)
1482     {
1483         auto& rx_chain = _get_rx_chan(chan);
1484         return rx_chain.radio->get_rx_power_reference(rx_chain.block_chan);
1485     }
1486 
get_rx_power_range(const size_t chan)1487     meta_range_t get_rx_power_range(const size_t chan)
1488     {
1489         auto& rx_chain = _get_rx_chan(chan);
1490         return rx_chain.radio->get_rx_power_range(rx_chain.block_chan);
1491     }
1492 
set_rx_antenna(const std::string & ant,size_t chan=0)1493     void set_rx_antenna(const std::string& ant, size_t chan = 0)
1494     {
1495         MUX_RX_API_CALL(set_rx_antenna, ant);
1496         auto& rx_chain = _get_rx_chan(chan);
1497         rx_chain.radio->set_rx_antenna(ant, rx_chain.block_chan);
1498     }
1499 
get_rx_antenna(size_t chan=0)1500     std::string get_rx_antenna(size_t chan = 0)
1501     {
1502         auto& rx_chain = _get_rx_chan(chan);
1503         return rx_chain.radio->get_rx_antenna(rx_chain.block_chan);
1504     }
1505 
get_rx_antennas(size_t chan=0)1506     std::vector<std::string> get_rx_antennas(size_t chan = 0)
1507     {
1508         auto& rx_chain = _get_rx_chan(chan);
1509         return rx_chain.radio->get_rx_antennas(rx_chain.block_chan);
1510     }
1511 
set_rx_bandwidth(double bandwidth,size_t chan=0)1512     void set_rx_bandwidth(double bandwidth, size_t chan = 0)
1513     {
1514         MUX_RX_API_CALL(set_rx_bandwidth, bandwidth);
1515         auto& rx_chain = _get_rx_chan(chan);
1516         rx_chain.radio->set_rx_bandwidth(bandwidth, rx_chain.block_chan);
1517     }
1518 
get_rx_bandwidth(size_t chan=0)1519     double get_rx_bandwidth(size_t chan = 0)
1520     {
1521         auto& rx_chain = _get_rx_chan(chan);
1522         return rx_chain.radio->get_rx_bandwidth(rx_chain.block_chan);
1523     }
1524 
get_rx_bandwidth_range(size_t chan=0)1525     meta_range_t get_rx_bandwidth_range(size_t chan = 0)
1526     {
1527         auto& rx_chain = _get_rx_chan(chan);
1528         return rx_chain.radio->get_rx_bandwidth_range(rx_chain.block_chan);
1529     }
1530 
get_rx_dboard_iface(size_t chan=0)1531     dboard_iface::sptr get_rx_dboard_iface(size_t chan = 0)
1532     {
1533         auto& rx_chain = _get_rx_chan(chan);
1534         return rx_chain.radio->get_tree()->access<dboard_iface::sptr>("iface").get();
1535     }
1536 
get_rx_sensor(const std::string & name,size_t chan=0)1537     sensor_value_t get_rx_sensor(const std::string& name, size_t chan = 0)
1538     {
1539         auto rx_chain = _get_rx_chan(chan);
1540         return rx_chain.radio->get_rx_sensor(name, rx_chain.block_chan);
1541     }
1542 
get_rx_sensor_names(size_t chan=0)1543     std::vector<std::string> get_rx_sensor_names(size_t chan = 0)
1544     {
1545         auto rx_chain = _get_rx_chan(chan);
1546         return rx_chain.radio->get_rx_sensor_names(rx_chain.block_chan);
1547     }
1548 
set_rx_dc_offset(const bool enb,size_t chan=ALL_CHANS)1549     void set_rx_dc_offset(const bool enb, size_t chan = ALL_CHANS)
1550     {
1551         MUX_RX_API_CALL(set_rx_dc_offset, enb);
1552         const auto rx_chain = _get_rx_chan(chan);
1553         rx_chain.radio->set_rx_dc_offset(enb, rx_chain.block_chan);
1554     }
1555 
set_rx_dc_offset(const std::complex<double> & offset,size_t chan=ALL_CHANS)1556     void set_rx_dc_offset(const std::complex<double>& offset, size_t chan = ALL_CHANS)
1557     {
1558         MUX_RX_API_CALL(set_rx_dc_offset, offset);
1559         const auto rx_chain = _get_rx_chan(chan);
1560         rx_chain.radio->set_rx_dc_offset(offset, rx_chain.block_chan);
1561     }
1562 
get_rx_dc_offset_range(size_t chan=0)1563     meta_range_t get_rx_dc_offset_range(size_t chan = 0)
1564     {
1565         auto rx_chain = _get_rx_chan(chan);
1566         return rx_chain.radio->get_rx_dc_offset_range(rx_chain.block_chan);
1567     }
1568 
set_rx_iq_balance(const bool enb,size_t chan)1569     void set_rx_iq_balance(const bool enb, size_t chan)
1570     {
1571         MUX_RX_API_CALL(set_rx_iq_balance, enb);
1572         auto rx_chain = _get_rx_chan(chan);
1573         rx_chain.radio->set_rx_iq_balance(enb, rx_chain.block_chan);
1574     }
1575 
set_rx_iq_balance(const std::complex<double> & correction,size_t chan=ALL_CHANS)1576     void set_rx_iq_balance(
1577         const std::complex<double>& correction, size_t chan = ALL_CHANS)
1578     {
1579         MUX_RX_API_CALL(set_rx_iq_balance, correction);
1580         const auto rx_chain = _get_rx_chan(chan);
1581         rx_chain.radio->set_rx_iq_balance(correction, rx_chain.block_chan);
1582     }
1583 
1584     /*******************************************************************
1585      * TX methods
1586      ******************************************************************/
_generate_tx_radio_chan(block_id_t radio_id,size_t block_chan)1587     tx_chan_t _generate_tx_radio_chan(block_id_t radio_id, size_t block_chan)
1588     {
1589         auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
1590         // Now on to the DUC chain
1591         auto radio_sink_chain = get_block_chain(_graph, radio_id, block_chan, false);
1592 
1593         // Find out if we have a DUC in the radio block chain
1594         auto duc_port_def = [this, radio_sink_chain, radio_id, block_chan]() {
1595             try {
1596                 for (auto edge : radio_sink_chain) {
1597                     if (block_id_t(edge.src_blockid).match("DUC")) {
1598                         if (edge.src_port != block_chan) {
1599                             /*  We don't expect this to happen very often. But in
1600                              * the case that port numbers don't match, we need to
1601                              * disable DUC control to ensure we're not controlling
1602                              * another channel's DDC
1603                              */
1604                             UHD_LOGGER_WARNING("MULTI_USRP")
1605                                 << "DUC in radio chain " << radio_id << ":"
1606                                 << std::to_string(block_chan)
1607                                 << " not connected to the same port number! "
1608                                    "Disabling DUC control.";
1609                             break;
1610                         }
1611                         auto ddc_blk = _graph->get_block<uhd::rfnoc::duc_block_control>(
1612                             edge.src_blockid);
1613                         return std::tuple<uhd::rfnoc::duc_block_control::sptr, size_t>(
1614                             ddc_blk, block_chan);
1615                     }
1616                 }
1617             } catch (const uhd::exception&) {
1618                 UHD_LOGGER_DEBUG("MULTI_USRP")
1619                     << "No DDC found for radio block " << radio_id << ":"
1620                     << std::to_string(block_chan);
1621                 // Then just return a nullptr
1622             }
1623             return std::tuple<uhd::rfnoc::duc_block_control::sptr, size_t>(nullptr, 0);
1624         }();
1625 
1626         // Create the TX chan
1627         return tx_chan_t(
1628             {radio_blk, std::get<0>(duc_port_def), block_chan, radio_sink_chain});
1629     }
1630 
_generate_mboard_tx_chans(const uhd::usrp::subdev_spec_t & spec,size_t mboard)1631     std::vector<tx_chan_t> _generate_mboard_tx_chans(
1632         const uhd::usrp::subdev_spec_t& spec, size_t mboard)
1633     {
1634         // Discover all of the radios on our devices and create a mapping between radio
1635         // chains and channel numbers
1636         auto radio_blk_ids = _graph->find_blocks(std::to_string(mboard) + "/Radio");
1637         // If we don't find any radios, we don't have a multi_usrp object
1638         if (radio_blk_ids.empty()) {
1639             throw uhd::runtime_error(
1640                 "[multi_usrp] No radios found in the requested mboard: "
1641                 + std::to_string(mboard));
1642         }
1643 
1644         // Iterate through the subdev pairs, and try to find a radio that matches
1645         std::vector<tx_chan_t> new_chans;
1646         for (auto chan_subdev_pair : spec) {
1647             bool subdev_found = false;
1648             for (auto radio_id : radio_blk_ids) {
1649                 auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
1650                 size_t block_chan;
1651                 try {
1652                     block_chan = radio_blk->get_chan_from_dboard_fe(
1653                         chan_subdev_pair.sd_name, TX_DIRECTION);
1654                 } catch (const uhd::lookup_error&) {
1655                     // This is OK, since we're probing all radios, this
1656                     // particular radio may not have the requested frontend name
1657                     // so it's not one that we want in this list.
1658                     continue;
1659                 }
1660                 subdev_spec_pair_t radio_subdev(radio_blk->get_slot_name(),
1661                     radio_blk->get_dboard_fe_from_chan(block_chan, uhd::TX_DIRECTION));
1662                 if (chan_subdev_pair == radio_subdev) {
1663                     new_chans.push_back(_generate_tx_radio_chan(radio_id, block_chan));
1664                     subdev_found = true;
1665                 }
1666             }
1667             if (!subdev_found) {
1668                 std::string err_msg("Could not find radio on mboard "
1669                                     + std::to_string(mboard) + " that matches subdev "
1670                                     + chan_subdev_pair.db_name + ":"
1671                                     + chan_subdev_pair.sd_name);
1672                 UHD_LOG_ERROR("MULTI_USRP", err_msg);
1673                 throw uhd::lookup_error(err_msg);
1674             }
1675         }
1676         UHD_LOG_TRACE("MULTI_USRP",
1677             std::string("Using TX subdev " + spec.to_string() + ", found ")
1678                 + std::to_string(new_chans.size()) + " channels for mboard "
1679                 + std::to_string(mboard));
1680         return new_chans;
1681     }
1682 
set_tx_subdev_spec(const uhd::usrp::subdev_spec_t & spec,size_t mboard=ALL_MBOARDS)1683     void set_tx_subdev_spec(
1684         const uhd::usrp::subdev_spec_t& spec, size_t mboard = ALL_MBOARDS)
1685     {
1686         /* TODO: Refactor with get_rx_subdev_spec- the algorithms are the same, just the
1687          * types are different
1688          */
1689         // First, generate a vector of the tx channels that we need to register
1690         auto new_tx_chans = [this, spec, mboard]() {
1691             /* When setting the subdev spec in multiple mboard scenarios, there are two
1692              * cases we need to handle:
1693              * 1. Setting all mboard to the same subdev spec. This is the easy case.
1694              * 2. Setting a single mboard's subdev spec. In this case, we need to update
1695              * the requested mboard's subdev spec, and keep the old subdev spec for the
1696              * other mboards.
1697              */
1698             std::vector<tx_chan_t> new_tx_chans;
1699             for (size_t current_mboard = 0; current_mboard < get_num_mboards();
1700                  ++current_mboard) {
1701                 auto current_spec = [this, spec, mboard, current_mboard]() {
1702                     if (mboard == ALL_MBOARDS || mboard == current_mboard) {
1703                         // Update all mboards to the same subdev spec OR
1704                         // only update this mboard to the new subdev spec
1705                         return spec;
1706                     } else {
1707                         // Keep the old subdev spec for this mboard
1708                         return get_tx_subdev_spec(current_mboard);
1709                     }
1710                 }();
1711                 auto new_mboard_chans =
1712                     _generate_mboard_tx_chans(current_spec, current_mboard);
1713                 new_tx_chans.insert(
1714                     new_tx_chans.end(), new_mboard_chans.begin(), new_mboard_chans.end());
1715             }
1716             return new_tx_chans;
1717         }();
1718 
1719         // Disconnect and clear existing chains
1720         for (size_t i = 0; i < _tx_chans.size(); i++) {
1721             _disconnect_tx_chain(i);
1722         }
1723         _tx_chans.clear();
1724 
1725         // Register new chains
1726         size_t musrp_tx_channel = 0;
1727         for (auto tx_chan : new_tx_chans) {
1728             _tx_chans.emplace(musrp_tx_channel++, tx_chan);
1729         }
1730     }
1731 
get_tx_subdev_spec(size_t mboard=0)1732     uhd::usrp::subdev_spec_t get_tx_subdev_spec(size_t mboard = 0)
1733     {
1734         uhd::usrp::subdev_spec_t result;
1735         for (size_t tx_chan = 0; tx_chan < get_tx_num_channels(); tx_chan++) {
1736             auto& tx_chain = _tx_chans.at(tx_chan);
1737             if (tx_chain.radio->get_block_id().get_device_no() == mboard) {
1738                 result.push_back(
1739                     uhd::usrp::subdev_spec_pair_t(tx_chain.radio->get_slot_name(),
1740                         tx_chain.radio->get_dboard_fe_from_chan(
1741                             tx_chain.block_chan, uhd::TX_DIRECTION)));
1742             }
1743         }
1744 
1745         return result;
1746     }
1747 
get_tx_num_channels(void)1748     size_t get_tx_num_channels(void)
1749     {
1750         return _tx_chans.size();
1751     }
1752 
get_tx_subdev_name(size_t chan=0)1753     std::string get_tx_subdev_name(size_t chan = 0)
1754     {
1755         auto tx_chain = _get_tx_chan(chan);
1756         return tx_chain.radio->get_fe_name(tx_chain.block_chan, uhd::TX_DIRECTION);
1757     }
1758 
set_tx_rate(double rate,size_t chan=ALL_CHANS)1759     void set_tx_rate(double rate, size_t chan = ALL_CHANS)
1760     {
1761         std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1762         MUX_TX_API_CALL(set_tx_rate, rate);
1763         const double actual_rate = [&]() {
1764             auto tx_chain = _get_tx_chan(chan);
1765             if (tx_chain.duc) {
1766                 return tx_chain.duc->set_input_rate(rate, tx_chain.block_chan);
1767             } else {
1768                 return tx_chain.radio->set_rate(rate);
1769             }
1770         }();
1771         if (actual_rate != rate) {
1772             UHD_LOGGER_WARNING("MULTI_USRP")
1773                 << boost::format(
1774                        "Could not set TX rate to %.3f MHz. Actual rate is %.3f MHz")
1775                        % (rate / 1.0e6) % (actual_rate / 1.0e6);
1776         }
1777         _tx_rates[chan] = actual_rate;
1778     }
1779 
get_tx_rate(size_t chan=0)1780     double get_tx_rate(size_t chan = 0)
1781     {
1782         std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1783         auto& tx_chain = _get_tx_chan(chan);
1784         if (tx_chain.duc) {
1785             return tx_chain.duc->get_input_rate(tx_chain.block_chan);
1786         }
1787         return tx_chain.radio->get_rate();
1788     }
1789 
get_tx_rates(size_t chan=0)1790     meta_range_t get_tx_rates(size_t chan = 0)
1791     {
1792         std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1793         auto tx_chain = _get_tx_chan(chan);
1794         if (tx_chain.duc) {
1795             return tx_chain.duc->get_input_rates(tx_chain.block_chan);
1796         }
1797         return tx_chain.radio->get_rate_range();
1798     }
1799 
set_tx_freq(const tune_request_t & tune_request,size_t chan=0)1800     tune_result_t set_tx_freq(const tune_request_t& tune_request, size_t chan = 0)
1801     {
1802         std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1803         auto tx_chain = _get_tx_chan(chan);
1804 
1805         tx_chain.radio->set_tx_tune_args(tune_request.args, tx_chain.block_chan);
1806         //------------------------------------------------------------------
1807         //-- calculate the tunable frequency ranges of the system
1808         //------------------------------------------------------------------
1809         freq_range_t tune_range =
1810             (tx_chain.duc)
1811                 ? make_overall_tune_range(
1812                       tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan),
1813                       tx_chain.duc->get_frequency_range(tx_chain.block_chan),
1814                       tx_chain.radio->get_tx_bandwidth(tx_chain.block_chan))
1815                 : tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan);
1816 
1817         freq_range_t rf_range =
1818             tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan);
1819         freq_range_t dsp_range =
1820             (tx_chain.duc) ? tx_chain.duc->get_frequency_range(tx_chain.block_chan)
1821                            : meta_range_t(0, 0);
1822         // Create lambdas to feed to tune_xx_subdev_and_dsp()
1823         // Note: If there is no DDC present, register empty lambdas for the DSP functions
1824         auto set_rf_freq = [tx_chain](double freq) {
1825             tx_chain.radio->set_tx_frequency(freq, tx_chain.block_chan);
1826         };
1827         auto get_rf_freq = [tx_chain](void) {
1828             return tx_chain.radio->get_tx_frequency(tx_chain.block_chan);
1829         };
1830         auto set_dsp_freq = [tx_chain](double freq) {
1831             (tx_chain.duc) ? tx_chain.duc->set_freq(freq, tx_chain.block_chan) : 0;
1832         };
1833         auto get_dsp_freq = [tx_chain](void) {
1834             return (tx_chain.duc) ? tx_chain.duc->get_freq(tx_chain.block_chan) : 0.0;
1835         };
1836         return tune_xx_subdev_and_dsp(TX_SIGN,
1837             tune_range,
1838             rf_range,
1839             dsp_range,
1840             set_rf_freq,
1841             get_rf_freq,
1842             set_dsp_freq,
1843             get_dsp_freq,
1844             tune_request);
1845     }
1846 
get_tx_freq(size_t chan=0)1847     double get_tx_freq(size_t chan = 0)
1848     {
1849         auto& tx_chain = _get_tx_chan(chan);
1850         // extract actual dsp and IF frequencies
1851         const double actual_rf_freq =
1852             tx_chain.radio->get_tx_frequency(tx_chain.block_chan);
1853         const double actual_dsp_freq =
1854             (tx_chain.duc) ? tx_chain.duc->get_freq(tx_chain.block_chan) : 0.0;
1855 
1856         return actual_rf_freq - actual_dsp_freq * TX_SIGN;
1857     }
1858 
get_tx_freq_range(size_t chan=0)1859     freq_range_t get_tx_freq_range(size_t chan = 0)
1860     {
1861         auto tx_chain = _get_tx_chan(chan);
1862         return (tx_chain.duc)
1863                    ? make_overall_tune_range(get_fe_tx_freq_range(chan),
1864                          tx_chain.duc->get_frequency_range(tx_chain.block_chan),
1865                          tx_chain.radio->get_tx_bandwidth(tx_chain.block_chan))
1866                    : get_fe_tx_freq_range(chan);
1867     }
1868 
get_fe_tx_freq_range(size_t chan=0)1869     freq_range_t get_fe_tx_freq_range(size_t chan = 0)
1870     {
1871         auto tx_chain = _get_tx_chan(chan);
1872         return tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan);
1873     }
1874 
set_tx_gain(double gain,const std::string & name,size_t chan=0)1875     void set_tx_gain(double gain, const std::string& name, size_t chan = 0)
1876     {
1877         MUX_TX_API_CALL(set_tx_gain, gain, name);
1878         auto tx_chain = _get_tx_chan(chan);
1879         tx_chain.radio->set_tx_gain(gain, name, tx_chain.block_chan);
1880     }
1881 
get_tx_gain_profile_names(const size_t chan=0)1882     std::vector<std::string> get_tx_gain_profile_names(const size_t chan = 0)
1883     {
1884         auto tx_chain = _get_tx_chan(chan);
1885         return tx_chain.radio->get_tx_gain_profile_names(tx_chain.block_chan);
1886     }
1887 
set_tx_gain_profile(const std::string & profile,const size_t chan=0)1888     void set_tx_gain_profile(const std::string& profile, const size_t chan = 0)
1889     {
1890         MUX_TX_API_CALL(set_tx_gain_profile, profile);
1891         auto tx_chain = _get_tx_chan(chan);
1892         tx_chain.radio->set_tx_gain_profile(profile, tx_chain.block_chan);
1893     }
1894 
get_tx_gain_profile(const size_t chan=0)1895     std::string get_tx_gain_profile(const size_t chan = 0)
1896     {
1897         auto tx_chain = _get_tx_chan(chan);
1898         return tx_chain.radio->get_tx_gain_profile(tx_chain.block_chan);
1899     }
1900 
set_normalized_tx_gain(double gain,size_t chan=0)1901     void set_normalized_tx_gain(double gain, size_t chan = 0)
1902     {
1903         if (gain > 1.0 || gain < 0.0) {
1904             throw uhd::runtime_error("Normalized gain out of range, must be in [0, 1].");
1905         }
1906         MUX_TX_API_CALL(set_normalized_tx_gain, gain);
1907         gain_range_t gain_range = get_tx_gain_range(ALL_GAINS, chan);
1908         double abs_gain =
1909             (gain * (gain_range.stop() - gain_range.start())) + gain_range.start();
1910         set_tx_gain(abs_gain, ALL_GAINS, chan);
1911     }
1912 
get_tx_gain(const std::string & name,size_t chan=0)1913     double get_tx_gain(const std::string& name, size_t chan = 0)
1914     {
1915         auto tx_chain = _get_tx_chan(chan);
1916         return tx_chain.radio->get_tx_gain(name, tx_chain.block_chan);
1917     }
1918 
get_normalized_tx_gain(size_t chan=0)1919     double get_normalized_tx_gain(size_t chan = 0)
1920     {
1921         gain_range_t gain_range       = get_tx_gain_range(ALL_GAINS, chan);
1922         const double gain_range_width = gain_range.stop() - gain_range.start();
1923         // In case we have a device without a range of gains:
1924         if (gain_range_width == 0.0) {
1925             return 0;
1926         }
1927         const double norm_gain =
1928             (get_tx_gain(ALL_GAINS, chan) - gain_range.start()) / gain_range_width;
1929         // Avoid rounding errors:
1930         return std::max(std::min(norm_gain, 1.0), 0.0);
1931     }
1932 
get_tx_gain_range(const std::string & name,size_t chan=0)1933     gain_range_t get_tx_gain_range(const std::string& name, size_t chan = 0)
1934     {
1935         auto tx_chain = _get_tx_chan(chan);
1936         return tx_chain.radio->get_tx_gain_range(name, tx_chain.block_chan);
1937     }
1938 
get_tx_gain_names(size_t chan=0)1939     std::vector<std::string> get_tx_gain_names(size_t chan = 0)
1940     {
1941         auto tx_chain = _get_tx_chan(chan);
1942         return tx_chain.radio->get_tx_gain_names(tx_chain.block_chan);
1943     }
1944 
has_tx_power_reference(const size_t chan=0)1945     bool has_tx_power_reference(const size_t chan = 0)
1946     {
1947         auto& tx_chain = _get_tx_chan(chan);
1948         return tx_chain.radio->has_rx_power_reference(tx_chain.block_chan);
1949     }
1950 
set_tx_power_reference(const double power_dbm,const size_t chan=0)1951     void set_tx_power_reference(const double power_dbm, const size_t chan = 0)
1952     {
1953         MUX_TX_API_CALL(set_tx_power_reference, power_dbm);
1954         auto& tx_chain = _get_tx_chan(chan);
1955         tx_chain.radio->set_tx_power_reference(power_dbm, tx_chain.block_chan);
1956     }
1957 
get_tx_power_reference(const size_t chan=0)1958     double get_tx_power_reference(const size_t chan = 0)
1959     {
1960         auto& tx_chain = _get_tx_chan(chan);
1961         return tx_chain.radio->get_tx_power_reference(tx_chain.block_chan);
1962     }
1963 
get_tx_power_range(const size_t chan)1964     meta_range_t get_tx_power_range(const size_t chan)
1965     {
1966         auto& tx_chain = _get_tx_chan(chan);
1967         return tx_chain.radio->get_tx_power_range(tx_chain.block_chan);
1968     }
1969 
set_tx_antenna(const std::string & ant,size_t chan=0)1970     void set_tx_antenna(const std::string& ant, size_t chan = 0)
1971     {
1972         MUX_TX_API_CALL(set_tx_antenna, ant);
1973         auto tx_chain = _get_tx_chan(chan);
1974         tx_chain.radio->set_tx_antenna(ant, tx_chain.block_chan);
1975     }
1976 
get_tx_antenna(size_t chan=0)1977     std::string get_tx_antenna(size_t chan = 0)
1978     {
1979         auto& tx_chain = _get_tx_chan(chan);
1980         return tx_chain.radio->get_tx_antenna(tx_chain.block_chan);
1981     }
1982 
get_tx_antennas(size_t chan=0)1983     std::vector<std::string> get_tx_antennas(size_t chan = 0)
1984     {
1985         auto& tx_chain = _get_tx_chan(chan);
1986         return tx_chain.radio->get_tx_antennas(tx_chain.block_chan);
1987     }
1988 
set_tx_bandwidth(double bandwidth,size_t chan=0)1989     void set_tx_bandwidth(double bandwidth, size_t chan = 0)
1990     {
1991         MUX_TX_API_CALL(set_tx_bandwidth, bandwidth);
1992         auto tx_chain = _get_tx_chan(chan);
1993         tx_chain.radio->set_tx_bandwidth(bandwidth, tx_chain.block_chan);
1994     }
1995 
get_tx_bandwidth(size_t chan=0)1996     double get_tx_bandwidth(size_t chan = 0)
1997     {
1998         auto tx_chain = _get_tx_chan(chan);
1999         return tx_chain.radio->get_tx_bandwidth(tx_chain.block_chan);
2000     }
2001 
get_tx_bandwidth_range(size_t chan=0)2002     meta_range_t get_tx_bandwidth_range(size_t chan = 0)
2003     {
2004         auto tx_chain = _get_tx_chan(chan);
2005         return tx_chain.radio->get_tx_bandwidth_range(tx_chain.block_chan);
2006     }
2007 
get_tx_dboard_iface(size_t chan=0)2008     dboard_iface::sptr get_tx_dboard_iface(size_t chan = 0)
2009     {
2010         auto& tx_chain = _get_tx_chan(chan);
2011         return tx_chain.radio->get_tree()->access<dboard_iface::sptr>("iface").get();
2012     }
2013 
get_tx_sensor(const std::string & name,size_t chan=0)2014     sensor_value_t get_tx_sensor(const std::string& name, size_t chan = 0)
2015     {
2016         auto tx_chain = _get_tx_chan(chan);
2017         return tx_chain.radio->get_tx_sensor(name, tx_chain.block_chan);
2018     }
2019 
get_tx_sensor_names(size_t chan=0)2020     std::vector<std::string> get_tx_sensor_names(size_t chan = 0)
2021     {
2022         auto tx_chain = _get_tx_chan(chan);
2023         return tx_chain.radio->get_tx_sensor_names(tx_chain.block_chan);
2024     }
2025 
set_tx_dc_offset(const std::complex<double> & offset,size_t chan=ALL_CHANS)2026     void set_tx_dc_offset(const std::complex<double>& offset, size_t chan = ALL_CHANS)
2027     {
2028         MUX_TX_API_CALL(set_tx_dc_offset, offset);
2029         const auto tx_chain = _get_tx_chan(chan);
2030         tx_chain.radio->set_tx_dc_offset(offset, tx_chain.block_chan);
2031     }
2032 
get_tx_dc_offset_range(size_t chan=0)2033     meta_range_t get_tx_dc_offset_range(size_t chan = 0)
2034     {
2035         auto tx_chain = _get_tx_chan(chan);
2036         return tx_chain.radio->get_tx_dc_offset_range(tx_chain.block_chan);
2037     }
2038 
set_tx_iq_balance(const std::complex<double> & correction,size_t chan=ALL_CHANS)2039     void set_tx_iq_balance(
2040         const std::complex<double>& correction, size_t chan = ALL_CHANS)
2041     {
2042         MUX_TX_API_CALL(set_tx_iq_balance, correction);
2043         const auto tx_chain = _get_tx_chan(chan);
2044         tx_chain.radio->set_tx_iq_balance(correction, tx_chain.block_chan);
2045     }
2046 
2047     /*******************************************************************
2048      * GPIO methods
2049      ******************************************************************/
2050     /*! Helper function to identify the radio and the bank on that radio.
2051      *
2052      * Background: Historically, multi_usrp has made up its own GPIO bank names,
2053      * unrelated to the radios. Now, we need to be a bit more clever to get that
2054      * legacy behaviour to work.
2055      *
2056      * Here's the algorithm:
2057      * - If the bank ends with 'A' or 'B' we'll use that to identify the radio
2058      * - Otherwise, we'll pick the first radio
2059      * - If the bank ends with 'A' or 'B' we strip that suffix
2060      *
2061      * The returned radio will now have a GPIO bank with the returned name.
2062      */
_get_gpio_radio_bank(const std::string & bank,const size_t mboard)2063     std::pair<uhd::rfnoc::radio_control::sptr, std::string> _get_gpio_radio_bank(
2064         const std::string& bank, const size_t mboard)
2065     {
2066         UHD_ASSERT_THROW(!bank.empty());
2067         char suffix = bank[bank.size() - 1];
2068         std::string slot_name;
2069         if (suffix == 'A' || suffix == 'a') {
2070             slot_name = "A";
2071         }
2072         if (suffix == 'B' || suffix == 'b') {
2073             slot_name = "B";
2074         }
2075 
2076         uhd::rfnoc::radio_control::sptr radio = [bank, mboard, slot_name, this]() {
2077             auto radio_blocks = _graph->find_blocks<uhd::rfnoc::radio_control>(
2078                 std::to_string(mboard) + "/Radio");
2079             for (auto radio_id : radio_blocks) {
2080                 auto radio = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
2081                 if (slot_name.empty() || radio->get_slot_name() == slot_name) {
2082                     return radio;
2083                 }
2084             }
2085             throw uhd::runtime_error(std::string("Could not match GPIO bank ") + bank
2086                                      + " to a radio block controller.");
2087         }();
2088 
2089         const std::string normalized_bank = [radio, bank]() {
2090             auto radio_banks = radio->get_gpio_banks();
2091             for (auto& radio_bank : radio_banks) {
2092                 if (bank.find(radio_bank) == 0) {
2093                     return radio_bank;
2094                 }
2095             }
2096             throw uhd::runtime_error(std::string("Could not match GPIO bank ") + bank
2097                                      + " to radio " + radio->get_unique_id());
2098         }();
2099 
2100         return {radio, normalized_bank};
2101     }
2102 
get_gpio_banks(const size_t mboard)2103     std::vector<std::string> get_gpio_banks(const size_t mboard)
2104     {
2105         auto radio_blocks = _graph->find_blocks<uhd::rfnoc::radio_control>(
2106             std::to_string(mboard) + "/Radio");
2107         std::vector<std::string> gpio_banks;
2108         for (auto radio_id : radio_blocks) {
2109             auto radio       = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
2110             auto radio_banks = radio->get_gpio_banks();
2111             for (const auto& bank : radio_banks) {
2112                 gpio_banks.push_back(bank + radio->get_slot_name());
2113             }
2114         }
2115 
2116         return gpio_banks;
2117     }
2118 
set_gpio_attr(const std::string & bank,const std::string & attr,const uint32_t value,const uint32_t mask=0xffffffff,const size_t mboard=0)2119     void set_gpio_attr(const std::string& bank,
2120         const std::string& attr,
2121         const uint32_t value,
2122         const uint32_t mask = 0xffffffff,
2123         const size_t mboard = 0)
2124     {
2125         auto radio_bank_pair = _get_gpio_radio_bank(bank, mboard);
2126         const uint32_t current =
2127             radio_bank_pair.first->get_gpio_attr(radio_bank_pair.second, attr);
2128         const uint32_t new_value = (current & ~mask) | (value & mask);
2129         radio_bank_pair.first->set_gpio_attr(radio_bank_pair.second, attr, new_value);
2130     }
2131 
get_gpio_attr(const std::string & bank,const std::string & attr,const size_t mboard=0)2132     uint32_t get_gpio_attr(
2133         const std::string& bank, const std::string& attr, const size_t mboard = 0)
2134     {
2135         auto radio_bank_pair = _get_gpio_radio_bank(bank, mboard);
2136         return radio_bank_pair.first->get_gpio_attr(radio_bank_pair.second, attr);
2137     }
2138 
get_gpio_src_banks(const size_t mboard=0)2139     std::vector<std::string> get_gpio_src_banks(const size_t mboard = 0)
2140     {
2141         return get_mbc(mboard)->get_gpio_banks();
2142     }
2143 
get_gpio_srcs(const std::string & bank,const size_t mboard=0)2144     std::vector<std::string> get_gpio_srcs(
2145         const std::string& bank, const size_t mboard = 0)
2146     {
2147         return get_mbc(mboard)->get_gpio_srcs(bank);
2148     }
2149 
get_gpio_src(const std::string & bank,const size_t mboard=0)2150     std::vector<std::string> get_gpio_src(
2151         const std::string& bank, const size_t mboard = 0)
2152     {
2153         return get_mbc(mboard)->get_gpio_src(bank);
2154     }
2155 
set_gpio_src(const std::string & bank,const std::vector<std::string> & src,const size_t mboard=0)2156     void set_gpio_src(const std::string& bank,
2157         const std::vector<std::string>& src,
2158         const size_t mboard = 0)
2159     {
2160         get_mbc(mboard)->set_gpio_src(bank, src);
2161     }
2162 
2163     /*******************************************************************
2164      * Filter API methods
2165      ******************************************************************/
get_rx_filter_names(const size_t chan)2166     std::vector<std::string> get_rx_filter_names(const size_t chan)
2167     {
2168         std::vector<std::string> filter_names;
2169         // Grab the Radio's filters
2170         auto rx_chan    = _get_rx_chan(chan);
2171         auto radio_id   = rx_chan.radio->get_block_id();
2172         auto radio_ctrl = std::dynamic_pointer_cast<detail::filter_node>(rx_chan.radio);
2173         if (radio_ctrl) {
2174             auto radio_filters = radio_ctrl->get_rx_filter_names(rx_chan.block_chan);
2175             // Prepend the radio's block ID to each filter name
2176             std::transform(radio_filters.begin(),
2177                 radio_filters.end(),
2178                 radio_filters.begin(),
2179                 [radio_id](
2180                     std::string name) { return radio_id.to_string() + ":" + name; });
2181             // Add the radio's filter names to the return vector
2182             filter_names.insert(
2183                 filter_names.end(), radio_filters.begin(), radio_filters.end());
2184         } else {
2185             UHD_LOG_DEBUG("MULTI_USRP",
2186                 "Radio block " + radio_id.to_string() + " does not support filters");
2187         }
2188         // Grab the DDC's filter
2189         auto ddc_id   = rx_chan.ddc->get_block_id();
2190         auto ddc_ctrl = std::dynamic_pointer_cast<detail::filter_node>(rx_chan.ddc);
2191         if (ddc_ctrl) {
2192             auto ddc_filters = ddc_ctrl->get_rx_filter_names(rx_chan.block_chan);
2193             // Prepend the DDC's block ID to each filter name
2194             std::transform(ddc_filters.begin(),
2195                 ddc_filters.end(),
2196                 ddc_filters.begin(),
2197                 [ddc_id](std::string name) { return ddc_id.to_string() + ":" + name; });
2198             // Add the radio's filter names to the return vector
2199             filter_names.insert(
2200                 filter_names.end(), ddc_filters.begin(), ddc_filters.end());
2201         } else {
2202             UHD_LOG_DEBUG("MULTI_USRP",
2203                 "DDC block " + ddc_id.to_string() + " does not support filters");
2204         }
2205         return filter_names;
2206     }
2207 
get_rx_filter(const std::string & name,const size_t chan)2208     uhd::filter_info_base::sptr get_rx_filter(const std::string& name, const size_t chan)
2209     {
2210         try {
2211             // The block_id_t constructor is pretty smart; let it handle the parsing.
2212             block_id_t block_id(name);
2213             auto rx_chan = _get_rx_chan(chan);
2214             // The filter name is the `name` after the BLOCK_ID and a `:`
2215             std::string filter_name = name.substr(block_id.to_string().size() + 1);
2216             // Try to dynamic cast either the radio or the DDC to a filter_node, and call
2217             // its filter function
2218             auto block_ctrl = [rx_chan, block_id, chan]() -> noc_block_base::sptr {
2219                 if (block_id == rx_chan.radio->get_block_id()) {
2220                     return rx_chan.radio;
2221                 } else if (block_id == rx_chan.ddc->get_block_id()) {
2222                     return rx_chan.ddc;
2223                 } else {
2224                     throw uhd::runtime_error("Requested block " + block_id.to_string()
2225                                              + " does not match block ID in channel "
2226                                              + std::to_string(chan));
2227                 }
2228             }();
2229             auto filter_ctrl = std::dynamic_pointer_cast<detail::filter_node>(block_ctrl);
2230             if (filter_ctrl) {
2231                 return filter_ctrl->get_rx_filter(filter_name, rx_chan.block_chan);
2232             }
2233             std::string err_msg =
2234                 block_ctrl->get_block_id().to_string() + " does not support filters";
2235             UHD_LOG_ERROR("MULTI_USRP", err_msg);
2236             throw uhd::runtime_error(err_msg);
2237         } catch (const uhd::value_error&) {
2238             // Catch the error from the block_id_t constructor and add better logging
2239             UHD_LOG_ERROR("MULTI_USRP",
2240                 "Invalid filter name; could not determine block controller from name: "
2241                     + name);
2242             throw;
2243         }
2244     }
2245 
set_rx_filter(const std::string & name,uhd::filter_info_base::sptr filter,const size_t chan)2246     void set_rx_filter(
2247         const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan)
2248     {
2249         MUX_RX_API_CALL(set_rx_filter, name, filter);
2250         try {
2251             // The block_id_t constructor is pretty smart; let it handle the parsing.
2252             block_id_t block_id(name);
2253             auto rx_chan = _get_rx_chan(chan);
2254             // The filter name is the `name` after the BLOCK_ID and a `:`
2255             std::string filter_name = name.substr(block_id.to_string().size() + 1);
2256             // Try to dynamic cast either the radio or the DDC to a filter_node, and call
2257             // its filter function
2258             auto block_ctrl = [rx_chan, block_id, chan]() -> noc_block_base::sptr {
2259                 if (block_id == rx_chan.radio->get_block_id()) {
2260                     return rx_chan.radio;
2261                 } else if (block_id == rx_chan.ddc->get_block_id()) {
2262                     return rx_chan.ddc;
2263                 } else {
2264                     throw uhd::runtime_error("Requested block " + block_id.to_string()
2265                                              + " does not match block ID in channel "
2266                                              + std::to_string(chan));
2267                 }
2268             }();
2269             auto filter_ctrl = std::dynamic_pointer_cast<detail::filter_node>(block_ctrl);
2270             if (filter_ctrl) {
2271                 return filter_ctrl->set_rx_filter(
2272                     filter_name, filter, rx_chan.block_chan);
2273             }
2274             std::string err_msg =
2275                 block_ctrl->get_block_id().to_string() + " does not support filters";
2276             UHD_LOG_ERROR("MULTI_USRP", err_msg);
2277             throw uhd::runtime_error(err_msg);
2278         } catch (const uhd::value_error&) {
2279             // Catch the error from the block_id_t constructor and add better logging
2280             UHD_LOG_ERROR("MULTI_USRP",
2281                 "Invalid filter name; could not determine block controller from name: "
2282                     + name);
2283             throw;
2284         }
2285     }
2286 
get_tx_filter_names(const size_t chan)2287     std::vector<std::string> get_tx_filter_names(const size_t chan)
2288     {
2289         std::vector<std::string> filter_names;
2290         // Grab the Radio's filters
2291         auto tx_chan    = _get_tx_chan(chan);
2292         auto radio_id   = tx_chan.radio->get_block_id();
2293         auto radio_ctrl = std::dynamic_pointer_cast<detail::filter_node>(tx_chan.radio);
2294         if (radio_ctrl) {
2295             auto radio_filters = radio_ctrl->get_tx_filter_names(tx_chan.block_chan);
2296             // Prepend the radio's block ID to each filter name
2297             std::transform(radio_filters.begin(),
2298                 radio_filters.end(),
2299                 radio_filters.begin(),
2300                 [radio_id](
2301                     std::string name) { return radio_id.to_string() + ":" + name; });
2302             // Add the radio's filter names to the return vector
2303             filter_names.insert(
2304                 filter_names.end(), radio_filters.begin(), radio_filters.end());
2305         } else {
2306             UHD_LOG_DEBUG("MULTI_USRP",
2307                 "Radio block " + radio_id.to_string() + " does not support filters");
2308         }
2309         // Grab the DUC's filter
2310         auto duc_id   = tx_chan.duc->get_block_id();
2311         auto duc_ctrl = std::dynamic_pointer_cast<detail::filter_node>(tx_chan.duc);
2312         if (duc_ctrl) {
2313             auto duc_filters = duc_ctrl->get_tx_filter_names(tx_chan.block_chan);
2314             // Prepend the DUC's block ID to each filter name
2315             std::transform(duc_filters.begin(),
2316                 duc_filters.end(),
2317                 duc_filters.begin(),
2318                 [duc_id](std::string name) { return duc_id.to_string() + ":" + name; });
2319             // Add the radio's filter names to the return vector
2320             filter_names.insert(
2321                 filter_names.end(), duc_filters.begin(), duc_filters.end());
2322         } else {
2323             UHD_LOG_DEBUG("MULTI_USRP",
2324                 "DUC block " + duc_id.to_string() + " does not support filters");
2325         }
2326         return filter_names;
2327     }
2328 
get_tx_filter(const std::string & name,const size_t chan)2329     uhd::filter_info_base::sptr get_tx_filter(const std::string& name, const size_t chan)
2330     {
2331         try {
2332             // The block_id_t constructor is pretty smart; let it handle the parsing.
2333             block_id_t block_id(name);
2334             auto tx_chan = _get_tx_chan(chan);
2335             // The filter name is the `name` after the BLOCK_ID and a `:`
2336             std::string filter_name = name.substr(block_id.to_string().size() + 1);
2337             // Try to dynamic cast either the radio or the DUC to a filter_node, and call
2338             // its filter function
2339             auto block_ctrl = [tx_chan, block_id, chan]() -> noc_block_base::sptr {
2340                 if (block_id == tx_chan.radio->get_block_id()) {
2341                     return tx_chan.radio;
2342                 } else if (block_id == tx_chan.duc->get_block_id()) {
2343                     return tx_chan.duc;
2344                 } else {
2345                     throw uhd::runtime_error("Requested block " + block_id.to_string()
2346                                              + " does not match block ID in channel "
2347                                              + std::to_string(chan));
2348                 }
2349             }();
2350             auto filter_ctrl = std::dynamic_pointer_cast<detail::filter_node>(block_ctrl);
2351             if (filter_ctrl) {
2352                 return filter_ctrl->get_tx_filter(filter_name, tx_chan.block_chan);
2353             }
2354             std::string err_msg =
2355                 block_ctrl->get_block_id().to_string() + " does not support filters";
2356             UHD_LOG_ERROR("MULTI_USRP", err_msg);
2357             throw uhd::runtime_error(err_msg);
2358         } catch (const uhd::value_error&) {
2359             // Catch the error from the block_id_t constructor and add better logging
2360             UHD_LOG_ERROR("MULTI_USRP",
2361                 "Invalid filter name; could not determine block controller from name: "
2362                     + name);
2363             throw;
2364         }
2365     }
2366 
set_tx_filter(const std::string & name,uhd::filter_info_base::sptr filter,const size_t chan)2367     void set_tx_filter(
2368         const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan)
2369     {
2370         MUX_TX_API_CALL(set_tx_filter, name, filter);
2371         try {
2372             // The block_id_t constructor is pretty smart; let it handle the parsing.
2373             block_id_t block_id(name);
2374             auto tx_chan = _get_tx_chan(chan);
2375             // The filter name is the `name` after the BLOCK_ID and a `:`
2376             std::string filter_name = name.substr(block_id.to_string().size() + 1);
2377             // Try to dynamic cast either the radio or the DUC to a filter_node, and call
2378             // its filter function
2379             auto block_ctrl = [tx_chan, block_id, chan]() -> noc_block_base::sptr {
2380                 if (block_id == tx_chan.radio->get_block_id()) {
2381                     return tx_chan.radio;
2382                 } else if (block_id == tx_chan.duc->get_block_id()) {
2383                     return tx_chan.duc;
2384                 } else {
2385                     throw uhd::runtime_error("Requested block " + block_id.to_string()
2386                                              + " does not match block ID in channel "
2387                                              + std::to_string(chan));
2388                 }
2389             }();
2390             auto filter_ctrl = std::dynamic_pointer_cast<detail::filter_node>(block_ctrl);
2391             if (filter_ctrl) {
2392                 return filter_ctrl->set_tx_filter(
2393                     filter_name, filter, tx_chan.block_chan);
2394             }
2395             std::string err_msg =
2396                 block_ctrl->get_block_id().to_string() + " does not support filters";
2397             UHD_LOG_ERROR("MULTI_USRP", err_msg);
2398             throw uhd::runtime_error(err_msg);
2399         } catch (const uhd::value_error&) {
2400             // Catch the error from the block_id_t constructor and add better logging
2401             UHD_LOG_ERROR("MULTI_USRP",
2402                 "Invalid filter name; could not determine block controller from name: "
2403                     + name);
2404             throw;
2405         }
2406     }
2407 
2408 private:
2409     /**************************************************************************
2410      * Private Helpers
2411      *************************************************************************/
get_mbc(const size_t mb_idx)2412     mb_controller::sptr get_mbc(const size_t mb_idx)
2413     {
2414         if (mb_idx >= get_num_mboards()) {
2415             throw uhd::key_error(
2416                 std::string("No such mboard: ") + std::to_string(mb_idx));
2417         }
2418         return _graph->get_mb_controller(mb_idx);
2419     }
2420 
_get_rx_chan(const size_t chan)2421     rx_chan_t& _get_rx_chan(const size_t chan)
2422     {
2423         if (!_rx_chans.count(chan)) {
2424             throw uhd::key_error(
2425                 std::string("Invalid RX channel: ") + std::to_string(chan));
2426         }
2427         return _rx_chans.at(chan);
2428     }
2429 
_get_tx_chan(const size_t chan)2430     tx_chan_t& _get_tx_chan(const size_t chan)
2431     {
2432         if (!_tx_chans.count(chan)) {
2433             throw uhd::key_error(
2434                 std::string("Invalid TX channel: ") + std::to_string(chan));
2435         }
2436         return _tx_chans.at(chan);
2437     }
2438 
_connect_rx_chain(size_t chan)2439     void _connect_rx_chain(size_t chan)
2440     {
2441         auto rx_chan = _rx_chans.at(chan);
2442         for (auto edge : rx_chan.edge_list) {
2443             if (block_id_t(edge.dst_blockid).match(NODE_ID_SEP)) {
2444                 break;
2445             }
2446             UHD_LOG_TRACE(
2447                 "MULTI_USRP", std::string("Connecting RX edge: ") + edge.to_string());
2448             _graph->connect(
2449                 edge.src_blockid, edge.src_port, edge.dst_blockid, edge.dst_port);
2450         }
2451     }
2452 
_connect_rx_chains(std::vector<size_t> chans)2453     void _connect_rx_chains(std::vector<size_t> chans)
2454     {
2455         for (auto chan : chans) {
2456             _connect_rx_chain(chan);
2457         }
2458     }
2459 
_connect_tx_chain(size_t chan)2460     void _connect_tx_chain(size_t chan)
2461     {
2462         auto tx_chan = _tx_chans.at(chan);
2463         for (auto edge : tx_chan.edge_list) {
2464             if (block_id_t(edge.src_blockid).match(NODE_ID_SEP)) {
2465                 break;
2466             }
2467             UHD_LOG_TRACE(
2468                 "MULTI_USRP", std::string("Connecting TX edge: ") + edge.to_string());
2469             _graph->connect(
2470                 edge.src_blockid, edge.src_port, edge.dst_blockid, edge.dst_port);
2471         }
2472     }
2473 
_connect_tx_chains(std::vector<size_t> chans)2474     void _connect_tx_chains(std::vector<size_t> chans)
2475     {
2476         for (auto chan : chans) {
2477             _connect_tx_chain(chan);
2478         }
2479     }
2480 
_disconnect_rx_chain(size_t chan)2481     void _disconnect_rx_chain(size_t chan)
2482     {
2483         auto rx_chan = _rx_chans.at(chan);
2484         for (auto edge : rx_chan.edge_list) {
2485             if (block_id_t(edge.dst_blockid).match(NODE_ID_SEP)) {
2486                 break;
2487             }
2488             UHD_LOG_TRACE(
2489                 "MULTI_USRP", std::string("Disconnecting RX edge: ") + edge.to_string());
2490             _graph->disconnect(
2491                 edge.src_blockid, edge.src_port, edge.dst_blockid, edge.dst_port);
2492         }
2493     }
2494 
_disconnect_rx_chains(std::vector<size_t> chans)2495     void _disconnect_rx_chains(std::vector<size_t> chans)
2496     {
2497         for (auto chan : chans) {
2498             _disconnect_rx_chain(chan);
2499         }
2500     }
2501 
_disconnect_tx_chain(size_t chan)2502     void _disconnect_tx_chain(size_t chan)
2503     {
2504         auto tx_chan = _tx_chans.at(chan);
2505         for (auto edge : tx_chan.edge_list) {
2506             if (block_id_t(edge.src_blockid).match(NODE_ID_SEP)) {
2507                 break;
2508             }
2509             UHD_LOG_TRACE(
2510                 "MULTI_USRP", std::string("Disconnecting TX edge: ") + edge.to_string());
2511             _graph->disconnect(
2512                 edge.src_blockid, edge.src_port, edge.dst_blockid, edge.dst_port);
2513         }
2514     }
2515 
_disconnect_tx_chains(std::vector<size_t> chans)2516     void _disconnect_tx_chains(std::vector<size_t> chans)
2517     {
2518         for (auto chan : chans) {
2519             _disconnect_tx_chain(chan);
2520         }
2521     }
2522 
2523     /**************************************************************************
2524      * Private Attributes
2525      *************************************************************************/
2526     //! Devices args used to spawn this multi_usrp
2527     const uhd::device_addr_t _args;
2528     //! Reference to rfnoc_graph
2529     rfnoc_graph::sptr _graph;
2530     //! Reference to the prop tree
2531     property_tree::sptr _tree;
2532     //! Mapping between channel number and the RFNoC blocks in that RX chain
2533     std::unordered_map<size_t, rx_chan_t> _rx_chans;
2534     //! Mapping between channel number and the RFNoC blocks in that TX chain
2535     std::unordered_map<size_t, tx_chan_t> _tx_chans;
2536     //! Cache the requested RX rates
2537     std::unordered_map<size_t, double> _rx_rates;
2538     //! Cache the requested TX rates
2539     std::unordered_map<size_t, double> _tx_rates;
2540 
2541     std::recursive_mutex _graph_mutex;
2542 
2543     std::shared_ptr<redirector_device> _device;
2544 };
2545 
2546 /******************************************************************************
2547  * Factory
2548  *****************************************************************************/
2549 namespace uhd { namespace rfnoc { namespace detail {
2550 // Forward declare
2551 rfnoc_graph::sptr make_rfnoc_graph(
2552     detail::rfnoc_device::sptr dev, const uhd::device_addr_t& device_addr);
2553 
make_rfnoc_device(detail::rfnoc_device::sptr rfnoc_device,const uhd::device_addr_t & dev_addr)2554 multi_usrp::sptr make_rfnoc_device(
2555     detail::rfnoc_device::sptr rfnoc_device, const uhd::device_addr_t& dev_addr)
2556 {
2557     auto graph = uhd::rfnoc::detail::make_rfnoc_graph(rfnoc_device, dev_addr);
2558     return std::make_shared<multi_usrp_rfnoc>(graph, dev_addr);
2559 }
2560 
2561 }}} // namespace uhd::rfnoc::detail
2562