// // Copyright 2016 Ettus Research LLC // Copyright 2018 Ettus Research, a National Instruments Company // // SPDX-License-Identifier: GPL-3.0-or-later // #include "../usrp/device3/device3_impl.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define UHD_LEGACY_LOG() UHD_LOGGER_TRACE("RFNOC") using namespace uhd::rfnoc; using uhd::stream_cmd_t; using uhd::usrp::subdev_spec_pair_t; using uhd::usrp::subdev_spec_t; /************************************************************************ * Constants and globals ***********************************************************************/ static const std::string RADIO_BLOCK_NAME = "Radio"; static const std::string DFIFO_BLOCK_NAME = "DmaFIFO"; static const std::string SFIFO_BLOCK_NAME = "FIFO"; static const std::string DDC_BLOCK_NAME = "DDC"; static const std::string DUC_BLOCK_NAME = "DUC"; static const size_t MAX_BYTES_PER_HEADER = uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t); static const size_t BYTES_PER_SAMPLE = 4; // We currently only support sc16 static boost::mutex _make_mutex; static const std::vector LEGACY_BLOCKS_LIST = { RADIO_BLOCK_NAME, DFIFO_BLOCK_NAME, SFIFO_BLOCK_NAME, DDC_BLOCK_NAME, DUC_BLOCK_NAME}; typedef std::vector source_block_list_t; typedef std::vector sink_block_list_t; typedef std::map> block_name_to_block_map_t; typedef std::pair source_port_t; typedef std::pair sink_port_t; /************************************************************************ * Static helpers ***********************************************************************/ static uhd::fs_path mb_root(const size_t mboard) { return uhd::fs_path("/mboards") / mboard; } size_t num_ports(const uhd::property_tree::sptr& tree, const std::string& block_name, const std::string& in_out) { return tree ->list(uhd::fs_path("/mboards/0/xbar") / str(boost::format("%s_0") % block_name) / "ports" / in_out) .size(); } size_t calc_num_tx_chans_per_radio(const uhd::property_tree::sptr& tree, const size_t num_radios_per_board, const bool has_ducs, const bool has_dmafifo) { const size_t num_radio_ports = num_ports(tree, RADIO_BLOCK_NAME, "in"); if (has_ducs) { return std::min(num_radio_ports, num_ports(tree, DUC_BLOCK_NAME, "in")); } if (not has_dmafifo) { return num_radio_ports; } const size_t num_dmafifo_ports_per_radio = num_ports(tree, DFIFO_BLOCK_NAME, "in") / num_radios_per_board; UHD_ASSERT_THROW(num_dmafifo_ports_per_radio); return std::min(num_radio_ports, num_dmafifo_ports_per_radio); } /*! Recreate passed property without bound subscribers. Maintains current property value. */ template static void recreate_property(const uhd::fs_path& path, uhd::property_tree::sptr& tree) { T temp = tree->access(path).get(); tree->remove(path); tree->create(path).set(temp); } /************************************************************************ * Class Definition ***********************************************************************/ class legacy_compat_impl : public legacy_compat { public: /************************************************************************ * Structors and Initialization ***********************************************************************/ legacy_compat_impl(uhd::device3::sptr device, const uhd::device_addr_t& args) : _device(device) , _tree(device->get_tree()) , _has_ducs(not args.has_key("skip_duc") and not device->find_blocks(DUC_BLOCK_NAME).empty()) , _has_ddcs(not args.has_key("skip_ddc") and not device->find_blocks(DDC_BLOCK_NAME).empty()) , _has_dmafifo(not args.has_key("skip_dram") and not device->find_blocks(DFIFO_BLOCK_NAME).empty()) , _has_sramfifo(not args.has_key("skip_sram") and not device->find_blocks(SFIFO_BLOCK_NAME).empty()) , _num_mboards(_tree->list("/mboards").size()) , _num_radios_per_board(device->find_blocks("0/Radio").size()) , // These might throw, maybe we catch that and provide a nicer error message. _num_tx_chans_per_radio(calc_num_tx_chans_per_radio( _tree, _num_radios_per_board, _has_ducs, _has_dmafifo)) , _num_rx_chans_per_radio( _has_ddcs ? std::min(num_ports(_tree, RADIO_BLOCK_NAME, "out"), num_ports(_tree, DDC_BLOCK_NAME, "out")) : num_ports(_tree, RADIO_BLOCK_NAME, "out")) , _rx_spp(get_block_ctrl(0, RADIO_BLOCK_NAME, 0)->get_arg("spp")) , _tx_spp(_rx_spp) , _rx_channel_map(_num_mboards, std::vector()) , _tx_channel_map(_num_mboards, std::vector()) { _device->clear(); check_available_periphs(); // Throws if invalid configuration. setup_prop_tree(); if (_tree->exists("/mboards/0/mtu/send")) { _tx_spp = (_tree->access("/mboards/0/mtu/send").get() - MAX_BYTES_PER_HEADER) / BYTES_PER_SAMPLE; } connect_blocks(); if (args.has_key("skip_ddc")) { UHD_LEGACY_LOG() << "[legacy_compat] Skipping DDCs by user request."; } else if (not _has_ddcs) { UHD_LOGGER_WARNING("RFNOC") << "[legacy_compat] No DDCs detected. You will only be able to receive " "at the radio frontend rate."; } if (args.has_key("skip_duc")) { UHD_LEGACY_LOG() << "[legacy_compat] Skipping DUCs by user request."; } else if (not _has_ducs) { UHD_LOGGER_WARNING("RFNOC") << "[legacy_compat] No DUCs detected. You will only be able to transmit " "at the radio frontend rate."; } if (args.has_key("skip_dram")) { UHD_LEGACY_LOG() << "[legacy_compat] Skipping DRAM by user request."; } if (args.has_key("skip_sram")) { UHD_LEGACY_LOG() << "[legacy_compat] Skipping SRAM by user request."; } if (not _has_dmafifo and not _has_sramfifo) { UHD_LOGGER_WARNING("RFNOC") << "[legacy_compat] No FIFO detected. Higher " "transmit rates may encounter errors."; } for (size_t mboard = 0; mboard < _num_mboards; mboard++) { for (size_t radio = 0; radio < _num_radios_per_board; radio++) { auto radio_block_ctrl = get_block_ctrl(mboard, "Radio", radio); for (size_t port = 0; port < _num_rx_chans_per_radio; port++) { if (!radio_block_ctrl->get_dboard_fe_from_chan( port, uhd::RX_DIRECTION) .empty()) { _rx_channel_map[mboard].push_back({radio, port}); } } for (size_t port = 0; port < _num_tx_chans_per_radio; port++) { if (!radio_block_ctrl->get_dboard_fe_from_chan( port, uhd::TX_DIRECTION) .empty()) { _tx_channel_map[mboard].push_back({radio, port}); } } } update_sample_rate_on_blocks(mboard); } } ~legacy_compat_impl() { remove_prop_subscribers(); } /************************************************************************ * API Calls ***********************************************************************/ inline uhd::fs_path rx_dsp_root( const size_t mboard_idx, const size_t dsp_index, const size_t port_index) { return mb_root(mboard_idx) / "xbar" / str(boost::format("%s_%d") % DDC_BLOCK_NAME % dsp_index) / "legacy_api" / port_index; } uhd::fs_path rx_dsp_root(const size_t mboard_idx, const size_t chan) { // The DSP index is the same as the radio index size_t dsp_index = _rx_channel_map[mboard_idx][chan].radio_index; size_t port_index = _rx_channel_map[mboard_idx][chan].port_index; if (not _has_ddcs) { return mb_root(mboard_idx) / "rx_dsps" / dsp_index / port_index; } return rx_dsp_root(mboard_idx, dsp_index, port_index); } inline uhd::fs_path tx_dsp_root( const size_t mboard_idx, const size_t dsp_index, const size_t port_index) { return mb_root(mboard_idx) / "xbar" / str(boost::format("%s_%d") % DUC_BLOCK_NAME % dsp_index) / "legacy_api" / port_index; } uhd::fs_path tx_dsp_root(const size_t mboard_idx, const size_t chan) { // The DSP index is the same as the radio index size_t dsp_index = _tx_channel_map[mboard_idx][chan].radio_index; size_t port_index = _tx_channel_map[mboard_idx][chan].port_index; if (not _has_ducs) { return mb_root(mboard_idx) / "tx_dsps" / dsp_index / port_index; } return tx_dsp_root(mboard_idx, dsp_index, port_index); } uhd::fs_path rx_fe_root(const size_t mboard_idx, const size_t chan) { size_t radio_index = _rx_channel_map[mboard_idx][chan].radio_index; size_t port_index = _rx_channel_map[mboard_idx][chan].port_index; return uhd::fs_path( str(boost::format("/mboards/%d/xbar/%s_%d/rx_fe_corrections/%d/") % mboard_idx % RADIO_BLOCK_NAME % radio_index % port_index)); } uhd::fs_path tx_fe_root(const size_t mboard_idx, const size_t chan) { size_t radio_index = _tx_channel_map[mboard_idx][chan].radio_index; size_t port_index = _tx_channel_map[mboard_idx][chan].port_index; return uhd::fs_path( str(boost::format("/mboards/%d/xbar/%s_%d/tx_fe_corrections/%d/") % mboard_idx % RADIO_BLOCK_NAME % radio_index % port_index)); } //! Get all legacy blocks from the LEGACY_BLOCK_LIST return in a form of // {BLOCK_NAME: <{source_block_pointer},{sink_block_pointer}>} block_name_to_block_map_t get_legacy_blocks(uhd::device3::sptr _device) { block_name_to_block_map_t result; for (auto each_block_name : LEGACY_BLOCKS_LIST) { std::vector block_list = _device->find_blocks(each_block_name); std::pair ss_pair; source_block_list_t src_list; sink_block_list_t snk_list; for (auto each_block : block_list) { uhd::rfnoc::source_block_ctrl_base::sptr src = _device->get_block_ctrl(each_block); src_list.push_back(src); uhd::rfnoc::sink_block_ctrl_base::sptr snk = _device->get_block_ctrl(each_block); snk_list.push_back(snk); } ss_pair = std::make_pair(src_list, snk_list); result[each_block_name] = ss_pair; } return result; } void issue_stream_cmd(const stream_cmd_t& stream_cmd, size_t mboard, size_t chan) { UHD_LEGACY_LOG() << "[legacy_compat] issue_stream_cmd() "; const size_t& radio_index = _rx_channel_map[mboard][chan].radio_index; const size_t& port_index = _rx_channel_map[mboard][chan].port_index; if (_has_ddcs) { get_block_ctrl(mboard, DDC_BLOCK_NAME, radio_index) ->issue_stream_cmd(stream_cmd, port_index); } else { get_block_ctrl(mboard, RADIO_BLOCK_NAME, radio_index) ->issue_stream_cmd(stream_cmd, port_index); } } //! Sets block_id and block_port in the streamer args, otherwise forwards the //! call uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t& args_) { uhd::stream_args_t args(args_); if (args.otw_format.empty()) { args.otw_format = "sc16"; } _update_stream_args_for_streaming(args, _rx_channel_map); UHD_LEGACY_LOG() << "[legacy_compat] rx stream args: " << args.args.to_string(); uhd::rx_streamer::sptr streamer = _device->get_rx_stream(args); for (const size_t chan : args.channels) { _rx_stream_cache[chan] = streamer; } return streamer; } //! Sets block_id and block_port in the streamer args, otherwise forwards the //! call. // If spp is in the args, update the radios. If it's not set, copy the value from the // radios. uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t& args_) { uhd::stream_args_t args(args_); if (args.otw_format.empty()) { args.otw_format = "sc16"; } _update_stream_args_for_streaming(args, _tx_channel_map); UHD_LEGACY_LOG() << "[legacy_compat] tx stream args: " << args.args.to_string(); uhd::tx_streamer::sptr streamer = _device->get_tx_stream(args); for (const size_t chan : args.channels) { _tx_stream_cache[chan] = streamer; } return streamer; } double get_tick_rate(const size_t mboard_idx = 0) { return _tree->access(mb_root(mboard_idx) / "tick_rate").get(); } uhd::meta_range_t lambda_get_samp_rate_range(const size_t mboard_idx, const size_t radio_idx, const size_t chan, uhd::direction_t dir) { radio_ctrl::sptr radio_sptr = get_block_ctrl(mboard_idx, RADIO_BLOCK_NAME, radio_idx); const double samp_rate = (dir == uhd::TX_DIRECTION) ? radio_sptr->get_input_samp_rate(chan) : radio_sptr->get_output_samp_rate(chan); return uhd::meta_range_t(samp_rate, samp_rate, 0.0); } void set_tick_rate(const double tick_rate, const size_t mboard_idx = 0) { _tree->access(mb_root(mboard_idx) / "tick_rate").set(tick_rate); for (size_t radio = 0; radio < _num_radios_per_board; radio++) { auto radio_block_ctrl = get_block_ctrl(mboard_idx, "Radio", radio); radio_block_ctrl->set_rate(tick_rate); } update_sample_rate_on_blocks(mboard_idx); } void set_rx_rate(const double rate, const size_t chan) { if (not _has_ddcs) { return; } // Set DDC values: if (chan == uhd::usrp::multi_usrp::ALL_CHANS) { for (size_t mboard_idx = 0; mboard_idx < _rx_channel_map.size(); mboard_idx++) { for (size_t chan_idx = 0; chan_idx < _rx_channel_map[mboard_idx].size(); chan_idx++) { const size_t dsp_index = _rx_channel_map[mboard_idx][chan_idx].radio_index; const size_t port_index = _rx_channel_map[mboard_idx][chan_idx].port_index; _tree ->access( rx_dsp_root(mboard_idx, dsp_index, port_index) / "rate/value") .set(rate); } } } else { std::set chans_to_change{chan}; if (_rx_stream_cache.count(chan)) { uhd::rx_streamer::sptr str_ptr = _rx_stream_cache[chan].lock(); if (str_ptr) { for (const rx_stream_map_type::value_type& chan_streamer_pair : _rx_stream_cache) { if (chan_streamer_pair.second.lock() == str_ptr) { chans_to_change.insert(chan_streamer_pair.first); } } } } for (const size_t this_chan : chans_to_change) { size_t mboard, mb_chan; chan_to_mcp( this_chan, _rx_channel_map, mboard, mb_chan); const size_t dsp_index = _rx_channel_map[mboard][mb_chan].radio_index; const size_t port_index = _rx_channel_map[mboard][mb_chan].port_index; _tree ->access( rx_dsp_root(mboard, dsp_index, port_index) / "rate/value") .set(rate); } } // Update streamers: boost::dynamic_pointer_cast(_device) ->update_rx_streamers(rate); } void set_tx_rate(const double rate, const size_t chan) { if (not _has_ducs) { return; } // Set DUC values: if (chan == uhd::usrp::multi_usrp::ALL_CHANS) { for (size_t mboard_idx = 0; mboard_idx < _tx_channel_map.size(); mboard_idx++) { for (size_t chan_idx = 0; chan_idx < _tx_channel_map[mboard_idx].size(); chan_idx++) { const size_t dsp_index = _tx_channel_map[mboard_idx][chan_idx].radio_index; const size_t port_index = _tx_channel_map[mboard_idx][chan_idx].port_index; _tree ->access( tx_dsp_root(mboard_idx, dsp_index, port_index) / "rate/value") .set(rate); } } } else { std::set chans_to_change{chan}; if (_tx_stream_cache.count(chan)) { uhd::tx_streamer::sptr str_ptr = _tx_stream_cache[chan].lock(); if (str_ptr) { for (const tx_stream_map_type::value_type& chan_streamer_pair : _tx_stream_cache) { if (chan_streamer_pair.second.lock() == str_ptr) { chans_to_change.insert(chan_streamer_pair.first); } } } } for (const size_t this_chan : chans_to_change) { size_t mboard, mb_chan; chan_to_mcp( this_chan, _tx_channel_map, mboard, mb_chan); const size_t dsp_index = _tx_channel_map[mboard][mb_chan].radio_index; const size_t port_index = _tx_channel_map[mboard][mb_chan].port_index; _tree ->access( tx_dsp_root(mboard, dsp_index, port_index) / "rate/value") .set(rate); } } // Update streamers: boost::dynamic_pointer_cast(_device) ->update_tx_streamers(rate); } private: // types struct radio_port_pair_t { radio_port_pair_t(const size_t radio = 0, const size_t port = 0) : radio_index(radio), port_index(port) { } size_t radio_index; size_t port_index; }; //! Map: _rx_channel_map[mboard_idx][chan_idx] => (Radio, Port) // Container is not a std::map because we need to guarantee contiguous // ports and correct order anyway. typedef std::vector> chan_map_t; private: // methods /************************************************************************ * Private helpers ***********************************************************************/ std::string get_slot_name(const size_t radio_index) { if (radio_index == 0) { return "A"; } else if (radio_index == 1) { return "B"; } else if (radio_index == 2) { return "C"; } else if (radio_index == 3) { return "D"; } else { throw uhd::index_error(str( boost::format("[legacy_compat]: radio index %u out of supported range.") % radio_index)); } } size_t get_radio_index(const std::string slot_name) { if (slot_name == "A") { return 0; } else if (slot_name == "B") { return 1; } else if (slot_name == "C") { return 2; } else if (slot_name == "D") { return 3; } else { throw uhd::key_error( str(boost::format( "[legacy_compat]: radio slot name %s out of supported range.") % slot_name)); } } template inline typename block_type::sptr get_block_ctrl( const size_t mboard_idx, const std::string& name, const size_t block_count) { block_id_t block_id(mboard_idx, name, block_count); return _device->get_block_ctrl(block_id); } template inline void chan_to_mcp(const size_t chan, const chan_map_t& chan_map, size_t& mboard_idx, size_t& mb_chan_idx) { mboard_idx = 0; mb_chan_idx = chan; while (mb_chan_idx >= chan_map[mboard_idx].size()) { mb_chan_idx -= chan_map[mboard_idx++].size(); } if (mboard_idx >= chan_map.size()) { throw uhd::index_error( str(boost::format("[legacy_compat]: %s channel %u out of range for given " "frontend configuration.") % (dir == uhd::TX_DIRECTION ? "TX" : "RX") % chan)); } } template void _update_stream_args_for_streaming( uhd::stream_args_t& args, const chan_map_t& chan_map) { // If the user provides spp, that value is always applied. If it's // different from what we thought it was, we need to update the blocks. // If it's not provided, we provide our own spp value. const size_t args_spp = args.args.cast("spp", 0); if (dir == uhd::RX_DIRECTION) { size_t target_spp = _rx_spp; if (args.args.has_key("spp") and args_spp != _rx_spp) { target_spp = args_spp; // TODO: Update flow control on the blocks } else { for (size_t mboard = 0; mboard < _num_mboards; mboard++) { for (size_t radio = 0; radio < _num_radios_per_board; radio++) { const size_t this_spp = get_block_ctrl(mboard, RADIO_BLOCK_NAME, radio) ->get_arg("spp"); target_spp = std::min(this_spp, target_spp); } } } for (size_t mboard = 0; mboard < _num_mboards; mboard++) { for (size_t radio = 0; radio < _num_radios_per_board; radio++) { get_block_ctrl(mboard, RADIO_BLOCK_NAME, radio) ->set_arg("spp", target_spp); } } _rx_spp = target_spp; args.args["spp"] = str(boost::format("%d") % _rx_spp); } else { if (args.args.has_key("spp") and args_spp != _tx_spp) { _tx_spp = args_spp; // TODO: Update flow control on the blocks } else { args.args["spp"] = str(boost::format("%d") % _tx_spp); } } if (args.channels.empty()) { args.channels = std::vector(1, 0); } for (size_t i = 0; i < args.channels.size(); i++) { const size_t stream_arg_chan_idx = args.channels[i]; // Determine which mboard, and on that mboard, which channel this is: size_t mboard_idx, this_mboard_chan_idx; chan_to_mcp( stream_arg_chan_idx, chan_map, mboard_idx, this_mboard_chan_idx); // Map that mboard and channel to a block: const size_t radio_index = chan_map[mboard_idx][this_mboard_chan_idx].radio_index; size_t port_index = chan_map[mboard_idx][this_mboard_chan_idx].port_index; auto block_and_port = _get_streamer_block_id_and_port(mboard_idx, radio_index, port_index); auto block_name = block_and_port.first.to_string(); port_index = block_and_port.second; args.args[str(boost::format("block_id%d") % stream_arg_chan_idx)] = block_name; args.args[str(boost::format("block_port%d") % stream_arg_chan_idx)] = str(boost::format("%d") % port_index); // Map radio to channel (for in-band response) args.args[str(boost::format("radio_id%d") % stream_arg_chan_idx)] = block_id_t(mboard_idx, RADIO_BLOCK_NAME, radio_index).to_string(); args.args[str(boost::format("radio_port%d") % stream_arg_chan_idx)] = str(boost::format("%d") % chan_map[mboard_idx][this_mboard_chan_idx].port_index); } } //! Given mboard_index(m), radio_index(r), and port_index(p), // this function returns the index of a block on the input block list that match // m,r,p template size_t find_block(const std::vector& port_list, const size_t& m, const size_t& r, const size_t& p) { size_t index = 0; for (auto port : port_list) { auto block_id = (port.first)->get_block_id(); if (p == port.second && r == block_id.get_block_count() && m == block_id.get_device_no()) { return index; } index++; } throw uhd::runtime_error( (boost::format( "Could not find block in list for device %d, radio %d, and port %d") % m % r % p) .str()); } template std::pair _get_streamer_block_id_and_port( const size_t& mboard_idx, const size_t& radio_index, const size_t& port_index) { block_name_to_block_map_t legacy_block_map = get_legacy_blocks(_device); if (dir == uhd::TX_DIRECTION) { auto radio_snk_flat = _flatten_blocks_by_n_ports(legacy_block_map[RADIO_BLOCK_NAME].second); size_t index_snk = find_block( radio_snk_flat, mboard_idx, radio_index, port_index); if (_has_sramfifo) { auto sfifo_snk_flat = _flatten_blocks_by_n_ports(legacy_block_map[SFIFO_BLOCK_NAME].second); UHD_ASSERT_THROW(index_snk < sfifo_snk_flat.size()); auto sfifo_block = sfifo_snk_flat[index_snk].first->get_block_id(); return std::make_pair(sfifo_block, sfifo_snk_flat[index_snk].second); } else if (_has_dmafifo) { auto dfifo_snk_flat = _flatten_blocks_by_n_ports(legacy_block_map[DFIFO_BLOCK_NAME].second); UHD_ASSERT_THROW(index_snk < dfifo_snk_flat.size()); auto dfifo_block = dfifo_snk_flat[index_snk].first->get_block_id(); return std::make_pair(dfifo_block, dfifo_snk_flat[index_snk].second); } else { if (_has_ducs) { return std::make_pair( block_id_t(mboard_idx, DUC_BLOCK_NAME, radio_index).to_string(), port_index); auto duc_snk_flat = _flatten_blocks_by_n_ports( legacy_block_map[DUC_BLOCK_NAME].second); UHD_ASSERT_THROW(index_snk < duc_snk_flat.size()); auto duc_block = duc_snk_flat[index_snk].first->get_block_id(); return std::make_pair(duc_block, duc_snk_flat[index_snk].second); } else { return std::make_pair( block_id_t(mboard_idx, RADIO_BLOCK_NAME, radio_index).to_string(), port_index); } } } else { auto radio_src_flat = _flatten_blocks_by_n_ports(legacy_block_map[RADIO_BLOCK_NAME].first); size_t index_src = find_block( radio_src_flat, mboard_idx, radio_index, port_index); if (_has_ddcs) { auto ddc_src_flat = _flatten_blocks_by_n_ports(legacy_block_map[DDC_BLOCK_NAME].first); UHD_ASSERT_THROW(index_src < ddc_src_flat.size()); auto ddc_block = ddc_src_flat[index_src].first->get_block_id(); return std::make_pair(ddc_block, ddc_src_flat[index_src].second); } else { return std::make_pair( block_id_t(mboard_idx, RADIO_BLOCK_NAME, radio_index).to_string(), port_index); } } } /************************************************************************ * Initialization ***********************************************************************/ /*! Check this device has all the required peripherals. * * Check rules: * - Every mboard needs the same number of radios. * - For every radio block, there must be DDC and a DUC block, * with matching number of ports. * * \throw uhd::runtime_error if any of these checks fail. */ void check_available_periphs() { if (_num_radios_per_board == 0) { throw uhd::runtime_error( "For legacy APIs, all devices require at least one radio."); } block_id_t radio_block_id(0, RADIO_BLOCK_NAME); block_id_t duc_block_id(0, DUC_BLOCK_NAME); block_id_t ddc_block_id(0, DDC_BLOCK_NAME); block_id_t fifo_block_id(0, DFIFO_BLOCK_NAME, 0); for (size_t i = 0; i < _num_mboards; i++) { radio_block_id.set_device_no(i); duc_block_id.set_device_no(i); ddc_block_id.set_device_no(i); fifo_block_id.set_device_no(i); for (size_t k = 0; k < _num_radios_per_board; k++) { radio_block_id.set_block_count(k); duc_block_id.set_block_count(k); ddc_block_id.set_block_count(k); // Only one FIFO per crossbar, so don't set block count for that block if (not _device->has_block(radio_block_id) or (_has_ducs and not _device->has_block(duc_block_id)) or (_has_ddcs and not _device->has_block(ddc_block_id)) or (_has_dmafifo and not _device->has_block(fifo_block_id))) { throw uhd::runtime_error("For legacy APIs, all devices require the " "same number of radios, DDCs and DUCs."); } const size_t this_spp = get_block_ctrl(i, RADIO_BLOCK_NAME, k) ->get_arg("spp"); if (this_spp != _rx_spp) { UHD_LOGGER_WARNING("RFNOC") << str( boost::format( "[legacy compat] Radios have differing spp values: %s has " "%d, others have %d. UHD will use smaller spp value for all " "connections. Performance might be not optimal.") % radio_block_id.to_string() % this_spp % _rx_spp); } } } } /*! Initialize properties in property tree to match legacy mode */ void setup_prop_tree() { for (size_t mboard_idx = 0; mboard_idx < _num_mboards; mboard_idx++) { uhd::fs_path root = mb_root(mboard_idx); // Subdev specs if (_tree->exists(root / "tx_subdev_spec")) { _tree->access(root / "tx_subdev_spec") .add_coerced_subscriber( boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::TX_DIRECTION)) .update() .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::TX_DIRECTION)); } else { _tree->create(root / "tx_subdev_spec") .add_coerced_subscriber( boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::TX_DIRECTION)) .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::TX_DIRECTION)); } if (_tree->exists(root / "rx_subdev_spec")) { _tree->access(root / "rx_subdev_spec") .add_coerced_subscriber( boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::RX_DIRECTION)) .update() .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::RX_DIRECTION)); } else { _tree->create(root / "rx_subdev_spec") .add_coerced_subscriber( boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::RX_DIRECTION)) .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::RX_DIRECTION)); } if (not _has_ddcs) { for (size_t radio_idx = 0; radio_idx < _num_radios_per_board; radio_idx++) { for (size_t chan = 0; chan < _num_rx_chans_per_radio; chan++) { const uhd::fs_path rx_dsp_base_path( mb_root(mboard_idx) / "rx_dsps" / radio_idx / chan); _tree->create(rx_dsp_base_path / "rate/value") .set(0.0) .set_publisher(boost::bind(&radio_ctrl::get_output_samp_rate, get_block_ctrl( mboard_idx, RADIO_BLOCK_NAME, radio_idx), chan)); _tree->create(rx_dsp_base_path / "rate/range") .set_publisher(boost::bind( &legacy_compat_impl::lambda_get_samp_rate_range, this, mboard_idx, radio_idx, chan, uhd::RX_DIRECTION)); _tree->create(rx_dsp_base_path / "freq/value") .set_publisher([]() { return 0.0; }); _tree->create(rx_dsp_base_path / "freq/range") .set_publisher( []() { return uhd::meta_range_t(0.0, 0.0, 0.0); }); } } } /* if not _has_ddcs */ if (not _has_ducs) { for (size_t radio_idx = 0; radio_idx < _num_radios_per_board; radio_idx++) { for (size_t chan = 0; chan < _num_tx_chans_per_radio; chan++) { const uhd::fs_path tx_dsp_base_path( mb_root(mboard_idx) / "tx_dsps" / radio_idx / chan); _tree->create(tx_dsp_base_path / "rate/value") .set(0.0) .set_publisher(boost::bind(&radio_ctrl::get_input_samp_rate, get_block_ctrl( mboard_idx, RADIO_BLOCK_NAME, radio_idx), chan)); _tree->create(tx_dsp_base_path / "rate/range") .set_publisher(boost::bind( &legacy_compat_impl::lambda_get_samp_rate_range, this, mboard_idx, radio_idx, chan, uhd::TX_DIRECTION)); _tree->create(tx_dsp_base_path / "freq/value") .set_publisher([]() { return 0.0; }); _tree->create(tx_dsp_base_path / "freq/range") .set_publisher( []() { return uhd::meta_range_t(0.0, 0.0, 0.0); }); } } } /* if not _has_ducs */ } } /*! Remove properties with bound functions in property tree and recreate */ void remove_prop_subscribers() { for (size_t mboard_idx = 0; mboard_idx < _num_mboards; mboard_idx++) { uhd::fs_path root = mb_root(mboard_idx); // Subdev specs if (_tree->exists(root / "tx_subdev_spec")) { recreate_property(root / "tx_subdev_spec", _tree); } if (_tree->exists(root / "rx_subdev_spec")) { recreate_property(root / "rx_subdev_spec", _tree); } } } //! Flatten block list into a list of // For example block list {b0[0,1] ,b1[0,1]} (i.e block 0 with 2 port 0 and 1,etc ..) // this will return { } std::vector _flatten_blocks_by_n_ports(source_block_list_t block_list) { std::vector result; for (auto block : block_list) { for (auto port : block->get_output_ports()) { result.push_back(std::make_pair(block, port)); } } // assign to block prior ports size_t port = 0; size_t i = 0; for (size_t j = 0; j < result.size(); j++) { auto block = block_list[j % block_list.size()]; UHD_ASSERT_THROW(port < block->get_output_ports().size()); if (i == block_list.size()) { i = 0; port++; } result[j] = std::make_pair(block, port); i++; } return result; } //! Flatten block list into a list of // For example block list {b0[0,1] ,b1[0,1]} (i.e block 0 with 2 port 0 and 1,etc ..) // this will return { } std::vector _flatten_blocks_by_n_ports(sink_block_list_t block_list) { std::vector result; for (auto block : block_list) { for (auto port : block->get_input_ports()) { result.push_back(std::make_pair(block, port)); } } // assign to block prior ports size_t port = 0; size_t i = 0; for (size_t j = 0; j < result.size(); j++) { auto block = block_list[j % block_list.size()]; UHD_ASSERT_THROW(port < block->get_input_ports().size()); if (i == block_list.size()) { i = 0; port++; } result[j] = std::make_pair(block, port); i++; } return result; } /*! Default block connections. * * Tx connections: * * [Host] => DMA FIFO => DUC => Radio * * Note: There is only one DMA FIFO per crossbar, with twice the number of ports. * * Rx connections: * * Radio => DDC => [Host] * * Streamers are *not* generated here. */ void connect_blocks() { _graph = _device->create_graph("legacy"); const size_t rx_bpp = _rx_spp * BYTES_PER_SAMPLE + MAX_BYTES_PER_HEADER; const size_t tx_bpp = _tx_spp * BYTES_PER_SAMPLE + MAX_BYTES_PER_HEADER; block_name_to_block_map_t legacy_block_map = get_legacy_blocks(_device); size_t index = 0, sram_fifo_index = 0, dma_fifo_index = 0; auto ddc_snk_flat = _flatten_blocks_by_n_ports(legacy_block_map[DDC_BLOCK_NAME].second); auto duc_src_flat = _flatten_blocks_by_n_ports(legacy_block_map[DUC_BLOCK_NAME].first); auto duc_snk_flat = _flatten_blocks_by_n_ports(legacy_block_map[DUC_BLOCK_NAME].second); auto radio_src_flat = _flatten_blocks_by_n_ports(legacy_block_map[RADIO_BLOCK_NAME].first); auto radio_snk_flat = _flatten_blocks_by_n_ports(legacy_block_map[RADIO_BLOCK_NAME].second); auto sfifo_src_flat = _flatten_blocks_by_n_ports(legacy_block_map[SFIFO_BLOCK_NAME].first); auto dfifo_src_flat = _flatten_blocks_by_n_ports(legacy_block_map[DFIFO_BLOCK_NAME].first); for (auto each_src_radio_block : radio_src_flat) { auto radio_block = each_src_radio_block.first->get_block_id(); if (_has_ddcs) { UHD_ASSERT_THROW(index < ddc_snk_flat.size()); auto ddc_block = ddc_snk_flat[index].first->get_block_id(); _graph->connect(radio_block, each_src_radio_block.second, ddc_block, ddc_snk_flat[index].second, rx_bpp); } index++; } index = 0; for (auto each_snk_radio_block : radio_snk_flat) { auto radio_block = each_snk_radio_block.first->get_block_id(); auto down_stream_block = radio_block; auto down_stream_port = each_snk_radio_block.second; if (_has_ducs) { UHD_ASSERT_THROW(index < duc_snk_flat.size()); UHD_ASSERT_THROW(index < duc_src_flat.size()); auto duc_snk_block = duc_snk_flat[index].first->get_block_id(); auto duc_src_block = duc_src_flat[index].first->get_block_id(); _graph->connect(duc_src_block, duc_src_flat[index].second, radio_block, each_snk_radio_block.second, tx_bpp); down_stream_block = duc_snk_block; down_stream_port = duc_snk_flat[index].second; } if (_has_sramfifo) { if (sram_fifo_index < sfifo_src_flat.size()) { auto sfifo_block = sfifo_src_flat[sram_fifo_index].first->get_block_id(); _graph->connect(sfifo_block, sfifo_src_flat[sram_fifo_index].second, down_stream_block, down_stream_port, tx_bpp); sram_fifo_index++; } else { UHD_LOGGER_WARNING("RFNOC") << "[legacy compat] Running out of SRAM FIFO ports to connect."; } } else if (_has_dmafifo) { if (dma_fifo_index < dfifo_src_flat.size()) { auto dfifo_block = dfifo_src_flat[dma_fifo_index].first->get_block_id(); _graph->connect(dfifo_block, dfifo_src_flat[dma_fifo_index].second, down_stream_block, down_stream_port, tx_bpp); dma_fifo_index++; } else { UHD_LOGGER_WARNING("RFNOC") << "[legacy compat] Running out of DRAM FIFO ports to connect."; } } index++; } } /************************************************************************ * Subdev translation ***********************************************************************/ /*! Subdev -> (Radio, Port) * * Example: Device is X300, subdev spec is 'A:0 B:0', we have 2 radios. * Then we map to ((0, 0), (1, 0)). I.e., zero-th port on radio 0 and * radio 1, respectively. */ void set_subdev_spec( const subdev_spec_t& spec, const size_t mboard, const uhd::direction_t dir) { UHD_ASSERT_THROW(mboard < _num_mboards); chan_map_t& chan_map = (dir == uhd::TX_DIRECTION) ? _tx_channel_map : _rx_channel_map; std::vector new_mapping(spec.size()); for (size_t i = 0; i < spec.size(); i++) { const size_t new_radio_index = get_radio_index(spec[i].db_name); radio_ctrl::sptr radio = get_block_ctrl(mboard, "Radio", new_radio_index); size_t new_port_index = radio->get_chan_from_dboard_fe(spec[i].sd_name, dir); auto port_size = (dir == uhd::TX_DIRECTION) ? radio->get_input_ports().size() : radio->get_output_ports().size(); auto default_index = (dir == uhd::TX_DIRECTION) ? radio->get_input_ports().at(0) : radio->get_output_ports().at(0); if (new_port_index >= port_size) { new_port_index = default_index; } radio_port_pair_t new_radio_port_pair(new_radio_index, new_port_index); new_mapping[i] = new_radio_port_pair; } chan_map[mboard] = new_mapping; } subdev_spec_t get_subdev_spec(const size_t mboard, const uhd::direction_t dir) { UHD_ASSERT_THROW(mboard < _num_mboards); subdev_spec_t subdev_spec; chan_map_t& chan_map = (dir == uhd::TX_DIRECTION) ? _tx_channel_map : _rx_channel_map; for (size_t chan_idx = 0; chan_idx < chan_map[mboard].size(); chan_idx++) { const size_t radio_index = chan_map[mboard][chan_idx].radio_index; const size_t port_index = chan_map[mboard][chan_idx].port_index; const std::string new_db_name = get_slot_name(radio_index); const std::string new_sd_name = get_block_ctrl(mboard, "Radio", radio_index) ->get_dboard_fe_from_chan(port_index, dir); subdev_spec_pair_t new_pair(new_db_name, new_sd_name); subdev_spec.push_back(new_pair); } return subdev_spec; } void update_sample_rate_on_blocks(const size_t mboard_idx) { block_id_t radio_block_id(mboard_idx, RADIO_BLOCK_NAME); block_id_t duc_block_id(mboard_idx, DUC_BLOCK_NAME); block_id_t ddc_block_id(mboard_idx, DDC_BLOCK_NAME); for (size_t radio = 0; radio < _num_radios_per_board; radio++) { radio_block_id.set_block_count(radio); duc_block_id.set_block_count(radio); ddc_block_id.set_block_count(radio); radio_ctrl::sptr radio_sptr = _device->get_block_ctrl(radio_block_id); for (size_t chan = 0; chan < _num_rx_chans_per_radio and _has_ddcs; chan++) { const double radio_output_rate = radio_sptr->get_output_samp_rate(chan); _device->get_block_ctrl(ddc_block_id) ->set_arg("input_rate", radio_output_rate, chan); } for (size_t chan = 0; chan < _num_tx_chans_per_radio and _has_ducs; chan++) { const double radio_input_rate = radio_sptr->get_input_samp_rate(chan); _device->get_block_ctrl(duc_block_id) ->set_arg("output_rate", radio_input_rate, chan); } } } private: // attributes uhd::device3::sptr _device; uhd::property_tree::sptr _tree; const bool _has_ducs; const bool _has_ddcs; const bool _has_dmafifo; const bool _has_sramfifo; const size_t _num_mboards; const size_t _num_radios_per_board; const size_t _num_tx_chans_per_radio; const size_t _num_rx_chans_per_radio; size_t _rx_spp; size_t _tx_spp; chan_map_t _rx_channel_map; chan_map_t _tx_channel_map; //! Stores a weak pointer for every streamer that's generated through this API. // Key is the channel number (same format as e.g. the set_rx_rate() call). typedef std::map> rx_stream_map_type; rx_stream_map_type _rx_stream_cache; typedef std::map> tx_stream_map_type; tx_stream_map_type _tx_stream_cache; graph::sptr _graph; }; legacy_compat::sptr legacy_compat::make( uhd::device3::sptr device, const uhd::device_addr_t& args) { boost::lock_guard lock(_make_mutex); UHD_ASSERT_THROW(bool(device)); static std::map> legacy_cache; if (legacy_cache.count(device.get()) and not legacy_cache.at(device.get()).expired()) { legacy_compat::sptr legacy_compat_copy = legacy_cache.at(device.get()).lock(); UHD_ASSERT_THROW(bool(legacy_compat_copy)); UHD_LEGACY_LOG() << "[legacy_compat] Using existing legacy compat object for this device."; return legacy_compat_copy; } legacy_compat::sptr new_legacy_compat = boost::make_shared(device, args); legacy_cache[device.get()] = new_legacy_compat; return new_legacy_compat; }