diff options
author | Martin Braun <martin.braun@ettus.com> | 2019-07-25 10:41:25 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2019-08-02 11:03:11 -0700 |
commit | 173531521970ed823b2f300180b8cacda94ec841 (patch) | |
tree | df23a21876e4430ae2b232499af5626189b819a3 /host | |
parent | e2ce13e35d915c2d8b1300fd88745610c17638d1 (diff) | |
download | uhd-173531521970ed823b2f300180b8cacda94ec841.tar.gz uhd-173531521970ed823b2f300180b8cacda94ec841.tar.bz2 uhd-173531521970ed823b2f300180b8cacda94ec841.zip |
x300: Refactor heavily
This pulls out a lot of code from x300_impl and puts it into its own
compilation units:
- EEPROM code goes to x300_mb_eeprom.*
- Claim code goes to x300_claim.*
- PCIe code goes to uhd::usrp::x300::pcie_manager
- Ethernet code goes to uhd::usrp::x300::eth_manager
Diffstat (limited to 'host')
-rw-r--r-- | host/lib/usrp/x300/CMakeLists.txt | 4 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_claim.cpp | 89 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_claim.hpp | 25 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_defaults.hpp | 56 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_device_args.hpp | 40 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_eth_mgr.cpp | 724 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_eth_mgr.hpp | 126 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_impl.cpp | 1218 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_impl.hpp | 147 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_mb_eeprom.cpp | 9 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_mb_eeprom.hpp | 24 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_mb_eeprom_iface.cpp | 23 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_mboard_type.hpp | 2 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_pcie_mgr.cpp | 390 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_pcie_mgr.hpp | 82 |
15 files changed, 1607 insertions, 1352 deletions
diff --git a/host/lib/usrp/x300/CMakeLists.txt b/host/lib/usrp/x300/CMakeLists.txt index 76a9e6b54..062db3f16 100644 --- a/host/lib/usrp/x300/CMakeLists.txt +++ b/host/lib/usrp/x300/CMakeLists.txt @@ -1,6 +1,7 @@ # # Copyright 2013,2015 Ettus Research LLC # Copyright 2018 Ettus Research, a National Instruments Company +# Copyright 2019 Ettus Research, a National Instruments Brand # # SPDX-License-Identifier: GPL-3.0-or-later # @@ -14,12 +15,14 @@ ######################################################################## if(ENABLE_X300) LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/x300_claim.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_radio_ctrl_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_uart.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_adc_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_dac_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_eth_mgr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_io_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_dboard_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_clock_ctrl.cpp @@ -28,6 +31,7 @@ if(ENABLE_X300) ${CMAKE_CURRENT_SOURCE_DIR}/x300_mb_eeprom.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_mboard_type.hpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_mboard_type.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_pcie_mgr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cdecode.c ) endif(ENABLE_X300) diff --git a/host/lib/usrp/x300/x300_claim.cpp b/host/lib/usrp/x300/x300_claim.cpp new file mode 100644 index 000000000..8a8de037d --- /dev/null +++ b/host/lib/usrp/x300/x300_claim.cpp @@ -0,0 +1,89 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "x300_claim.hpp" +#include "x300_fw_common.h" +#include <uhd/utils/platform.hpp> +#include <chrono> +#include <thread> + +using namespace uhd; +using namespace uhd::usrp::x300; + +/*********************************************************************** + * claimer logic + **********************************************************************/ + +void uhd::usrp::x300::claimer_loop(wb_iface::sptr iface) +{ + claim(iface); + std::this_thread::sleep_for(std::chrono::seconds(1)); +} + +claim_status_t uhd::usrp::x300::claim_status(wb_iface::sptr iface) +{ + claim_status_t claim_status = CLAIMED_BY_OTHER; // Default to most restrictive + auto timeout_time = std::chrono::steady_clock::now() + std::chrono::seconds(1); + while (std::chrono::steady_clock::now() < timeout_time) { + // If timed out, then device is definitely unclaimed + if (iface->peek32(X300_FW_SHMEM_ADDR(X300_FW_SHMEM_CLAIM_STATUS)) == 0) { + claim_status = UNCLAIMED; + break; + } + + // otherwise check claim src to determine if another thread with the same src has + // claimed the device + uint32_t hash = iface->peek32(X300_FW_SHMEM_ADDR(X300_FW_SHMEM_CLAIM_SRC)); + if (hash == 0) { + // A non-zero claim status and an empty hash means the claim might + // be in the process of being released. This is possible because + // older firmware takes a long time to update the status. Wait and + // check status again. + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + continue; + } + claim_status = (hash == get_process_hash() ? CLAIMED_BY_US : CLAIMED_BY_OTHER); + break; + } + return claim_status; +} + +void uhd::usrp::x300::claim(wb_iface::sptr iface) +{ + iface->poke32(X300_FW_SHMEM_ADDR(X300_FW_SHMEM_CLAIM_TIME), uint32_t(time(NULL))); + iface->poke32(X300_FW_SHMEM_ADDR(X300_FW_SHMEM_CLAIM_SRC), get_process_hash()); +} + +bool uhd::usrp::x300::try_to_claim(wb_iface::sptr iface, long timeout_ms) +{ + const auto timeout_time = + std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms); + while (1) { + claim_status_t status = claim_status(iface); + if (status == UNCLAIMED) { + claim(iface); + // It takes the claimer 10ms to update status, so wait 20ms before verifying + // claim + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + continue; + } + if (status == CLAIMED_BY_US) { + break; + } + if (std::chrono::steady_clock::now() > timeout_time) { + // Another process owns the device - give up + return false; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + return true; +} + +void uhd::usrp::x300::release(wb_iface::sptr iface) +{ + iface->poke32(X300_FW_SHMEM_ADDR(X300_FW_SHMEM_CLAIM_TIME), 0); + iface->poke32(X300_FW_SHMEM_ADDR(X300_FW_SHMEM_CLAIM_SRC), 0); +} diff --git a/host/lib/usrp/x300/x300_claim.hpp b/host/lib/usrp/x300/x300_claim.hpp new file mode 100644 index 000000000..bd222f55e --- /dev/null +++ b/host/lib/usrp/x300/x300_claim.hpp @@ -0,0 +1,25 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_X300_CLAIM_HPP +#define INCLUDED_X300_CLAIM_HPP + +#include <uhd/types/wb_iface.hpp> + +namespace uhd { namespace usrp { namespace x300 { + +// device claim functions +enum claim_status_t { UNCLAIMED, CLAIMED_BY_US, CLAIMED_BY_OTHER }; + +claim_status_t claim_status(uhd::wb_iface::sptr iface); +void claimer_loop(uhd::wb_iface::sptr iface); +void claim(uhd::wb_iface::sptr iface); +bool try_to_claim(uhd::wb_iface::sptr iface, long timeout = 2000); +void release(uhd::wb_iface::sptr iface); + +}}} // namespace uhd::usrp::x300 + +#endif /* INCLUDED_X300_CLAIM_HPP */ diff --git a/host/lib/usrp/x300/x300_defaults.hpp b/host/lib/usrp/x300/x300_defaults.hpp index c5e9f6004..752298aff 100644 --- a/host/lib/usrp/x300/x300_defaults.hpp +++ b/host/lib/usrp/x300/x300_defaults.hpp @@ -8,15 +8,13 @@ #define INCLUDED_X300_DEFAULTS_HPP #include "../device3/device3_impl.hpp" -#include <uhd/transport/udp_simple.hpp> //mtu #include <string> - +#include <vector> namespace uhd { namespace usrp { namespace x300 { static constexpr size_t NIUSRPRIO_DEFAULT_RPC_PORT = 5444; -static constexpr uint32_t RADIO_DEST_PREFIX_TX = 0; static constexpr size_t XB_DST_E0 = 0; static constexpr size_t XB_DST_E1 = 1; static constexpr size_t XB_DST_PCI = 2; @@ -32,6 +30,7 @@ static constexpr double DEFAULT_TICK_RATE = 200e6; // Hz static constexpr double MAX_TICK_RATE = 200e6; // Hz static constexpr double MIN_TICK_RATE = 184.32e6; // Hz static constexpr double BUS_CLOCK_RATE = 187.5e6; // Hz +static constexpr double DEFAULT_SYSREF_RATE = 10e6; static const std::string FW_FILE_NAME = "usrp_x300_fw.bin"; @@ -47,61 +46,14 @@ static const std::vector<std::string> TIME_SOURCE_OPTIONS{ static const std::vector<double> EXTERNAL_FREQ_OPTIONS{ 10e6, 11.52e6, 23.04e6, 30.72e6}; -static constexpr size_t RX_SW_BUFF_SIZE_ETH = - 0x2000000; // 32MiB For an ~8k frame size any size >32MiB is just wasted buffer - // space -static constexpr size_t RX_SW_BUFF_SIZE_ETH_MACOS = 0x100000; // 1Mib - -// The FIFO closest to the DMA controller is 1023 elements deep for RX and 1029 elements -// deep for TX where an element is 8 bytes. The buffers (number of frames * frame size) -// must be aligned to the memory page size. For the control, we are getting lucky because -// 64 frames * 256 bytes each aligns with the typical page size of 4096 bytes. Since most -// page sizes are 4096 bytes or some multiple of that, keep the number of frames * frame -// size aligned to it. -static constexpr size_t PCIE_RX_DATA_FRAME_SIZE = 4096; // bytes -static constexpr size_t PCIE_RX_DATA_NUM_FRAMES = 4096; -static constexpr size_t PCIE_TX_DATA_FRAME_SIZE = 4096; // bytes -static constexpr size_t PCIE_TX_DATA_NUM_FRAMES = 4096; -static constexpr size_t PCIE_MSG_FRAME_SIZE = 256; // bytes -static constexpr size_t PCIE_MSG_NUM_FRAMES = 64; -static constexpr size_t PCIE_MAX_CHANNELS = 6; -static constexpr size_t PCIE_MAX_MUXED_CTRL_XPORTS = 32; -static constexpr size_t PCIE_MAX_MUXED_ASYNC_XPORTS = 4; +// Limit the number of initialization threads +static const size_t MAX_INIT_THREADS = 10; static const size_t DATA_FRAME_MAX_SIZE = 8000; // CHDR packet size in bytes -static const size_t XGE_DATA_FRAME_SEND_SIZE = - 4000; // Reduced to make sure flow control packets are not blocked for too long at - // high rates -static const size_t XGE_DATA_FRAME_RECV_SIZE = 8000; -static const size_t GE_DATA_FRAME_SEND_SIZE = 1472; -static const size_t GE_DATA_FRAME_RECV_SIZE = 1472; - -static const size_t ETH_MSG_FRAME_SIZE = uhd::transport::udp_simple::mtu; // bytes -// MTU throttling for ethernet/TX (see above): -static constexpr size_t ETH_DATA_FRAME_MAX_TX_SIZE = 8000; static constexpr double RECV_OFFLOAD_BUFFER_TIMEOUT = 0.1; // seconds static constexpr double THREAD_BUFFER_TIMEOUT = 0.1; // Time in seconds -static constexpr size_t ETH_MSG_NUM_FRAMES = 64; -static constexpr size_t ETH_DATA_NUM_FRAMES = 32; -static constexpr double DEFAULT_SYSREF_RATE = 10e6; - -// Limit the number of initialization threads -static const size_t MAX_INIT_THREADS = 10; - -static const size_t MAX_RATE_PCIE = 800000000; // bytes/s -static const size_t MAX_RATE_10GIGE = (size_t)( // bytes/s - 10e9 / 8 * // wire speed multiplied by percentage of packets that is sample data - (float(DATA_FRAME_MAX_SIZE - uhd::usrp::DEVICE3_TX_MAX_HDR_LEN) - / float(DATA_FRAME_MAX_SIZE - + 8 /* UDP header */ + 20 /* Ethernet header length */))); -static const size_t MAX_RATE_1GIGE = (size_t)( // bytes/s - 10e9 / 8 * // wire speed multiplied by percentage of packets that is sample data - (float(GE_DATA_FRAME_RECV_SIZE - uhd::usrp::DEVICE3_TX_MAX_HDR_LEN) - / float(GE_DATA_FRAME_RECV_SIZE - + 8 /* UDP header */ + 20 /* Ethernet header length */))); - static constexpr double DEFAULT_EXT_ADC_SELF_TEST_DURATION = 30.0; }}} /* namespace uhd::usrp::x300 */ diff --git a/host/lib/usrp/x300/x300_device_args.hpp b/host/lib/usrp/x300/x300_device_args.hpp index 1b153184c..9e9e328a9 100644 --- a/host/lib/usrp/x300/x300_device_args.hpp +++ b/host/lib/usrp/x300/x300_device_args.hpp @@ -8,7 +8,7 @@ #define INCLUDED_X300_DEV_ARGS_HPP #include "x300_defaults.hpp" -#include "x300_impl.hpp" +#include <uhd/utils/log.hpp> #include <uhdlib/usrp/constrained_device_args.hpp> namespace uhd { namespace usrp { namespace x300 { @@ -37,6 +37,10 @@ public: , _blank_eeprom("blank_eeprom", false) , _enable_tx_dual_eth("enable_tx_dual_eth", false) , _use_dpdk("use_dpdk", false) + , _fpga_option("fpga", "") + , _download_fpga("download-fpga", false) + , _recv_frame_size("recv_frame_size", DATA_FRAME_MAX_SIZE) + , _send_frame_size("send_frame_size", DATA_FRAME_MAX_SIZE) { // nop } @@ -121,6 +125,26 @@ public: { return _use_dpdk.get(); } + std::string get_fpga_option() const + { + return _fpga_option.get(); + } + bool get_download_fpga() const + { + return _download_fpga.get(); + } + size_t get_recv_frame_size() const + { + return _recv_frame_size.get(); + } + size_t get_send_frame_size() const + { + return _send_frame_size.get(); + } + device_addr_t get_orig_args() const + { + return _orig_args; + } inline virtual std::string to_string() const { @@ -156,12 +180,14 @@ public: + (_has_fw_file.get() ? _fw_file.to_string() + ", " : "") + (_enable_tx_dual_eth.get() ? (_enable_tx_dual_eth.to_string() + ", ") : "") - ; + + (_fpga_option.get().empty() ? "" : _fpga_option.to_string() + ", ") + + (_download_fpga.get() ? _download_fpga.to_string() + ", " : ""); } private: virtual void _parse(const device_addr_t& dev_args) { + _orig_args = dev_args; // Extract parameters from dev_args #define PARSE_DEFAULT(arg) parse_arg_default(dev_args, arg); PARSE_DEFAULT(_master_clock_rate) @@ -188,6 +214,8 @@ private: PARSE_DEFAULT(_first_addr) PARSE_DEFAULT(_second_addr) PARSE_DEFAULT(_resource) + PARSE_DEFAULT(_fpga_option) + PARSE_DEFAULT(_download_fpga) if (_first_addr.get().empty() && !_second_addr.get().empty()) { UHD_LOG_WARNING("X300", "Specifying `second_addr' without `addr'is inconsistent and has " @@ -233,6 +261,8 @@ private: "Detected use_dpdk argument, but DPDK support not built in."); #endif } + PARSE_DEFAULT(_recv_frame_size) + PARSE_DEFAULT(_send_frame_size) // Sanity check params _enforce_range(_master_clock_rate, MIN_TICK_RATE, MAX_TICK_RATE); @@ -261,6 +291,12 @@ private: constrained_device_args_t::bool_arg _blank_eeprom; constrained_device_args_t::bool_arg _enable_tx_dual_eth; constrained_device_args_t::bool_arg _use_dpdk; + constrained_device_args_t::str_arg<true> _fpga_option; + constrained_device_args_t::bool_arg _download_fpga; + constrained_device_args_t::num_arg<size_t> _recv_frame_size; + constrained_device_args_t::num_arg<size_t> _send_frame_size; + + device_addr_t _orig_args; }; }}} // namespace uhd::usrp::x300 diff --git a/host/lib/usrp/x300/x300_eth_mgr.cpp b/host/lib/usrp/x300/x300_eth_mgr.cpp new file mode 100644 index 000000000..adaf101d4 --- /dev/null +++ b/host/lib/usrp/x300/x300_eth_mgr.cpp @@ -0,0 +1,724 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "x300_eth_mgr.hpp" +#include "x300_claim.hpp" +#include "x300_fw_common.h" +#include "x300_mb_eeprom.hpp" +#include "x300_mb_eeprom_iface.hpp" +#include "x300_regs.hpp" +#include <uhd/exception.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_constants.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/transport/zero_copy_recv_offload.hpp> +#ifdef HAVE_DPDK +# include <uhdlib/transport/dpdk_simple.hpp> +# include <uhdlib/transport/dpdk_zero_copy.hpp> +#endif +#include <uhdlib/usrp/cores/i2c_core_100_wb32.hpp> +#include <boost/asio.hpp> +#include <string> + +uhd::wb_iface::sptr x300_make_ctrl_iface_enet( + uhd::transport::udp_simple::sptr udp, bool enable_errors = true); + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +using namespace uhd::usrp::x300; +namespace asio = boost::asio; + +namespace { + +constexpr unsigned int X300_UDP_RESERVED_FRAME_SIZE = 64; +// Reduced to make sure flow control packets are not blocked for too long at +// high rates: +constexpr size_t XGE_DATA_FRAME_SEND_SIZE = 4000; +constexpr size_t XGE_DATA_FRAME_RECV_SIZE = 8000; +constexpr size_t GE_DATA_FRAME_SEND_SIZE = 1472; +constexpr size_t GE_DATA_FRAME_RECV_SIZE = 1472; +constexpr size_t ETH_MSG_NUM_FRAMES = 64; +constexpr size_t ETH_DATA_NUM_FRAMES = 32; +constexpr size_t ETH_MSG_FRAME_SIZE = uhd::transport::udp_simple::mtu; // bytes +constexpr size_t MAX_RATE_10GIGE = (size_t)( // bytes/s + 10e9 / 8 * // wire speed multiplied by percentage of packets that is sample data + (float(x300::DATA_FRAME_MAX_SIZE - uhd::usrp::DEVICE3_TX_MAX_HDR_LEN) + / float(x300::DATA_FRAME_MAX_SIZE + + 8 /* UDP header */ + 20 /* Ethernet header length */))); +constexpr size_t MAX_RATE_1GIGE = (size_t)( // bytes/s + 10e9 / 8 * // wire speed multiplied by percentage of packets that is sample data + (float(GE_DATA_FRAME_RECV_SIZE - uhd::usrp::DEVICE3_TX_MAX_HDR_LEN) + / float(GE_DATA_FRAME_RECV_SIZE + + 8 /* UDP header */ + 20 /* Ethernet header length */))); + + +} // namespace + +/****************************************************************************** + * Static Methods + *****************************************************************************/ +eth_manager::udp_simple_factory_t eth_manager::x300_get_udp_factory( + const device_addr_t& args) +{ + udp_simple_factory_t udp_make_connected = udp_simple::make_connected; + if (args.has_key("use_dpdk")) { +#ifdef HAVE_DPDK + udp_make_connected = [](const std::string& addr, const std::string& port) { + auto& ctx = uhd::transport::uhd_dpdk_ctx::get(); + return dpdk_simple::make_connected(ctx, addr, port); + }; +#else + UHD_LOG_WARNING( + "DPDK", "Detected use_dpdk argument, but DPDK support not built in."); +#endif + } + return udp_make_connected; +} + +device_addrs_t eth_manager::find(const device_addr_t& hint) +{ + udp_simple_factory_t udp_make_broadcast = udp_simple::make_broadcast; + udp_simple_factory_t udp_make_connected = x300_get_udp_factory(hint); +#ifdef HAVE_DPDK + if (hint.has_key("use_dpdk")) { + auto& dpdk_ctx = uhd::transport::uhd_dpdk_ctx::get(); + if (not dpdk_ctx.is_init_done()) { + dpdk_ctx.init(hint); + } + udp_make_broadcast = [](const std::string& addr, const std::string& port) { + auto& ctx = uhd::transport::uhd_dpdk_ctx::get(); + return dpdk_simple::make_broadcast(ctx, addr, port); + }; + } +#endif + udp_simple::sptr comm = + udp_make_broadcast(hint["addr"], BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)); + + // load request struct + x300_fw_comms_t request = x300_fw_comms_t(); + request.flags = uhd::htonx<uint32_t>(X300_FW_COMMS_FLAGS_ACK); + request.sequence = uhd::htonx<uint32_t>(std::rand()); + + // send request + comm->send(asio::buffer(&request, sizeof(request))); + + // loop for replies until timeout + device_addrs_t addrs; + while (true) { + char buff[X300_FW_COMMS_MTU] = {}; + const size_t nbytes = comm->recv(asio::buffer(buff), 0.050); + if (nbytes == 0) + break; + const x300_fw_comms_t* reply = (const x300_fw_comms_t*)buff; + if (request.flags != reply->flags) + continue; + if (request.sequence != reply->sequence) + continue; + device_addr_t new_addr; + new_addr["type"] = "x300"; + new_addr["addr"] = comm->get_recv_addr(); + + // Attempt to read the name from the EEPROM and perform filtering. + // This operation can throw due to compatibility mismatch. + try { + wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_enet( + udp_make_connected( + new_addr["addr"], BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)), + false /* Suppress timeout errors */ + ); + + new_addr["fpga"] = get_fpga_option(zpu_ctrl); + + i2c_core_100_wb32::sptr zpu_i2c = + i2c_core_100_wb32::make(zpu_ctrl, I2C1_BASE); + x300_mb_eeprom_iface::sptr eeprom_iface = + x300_mb_eeprom_iface::make(zpu_ctrl, zpu_i2c); + const mboard_eeprom_t mb_eeprom = get_mb_eeprom(eeprom_iface); + if (mb_eeprom.size() == 0 or claim_status(zpu_ctrl) == CLAIMED_BY_OTHER) { + // Skip device claimed by another process + continue; + } + new_addr["name"] = mb_eeprom["name"]; + new_addr["serial"] = mb_eeprom["serial"]; + const std::string product_name = + map_mb_type_to_product_name(get_mb_type_from_eeprom(mb_eeprom)); + if (!product_name.empty()) { + new_addr["product"] = product_name; + } + } catch (const std::exception&) { + // set these values as empty string so the device may still be found + // and the filter's below can still operate on the discovered device + new_addr["name"] = ""; + new_addr["serial"] = ""; + } + // filter the discovered device below by matching optional keys + if ((not hint.has_key("name") or hint["name"] == new_addr["name"]) + and (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) + and (not hint.has_key("product") or hint["product"] == new_addr["product"])) { + addrs.push_back(new_addr); + } + } + + return addrs; +} + +/****************************************************************************** + * Structors + *****************************************************************************/ +eth_manager::eth_manager(const x300_device_args_t& args, + uhd::property_tree::sptr tree, + const uhd::fs_path& root_path) + : _args(args) +{ + UHD_ASSERT_THROW(!args.get_first_addr().empty()); + + auto dev_addr = args.get_orig_args(); + for (const std::string& key : dev_addr.keys()) { + if (key.find("recv") != std::string::npos) + recv_args[key] = dev_addr[key]; + if (key.find("send") != std::string::npos) + send_args[key] = dev_addr[key]; + } + + // Initially store only the first address provided to setup communication + // Once we read the EEPROM, we use it to map IP to its interface + // In discover_eth(), we'll check and enable the other IP address, if given + x300_eth_conn_t init; + init.addr = args.get_first_addr(); + eth_conns.push_back(init); + + _x300_make_udp_connected = x300_get_udp_factory(dev_addr); + + tree->create<double>(root_path / "link_max_rate").set(10e9); + _tree = tree->subtree(root_path); +} + +both_xports_t eth_manager::make_transport(both_xports_t xports, + const uhd::usrp::device3_impl::xport_type_t xport_type, + const uhd::device_addr_t& args, + const size_t send_mtu, + const size_t recv_mtu, + std::function<uhd::sid_t(uint32_t, uint32_t)>&& allocate_sid) +{ + zero_copy_xport_params default_buff_args; + xports.endianness = ENDIANNESS_BIG; + xports.lossless = false; + xports.recv = nullptr; + + size_t& next_src_addr = xport_type == uhd::usrp::device3_impl::TX_DATA + ? _next_tx_src_addr + : xport_type == uhd::usrp::device3_impl::RX_DATA + ? _next_rx_src_addr + : _next_src_addr; + + // Decide on the IP/Interface pair based on the endpoint index + x300_eth_conn_t conn = eth_conns[next_src_addr]; + const uint32_t xbar_src_addr = next_src_addr == 0 ? x300::SRC_ADDR0 : x300::SRC_ADDR1; + const uint32_t xbar_src_dst = conn.type == X300_IFACE_ETH0 ? x300::XB_DST_E0 + : x300::XB_DST_E1; + + // Do not increment src addr for tx_data by default, using dual ethernet + // with the DMA FIFO causes sequence errors to DMA FIFO bandwidth + // limitations. + if (xport_type != uhd::usrp::device3_impl::TX_DATA + || _args.get_enable_tx_dual_eth()) { + next_src_addr = (next_src_addr + 1) % eth_conns.size(); + } + + xports.send_sid = allocate_sid(xbar_src_addr, xbar_src_dst); + xports.recv_sid = xports.send_sid.reversed(); + // Set size and number of frames + default_buff_args.send_frame_size = std::min(send_mtu, ETH_MSG_FRAME_SIZE); + default_buff_args.recv_frame_size = std::min(recv_mtu, ETH_MSG_FRAME_SIZE); + + if (_args.get_use_dpdk()) { +#ifdef HAVE_DPDK + auto& dpdk_ctx = uhd::transport::uhd_dpdk_ctx::get(); + + default_buff_args.num_recv_frames = ETH_MSG_NUM_FRAMES; + default_buff_args.num_send_frames = ETH_MSG_NUM_FRAMES; + if (xport_type == uhd::usrp::device3_impl::CTRL) { + // Increasing number of recv frames here because ctrl_iface uses it + // to determine how many control packets can be in flight before it + // must wait for an ACK + default_buff_args.num_recv_frames = + uhd::rfnoc::CMD_FIFO_SIZE / uhd::rfnoc::MAX_CMD_PKT_SIZE; + } else if (xport_type == uhd::usrp::device3_impl::TX_DATA) { + size_t default_frame_size = conn.link_rate == MAX_RATE_1GIGE + ? GE_DATA_FRAME_SEND_SIZE + : XGE_DATA_FRAME_SEND_SIZE; + default_buff_args.send_frame_size = args.cast<size_t>( + "send_frame_size", std::min(default_frame_size, send_mtu)); + default_buff_args.num_send_frames = + args.cast<size_t>("num_send_frames", default_buff_args.num_send_frames); + default_buff_args.send_buff_size = args.cast<size_t>("send_buff_size", 0); + } else if (xport_type == uhd::usrp::device3_impl::RX_DATA) { + size_t default_frame_size = conn.link_rate == MAX_RATE_1GIGE + ? GE_DATA_FRAME_RECV_SIZE + : XGE_DATA_FRAME_RECV_SIZE; + default_buff_args.recv_frame_size = args.cast<size_t>( + "recv_frame_size", std::min(default_frame_size, recv_mtu)); + default_buff_args.num_recv_frames = + args.cast<size_t>("num_recv_frames", default_buff_args.num_recv_frames); + default_buff_args.recv_buff_size = args.cast<size_t>("recv_buff_size", 0); + } + + int dpdk_port_id = dpdk_ctx.get_route(conn.addr); + if (dpdk_port_id < 0) { + throw uhd::runtime_error( + "Could not find a DPDK port with route to " + conn.addr); + } + auto recv = transport::dpdk_zero_copy::make(dpdk_ctx, + (const unsigned int)dpdk_port_id, + conn.addr, + BOOST_STRINGIZE(X300_VITA_UDP_PORT), + "0", + default_buff_args, + uhd::device_addr_t()); + + xports.recv = recv; // Note: This is a type cast! + xports.send = xports.recv; + xports.recv_buff_size = + (default_buff_args.recv_frame_size - X300_UDP_RESERVED_FRAME_SIZE) + * default_buff_args.num_recv_frames; + xports.send_buff_size = + (default_buff_args.send_frame_size - X300_UDP_RESERVED_FRAME_SIZE) + * default_buff_args.num_send_frames; + UHD_LOG_TRACE("BUFF", + "num_recv_frames=" + << default_buff_args.num_recv_frames + << ", num_send_frames=" << default_buff_args.num_send_frames + << ", recv_frame_size=" << default_buff_args.recv_frame_size + << ", send_frame_size=" << default_buff_args.send_frame_size); + +#else + UHD_LOG_WARNING("X300", "Cannot create DPDK transport, falling back to UDP"); +#endif + } + if (!xports.recv) { + // Buffering is done in the socket buffers, so size them relative to + // the link rate + default_buff_args.send_buff_size = conn.link_rate / 50; // 20ms + default_buff_args.recv_buff_size = std::max(conn.link_rate / 50, + ETH_MSG_NUM_FRAMES * ETH_MSG_FRAME_SIZE); // enough to hold greater of 20ms or + // number of msg frames + // There is no need for more than 1 send and recv frame since the + // buffering is done in the socket buffers + default_buff_args.num_send_frames = 1; + default_buff_args.num_recv_frames = 1; + if (xport_type == uhd::usrp::device3_impl::CTRL) { + // Increasing number of recv frames here because ctrl_iface uses it + // to determine how many control packets can be in flight before it + // must wait for an ACK + default_buff_args.num_recv_frames = + uhd::rfnoc::CMD_FIFO_SIZE / uhd::rfnoc::MAX_CMD_PKT_SIZE; + } else if (xport_type == uhd::usrp::device3_impl::TX_DATA) { + size_t default_frame_size = conn.link_rate == MAX_RATE_1GIGE + ? GE_DATA_FRAME_SEND_SIZE + : XGE_DATA_FRAME_SEND_SIZE; + default_buff_args.send_frame_size = args.cast<size_t>( + "send_frame_size", std::min(default_frame_size, send_mtu)); + default_buff_args.num_send_frames = + args.cast<size_t>("num_send_frames", default_buff_args.num_send_frames); + default_buff_args.send_buff_size = + args.cast<size_t>("send_buff_size", default_buff_args.send_buff_size); + } else if (xport_type == uhd::usrp::device3_impl::RX_DATA) { + size_t default_frame_size = conn.link_rate == MAX_RATE_1GIGE + ? GE_DATA_FRAME_RECV_SIZE + : XGE_DATA_FRAME_RECV_SIZE; + default_buff_args.recv_frame_size = args.cast<size_t>( + "recv_frame_size", std::min(default_frame_size, recv_mtu)); + // set some buffers so the offload thread actually offloads the + // socket I/O + default_buff_args.num_recv_frames = args.cast<size_t>("num_recv_frames", 2); + default_buff_args.recv_buff_size = + args.cast<size_t>("recv_buff_size", default_buff_args.recv_buff_size); + } + + // make a new transport - fpga has no idea how to talk to us on this yet + udp_zero_copy::buff_params buff_params; + xports.recv = udp_zero_copy::make(conn.addr, + BOOST_STRINGIZE(X300_VITA_UDP_PORT), + default_buff_args, + buff_params); + + // Create a threaded transport for the receive chain only + if (xport_type == uhd::usrp::device3_impl::RX_DATA) { + xports.recv = zero_copy_recv_offload::make( + xports.recv, x300::RECV_OFFLOAD_BUFFER_TIMEOUT); + } + + xports.send = xports.recv; + + // For the UDP transport the buffer size is the size of the socket buffer + // in the kernel + xports.recv_buff_size = buff_params.recv_buff_size; + xports.send_buff_size = buff_params.send_buff_size; + } + + // send a mini packet with SID into the ZPU + // ZPU will reprogram the ethernet framer + UHD_LOGGER_DEBUG("X300") << "programming packet for new xport on " << conn.addr + << " sid " << xports.send_sid; + // YES, get a __send__ buffer from the __recv__ socket + //-- this is the only way to program the framer for recv: + managed_send_buffer::sptr buff = xports.recv->get_send_buff(); + buff->cast<uint32_t*>()[0] = 0; // eth dispatch looks for != 0 + buff->cast<uint32_t*>()[1] = uhd::htonx(xports.send_sid.get()); + buff->commit(8); + buff.reset(); + + return xports; +} + +/****************************************************************************** + * API + *****************************************************************************/ +wb_iface::sptr eth_manager::get_ctrl_iface() +{ + return x300_make_ctrl_iface_enet(_x300_make_udp_connected( + get_pri_eth().addr, BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT))); +} + +void eth_manager::init_link( + const mboard_eeprom_t& mb_eeprom, const std::string& loaded_fpga_image) +{ + double link_max_rate = 0.0; + + // Discover ethernet interfaces + discover_eth(mb_eeprom, loaded_fpga_image); + + /* This is an ETH connection. Figure out what the maximum supported frame + * size is for the transport in the up and down directions. The frame size + * depends on the host PC's NIC's MTU settings. To determine the frame size, + * we test for support up to an expected "ceiling". If the user + * specified a frame size, we use that frame size as the ceiling. If no + * frame size was specified, we use the maximum UHD frame size. + * + * To optimize performance, the frame size should be greater than or equal + * to the frame size that UHD uses so that frames don't get split across + * multiple transmission units - this is why the limits passed into the + * 'determine_max_frame_size' function are actually frame sizes. */ + frame_size_t req_max_frame_size; + req_max_frame_size.recv_frame_size = + (recv_args.has_key("recv_frame_size")) + ? boost::lexical_cast<size_t>(recv_args["recv_frame_size"]) + : x300::DATA_FRAME_MAX_SIZE; + req_max_frame_size.send_frame_size = + (send_args.has_key("send_frame_size")) + ? boost::lexical_cast<size_t>(send_args["send_frame_size"]) + : x300::DATA_FRAME_MAX_SIZE; + +#if defined UHD_PLATFORM_LINUX + const std::string mtu_tool("ip link"); +#elif defined UHD_PLATFORM_WIN32 + const std::string mtu_tool("netsh"); +#else + const std::string mtu_tool("ifconfig"); +#endif + + // Detect the frame size on the path to the USRP + try { + frame_size_t pri_frame_sizes = + determine_max_frame_size(get_pri_eth().addr, req_max_frame_size); + + _max_frame_sizes = pri_frame_sizes; + if (eth_conns.size() > 1) { + frame_size_t sec_frame_sizes = + determine_max_frame_size(eth_conns.at(1).addr, req_max_frame_size); + + // Choose the minimum of the max frame sizes + // to ensure we don't exceed any one of the links' MTU + _max_frame_sizes.recv_frame_size = std::min( + pri_frame_sizes.recv_frame_size, sec_frame_sizes.recv_frame_size); + + _max_frame_sizes.send_frame_size = std::min( + pri_frame_sizes.send_frame_size, sec_frame_sizes.send_frame_size); + } + } catch (std::exception& e) { + UHD_LOGGER_ERROR("X300") << e.what(); + } + + if ((recv_args.has_key("recv_frame_size")) + && (req_max_frame_size.recv_frame_size > _max_frame_sizes.recv_frame_size)) { + UHD_LOGGER_WARNING("X300") + << boost::format("You requested a receive frame size of (%lu) but your " + "NIC's max frame size is (%lu).") + % req_max_frame_size.recv_frame_size % _max_frame_sizes.recv_frame_size + << boost::format("Please verify your NIC's MTU setting using '%s' or set " + "the recv_frame_size argument appropriately.") + % mtu_tool + << "UHD will use the auto-detected max frame size for this connection."; + } + + if ((send_args.has_key("send_frame_size")) + && (req_max_frame_size.send_frame_size > _max_frame_sizes.send_frame_size)) { + UHD_LOGGER_WARNING("X300") + << boost::format("You requested a send frame size of (%lu) but your " + "NIC's max frame size is (%lu).") + % req_max_frame_size.send_frame_size % _max_frame_sizes.send_frame_size + << boost::format("Please verify your NIC's MTU setting using '%s' or set " + "the send_frame_size argument appropriately.") + % mtu_tool + << "UHD will use the auto-detected max frame size for this connection."; + } + + // Check frame sizes + for (auto conn : eth_conns) { + link_max_rate += conn.link_rate; + + size_t rec_send_frame_size = conn.link_rate == MAX_RATE_1GIGE + ? GE_DATA_FRAME_SEND_SIZE + : XGE_DATA_FRAME_SEND_SIZE; + size_t rec_recv_frame_size = conn.link_rate == MAX_RATE_1GIGE + ? GE_DATA_FRAME_RECV_SIZE + : XGE_DATA_FRAME_RECV_SIZE; + + if (_max_frame_sizes.send_frame_size < rec_send_frame_size) { + UHD_LOGGER_WARNING("X300") + << boost::format("For the %s connection, UHD recommends a send frame " + "size of at least %lu for best\nperformance, but " + "your configuration will only allow %lu.") + % conn.addr % rec_send_frame_size + % _max_frame_sizes.send_frame_size + << "This may negatively impact your maximum achievable sample " + "rate.\nCheck the MTU on the interface and/or the send_frame_size " + "argument."; + } + + if (_max_frame_sizes.recv_frame_size < rec_recv_frame_size) { + UHD_LOGGER_WARNING("X300") + << boost::format("For the %s connection, UHD recommends a receive " + "frame size of at least %lu for best\nperformance, " + "but your configuration will only allow %lu.") + % conn.addr % rec_recv_frame_size + % _max_frame_sizes.recv_frame_size + << "This may negatively impact your maximum achievable sample " + "rate.\nCheck the MTU on the interface and/or the recv_frame_size " + "argument."; + } + } + + _tree->create<size_t>("mtu/recv").set(_max_frame_sizes.recv_frame_size); + _tree->create<size_t>("mtu/send").set(_max_frame_sizes.send_frame_size); + _tree->access<double>("link_max_rate").set(link_max_rate); +} + +size_t eth_manager::get_mtu(uhd::direction_t dir) +{ + return dir == uhd::RX_DIRECTION ? _max_frame_sizes.recv_frame_size + : _max_frame_sizes.send_frame_size; +} + + +void eth_manager::discover_eth( + const mboard_eeprom_t mb_eeprom, const std::string& loaded_fpga_image) +{ + udp_simple_factory_t udp_make_connected = x300_get_udp_factory(send_args); + // Load all valid, non-duplicate IP addrs + std::vector<std::string> ip_addrs{_args.get_first_addr()}; + if (not _args.get_second_addr().empty() + && (_args.get_first_addr() != _args.get_second_addr())) { + ip_addrs.push_back(_args.get_second_addr()); + } + + // Clear any previous addresses added + eth_conns.clear(); + + // Index the MB EEPROM addresses + std::vector<std::string> mb_eeprom_addrs; + const size_t num_mb_eeprom_addrs = 4; + for (size_t i = 0; i < num_mb_eeprom_addrs; i++) { + const std::string key = "ip-addr" + boost::to_string(i); + + // Show a warning if there exists duplicate addresses in the mboard eeprom + if (std::find(mb_eeprom_addrs.begin(), mb_eeprom_addrs.end(), mb_eeprom[key]) + != mb_eeprom_addrs.end()) { + UHD_LOGGER_WARNING("X300") << str( + boost::format( + "Duplicate IP address %s found in mboard EEPROM. " + "Device may not function properly. View and reprogram the values " + "using the usrp_burn_mb_eeprom utility.") + % mb_eeprom[key]); + } + mb_eeprom_addrs.push_back(mb_eeprom[key]); + } + + for (const std::string& addr : ip_addrs) { + x300_eth_conn_t conn_iface; + conn_iface.addr = addr; + conn_iface.type = X300_IFACE_NONE; + + // Decide from the mboard eeprom what IP corresponds + // to an interface + for (size_t i = 0; i < mb_eeprom_addrs.size(); i++) { + if (addr == mb_eeprom_addrs[i]) { + // Choose the interface based on the index parity + if (i % 2 == 0) { + conn_iface.type = X300_IFACE_ETH0; + conn_iface.link_rate = loaded_fpga_image == "HG" ? MAX_RATE_1GIGE + : MAX_RATE_10GIGE; + } else { + conn_iface.type = X300_IFACE_ETH1; + conn_iface.link_rate = MAX_RATE_10GIGE; + } + break; + } + } + + // Check default IP addresses if we couldn't + // determine the IP from the mboard eeprom + if (conn_iface.type == X300_IFACE_NONE) { + UHD_LOGGER_WARNING("X300") << str( + boost::format( + "Address %s not found in mboard EEPROM. Address may be wrong or " + "the EEPROM may be corrupt. Attempting to continue with default " + "IP addresses.") + % conn_iface.addr); + + if (addr + == boost::asio::ip::address_v4(uint32_t(X300_DEFAULT_IP_ETH0_1G)) + .to_string()) { + conn_iface.type = X300_IFACE_ETH0; + conn_iface.link_rate = MAX_RATE_1GIGE; + } else if (addr + == boost::asio::ip::address_v4(uint32_t(X300_DEFAULT_IP_ETH1_1G)) + .to_string()) { + conn_iface.type = X300_IFACE_ETH1; + conn_iface.link_rate = MAX_RATE_1GIGE; + } else if (addr + == boost::asio::ip::address_v4(uint32_t(X300_DEFAULT_IP_ETH0_10G)) + .to_string()) { + conn_iface.type = X300_IFACE_ETH0; + conn_iface.link_rate = MAX_RATE_10GIGE; + } else if (addr + == boost::asio::ip::address_v4(uint32_t(X300_DEFAULT_IP_ETH1_10G)) + .to_string()) { + conn_iface.type = X300_IFACE_ETH1; + conn_iface.link_rate = MAX_RATE_10GIGE; + } else { + throw uhd::assertion_error( + str(boost::format( + "X300 Initialization Error: Failed to match address %s with " + "any addresses for the device. Please check the address.") + % conn_iface.addr)); + } + } + + // Save to a vector of connections + if (conn_iface.type != X300_IFACE_NONE) { + // Check the address before we add it + try { + wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_enet( + udp_make_connected( + conn_iface.addr, BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)), + false /* Suppress timeout errors */ + ); + + // Peek the ZPU ctrl to make sure this connection works + zpu_ctrl->peek32(0); + } + + // If the address does not work, throw an error + catch (std::exception&) { + throw uhd::io_error( + str(boost::format("X300 Initialization Error: Invalid address %s") + % conn_iface.addr)); + } + eth_conns.push_back(conn_iface); + } + } + + if (eth_conns.size() == 0) + throw uhd::assertion_error( + "X300 Initialization Error: No ethernet interfaces specified."); +} + +eth_manager::frame_size_t eth_manager::determine_max_frame_size( + const std::string& addr, const frame_size_t& user_frame_size) +{ + auto udp = _x300_make_udp_connected(addr, BOOST_STRINGIZE(X300_MTU_DETECT_UDP_PORT)); + + std::vector<uint8_t> buffer( + std::max(user_frame_size.recv_frame_size, user_frame_size.send_frame_size)); + x300_mtu_t* request = reinterpret_cast<x300_mtu_t*>(&buffer.front()); + constexpr double echo_timeout = 0.020; // 20 ms + + // test holler - check if its supported in this fw version + request->flags = uhd::htonx<uint32_t>(X300_MTU_DETECT_ECHO_REQUEST); + request->size = uhd::htonx<uint32_t>(sizeof(x300_mtu_t)); + udp->send(boost::asio::buffer(buffer, sizeof(x300_mtu_t))); + udp->recv(boost::asio::buffer(buffer), echo_timeout); + if (!(uhd::ntohx<uint32_t>(request->flags) & X300_MTU_DETECT_ECHO_REPLY)) { + throw uhd::not_implemented_error("Holler protocol not implemented"); + } + + // Reducing range of (min,max) by setting max value to 10gig max_frame_size as larger + // sizes are not supported + size_t min_recv_frame_size = sizeof(x300_mtu_t); + size_t max_recv_frame_size = + std::min(user_frame_size.recv_frame_size, x300::DATA_FRAME_MAX_SIZE) & size_t(~3); + size_t min_send_frame_size = sizeof(x300_mtu_t); + size_t max_send_frame_size = + std::min(user_frame_size.send_frame_size, x300::DATA_FRAME_MAX_SIZE) & size_t(~3); + + UHD_LOGGER_DEBUG("X300") << "Determining maximum frame size... "; + while (min_recv_frame_size < max_recv_frame_size) { + size_t test_frame_size = (max_recv_frame_size / 2 + min_recv_frame_size / 2 + 3) + & ~3; + + request->flags = uhd::htonx<uint32_t>(X300_MTU_DETECT_ECHO_REQUEST); + request->size = uhd::htonx<uint32_t>(test_frame_size); + udp->send(boost::asio::buffer(buffer, sizeof(x300_mtu_t))); + + size_t len = udp->recv(boost::asio::buffer(buffer), echo_timeout); + + if (len >= test_frame_size) + min_recv_frame_size = test_frame_size; + else + max_recv_frame_size = test_frame_size - 4; + } + + if (min_recv_frame_size < IP_PROTOCOL_MIN_MTU_SIZE - IP_PROTOCOL_UDP_PLUS_IP_HEADER) { + throw uhd::runtime_error("System receive MTU size is less than the minimum " + "required by the IP protocol."); + } + + while (min_send_frame_size < max_send_frame_size) { + size_t test_frame_size = (max_send_frame_size / 2 + min_send_frame_size / 2 + 3) + & ~3; + + request->flags = uhd::htonx<uint32_t>(X300_MTU_DETECT_ECHO_REQUEST); + request->size = uhd::htonx<uint32_t>(sizeof(x300_mtu_t)); + udp->send(boost::asio::buffer(buffer, test_frame_size)); + + size_t len = udp->recv(boost::asio::buffer(buffer), echo_timeout); + if (len >= sizeof(x300_mtu_t)) + len = uhd::ntohx<uint32_t>(request->size); + + if (len >= test_frame_size) + min_send_frame_size = test_frame_size; + else + max_send_frame_size = test_frame_size - 4; + } + + if (min_send_frame_size < IP_PROTOCOL_MIN_MTU_SIZE - IP_PROTOCOL_UDP_PLUS_IP_HEADER) { + throw uhd::runtime_error( + "System send MTU size is less than the minimum required by the IP protocol."); + } + + frame_size_t frame_size; + // There are cases when NICs accept oversized packets, in which case we'd falsely + // detect a larger-than-possible frame size. A safe and sensible value is the minimum + // of the recv and send frame sizes. + frame_size.recv_frame_size = std::min(min_recv_frame_size, min_send_frame_size); + frame_size.send_frame_size = std::min(min_recv_frame_size, min_send_frame_size); + UHD_LOGGER_INFO("X300") << "Maximum frame size: " << frame_size.send_frame_size + << " bytes."; + return frame_size; +} diff --git a/host/lib/usrp/x300/x300_eth_mgr.hpp b/host/lib/usrp/x300/x300_eth_mgr.hpp new file mode 100644 index 000000000..85b3eabd0 --- /dev/null +++ b/host/lib/usrp/x300/x300_eth_mgr.hpp @@ -0,0 +1,126 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_X300_ETH_MGR_HPP +#define INCLUDED_X300_ETH_MGR_HPP + +#include "x300_device_args.hpp" +#include "x300_mboard_type.hpp" +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_constants.hpp> +#include <uhd/transport/udp_simple.hpp> //mtu +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhdlib/rfnoc/xports.hpp> +#include <functional> +#include <vector> + +namespace uhd { namespace usrp { namespace x300 { + +/*! Helper class to manage the eth connections + */ +class eth_manager +{ +public: + eth_manager(const x300_device_args_t& args, + uhd::property_tree::sptr tree, + const uhd::fs_path& root_path); + + //! Return the motherboard type using eth + static x300_mboard_t get_mb_type_from_eth( + const std::string& resource, const std::string& rpc_port); + + static uhd::device_addrs_t find(const uhd::device_addr_t& hint); + + /*! Return a reference to a ZPU ctrl interface object + */ + uhd::wb_iface::sptr get_ctrl_iface(); + + void init_link( + const mboard_eeprom_t& mb_eeprom, const std::string& loaded_fpga_image); + + size_t get_mtu(uhd::direction_t dir); + + /*! Safely release a ZPU control object + * + * This embeds the release call (provided by \p release_fn) within a safe + * context to avoid multiple accesses to the eth bus. + */ + void release_ctrl_iface(std::function<void(void)>&& release_fn); + + both_xports_t make_transport(both_xports_t xports, + const uhd::usrp::device3_impl::xport_type_t xport_type, + const uhd::device_addr_t& args, + const size_t send_mtu, + const size_t recv_mtu, + std::function<uhd::sid_t(uint32_t, uint32_t)>&& allocate_sid); + +private: + //! Function to create a udp_simple::sptr (kernel-based or DPDK-based) + using udp_simple_factory_t = std::function<uhd::transport::udp_simple::sptr( + const std::string&, const std::string&)>; + + // Ethernet ports + enum x300_eth_iface_t { + X300_IFACE_NONE = 0, + X300_IFACE_ETH0 = 1, + X300_IFACE_ETH1 = 2, + }; + + struct x300_eth_conn_t + { + std::string addr; + x300_eth_iface_t type; + size_t link_rate; + }; + + struct frame_size_t + { + size_t recv_frame_size; + size_t send_frame_size; + }; + + // Get the primary ethernet connection + inline const x300_eth_conn_t& get_pri_eth() const + { + return eth_conns[0]; + } + + static udp_simple_factory_t x300_get_udp_factory(const device_addr_t& args); + + /*! + * Automatically determine the maximum frame size available by sending a UDP packet + * to the device and see which packet sizes actually work. This way, we can take + * switches etc. into account which might live between the device and the host. + */ + frame_size_t determine_max_frame_size( + const std::string& addr, const frame_size_t& user_mtu); + + // Discover the ethernet connections per motherboard + void discover_eth( + const uhd::usrp::mboard_eeprom_t mb_eeprom, const std::string& loaded_fpga_image); + + + const x300_device_args_t _args; + + uhd::property_tree::sptr _tree; + + udp_simple_factory_t _x300_make_udp_connected; + + std::vector<x300_eth_conn_t> eth_conns; + size_t _next_src_addr = 0; + size_t _next_tx_src_addr = 0; + size_t _next_rx_src_addr = 0; + + frame_size_t _max_frame_sizes; + + uhd::device_addr_t recv_args; + uhd::device_addr_t send_args; +}; + +}}} // namespace uhd::usrp::x300 + +#endif /* INCLUDED_X300_ETH_MGR_HPP */ diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp index 8ad1433d3..59ba1153b 100644 --- a/host/lib/usrp/x300/x300_impl.cpp +++ b/host/lib/usrp/x300/x300_impl.cpp @@ -1,278 +1,42 @@ // // Copyright 2013-2016 Ettus Research LLC // Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // #include "x300_impl.hpp" -#include "x300_lvbitx.hpp" +#include "x300_claim.hpp" +#include "x300_mb_eeprom.hpp" #include "x300_mb_eeprom_iface.hpp" #include "x300_mboard_type.hpp" -#include "x310_lvbitx.hpp" #include <uhd/transport/if_addrs.hpp> -#include <uhd/transport/nirio/niusrprio_session.h> -#include <uhd/transport/nirio_zero_copy.hpp> -#include <uhd/transport/udp_constants.hpp> -#include <uhd/transport/udp_zero_copy.hpp> -#include <uhd/transport/zero_copy_recv_offload.hpp> #include <uhd/types/sid.hpp> #include <uhd/usrp/subdev_spec.hpp> #include <uhd/utils/log.hpp> #include <uhd/utils/math.hpp> #include <uhd/utils/paths.hpp> -#include <uhd/utils/platform.hpp> #include <uhd/utils/safe_call.hpp> #include <uhd/utils/static.hpp> -#include <uhdlib/usrp/common/apply_corrections.hpp> -#ifdef HAVE_DPDK -# include <uhdlib/transport/dpdk_simple.hpp> -# include <uhdlib/transport/dpdk_zero_copy.hpp> -#endif #include <boost/algorithm/string.hpp> -#include <boost/asio.hpp> #include <boost/make_shared.hpp> #include <chrono> #include <fstream> #include <thread> +uhd::uart_iface::sptr x300_make_uart_iface(uhd::wb_iface::sptr iface); + using namespace uhd; using namespace uhd::usrp; using namespace uhd::rfnoc; -using namespace uhd::transport; -using namespace uhd::niusrprio; -using namespace uhd::usrp::gpio_atr; using namespace uhd::usrp::x300; namespace asio = boost::asio; -/****************************************************************************** - * Helpers - *****************************************************************************/ -namespace { - -constexpr unsigned int X300_UDP_RESERVED_FRAME_SIZE = 64; - -} // namespace - -static x300_impl::udp_simple_factory_t x300_get_udp_factory(const device_addr_t& args) -{ - x300_impl::udp_simple_factory_t udp_make_connected = udp_simple::make_connected; - if (args.has_key("use_dpdk")) { -#ifdef HAVE_DPDK - udp_make_connected = [](const std::string& addr, const std::string& port) { - auto& ctx = uhd::transport::uhd_dpdk_ctx::get(); - return dpdk_simple::make_connected(ctx, addr, port); - }; -#else - UHD_LOG_WARNING( - "DPDK", "Detected use_dpdk argument, but DPDK support not built in."); -#endif - } - return udp_make_connected; -} - /*********************************************************************** * Discovery over the udp and pcie transport **********************************************************************/ -//@TODO: Refactor the find functions to collapse common code for ethernet and PCIe -static device_addrs_t x300_find_with_addr(const device_addr_t& hint) -{ - x300_impl::udp_simple_factory_t udp_make_broadcast = udp_simple::make_broadcast; - x300_impl::udp_simple_factory_t udp_make_connected = x300_get_udp_factory(hint); -#ifdef HAVE_DPDK - if (hint.has_key("use_dpdk")) { - auto& dpdk_ctx = uhd::transport::uhd_dpdk_ctx::get(); - if (not dpdk_ctx.is_init_done()) { - dpdk_ctx.init(hint); - } - udp_make_broadcast = [](const std::string& addr, const std::string& port) { - auto& ctx = uhd::transport::uhd_dpdk_ctx::get(); - return dpdk_simple::make_broadcast(ctx, addr, port); - }; - } -#endif - udp_simple::sptr comm = - udp_make_broadcast(hint["addr"], BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)); - - // load request struct - x300_fw_comms_t request = x300_fw_comms_t(); - request.flags = uhd::htonx<uint32_t>(X300_FW_COMMS_FLAGS_ACK); - request.sequence = uhd::htonx<uint32_t>(std::rand()); - - // send request - comm->send(asio::buffer(&request, sizeof(request))); - - // loop for replies until timeout - device_addrs_t addrs; - while (true) { - char buff[X300_FW_COMMS_MTU] = {}; - const size_t nbytes = comm->recv(asio::buffer(buff), 0.050); - if (nbytes == 0) - break; - const x300_fw_comms_t* reply = (const x300_fw_comms_t*)buff; - if (request.flags != reply->flags) - continue; - if (request.sequence != reply->sequence) - continue; - device_addr_t new_addr; - new_addr["type"] = "x300"; - new_addr["addr"] = comm->get_recv_addr(); - - // Attempt to read the name from the EEPROM and perform filtering. - // This operation can throw due to compatibility mismatch. - try { - wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_enet( - udp_make_connected( - new_addr["addr"], BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)), - false /* Suppress timeout errors */ - ); - - new_addr["fpga"] = get_fpga_option(zpu_ctrl); - - i2c_core_100_wb32::sptr zpu_i2c = - i2c_core_100_wb32::make(zpu_ctrl, I2C1_BASE); - x300_mb_eeprom_iface::sptr eeprom_iface = - x300_mb_eeprom_iface::make(zpu_ctrl, zpu_i2c); - const mboard_eeprom_t mb_eeprom = x300_impl::get_mb_eeprom(eeprom_iface); - if (mb_eeprom.size() == 0 - or x300_impl::claim_status(zpu_ctrl) == x300_impl::CLAIMED_BY_OTHER) { - // Skip device claimed by another process - continue; - } - new_addr["name"] = mb_eeprom["name"]; - new_addr["serial"] = mb_eeprom["serial"]; - const std::string product_name = - map_mb_type_to_product_name(get_mb_type_from_eeprom(mb_eeprom)); - if (!product_name.empty()) { - new_addr["product"] = product_name; - } - } catch (const std::exception&) { - // set these values as empty string so the device may still be found - // and the filter's below can still operate on the discovered device - new_addr["name"] = ""; - new_addr["serial"] = ""; - } - // filter the discovered device below by matching optional keys - if ((not hint.has_key("name") or hint["name"] == new_addr["name"]) - and (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) - and (not hint.has_key("product") or hint["product"] == new_addr["product"])) { - addrs.push_back(new_addr); - } - } - - return addrs; -} - - -// We need a zpu xport registry to ensure synchronization between the static finder method -// and the instances of the x300_impl class. -typedef uhd::dict<std::string, boost::weak_ptr<wb_iface>> pcie_zpu_iface_registry_t; -UHD_SINGLETON_FCN(pcie_zpu_iface_registry_t, get_pcie_zpu_iface_registry) -static boost::mutex pcie_zpu_iface_registry_mutex; - -static device_addrs_t x300_find_pcie(const device_addr_t& hint, bool explicit_query) -{ - std::string rpc_port_name(std::to_string(NIUSRPRIO_DEFAULT_RPC_PORT)); - if (hint.has_key("niusrpriorpc_port")) { - rpc_port_name = hint["niusrpriorpc_port"]; - } - - device_addrs_t addrs; - niusrprio_session::device_info_vtr dev_info_vtr; - nirio_status status = niusrprio_session::enumerate(rpc_port_name, dev_info_vtr); - if (explicit_query) - nirio_status_to_exception( - status, "x300_find_pcie: Error enumerating NI-RIO devices."); - - for (niusrprio_session::device_info& dev_info : dev_info_vtr) { - device_addr_t new_addr; - new_addr["type"] = "x300"; - new_addr["resource"] = dev_info.resource_name; - std::string resource_d(dev_info.resource_name); - boost::to_upper(resource_d); - - const std::string product_name = map_mb_type_to_product_name( - x300_impl::get_mb_type_from_pcie(resource_d, rpc_port_name)); - if (product_name.empty()) { - continue; - } else { - new_addr["product"] = product_name; - } - - niriok_proxy::sptr kernel_proxy = - niriok_proxy::make_and_open(dev_info.interface_path); - - // Attempt to read the name from the EEPROM and perform filtering. - // This operation can throw due to compatibility mismatch. - try { - // This block could throw an exception if the user is switching to using UHD - // after LabVIEW FPGA. In that case, skip reading the name and serial and pick - // a default FPGA flavor. During make, a new image will be loaded and - // everything will be OK - - wb_iface::sptr zpu_ctrl; - - // Hold on to the registry mutex as long as zpu_ctrl is alive - // to prevent any use by different threads while enumerating - boost::mutex::scoped_lock lock(pcie_zpu_iface_registry_mutex); - - if (get_pcie_zpu_iface_registry().has_key(resource_d)) { - zpu_ctrl = get_pcie_zpu_iface_registry()[resource_d].lock(); - if (!zpu_ctrl) { - get_pcie_zpu_iface_registry().pop(resource_d); - } - } - - // if the registry didn't have a key OR that key was an orphaned weak_ptr - if (!zpu_ctrl) { - zpu_ctrl = x300_make_ctrl_iface_pcie( - kernel_proxy, false /* suppress timeout errors */); - // We don't put this zpu_ctrl in the registry because we need - // a persistent niriok_proxy associated with the object - } - - // Attempt to autodetect the FPGA type - if (not hint.has_key("fpga")) { - new_addr["fpga"] = get_fpga_option(zpu_ctrl); - } - - i2c_core_100_wb32::sptr zpu_i2c = - i2c_core_100_wb32::make(zpu_ctrl, I2C1_BASE); - x300_mb_eeprom_iface::sptr eeprom_iface = - x300_mb_eeprom_iface::make(zpu_ctrl, zpu_i2c); - const mboard_eeprom_t mb_eeprom = x300_impl::get_mb_eeprom(eeprom_iface); - if (mb_eeprom.size() == 0 - or x300_impl::claim_status(zpu_ctrl) == x300_impl::CLAIMED_BY_OTHER) { - // Skip device claimed by another process - continue; - } - new_addr["name"] = mb_eeprom["name"]; - new_addr["serial"] = mb_eeprom["serial"]; - } catch (const std::exception&) { - // set these values as empty string so the device may still be found - // and the filter's below can still operate on the discovered device - if (not hint.has_key("fpga")) { - new_addr["fpga"] = "HG"; - } - new_addr["name"] = ""; - new_addr["serial"] = ""; - } - - // filter the discovered device below by matching optional keys - std::string resource_i = hint.has_key("resource") ? hint["resource"] : ""; - boost::to_upper(resource_i); - - if ((not hint.has_key("resource") or resource_i == resource_d) - and (not hint.has_key("name") or hint["name"] == new_addr["name"]) - and (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) - and (not hint.has_key("product") or hint["product"] == new_addr["product"])) { - addrs.push_back(new_addr); - } - } - return addrs; -} - device_addrs_t x300_find(const device_addr_t& hint_) { // handle the multi-device discovery @@ -303,15 +67,15 @@ device_addrs_t x300_find(const device_addr_t& hint_) hints.resize(1); // in case it was empty device_addr_t hint = hints[0]; device_addrs_t addrs; - if (hint.has_key("type") and hint["type"] != "x300") + if (hint.has_key("type") and hint["type"] != "x300") { return addrs; - + } // use the address given if (hint.has_key("addr")) { device_addrs_t reply_addrs; try { - reply_addrs = x300_find_with_addr(hint); + reply_addrs = eth_manager::find(hint); } catch (const std::exception& ex) { UHD_LOGGER_ERROR("X300") << "X300 Network discovery error " << ex.what(); } catch (...) { @@ -322,7 +86,7 @@ device_addrs_t x300_find(const device_addr_t& hint_) if (!hint.has_key("resource")) { // otherwise, no address was specified, send a broadcast on each interface - for (const if_addrs_t& if_addrs : get_if_addrs()) { + for (const transport::if_addrs_t& if_addrs : transport::get_if_addrs()) { // avoid the loopback device if (if_addrs.inet == asio::ip::address_v4::loopback().to_string()) continue; @@ -355,9 +119,10 @@ device_addrs_t x300_find(const device_addr_t& hint_) } } - device_addrs_t pcie_addrs = x300_find_pcie(hint, hint.has_key("resource")); - if (not pcie_addrs.empty()) + device_addrs_t pcie_addrs = pcie_manager::find(hint, hint.has_key("resource")); + if (not pcie_addrs.empty()) { addrs.insert(addrs.end(), pcie_addrs.begin(), pcie_addrs.end()); + } return addrs; } @@ -406,8 +171,6 @@ x300_impl::x300_impl(const uhd::device_addr_t& dev_addr) : device3_impl(), _sid_ UHD_LOGGER_INFO("X300") << "X300 initialization sequence..."; _tree->create<std::string>("/name").set("X-Series Device"); - _x300_make_udp_connected = x300_get_udp_factory(dev_addr); - const device_addrs_t device_args = separate_device_addr(dev_addr); _mb.resize(device_args.size()); @@ -437,208 +200,13 @@ x300_impl::x300_impl(const uhd::device_addr_t& dev_addr) : device3_impl(), _sid_ } } -void x300_impl::mboard_members_t::discover_eth( - const mboard_eeprom_t mb_eeprom, const std::vector<std::string>& ip_addrs) -{ - x300_impl::udp_simple_factory_t udp_make_connected = x300_get_udp_factory(send_args); - - // Clear any previous addresses added - eth_conns.clear(); - - // Index the MB EEPROM addresses - std::vector<std::string> mb_eeprom_addrs; - const size_t num_mb_eeprom_addrs = 4; - for (size_t i = 0; i < num_mb_eeprom_addrs; i++) { - const std::string key = "ip-addr" + boost::to_string(i); - - // Show a warning if there exists duplicate addresses in the mboard eeprom - if (std::find(mb_eeprom_addrs.begin(), mb_eeprom_addrs.end(), mb_eeprom[key]) - != mb_eeprom_addrs.end()) { - UHD_LOGGER_WARNING("X300") << str( - boost::format( - "Duplicate IP address %s found in mboard EEPROM. " - "Device may not function properly. View and reprogram the values " - "using the usrp_burn_mb_eeprom utility.") - % mb_eeprom[key]); - } - mb_eeprom_addrs.push_back(mb_eeprom[key]); - } - - for (const std::string& addr : ip_addrs) { - x300_eth_conn_t conn_iface; - conn_iface.addr = addr; - conn_iface.type = X300_IFACE_NONE; - - // Decide from the mboard eeprom what IP corresponds - // to an interface - for (size_t i = 0; i < mb_eeprom_addrs.size(); i++) { - if (addr == mb_eeprom_addrs[i]) { - // Choose the interface based on the index parity - if (i % 2 == 0) { - conn_iface.type = X300_IFACE_ETH0; - conn_iface.link_rate = loaded_fpga_image == "HG" - ? x300::MAX_RATE_1GIGE - : x300::MAX_RATE_10GIGE; - } else { - conn_iface.type = X300_IFACE_ETH1; - conn_iface.link_rate = x300::MAX_RATE_10GIGE; - } - break; - } - } - - // Check default IP addresses if we couldn't - // determine the IP from the mboard eeprom - if (conn_iface.type == X300_IFACE_NONE) { - UHD_LOGGER_WARNING("X300") << str( - boost::format( - "Address %s not found in mboard EEPROM. Address may be wrong or " - "the EEPROM may be corrupt. Attempting to continue with default " - "IP addresses.") - % conn_iface.addr); - - if (addr - == boost::asio::ip::address_v4(uint32_t(X300_DEFAULT_IP_ETH0_1G)) - .to_string()) { - conn_iface.type = X300_IFACE_ETH0; - conn_iface.link_rate = x300::MAX_RATE_1GIGE; - } else if (addr - == boost::asio::ip::address_v4(uint32_t(X300_DEFAULT_IP_ETH1_1G)) - .to_string()) { - conn_iface.type = X300_IFACE_ETH1; - conn_iface.link_rate = x300::MAX_RATE_1GIGE; - } else if (addr - == boost::asio::ip::address_v4(uint32_t(X300_DEFAULT_IP_ETH0_10G)) - .to_string()) { - conn_iface.type = X300_IFACE_ETH0; - conn_iface.link_rate = x300::MAX_RATE_10GIGE; - } else if (addr - == boost::asio::ip::address_v4(uint32_t(X300_DEFAULT_IP_ETH1_10G)) - .to_string()) { - conn_iface.type = X300_IFACE_ETH1; - conn_iface.link_rate = x300::MAX_RATE_10GIGE; - } else { - throw uhd::assertion_error( - str(boost::format( - "X300 Initialization Error: Failed to match address %s with " - "any addresses for the device. Please check the address.") - % conn_iface.addr)); - } - } - - // Save to a vector of connections - if (conn_iface.type != X300_IFACE_NONE) { - // Check the address before we add it - try { - wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_enet( - udp_make_connected( - conn_iface.addr, BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)), - false /* Suppress timeout errors */ - ); - - // Peek the ZPU ctrl to make sure this connection works - zpu_ctrl->peek32(0); - } - - // If the address does not work, throw an error - catch (std::exception&) { - throw uhd::io_error( - str(boost::format("X300 Initialization Error: Invalid address %s") - % conn_iface.addr)); - } - eth_conns.push_back(conn_iface); - } - } - - if (eth_conns.size() == 0) - throw uhd::assertion_error( - "X300 Initialization Error: No ethernet interfaces specified."); -} - void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr) { const fs_path mb_path = fs_path("/mboards") / mb_i; mboard_members_t& mb = _mb[mb_i]; - mb.initialization_done = false; - - const std::string thread_id( - boost::lexical_cast<std::string>(boost::this_thread::get_id())); - const std::string thread_msg( - "Thread ID " + thread_id + " for motherboard " + std::to_string(mb_i)); - mb.args.parse(dev_addr); - - std::vector<std::string> eth_addrs; - // Not choosing eth0 based on resource might cause user issues - std::string eth0_addr = dev_addr.has_key("resource") ? dev_addr["resource"] - : dev_addr["addr"]; - eth_addrs.push_back(eth0_addr); - - mb.next_src_addr = 0; // Host source address for blocks - mb.next_tx_src_addr = 0; - mb.next_rx_src_addr = 0; - if (not mb.args.get_second_addr().empty()) { - std::string eth1_addr = mb.args.get_second_addr(); - - // Ensure we do not have duplicate addresses - if (eth1_addr != eth0_addr) - eth_addrs.push_back(eth1_addr); - } - - // Initially store the first address provided to setup communication - // Once we read the eeprom, we use it to map IP to its interface - x300_eth_conn_t init; - init.addr = eth_addrs[0]; - mb.eth_conns.push_back(init); - - mb.xport_path = dev_addr.has_key("resource") ? "nirio" : "eth"; - mb.if_pkt_is_big_endian = mb.xport_path != "nirio"; - - if (mb.xport_path == "nirio") { - nirio_status status = 0; - - const std::string rpc_port_name = mb.args.get_niusrprio_rpc_port(); - UHD_LOGGER_INFO("X300") - << boost::format("Connecting to niusrpriorpc at localhost:%s...") - % rpc_port_name; - - // Instantiate the correct lvbitx object - nifpga_lvbitx::sptr lvbitx; - switch (get_mb_type_from_pcie(mb.args.get_resource(), rpc_port_name)) { - case USRP_X300_MB: - lvbitx.reset(new x300_lvbitx(dev_addr["fpga"])); - break; - case USRP_X310_MB: - case USRP_X310_MB_NI_2974: - lvbitx.reset(new x310_lvbitx(dev_addr["fpga"])); - break; - default: - nirio_status_to_exception( - status, "Motherboard detection error. Please ensure that you \ - have a valid USRP X3x0, NI USRP-294xR, NI USRP-295xR or NI USRP-2974 device and that all the device \ - drivers have loaded successfully."); - } - // Load the lvbitx onto the device - UHD_LOGGER_INFO("X300") - << boost::format("Using LVBITX bitfile %s...") % lvbitx->get_bitfile_path(); - mb.rio_fpga_interface.reset( - new niusrprio_session(dev_addr["resource"], rpc_port_name)); - nirio_status_chain( - mb.rio_fpga_interface->open(lvbitx, dev_addr.has_key("download-fpga")), - status); - nirio_status_to_exception(status, "x300_impl: Could not initialize RIO session."); - - // Tell the quirks object which FIFOs carry TX stream data - const uint32_t tx_data_fifos[2] = { - x300::RADIO_DEST_PREFIX_TX, x300::RADIO_DEST_PREFIX_TX + 3}; - mb.rio_fpga_interface->get_kernel_proxy()->get_rio_quirks().register_tx_streams( - tx_data_fifos, 2); - - _tree->create<size_t>(mb_path / "mtu/recv").set(x300::PCIE_RX_DATA_FRAME_SIZE); - _tree->create<size_t>(mb_path / "mtu/send").set(x300::PCIE_TX_DATA_FRAME_SIZE); - _tree->create<double>(mb_path / "link_max_rate").set(x300::MAX_RATE_PCIE); - } - + mb.xport_path = dev_addr.has_key("resource") ? xport_path_t::NIRIO + : xport_path_t::ETH; for (const std::string& key : dev_addr.keys()) { if (key.find("recv") != std::string::npos) mb.recv_args[key] = dev_addr[key]; @@ -646,37 +214,23 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr) mb.send_args[key] = dev_addr[key]; } -#ifdef HAVE_DPDK - if (dev_addr.has_key("use_dpdk")) { - mb.recv_args["use_dpdk"] = dev_addr["use_dpdk"]; - mb.send_args["use_dpdk"] = dev_addr["use_dpdk"]; - } -#endif - - // create basic communication UHD_LOGGER_DEBUG("X300") << "Setting up basic communication..."; - if (mb.xport_path == "nirio") { - boost::mutex::scoped_lock lock(pcie_zpu_iface_registry_mutex); - if (get_pcie_zpu_iface_registry().has_key(mb.get_pri_eth().addr)) { - throw uhd::assertion_error( - "Someone else has a ZPU transport to the device open. Internal error!"); - } else { - mb.zpu_ctrl = - x300_make_ctrl_iface_pcie(mb.rio_fpga_interface->get_kernel_proxy()); - get_pcie_zpu_iface_registry()[mb.get_pri_eth().addr] = - boost::weak_ptr<wb_iface>(mb.zpu_ctrl); - } + if (mb.xport_path == xport_path_t::NIRIO) { + mb.pcie_mgr = + std::unique_ptr<pcie_manager>(new pcie_manager(mb.args, _tree, mb_path)); + mb.zpu_ctrl = mb.pcie_mgr->get_ctrl_iface(); } else { - mb.zpu_ctrl = x300_make_ctrl_iface_enet(_x300_make_udp_connected( - mb.get_pri_eth().addr, BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT))); + mb.eth_mgr = + std::unique_ptr<eth_manager>(new eth_manager(mb.args, _tree, mb_path)); + mb.zpu_ctrl = mb.eth_mgr->get_ctrl_iface(); } // Claim device if (not try_to_claim(mb.zpu_ctrl)) { throw uhd::runtime_error("Failed to claim device"); } - mb.claimer_task = uhd::task::make( - [this, mb]() { this->claimer_loop(mb.zpu_ctrl); }, "x300_claimer"); + mb.claimer_task = + uhd::task::make([&mb]() { claimer_loop(mb.zpu_ctrl); }, "x300_claimer"); // extract the FW path for the X300 // and live load fw over ethernet link @@ -741,8 +295,8 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr) // Initialize the property with a current copy of the EEPROM contents .set(mb_eeprom) // Whenever this property is written, update the chip - .add_coerced_subscriber([this, eeprom16](const mboard_eeprom_t& mb_eeprom) { - this->set_mb_eeprom(eeprom16, mb_eeprom); + .add_coerced_subscriber([eeprom16](const mboard_eeprom_t& mb_eeprom) { + set_mb_eeprom(eeprom16, mb_eeprom); }); if (mb.args.get_recover_mb_eeprom()) { @@ -775,131 +329,10 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr) //////////////////////////////////////////////////////////////////// // discover interfaces, frame sizes, and link rates //////////////////////////////////////////////////////////////////// - if (mb.xport_path == "nirio") { - _max_frame_sizes.recv_frame_size = PCIE_RX_DATA_FRAME_SIZE; - _max_frame_sizes.send_frame_size = PCIE_TX_DATA_FRAME_SIZE; - } else if (mb.xport_path == "eth") { - double link_max_rate = 0.0; - - // Discover ethernet interfaces - mb.discover_eth(mb_eeprom, eth_addrs); - - /* This is an ETH connection. Figure out what the maximum supported frame - * size is for the transport in the up and down directions. The frame size - * depends on the host PC's NIC's MTU settings. To determine the frame size, - * we test for support up to an expected "ceiling". If the user - * specified a frame size, we use that frame size as the ceiling. If no - * frame size was specified, we use the maximum UHD frame size. - * - * To optimize performance, the frame size should be greater than or equal - * to the frame size that UHD uses so that frames don't get split across - * multiple transmission units - this is why the limits passed into the - * 'determine_max_frame_size' function are actually frame sizes. */ - frame_size_t req_max_frame_size; - req_max_frame_size.recv_frame_size = - (mb.recv_args.has_key("recv_frame_size")) - ? boost::lexical_cast<size_t>(mb.recv_args["recv_frame_size"]) - : x300::DATA_FRAME_MAX_SIZE; - req_max_frame_size.send_frame_size = - (mb.send_args.has_key("send_frame_size")) - ? boost::lexical_cast<size_t>(mb.send_args["send_frame_size"]) - : x300::DATA_FRAME_MAX_SIZE; - -#if defined UHD_PLATFORM_LINUX - const std::string mtu_tool("ip link"); -#elif defined UHD_PLATFORM_WIN32 - const std::string mtu_tool("netsh"); -#else - const std::string mtu_tool("ifconfig"); -#endif - - // Detect the frame size on the path to the USRP - try { - frame_size_t pri_frame_sizes = - determine_max_frame_size(eth_addrs.at(0), req_max_frame_size); - - _max_frame_sizes = pri_frame_sizes; - if (eth_addrs.size() > 1) { - frame_size_t sec_frame_sizes = - determine_max_frame_size(eth_addrs.at(1), req_max_frame_size); - - // Choose the minimum of the max frame sizes - // to ensure we don't exceed any one of the links' MTU - _max_frame_sizes.recv_frame_size = std::min( - pri_frame_sizes.recv_frame_size, sec_frame_sizes.recv_frame_size); - - _max_frame_sizes.send_frame_size = std::min( - pri_frame_sizes.send_frame_size, sec_frame_sizes.send_frame_size); - } - } catch (std::exception& e) { - UHD_LOGGER_ERROR("X300") << e.what(); - } - - if ((mb.recv_args.has_key("recv_frame_size")) - && (req_max_frame_size.recv_frame_size > _max_frame_sizes.recv_frame_size)) { - UHD_LOGGER_WARNING("X300") - << boost::format("You requested a receive frame size of (%lu) but your " - "NIC's max frame size is (%lu).") - % req_max_frame_size.recv_frame_size - % _max_frame_sizes.recv_frame_size - << boost::format("Please verify your NIC's MTU setting using '%s' or set " - "the recv_frame_size argument appropriately.") - % mtu_tool - << "UHD will use the auto-detected max frame size for this connection."; - } - - if ((mb.send_args.has_key("send_frame_size")) - && (req_max_frame_size.send_frame_size > _max_frame_sizes.send_frame_size)) { - UHD_LOGGER_WARNING("X300") - << boost::format("You requested a send frame size of (%lu) but your " - "NIC's max frame size is (%lu).") - % req_max_frame_size.send_frame_size - % _max_frame_sizes.send_frame_size - << boost::format("Please verify your NIC's MTU setting using '%s' or set " - "the send_frame_size argument appropriately.") - % mtu_tool - << "UHD will use the auto-detected max frame size for this connection."; - } - - // Check frame sizes - for (auto conn : mb.eth_conns) { - link_max_rate += conn.link_rate; - - size_t rec_send_frame_size = conn.link_rate == x300::MAX_RATE_1GIGE - ? x300::GE_DATA_FRAME_SEND_SIZE - : x300::XGE_DATA_FRAME_SEND_SIZE; - size_t rec_recv_frame_size = conn.link_rate == x300::MAX_RATE_1GIGE - ? x300::GE_DATA_FRAME_RECV_SIZE - : x300::XGE_DATA_FRAME_RECV_SIZE; - - if (_max_frame_sizes.send_frame_size < rec_send_frame_size) { - UHD_LOGGER_WARNING("X300") - << boost::format("For the %s connection, UHD recommends a send frame " - "size of at least %lu for best\nperformance, but " - "your configuration will only allow %lu.") - % conn.addr % rec_send_frame_size - % _max_frame_sizes.send_frame_size - << "This may negatively impact your maximum achievable sample " - "rate.\nCheck the MTU on the interface and/or the send_frame_size " - "argument."; - } - - if (_max_frame_sizes.recv_frame_size < rec_recv_frame_size) { - UHD_LOGGER_WARNING("X300") - << boost::format("For the %s connection, UHD recommends a receive " - "frame size of at least %lu for best\nperformance, " - "but your configuration will only allow %lu.") - % conn.addr % rec_recv_frame_size - % _max_frame_sizes.recv_frame_size - << "This may negatively impact your maximum achievable sample " - "rate.\nCheck the MTU on the interface and/or the recv_frame_size " - "argument."; - } - } - - _tree->create<size_t>(mb_path / "mtu/recv").set(_max_frame_sizes.recv_frame_size); - _tree->create<size_t>(mb_path / "mtu/send").set(_max_frame_sizes.send_frame_size); - _tree->create<double>(mb_path / "link_max_rate").set(link_max_rate); + if (mb.xport_path == xport_path_t::NIRIO) { + mb.pcie_mgr->init_link(); + } else if (mb.xport_path == xport_path_t::ETH) { + mb.eth_mgr->init_link(mb_eeprom, mb.loaded_fpga_image); } //////////////////////////////////////////////////////////////////// @@ -930,7 +363,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr) //////////////////////////////////////////////////////////////////// // create clock properties //////////////////////////////////////////////////////////////////// - _tree->create<double>(mb_path / "master_clock_rate").set_publisher([mb]() { + _tree->create<double>(mb_path / "master_clock_rate").set_publisher([&mb]() { return mb.clock->get_master_clock_rate(); }); @@ -940,7 +373,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr) //////////////////////////////////////////////////////////////////// // Create the GPSDO control //////////////////////////////////////////////////////////////////// - static const uint32_t dont_look_for_gpsdo = 0x1234abcdul; + static constexpr uint32_t dont_look_for_gpsdo = 0x1234abcdul; // otherwise if not disabled, look for the internal GPSDO if (mb.zpu_ctrl->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_GPSDO_STATUS)) @@ -972,10 +405,8 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr) .add_coerced_subscriber([this, &mb](const std::string& time_source) { this->update_time_source(mb, time_source); }); - static const std::vector<std::string> time_sources = { - "internal", "external", "gpsdo"}; _tree->create<std::vector<std::string>>(mb_path / "time_source" / "options") - .set(time_sources); + .set(TIME_SOURCE_OPTIONS); // setup the time output, default to ON _tree->create<bool>(mb_path / "time_source" / "output") @@ -992,10 +423,8 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr) .add_coerced_subscriber([this, &mb](const std::string& clock_source) { this->update_clock_source(mb, clock_source); }); - static const std::vector<std::string> clock_source_options = { - "internal", "external", "gpsdo"}; _tree->create<std::vector<std::string>>(mb_path / "clock_source" / "options") - .set(clock_source_options); + .set(CLOCK_SOURCE_OPTIONS); // setup external reference options. default to 10 MHz input reference _tree->create<std::string>(mb_path / "clock_source" / "external"); @@ -1107,14 +536,10 @@ x300_impl::~x300_impl(void) for (mboard_members_t& mb : _mb) { // kill the claimer task and unclaim the device mb.claimer_task.reset(); - { // Critical section - boost::mutex::scoped_lock lock(pcie_zpu_iface_registry_mutex); + if (mb.xport_path == xport_path_t::NIRIO) { + mb.pcie_mgr->release_ctrl_iface([&mb]() { release(mb.zpu_ctrl); }); + } else { release(mb.zpu_ctrl); - // If the process is killed, the entire registry will disappear so we - // don't need to worry about unclean shutdowns here. - if (get_pcie_zpu_iface_registry().has_key(mb.get_pri_eth().addr)) { - get_pcie_zpu_iface_registry().pop(mb.get_pri_eth().addr); - } } } } catch (...) { @@ -1122,67 +547,13 @@ x300_impl::~x300_impl(void) } } -uint32_t x300_impl::mboard_members_t::allocate_pcie_dma_chan( - const uhd::sid_t& tx_sid, const xport_type_t xport_type) -{ - static const uint32_t CTRL_CHANNEL = 0; - static const uint32_t ASYNC_MSG_CHANNEL = 1; - static const uint32_t FIRST_DATA_CHANNEL = 2; - if (xport_type == CTRL) { - return CTRL_CHANNEL; - } else if (xport_type == ASYNC_MSG) { - return ASYNC_MSG_CHANNEL; - } else { - // sid_t has no comparison defined, so we need to convert it uint32_t - uint32_t raw_sid = tx_sid.get(); - - if (_dma_chan_pool.count(raw_sid) == 0) { - size_t channel = _dma_chan_pool.size() + FIRST_DATA_CHANNEL; - if (channel > x300::PCIE_MAX_CHANNELS) { - throw uhd::runtime_error( - "Trying to allocate more DMA channels than are available"); - } - _dma_chan_pool[raw_sid] = channel; - UHD_LOGGER_DEBUG("X300") - << "Assigning PCIe DMA channel " << _dma_chan_pool[raw_sid] << " to SID " - << tx_sid.to_pp_string_hex(); - } - - return _dma_chan_pool[raw_sid]; - } -} - -static uint32_t extract_sid_from_pkt(void* pkt, size_t) -{ - return uhd::sid_t(uhd::wtohx(static_cast<const uint32_t*>(pkt)[1])).get_dst(); -} - -static uhd::transport::muxed_zero_copy_if::sptr make_muxed_pcie_msg_xport( - uhd::niusrprio::niusrprio_session::sptr rio_fpga_interface, - uint32_t dma_channel_num, - size_t max_muxed_ports) -{ - zero_copy_xport_params buff_args; - buff_args.send_frame_size = x300::PCIE_MSG_FRAME_SIZE; - buff_args.recv_frame_size = x300::PCIE_MSG_FRAME_SIZE; - buff_args.num_send_frames = x300::PCIE_MSG_NUM_FRAMES; - buff_args.num_recv_frames = x300::PCIE_MSG_NUM_FRAMES; - - zero_copy_if::sptr base_xport = nirio_zero_copy::make( - rio_fpga_interface, dma_channel_num, buff_args, uhd::device_addr_t()); - return muxed_zero_copy_if::make(base_xport, extract_sid_from_pkt, max_muxed_ports); -} - uhd::both_xports_t x300_impl::make_transport(const uhd::sid_t& address, const xport_type_t xport_type, const uhd::device_addr_t& args) { - const size_t mb_index = address.get_dst_addr() - x300::DST_ADDR; - mboard_members_t& mb = _mb[mb_index]; - zero_copy_xport_params default_buff_args; - + const size_t mb_index = address.get_dst_addr() - x300::DST_ADDR; + mboard_members_t& mb = _mb[mb_index]; both_xports_t xports; - xports.endianness = mb.if_pkt_is_big_endian ? ENDIANNESS_BIG : ENDIANNESS_LITTLE; // Calculate MTU based on MTU in args and device limitations const size_t send_mtu = args.cast<size_t>("mtu", @@ -1190,183 +561,24 @@ uhd::both_xports_t x300_impl::make_transport(const uhd::sid_t& address, const size_t recv_mtu = args.cast<size_t>("mtu", get_mtu(mb_index, uhd::RX_DIRECTION)); - if (mb.xport_path == "nirio") { - xports.lossless = true; + if (mb.xport_path == xport_path_t::NIRIO) { xports.send_sid = this->allocate_sid(mb, address, x300::SRC_ADDR0, x300::XB_DST_PCI); xports.recv_sid = xports.send_sid.reversed(); - - uint32_t dma_channel_num = mb.allocate_pcie_dma_chan(xports.send_sid, xport_type); - if (xport_type == CTRL) { - // Transport for control stream - if (not mb.ctrl_dma_xport) { - // One underlying DMA channel will handle - // all control traffic - mb.ctrl_dma_xport = make_muxed_pcie_msg_xport(mb.rio_fpga_interface, - dma_channel_num, - x300::PCIE_MAX_MUXED_CTRL_XPORTS); - } - // Create a virtual control transport - xports.recv = mb.ctrl_dma_xport->make_stream(xports.recv_sid.get_dst()); - } else if (xport_type == ASYNC_MSG) { - // Transport for async message stream - if (not mb.async_msg_dma_xport) { - // One underlying DMA channel will handle - // all async message traffic - mb.async_msg_dma_xport = make_muxed_pcie_msg_xport(mb.rio_fpga_interface, - dma_channel_num, - x300::PCIE_MAX_MUXED_ASYNC_XPORTS); - } - // Create a virtual async message transport - xports.recv = mb.async_msg_dma_xport->make_stream(xports.recv_sid.get_dst()); - } else if (xport_type == TX_DATA) { - default_buff_args.send_frame_size = args.cast<size_t>( - "send_frame_size", std::min(send_mtu, - x300::PCIE_TX_DATA_FRAME_SIZE)); - default_buff_args.num_send_frames = args.cast<size_t>( - "num_send_frames", x300::PCIE_TX_DATA_NUM_FRAMES); - default_buff_args.send_buff_size = args.cast<size_t>( - "send_buff_size", 0); - default_buff_args.recv_frame_size = x300::PCIE_MSG_FRAME_SIZE; - default_buff_args.num_recv_frames = x300::PCIE_MSG_NUM_FRAMES; - xports.recv = nirio_zero_copy::make( - mb.rio_fpga_interface, dma_channel_num, default_buff_args); - } else if (xport_type == RX_DATA) { - default_buff_args.send_frame_size = x300::PCIE_MSG_FRAME_SIZE; - default_buff_args.num_send_frames = x300::PCIE_MSG_NUM_FRAMES; - default_buff_args.recv_frame_size = args.cast<size_t>( - "recv_frame_size", std::min(recv_mtu, - x300::PCIE_RX_DATA_FRAME_SIZE)); - default_buff_args.num_recv_frames = args.cast<size_t>( - "num_recv_frames", x300::PCIE_RX_DATA_NUM_FRAMES); - default_buff_args.recv_buff_size = args.cast<size_t>( - "recv_buff_size", 0); - xports.recv = nirio_zero_copy::make( - mb.rio_fpga_interface, dma_channel_num, default_buff_args); - } - - xports.send = xports.recv; - - // Router config word is: - // - Upper 16 bits: Destination address (e.g. 0.0) - // - Lower 16 bits: DMA channel - uint32_t router_config_word = (xports.recv_sid.get_dst() << 16) | dma_channel_num; - mb.rio_fpga_interface->get_kernel_proxy()->poke( - PCIE_ROUTER_REG(0), router_config_word); - - // For the nirio transport, buffer size is depends on the frame size and num - // frames - xports.recv_buff_size = - xports.recv->get_num_recv_frames() * xports.recv->get_recv_frame_size(); - xports.send_buff_size = - xports.send->get_num_send_frames() * xports.send->get_send_frame_size(); - -#ifdef HAVE_DPDK - } else if (mb.xport_path == "eth" and mb.recv_args.has_key("use_dpdk")) { - auto& dpdk_ctx = uhd::transport::uhd_dpdk_ctx::get(); - // Decide on the IP/Interface pair based on the endpoint index - size_t& next_src_addr = xport_type == TX_DATA - ? mb.next_tx_src_addr - : xport_type == RX_DATA ? mb.next_rx_src_addr - : mb.next_src_addr; - x300_eth_conn_t conn = mb.eth_conns[next_src_addr]; - const uint32_t xbar_src_addr = next_src_addr == 0 ? x300::SRC_ADDR0 - : x300::SRC_ADDR1; - const uint32_t xbar_src_dst = conn.type == X300_IFACE_ETH0 ? x300::XB_DST_E0 - : x300::XB_DST_E1; - - // Do not increment src addr for tx_data by default, using dual ethernet - // with the DMA FIFO causes sequence errors to DMA FIFO bandwidth - // limitations. - if (xport_type != TX_DATA || mb.args.get_enable_tx_dual_eth()) { - next_src_addr = (next_src_addr + 1) % mb.eth_conns.size(); - } - - xports.send_sid = this->allocate_sid(mb, address, xbar_src_addr, xbar_src_dst); - xports.recv_sid = xports.send_sid.reversed(); - - // Set size and number of frames - default_buff_args.send_frame_size = std::min(send_mtu, - x300::ETH_MSG_FRAME_SIZE); - default_buff_args.recv_frame_size = std::min(recv_mtu, - x300::ETH_MSG_FRAME_SIZE); - default_buff_args.num_recv_frames = x300::ETH_MSG_NUM_FRAMES; - default_buff_args.num_send_frames = x300::ETH_MSG_NUM_FRAMES; - if (xport_type == CTRL) { - // Increasing number of recv frames here because ctrl_iface uses it - // to determine how many control packets can be in flight before it - // must wait for an ACK - default_buff_args.num_recv_frames = - uhd::rfnoc::CMD_FIFO_SIZE / uhd::rfnoc::MAX_CMD_PKT_SIZE; - } else if (xport_type == TX_DATA) { - size_t default_frame_size = conn.link_rate == x300::MAX_RATE_1GIGE - ? x300::GE_DATA_FRAME_SEND_SIZE - : x300::XGE_DATA_FRAME_SEND_SIZE; - default_buff_args.send_frame_size = - args.cast<size_t>("send_frame_size", - std::min(default_frame_size, send_mtu)); - default_buff_args.num_send_frames = - args.cast<size_t>("num_send_frames", - default_buff_args.num_send_frames); - default_buff_args.send_buff_size = - args.cast<size_t>("send_buff_size", 0); - } else if (xport_type == RX_DATA) { - size_t default_frame_size = conn.link_rate == x300::MAX_RATE_1GIGE - ? x300::GE_DATA_FRAME_RECV_SIZE - : x300::XGE_DATA_FRAME_RECV_SIZE; - default_buff_args.recv_frame_size = - args.cast<size_t>("recv_frame_size", - std::min(default_frame_size, recv_mtu)); - default_buff_args.num_recv_frames = - args.cast<size_t>("num_recv_frames", - default_buff_args.num_recv_frames); - default_buff_args.recv_buff_size = - args.cast<size_t>("recv_buff_size", 0); - } - - int dpdk_port_id = dpdk_ctx.get_route(conn.addr); - if (dpdk_port_id < 0) { - throw uhd::runtime_error( - "Could not find a DPDK port with route to " + conn.addr); - } - auto recv = transport::dpdk_zero_copy::make( - dpdk_ctx, - (const unsigned int) dpdk_port_id, - conn.addr, - BOOST_STRINGIZE(X300_VITA_UDP_PORT), - "0", - default_buff_args, - uhd::device_addr_t() - ); - - xports.recv = recv; // Note: This is a type cast! - xports.send = xports.recv; - xports.recv_buff_size = - (default_buff_args.recv_frame_size - X300_UDP_RESERVED_FRAME_SIZE) - * default_buff_args.num_recv_frames; - xports.send_buff_size = - (default_buff_args.send_frame_size - X300_UDP_RESERVED_FRAME_SIZE) - * default_buff_args.num_send_frames; - UHD_LOG_TRACE("BUFF", - "num_recv_frames=" - << default_buff_args.num_recv_frames - << ", num_send_frames=" << default_buff_args.num_send_frames - << ", recv_frame_size=" << default_buff_args.recv_frame_size - << ", send_frame_size=" << default_buff_args.send_frame_size); - // send a mini packet with SID into the ZPU - // ZPU will reprogram the ethernet framer - UHD_LOGGER_DEBUG("X300") << "programming packet for new xport on " << conn.addr - << " sid " << xports.send_sid; - // YES, get a __send__ buffer from the __recv__ socket - //-- this is the only way to program the framer for recv: - managed_send_buffer::sptr buff = xports.recv->get_send_buff(); - buff->cast<uint32_t*>()[0] = 0; // eth dispatch looks for != 0 - buff->cast<uint32_t*>()[1] = uhd::htonx(xports.send_sid.get()); - buff->commit(8); - buff.reset(); + return mb.pcie_mgr->make_transport(xports, xport_type, args, send_mtu, recv_mtu); + } else if (mb.xport_path == xport_path_t::ETH) { + xports = mb.eth_mgr->make_transport(xports, + xport_type, + args, + send_mtu, + recv_mtu, + [this, &mb, address](const uint32_t src_addr, const uint32_t src_dst) { + return this->allocate_sid(mb, address, src_addr, src_dst); + }); // reprogram the ethernet dispatcher's udp port (should be safe to always set) - UHD_LOGGER_TRACE("X300") << "reprogram the ethernet dispatcher's udp port"; + UHD_LOGGER_TRACE("X300") + << "reprogram the ethernet dispatcher's udp port to " << X300_VITA_UDP_PORT; mb.zpu_ctrl->poke32( SR_ADDR(SET0_BASE, (ZPU_SR_ETHINT0 + 8 + 3)), X300_VITA_UDP_PORT); mb.zpu_ctrl->poke32( @@ -1375,128 +587,10 @@ uhd::both_xports_t x300_impl::make_transport(const uhd::sid_t& address, // Do a peek to an arbitrary address to guarantee that the // ethernet framer has been programmed before we return. mb.zpu_ctrl->peek32(0); -#endif - } else if (mb.xport_path == "eth") { - // Decide on the IP/Interface pair based on the endpoint index - size_t& next_src_addr = xport_type == TX_DATA - ? mb.next_tx_src_addr - : xport_type == RX_DATA ? mb.next_rx_src_addr - : mb.next_src_addr; - x300_eth_conn_t conn = mb.eth_conns[next_src_addr]; - const uint32_t xbar_src_addr = next_src_addr == 0 ? x300::SRC_ADDR0 - : x300::SRC_ADDR1; - const uint32_t xbar_src_dst = conn.type == X300_IFACE_ETH0 ? x300::XB_DST_E0 - : x300::XB_DST_E1; - - // Do not increment src addr for tx_data by default, using dual ethernet - // with the DMA FIFO causes sequence errors to DMA FIFO bandwidth - // limitations. - if (xport_type != TX_DATA || mb.args.get_enable_tx_dual_eth()) { - next_src_addr = (next_src_addr + 1) % mb.eth_conns.size(); - } - - xports.send_sid = this->allocate_sid(mb, address, xbar_src_addr, xbar_src_dst); - xports.recv_sid = xports.send_sid.reversed(); - - // Set size and number of frames - default_buff_args.send_frame_size = std::min(send_mtu, - x300::ETH_MSG_FRAME_SIZE); - default_buff_args.recv_frame_size = std::min(recv_mtu, - x300::ETH_MSG_FRAME_SIZE); - // Buffering is done in the socket buffers, so size them relative to - // the link rate - default_buff_args.send_buff_size = conn.link_rate / 50; // 20ms - default_buff_args.recv_buff_size = std::max(conn.link_rate / 50, - x300::ETH_MSG_NUM_FRAMES - * x300::ETH_MSG_FRAME_SIZE); // enough to hold greater of 20ms or number - // of msg frames - // There is no need for more than 1 send and recv frame since the - // buffering is done in the socket buffers - default_buff_args.num_send_frames = 1; - default_buff_args.num_recv_frames = 1; - if (xport_type == CTRL) { - // Increasing number of recv frames here because ctrl_iface uses it - // to determine how many control packets can be in flight before it - // must wait for an ACK - default_buff_args.num_recv_frames = - uhd::rfnoc::CMD_FIFO_SIZE / uhd::rfnoc::MAX_CMD_PKT_SIZE; - } else if (xport_type == TX_DATA) { - size_t default_frame_size = conn.link_rate == x300::MAX_RATE_1GIGE - ? x300::GE_DATA_FRAME_SEND_SIZE - : x300::XGE_DATA_FRAME_SEND_SIZE; - default_buff_args.send_frame_size = - args.cast<size_t>("send_frame_size", - std::min(default_frame_size, send_mtu)); - default_buff_args.num_send_frames = - args.cast<size_t>("num_send_frames", - default_buff_args.num_send_frames); - default_buff_args.send_buff_size = - args.cast<size_t>("send_buff_size", - default_buff_args.send_buff_size); - } else if (xport_type == RX_DATA) { - size_t default_frame_size = conn.link_rate == x300::MAX_RATE_1GIGE - ? x300::GE_DATA_FRAME_RECV_SIZE - : x300::XGE_DATA_FRAME_RECV_SIZE; - default_buff_args.recv_frame_size = - args.cast<size_t>("recv_frame_size", - std::min(default_frame_size, recv_mtu)); - // set some buffers so the offload thread actually offloads the - // socket I/O - default_buff_args.num_recv_frames = - args.cast<size_t>("num_recv_frames", 2); - default_buff_args.recv_buff_size = - args.cast<size_t>("recv_buff_size", - default_buff_args.recv_buff_size); - } - // make a new transport - fpga has no idea how to talk to us on this yet - udp_zero_copy::buff_params buff_params; - xports.recv = udp_zero_copy::make(conn.addr, - BOOST_STRINGIZE(X300_VITA_UDP_PORT), - default_buff_args, - buff_params); - - // Create a threaded transport for the receive chain only - // Note that this shouldn't affect PCIe - if (xport_type == RX_DATA) { - xports.recv = zero_copy_recv_offload::make( - xports.recv, x300::RECV_OFFLOAD_BUFFER_TIMEOUT); - } - xports.send = xports.recv; - - // For the UDP transport the buffer size is the size of the socket buffer - // in the kernel - xports.recv_buff_size = buff_params.recv_buff_size; - xports.send_buff_size = buff_params.send_buff_size; - - // clear the ethernet dispatcher's udp port - // NOT clearing this, the dispatcher is now intelligent - //_zpu_ctrl->poke32(SR_ADDR(SET0_BASE, (ZPU_SR_ETHINT0+8+3)), 0); - - // send a mini packet with SID into the ZPU - // ZPU will reprogram the ethernet framer - UHD_LOGGER_DEBUG("X300") << "programming packet for new xport on " << conn.addr - << " sid " << xports.send_sid; - // YES, get a __send__ buffer from the __recv__ socket - //-- this is the only way to program the framer for recv: - managed_send_buffer::sptr buff = xports.recv->get_send_buff(); - buff->cast<uint32_t*>()[0] = 0; // eth dispatch looks for != 0 - buff->cast<uint32_t*>()[1] = uhd::htonx(xports.send_sid.get()); - buff->commit(8); - buff.reset(); - - // reprogram the ethernet dispatcher's udp port (should be safe to always set) - UHD_LOGGER_TRACE("X300") << "reprogram the ethernet dispatcher's udp port"; - mb.zpu_ctrl->poke32( - SR_ADDR(SET0_BASE, (ZPU_SR_ETHINT0 + 8 + 3)), X300_VITA_UDP_PORT); - mb.zpu_ctrl->poke32( - SR_ADDR(SET0_BASE, (ZPU_SR_ETHINT1 + 8 + 3)), X300_VITA_UDP_PORT); - - // Do a peek to an arbitrary address to guarantee that the - // ethernet framer has been programmed before we return. - mb.zpu_ctrl->peek32(0); + return xports; } - return xports; + UHD_THROW_INVALID_CODE_PATH(); } @@ -1719,177 +813,20 @@ bool x300_impl::is_pps_present(mboard_members_t& mb) } /*********************************************************************** - * claimer logic - **********************************************************************/ - -void x300_impl::claimer_loop(wb_iface::sptr iface) -{ - claim(iface); - std::this_thread::sleep_for(std::chrono::seconds(1)); -} - -x300_impl::claim_status_t x300_impl::claim_status(wb_iface::sptr iface) -{ - claim_status_t claim_status = CLAIMED_BY_OTHER; // Default to most restrictive - auto timeout_time = std::chrono::steady_clock::now() + std::chrono::seconds(1); - while (std::chrono::steady_clock::now() < timeout_time) { - // If timed out, then device is definitely unclaimed - if (iface->peek32(X300_FW_SHMEM_ADDR(X300_FW_SHMEM_CLAIM_STATUS)) == 0) { - claim_status = UNCLAIMED; - break; - } - - // otherwise check claim src to determine if another thread with the same src has - // claimed the device - uint32_t hash = iface->peek32(X300_FW_SHMEM_ADDR(X300_FW_SHMEM_CLAIM_SRC)); - if (hash == 0) { - // A non-zero claim status and an empty hash means the claim might - // be in the process of being released. This is possible because - // older firmware takes a long time to update the status. Wait and - // check status again. - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - continue; - } - claim_status = (hash == get_process_hash() ? CLAIMED_BY_US : CLAIMED_BY_OTHER); - break; - } - return claim_status; -} - -void x300_impl::claim(wb_iface::sptr iface) -{ - iface->poke32(X300_FW_SHMEM_ADDR(X300_FW_SHMEM_CLAIM_TIME), uint32_t(time(NULL))); - iface->poke32(X300_FW_SHMEM_ADDR(X300_FW_SHMEM_CLAIM_SRC), get_process_hash()); -} - -bool x300_impl::try_to_claim(wb_iface::sptr iface, long timeout_ms) -{ - const auto timeout_time = - std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms); - while (1) { - claim_status_t status = claim_status(iface); - if (status == UNCLAIMED) { - claim(iface); - // It takes the claimer 10ms to update status, so wait 20ms before verifying - // claim - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - continue; - } - if (status == CLAIMED_BY_US) { - break; - } - if (std::chrono::steady_clock::now() > timeout_time) { - // Another process owns the device - give up - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - return true; -} - -void x300_impl::release(wb_iface::sptr iface) -{ - iface->poke32(X300_FW_SHMEM_ADDR(X300_FW_SHMEM_CLAIM_TIME), 0); - iface->poke32(X300_FW_SHMEM_ADDR(X300_FW_SHMEM_CLAIM_SRC), 0); -} - -/*********************************************************************** * Frame size detection **********************************************************************/ -x300_impl::frame_size_t x300_impl::determine_max_frame_size( - const std::string& addr, const frame_size_t& user_frame_size) +size_t x300_impl::get_mtu(const size_t mb_index, const uhd::direction_t dir) { - udp_simple::sptr udp = - _x300_make_udp_connected(addr, BOOST_STRINGIZE(X300_MTU_DETECT_UDP_PORT)); - - std::vector<uint8_t> buffer( - std::max(user_frame_size.recv_frame_size, user_frame_size.send_frame_size)); - x300_mtu_t* request = reinterpret_cast<x300_mtu_t*>(&buffer.front()); - static const double echo_timeout = 0.020; // 20 ms - - // test holler - check if its supported in this fw version - request->flags = uhd::htonx<uint32_t>(X300_MTU_DETECT_ECHO_REQUEST); - request->size = uhd::htonx<uint32_t>(sizeof(x300_mtu_t)); - udp->send(boost::asio::buffer(buffer, sizeof(x300_mtu_t))); - udp->recv(boost::asio::buffer(buffer), echo_timeout); - if (!(uhd::ntohx<uint32_t>(request->flags) & X300_MTU_DETECT_ECHO_REPLY)) - throw uhd::not_implemented_error("Holler protocol not implemented"); - - // Reducing range of (min,max) by setting max value to 10gig max_frame_size as larger - // sizes are not supported - size_t min_recv_frame_size = sizeof(x300_mtu_t); - size_t max_recv_frame_size = - std::min(user_frame_size.recv_frame_size, x300::DATA_FRAME_MAX_SIZE) & size_t(~3); - size_t min_send_frame_size = sizeof(x300_mtu_t); - size_t max_send_frame_size = - std::min(user_frame_size.send_frame_size, x300::DATA_FRAME_MAX_SIZE) & size_t(~3); - - UHD_LOGGER_DEBUG("X300") << "Determining maximum frame size... "; - while (min_recv_frame_size < max_recv_frame_size) { - size_t test_frame_size = (max_recv_frame_size / 2 + min_recv_frame_size / 2 + 3) - & ~3; - - request->flags = uhd::htonx<uint32_t>(X300_MTU_DETECT_ECHO_REQUEST); - request->size = uhd::htonx<uint32_t>(test_frame_size); - udp->send(boost::asio::buffer(buffer, sizeof(x300_mtu_t))); - - size_t len = udp->recv(boost::asio::buffer(buffer), echo_timeout); - - if (len >= test_frame_size) - min_recv_frame_size = test_frame_size; - else - max_recv_frame_size = test_frame_size - 4; - } - - if (min_recv_frame_size < IP_PROTOCOL_MIN_MTU_SIZE - IP_PROTOCOL_UDP_PLUS_IP_HEADER) { - throw uhd::runtime_error("System receive MTU size is less than the minimum " - "required by the IP protocol."); - } - - while (min_send_frame_size < max_send_frame_size) { - size_t test_frame_size = (max_send_frame_size / 2 + min_send_frame_size / 2 + 3) - & ~3; - - request->flags = uhd::htonx<uint32_t>(X300_MTU_DETECT_ECHO_REQUEST); - request->size = uhd::htonx<uint32_t>(sizeof(x300_mtu_t)); - udp->send(boost::asio::buffer(buffer, test_frame_size)); - - size_t len = udp->recv(boost::asio::buffer(buffer), echo_timeout); - if (len >= sizeof(x300_mtu_t)) - len = uhd::ntohx<uint32_t>(request->size); - - if (len >= test_frame_size) - min_send_frame_size = test_frame_size; - else - max_send_frame_size = test_frame_size - 4; - } - - if (min_send_frame_size < IP_PROTOCOL_MIN_MTU_SIZE - IP_PROTOCOL_UDP_PLUS_IP_HEADER) { - throw uhd::runtime_error( - "System send MTU size is less than the minimum required by the IP protocol."); + auto& mb = _mb.at(mb_index); + if (mb.xport_path == xport_path_t::NIRIO) { + return mb.pcie_mgr->get_mtu(dir); } - - frame_size_t frame_size; - // There are cases when NICs accept oversized packets, in which case we'd falsely - // detect a larger-than-possible frame size. A safe and sensible value is the minimum - // of the recv and send frame sizes. - frame_size.recv_frame_size = std::min(min_recv_frame_size, min_send_frame_size); - frame_size.send_frame_size = std::min(min_recv_frame_size, min_send_frame_size); - UHD_LOGGER_INFO("X300") << "Maximum frame size: " << frame_size.send_frame_size - << " bytes."; - return frame_size; -} - -size_t x300_impl::get_mtu(const size_t /*mb_index*/, const uhd::direction_t dir) -{ - return (dir == RX_DIRECTION) ? _max_frame_sizes.recv_frame_size : - _max_frame_sizes.send_frame_size; + return mb.eth_mgr->get_mtu(dir); } /*********************************************************************** * compat checks **********************************************************************/ - void x300_impl::check_fw_compat(const fs_path& mb_path, const mboard_members_t& members) { auto iface = members.zpu_ctrl; @@ -1901,10 +838,11 @@ void x300_impl::check_fw_compat(const fs_path& mb_path, const mboard_members_t& if (compat_major != X300_FW_COMPAT_MAJOR) { const std::string image_loader_path = (fs::path(uhd::get_pkg_path()) / "bin" / "uhd_image_loader").string(); - const std::string image_loader_cmd = - str(boost::format("\"%s\" --args=\"type=x300,%s=%s\"") % image_loader_path - % (members.xport_path == "eth" ? "addr" : "resource") - % members.get_pri_eth().addr); + const std::string image_loader_cmd = str( + boost::format("\"%s\" --args=\"type=x300,%s=%s\"") % image_loader_path + % (members.xport_path == xport_path_t::ETH ? "addr" : "resource") + % (members.xport_path == xport_path_t::ETH ? members.args.get_first_addr() + : members.args.get_resource())); throw uhd::runtime_error( str(boost::format( @@ -1934,10 +872,11 @@ void x300_impl::check_fpga_compat(const fs_path& mb_path, const mboard_members_t if (compat_major != X300_FPGA_COMPAT_MAJOR) { std::string image_loader_path = (fs::path(uhd::get_pkg_path()) / "bin" / "uhd_image_loader").string(); - std::string image_loader_cmd = - str(boost::format("\"%s\" --args=\"type=x300,%s=%s\"") % image_loader_path - % (members.xport_path == "eth" ? "addr" : "resource") - % members.get_pri_eth().addr); + std::string image_loader_cmd = str( + boost::format("\"%s\" --args=\"type=x300,%s=%s\"") % image_loader_path + % (members.xport_path == xport_path_t::ETH ? "addr" : "resource") + % (members.xport_path == xport_path_t::ETH ? members.args.get_first_addr() + : members.args.get_resource())); throw uhd::runtime_error( str(boost::format( @@ -1967,24 +906,3 @@ void x300_impl::check_fpga_compat(const fs_path& mb_path, const mboard_members_t << " git hash: " << git_hash_str); } -x300_mboard_t x300_impl::get_mb_type_from_pcie( - const std::string& resource, const std::string& rpc_port) -{ - // Detect the PCIe product ID to distinguish between X300 and X310 - nirio_status status = NiRio_Status_Success; - uint32_t pid; - niriok_proxy::sptr discovery_proxy = - niusrprio_session::create_kernel_proxy(resource, rpc_port); - if (discovery_proxy) { - nirio_status_chain( - discovery_proxy->get_attribute(RIO_PRODUCT_NUMBER, pid), status); - discovery_proxy->close(); - if (nirio_status_not_fatal(status)) { - return map_pid_to_mb_type(pid); - } - } - - UHD_LOGGER_WARNING("X300") << "NI-RIO Error -- unable to determine motherboard type!"; - return UNKNOWN; -} - diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp index d8bd23592..933006eab 100644 --- a/host/lib/usrp/x300/x300_impl.hpp +++ b/host/lib/usrp/x300/x300_impl.hpp @@ -1,6 +1,7 @@ // // Copyright 2013-2016 Ettus Research LLC // Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // @@ -8,81 +9,33 @@ #ifndef INCLUDED_X300_IMPL_HPP #define INCLUDED_X300_IMPL_HPP -#include "../device3/device3_impl.hpp" #include "x300_clock_ctrl.hpp" #include "x300_defaults.hpp" #include "x300_device_args.hpp" +#include "x300_eth_mgr.hpp" #include "x300_fw_common.h" #include "x300_mboard_type.hpp" +#include "x300_pcie_mgr.hpp" #include "x300_radio_ctrl_impl.hpp" #include "x300_regs.hpp" -#include <uhd/property_tree.hpp> -#include <uhd/transport/muxed_zero_copy_if.hpp> -#include <uhd/transport/nirio/niusrprio_session.h> -#include <uhd/transport/udp_simple.hpp> //mtu -#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/types/device_addr.hpp> #include <uhd/types/sensors.hpp> +#include <uhd/types/wb_iface.hpp> #include <uhd/usrp/gps_ctrl.hpp> -#include <uhd/usrp/mboard_eeprom.hpp> #include <uhd/usrp/subdev_spec.hpp> -///////////// RFNOC ///////////////////// -#include <uhd/rfnoc/block_ctrl.hpp> -///////////// RFNOC ///////////////////// - -#include <uhdlib/usrp/common/recv_packet_demuxer_3000.hpp> #include <uhdlib/usrp/cores/i2c_core_100_wb32.hpp> -#include <boost/dynamic_bitset.hpp> -#include <boost/weak_ptr.hpp> #include <atomic> -#include <functional> - -// Ethernet ports -enum x300_eth_iface_t { - X300_IFACE_NONE = 0, - X300_IFACE_ETH0 = 1, - X300_IFACE_ETH1 = 2, -}; - -struct x300_eth_conn_t -{ - std::string addr; - x300_eth_iface_t type; - size_t link_rate; -}; - -uhd::uart_iface::sptr x300_make_uart_iface(uhd::wb_iface::sptr iface); - -uhd::wb_iface::sptr x300_make_ctrl_iface_enet( - uhd::transport::udp_simple::sptr udp, bool enable_errors = true); -uhd::wb_iface::sptr x300_make_ctrl_iface_pcie( - uhd::niusrprio::niriok_proxy::sptr drv_proxy, bool enable_errors = true); +#include <memory> uhd::device_addrs_t x300_find(const uhd::device_addr_t& hint_); class x300_impl : public uhd::usrp::device3_impl { public: - //! Function to create a udp_simple::sptr (kernel-based or DPDK-based) - using udp_simple_factory_t = - std::function<uhd::transport::udp_simple::sptr(const std::string&, const std::string&)>; - x300_impl(const uhd::device_addr_t&); void setup_mb(const size_t which, const uhd::device_addr_t&); ~x300_impl(void); - // device claim functions - enum claim_status_t { UNCLAIMED, CLAIMED_BY_US, CLAIMED_BY_OTHER }; - static claim_status_t claim_status(uhd::wb_iface::sptr iface); - static void claim(uhd::wb_iface::sptr iface); - static bool try_to_claim(uhd::wb_iface::sptr iface, long timeout = 2000); - static void release(uhd::wb_iface::sptr iface); - - static uhd::usrp::x300::x300_mboard_t get_mb_type_from_pcie( - const std::string& resource, const std::string& rpc_port); - - //! Read out the on-board EEPROM, convert to dict, and return - static uhd::usrp::mboard_eeprom_t get_mb_eeprom(uhd::i2c_iface::sptr i2c); - protected: void subdev_to_blockid(const uhd::usrp::subdev_spec_pair_t& spec, const size_t mb_i, @@ -97,29 +50,11 @@ private: { uhd::usrp::x300::x300_device_args_t args; - bool initialization_done; + bool initialization_done = false; uhd::task::sptr claimer_task; - std::string xport_path; - - std::vector<x300_eth_conn_t> eth_conns; - size_t next_src_addr; - size_t next_tx_src_addr; - size_t next_rx_src_addr; - - // Discover the ethernet connections per motherboard - void discover_eth(const uhd::usrp::mboard_eeprom_t mb_eeprom, - const std::vector<std::string>& ip_addrs); - - // Get the primary ethernet connection - inline const x300_eth_conn_t& get_pri_eth() const - { - return eth_conns[0]; - } - + uhd::usrp::x300::xport_path_t xport_path; uhd::device_addr_t send_args; uhd::device_addr_t recv_args; - bool if_pkt_is_big_endian; - uhd::niusrprio::niusrprio_session::sptr rio_fpga_interface; // perifs in the zpu uhd::wb_iface::sptr zpu_ctrl; @@ -140,27 +75,14 @@ private: std::vector<uhd::rfnoc::x300_radio_ctrl_impl::sptr> radios; - // PCIe specific components: - - //! Maps SID -> DMA channel - std::map<uint32_t, uint32_t> _dma_chan_pool; - //! Control transport for one PCIe connection - uhd::transport::muxed_zero_copy_if::sptr ctrl_dma_xport; - //! Async message transport - uhd::transport::muxed_zero_copy_if::sptr async_msg_dma_xport; - - /*! Allocate or return a previously allocated PCIe channel pair - * - * Note the SID is always the transmit SID (i.e. from host to device). - */ - uint32_t allocate_pcie_dma_chan( - const uhd::sid_t& tx_sid, const xport_type_t xport_type); + // Ethernet-specific components: + std::unique_ptr<uhd::usrp::x300::eth_manager> eth_mgr; + + // PCIe-specific components: + std::unique_ptr<uhd::usrp::x300::pcie_manager> pcie_mgr; }; std::vector<mboard_members_t> _mb; - // task for periodically reclaiming the device from others - void claimer_loop(uhd::wb_iface::sptr); - std::atomic<size_t> _sid_framer; uhd::sid_t allocate_sid(mboard_members_t& mb, @@ -174,44 +96,6 @@ private: //! get mtu size_t get_mtu(const size_t, const uhd::direction_t); - struct frame_size_t - { - size_t recv_frame_size; - size_t send_frame_size; - }; - frame_size_t _max_frame_sizes; - - /*! - * Automatically determine the maximum frame size available by sending a UDP packet - * to the device and see which packet sizes actually work. This way, we can take - * switches etc. into account which might live between the device and the host. - */ - frame_size_t determine_max_frame_size( - const std::string& addr, const frame_size_t& user_mtu); - - //////////////////////////////////////////////////////////////////// - // - // Caching for transport interface re-use -- like sharing a DMA. - // The cache is optionally used by make_transport by use-case. - // The cache maps an ID string to a transport-ish object. - // The ID string identifies a purpose for the transport. - // - // For recv, there is a demux cache, which maps a ID string - // to a recv demux object. When a demux is used, the underlying transport - // must never be used outside of the demux. Use demux->make_proxy(sid). - // - uhd::dict<std::string, uhd::usrp::recv_packet_demuxer_3000::sptr> _demux_cache; - // - // For send, there is a shared send xport, which maps an ID string - // to a transport capable of sending buffers. Send transports - // can be shared amongst multiple callers, unlike recv. - // - uhd::dict<std::string, uhd::transport::zero_copy_if::sptr> _send_cache; - // - //////////////////////////////////////////////////////////////////// - - uhd::dict<std::string, uhd::usrp::dboard_manager::sptr> _dboard_managers; - bool _ignore_cal_file; void update_clock_control(mboard_members_t&); @@ -225,9 +109,6 @@ private: bool wait_for_clk_locked(mboard_members_t& mb, uint32_t which, double timeout); bool is_pps_present(mboard_members_t& mb); - //! Write the contents of an EEPROM dict to the on-board EEPROM - void set_mb_eeprom(uhd::i2c_iface::sptr i2c, const uhd::usrp::mboard_eeprom_t&); - void check_fw_compat(const uhd::fs_path& mb_path, const mboard_members_t& members); void check_fpga_compat(const uhd::fs_path& mb_path, const mboard_members_t& members); @@ -236,8 +117,6 @@ private: uhd::device_addr_t get_rx_hints(size_t mb_index); void post_streamer_hooks(uhd::direction_t dir); - - udp_simple_factory_t _x300_make_udp_connected; }; #endif /* INCLUDED_X300_IMPL_HPP */ diff --git a/host/lib/usrp/x300/x300_mb_eeprom.cpp b/host/lib/usrp/x300/x300_mb_eeprom.cpp index 663f4c9db..54505a43d 100644 --- a/host/lib/usrp/x300/x300_mb_eeprom.cpp +++ b/host/lib/usrp/x300/x300_mb_eeprom.cpp @@ -4,7 +4,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later // -#include "x300_impl.hpp" +#include "x300_mb_eeprom.hpp" #include <uhd/types/serial.hpp> #include <uhd/usrp/mboard_eeprom.hpp> #include <uhdlib/utils/eeprom_utils.hpp> @@ -41,7 +41,7 @@ struct x300_eeprom_map using namespace uhd; using uhd::usrp::mboard_eeprom_t; -mboard_eeprom_t x300_impl::get_mb_eeprom(uhd::i2c_iface::sptr iface) +mboard_eeprom_t uhd::usrp::x300::get_mb_eeprom(uhd::i2c_iface::sptr iface) { byte_vector_t bytes = iface->read_eeprom(X300_EEPROM_ADDR, 0, sizeof(struct x300_eeprom_map)); @@ -110,9 +110,10 @@ mboard_eeprom_t x300_impl::get_mb_eeprom(uhd::i2c_iface::sptr iface) return mb_eeprom; } -void x300_impl::set_mb_eeprom(i2c_iface::sptr iface, const mboard_eeprom_t& mb_eeprom) +void uhd::usrp::x300::set_mb_eeprom( + i2c_iface::sptr iface, const mboard_eeprom_t& mb_eeprom) { - const mboard_eeprom_t curr_eeprom = get_mb_eeprom(iface); + const mboard_eeprom_t curr_eeprom = uhd::usrp::x300::get_mb_eeprom(iface); // Check for duplicate MAC and IP addresses const std::vector<std::string> mac_keys{"mac-addr0", "mac-addr1"}; diff --git a/host/lib/usrp/x300/x300_mb_eeprom.hpp b/host/lib/usrp/x300/x300_mb_eeprom.hpp new file mode 100644 index 000000000..74e78fda0 --- /dev/null +++ b/host/lib/usrp/x300/x300_mb_eeprom.hpp @@ -0,0 +1,24 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_X300_EEPROM_HPP +#define INCLUDED_X300_EEPROM_HPP + +#include <uhd/types/serial.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> + +namespace uhd { namespace usrp { namespace x300 { + +//! Read out the on-board EEPROM, convert to dict, and return +uhd::usrp::mboard_eeprom_t get_mb_eeprom(uhd::i2c_iface::sptr i2c); + +//! Write the contents of an EEPROM dict to the on-board EEPROM +void set_mb_eeprom( + uhd::i2c_iface::sptr iface, const uhd::usrp::mboard_eeprom_t& mb_eeprom); + +}}} // namespace uhd::usrp::x300 + +#endif /* INCLUDED_X300_EEPROM_HPP */ diff --git a/host/lib/usrp/x300/x300_mb_eeprom_iface.cpp b/host/lib/usrp/x300/x300_mb_eeprom_iface.cpp index 12022ec24..a54caa560 100644 --- a/host/lib/usrp/x300/x300_mb_eeprom_iface.cpp +++ b/host/lib/usrp/x300/x300_mb_eeprom_iface.cpp @@ -18,6 +18,7 @@ */ #include "x300_mb_eeprom_iface.hpp" +#include "x300_claim.hpp" #include "x300_fw_common.h" #include "x300_impl.hpp" #include "x300_regs.hpp" @@ -52,7 +53,7 @@ public: void write_i2c(uint16_t addr, const byte_vector_t& buf) { UHD_ASSERT_THROW(addr == MBOARD_EEPROM_ADDR); - if (x300_impl::claim_status(_wb) != x300_impl::CLAIMED_BY_US) { + if (uhd::usrp::x300::claim_status(_wb) != uhd::usrp::x300::CLAIMED_BY_US) { throw uhd::io_error("Attempted to write MB EEPROM without claim to device."); } _i2c->write_i2c(addr, buf); @@ -71,13 +72,14 @@ public: if (_compat_num > X300_FW_SHMEM_IDENT_MIN_VERSION) { bytes = read_eeprom(addr, 0, num_bytes); } else { - x300_impl::claim_status_t status = x300_impl::claim_status(_wb); + auto status = uhd::usrp::x300::claim_status(_wb); // Claim device before driving the I2C bus - if (status == x300_impl::CLAIMED_BY_US or x300_impl::try_to_claim(_wb)) { + if (status == uhd::usrp::x300::CLAIMED_BY_US + or uhd::usrp::x300::try_to_claim(_wb)) { bytes = _i2c->read_i2c(addr, num_bytes); - if (status != x300_impl::CLAIMED_BY_US) { + if (status != uhd::usrp::x300::CLAIMED_BY_US) { // We didn't originally have the claim, so give it up - x300_impl::release(_wb); + uhd::usrp::x300::release(_wb); } } } @@ -93,7 +95,7 @@ public: void write_eeprom(uint16_t addr, uint16_t offset, const byte_vector_t& buf) { UHD_ASSERT_THROW(addr == MBOARD_EEPROM_ADDR); - if (x300_impl::claim_status(_wb) != x300_impl::CLAIMED_BY_US) { + if (uhd::usrp::x300::claim_status(_wb) != uhd::usrp::x300::CLAIMED_BY_US) { throw uhd::io_error("Attempted to write MB EEPROM without claim to device."); } _i2c->write_eeprom(addr, offset, buf); @@ -110,7 +112,7 @@ public: { UHD_ASSERT_THROW(addr == MBOARD_EEPROM_ADDR); byte_vector_t bytes; - x300_impl::claim_status_t status = x300_impl::claim_status(_wb); + uhd::usrp::x300::claim_status_t status = uhd::usrp::x300::claim_status(_wb); if (_compat_num >= X300_FW_SHMEM_IDENT_MIN_VERSION) { // Get MB EEPROM data from firmware memory if (num_bytes == 0) @@ -128,11 +130,12 @@ public: } } else { // Claim device before driving the I2C bus - if (status == x300_impl::CLAIMED_BY_US or x300_impl::try_to_claim(_wb)) { + if (status == uhd::usrp::x300::CLAIMED_BY_US + or uhd::usrp::x300::try_to_claim(_wb)) { bytes = _i2c->read_eeprom(addr, offset, num_bytes); - if (status != x300_impl::CLAIMED_BY_US) { + if (status != uhd::usrp::x300::CLAIMED_BY_US) { // We didn't originally have the claim, so give it up - x300_impl::release(_wb); + uhd::usrp::x300::release(_wb); } } } diff --git a/host/lib/usrp/x300/x300_mboard_type.hpp b/host/lib/usrp/x300/x300_mboard_type.hpp index 0a83aaf4d..d218a142f 100644 --- a/host/lib/usrp/x300/x300_mboard_type.hpp +++ b/host/lib/usrp/x300/x300_mboard_type.hpp @@ -13,6 +13,8 @@ namespace uhd { namespace usrp { namespace x300 { +enum class xport_path_t { ETH, NIRIO }; + enum x300_mboard_t { USRP_X300_MB, USRP_X310_MB, USRP_X310_MB_NI_2974, UNKNOWN }; /*! Return the correct motherboard type for a given product ID diff --git a/host/lib/usrp/x300/x300_pcie_mgr.cpp b/host/lib/usrp/x300/x300_pcie_mgr.cpp new file mode 100644 index 000000000..47095b370 --- /dev/null +++ b/host/lib/usrp/x300/x300_pcie_mgr.cpp @@ -0,0 +1,390 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "x300_pcie_mgr.hpp" +#include "x300_claim.hpp" +#include "x300_lvbitx.hpp" +#include "x300_mb_eeprom.hpp" +#include "x300_mb_eeprom_iface.hpp" +#include "x300_mboard_type.hpp" +#include "x300_regs.hpp" +#include "x310_lvbitx.hpp" +#include <uhd/transport/nirio_zero_copy.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhdlib/usrp/cores/i2c_core_100_wb32.hpp> +#include <unordered_map> +#include <mutex> + +namespace { + +uint32_t extract_sid_from_pkt(void* pkt, size_t) +{ + return uhd::sid_t(uhd::wtohx(static_cast<const uint32_t*>(pkt)[1])).get_dst(); +} + +constexpr uint32_t RADIO_DEST_PREFIX_TX = 0; + +// The FIFO closest to the DMA controller is 1023 elements deep for RX and 1029 elements +// deep for TX where an element is 8 bytes. The buffers (number of frames * frame size) +// must be aligned to the memory page size. For the control, we are getting lucky because +// 64 frames * 256 bytes each aligns with the typical page size of 4096 bytes. Since most +// page sizes are 4096 bytes or some multiple of that, keep the number of frames * frame +// size aligned to it. +constexpr size_t PCIE_RX_DATA_FRAME_SIZE = 4096; // bytes +constexpr size_t PCIE_RX_DATA_NUM_FRAMES = 4096; +constexpr size_t PCIE_TX_DATA_FRAME_SIZE = 4096; // bytes +constexpr size_t PCIE_TX_DATA_NUM_FRAMES = 4096; +constexpr size_t PCIE_MSG_FRAME_SIZE = 256; // bytes +constexpr size_t PCIE_MSG_NUM_FRAMES = 64; +constexpr size_t PCIE_MAX_MUXED_CTRL_XPORTS = 32; +constexpr size_t PCIE_MAX_MUXED_ASYNC_XPORTS = 4; +constexpr size_t PCIE_MAX_CHANNELS = 6; +constexpr size_t MAX_RATE_PCIE = 800000000; // bytes/s +} + +uhd::wb_iface::sptr x300_make_ctrl_iface_pcie( + uhd::niusrprio::niriok_proxy::sptr drv_proxy, bool enable_errors = true); + +using namespace uhd; +using namespace uhd::transport; +using namespace uhd::usrp::x300; +using namespace uhd::niusrprio; + +// We need a zpu xport registry to ensure synchronization between the static +// finder method and the instances of the x300_impl class. +typedef std::unordered_map<std::string, boost::weak_ptr<uhd::wb_iface>> + pcie_zpu_iface_registry_t; +UHD_SINGLETON_FCN(pcie_zpu_iface_registry_t, get_pcie_zpu_iface_registry) +static std::mutex pcie_zpu_iface_registry_mutex; + + +/****************************************************************************** + * Static methods + *****************************************************************************/ +x300_mboard_t pcie_manager::get_mb_type_from_pcie( + const std::string& resource, const std::string& rpc_port) +{ + // Detect the PCIe product ID to distinguish between X300 and X310 + nirio_status status = NiRio_Status_Success; + uint32_t pid; + niriok_proxy::sptr discovery_proxy = + niusrprio_session::create_kernel_proxy(resource, rpc_port); + if (discovery_proxy) { + nirio_status_chain( + discovery_proxy->get_attribute(RIO_PRODUCT_NUMBER, pid), status); + discovery_proxy->close(); + if (nirio_status_not_fatal(status)) { + return map_pid_to_mb_type(pid); + } + } + + UHD_LOG_WARNING("X300", "NI-RIO Error -- unable to determine motherboard type!"); + return UNKNOWN; +} + +/****************************************************************************** + * Find + *****************************************************************************/ +device_addrs_t pcie_manager::find(const device_addr_t& hint, bool explicit_query) +{ + std::string rpc_port_name(std::to_string(NIUSRPRIO_DEFAULT_RPC_PORT)); + if (hint.has_key("niusrpriorpc_port")) { + rpc_port_name = hint["niusrpriorpc_port"]; + } + + device_addrs_t addrs; + niusrprio_session::device_info_vtr dev_info_vtr; + nirio_status status = niusrprio_session::enumerate(rpc_port_name, dev_info_vtr); + if (explicit_query) { + nirio_status_to_exception( + status, "x300::pcie_manager::find: Error enumerating NI-RIO devices."); + } + + for (niusrprio_session::device_info& dev_info : dev_info_vtr) { + device_addr_t new_addr; + new_addr["type"] = "x300"; + new_addr["resource"] = dev_info.resource_name; + std::string resource_d(dev_info.resource_name); + boost::to_upper(resource_d); + + const std::string product_name = + map_mb_type_to_product_name(get_mb_type_from_pcie(resource_d, rpc_port_name)); + if (product_name.empty()) { + continue; + } else { + new_addr["product"] = product_name; + } + + niriok_proxy::sptr kernel_proxy = + niriok_proxy::make_and_open(dev_info.interface_path); + + // Attempt to read the name from the EEPROM and perform filtering. + // This operation can throw due to compatibility mismatch. + try { + // This block could throw an exception if the user is switching to using UHD + // after LabVIEW FPGA. In that case, skip reading the name and serial and pick + // a default FPGA flavor. During make, a new image will be loaded and + // everything will be OK + + wb_iface::sptr zpu_ctrl; + + // Hold on to the registry mutex as long as zpu_ctrl is alive + // to prevent any use by different threads while enumerating + std::lock_guard<std::mutex> lock(pcie_zpu_iface_registry_mutex); + + if (get_pcie_zpu_iface_registry().count(resource_d)) { + zpu_ctrl = get_pcie_zpu_iface_registry()[resource_d].lock(); + if (!zpu_ctrl) { + get_pcie_zpu_iface_registry().erase(resource_d); + } + } + + // if the registry didn't have a key OR that key was an orphaned weak_ptr + if (!zpu_ctrl) { + zpu_ctrl = x300_make_ctrl_iface_pcie( + kernel_proxy, false /* suppress timeout errors */); + // We don't put this zpu_ctrl in the registry because we need + // a persistent niriok_proxy associated with the object + } + + // Attempt to autodetect the FPGA type + if (not hint.has_key("fpga")) { + new_addr["fpga"] = get_fpga_option(zpu_ctrl); + } + + i2c_core_100_wb32::sptr zpu_i2c = + i2c_core_100_wb32::make(zpu_ctrl, I2C1_BASE); + x300_mb_eeprom_iface::sptr eeprom_iface = + x300_mb_eeprom_iface::make(zpu_ctrl, zpu_i2c); + const mboard_eeprom_t mb_eeprom = get_mb_eeprom(eeprom_iface); + if (mb_eeprom.size() == 0 or claim_status(zpu_ctrl) == CLAIMED_BY_OTHER) { + // Skip device claimed by another process + continue; + } + new_addr["name"] = mb_eeprom["name"]; + new_addr["serial"] = mb_eeprom["serial"]; + } catch (const std::exception&) { + // set these values as empty string so the device may still be found + // and the filter's below can still operate on the discovered device + if (not hint.has_key("fpga")) { + new_addr["fpga"] = "HG"; + } + new_addr["name"] = ""; + new_addr["serial"] = ""; + } + + // filter the discovered device below by matching optional keys + std::string resource_i = hint.has_key("resource") ? hint["resource"] : ""; + boost::to_upper(resource_i); + + if ((not hint.has_key("resource") or resource_i == resource_d) + and (not hint.has_key("name") or hint["name"] == new_addr["name"]) + and (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) + and (not hint.has_key("product") or hint["product"] == new_addr["product"])) { + addrs.push_back(new_addr); + } + } + return addrs; +} + + +/****************************************************************************** + * Structors + *****************************************************************************/ +pcie_manager::pcie_manager(const x300_device_args_t& args, + uhd::property_tree::sptr tree, + const uhd::fs_path& root_path) + : _args(args), _resource(args.get_resource()) +{ + nirio_status status = 0; + + const std::string rpc_port_name = args.get_niusrprio_rpc_port(); + UHD_LOG_INFO( + "X300", "Connecting to niusrpriorpc at localhost:" << rpc_port_name << "..."); + + // Instantiate the correct lvbitx object + nifpga_lvbitx::sptr lvbitx; + switch (get_mb_type_from_pcie(args.get_resource(), rpc_port_name)) { + case USRP_X300_MB: + lvbitx.reset(new x300_lvbitx(args.get_fpga_option())); + break; + case USRP_X310_MB: + case USRP_X310_MB_NI_2974: + lvbitx.reset(new x310_lvbitx(args.get_fpga_option())); + break; + default: + nirio_status_to_exception( + status, "Motherboard detection error. Please ensure that you \ + have a valid USRP X3x0, NI USRP-294xR, NI USRP-295xR or NI USRP-2974 device and that all the device \ + drivers have loaded successfully."); + } + // Load the lvbitx onto the device + UHD_LOG_INFO("X300", "Using LVBITX bitfile " << lvbitx->get_bitfile_path()); + _rio_fpga_interface.reset(new niusrprio_session(args.get_resource(), rpc_port_name)); + nirio_status_chain( + _rio_fpga_interface->open(lvbitx, args.get_download_fpga()), status); + nirio_status_to_exception(status, "x300_impl: Could not initialize RIO session."); + + // Tell the quirks object which FIFOs carry TX stream data + const uint32_t tx_data_fifos[2] = {RADIO_DEST_PREFIX_TX, RADIO_DEST_PREFIX_TX + 3}; + _rio_fpga_interface->get_kernel_proxy()->get_rio_quirks().register_tx_streams( + tx_data_fifos, 2); + + tree->create<size_t>(root_path / "mtu/recv").set(PCIE_RX_DATA_FRAME_SIZE); + tree->create<size_t>(root_path / "mtu/send").set(PCIE_TX_DATA_FRAME_SIZE); + tree->create<double>(root_path / "link_max_rate").set(MAX_RATE_PCIE); +} + +/****************************************************************************** + * API + *****************************************************************************/ +wb_iface::sptr pcie_manager::get_ctrl_iface() +{ + std::lock_guard<std::mutex> lock(pcie_zpu_iface_registry_mutex); + if (get_pcie_zpu_iface_registry().count(_resource)) { + throw uhd::assertion_error( + "Someone else has a ZPU transport to the device open. Internal error!"); + } + auto zpu_ctrl = x300_make_ctrl_iface_pcie(_rio_fpga_interface->get_kernel_proxy()); + get_pcie_zpu_iface_registry()[_resource] = boost::weak_ptr<wb_iface>(zpu_ctrl); + return zpu_ctrl; +} + +void pcie_manager::init_link() {} + +size_t pcie_manager::get_mtu(uhd::direction_t dir) +{ + return dir == uhd::RX_DIRECTION ? PCIE_RX_DATA_FRAME_SIZE : PCIE_TX_DATA_FRAME_SIZE; +} + +void pcie_manager::release_ctrl_iface(std::function<void(void)>&& release_fn) +{ + std::lock_guard<std::mutex> lock(pcie_zpu_iface_registry_mutex); + release_fn(); + // If the process is killed, the entire registry will disappear so we + // don't need to worry about unclean shutdowns here. + if (get_pcie_zpu_iface_registry().count(_resource)) { + get_pcie_zpu_iface_registry().erase(_resource); + } +} + +uint32_t pcie_manager::allocate_pcie_dma_chan( + const uhd::sid_t& tx_sid, const uhd::usrp::device3_impl::xport_type_t xport_type) +{ + constexpr uint32_t CTRL_CHANNEL = 0; + constexpr uint32_t ASYNC_MSG_CHANNEL = 1; + constexpr uint32_t FIRST_DATA_CHANNEL = 2; + if (xport_type == uhd::usrp::device3_impl::CTRL) { + return CTRL_CHANNEL; + } else if (xport_type == uhd::usrp::device3_impl::ASYNC_MSG) { + return ASYNC_MSG_CHANNEL; + } else { + // sid_t has no comparison defined, so we need to convert it uint32_t + uint32_t raw_sid = tx_sid.get(); + + if (_dma_chan_pool.count(raw_sid) == 0) { + size_t channel = _dma_chan_pool.size() + FIRST_DATA_CHANNEL; + if (channel > PCIE_MAX_CHANNELS) { + throw uhd::runtime_error( + "Trying to allocate more DMA channels than are available"); + } + _dma_chan_pool[raw_sid] = channel; + UHD_LOGGER_DEBUG("X300") + << "Assigning PCIe DMA channel " << _dma_chan_pool[raw_sid] << " to SID " + << tx_sid.to_pp_string_hex(); + } + + return _dma_chan_pool[raw_sid]; + } +} + +muxed_zero_copy_if::sptr pcie_manager::make_muxed_pcie_msg_xport( + uint32_t dma_channel_num, size_t max_muxed_ports) +{ + zero_copy_xport_params buff_args; + buff_args.send_frame_size = PCIE_MSG_FRAME_SIZE; + buff_args.recv_frame_size = PCIE_MSG_FRAME_SIZE; + buff_args.num_send_frames = PCIE_MSG_NUM_FRAMES; + buff_args.num_recv_frames = PCIE_MSG_NUM_FRAMES; + + zero_copy_if::sptr base_xport = nirio_zero_copy::make( + _rio_fpga_interface, dma_channel_num, buff_args, uhd::device_addr_t()); + return muxed_zero_copy_if::make(base_xport, extract_sid_from_pkt, max_muxed_ports); +} + +both_xports_t pcie_manager::make_transport(both_xports_t xports, + const uhd::usrp::device3_impl::xport_type_t xport_type, + const uhd::device_addr_t& args, + const size_t send_mtu, + const size_t recv_mtu) +{ + zero_copy_xport_params default_buff_args; + xports.endianness = ENDIANNESS_LITTLE; + xports.lossless = true; + const uint32_t dma_channel_num = allocate_pcie_dma_chan(xports.send_sid, xport_type); + if (xport_type == uhd::usrp::device3_impl::CTRL) { + // Transport for control stream + if (not _ctrl_dma_xport) { + // One underlying DMA channel will handle + // all control traffic + _ctrl_dma_xport = + make_muxed_pcie_msg_xport(dma_channel_num, PCIE_MAX_MUXED_CTRL_XPORTS); + } + // Create a virtual control transport + xports.recv = _ctrl_dma_xport->make_stream(xports.recv_sid.get_dst()); + } else if (xport_type == uhd::usrp::device3_impl::ASYNC_MSG) { + // Transport for async message stream + if (not _async_msg_dma_xport) { + // One underlying DMA channel will handle + // all async message traffic + _async_msg_dma_xport = + make_muxed_pcie_msg_xport(dma_channel_num, PCIE_MAX_MUXED_ASYNC_XPORTS); + } + // Create a virtual async message transport + xports.recv = _async_msg_dma_xport->make_stream(xports.recv_sid.get_dst()); + } else if (xport_type == uhd::usrp::device3_impl::TX_DATA) { + default_buff_args.send_frame_size = args.cast<size_t>( + "send_frame_size", std::min(send_mtu, PCIE_TX_DATA_FRAME_SIZE)); + default_buff_args.num_send_frames = + args.cast<size_t>("num_send_frames", PCIE_TX_DATA_NUM_FRAMES); + default_buff_args.send_buff_size = args.cast<size_t>("send_buff_size", 0); + default_buff_args.recv_frame_size = PCIE_MSG_FRAME_SIZE; + default_buff_args.num_recv_frames = PCIE_MSG_NUM_FRAMES; + xports.recv = nirio_zero_copy::make( + _rio_fpga_interface, dma_channel_num, default_buff_args); + } else if (xport_type == uhd::usrp::device3_impl::RX_DATA) { + default_buff_args.send_frame_size = PCIE_MSG_FRAME_SIZE; + default_buff_args.num_send_frames = PCIE_MSG_NUM_FRAMES; + default_buff_args.recv_frame_size = args.cast<size_t>( + "recv_frame_size", std::min(recv_mtu, PCIE_RX_DATA_FRAME_SIZE)); + default_buff_args.num_recv_frames = + args.cast<size_t>("num_recv_frames", PCIE_RX_DATA_NUM_FRAMES); + default_buff_args.recv_buff_size = args.cast<size_t>("recv_buff_size", 0); + xports.recv = nirio_zero_copy::make( + _rio_fpga_interface, dma_channel_num, default_buff_args); + } + + xports.send = xports.recv; + + // Router config word is: + // - Upper 16 bits: Destination address (e.g. 0.0) + // - Lower 16 bits: DMA channel + uint32_t router_config_word = (xports.recv_sid.get_dst() << 16) | dma_channel_num; + _rio_fpga_interface->get_kernel_proxy()->poke(PCIE_ROUTER_REG(0), router_config_word); + + // For the nirio transport, buffer size is depends on the frame size and num + // frames + xports.recv_buff_size = + xports.recv->get_num_recv_frames() * xports.recv->get_recv_frame_size(); + xports.send_buff_size = + xports.send->get_num_send_frames() * xports.send->get_send_frame_size(); + + return xports; +} diff --git a/host/lib/usrp/x300/x300_pcie_mgr.hpp b/host/lib/usrp/x300/x300_pcie_mgr.hpp new file mode 100644 index 000000000..a9677e9bc --- /dev/null +++ b/host/lib/usrp/x300/x300_pcie_mgr.hpp @@ -0,0 +1,82 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_X300_PCI_MGR_HPP +#define INCLUDED_X300_PCI_MGR_HPP + +#include "../device3/device3_impl.hpp" +#include "x300_device_args.hpp" +#include "x300_mboard_type.hpp" +#include <uhd/transport/muxed_zero_copy_if.hpp> +#include <uhd/transport/nirio/niusrprio_session.h> +#include <uhdlib/rfnoc/xports.hpp> + +namespace uhd { namespace usrp { namespace x300 { + +/*! Helper class to manage the PCIe connections + */ +class pcie_manager +{ +public: + pcie_manager(const x300_device_args_t& args, + uhd::property_tree::sptr tree, + const uhd::fs_path& root_path); + + //! Return the motherboard type using PCIe + static x300_mboard_t get_mb_type_from_pcie( + const std::string& resource, const std::string& rpc_port); + + static uhd::device_addrs_t find(const device_addr_t& hint, bool explicit_query); + + /*! Return a reference to a ZPU ctrl interface object + */ + uhd::wb_iface::sptr get_ctrl_iface(); + + void init_link(); + + size_t get_mtu(uhd::direction_t dir); + + /*! Safely release a ZPU control object + * + * This embeds the release call (provided by \p release_fn) within a safe + * context to avoid multiple accesses to the PCIe bus. + */ + void release_ctrl_iface(std::function<void(void)>&& release_fn); + + both_xports_t make_transport(both_xports_t xports, + const uhd::usrp::device3_impl::xport_type_t xport_type, + const uhd::device_addr_t& args, + const size_t send_mtu, + const size_t recv_mtu); + +private: + /*! Allocate or return a previously allocated PCIe channel pair + * + * Note the SID is always the transmit SID (i.e. from host to device). + */ + uint32_t allocate_pcie_dma_chan( + const uhd::sid_t& tx_sid, const uhd::usrp::device3_impl::xport_type_t xport_type); + + uhd::transport::muxed_zero_copy_if::sptr make_muxed_pcie_msg_xport( + uint32_t dma_channel_num, size_t max_muxed_ports); + + const x300_device_args_t _args; + const std::string _resource; + + uhd::niusrprio::niusrprio_session::sptr _rio_fpga_interface; + + //! Maps SID -> DMA channel + std::map<uint32_t, uint32_t> _dma_chan_pool; + + //! Control transport for one PCIe connection + uhd::transport::muxed_zero_copy_if::sptr _ctrl_dma_xport; + //! Async message transport + uhd::transport::muxed_zero_copy_if::sptr _async_msg_dma_xport; +}; + +}}} // namespace uhd::usrp::x300 + +#endif /* INCLUDED_X300_PCI_MGR_HPP */ |