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/lib/usrp/x300/x300_eth_mgr.cpp | |
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/lib/usrp/x300/x300_eth_mgr.cpp')
-rw-r--r-- | host/lib/usrp/x300/x300_eth_mgr.cpp | 724 |
1 files changed, 724 insertions, 0 deletions
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; +} |