// // Copyright 2013 Ettus Research LLC // Copyright 2018 Ettus Research, a National Instruments Company // // SPDX-License-Identifier: GPL-3.0-or-later // #include "n230_resource_manager.hpp" #include "n230_fw_ctrl_iface.hpp" #include #include #include #include #include #include #include #include #include #include "n230_fw_defs.h" #include "n230_fw_host_iface.h" #include #include #define IF_DATA_I_MASK 0xFFF00000 #define IF_DATA_Q_MASK 0x0000FFF0 namespace uhd { namespace usrp { namespace n230 { //Constants static const uint8_t N230_HOST_SRC_ADDR_ETH0 = 0; static const uint8_t N230_HOST_SRC_ADDR_ETH1 = 1; static const uint8_t N230_HOST_DEST_ADDR = 2; static const uint8_t N230_ETH0_IFACE_ID = 0; static const uint8_t N230_ETH1_IFACE_ID = 1; boost::mutex n230_resource_manager::_claimer_mutex; class n230_ad9361_client_t : public ad9361_params { public: ~n230_ad9361_client_t() {} double get_band_edge(frequency_band_t band) { switch (band) { case AD9361_RX_BAND0: return 2.2e9; case AD9361_RX_BAND1: return 4.0e9; case AD9361_TX_BAND0: return 2.5e9; default: return 0; } } clocking_mode_t get_clocking_mode() { return clocking_mode_t::AD9361_XTAL_N_CLK_PATH; } digital_interface_mode_t get_digital_interface_mode() { return AD9361_DDR_FDD_LVDS; } digital_interface_delays_t get_digital_interface_timing() { digital_interface_delays_t delays; delays.rx_clk_delay = 0; delays.rx_data_delay = 0; delays.tx_clk_delay = 0; delays.tx_data_delay = 2; return delays; } }; n230_resource_manager::n230_resource_manager( const std::vector ip_addrs, const bool safe_mode ) : _safe_mode(safe_mode), _last_host_enpoint(0) { if (_safe_mode) UHD_LOGGER_WARNING("N230") << "Initializing device in safe mode\n"; UHD_LOGGER_INFO("N230") << "Setup basic communication..."; //Discover ethernet interfaces bool dual_eth_expected = (ip_addrs.size() > 1); for(const std::string& addr: ip_addrs) { n230_eth_conn_t conn_iface; conn_iface.ip_addr = addr; uint32_t iface_id = 0xFFFFFFFF; try { iface_id = n230::n230_fw_ctrl_iface::get_iface_id( conn_iface.ip_addr, BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT), N230_FW_PRODUCT_ID); } catch (uhd::io_error&) { throw uhd::io_error(str(boost::format( "Could not communicate with the device over address %s") % conn_iface.ip_addr)); } switch (iface_id) { case N230_ETH0_IFACE_ID: conn_iface.type = ETH0; break; case N230_ETH1_IFACE_ID: conn_iface.type = ETH1; break; default: { if (dual_eth_expected) { throw uhd::runtime_error("N230 Initialization Error: Could not detect ethernet port number."); } else { //For backwards compatibility, if only one port is specified, assume that a detection //failure means that the device does not support dual-ethernet behavior. conn_iface.type = ETH0; break; } } } _eth_conns.push_back(conn_iface); } if (_eth_conns.size() < 1) { throw uhd::runtime_error("N230 Initialization Error: No eth interfaces specified.)"); } //Create firmware communication interface _fw_ctrl = n230::n230_fw_ctrl_iface::make( transport::udp_simple::make_connected( _get_conn(PRI_ETH).ip_addr, BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT)), N230_FW_PRODUCT_ID); if (_fw_ctrl.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create n230_ctrl_iface.)"); } _check_fw_compat(); //Start the device claimer _claimer_task = uhd::task::make(boost::bind(&n230_resource_manager::_claimer_loop, this)); //Create common settings interface const sid_t core_sid = _generate_sid(CORE, _get_conn(PRI_ETH).type); transport::udp_zero_copy::buff_params dummy_out_params; transport::zero_copy_if::sptr core_xport = _create_transport(_get_conn(PRI_ETH), core_sid, device_addr_t(), dummy_out_params); if (core_xport.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create settings transport.)"); } _core_ctrl = radio_ctrl_core_3000::make( fpga::CVITA_BIG_ENDIAN, core_xport, core_xport, core_sid.get()); if (_core_ctrl.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create settings ctrl.)"); } _check_fpga_compat(); UHD_LOGGER_INFO("N230") << boost::format("Version signatures... Firmware:%s FPGA:%s...") % _fw_version.get_hash_str() % _fpga_version.get_hash_str(); _core_radio_ctrl_reg.initialize(*_core_ctrl, true /*flush*/); _core_misc_reg.initialize(*_core_ctrl, true /*flush*/); _core_pps_sel_reg.initialize(*_core_ctrl, true /*flush*/); _core_status_reg.initialize(*_core_ctrl); //Create common SPI interface _core_spi_ctrl = n230_core_spi_core::make(_core_ctrl); if (_core_spi_ctrl.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create SPI ctrl.)"); } //Create AD9361 interface UHD_LOGGER_INFO("N230") << "Initializing CODEC..."; _codec_ctrl = ad9361_ctrl::make_spi( boost::make_shared(), _core_spi_ctrl, fpga::AD9361_SPI_SLAVE_NUM); if (_codec_ctrl.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create Catalina ctrl.)"); } _codec_ctrl->set_clock_rate(fpga::CODEC_DEFAULT_CLK_RATE); _codec_mgr = ad936x_manager::make(_codec_ctrl, fpga::NUM_RADIOS); _codec_mgr->init_codec(); //Create AD4001 interface _ref_pll_ctrl = boost::make_shared(_core_spi_ctrl); if (_ref_pll_ctrl.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create ADF4001 ctrl.)"); } //Reset SERDES interface and synchronize to frame sync from AD9361 _reset_codec_digital_interface(); std::vector time_cores; std::vector gpio_cores; for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { _initialize_radio(i); time_cores.push_back(_radios[i].time); gpio_cores.push_back(_radios[i].gpio_atr); } //Create clock and PPS control interface _clk_pps_ctrl = n230_clk_pps_ctrl::make( _codec_ctrl, _ref_pll_ctrl, _core_misc_reg, _core_pps_sel_reg, _core_status_reg, time_cores); if (_clk_pps_ctrl.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create clock and PPS ctrl.)"); } //Create front-end control interface _frontend_ctrl = n230_frontend_ctrl::make(_core_ctrl, _core_misc_reg, _codec_ctrl, gpio_cores); if (_frontend_ctrl.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create front-end ctrl.)"); } //Create miniSAS GPIO interfaces _ms0_gpio = gpio_atr::gpio_atr_3000::make( _core_ctrl, fpga::sr_addr(fpga::SR_CORE_MS0_GPIO), fpga::rb_addr(fpga::RB_CORE_MS0_GPIO)); _ms0_gpio->set_atr_mode(gpio_atr::MODE_GPIO,gpio_atr::gpio_atr_3000::MASK_SET_ALL); _ms1_gpio = gpio_atr::gpio_atr_3000::make( _core_ctrl, fpga::sr_addr(fpga::SR_CORE_MS1_GPIO), fpga::rb_addr(fpga::RB_CORE_MS1_GPIO)); _ms1_gpio->set_atr_mode(gpio_atr::MODE_GPIO,gpio_atr::gpio_atr_3000::MASK_SET_ALL); //Create GPSDO interface if (_core_status_reg.read(fpga::core_status_reg_t::GPSDO_STATUS) != fpga::GPSDO_ST_ABSENT) { UHD_LOGGER_INFO("N230") << "Detecting GPSDO.... "; try { const sid_t gps_uart_sid = _generate_sid(GPS_UART, _get_conn(PRI_ETH).type); transport::zero_copy_if::sptr gps_uart_xport = _create_transport(_get_conn(PRI_ETH), gps_uart_sid, device_addr_t(), dummy_out_params); _gps_uart = n230_uart::make(gps_uart_xport, uhd::htonx(gps_uart_sid.get())); _gps_uart->set_baud_divider(fpga::BUS_CLK_RATE/fpga::GPSDO_UART_BAUDRATE); _gps_uart->write_uart("\n"); //cause the baud and response to be setup std::this_thread::sleep_for(std::chrono::seconds(1)); //allow for a little propagation _gps_ctrl = gps_ctrl::make(_gps_uart); } catch(std::exception &e) { UHD_LOGGER_ERROR("N230") << "An error occurred making GPSDO control: " << e.what() ; } if (not is_gpsdo_present()) { _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_GPSDO_ST), fpga::GPSDO_ST_ABSENT); } } //Perform data self-tests _frontend_ctrl->set_stream_state(TXRX_STREAMING, TXRX_STREAMING); for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { _frontend_ctrl->set_self_test_mode(LOOPBACK_RADIO); bool radio_selftest_pass = _radio_data_loopback_self_test(_radios[i].ctrl); if (!radio_selftest_pass) { throw uhd::runtime_error("N230 Initialization Error: Data loopback test failed.)"); } _frontend_ctrl->set_self_test_mode(LOOPBACK_CODEC); bool codec_selftest_pass = _radio_data_loopback_self_test(_radios[i].ctrl); if (!codec_selftest_pass) { throw uhd::runtime_error("N230 Initialization Error: Codec loopback test failed.)"); } } _frontend_ctrl->set_self_test_mode(LOOPBACK_DISABLED); _frontend_ctrl->set_stream_state(NONE_STREAMING, NONE_STREAMING); } n230_resource_manager::~n230_resource_manager() { _claimer_task.reset(); { //Critical section boost::mutex::scoped_lock lock(_claimer_mutex); _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_time), 0); _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_src), 0); } } transport::zero_copy_if::sptr n230_resource_manager::create_transport( n230_data_dir_t direction, size_t radio_instance, const device_addr_t ¶ms, sid_t& sid_pair, transport::udp_zero_copy::buff_params& buff_out_params) { const n230_eth_conn_t& conn = _get_conn((radio_instance==1)?SEC_ETH:PRI_ETH); const sid_t temp_sid_pair = _generate_sid(direction==RX_DATA?RADIO_RX_DATA:RADIO_TX_DATA, conn.type, radio_instance); transport::zero_copy_if::sptr xport = _create_transport(conn, temp_sid_pair, params, buff_out_params); if (xport.get() == NULL) { throw uhd::runtime_error("N230 Create Data Transport: Could not create data transport.)"); } else { sid_pair = temp_sid_pair; } return xport; } bool n230_resource_manager::is_device_claimed(n230_fw_ctrl_iface::sptr fw_ctrl) { boost::mutex::scoped_lock lock(_claimer_mutex); //If timed out then device is definitely unclaimed if (fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(claim_status)) == 0) return false; //otherwise check claim src to determine if another thread with the same src has claimed the device return fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(claim_src)) != get_process_hash(); } void n230_resource_manager::_claimer_loop() { { //Critical section boost::mutex::scoped_lock lock(_claimer_mutex); _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_time), time(NULL)); _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_src), get_process_hash()); } std::this_thread::sleep_for(std::chrono::milliseconds(N230_CLAIMER_TIMEOUT_IN_MS / 2)); } void n230_resource_manager::_initialize_radio(size_t instance) { radio_resource_t& radio = _radios[instance]; //Create common settings interface const sid_t ctrl_sid = _generate_sid(RADIO_CONTROL, _get_conn(PRI_ETH).type, instance); transport::udp_zero_copy::buff_params buff_out_params; transport::zero_copy_if::sptr ctrl_xport = _create_transport(_get_conn(PRI_ETH), ctrl_sid, device_addr_t(), buff_out_params); if (ctrl_xport.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create radio transport.)"); } radio.ctrl = radio_ctrl_core_3000::make( fpga::CVITA_BIG_ENDIAN, ctrl_xport, ctrl_xport, ctrl_sid.get()); if (radio.ctrl.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create radio ctrl.)"); } //Perform register loopback test to verify the radio clock bool reg_selftest_pass = _radio_register_loopback_self_test(radio.ctrl); if (!reg_selftest_pass) { throw uhd::runtime_error("N230 Initialization Error: Register loopback test failed.)"); } //Write-only ATR interface radio.gpio_atr = gpio_atr::gpio_atr_3000::make_write_only(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_ATR)); radio.gpio_atr->set_atr_mode(gpio_atr::MODE_ATR,gpio_atr::gpio_atr_3000::MASK_SET_ALL); //Core VITA time interface time_core_3000::readback_bases_type time_bases; time_bases.rb_now = fpga::rb_addr(fpga::RB_RADIO_TIME_NOW); time_bases.rb_pps = fpga::rb_addr(fpga::RB_RADIO_TIME_PPS); radio.time = time_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TIME), time_bases); if (radio.time.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create time core.)"); } //RX DSP radio.framer = rx_vita_core_3000::make( radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_RX_CTRL)); radio.ddc = rx_dsp_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_RX_DSP), true /*old DDC?*/); if (radio.framer.get() == NULL || radio.ddc.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create RX DSP interface.)"); } radio.ddc->set_link_rate(fpga::N230_LINK_RATE_BPS); //TX DSP radio.deframer = tx_vita_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TX_CTRL)); radio.duc = tx_dsp_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TX_DSP)); if (radio.deframer.get() == NULL || radio.duc.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create RX DSP interface.)"); } radio.duc->set_link_rate(fpga::N230_LINK_RATE_BPS); //User settings radio.user_settings = user_settings_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_USER_SR), fpga::rb_addr(fpga::SR_RADIO_USER_RB)); if (radio.user_settings.get() == NULL) { throw uhd::runtime_error("N230 Initialization Error: Could not create user settings bus.)"); } } uint8_t xb_ep_to_sid(fpga::xb_endpoint_t ep) { return static_cast(ep) << 4; } const sid_t n230_resource_manager::_generate_sid(const n230_endpoint_t type, const n230_eth_port_t xport, size_t instance) { fpga::xb_endpoint_t xb_dest_ep; uint8_t sid_dest_ep = 0; fpga::xb_endpoint_t xb_ret_ep = (xport == ETH1) ? fpga::N230_XB_DST_E1 : fpga::N230_XB_DST_E0; uint8_t sid_ret_addr = (xport == ETH1) ? N230_HOST_SRC_ADDR_ETH1 : N230_HOST_SRC_ADDR_ETH0; if (type == CORE or type == GPS_UART) { //Non-radio endpoints xb_dest_ep = (type == CORE) ? fpga::N230_XB_DST_GCTRL : fpga::N230_XB_DST_UART; sid_dest_ep = xb_ep_to_sid(xb_dest_ep); } else { //Radio endpoints xb_dest_ep = (instance == 1) ? fpga::N230_XB_DST_R1 : fpga::N230_XB_DST_R0; sid_dest_ep = xb_ep_to_sid(xb_dest_ep); switch (type) { case RADIO_TX_DATA: sid_dest_ep |= fpga::RADIO_DATA_SUFFIX; break; case RADIO_RX_DATA: sid_dest_ep |= fpga::RADIO_FC_SUFFIX; break; default: sid_dest_ep |= fpga::RADIO_CTRL_SUFFIX; break; } } //Increment last host logical endpoint sid_t sid(sid_ret_addr, ++_last_host_enpoint, N230_HOST_DEST_ADDR, sid_dest_ep); //Program the crossbar addr _fw_ctrl->poke32(fw::reg_addr(fw::WB_SBRB_BASE, fw::SR_ZPU_XB_LOCAL), sid.get_dst_addr()); // Program CAM entry for returning packets to us // This type of packet does not match the XB_LOCAL address and is looked up in the lower half of the CAM _fw_ctrl->poke32(fw::reg_addr(fw::WB_XB_SBRB_BASE, sid.get_src_addr()), static_cast(xb_ret_ep)); // Program CAM entry for outgoing packets matching a N230 resource (for example a Radio) // This type of packet does matches the XB_LOCAL address and is looked up in the upper half of the CAM _fw_ctrl->poke32(fw::reg_addr(fw::WB_XB_SBRB_BASE, 256 + sid.get_dst_endpoint()), static_cast(xb_dest_ep)); return sid; } transport::zero_copy_if::sptr n230_resource_manager::_create_transport( const n230_eth_conn_t& eth_conn, const sid_t& sid, const device_addr_t &buff_params, transport::udp_zero_copy::buff_params& buff_params_out) { transport::zero_copy_xport_params default_buff_args; default_buff_args.recv_frame_size = transport::udp_simple::mtu; default_buff_args.send_frame_size = transport::udp_simple::mtu; default_buff_args.num_recv_frames = 32; default_buff_args.num_send_frames = 32; transport::zero_copy_if::sptr xport = transport::udp_zero_copy::make( eth_conn.ip_addr, std::to_string(fpga::CVITA_UDP_PORT), default_buff_args, buff_params_out, buff_params); if (xport.get()) { _program_dispatcher(*xport, eth_conn.type, sid); } return xport; } void n230_resource_manager::_program_dispatcher( transport::zero_copy_if& xport, const n230_eth_port_t port, const sid_t& sid) { //Send a mini packet with SID into the ZPU //ZPU will reprogram the ethernet framer transport::managed_send_buffer::sptr buff = xport.get_send_buff(); buff->cast()[0] = 0; //eth dispatch looks for != 0 buff->cast()[1] = uhd::htonx(sid.get()); buff->commit(8); buff.reset(); //reprogram the ethernet dispatcher's udp port (should be safe to always set) uint32_t disp_base_offset = ((port == ETH1) ? fw::SR_ZPU_ETHINT1 : fw::SR_ZPU_ETHINT0) + fw::SR_ZPU_ETHINT_DISPATCHER_BASE; _fw_ctrl->poke32(fw::reg_addr(fw::WB_SBRB_BASE, disp_base_offset + fw::ETH_FRAMER_SRC_UDP_PORT), fpga::CVITA_UDP_PORT); //Do a peek to an arbitrary address to guarantee that the //ethernet framer has been programmed before we return. _fw_ctrl->peek32(0); } void n230_resource_manager::_reset_codec_digital_interface() { //Set timing registers _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_DATA_DELAY), fpga::CODEC_DATA_DELAY); _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_CLK_DELAY), fpga::CODEC_CLK_DELAY); _core_radio_ctrl_reg.write(fpga::core_radio_ctrl_reg_t::CODEC_ARST, 1); std::this_thread::sleep_for(std::chrono::milliseconds(10)); _core_radio_ctrl_reg.write(fpga::core_radio_ctrl_reg_t::CODEC_ARST, 0); } bool n230_resource_manager::_radio_register_loopback_self_test(wb_iface::sptr iface) { bool test_fail = false; size_t hash = static_cast(time(NULL)); for (size_t i = 0; i < 100; i++) { boost::hash_combine(hash, i); iface->poke32(fpga::sr_addr(fpga::SR_RADIO_TEST), uint32_t(hash)); test_fail = iface->peek32(fpga::rb_addr(fpga::RB_RADIO_TEST)) != uint32_t(hash); if (test_fail) break; //exit loop on any failure } return !test_fail; } bool n230_resource_manager::_radio_data_loopback_self_test(wb_iface::sptr iface) { bool test_fail = false; size_t hash = size_t(time(NULL)); for (size_t i = 0; i < 100; i++) { boost::hash_combine(hash, i); const uint32_t word32 = uint32_t(hash) & (IF_DATA_I_MASK | IF_DATA_Q_MASK); iface->poke32(fpga::sr_addr(fpga::SR_RADIO_CODEC_IDLE), word32); iface->peek64(fpga::rb_addr(fpga::RB_RADIO_CODEC_DATA)); //block until request completes std::this_thread::sleep_for(std::chrono::microseconds(100)); //wait for loopback to propagate through codec const uint64_t rb_word64 = iface->peek64(fpga::rb_addr(fpga::RB_RADIO_CODEC_DATA)); const uint32_t rb_tx = uint32_t(rb_word64 >> 32); const uint32_t rb_rx = uint32_t(rb_word64 & 0xffffffff); test_fail = word32 != rb_tx or word32 != rb_rx; if (test_fail){ UHD_LOG_ERROR("N230", str(boost::format("mismatch (exp:%x, got:%x and %x)... ") % word32 % rb_tx % rb_rx)); break; //exit loop on any failure } } /* Zero out the idle data. */ iface->poke32(fpga::sr_addr(fpga::SR_RADIO_CODEC_IDLE), 0); return !test_fail; } std::string n230_resource_manager::_get_fpga_upgrade_msg() { std::string img_loader_path = (fs::path(uhd::get_pkg_path()) / "bin" / "uhd_image_loader").string(); return str(boost::format( "\nDownload the appropriate FPGA images for this version of UHD.\n" "%s\n\n" "Then burn a new image to the on-board flash storage of your\n" "USRP N230 device using the image loader utility. Use this command:\n" "\n \"%s\" --args=\"type=n230,addr=%s\"\n") % print_utility_error("uhd_images_downloader.py") % img_loader_path % _get_conn(PRI_ETH).ip_addr); } void n230_resource_manager::_check_fw_compat() { uint32_t compat_num = _fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(fw_compat_num)); _fw_version.compat_major = compat_num >> 16; _fw_version.compat_minor = compat_num; _fw_version.version_hash = _fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(fw_version_hash)); if (_fw_version.compat_major != N230_FW_COMPAT_NUM_MAJOR){ throw uhd::runtime_error(str(boost::format( "Expected firmware compatibility number %d.x, but got %d.%d\n" "The firmware build is not compatible with the host code build.\n" "%s" ) % static_cast(N230_FW_COMPAT_NUM_MAJOR) % static_cast(_fw_version.compat_major) % static_cast(_fw_version.compat_minor) % _get_fpga_upgrade_msg())); } } void n230_resource_manager::_check_fpga_compat() { const uint64_t compat = _core_ctrl->peek64(fpga::rb_addr(fpga::RB_CORE_SIGNATUE)); const uint32_t signature = uint32_t(compat >> 32); const uint16_t product_id = uint8_t(compat >> 24); _fpga_version.compat_major = static_cast(compat >> 16); _fpga_version.compat_minor = static_cast(compat); const uint64_t version_hash = _core_ctrl->peek64(fpga::rb_addr(fpga::RB_CORE_VERSION_HASH)); _fpga_version.version_hash = uint32_t(version_hash); if (signature != 0x0ACE0BA5E || product_id != fpga::RB_N230_PRODUCT_ID) throw uhd::runtime_error("Signature check failed. Please contact support."); bool is_safe_image = (_fpga_version.compat_major > fpga::RB_N230_COMPAT_SAFE); if (is_safe_image && !_safe_mode) { throw uhd::runtime_error( "The device appears to have the failsafe FPGA image loaded\n" "This could have happened because the production FPGA image in the flash was either corrupt or non-existent\n" "To remedy this error, please burn a valid FPGA image to the flash.\n" "To continue using the failsafe image with UHD, create the UHD device with the \"safe_mode\" device arg.\n" "Radio functionality/performance not guaranteed when operating in safe mode.\n"); } else if (_fpga_version.compat_major != fpga::RB_N230_COMPAT_MAJOR && !is_safe_image) { throw uhd::runtime_error(str(boost::format( "Expected FPGA compatibility number %d.x, but got %d.%d:\n" "The FPGA build is not compatible with the host code build.\n" "%s" ) % static_cast(fpga::RB_N230_COMPAT_MAJOR) % static_cast(_fpga_version.compat_major) % static_cast(_fpga_version.compat_minor) % _get_fpga_upgrade_msg())); } } }}} //namespace