diff options
author | Martin Braun <martin.braun@ettus.com> | 2022-07-07 17:03:50 +0200 |
---|---|---|
committer | michael-west <michael.west@ettus.com> | 2023-01-10 14:19:00 -0800 |
commit | e315f0dbc69b1939abd5fffb7a17ff2f3f9aeb81 (patch) | |
tree | 26dade33fa4107356d9284bf3b0f6277ec8e32cf | |
parent | x300: Respect X300_FW_COMMS_FLAGS_ARP_FAIL flag (diff) | |
download | uhd-e315f0dbc69b1939abd5fffb7a17ff2f3f9aeb81.tar.xz uhd-e315f0dbc69b1939abd5fffb7a17ff2f3f9aeb81.zip |
usrp: Add xport_adapter_ctrl core
This allows accessing the advanced transport adapter capabilities on
X300, where there is no MPM to aid accessing it. It is functionally
equivalent to xport_adapter_ctrl.py.
Includes unit tests.
Co-authored-by: Virendra Kakade <virendra.kakade@ni.com>
-rw-r--r-- | host/lib/include/uhdlib/usrp/cores/xport_adapter_ctrl.hpp | 96 | ||||
-rw-r--r-- | host/lib/usrp/cores/CMakeLists.txt | 6 | ||||
-rw-r--r-- | host/lib/usrp/cores/xport_adapter_ctrl.cpp | 183 | ||||
-rw-r--r-- | host/tests/CMakeLists.txt | 6 | ||||
-rw-r--r-- | host/tests/xport_adapter_ctrl_test.cpp | 188 |
5 files changed, 479 insertions, 0 deletions
diff --git a/host/lib/include/uhdlib/usrp/cores/xport_adapter_ctrl.hpp b/host/lib/include/uhdlib/usrp/cores/xport_adapter_ctrl.hpp new file mode 100644 index 000000000..80a16c5ac --- /dev/null +++ b/host/lib/include/uhdlib/usrp/cores/xport_adapter_ctrl.hpp @@ -0,0 +1,96 @@ +// +// Copyright 2022 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include <uhd/types/device_addr.hpp> +#include <uhdlib/rfnoc/rfnoc_common.hpp> +#include <uhdlib/utils/compat_check.hpp> +#include <cstdlib> +#include <functional> +#include <string> + +namespace uhd { namespace usrp { + +/*! Transport adapter control + * + * This is a C++ version of xport_adapter_ctrl.py and xport_adapter_mgr.py + * rolled into one class. + */ +class xport_adapter_ctrl +{ +public: + // Register offsets + static constexpr uint32_t XPORT_ADAPTER_COMPAT_NUM = + 0x0000; // 8 bits major, 8 bits minor + static constexpr uint32_t XPORT_ADAPTER_INFO = 0x0004; + static constexpr uint32_t XPORT_ADAPTER_NODE_INST = 0x0008; // read-only + static constexpr uint32_t KV_MAC_LO = 0x000C; + static constexpr uint32_t KV_MAC_HI = 0x0010; + static constexpr uint32_t KV_IPV4 = 0x0014; + static constexpr uint32_t KV_UDP_PORT = 0x0018; + static constexpr uint32_t KV_CFG = 0x001C; + static constexpr uint32_t KV_IPV4_W_ARP = 0x0020; + // The last entry has no equivalent in the FPGA, it will be mapped back to + // KV_IPV4, but will force a MAC address lookup on the device firmware. + + // Use these as the stream_mode argument in add_remote_ep_route() + static const char STREAM_MODE_RAW_PAYLOAD[]; + static const char STREAM_MODE_FULL_PACKET[]; + + + using poke_fn_type = std::function<void(const uint32_t, const uint32_t)>; + using peek_fn_type = std::function<uint32_t(const uint32_t)>; + + xport_adapter_ctrl(poke_fn_type&& poke_fn, + peek_fn_type&& peek_fn, + const bool has_arp, + const std::string& log_id); + + ~xport_adapter_ctrl(void) = default; + + uhd::rfnoc::sep_inst_t get_xport_adapter_inst() const + { + return _ta_inst; + } + + uhd::compat_num16 get_compat_num() const + { + return _compat_num; + } + + uhd::device_addr_t get_capabilities() const + { + return _capabilities; + } + + void add_remote_ep_route(const rfnoc::sep_inst_t epid, + const std::string ipv4, + const std::string port, + const std::string mac_addr, + const std::string stream_mode); + +private: + //! Poker object + poke_fn_type _poke32; + + //! Peeker object + peek_fn_type _peek32; + + //! Log ID (prefix) + const std::string _log_id; + + //! Compat numbers + const uhd::compat_num16 _compat_num; + + //! Transport adapter instance + const rfnoc::sep_inst_t _ta_inst; + + //! Dictionary of available capabilities (rx_routing, rx_hdr_removal, ...) + uhd::device_addr_t _capabilities; +}; + +}} // namespace usrp::zbx diff --git a/host/lib/usrp/cores/CMakeLists.txt b/host/lib/usrp/cores/CMakeLists.txt index 807eec685..2e2132ad3 100644 --- a/host/lib/usrp/cores/CMakeLists.txt +++ b/host/lib/usrp/cores/CMakeLists.txt @@ -20,6 +20,12 @@ if(ENABLE_B100 OR ENABLE_USRP2) ) endif(ENABLE_B100 OR ENABLE_USRP2) +if(ENABLE_X300) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/xport_adapter_ctrl.cpp + ) +endif() + LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/dma_fifo_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/dsp_core_utils.cpp diff --git a/host/lib/usrp/cores/xport_adapter_ctrl.cpp b/host/lib/usrp/cores/xport_adapter_ctrl.cpp new file mode 100644 index 000000000..3a07b767a --- /dev/null +++ b/host/lib/usrp/cores/xport_adapter_ctrl.cpp @@ -0,0 +1,183 @@ +// +// Copyright 2022 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/usrp/cores/xport_adapter_ctrl.hpp> +#include <unordered_map> +#include <boost/algorithm/string/case_conv.hpp> +#include <boost/asio.hpp> +#include <cstdio> +#include <chrono> +#include <thread> + +using namespace uhd::usrp; + +namespace { + +const uhd::compat_num16 MIN_COMPAT_REMOTE_STRM{1, 0}; + +const std::unordered_map<std::string, uint32_t> STREAM_MODES{ + {xport_adapter_ctrl::STREAM_MODE_FULL_PACKET, 0}, + {xport_adapter_ctrl::STREAM_MODE_RAW_PAYLOAD, 1}}; + +std::pair<uint32_t, uint32_t> cast_ipv4_and_port( + const std::string& ipv4, const std::string& port) +{ + using namespace boost::asio; + io_service io_service; + ip::udp::resolver resolver(io_service); + try { + ip::udp::resolver::query query(ip::udp::v4(), ipv4, port); + ip::udp::endpoint endpoint = *resolver.resolve(query); + return {uint32_t(endpoint.address().to_v4().to_ulong()), + uint32_t(endpoint.port())}; + } catch (const std::exception&) { + throw uhd::value_error("Invalid UDP address: " + ipv4 + ":" + port); + } +} + +std::pair<uint32_t, uint32_t> cast_mac(const std::string& mac_addr) +{ + unsigned char mac[8] = {0}; // 8 bytes for conversion to uint64_t + int ret = std::sscanf(mac_addr.c_str(), + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &mac[5], + &mac[4], + &mac[3], + &mac[2], + &mac[1], + &mac[0]); + if (ret != 6) { + throw uhd::value_error("Invalid MAC address: " + mac_addr); + } + const uint32_t mac_msb = *(reinterpret_cast<uint32_t*>(mac + 0)); + const uint32_t mac_lsb = *(reinterpret_cast<uint32_t*>(mac + 4)); + return {mac_msb, mac_lsb}; +} + + +} // namespace + +constexpr const char xport_adapter_ctrl::STREAM_MODE_RAW_PAYLOAD[] = "RAW_PAYLOAD"; +constexpr const char xport_adapter_ctrl::STREAM_MODE_FULL_PACKET[] = "FULL_PACKET"; + +xport_adapter_ctrl::xport_adapter_ctrl(poke_fn_type&& poke_fn, + peek_fn_type&& peek_fn, + const bool has_arp, + const std::string& log_id) + : _poke32(std::move(poke_fn)) + , _peek32(std::move(peek_fn)) + , _log_id(log_id) + , _compat_num(_peek32(XPORT_ADAPTER_COMPAT_NUM)) + , _ta_inst( + _compat_num >= MIN_COMPAT_REMOTE_STRM ? _peek32(XPORT_ADAPTER_NODE_INST) : 0) +{ + if (_compat_num >= MIN_COMPAT_REMOTE_STRM) { + const uint32_t capabilities = _peek32(XPORT_ADAPTER_INFO); + if (capabilities & (1 << 0)) { + _capabilities["rx_routing"] = "1"; + } + if (capabilities & (1 << 1)) { + _capabilities["rx_hdr_removal"] = "1"; + } + if (has_arp) { + _capabilities["arp"] = "1"; + } + _capabilities["ta_inst"] = std::to_string(_ta_inst); + } + UHD_LOG_TRACE( + _log_id, "Remote streaming capabilities: " << _capabilities.to_string()); +} + + +void xport_adapter_ctrl::add_remote_ep_route(const uhd::rfnoc::sep_inst_t epid, + const std::string ipv4, + const std::string port, + const std::string mac_addr, + const std::string stream_mode) +{ + const std::string stream_mode_upper = boost::to_upper_copy(stream_mode); + UHD_ASSERT_THROW(STREAM_MODES.count(stream_mode_upper)); + if (!_capabilities.has_key("rx_routing")) { + throw uhd::runtime_error( + "This transport adapter does not support routing to remote " + "destinations!"); + } + if (stream_mode_upper == STREAM_MODE_RAW_PAYLOAD + && !_capabilities.has_key("rx_hdr_removal")) { + throw uhd::runtime_error( + "Requesting to remove CHDR headers, but feature not enabled!"); + } + + if (ipv4.empty()) { + throw uhd::value_error("Must provide valid IPv4 address!"); + } + if (port.empty()) { + throw uhd::value_error("Must provide valid port value!"); + } + if (mac_addr.empty() && !_capabilities.has_key("arp")) { + throw uhd::value_error( + "Device has no ARP capabilities -- must provide MAC address!"); + } + const bool request_arp = mac_addr.empty(); + const auto ipv4_and_port = cast_ipv4_and_port(ipv4, port); + const uint32_t stream_mode_int = STREAM_MODES.at(stream_mode_upper); + const uint32_t cfg_word = epid | (stream_mode_int << 16); + + // Check TA is ready by polling BUSY flag + using namespace std::chrono_literals; + const auto timeout = std::chrono::steady_clock::now() + 500ms; + while (bool(_peek32(KV_CFG) & (1 << 31))) { + if (std::chrono::steady_clock::now() > timeout) { + UHD_LOG_THROW(uhd::runtime_error, + _log_id, + "Timeout while polling BUSY flag on transport adapter!"); + } + std::this_thread::sleep_for(100ms); + } + + // Now write settings to TA + UHD_LOG_DEBUG(_log_id, + "On transport adapter " << _ta_inst << ": Adding route from EPID " << epid + << " to destination " << ipv4 << ":" << port + << " (MAC Address: " << (request_arp ? "AUTO" : mac_addr) + << "), stream mode " << stream_mode_upper << " (" + << stream_mode_int << ")"); + if (!request_arp) { + const auto mac_int = cast_mac(mac_addr); + _poke32(KV_MAC_LO, mac_int.first); + _poke32(KV_MAC_HI, mac_int.second); + _poke32(KV_IPV4, ipv4_and_port.first); + } else { + // If the user didn't specify MAC, then the device firmware can try and + // look it up. + constexpr int num_arp_tries = 3; + constexpr auto retry_interval = 300ms; + bool arp_successful = false; + for (int i = 0; i < num_arp_tries; i++) { + try { + _poke32(KV_IPV4_W_ARP, ipv4_and_port.first); + arp_successful = true; + break; + } catch (const uhd::lookup_error&) { + UHD_LOG_TRACE(_log_id, "ARP lookup failed for IP address " << ipv4); + std::this_thread::sleep_for(retry_interval); + } + } + if (!arp_successful) { + UHD_LOG_THROW(uhd::lookup_error, + _log_id, + "Device was unable to look up Ethernet (MAC) address for IP " + "address " + << ipv4 + << ". Make sure device is correctly connected, " + "or provide MAC address manually."); + } + } + _poke32(KV_UDP_PORT, ipv4_and_port.second); + _poke32(KV_CFG, cfg_word); +} diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index b8a07d341..f40c252ad 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -453,6 +453,12 @@ UHD_ADD_NONAPI_TEST( ${UHD_SOURCE_DIR}/lib/utils/compat_check.cpp ) +UHD_ADD_NONAPI_TEST( + TARGET "xport_adapter_ctrl_test.cpp" + EXTRA_SOURCES + ${UHD_SOURCE_DIR}/lib/usrp/cores/xport_adapter_ctrl.cpp +) + ######################################################################## # demo of a loadable module ######################################################################## diff --git a/host/tests/xport_adapter_ctrl_test.cpp b/host/tests/xport_adapter_ctrl_test.cpp new file mode 100644 index 000000000..9fd2ddfd6 --- /dev/null +++ b/host/tests/xport_adapter_ctrl_test.cpp @@ -0,0 +1,188 @@ +// +// Copyright 2022 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + + +#include <uhd/utils/log.hpp> +#include <uhdlib/usrp/cores/xport_adapter_ctrl.hpp> +#include <boost/test/unit_test.hpp> +#include <iostream> +#include <map> + +using namespace uhd::usrp; + + +struct mock_xport_adapter_regs +{ + mock_xport_adapter_regs(uhd::rfnoc::sep_inst_t ta_inst, + const uhd::compat_num16 compat, + const uint32_t cap_flags) + { + mem[uint32_t(xport_adapter_ctrl::XPORT_ADAPTER_COMPAT_NUM)] = + (static_cast<uint32_t>(compat.get_major()) << 8) | compat.get_minor(); + mem[uint32_t(xport_adapter_ctrl::XPORT_ADAPTER_NODE_INST)] = + static_cast<uint32_t>(ta_inst); + mem[uint32_t(xport_adapter_ctrl::XPORT_ADAPTER_INFO)] = cap_flags; + mem[uint32_t(xport_adapter_ctrl::KV_CFG)] = 0; // not busy + } + + void poke32(const uint32_t addr, const uint32_t data) + { + mem[addr] = data; + } + + uint32_t peek32(const uint32_t addr) const + { + return mem.count(addr) ? mem.at(addr) : 0xFFFFFFFF; + } + + std::map<uint32_t, uint32_t> mem; +}; + +BOOST_AUTO_TEST_CASE(test_xport_adapter_init) +{ + constexpr uhd::rfnoc::sep_inst_t ta_inst = 1; + uhd::compat_num16 compat(1, 0); + mock_xport_adapter_regs regs(ta_inst, compat, 0x3); + + xport_adapter_ctrl ta_ctl( + [&](const uint32_t addr, const uint32_t data) { regs.poke32(addr, data); }, + [&](const uint32_t addr) { return regs.peek32(addr); }, + false, + "TEST_TA_CTL"); + + BOOST_CHECK(ta_ctl.get_compat_num() == compat); + BOOST_CHECK_EQUAL(ta_ctl.get_xport_adapter_inst(), ta_inst); + const auto caps = ta_ctl.get_capabilities(); + BOOST_CHECK(caps.has_key("rx_routing")); + BOOST_CHECK(caps.has_key("rx_hdr_removal")); + BOOST_CHECK(!caps.has_key("arp")); + + xport_adapter_ctrl ta_ctl_w_arp( + [&](const uint32_t addr, const uint32_t data) { regs.poke32(addr, data); }, + [&](const uint32_t addr) { return regs.peek32(addr); }, + true, + "TEST_TA_CTL"); + BOOST_CHECK(ta_ctl_w_arp.get_capabilities().has_key("arp")); +} + +BOOST_AUTO_TEST_CASE(test_xport_adapter_sanity_check) +{ + constexpr uhd::rfnoc::sep_inst_t ta_inst = 1; + uhd::compat_num16 compat(1, 0); + mock_xport_adapter_regs regs(ta_inst, compat, 0x0); + xport_adapter_ctrl ta_ctl_nostream( + [&](const uint32_t addr, const uint32_t data) { regs.poke32(addr, data); }, + [&](const uint32_t addr) { return regs.peek32(addr); }, + false, + "TEST_TA_CTL"); + + BOOST_REQUIRE_THROW( + ta_ctl_nostream.add_remote_ep_route(0, "1.2.3.4", "5678", "", "INVALID_MODE"), + uhd::assertion_error); + BOOST_REQUIRE_THROW( + ta_ctl_nostream.add_remote_ep_route( + 0, "1.2.3.4", "5678", "", xport_adapter_ctrl::STREAM_MODE_RAW_PAYLOAD), + uhd::runtime_error); + + regs.mem[uint32_t(xport_adapter_ctrl::XPORT_ADAPTER_INFO)] = 0x1; + xport_adapter_ctrl ta_ctl_noraw( + [&](const uint32_t addr, const uint32_t data) { regs.poke32(addr, data); }, + [&](const uint32_t addr) { return regs.peek32(addr); }, + false, + "TEST_TA_CTL"); + BOOST_REQUIRE_THROW( + ta_ctl_nostream.add_remote_ep_route( + 0, "1.2.3.4", "5678", "", xport_adapter_ctrl::STREAM_MODE_RAW_PAYLOAD), + uhd::runtime_error); + + regs.mem[uint32_t(xport_adapter_ctrl::XPORT_ADAPTER_INFO)] = 0x3; + xport_adapter_ctrl ta_ctl( + [&](const uint32_t addr, const uint32_t data) { regs.poke32(addr, data); }, + [&](const uint32_t addr) { return regs.peek32(addr); }, + false, + "TEST_TA_CTL"); + + ta_ctl.add_remote_ep_route(23, + "192.168.40.1", + "5678", + "AA:BB:CC:DD:EE:FF", + xport_adapter_ctrl::STREAM_MODE_RAW_PAYLOAD); + BOOST_CHECK_EQUAL(regs.mem[uint32_t(xport_adapter_ctrl::KV_MAC_LO)], 0xCCDDEEFF); + BOOST_CHECK_EQUAL(regs.mem[uint32_t(xport_adapter_ctrl::KV_MAC_HI)], 0x0000AABB); + BOOST_CHECK_EQUAL(regs.mem[uint32_t(xport_adapter_ctrl::KV_IPV4)], 0xc0a82801); + BOOST_CHECK_EQUAL(regs.mem[uint32_t(xport_adapter_ctrl::KV_UDP_PORT)], 5678); + BOOST_CHECK_EQUAL(regs.mem[uint32_t(xport_adapter_ctrl::KV_CFG)] & 0xFFFF, 23); + BOOST_CHECK_EQUAL((regs.mem[uint32_t(xport_adapter_ctrl::KV_CFG)] >> 16), 1); +} + +BOOST_AUTO_TEST_CASE(test_xport_adapter_busy_flag) +{ + constexpr uhd::rfnoc::sep_inst_t ta_inst = 1; + uhd::compat_num16 compat(1, 0); + mock_xport_adapter_regs regs(ta_inst, compat, 0x3); + // We make it permanently busy + regs.mem[uint32_t(xport_adapter_ctrl::KV_CFG)] = 1 << 31; + xport_adapter_ctrl ta_ctl( + [&](const uint32_t addr, const uint32_t data) { regs.poke32(addr, data); }, + [&](const uint32_t addr) { return regs.peek32(addr); }, + false, + "TEST_TA_CTL"); + + UHD_LOG_INFO("TEST", "Expecting error here VVV"); + BOOST_CHECK_THROW(ta_ctl.add_remote_ep_route(23, + "192.168.40.1", + "5678", + "AA:BB:CC:DD:EE:FF", + xport_adapter_ctrl::STREAM_MODE_RAW_PAYLOAD), + uhd::runtime_error); + UHD_LOG_INFO("TEST", "Expecting error here ^^^"); + + // Now we fake out BUSY flag going low after 2 peeks + int peek_count = 0; + xport_adapter_ctrl ta_ctl2( + [&](const uint32_t addr, const uint32_t data) { regs.poke32(addr, data); }, + [&](const uint32_t addr) { + if (addr == uint32_t(xport_adapter_ctrl::KV_CFG)) { + UHD_LOG_INFO("TEST", "Detecting peek to KV_CFG..."); + if (++peek_count == 2) { + return uint32_t(0); + } + } + return regs.peek32(addr); + }, + false, + "TEST_TA_CTL"); + + BOOST_CHECK_NO_THROW(ta_ctl2.add_remote_ep_route(23, + "192.168.40.1", + "5678", + "AA:BB:CC:DD:EE:FF", + xport_adapter_ctrl::STREAM_MODE_RAW_PAYLOAD)); +} + +BOOST_AUTO_TEST_CASE(test_xport_adapter_arp) +{ + constexpr uhd::rfnoc::sep_inst_t ta_inst = 2; + uhd::compat_num16 compat(1, 0); + mock_xport_adapter_regs regs(ta_inst, compat, 0x3); + xport_adapter_ctrl ta_ctl( + [&](const uint32_t addr, const uint32_t data) { regs.poke32(addr, data); }, + [&](const uint32_t addr) { return regs.peek32(addr); }, + true, + "TEST_TA_CTL"); + + BOOST_CHECK_NO_THROW(ta_ctl.add_remote_ep_route( + 42, "192.168.30.1", "5678", "", xport_adapter_ctrl::STREAM_MODE_RAW_PAYLOAD)); + BOOST_CHECK(!regs.mem.count(uint32_t(xport_adapter_ctrl::KV_MAC_LO))); + BOOST_CHECK(!regs.mem.count(uint32_t(xport_adapter_ctrl::KV_MAC_HI))); + BOOST_CHECK(!regs.mem.count(uint32_t(xport_adapter_ctrl::KV_IPV4))); + BOOST_CHECK_EQUAL(regs.mem[uint32_t(xport_adapter_ctrl::KV_UDP_PORT)], 5678); + BOOST_CHECK_EQUAL(regs.mem[uint32_t(xport_adapter_ctrl::KV_CFG)] & 0xFFFF, 42); + BOOST_CHECK_EQUAL((regs.mem[uint32_t(xport_adapter_ctrl::KV_CFG)] >> 16), 1); + BOOST_CHECK_EQUAL(regs.mem[uint32_t(xport_adapter_ctrl::KV_IPV4_W_ARP)], 0xc0a81e01); +} + + |