diff options
Diffstat (limited to 'host/utils')
21 files changed, 3780 insertions, 0 deletions
| diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt new file mode 100644 index 000000000..9e997071e --- /dev/null +++ b/host/utils/CMakeLists.txt @@ -0,0 +1,107 @@ +# +# Copyright 2010-2012 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Utilities that get installed into the runtime path +######################################################################## +SET(util_runtime_sources +    uhd_find_devices.cpp +    uhd_usrp_probe.cpp +    uhd_cal_rx_iq_balance.cpp +    uhd_cal_tx_dc_offset.cpp +    uhd_cal_tx_iq_balance.cpp +) + +#for each source: build an executable and install +FOREACH(util_source ${util_runtime_sources}) +    GET_FILENAME_COMPONENT(util_name ${util_source} NAME_WE) +    ADD_EXECUTABLE(${util_name} ${util_source}) +    TARGET_LINK_LIBRARIES(${util_name} uhd) +    INSTALL(TARGETS ${util_name} RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT utilities) +ENDFOREACH(util_source) + +######################################################################## +# Utilities that get installed into the share path +######################################################################## +SET(util_share_sources +    query_gpsdo_sensors.cpp +    usrp_burn_db_eeprom.cpp +    usrp_burn_mb_eeprom.cpp +    usrp_n2xx_simple_net_burner.cpp +) + +IF(ENABLE_USB) +    LIST(APPEND util_share_sources +        fx2_init_eeprom.cpp +    ) +ENDIF(ENABLE_USB) + +IF(LINUX AND ENABLE_USB) +    INSTALL(FILES +        uhd-usrp.rules +        DESTINATION ${PKG_LIB_DIR}/utils +        COMPONENT utilities +    ) +ENDIF(LINUX AND ENABLE_USB) + +#for each source: build an executable and install +FOREACH(util_source ${util_share_sources}) +    GET_FILENAME_COMPONENT(util_name ${util_source} NAME_WE) +    ADD_EXECUTABLE(${util_name} ${util_source}) +    TARGET_LINK_LIBRARIES(${util_name} uhd) +    INSTALL(TARGETS ${util_name} RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) +ENDFOREACH(util_source) + +#UHD images downloader configuration +CONFIGURE_FILE( +    ${CMAKE_CURRENT_SOURCE_DIR}/uhd_images_downloader.py.in +    ${CMAKE_CURRENT_BINARY_DIR}/uhd_images_downloader.py +@ONLY) +INSTALL(PROGRAMS +    ${CMAKE_CURRENT_BINARY_DIR}/uhd_images_downloader.py +    DESTINATION ${PKG_LIB_DIR}/utils +    COMPONENT utilities +) + +IF(ENABLE_USRP2) +    IF(WIN32 AND UHD_RELEASE_MODE) #include dd.exe +        FILE(DOWNLOAD +            "http://files.ettus.com/dd.exe" +            ${CMAKE_CURRENT_BINARY_DIR}/dd.exe +        ) +        INSTALL(FILES +            ${CMAKE_CURRENT_BINARY_DIR}/dd.exe +            DESTINATION ${PKG_LIB_DIR}/utils +            COMPONENT utilities +        ) +    ENDIF(WIN32 AND UHD_RELEASE_MODE) +    IF(LINUX) +        INSTALL(PROGRAMS +            usrp2_recovery.py +            DESTINATION ${PKG_LIB_DIR}/utils +            COMPONENT utilities +        ) +    ENDIF(LINUX) +    INSTALL(PROGRAMS +        usrp2_card_burner.py +        usrp2_card_burner_gui.py +        usrp_n2xx_net_burner.py +        usrp_n2xx_net_burner_gui.py +        DESTINATION ${PKG_LIB_DIR}/utils +        COMPONENT utilities +    ) +ENDIF(ENABLE_USRP2) diff --git a/host/utils/FastSendDatagramThreshold.reg b/host/utils/FastSendDatagramThreshold.regBinary files differ new file mode 100755 index 000000000..c0665d09e --- /dev/null +++ b/host/utils/FastSendDatagramThreshold.reg diff --git a/host/utils/fx2_init_eeprom.cpp b/host/utils/fx2_init_eeprom.cpp new file mode 100644 index 000000000..c210ae575 --- /dev/null +++ b/host/utils/fx2_init_eeprom.cpp @@ -0,0 +1,93 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/safe_main.hpp> +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <cstdlib> + +const std::string FX2_VENDOR_ID("0x04b4"); +const std::string FX2_PRODUCT_ID("0x8613"); + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ +    std::string type; +    po::options_description desc("Allowed options"); +    desc.add_options() +        ("help", "help message") +        ("image", po::value<std::string>(), "BIN image file") +        ("vid", po::value<std::string>(), "VID of device to program") +        ("pid", po::value<std::string>(), "PID of device to program") +        ("type", po::value<std::string>(), "device type (usrp1 or b100)") +    ; + +    po::variables_map vm; +    po::store(po::parse_command_line(argc, argv, desc), vm); +    po::notify(vm);  + +    //print the help message +    if (vm.count("help")){ +        std::cout << boost::format("USRP EEPROM initialization %s") % desc << std::endl; +        return ~0; +    } + +    //cant find a uninitialized usrp with this mystery module in the way... +    if (std::system("/sbin/rmmod usbtest") != 0){ +        std::cerr << "Did not rmmod usbtest, this may be ok..." << std::endl; +    } + +    //load the options into the address +    uhd::device_addr_t device_addr; +    device_addr["type"] = type; +    if(vm.count("vid") or vm.count("pid") or vm.count("type")) { +        if(not (vm.count("vid") and vm.count("pid") and vm.count("type"))) { +            std::cerr << "ERROR: Must specify vid, pid, and type if specifying any of the three args" << std::endl; +        } else { +            device_addr["vid"] = vm["vid"].as<std::string>(); +            device_addr["pid"] = vm["pid"].as<std::string>(); +            device_addr["type"] = vm["type"].as<std::string>(); +        } +    } else { +        device_addr["vid"] = FX2_VENDOR_ID; +        device_addr["pid"] = FX2_PRODUCT_ID; +    } + +    //find and create a control transport to do the writing. + +    uhd::device_addrs_t found_addrs = uhd::device::find(device_addr); + +    if (found_addrs.size() == 0){ +        std::cerr << "No USRP devices found" << std::endl; +        return ~0; +    } + +    for (size_t i = 0; i < found_addrs.size(); i++){ +        std::cout << "Writing EEPROM data..." << std::endl; +        //uhd::device_addrs_t devs = uhd::device::find(found_addrs[i]); +        uhd::device::sptr dev = uhd::device::make(found_addrs[i]); +        uhd::property_tree::sptr tree = dev->get_tree(); +        tree->access<std::string>("/mboards/0/load_eeprom").set(vm["image"].as<std::string>()); +    } + + +    std::cout << "Power-cycle the usrp for the changes to take effect." << std::endl; +    return 0; +} diff --git a/host/utils/query_gpsdo_sensors.cpp b/host/utils/query_gpsdo_sensors.cpp new file mode 100644 index 000000000..de6bdcd72 --- /dev/null +++ b/host/utils/query_gpsdo_sensors.cpp @@ -0,0 +1,122 @@ +// +// Copyright 2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/paths.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/filesystem.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <complex> +#include <boost/thread.hpp> +#include <string> +#include <cmath> +#include <cstdlib> + +namespace po = boost::program_options; +namespace fs = boost::filesystem; + +void print_notes(void) { +  // Helpful notes +  std::cout << boost::format("**************************************Helpful Notes on Clock/PPS Selection**************************************\n"); +  std::cout << boost::format("As you can see, the default 10 MHz Reference and 1 PPS signals are now from the GPSDO.\n"); +  std::cout << boost::format("If you would like to use the internal reference(TCXO) in other applications, you must configure that explicitly.\n"); +  std::cout << boost::format("You can no longer select the external SMAs for 10 MHz or 1 PPS signaling.\n"); +  std::cout << boost::format("****************************************************************************************************************\n"); +} + +int UHD_SAFE_MAIN(int argc, char *argv[]){ +  uhd::set_thread_priority_safe(); + +  std::string args; + +  //Set up program options +  po::options_description desc("Allowed options"); +  desc.add_options() +    ("help", "help message") +    ("args", po::value<std::string>(&args)->default_value(""), "Specify a single USRP.") +    ; +  po::variables_map vm; +  po::store(po::parse_command_line(argc, argv, desc), vm); +  po::notify(vm); + +  //Print the help message +  if (vm.count("help")) { +    std::cout << boost::format("Query GPSDO Sensors %s") % desc << std::endl; +    return EXIT_FAILURE; +  } + +  //Create a USRP device +  std::cout << boost::format("\nCreating the USRP device with: %s...\n") % args; +  uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); +  std::cout << boost::format("Using Device: %s\n") % usrp->get_pp_string(); + +  print_notes(); + + +  //Verify GPS sensors are present (i.e. EEPROM has been burnt) +  std::vector<std::string> sensor_names = usrp->get_mboard_sensor_names(0); + +  if(std::find(sensor_names.begin(), sensor_names.end(), "gps_locked") == sensor_names.end()) { +    std::cout << boost::format("\ngps_locked sensor not found.  This could mean that you have not installed the GPSDO correctly.\n\n"); +    std::cout << boost::format("Visit this page if the problem persists:\n"); +    std::cout << boost::format("http://files.ettus.com/uhd_docs/manual/html/gpsdo.html\n\n"); +    exit(EXIT_FAILURE); +  } + +  //Check for GPS lock +  uhd::sensor_value_t gps_locked = usrp->get_mboard_sensor("gps_locked",0); +  if(not gps_locked.to_bool()) { +    std::cout << boost::format("\nGPS does not have lock. Wait a few minutes and try again.\n"); +    std::cout << boost::format("NMEA strings and device time may not be accurate until lock is achieved.\n\n"); +  } else +      std::cout << boost::format("GPS Locked\n"); + +  //Check for 10 MHz lock +  if(std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end()) { +    uhd::sensor_value_t gps_locked = usrp->get_mboard_sensor("ref_locked",0); +    if(not gps_locked.to_bool()) { +      std::cout << boost::format("USRP NOT Locked to GPSDO 10 MHz Reference.\n"); +      std::cout << boost::format("Double check installation instructions: https://www.ettus.com/content/files/gpsdo-kit_2.pdf\n\n"); +    } else +        std::cout << boost::format("USRP Locked to GPSDO 10 MHz Reference.\n"); +  }else +    std::cout << boost::format("ref_locked sensor not present on this board.\n"); + +  //Check PPS and compare UHD device time to GPS time +  boost::this_thread::sleep(boost::posix_time::seconds(1)); +  uhd::sensor_value_t gps_time = usrp->get_mboard_sensor("gps_time"); +  const uhd::time_spec_t last_pps_time = usrp->get_time_last_pps(); +  if (last_pps_time.get_full_secs() == gps_time.to_int()) { +    std::cout << boost::format("GPS and UHD Device time are aligned.\n"); +  } else +    std::cout << boost::format("\nGPS and UHD Device time are NOT aligned. Try re-running the program. Double check 1 PPS connection from GPSDO.\n\n"); +     +  //print NMEA strings +  std::cout << boost::format("Printing available NMEA strings:\n"); +  uhd::sensor_value_t gga_string = usrp->get_mboard_sensor("gps_gpgga"); +  uhd::sensor_value_t rmc_string = usrp->get_mboard_sensor("gps_gprmc"); +  std::cout << boost::format("%s\n%s\n%s\n") % gga_string.to_pp_string() % rmc_string.to_pp_string() % gps_time.to_pp_string(); +  std::cout << boost::format("UHD Device time: %.0f seconds\n") % (last_pps_time.get_real_secs()); + +  //finished +  std::cout << boost::format("\nDone!\n\n"); + +  return EXIT_SUCCESS; +} diff --git a/host/utils/uhd-usrp.rules b/host/utils/uhd-usrp.rules new file mode 100644 index 000000000..56d9a8c43 --- /dev/null +++ b/host/utils/uhd-usrp.rules @@ -0,0 +1,19 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="fffe", ATTRS{idProduct}=="0002", MODE:="0666" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2500", ATTRS{idProduct}=="0002", MODE:="0666" diff --git a/host/utils/uhd_cal_rx_iq_balance.cpp b/host/utils/uhd_cal_rx_iq_balance.cpp new file mode 100644 index 000000000..68d0443da --- /dev/null +++ b/host/utils/uhd_cal_rx_iq_balance.cpp @@ -0,0 +1,243 @@ +// +// Copyright 2010,2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_cal_utils.hpp" +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> +#include <complex> +#include <cmath> +#include <ctime> + +namespace po = boost::program_options; + +/*********************************************************************** + * Transmit thread + **********************************************************************/ +static void tx_thread(uhd::usrp::multi_usrp::sptr usrp, const double tx_wave_ampl){ +    uhd::set_thread_priority_safe(); + +    //create a transmit streamer +    uhd::stream_args_t stream_args("fc32"); //complex floats +    uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + +    //setup variables and allocate buffer +    uhd::tx_metadata_t md; +    md.has_time_spec = false; +    std::vector<samp_type> buff(tx_stream->get_max_num_samps()*10); + +    //fill buff and send until interrupted +    while (not boost::this_thread::interruption_requested()){ +        for (size_t i = 0; i < buff.size(); i++){ +            buff[i] = float(tx_wave_ampl); +        } +        tx_stream->send(&buff.front(), buff.size(), md); +    } + +    //send a mini EOB packet +    md.end_of_burst = true; +    tx_stream->send("", 0, md); +} + +/*********************************************************************** + * Tune RX and TX routine + **********************************************************************/ +static double tune_rx_and_tx(uhd::usrp::multi_usrp::sptr usrp, const double rx_lo_freq, const double tx_offset){ +    //tune the receiver with no cordic +    uhd::tune_request_t rx_tune_req(rx_lo_freq); +    rx_tune_req.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; +    rx_tune_req.dsp_freq = 0; +    usrp->set_rx_freq(rx_tune_req); + +    //tune the transmitter with no cordic +    uhd::tune_request_t tx_tune_req(usrp->get_rx_freq() - tx_offset); +    tx_tune_req.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; +    tx_tune_req.dsp_freq = 0; +    usrp->set_tx_freq(tx_tune_req); + +    //wait for the LOs to become locked +    boost::this_thread::sleep(boost::posix_time::milliseconds(50)); +    boost::system_time start = boost::get_system_time(); +    while (not usrp->get_tx_sensor("lo_locked").to_bool() or not usrp->get_rx_sensor("lo_locked").to_bool()){ +        if (boost::get_system_time() > start + boost::posix_time::milliseconds(100)){ +            throw std::runtime_error("timed out waiting for TX and/or RX LO to lock"); +        } +    } + +    return usrp->get_rx_freq(); +} + +/*********************************************************************** + * Main + **********************************************************************/ +int UHD_SAFE_MAIN(int argc, char *argv[]){ +    std::string args; +    double tx_wave_ampl, tx_offset; +    double freq_start, freq_stop, freq_step; +    size_t nsamps; + +    po::options_description desc("Allowed options"); +    desc.add_options() +        ("help", "help message") +        ("verbose", "enable some verbose") +        ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") +        ("tx_wave_ampl", po::value<double>(&tx_wave_ampl)->default_value(0.7), "Transmit wave amplitude in counts") +        ("tx_offset", po::value<double>(&tx_offset)->default_value(.9344e6), "TX LO offset from the RX LO in Hz") +        ("freq_start", po::value<double>(&freq_start), "Frequency start in Hz (do not specify for default)") +        ("freq_stop", po::value<double>(&freq_stop), "Frequency stop in Hz (do not specify for default)") +        ("freq_step", po::value<double>(&freq_step)->default_value(default_freq_step), "Step size for LO sweep in Hz") +        ("nsamps", po::value<size_t>(&nsamps)->default_value(default_num_samps), "Samples per data capture") +    ; + +    po::variables_map vm; +    po::store(po::parse_command_line(argc, argv, desc), vm); +    po::notify(vm); + +    //print the help message +    if (vm.count("help")){ +        std::cout << boost::format("USRP Generate RX IQ Balance Calibration Table %s") % desc << std::endl; +        std::cout << +            "This application measures leakage between RX and TX on an XCVR daughterboard to self-calibrate.\n" +            << std::endl; +        return ~0; +    } + +    //create a usrp device +    std::cout << std::endl; +    std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; +    uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + +    //set the antennas to cal +    if (not uhd::has(usrp->get_rx_antennas(), "CAL") or not uhd::has(usrp->get_tx_antennas(), "CAL")){ +        throw std::runtime_error("This board does not have the CAL antenna option, cannot self-calibrate."); +    } +    usrp->set_rx_antenna("CAL"); +    usrp->set_tx_antenna("CAL"); + +    //fail if daughterboard has no serial +    check_for_empty_serial(usrp, "RX", "rx", args); + +    //set optimum defaults +    set_optimum_defaults(usrp); + +    //create a receive streamer +    uhd::stream_args_t stream_args("fc32"); //complex floats +    uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + +    //create a transmitter thread +    boost::thread_group threads; +    threads.create_thread(boost::bind(&tx_thread, usrp, tx_wave_ampl)); + +    //re-usable buffer for samples +    std::vector<samp_type> buff; + +    //store the results here +    std::vector<result_t> results; + +    if (not vm.count("freq_start")) freq_start = usrp->get_rx_freq_range().start() + 50e6; +    if (not vm.count("freq_stop")) freq_stop = usrp->get_rx_freq_range().stop() - 50e6; + +    for (double rx_lo_i = freq_start; rx_lo_i <= freq_stop; rx_lo_i += freq_step){ +        const double rx_lo = tune_rx_and_tx(usrp, rx_lo_i, tx_offset); + +        //frequency constants for this tune event +        const double actual_rx_rate = usrp->get_rx_rate(); +        const double actual_tx_freq = usrp->get_tx_freq(); +        const double actual_rx_freq = usrp->get_rx_freq(); +        const double bb_tone_freq = actual_tx_freq - actual_rx_freq; +        const double bb_imag_freq = -bb_tone_freq; + +        //capture initial uncorrected value +        usrp->set_rx_iq_balance(0.0); +        capture_samples(usrp, rx_stream, buff, nsamps); +        const double initial_suppression = compute_tone_dbrms(buff, bb_tone_freq/actual_rx_rate) - compute_tone_dbrms(buff, bb_imag_freq/actual_rx_rate); + +        //bounds and results from searching +        std::complex<double> best_correction; +        double phase_corr_start = -.3, phase_corr_stop = .3, phase_corr_step; +        double ampl_corr_start = -.3, ampl_corr_stop = .3, ampl_corr_step; +        double best_suppression = 0, best_phase_corr = 0, best_ampl_corr = 0; + +        for (size_t i = 0; i < num_search_iters; i++){ + +            phase_corr_step = (phase_corr_stop - phase_corr_start)/(num_search_steps-1); +            ampl_corr_step = (ampl_corr_stop - ampl_corr_start)/(num_search_steps-1); + +            for (double phase_corr = phase_corr_start; phase_corr <= phase_corr_stop + phase_corr_step/2; phase_corr += phase_corr_step){ +            for (double ampl_corr = ampl_corr_start; ampl_corr <= ampl_corr_stop + ampl_corr_step/2; ampl_corr += ampl_corr_step){ + +                const std::complex<double> correction(ampl_corr, phase_corr); +                usrp->set_rx_iq_balance(correction); + +                //receive some samples +                capture_samples(usrp, rx_stream, buff, nsamps); + +                const double tone_dbrms = compute_tone_dbrms(buff, bb_tone_freq/actual_rx_rate); +                const double imag_dbrms = compute_tone_dbrms(buff, bb_imag_freq/actual_rx_rate); +                const double suppression = tone_dbrms - imag_dbrms; + +                if (suppression > best_suppression){ +                    best_correction = correction; +                    best_suppression = suppression; +                    best_phase_corr = phase_corr; +                    best_ampl_corr = ampl_corr; +                } + +            }} + +            //std::cout << "best_phase_corr " << best_phase_corr << std::endl; +            //std::cout << "best_ampl_corr " << best_ampl_corr << std::endl; +            //std::cout << "best_suppression " << best_suppression << std::endl; + +            phase_corr_start = best_phase_corr - phase_corr_step; +            phase_corr_stop = best_phase_corr + phase_corr_step; +            ampl_corr_start = best_ampl_corr - ampl_corr_step; +            ampl_corr_stop = best_ampl_corr + ampl_corr_step; +        } + +        if (best_suppression > 30){ //most likely valid, keep result +            result_t result; +            result.freq = rx_lo; +            result.real_corr = best_correction.real(); +            result.imag_corr = best_correction.imag(); +            result.best = best_suppression; +            result.delta = best_suppression - initial_suppression; +            results.push_back(result); +            if (vm.count("verbose")){ +                std::cout << boost::format("RX IQ: %f MHz: best suppression %f dB, corrected %f dB") % (rx_lo/1e6) % result.best % result.delta << std::endl; +            } +            else std::cout << "." << std::flush; +        } + +    } +    std::cout << std::endl; + +    //stop the transmitter +    threads.interrupt_all(); +    threads.join_all(); + +    store_results(usrp, results, "RX", "rx", "iq"); + +    return 0; +} diff --git a/host/utils/uhd_cal_tx_dc_offset.cpp b/host/utils/uhd_cal_tx_dc_offset.cpp new file mode 100644 index 000000000..8f69b3ce1 --- /dev/null +++ b/host/utils/uhd_cal_tx_dc_offset.cpp @@ -0,0 +1,241 @@ +// +// Copyright 2010,2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_cal_utils.hpp" +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> +#include <complex> +#include <ctime> + +namespace po = boost::program_options; + +/*********************************************************************** + * Transmit thread + **********************************************************************/ +static void tx_thread(uhd::usrp::multi_usrp::sptr usrp, const double tx_wave_freq, const double tx_wave_ampl){ +    uhd::set_thread_priority_safe(); + +    //create a transmit streamer +    uhd::stream_args_t stream_args("fc32"); //complex floats +    uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + +    //setup variables and allocate buffer +    uhd::tx_metadata_t md; +    md.has_time_spec = false; +    std::vector<samp_type> buff(tx_stream->get_max_num_samps()*10); + +    //values for the wave table lookup +    size_t index = 0; +    const double tx_rate = usrp->get_tx_rate(); +    const size_t step = boost::math::iround(wave_table_len * tx_wave_freq/tx_rate); +    wave_table table(tx_wave_ampl); + +    //fill buff and send until interrupted +    while (not boost::this_thread::interruption_requested()){ +        for (size_t i = 0; i < buff.size(); i++){ +            buff[i] = table(index += step); +        } +        tx_stream->send(&buff.front(), buff.size(), md); +    } + +    //send a mini EOB packet +    md.end_of_burst = true; +    tx_stream->send("", 0, md); +} + +/*********************************************************************** + * Tune RX and TX routine + **********************************************************************/ +static double tune_rx_and_tx(uhd::usrp::multi_usrp::sptr usrp, const double tx_lo_freq, const double rx_offset){ +    //tune the transmitter with no cordic +    uhd::tune_request_t tx_tune_req(tx_lo_freq); +    tx_tune_req.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; +    tx_tune_req.dsp_freq = 0; +    usrp->set_tx_freq(tx_tune_req); + +    //tune the receiver +    usrp->set_rx_freq(usrp->get_tx_freq() - rx_offset); + +    //wait for the LOs to become locked +    boost::this_thread::sleep(boost::posix_time::milliseconds(50)); +    boost::system_time start = boost::get_system_time(); +    while (not usrp->get_tx_sensor("lo_locked").to_bool() or not usrp->get_rx_sensor("lo_locked").to_bool()){ +        if (boost::get_system_time() > start + boost::posix_time::milliseconds(100)){ +            throw std::runtime_error("timed out waiting for TX and/or RX LO to lock"); +        } +    } + +    return usrp->get_tx_freq(); +} + +/*********************************************************************** + * Main + **********************************************************************/ +int UHD_SAFE_MAIN(int argc, char *argv[]){ +    std::string args; +    double tx_wave_freq, tx_wave_ampl, rx_offset; +    double freq_start, freq_stop, freq_step; +    size_t nsamps; + +    po::options_description desc("Allowed options"); +    desc.add_options() +        ("help", "help message") +        ("verbose", "enable some verbose") +        ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") +        ("tx_wave_freq", po::value<double>(&tx_wave_freq)->default_value(507.123e3), "Transmit wave frequency in Hz") +        ("tx_wave_ampl", po::value<double>(&tx_wave_ampl)->default_value(0.7), "Transmit wave amplitude in counts") +        ("rx_offset", po::value<double>(&rx_offset)->default_value(.9344e6), "RX LO offset from the TX LO in Hz") +        ("freq_start", po::value<double>(&freq_start), "Frequency start in Hz (do not specify for default)") +        ("freq_stop", po::value<double>(&freq_stop), "Frequency stop in Hz (do not specify for default)") +        ("freq_step", po::value<double>(&freq_step)->default_value(default_freq_step), "Step size for LO sweep in Hz") +        ("nsamps", po::value<size_t>(&nsamps)->default_value(default_num_samps), "Samples per data capture") +    ; + +    po::variables_map vm; +    po::store(po::parse_command_line(argc, argv, desc), vm); +    po::notify(vm); + +    //print the help message +    if (vm.count("help")){ +        std::cout << boost::format("USRP Generate TX DC Offset Calibration Table %s") % desc << std::endl; +        std::cout << +            "This application measures leakage between RX and TX on an XCVR daughterboard to self-calibrate.\n" +            << std::endl; +        return ~0; +    } + +    //create a usrp device +    std::cout << std::endl; +    std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; +    uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + +    //set the antennas to cal +    if (not uhd::has(usrp->get_rx_antennas(), "CAL") or not uhd::has(usrp->get_tx_antennas(), "CAL")){ +        throw std::runtime_error("This board does not have the CAL antenna option, cannot self-calibrate."); +    } +    usrp->set_rx_antenna("CAL"); +    usrp->set_tx_antenna("CAL"); + +    //fail if daughterboard has no serial +    check_for_empty_serial(usrp, "TX", "tx", args); + +    //set optimum defaults +    set_optimum_defaults(usrp); + +    //create a receive streamer +    uhd::stream_args_t stream_args("fc32"); //complex floats +    uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + +    //create a transmitter thread +    boost::thread_group threads; +    threads.create_thread(boost::bind(&tx_thread, usrp, tx_wave_freq, tx_wave_ampl)); + +    //re-usable buffer for samples +    std::vector<samp_type> buff; + +    //store the results here +    std::vector<result_t> results; + +    if (not vm.count("freq_start")) freq_start = usrp->get_tx_freq_range().start() + 50e6; +    if (not vm.count("freq_stop")) freq_stop = usrp->get_tx_freq_range().stop() - 50e6; + +    for (double tx_lo_i = freq_start; tx_lo_i <= freq_stop; tx_lo_i += freq_step){ +        const double tx_lo = tune_rx_and_tx(usrp, tx_lo_i, rx_offset); + +        //frequency constants for this tune event +        const double actual_rx_rate = usrp->get_rx_rate(); +        const double actual_tx_freq = usrp->get_tx_freq(); +        const double actual_rx_freq = usrp->get_rx_freq(); +        const double bb_dc_freq = actual_tx_freq - actual_rx_freq; + +        //capture initial uncorrected value +        usrp->set_tx_dc_offset(std::complex<double>(0, 0)); +        capture_samples(usrp, rx_stream, buff, nsamps); +        const double initial_dc_dbrms = compute_tone_dbrms(buff, bb_dc_freq/actual_rx_rate); + +        //bounds and results from searching +        double dc_i_start = -.01, dc_i_stop = .01, dc_i_step; +        double dc_q_start = -.01, dc_q_stop = .01, dc_q_step; +        double lowest_offset = 0, best_dc_i = 0, best_dc_q = 0; + +        for (size_t i = 0; i < num_search_iters; i++){ + +            dc_i_step = (dc_i_stop - dc_i_start)/(num_search_steps-1); +            dc_q_step = (dc_q_stop - dc_q_start)/(num_search_steps-1); + +            for (double dc_i = dc_i_start; dc_i <= dc_i_stop + dc_i_step/2; dc_i += dc_i_step){ +            for (double dc_q = dc_q_start; dc_q <= dc_q_stop + dc_q_step/2; dc_q += dc_q_step){ + +                const std::complex<double> correction(dc_i, dc_q); +                usrp->set_tx_dc_offset(correction); + +                //receive some samples +                capture_samples(usrp, rx_stream, buff, nsamps); + +                const double dc_dbrms = compute_tone_dbrms(buff, bb_dc_freq/actual_rx_rate); + +                if (dc_dbrms < lowest_offset){ +                    lowest_offset = dc_dbrms; +                    best_dc_i = dc_i; +                    best_dc_q = dc_q; +                } + +            }} + +            //std::cout << "best_dc_i " << best_dc_i << std::endl; +            //std::cout << "best_dc_q " << best_dc_q << std::endl; +            //std::cout << "lowest_offset " << lowest_offset << std::endl; + +            dc_i_start = best_dc_i - dc_i_step; +            dc_i_stop = best_dc_i + dc_i_step; +            dc_q_start = best_dc_q - dc_q_step; +            dc_q_stop = best_dc_q + dc_q_step; +        } + +        if (lowest_offset < initial_dc_dbrms){ //most likely valid, keep result +            result_t result; +            result.freq = tx_lo; +            result.real_corr = best_dc_i; +            result.imag_corr = best_dc_q; +            result.best = lowest_offset; +            result.delta = initial_dc_dbrms - lowest_offset; +            results.push_back(result); +            if (vm.count("verbose")){ +                std::cout << boost::format("TX DC: %f MHz: lowest offset %f dB, corrected %f dB") % (tx_lo/1e6) % result.best % result.delta << std::endl; +            } +            else std::cout << "." << std::flush; +        } + +    } +    std::cout << std::endl; + +    //stop the transmitter +    threads.interrupt_all(); +    threads.join_all(); + +    store_results(usrp, results, "TX", "tx", "dc"); + +    return 0; +} diff --git a/host/utils/uhd_cal_tx_iq_balance.cpp b/host/utils/uhd_cal_tx_iq_balance.cpp new file mode 100644 index 000000000..5478b07e3 --- /dev/null +++ b/host/utils/uhd_cal_tx_iq_balance.cpp @@ -0,0 +1,246 @@ +// +// Copyright 2010,2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_cal_utils.hpp" +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> +#include <complex> +#include <ctime> + +namespace po = boost::program_options; + +/*********************************************************************** + * Transmit thread + **********************************************************************/ +static void tx_thread(uhd::usrp::multi_usrp::sptr usrp, const double tx_wave_freq, const double tx_wave_ampl){ +    uhd::set_thread_priority_safe(); + +    //create a transmit streamer +    uhd::stream_args_t stream_args("fc32"); //complex floats +    uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + +    //setup variables and allocate buffer +    uhd::tx_metadata_t md; +    md.has_time_spec = false; +    std::vector<samp_type> buff(tx_stream->get_max_num_samps()*10); + +    //values for the wave table lookup +    size_t index = 0; +    const double tx_rate = usrp->get_tx_rate(); +    const size_t step = boost::math::iround(wave_table_len * tx_wave_freq/tx_rate); +    wave_table table(tx_wave_ampl); + +    //fill buff and send until interrupted +    while (not boost::this_thread::interruption_requested()){ +        for (size_t i = 0; i < buff.size(); i++){ +            buff[i] = table(index += step); +        } +        tx_stream->send(&buff.front(), buff.size(), md); +    } + +    //send a mini EOB packet +    md.end_of_burst = true; +    tx_stream->send("", 0, md); +} + +/*********************************************************************** + * Tune RX and TX routine + **********************************************************************/ +static double tune_rx_and_tx(uhd::usrp::multi_usrp::sptr usrp, const double tx_lo_freq, const double rx_offset){ +    //tune the transmitter with no cordic +    uhd::tune_request_t tx_tune_req(tx_lo_freq); +    tx_tune_req.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; +    tx_tune_req.dsp_freq = 0; +    usrp->set_tx_freq(tx_tune_req); + +    //tune the receiver +    usrp->set_rx_freq(usrp->get_tx_freq() - rx_offset); + +    //wait for the LOs to become locked +    boost::this_thread::sleep(boost::posix_time::milliseconds(50)); +    boost::system_time start = boost::get_system_time(); +    while (not usrp->get_tx_sensor("lo_locked").to_bool() or not usrp->get_rx_sensor("lo_locked").to_bool()){ +        if (boost::get_system_time() > start + boost::posix_time::milliseconds(100)){ +            throw std::runtime_error("timed out waiting for TX and/or RX LO to lock"); +        } +    } + +    return usrp->get_tx_freq(); +} + +/*********************************************************************** + * Main + **********************************************************************/ +int UHD_SAFE_MAIN(int argc, char *argv[]){ +    std::string args; +    double tx_wave_freq, tx_wave_ampl, rx_offset; +    double freq_start, freq_stop, freq_step; +    size_t nsamps; + +    po::options_description desc("Allowed options"); +    desc.add_options() +        ("help", "help message") +        ("verbose", "enable some verbose") +        ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") +        ("tx_wave_freq", po::value<double>(&tx_wave_freq)->default_value(507.123e3), "Transmit wave frequency in Hz") +        ("tx_wave_ampl", po::value<double>(&tx_wave_ampl)->default_value(0.7), "Transmit wave amplitude in counts") +        ("rx_offset", po::value<double>(&rx_offset)->default_value(.9344e6), "RX LO offset from the TX LO in Hz") +        ("freq_start", po::value<double>(&freq_start), "Frequency start in Hz (do not specify for default)") +        ("freq_stop", po::value<double>(&freq_stop), "Frequency stop in Hz (do not specify for default)") +        ("freq_step", po::value<double>(&freq_step)->default_value(default_freq_step), "Step size for LO sweep in Hz") +        ("nsamps", po::value<size_t>(&nsamps)->default_value(default_num_samps), "Samples per data capture") +    ; + +    po::variables_map vm; +    po::store(po::parse_command_line(argc, argv, desc), vm); +    po::notify(vm); + +    //print the help message +    if (vm.count("help")){ +        std::cout << boost::format("USRP Generate TX IQ Balance Calibration Table %s") % desc << std::endl; +        std::cout << +            "This application measures leakage between RX and TX on an XCVR daughterboard to self-calibrate.\n" +            << std::endl; +        return ~0; +    } + +    //create a usrp device +    std::cout << std::endl; +    std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; +    uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + +    //set the antennas to cal +    if (not uhd::has(usrp->get_rx_antennas(), "CAL") or not uhd::has(usrp->get_tx_antennas(), "CAL")){ +        throw std::runtime_error("This board does not have the CAL antenna option, cannot self-calibrate."); +    } +    usrp->set_rx_antenna("CAL"); +    usrp->set_tx_antenna("CAL"); + +    //fail if daughterboard has no serial +    check_for_empty_serial(usrp, "TX", "tx", args); + +    //set optimum defaults +    set_optimum_defaults(usrp); + +    //create a receive streamer +    uhd::stream_args_t stream_args("fc32"); //complex floats +    uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + +    //create a transmitter thread +    boost::thread_group threads; +    threads.create_thread(boost::bind(&tx_thread, usrp, tx_wave_freq, tx_wave_ampl)); + +    //re-usable buffer for samples +    std::vector<samp_type> buff; + +    //store the results here +    std::vector<result_t> results; + +    if (not vm.count("freq_start")) freq_start = usrp->get_tx_freq_range().start() + 50e6; +    if (not vm.count("freq_stop")) freq_stop = usrp->get_tx_freq_range().stop() - 50e6; + +    for (double tx_lo_i = freq_start; tx_lo_i <= freq_stop; tx_lo_i += freq_step){ +        const double tx_lo = tune_rx_and_tx(usrp, tx_lo_i, rx_offset); + +        //frequency constants for this tune event +        const double actual_rx_rate = usrp->get_rx_rate(); +        const double actual_tx_freq = usrp->get_tx_freq(); +        const double actual_rx_freq = usrp->get_rx_freq(); +        const double bb_tone_freq = actual_tx_freq + tx_wave_freq - actual_rx_freq; +        const double bb_imag_freq = actual_tx_freq - tx_wave_freq - actual_rx_freq; + +        //capture initial uncorrected value +        usrp->set_tx_iq_balance(0.0); +        capture_samples(usrp, rx_stream, buff, nsamps); +        const double initial_suppression = compute_tone_dbrms(buff, bb_tone_freq/actual_rx_rate) - compute_tone_dbrms(buff, bb_imag_freq/actual_rx_rate); + +        //bounds and results from searching +        std::complex<double> best_correction; +        double phase_corr_start = -.3, phase_corr_stop = .3, phase_corr_step; +        double ampl_corr_start = -.3, ampl_corr_stop = .3, ampl_corr_step; +        double best_suppression = 0, best_phase_corr = 0, best_ampl_corr = 0; + +        for (size_t i = 0; i < num_search_iters; i++){ + +            phase_corr_step = (phase_corr_stop - phase_corr_start)/(num_search_steps-1); +            ampl_corr_step = (ampl_corr_stop - ampl_corr_start)/(num_search_steps-1); + +            for (double phase_corr = phase_corr_start; phase_corr <= phase_corr_stop + phase_corr_step/2; phase_corr += phase_corr_step){ +            for (double ampl_corr = ampl_corr_start; ampl_corr <= ampl_corr_stop + ampl_corr_step/2; ampl_corr += ampl_corr_step){ + +                const std::complex<double> correction(ampl_corr, phase_corr); +                usrp->set_tx_iq_balance(correction); + +                //receive some samples +                capture_samples(usrp, rx_stream, buff, nsamps); + +                const double tone_dbrms = compute_tone_dbrms(buff, bb_tone_freq/actual_rx_rate); +                const double imag_dbrms = compute_tone_dbrms(buff, bb_imag_freq/actual_rx_rate); +                const double suppression = tone_dbrms - imag_dbrms; + +                if (suppression > best_suppression){ +                    best_correction = correction; +                    best_suppression = suppression; +                    best_phase_corr = phase_corr; +                    best_ampl_corr = ampl_corr; +                } + +            }} + +            //std::cout << "best_phase_corr " << best_phase_corr << std::endl; +            //std::cout << "best_ampl_corr " << best_ampl_corr << std::endl; +            //std::cout << "best_suppression " << best_suppression << std::endl; + +            phase_corr_start = best_phase_corr - phase_corr_step; +            phase_corr_stop = best_phase_corr + phase_corr_step; +            ampl_corr_start = best_ampl_corr - ampl_corr_step; +            ampl_corr_stop = best_ampl_corr + ampl_corr_step; +        } + +        if (best_suppression > 30){ //most likely valid, keep result +            result_t result; +            result.freq = tx_lo; +            result.real_corr = best_correction.real(); +            result.imag_corr = best_correction.imag(); +            result.best = best_suppression; +            result.delta = best_suppression - initial_suppression; +            results.push_back(result); +            if (vm.count("verbose")){ +                std::cout << boost::format("TX IQ: %f MHz: best suppression %f dB, corrected %f dB") % (tx_lo/1e6) % result.best % result.delta << std::endl; +            } +            else std::cout << "." << std::flush; +        } + +    } +    std::cout << std::endl; + +    //stop the transmitter +    threads.interrupt_all(); +    threads.join_all(); + +    store_results(usrp, results, "TX", "tx", "iq"); + +    return 0; +} diff --git a/host/utils/uhd_find_devices.cpp b/host/utils/uhd_find_devices.cpp new file mode 100644 index 000000000..b778eeb68 --- /dev/null +++ b/host/utils/uhd_find_devices.cpp @@ -0,0 +1,60 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/safe_main.hpp> +#include <uhd/device.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ +    po::options_description desc("Allowed options"); +    desc.add_options() +        ("help", "help message") +        ("args", po::value<std::string>()->default_value(""), "device address args") +    ; + +    po::variables_map vm; +    po::store(po::parse_command_line(argc, argv, desc), vm); +    po::notify(vm); + +    //print the help message +    if (vm.count("help")){ +        std::cout << boost::format("UHD Find Devices %s") % desc << std::endl; +        return ~0; +    } + +    //discover the usrps and print the results +    uhd::device_addrs_t device_addrs = uhd::device::find(vm["args"].as<std::string>()); + +    if (device_addrs.size() == 0){ +        std::cerr << "No UHD Devices Found" << std::endl; +        return ~0; +    } + +    for (size_t i = 0; i < device_addrs.size(); i++){ +        std::cout << "--------------------------------------------------" << std::endl; +        std::cout << "-- UHD Device " << i << std::endl; +        std::cout << "--------------------------------------------------" << std::endl; +        std::cout << device_addrs[i].to_pp_string() << std::endl << std::endl; +        //uhd::device::make(device_addrs[i]); //test make +    } + +    return 0; +} diff --git a/host/utils/uhd_images_downloader.py.in b/host/utils/uhd_images_downloader.py.in new file mode 100644 index 000000000..8c8d2df81 --- /dev/null +++ b/host/utils/uhd_images_downloader.py.in @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# +# Copyright 2012 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +import atexit +from optparse import OptionParser +import os +import os.path +import shutil +import sys +import tempfile +import urllib2 +import zipfile + +class temp_dir(): + +    def __enter__(self): +        self.name = tempfile.mkdtemp() +        return self.name +    def __exit__(self, type, value, traceback): +        os.removedirs(self.name) + +if __name__ == "__main__": + +    #Command line options +    parser = OptionParser() +    parser.add_option("--install-location", type="string", default="", help="Set custom install location for images") +    parser.add_option("--buffer-size", type="int", default=8192, help="Set download buffer size, [default=%default]",) +    (options, args) = parser.parse_args() +     +    #Configuring image download info +    images_src = "@UHD_IMAGES_DOWNLOAD_SRC@" +    filename = images_src.split("/")[-1] + +    with temp_dir() as dirname: +        os.chdir(dirname) + +        #Configuring image destination +        if options.install_location != "": +            images_dir = options.install_location +        elif os.environ.get("UHD_IMAGES_DIR") != "" and os.environ.get("UHD_IMAGES_DIR") != None: +            images_dir = os.environ.get("UHD_IMAGES_DIR") +        else: +            images_dir = "@CMAKE_INSTALL_PREFIX@/share/uhd/images" +         +        u = urllib2.urlopen(images_src) +        f = open(filename, "wb") +        meta = u.info() +        filesize = float(meta.getheaders("Content-Length")[0]) +         +        print "Downloading images from: %s" % images_src +         +        filesize_dl = 0.0 + +        #Downloading file     +        while True: +            buffer = u.read(options.buffer_size) +            if not buffer: +                break +         +            filesize_dl -= len(buffer) +            f.write(buffer) + +            status = r"%2.2f MB/%2.2f MB (%3.2f" % (-filesize_dl/1e6, filesize/1e6, (-filesize_dl*100.)/filesize) + r"%)" +            status += chr(8)*(len(status)+1) +            print status, +         +        f.close() + +        #Extracting contents of zip file +        if os.path.exists("tempdir"): +            shutil.rmtree("tempdir") +        os.mkdir("tempdir") + +        images_zip = zipfile.ZipFile(filename) +        images_zip.extractall("tempdir") + +        #Removing images currently in images_dir +        if os.path.exists(images_dir): +            try: +                shutil.rmtree(images_dir) +            except: +                sys.stderr.write("\nMake sure you have write permissions in the images directory.\n") +                sys.exit(0) + +        #Copying downloaded images into images_dir +        shutil.copytree("tempdir/%s/share/uhd/images" % filename[:-4],images_dir) + +        #Removing tempdir and zip file +        shutil.rmtree("tempdir") +        images_zip.close() +        os.remove(filename) + +        os.chdir(images_dir) +        print "\nImages successfully installed to: %s" % images_dir diff --git a/host/utils/uhd_usrp_probe.cpp b/host/utils/uhd_usrp_probe.cpp new file mode 100644 index 000000000..5b3702fb4 --- /dev/null +++ b/host/utils/uhd_usrp_probe.cpp @@ -0,0 +1,220 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/safe_main.hpp> +#include <uhd/version.hpp> +#include <uhd/device.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/property_tree.hpp> +#include <boost/algorithm/string.hpp> //for split +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <iostream> +#include <sstream> +#include <vector> + +namespace po = boost::program_options; +using namespace uhd; + +static std::string indent(size_t level){ +    return (level)? (indent(level-1) + " ") : ""; +} + +static std::string make_border(const std::string &text){ +    std::stringstream ss; +    ss << boost::format("  _____________________________________________________") << std::endl; +    ss << boost::format(" /") << std::endl; +    std::vector<std::string> lines; boost::split(lines, text, boost::is_any_of("\n")); +    while (lines.back().empty()) lines.pop_back(); //strip trailing newlines +    if (lines.size()) lines[0] = "    " + lines[0]; //indent the title line +    BOOST_FOREACH(const std::string &line, lines){ +        ss << boost::format("|   %s") % line << std::endl; +    } +    //ss << boost::format(" \\_____________________________________________________") << std::endl; +    return ss.str(); +} + +static std::string get_dsp_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ +    std::stringstream ss; +    ss << boost::format("%s DSP: %s") % type % path.leaf() << std::endl; +    //ss << std::endl; +    meta_range_t freq_range = tree->access<meta_range_t>(path / "freq/range").get(); +    ss << boost::format("Freq range: %.3f to %.3f Mhz") % (freq_range.start()/1e6) % (freq_range.stop()/1e6) << std::endl;; +    return ss.str(); +} + +static std::string prop_names_to_pp_string(const std::vector<std::string> &prop_names){ +    std::stringstream ss; size_t count = 0; +    BOOST_FOREACH(const std::string &prop_name, prop_names){ +        ss << ((count++)? ", " : "") << prop_name; +    } +    return ss.str(); +} + +static std::string get_frontend_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ +    std::stringstream ss; +    ss << boost::format("%s Frontend: %s") % type % path.leaf() << std::endl; +    //ss << std::endl; + +    ss << boost::format("Name: %s") % (tree->access<std::string>(path / "name").get()) << std::endl; +    ss << boost::format("Antennas: %s") % prop_names_to_pp_string(tree->access<std::vector<std::string> >(path / "antenna/options").get()) << std::endl; +    ss << boost::format("Sensors: %s") % prop_names_to_pp_string(tree->list(path / "sensors")) << std::endl; + +    meta_range_t freq_range = tree->access<meta_range_t>(path / "freq/range").get(); +    ss << boost::format("Freq range: %.3f to %.3f Mhz") % (freq_range.start()/1e6) % (freq_range.stop()/1e6) << std::endl; + +    std::vector<std::string> gain_names = tree->list(path / "gains"); +    if (gain_names.size() == 0) ss << "Gain Elements: None" << std::endl; +    BOOST_FOREACH(const std::string &name, gain_names){ +        meta_range_t gain_range = tree->access<meta_range_t>(path / "gains" / name / "range").get(); +        ss << boost::format("Gain range %s: %.1f to %.1f step %.1f dB") % name % gain_range.start() % gain_range.stop() % gain_range.step() << std::endl; +    } + +    ss << boost::format("Connection Type: %s") % (tree->access<std::string>(path / "connection").get()) << std::endl; +    ss << boost::format("Uses LO offset: %s") % ((tree->access<bool>(path / "use_lo_offset").get())? "Yes" : "No") << std::endl; + +    return ss.str(); +} + +static std::string get_codec_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ +    std::stringstream ss; +    ss << boost::format("%s Codec: %s") % type % path.leaf() << std::endl; +    //ss << std::endl; + +    ss << boost::format("Name: %s") % (tree->access<std::string>(path / "name").get()) << std::endl; +    std::vector<std::string> gain_names = tree->list(path / "gains"); +    if (gain_names.size() == 0) ss << "Gain Elements: None" << std::endl; +    BOOST_FOREACH(const std::string &name, gain_names){ +        meta_range_t gain_range = tree->access<meta_range_t>(path / "gains" / name / "range").get(); +        ss << boost::format("Gain range %s: %.1f to %.1f step %.1f dB") % name % gain_range.start() % gain_range.stop() % gain_range.step() << std::endl; +    } +    return ss.str(); +} + +static std::string get_dboard_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ +    std::stringstream ss; +    ss << boost::format("%s Dboard: %s") % type % path.leaf() << std::endl; +    //ss << std::endl; +    const std::string prefix = (type == "RX")? "rx" : "tx"; +    usrp::dboard_eeprom_t db_eeprom = tree->access<usrp::dboard_eeprom_t>(path / (prefix + "_eeprom")).get(); +    if (db_eeprom.id != usrp::dboard_id_t::none()) ss << boost::format("ID: %s") % db_eeprom.id.to_pp_string() << std::endl; +    if (not db_eeprom.serial.empty()) ss << boost::format("Serial: %s") % db_eeprom.serial << std::endl; +    if (type == "TX"){ +        usrp::dboard_eeprom_t gdb_eeprom = tree->access<usrp::dboard_eeprom_t>(path / "gdb_eeprom").get(); +        if (gdb_eeprom.id != usrp::dboard_id_t::none()) ss << boost::format("ID: %s") % gdb_eeprom.id.to_pp_string() << std::endl; +        if (not gdb_eeprom.serial.empty()) ss << boost::format("Serial: %s") % gdb_eeprom.serial << std::endl; +    } +    BOOST_FOREACH(const std::string &name, tree->list(path / (prefix + "_frontends"))){ +        ss << make_border(get_frontend_pp_string(type, tree, path / (prefix + "_frontends") / name)); +    } +    ss << make_border(get_codec_pp_string(type, tree, path.branch_path().branch_path() / (prefix + "_codecs") / path.leaf())); +    return ss.str(); +} + +static std::string get_mboard_pp_string(property_tree::sptr tree, const fs_path &path){ +    std::stringstream ss; +    ss << boost::format("Mboard: %s") % (tree->access<std::string>(path / "name").get()) << std::endl; +    //ss << std::endl; +    usrp::mboard_eeprom_t mb_eeprom = tree->access<usrp::mboard_eeprom_t>(path / "eeprom").get(); +    BOOST_FOREACH(const std::string &key, mb_eeprom.keys()){ +        if (not mb_eeprom[key].empty()) ss << boost::format("%s: %s") % key % mb_eeprom[key] << std::endl; +    } +    if (tree->exists(path / "fw_version")){ +        ss << "FW Version: " << tree->access<std::string>(path / "fw_version").get() << std::endl; +    } +    if (tree->exists(path / "fpga_version")){ +        ss << "FPGA Version: " << tree->access<std::string>(path / "fpga_version").get() << std::endl; +    } +    ss << std::endl; +    ss << "Time sources: " << prop_names_to_pp_string(tree->access<std::vector<std::string> >(path / "time_source" / "options").get()) << std::endl; +    ss << "Clock sources: " << prop_names_to_pp_string(tree->access<std::vector<std::string> >(path / "clock_source" / "options").get()) << std::endl; +    ss << "Sensors: " << prop_names_to_pp_string(tree->list(path / "sensors")) << std::endl; +    BOOST_FOREACH(const std::string &name, tree->list(path / "rx_dsps")){ +        ss << make_border(get_dsp_pp_string("RX", tree, path / "rx_dsps" / name)); +    } +    BOOST_FOREACH(const std::string &name, tree->list(path / "dboards")){ +        ss << make_border(get_dboard_pp_string("RX", tree, path / "dboards" / name)); +    } +    BOOST_FOREACH(const std::string &name, tree->list(path / "tx_dsps")){ +        ss << make_border(get_dsp_pp_string("TX", tree, path / "tx_dsps" / name)); +    } +    BOOST_FOREACH(const std::string &name, tree->list(path / "dboards")){ +        ss << make_border(get_dboard_pp_string("TX", tree, path / "dboards" / name)); +    } +    return ss.str(); +} + + +static std::string get_device_pp_string(property_tree::sptr tree){ +    std::stringstream ss; +    ss << boost::format("Device: %s") % (tree->access<std::string>("/name").get()) << std::endl; +    //ss << std::endl; +    BOOST_FOREACH(const std::string &name, tree->list("/mboards")){ +        ss << make_border(get_mboard_pp_string(tree, "/mboards/" + name)); +    } +    return ss.str(); +} + +void print_tree(const uhd::fs_path &path, uhd::property_tree::sptr tree){ +    std::cout << path << std::endl; +    BOOST_FOREACH(const std::string &name, tree->list(path)){ +        print_tree(path / name, tree); +    } +} + +int UHD_SAFE_MAIN(int argc, char *argv[]){ +    po::options_description desc("Allowed options"); +    desc.add_options() +        ("help", "help message") +        ("version", "print the version string and exit") +        ("args", po::value<std::string>()->default_value(""), "device address args") +        ("tree", "specify to print a complete property tree") +        ("string", po::value<std::string>(), "query a string value from the properties tree") +    ; + +    po::variables_map vm; +    po::store(po::parse_command_line(argc, argv, desc), vm); +    po::notify(vm); + +    //print the help message +    if (vm.count("help")){ +        std::cout << boost::format("UHD USRP Probe %s") % desc << std::endl; +        return ~0; +    } + +    if (vm.count("version")){ +        std::cout << uhd::get_version_string() << std::endl; +        return 0; +    } + +    device::sptr dev = device::make(vm["args"].as<std::string>()); +    property_tree::sptr tree = dev->get_tree(); + +    if (vm.count("string")){ +        std::cout << tree->access<std::string>(vm["string"].as<std::string>()).get() << std::endl; +        return 0; +    } + +    if (vm.count("tree") != 0) print_tree("/", tree); +    else std::cout << make_border(get_device_pp_string(tree)) << std::endl; + +    return 0; +} diff --git a/host/utils/usrp2_card_burner.py b/host/utils/usrp2_card_burner.py new file mode 100755 index 000000000..8e4a4f224 --- /dev/null +++ b/host/utils/usrp2_card_burner.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +import platform +import tempfile +import subprocess +try: +    import urllib.request +except ImportError: +    import urllib +    urllib.request = urllib +import optparse +import math +import os +import re + +######################################################################## +# constants +######################################################################## +SECTOR_SIZE = 512                 # bytes +MAX_FILE_SIZE =  1 * (2**20)      # maximum number of bytes we'll burn to a slot + +FPGA_OFFSET = 0                   # offset in flash to fpga image +FIRMWARE_OFFSET = 1 * (2**20)     # offset in flash to firmware image + +MAX_SD_CARD_SIZE = 2048e6         # bytes (any bigger is sdhc) + +######################################################################## +# helper functions +######################################################################## +def command(*args): +    p = subprocess.Popen( +        args, +        stdout=subprocess.PIPE, +        stderr=subprocess.STDOUT, +    ) +    ret = p.wait() +    verbose = p.stdout.read().decode('utf-8') +    if ret != 0: raise Exception(verbose) +    return verbose + +def get_dd_path(): +    if platform.system() == 'Windows': +        dd_path = os.path.join(os.path.dirname(__file__), 'dd.exe') +        if os.path.exists(dd_path): return dd_path +        dd_path = os.path.join(tempfile.gettempdir(), 'dd.exe') +        if not os.path.exists(dd_path): +            print('Downloading dd.exe to %s'%dd_path) +            dd_bin = urllib.request.urlopen('http://files.ettus.com/dd.exe').read() +            open(dd_path, 'wb').write(dd_bin) +        return dd_path +    return 'dd' + +def int_ceil_div(num, den): +    return int(math.ceil(float(num)/float(den))) + +def get_tmp_file(): +    tmp = tempfile.mkstemp() +    os.close(tmp[0]) +    return tmp[1] + +######################################################################## +# list possible devices +######################################################################## +def get_raw_device_hints(): +    #################################################################### +    # Platform Windows: parse the output of dd.exe --list +    #################################################################### +    if platform.system() == 'Windows': +        def extract_info_value(info, key): +            return info.split(key)[-1].split()[0] +        def get_info_list(output): +            in_info = False +            for line in output.splitlines(): +                if line.startswith('\\\\'): in_info = True; info = '' +                elif in_info and not line.strip(): in_info = False; yield info +                if in_info: info += '\n'+line.strip() +        def is_info_valid(info): +            try: +                if 'link to' not in info: return False +                #handles two spellings of remov(e)able: +                if 'remov' not in info.lower(): return False +                if 'size is' in info and int(extract_info_value(info, 'size is')) > MAX_SD_CARD_SIZE: return False +            except: return False +            return True +        def extract_info_name(info): +            for key in ('Mounted on', 'link to'): +                if key in info: return extract_info_value(info, key) +            return info.splitlines()[0].strip() + +        return sorted(set(map(extract_info_name, list(filter(is_info_valid, get_info_list(command(get_dd_path(), '--list'))))))) + +    #################################################################### +    # Platform Linux: parse procfs /proc/partitions +    #################################################################### +    if platform.system() == 'Linux': +        devs = list() +        for line in command('cat', '/proc/partitions').splitlines(): +            try: +                major, minor, blocks, name = line.split() +                if not name[-1].isdigit() and int(minor) == 0: continue +                if int(blocks)*1024 > MAX_SD_CARD_SIZE: continue +            except: continue +            devs.append(os.path.join('/dev', name)) + +        return sorted(set(devs)) + +    #################################################################### +    # Platform Mac OS X: parse diskutil list and info commands +    #################################################################### +    if platform.system() == 'Darwin': +        devs = [d.split()[0] for d in [l for l in command('diskutil', 'list').splitlines() if l.startswith('/dev')]] +        def output_to_info(output): +            return dict([list(map(lambda x: x.strip(), pair.lower().split(':'))) for pair in [l for l in output.splitlines() if ':' in l]]) +        def is_dev_valid(dev): +            info = output_to_info(command('diskutil', 'info', dev)) +            try: +                if 'internal' in info and info['internal'] == 'yes': return False +                if 'ejectable' in info and info['ejectable'] == 'no': return False +                if 'total size' in info: +                    size_match = re.match('^.*\((\d+)\s*bytes\).*$', info['total size']) +                    if size_match and int(size_match.groups()[0]) > MAX_SD_CARD_SIZE: return False +            except: return False +            return True + +        return sorted(set(filter(is_dev_valid, devs))) + +    #################################################################### +    # Platform Others: +    #################################################################### +    return () + +######################################################################## +# write and verify with dd +######################################################################## +def verify_image(image_file, device_file, offset): +    #create a temporary file to store the readback image +    tmp_file = get_tmp_file() + +    #read the image data +    img_data = open(image_file, 'rb').read() +    count = int_ceil_div(len(img_data), SECTOR_SIZE) + +    #execute a dd subprocess +    verbose = command( +        get_dd_path(), +        "of=%s"%tmp_file, +        "if=%s"%device_file, +        "skip=%d"%(offset/SECTOR_SIZE), +        "bs=%d"%SECTOR_SIZE, +        "count=%d"%count, +    ) + +    #verfy the data +    tmp_data = open(tmp_file, 'rb').read(len(img_data)) +    if img_data != tmp_data: return 'Verification Failed:\n%s'%verbose +    return 'Verification Passed:\n%s'%verbose + +def write_image(image_file, device_file, offset): +    #create a temporary file to store the padded image +    tmp_file = get_tmp_file() + +    #write the padded image data +    img_data = open(image_file, 'rb').read() +    count = int_ceil_div(len(img_data), SECTOR_SIZE) +    pad_len = SECTOR_SIZE*count - len(img_data) +    padding = bytes(b'\x00')*pad_len #zero-padding +    open(tmp_file, 'wb').write(img_data + padding) + +    #execute a dd subprocess +    verbose = command( +        get_dd_path(), +        "if=%s"%tmp_file, +        "of=%s"%device_file, +        "seek=%d"%(offset/SECTOR_SIZE), +        "bs=%d"%SECTOR_SIZE, +        "count=%d"%count, +    ) + +    try: #exec the sync command (only works on linux) +        if platform.system() == 'Linux': command('sync') +    except: pass + +    return verbose + +def write_and_verify(image_file, device_file, offset): +    if os.path.getsize(image_file) > MAX_FILE_SIZE: +        raise Exception('Image file larger than %d bytes!'%MAX_FILE_SIZE) +    return '%s\n%s'%( +        write_image( +            image_file=image_file, +            device_file=device_file, +            offset=offset, +        ), verify_image( +            image_file=image_file, +            device_file=device_file, +            offset=offset, +        ), +    ) + +def burn_sd_card(dev, fw, fpga): +    verbose = '' +    if fw: verbose += 'Burn firmware image:\n%s\n'%write_and_verify( +        image_file=fw, device_file=dev, offset=FIRMWARE_OFFSET +    ) +    if fpga: verbose += 'Burn fpga image:\n%s\n'%write_and_verify( +        image_file=fpga, device_file=dev, offset=FPGA_OFFSET +    ) +    return verbose + +######################################################################## +# command line options +######################################################################## +def get_options(): +    parser = optparse.OptionParser() +    parser.add_option("--dev",  type="string",       help="raw device path",                default='') +    parser.add_option("--fw",   type="string",       help="firmware image path (optional)", default='') +    parser.add_option("--fpga", type="string",       help="fpga image path (optional)",     default='') +    parser.add_option("--list", action="store_true", help="list possible raw devices",      default=False) +    parser.add_option("--force", action="store_true", help="override safety check",         default=False) +    (options, args) = parser.parse_args() + +    return options + +######################################################################## +# main +######################################################################## +if __name__=='__main__': +    options = get_options() +    device_hints = get_raw_device_hints() +    show_listing = options.list + +    if not show_listing and not options.force and options.dev and options.dev not in device_hints: +        print('The device "%s" was not in the list of possible raw devices.'%options.dev) +        print('The card burner application will now exit without burning your card.') +        print('To override this safety check, specify the --force option.\n') +        show_listing = True + +    if show_listing: +        print('Possible raw devices:') +        print('  ' + '\n  '.join(device_hints)) +        exit() + +    if not options.dev: raise Exception('no raw device path specified') +    print(burn_sd_card(dev=options.dev, fw=options.fw, fpga=options.fpga)) diff --git a/host/utils/usrp2_card_burner_gui.py b/host/utils/usrp2_card_burner_gui.py new file mode 100755 index 000000000..2941629b9 --- /dev/null +++ b/host/utils/usrp2_card_burner_gui.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +import usrp2_card_burner #import implementation +try: +    import tkinter, tkinter.filedialog, tkinter.font, tkinter.messagebox +except ImportError: +    import tkFileDialog, tkFont, tkMessageBox +    import Tkinter as tkinter +    tkinter.filedialog = tkFileDialog +    tkinter.font = tkFont +    tkinter.messagebox = tkMessageBox +import os + +class BinFileEntry(tkinter.Frame): +    """ +    Simple file entry widget for getting the file path of bin files. +    Combines a label, entry, and button with file dialog callback. +    """ + +    def __init__(self, root, what, def_path=''): +        self._what = what +        tkinter.Frame.__init__(self, root) +        tkinter.Label(self, text=what+":").pack(side=tkinter.LEFT) +        self._entry = tkinter.Entry(self, width=50) +        self._entry.insert(tkinter.END, def_path) +        self._entry.pack(side=tkinter.LEFT) +        tkinter.Button(self, text="...", command=self._button_cb).pack(side=tkinter.LEFT) + +    def _button_cb(self): +        filename = tkinter.filedialog.askopenfilename( +            parent=self, +            filetypes=[('bin files', '*.bin'), ('all files', '*.*')], +            title="Select bin file for %s"%self._what, +            initialdir=os.path.dirname(self.get_filename()), +        ) + +        # open file on your own +        if filename: +            self._entry.delete(0, tkinter.END) +            self._entry.insert(0, filename) + +    def get_filename(self): +        return self._entry.get() + +class DeviceEntryWidget(tkinter.Frame): +    """ +    Simple entry widget for getting the raw device name. +    Combines a label, entry, and helpful text box with hints. +    """ + +    def __init__(self, root, text=''): +        tkinter.Frame.__init__(self, root) + +        tkinter.Button(self, text="Rescan for Devices", command=self._reload_cb).pack() + +        self._hints = tkinter.Listbox(self) +        self._hints.bind("<<ListboxSelect>>", self._listbox_cb) +        self._reload_cb() +        self._hints.pack(expand=tkinter.YES, fill=tkinter.X) + +        frame = tkinter.Frame(self) +        frame.pack() + +        tkinter.Label(frame, text="Raw Device:").pack(side=tkinter.LEFT) +        self._entry = tkinter.Entry(frame, width=50) +        self._entry.insert(tkinter.END, text) +        self._entry.pack(side=tkinter.LEFT) + +    def _reload_cb(self): +        self._hints.delete(0, tkinter.END) +        for hint in usrp2_card_burner.get_raw_device_hints(): +            self._hints.insert(tkinter.END, hint) + +    def _listbox_cb(self, event): +        try: +            sel = self._hints.get(self._hints.curselection()[0]) +            self._entry.delete(0, tkinter.END) +            self._entry.insert(0, sel) +        except Exception as e: print(e) + +    def get_devname(self): +        return self._entry.get() + +class SectionLabel(tkinter.Label): +    """ +    Make a text label with bold font. +    """ + +    def __init__(self, root, text): +        tkinter.Label.__init__(self, root, text=text) + +        #set the font bold +        f = tkinter.font.Font(font=self['font']) +        f['weight'] = 'bold' +        self['font'] = f.name + +class USRP2CardBurnerApp(tkinter.Frame): +    """ +    The top level gui application for the usrp2 sd card burner. +    Creates entry widgets and button with callback to write images. +    """ + +    def __init__(self, root, dev, fw, fpga): + +        tkinter.Frame.__init__(self, root) + +        #pack the file entry widgets +        SectionLabel(self, text="Select Images").pack(pady=5) +        self._fw_img_entry = BinFileEntry(self, "Firmware Image", def_path=fw) +        self._fw_img_entry.pack() +        self._fpga_img_entry = BinFileEntry(self, "FPGA Image", def_path=fpga) +        self._fpga_img_entry.pack() + +        #pack the destination entry widget +        SectionLabel(self, text="Select Device").pack(pady=5) +        self._raw_dev_entry = DeviceEntryWidget(self, text=dev) +        self._raw_dev_entry.pack() + +        #the do it button +        SectionLabel(self, text="").pack(pady=5) +        tkinter.Label(self, text="Warning! This tool can overwrite your hard drive. Use with caution.").pack() +        tkinter.Button(self, text="Burn SD Card", command=self._burn).pack() + +    def _burn(self): +        #grab strings from the gui +        fw = self._fw_img_entry.get_filename() +        fpga = self._fpga_img_entry.get_filename() +        dev = self._raw_dev_entry.get_devname() + +        #check input +        if not dev: +            tkinter.messagebox.showerror('Error:', 'No device specified!') +            return +        if not fw and not fpga: +            tkinter.messagebox.showerror('Error:', 'No images specified!') +            return +        if fw and not os.path.exists(fw): +            tkinter.messagebox.showerror('Error:', 'Firmware image not found!') +            return +        if fpga and not os.path.exists(fpga): +            tkinter.messagebox.showerror('Error:', 'FPGA image not found!') +            return + +        #burn the sd card +        try: +            verbose = usrp2_card_burner.burn_sd_card(dev=dev, fw=fw, fpga=fpga) +            tkinter.messagebox.showinfo('Verbose:', verbose) +        except Exception as e: +            tkinter.messagebox.showerror('Verbose:', 'Error: %s'%str(e)) + +######################################################################## +# main +######################################################################## +if __name__=='__main__': +    options = usrp2_card_burner.get_options() +    root = tkinter.Tk() +    root.title('USRP2 SD Card Burner') +    USRP2CardBurnerApp(root, dev=options.dev, fw=options.fw, fpga=options.fpga).pack() +    root.mainloop() +    exit() diff --git a/host/utils/usrp2_recovery.py b/host/utils/usrp2_recovery.py new file mode 100755 index 000000000..c7578d3a0 --- /dev/null +++ b/host/utils/usrp2_recovery.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +""" +The usrp2 recovery app: + +When the usrp2 has an unknown or bad ip address in its eeprom, +it may not be possible to communicate with the usrp2 over ip/udp. + +This app will send a raw ethernet packet to bypass the ip layer. +The packet will contain a known ip address to burn into eeprom. +Because the recovery packet is sent with a broadcast mac address, +only one usrp2 should be present on the interface upon execution. + +This app requires super-user privileges and only works on linux.  +""" + +import socket +import struct +import optparse + +BCAST_MAC_ADDR = 'ff:ff:ff:ff:ff:ff' +RECOVERY_ETHERTYPE = 0xbeee +IP_RECOVERY_CODE = 'addr' + +def mac_addr_repr_to_binary_string(mac_addr): +    return ''.join([chr(int(x, 16)) for x in mac_addr.split(':')]) + +if __name__ == '__main__': +    parser = optparse.OptionParser(usage='usage: %prog [options]\n'+__doc__) +    parser.add_option('--ifc', type='string', help='ethernet interface name [default=%default]', default='eth0') +    parser.add_option('--new-ip', type='string', help='ip address to set [default=%default]', default='192.168.10.2') +    (options, args) = parser.parse_args() + +    #create the raw socket +    print("Opening raw socket on interface:", options.ifc) +    soc = socket.socket(socket.PF_PACKET, socket.SOCK_RAW) +    soc.bind((options.ifc, RECOVERY_ETHERTYPE)) + +    #create the recovery packet +    print("Loading packet with ip address:", options.new_ip) +    packet = struct.pack( +        '!6s6sH4s4s', +        mac_addr_repr_to_binary_string(BCAST_MAC_ADDR), +        mac_addr_repr_to_binary_string(BCAST_MAC_ADDR), +        RECOVERY_ETHERTYPE, +        IP_RECOVERY_CODE, +        socket.inet_aton(options.new_ip), +    ) + +    print("Sending packet (%d bytes)"%len(packet)) +    soc.send(packet) +    print("Done") diff --git a/host/utils/usrp_burn_db_eeprom.cpp b/host/utils/usrp_burn_db_eeprom.cpp new file mode 100644 index 000000000..b6b2dc4d6 --- /dev/null +++ b/host/utils/usrp_burn_db_eeprom.cpp @@ -0,0 +1,100 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + + +#include <uhd/utils/safe_main.hpp> +#include <uhd/device.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/assign.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ +    //command line variables +    std::string args, slot, unit; + +    po::options_description desc("Allowed options"); +    desc.add_options() +        ("help", "help message") +        ("args", po::value<std::string>(&args)->default_value(""),    "device address args [default = \"\"]") +        ("slot", po::value<std::string>(&slot)->default_value(""),    "dboard slot name [default is blank for automatic]") +        ("unit", po::value<std::string>(&unit)->default_value(""),    "which unit [RX, TX, or GDB]") +        ("id",   po::value<std::string>(),                            "dboard id to burn, omit for readback") +        ("ser",  po::value<std::string>(),                            "serial to burn, omit for readback") +        ("rev",  po::value<std::string>(),                            "revision to burn, omit for readback") +    ; + +    po::variables_map vm; +    po::store(po::parse_command_line(argc, argv, desc), vm); +    po::notify(vm); + +    //print the help message +    if (vm.count("help")){ +        std::cout << boost::format("USRP Burn Daughterboard EEPROM %s") % desc << std::endl; +        std::cout << boost::format( +            "Omit the ID argument to perform readback,\n" +            "Or specify a new ID to burn into the EEPROM.\n" +        ) << std::endl; +        return ~0; +    } + +    //make the device and extract the dboard w/ property +    device::sptr dev = device::make(args); +    uhd::property_tree::sptr tree = dev->get_tree(); +    const uhd::fs_path db_root = "/mboards/0/dboards"; +    std::vector<std::string> dboard_names = tree->list(db_root); +    if (dboard_names.size() == 1 and slot.empty()) slot = dboard_names.front(); +    uhd::assert_has(dboard_names, slot, "dboard slot name"); + +    std::cout << boost::format("Reading %s EEPROM on %s dboard...") % unit % slot << std::endl; +    boost::to_lower(unit); +    const uhd::fs_path db_path = db_root / slot / (unit + "_eeprom"); +    dboard_eeprom_t db_eeprom = tree->access<dboard_eeprom_t>(db_path).get(); + +    //------------- handle the dboard ID -----------------------------// +    if (vm.count("id")){ +        db_eeprom.id = dboard_id_t::from_string(vm["id"].as<std::string>()); +        tree->access<dboard_eeprom_t>(db_path).set(db_eeprom); +    } +    std::cout << boost::format("  Current ID: %s") % db_eeprom.id.to_pp_string() << std::endl; + +    //------------- handle the dboard serial--------------------------// +    if (vm.count("ser")){ +        db_eeprom.serial = vm["ser"].as<std::string>(); +        tree->access<dboard_eeprom_t>(db_path).set(db_eeprom); +    } +    std::cout << boost::format("  Current serial: \"%s\"") % db_eeprom.serial << std::endl; + +    //------------- handle the dboard revision------------------------// +    if (vm.count("rev")){ +        db_eeprom.revision = vm["rev"].as<std::string>(); +        tree->access<dboard_eeprom_t>(db_path).set(db_eeprom); +    } +    std::cout << boost::format("  Current revision: \"%s\"") % db_eeprom.revision << std::endl; + +    std::cout << "  Done" << std::endl << std::endl; +    return 0; +} diff --git a/host/utils/usrp_burn_mb_eeprom.cpp b/host/utils/usrp_burn_mb_eeprom.cpp new file mode 100644 index 000000000..ca9a6c8ba --- /dev/null +++ b/host/utils/usrp_burn_mb_eeprom.cpp @@ -0,0 +1,78 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/safe_main.hpp> +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ +    std::string args, key, val; + +    po::options_description desc("Allowed options"); +    desc.add_options() +        ("help", "help message") +        ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") +        ("key", po::value<std::string>(&key), "the indentifier for a value in EEPROM") +        ("val", po::value<std::string>(&val), "the new value to set, omit for readback") +    ; + +    po::variables_map vm; +    po::store(po::parse_command_line(argc, argv, desc), vm); +    po::notify(vm); + +    //print the help message +    if (vm.count("help") or not vm.count("key")){ +        std::cout << boost::format("USRP Burn Motherboard EEPROM %s") % desc << std::endl; +        std::cout << boost::format( +            "Omit the value argument to perform a readback,\n" +            "Or specify a new value to burn into the EEPROM.\n" +        ) << std::endl; +        return ~0; +    } + +    std::cout << "Creating USRP device from address: " + args << std::endl; +    uhd::device::sptr dev = uhd::device::make(args); +    uhd::property_tree::sptr tree = dev->get_tree(); +    std::cout << std::endl; + +    if (true /*always readback*/){ +        std::cout << "Fetching current settings from EEPROM..." << std::endl; +        uhd::usrp::mboard_eeprom_t mb_eeprom = tree->access<uhd::usrp::mboard_eeprom_t>("/mboards/0/eeprom").get(); +        if (not mb_eeprom.has_key(key)){ +            std::cerr << boost::format("Cannot find value for EEPROM[%s]") % key << std::endl; +            return ~0; +        } +        std::cout << boost::format("    EEPROM [\"%s\"] is \"%s\"") % key % mb_eeprom[key] << std::endl; +        std::cout << std::endl; +    } +    if (vm.count("val")){ +        uhd::usrp::mboard_eeprom_t mb_eeprom; mb_eeprom[key] = val; +        std::cout << boost::format("Setting EEPROM [\"%s\"] to \"%s\"...") % key % val << std::endl; +        tree->access<uhd::usrp::mboard_eeprom_t>("/mboards/0/eeprom").set(mb_eeprom); +        std::cout << "Power-cycle the USRP device for the changes to take effect." << std::endl; +        std::cout << std::endl; +    } + +    std::cout << "Done" << std::endl; +    return 0; +} diff --git a/host/utils/usrp_cal_utils.hpp b/host/utils/usrp_cal_utils.hpp new file mode 100644 index 000000000..bda6fc31b --- /dev/null +++ b/host/utils/usrp_cal_utils.hpp @@ -0,0 +1,255 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/paths.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/utils/paths.hpp> +#include <boost/filesystem.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <vector> +#include <complex> +#include <cmath> +#include <cstdlib> +#include <fstream> + +namespace fs = boost::filesystem; + +struct result_t{double freq, real_corr, imag_corr, best, delta;}; + +typedef std::complex<float> samp_type; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const double tau = 6.28318531; +static const size_t wave_table_len = 8192; +static const size_t num_search_steps = 5; +static const size_t num_search_iters = 7; +static const double default_freq_step = 7.3e6; +static const size_t default_num_samps = 10000; + +/*********************************************************************** + * Set standard defaults for devices + **********************************************************************/ +static inline void set_optimum_defaults(uhd::usrp::multi_usrp::sptr usrp){ +    uhd::property_tree::sptr tree = usrp->get_device()->get_tree(); + +    const uhd::fs_path mb_path = "/mboards/0"; +    const std::string mb_name = tree->access<std::string>(mb_path / "name").get(); +    if (mb_name.find("USRP2") != std::string::npos or mb_name.find("N200") != std::string::npos or mb_name.find("N210") != std::string::npos){ +        usrp->set_tx_rate(12.5e6); +        usrp->set_rx_rate(12.5e6); +    } +    else if (mb_name.find("B100") != std::string::npos){ +        usrp->set_tx_rate(4e6); +        usrp->set_rx_rate(4e6); +    } +    else if (mb_name.find("E100") != std::string::npos or mb_name.find("E110") != std::string::npos){ +        usrp->set_tx_rate(4e6); +        usrp->set_rx_rate(8e6); +    } +    else{ +        throw std::runtime_error("self-calibration is not supported for this hardware"); +    } + +    const uhd::fs_path tx_fe_path = "/mboards/0/dboards/A/tx_frontends/0"; +    const std::string tx_name = tree->access<std::string>(tx_fe_path / "name").get(); +    if (tx_name.find("WBX") != std::string::npos){ +        usrp->set_tx_gain(0); +    } +    else if (tx_name.find("SBX") != std::string::npos){ +        usrp->set_tx_gain(0); +    } +    else if (tx_name.find("RFX") != std::string::npos){ +        usrp->set_tx_gain(0); +    } +    else{ +        throw std::runtime_error("self-calibration is not supported for this hardware"); +    } + +    const uhd::fs_path rx_fe_path = "/mboards/0/dboards/A/rx_frontends/0"; +    const std::string rx_name = tree->access<std::string>(rx_fe_path / "name").get(); +    if (rx_name.find("WBX") != std::string::npos){ +        usrp->set_rx_gain(25); +    } +    else if (rx_name.find("SBX") != std::string::npos){ +        usrp->set_rx_gain(25); +    } +    else if (rx_name.find("RFX") != std::string::npos){ +        usrp->set_rx_gain(25); +    } +    else{ +        throw std::runtime_error("self-calibration is not supported for this hardware"); +    } + +} + +/*********************************************************************** + * Check for empty serial + **********************************************************************/ + +void check_for_empty_serial( +    uhd::usrp::multi_usrp::sptr usrp, +    std::string XX, +    std::string xx, +    std::string uhd_args +){ + +    //extract eeprom +    uhd::property_tree::sptr tree = usrp->get_device()->get_tree(); +    const uhd::fs_path db_path = "/mboards/0/dboards/A/" + xx + "_eeprom"; +    const uhd::usrp::dboard_eeprom_t db_eeprom = tree->access<uhd::usrp::dboard_eeprom_t>(db_path).get(); + +    std::string args_str = ""; +    if(uhd_args != "") args_str = str(boost::format(" --args=%s") % uhd_args); + +    std::string error_string = str(boost::format("This %s dboard has no serial!\n\nPlease see the Calibration documentation for details on how to fix this.") % XX); + +    if (db_eeprom.serial.empty()) throw std::runtime_error(error_string); +} + +/*********************************************************************** + * Sinusoid wave table + **********************************************************************/ +class wave_table{ +public: +    wave_table(const double ampl){ +        _table.resize(wave_table_len); +        for (size_t i = 0; i < wave_table_len; i++){ +            _table[i] = samp_type(std::polar(ampl, (tau*i)/wave_table_len)); +        } +    } + +    inline samp_type operator()(const size_t index) const{ +        return _table[index % wave_table_len]; +    } + +private: +    std::vector<samp_type > _table; +}; + +/*********************************************************************** + * Compute power of a tone + **********************************************************************/ +static inline double compute_tone_dbrms( +    const std::vector<samp_type > &samples, +    const double freq //freq is fractional +){ +    //shift the samples so the tone at freq is down at DC +    //and average the samples to measure the DC component +    samp_type average = 0; +    for (size_t i = 0; i < samples.size(); i++){ +        average += samp_type(std::polar(1.0, -freq*tau*i)) * samples[i]; +    } + +    return 20*std::log10(std::abs(average/float(samples.size()))); +} + +/*********************************************************************** + * Write a dat file + **********************************************************************/ +static inline void write_samples_to_file( +    const std::vector<samp_type > &samples, const std::string &file +){ +    std::ofstream outfile(file.c_str(), std::ofstream::binary); +    outfile.write((const char*)&samples.front(), samples.size()*sizeof(samp_type)); +    outfile.close(); +} + +/*********************************************************************** + * Store data to file + **********************************************************************/ +static void store_results( +    uhd::usrp::multi_usrp::sptr usrp, +    const std::vector<result_t> &results, +    const std::string &XX, +    const std::string &xx, +    const std::string &what +){ +    //extract eeprom serial +    uhd::property_tree::sptr tree = usrp->get_device()->get_tree(); +    const uhd::fs_path db_path = "/mboards/0/dboards/A/" + xx + "_eeprom"; +    const uhd::usrp::dboard_eeprom_t db_eeprom = tree->access<uhd::usrp::dboard_eeprom_t>(db_path).get(); + +    //make the calibration file path +    fs::path cal_data_path = fs::path(uhd::get_app_path()) / ".uhd"; +    fs::create_directory(cal_data_path); +    cal_data_path = cal_data_path / "cal"; +    fs::create_directory(cal_data_path); +    cal_data_path = cal_data_path / str(boost::format("%s_%s_cal_v0.2_%s.csv") % xx % what % db_eeprom.serial); +    if (fs::exists(cal_data_path)){ +        fs::rename(cal_data_path, cal_data_path.string() + str(boost::format(".%d") % time(NULL))); +    } + +    //fill the calibration file +    std::ofstream cal_data(cal_data_path.string().c_str()); +    cal_data << boost::format("name, %s Frontend Calibration\n") % XX; +    cal_data << boost::format("serial, %s\n") % db_eeprom.serial; +    cal_data << boost::format("timestamp, %d\n") % time(NULL); +    cal_data << boost::format("version, 0, 1\n"); +    cal_data << boost::format("DATA STARTS HERE\n"); +    cal_data << "lo_frequency, correction_real, correction_imag, measured, delta\n"; + +    for (size_t i = 0; i < results.size(); i++){ +        cal_data +            << results[i].freq << ", " +            << results[i].real_corr << ", " +            << results[i].imag_corr << ", " +            << results[i].best << ", " +            << results[i].delta << "\n" +        ; +    } + +    std::cout << "wrote cal data to " << cal_data_path << std::endl; +} + +/*********************************************************************** + * Data capture routine + **********************************************************************/ +static void capture_samples( +    uhd::usrp::multi_usrp::sptr usrp, +    uhd::rx_streamer::sptr rx_stream, +    std::vector<samp_type > &buff, +    const size_t nsamps_requested +){ +    buff.resize(nsamps_requested); +    uhd::rx_metadata_t md; + +    uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); +    stream_cmd.num_samps = buff.size(); +    stream_cmd.stream_now = true; +    usrp->issue_stream_cmd(stream_cmd); +    const size_t num_rx_samps = rx_stream->recv(&buff.front(), buff.size(), md); + +    //validate the received data +    if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE){ +        throw std::runtime_error(str(boost::format( +            "Unexpected error code 0x%x" +        ) % md.error_code)); +    } +    //we can live if all the data didnt come in +    if (num_rx_samps > buff.size()/2){ +        buff.resize(num_rx_samps); +        return; +    } +    if (num_rx_samps != buff.size()){ +        throw std::runtime_error("did not get all the samples requested"); +    } +} diff --git a/host/utils/usrp_n2xx_net_burner.py b/host/utils/usrp_n2xx_net_burner.py new file mode 100755 index 000000000..8f16de501 --- /dev/null +++ b/host/utils/usrp_n2xx_net_burner.py @@ -0,0 +1,528 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +# TODO: make it autodetect UHD devices + +import optparse +import math +import os +import re +import struct +import socket +import sys +import time +import platform +import subprocess + +######################################################################## +# constants +######################################################################## +UDP_FW_UPDATE_PORT = 49154 +UDP_MAX_XFER_BYTES = 1024 +UDP_TIMEOUT = 3 +UDP_POLL_INTERVAL = 0.10 #in seconds + +USRP2_FW_PROTO_VERSION = 7 #should be unused after r6 + +#from bootloader_utils.h + +FPGA_IMAGE_SIZE_BYTES = 1572864 +FW_IMAGE_SIZE_BYTES = 31744 +SAFE_FPGA_IMAGE_LOCATION_ADDR = 0x00000000 +SAFE_FW_IMAGE_LOCATION_ADDR = 0x003F0000 +PROD_FPGA_IMAGE_LOCATION_ADDR = 0x00180000 +PROD_FW_IMAGE_LOCATION_ADDR = 0x00300000 + +FLASH_DATA_PACKET_SIZE = 256 + +#see fw_common.h +FLASH_ARGS_FMT = '!LLLLL256s' +FLASH_INFO_FMT = '!LLLLL256x' +FLASH_IP_FMT =   '!LLLL260x' +FLASH_HW_REV_FMT = '!LLLL260x' + +n2xx_revs = { +             0x0a00: ["n200_r3", "n200_r2"], +             0x0a10: ["n200_r4"], +             0x0a01: ["n210_r3", "n210_r2"], +             0x0a11: ["n210_r4"] +            } + +class update_id_t: +  USRP2_FW_UPDATE_ID_WAT = ord(' ') +  USRP2_FW_UPDATE_ID_OHAI_LOL = ord('a') +  USRP2_FW_UPDATE_ID_OHAI_OMG = ord('A') +  USRP2_FW_UPDATE_ID_WATS_TEH_FLASH_INFO_LOL = ord('f') +  USRP2_FW_UPDATE_ID_HERES_TEH_FLASH_INFO_OMG = ord('F') +  USRP2_FW_UPDATE_ID_ERASE_TEH_FLASHES_LOL = ord('e') +  USRP2_FW_UPDATE_ID_ERASING_TEH_FLASHES_OMG = ord('E') +  USRP2_FW_UPDATE_ID_R_U_DONE_ERASING_LOL = ord('d') +  USRP2_FW_UPDATE_ID_IM_DONE_ERASING_OMG = ord('D') +  USRP2_FW_UPDATE_ID_NOPE_NOT_DONE_ERASING_OMG = ord('B') +  USRP2_FW_UPDATE_ID_WRITE_TEH_FLASHES_LOL = ord('w') +  USRP2_FW_UPDATE_ID_WROTE_TEH_FLASHES_OMG = ord('W') +  USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL = ord('r') +  USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG = ord('R') +  USRP2_FW_UPDATE_ID_RESET_MAH_COMPUTORZ_LOL = ord('s') +  USRP2_FW_UPDATE_ID_RESETTIN_TEH_COMPUTORZ_OMG = ord('S') +  USRP2_FW_UPDATE_ID_I_CAN_HAS_HW_REV_LOL = ord('v') +  USRP2_FW_UPDATE_ID_HERES_TEH_HW_REV_OMG = ord('V') +  USRP2_FW_UPDATE_ID_KTHXBAI = ord('~') + +_seq = -1 +def seq(): +    global _seq +    _seq = _seq+1 +    return _seq + +######################################################################## +# helper functions +######################################################################## +def unpack_flash_args_fmt(s): +    return struct.unpack(FLASH_ARGS_FMT, s) #(proto_ver, pktid, seq, flash_addr, length, data) + +def unpack_flash_info_fmt(s): +    return struct.unpack(FLASH_INFO_FMT, s) #(proto_ver, pktid, seq, sector_size_bytes, memory_size_bytes) + +def unpack_flash_ip_fmt(s): +    return struct.unpack(FLASH_IP_FMT, s) #(proto_ver, pktid, seq, ip_addr) + +def unpack_flash_hw_rev_fmt(s): +    return struct.unpack(FLASH_HW_REV_FMT, s) #proto_ver, pktid, seq, hw_rev + +def pack_flash_args_fmt(proto_ver, pktid, seq, flash_addr, length, data=bytes()): +    return struct.pack(FLASH_ARGS_FMT, proto_ver, pktid, seq, flash_addr, length, data) + +def pack_flash_info_fmt(proto_ver, pktid, seq, sector_size_bytes, memory_size_bytes): +    return struct.pack(FLASH_INFO_FMT, proto_ver, pktid, seq, sector_size_bytes, memory_size_bytes) + +def pack_flash_hw_rev_fmt(proto_ver, pktid, seq, hw_rev): +    return struct.pack(FLASH_HW_REV_FMT, proto_ver, pktid, seq, hw_rev) + +def is_valid_fpga_image(fpga_image): +    for i in range(0,63): +        if fpga_image[i:i+1] == bytes(b'\xFF'): continue +        if fpga_image[i:i+2] == bytes(b'\xAA\x99'): return True +    return False + +def is_valid_fw_image(fw_image): +    return fw_image[:4] == bytes(b'\x0B\x0B\x0B\x0B') + + +######################################################################## +# interface discovery and device enumeration +######################################################################## +def command(*args): +    p = subprocess.Popen( +        args, +        stdout=subprocess.PIPE, +        stderr=subprocess.STDOUT, +    ) +    ret = p.wait() +    verbose = p.stdout.read().decode('utf-8') +    if ret != 0: raise Exception(verbose) +    return verbose + +def get_interfaces(): +    if(platform.system() is "Windows"): return win_get_interfaces() +    else: return unix_get_interfaces() + +def unix_get_interfaces(): +    ifconfig = command("/sbin/ifconfig") +    ip_addr_re = "cast\D*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" +    bcasts = re.findall(ip_addr_re, ifconfig) +    return bcasts + +def win_get_interfaces(): +    from ctypes import Structure, windll, sizeof +    from ctypes import POINTER, byref +    from ctypes import c_ulong, c_uint, c_ubyte, c_char +    MAX_ADAPTER_DESCRIPTION_LENGTH = 128 +    MAX_ADAPTER_NAME_LENGTH = 256 +    MAX_ADAPTER_ADDRESS_LENGTH = 8 +    class IP_ADDR_STRING(Structure): +        pass +    LP_IP_ADDR_STRING = POINTER(IP_ADDR_STRING) +    IP_ADDR_STRING._fields_ = [ +        ("next", LP_IP_ADDR_STRING), +        ("ipAddress", c_char * 16), +        ("ipMask", c_char * 16), +        ("context", c_ulong)] +    class IP_ADAPTER_INFO (Structure): +        pass +    LP_IP_ADAPTER_INFO = POINTER(IP_ADAPTER_INFO) +    IP_ADAPTER_INFO._fields_ = [ +        ("next", LP_IP_ADAPTER_INFO), +        ("comboIndex", c_ulong), +        ("adapterName", c_char * (MAX_ADAPTER_NAME_LENGTH + 4)), +        ("description", c_char * (MAX_ADAPTER_DESCRIPTION_LENGTH + 4)), +        ("addressLength", c_uint), +        ("address", c_ubyte * MAX_ADAPTER_ADDRESS_LENGTH), +        ("index", c_ulong), +        ("type", c_uint), +        ("dhcpEnabled", c_uint), +        ("currentIpAddress", LP_IP_ADDR_STRING), +        ("ipAddressList", IP_ADDR_STRING), +        ("gatewayList", IP_ADDR_STRING), +        ("dhcpServer", IP_ADDR_STRING), +        ("haveWins", c_uint), +        ("primaryWinsServer", IP_ADDR_STRING), +        ("secondaryWinsServer", IP_ADDR_STRING), +        ("leaseObtained", c_ulong), +        ("leaseExpires", c_ulong)] +    GetAdaptersInfo = windll.iphlpapi.GetAdaptersInfo +    GetAdaptersInfo.restype = c_ulong +    GetAdaptersInfo.argtypes = [LP_IP_ADAPTER_INFO, POINTER(c_ulong)] +    adapterList = (IP_ADAPTER_INFO * 10)() +    buflen = c_ulong(sizeof(adapterList)) +    rc = GetAdaptersInfo(byref(adapterList[0]), byref(buflen)) +    if rc == 0: +        for a in adapterList: +            adNode = a.ipAddressList +            while True: +                #convert ipAddr and ipMask into hex addrs that can be turned into a bcast addr +                try: +                    ipAddr = adNode.ipAddress.decode() +                    ipMask = adNode.ipMask.decode() +                except: ipAddr = None +                if ipAddr and ipMask: +                    hexAddr = struct.unpack("<L", socket.inet_aton(ipAddr))[0] +                    hexMask = struct.unpack("<L", socket.inet_aton(ipMask))[0] +                    if(hexAddr and hexMask): #don't broadcast on 255.255.255.255, that's just lame +                        yield socket.inet_ntoa(struct.pack("<L", (hexAddr & hexMask) | (~hexMask) & 0xFFFFFFFF)) +                try: adNode = adNode.next +                except: break +                if not adNode: break + +def enumerate_devices(): +    for bcast_addr in get_interfaces(): +        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) +        sock.settimeout(0.1) +        out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_OHAI_LOL, 0, 0, 0) +        sock.sendto(out_pkt, (bcast_addr, UDP_FW_UPDATE_PORT)) +        still_goin = True +        while(still_goin): +            try: +                pkt = sock.recv(UDP_MAX_XFER_BYTES) +                (proto_ver, pktid, rxseq, ip_addr) = unpack_flash_ip_fmt(pkt) +                if(pktid == update_id_t.USRP2_FW_UPDATE_ID_OHAI_OMG): +                    use_addr = socket.inet_ntoa(struct.pack("<L", socket.ntohl(ip_addr))) +                    burner = burner_socket(use_addr, True) +                    yield "%s (%s)" % (socket.inet_ntoa(struct.pack("<L", socket.ntohl(ip_addr))), n2xx_revs[burner.get_hw_rev()][0]) +            except socket.timeout: +                still_goin = False + +######################################################################## +# Burner class, holds a socket and send/recv routines +######################################################################## +class burner_socket(object): +    def __init__(self, addr, quiet): +        self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +        self._quiet = quiet +        self._sock.settimeout(UDP_TIMEOUT) +        self._sock.connect((addr, UDP_FW_UPDATE_PORT)) +        self.set_callbacks(lambda *a: None, lambda *a: None) +        self.init_update(quiet) #check that the device is there +        self.get_hw_rev() + +    def set_callbacks(self, progress_cb, status_cb): +        self._progress_cb = progress_cb +        self._status_cb = status_cb + +    def send_and_recv(self, pkt): +        self._sock.send(pkt) +        return self._sock.recv(UDP_MAX_XFER_BYTES) + +    #just here to validate comms +    def init_update(self,quiet): +        out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_OHAI_LOL, seq(), 0, 0) +        try: in_pkt = self.send_and_recv(out_pkt) +        except socket.timeout: raise Exception("No response from device") +        (proto_ver, pktid, rxseq, ip_addr) = unpack_flash_ip_fmt(in_pkt) +        if pktid == update_id_t.USRP2_FW_UPDATE_ID_OHAI_OMG: +            if not quiet: print("USRP-N2XX found.") +        else: +            raise Exception("Invalid reply received from device.") + +    def get_hw_rev(self): +        out_pkt = pack_flash_hw_rev_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_I_CAN_HAS_HW_REV_LOL, seq(), 0) +        in_pkt = self.send_and_recv(out_pkt) +        (proto_ver, pktid, rxseq, hw_rev) = unpack_flash_hw_rev_fmt(in_pkt) +        if(pktid != update_id_t.USRP2_FW_UPDATE_ID_HERES_TEH_HW_REV_OMG): hw_rev = 0 +        return socket.ntohs(hw_rev) + +    memory_size_bytes = 0 +    sector_size_bytes = 0 +    def get_flash_info(self): +        if (self.memory_size_bytes != 0) and (self.sector_size_bytes != 0): +            return (self.memory_size_bytes, self.sector_size_bytes) + +        out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_WATS_TEH_FLASH_INFO_LOL, seq(), 0, 0) +        in_pkt = self.send_and_recv(out_pkt) + +        (proto_ver, pktid, rxseq, self.sector_size_bytes, self.memory_size_bytes) = unpack_flash_info_fmt(in_pkt) + +        if pktid != update_id_t.USRP2_FW_UPDATE_ID_HERES_TEH_FLASH_INFO_OMG: +            raise Exception("Invalid reply %c from device." % (chr(pktid))) + +        return (self.memory_size_bytes, self.sector_size_bytes) + +    def burn_fw(self, fw, fpga, reset, safe, check_rev=True): +        (flash_size, sector_size) = self.get_flash_info() +        hw_rev = self.get_hw_rev() + +        if hw_rev in n2xx_revs: print("Hardware type: %s" % n2xx_revs[hw_rev][0]) +        print("Flash size: %i\nSector size: %i\n" % (flash_size, sector_size)) + +        if fpga: +            #validate fpga image name against hardware rev +            if(check_rev and hw_rev != 0 and not any(name in fpga for name in n2xx_revs[hw_rev])): +                raise Exception("Error: incorrect FPGA image version. Please use the correct image for device %s" % n2xx_revs[hw_rev][0]) + +            if safe: image_location = SAFE_FPGA_IMAGE_LOCATION_ADDR +            else:    image_location = PROD_FPGA_IMAGE_LOCATION_ADDR + +            fpga_file = open(fpga, 'rb') +            fpga_image = fpga_file.read() + +            if len(fpga_image) > FPGA_IMAGE_SIZE_BYTES: +                raise Exception("Error: FPGA image file too large.") + +            if not is_valid_fpga_image(fpga_image): +                raise Exception("Error: Invalid FPGA image file.") + +            if (len(fpga_image) + image_location) > flash_size: +                raise Exception("Error: Cannot write past end of device") + +            print("Begin FPGA write: this should take about 1 minute...") +            start_time = time.time() +            self.erase_image(image_location, FPGA_IMAGE_SIZE_BYTES) +            self.write_image(fpga_image, image_location) +            self.verify_image(fpga_image, image_location) +            print("Time elapsed: %f seconds"%(time.time() - start_time)) +            print("\n\n") + +        if fw: +            if safe: image_location = SAFE_FW_IMAGE_LOCATION_ADDR +            else:    image_location = PROD_FW_IMAGE_LOCATION_ADDR + +            fw_file = open(fw, 'rb') +            fw_image = fw_file.read() + +            if len(fw_image) > FW_IMAGE_SIZE_BYTES: +                raise Exception("Error: Firmware image file too large.") + +            if not is_valid_fw_image(fw_image): +                raise Exception("Error: Invalid firmware image file.") + +            if (len(fw_image) + image_location) > flash_size: +                raise Exception("Error: Cannot write past end of device") + +            print("Begin firmware write: this should take about 1 second...") +            start_time = time.time() +            self.erase_image(image_location, FW_IMAGE_SIZE_BYTES) +            self.write_image(fw_image, image_location) +            self.verify_image(fw_image, image_location) +            print("Time elapsed: %f seconds"%(time.time() - start_time)) +            print("\n\n") + +        if reset: self.reset_usrp() + +    def write_image(self, image, addr): +        print("Writing image") +        self._status_cb("Writing") +        writedata = image +        #we split the image into smaller (256B) bits and send them down the wire +        (mem_size, sector_size) = self.get_flash_info() +        if (addr + len(writedata)) > mem_size: +            raise Exception("Error: Cannot write past end of device") + +        while writedata: +            out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_WRITE_TEH_FLASHES_LOL, seq(), addr, FLASH_DATA_PACKET_SIZE, writedata[:FLASH_DATA_PACKET_SIZE]) +            in_pkt = self.send_and_recv(out_pkt) + +            (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + +            if pktid != update_id_t.USRP2_FW_UPDATE_ID_WROTE_TEH_FLASHES_OMG: +              raise Exception("Invalid reply %c from device." % (chr(pktid))) + +            writedata = writedata[FLASH_DATA_PACKET_SIZE:] +            addr += FLASH_DATA_PACKET_SIZE +            self._progress_cb(float(len(image)-len(writedata))/len(image)) + +    def verify_image(self, image, addr): +        print("Verifying data") +        self._status_cb("Verifying") +        readsize = len(image) +        readdata = bytes() +        while readsize > 0: +            if readsize < FLASH_DATA_PACKET_SIZE: thisreadsize = readsize +            else: thisreadsize = FLASH_DATA_PACKET_SIZE +            out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL, seq(), addr, thisreadsize) +            in_pkt = self.send_and_recv(out_pkt) + +            (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + +            if pktid != update_id_t.USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG: +              raise Exception("Invalid reply %c from device." % (chr(pktid))) + +            readdata += data[:thisreadsize] +            readsize -= FLASH_DATA_PACKET_SIZE +            addr += FLASH_DATA_PACKET_SIZE +            self._progress_cb(float(len(readdata))/len(image)) + +        print("Read back %i bytes" % len(readdata)) +        #  print readdata + +        #  for i in range(256, 512): +        #    print "out: %i in: %i" % (ord(image[i]), ord(readdata[i])) + +        if readdata != image: +            raise Exception("Verify failed. Image did not write correctly.") +        else: +            print("Success.") + +    def read_image(self, image, size, addr): +        print("Reading image") +        readsize = size +        readdata = str() +        while readsize > 0: +            if readsize < FLASH_DATA_PACKET_SIZE: thisreadsize = readsize +            else: thisreadsize = FLASH_DATA_PACKET_SIZE +            out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL, seq(), addr, thisreadsize) +            in_pkt = self.send_and_recv(out_pkt) + +            (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + +            if pktid != update_id_t.USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG: +              raise Exception("Invalid reply %c from device." % (chr(pktid))) + +            readdata += data[:thisreadsize] +            readsize -= FLASH_DATA_PACKET_SIZE +            addr += FLASH_DATA_PACKET_SIZE + +        print("Read back %i bytes" % len(readdata)) + +        #write to disk +        f = open(image, 'w') +        f.write(readdata) +        f.close() + +    def reset_usrp(self): +        out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_RESET_MAH_COMPUTORZ_LOL, seq(), 0, 0) +        try: in_pkt = self.send_and_recv(out_pkt) +        except socket.timeout: return + +        (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) +        if pktid == update_id_t.USRP2_FW_UPDATE_ID_RESETTIN_TEH_COMPUTORZ_OMG: +            raise Exception("Device failed to reset.") + +    def erase_image(self, addr, length): +        self._status_cb("Erasing") +        #get flash info first +        (flash_size, sector_size) = self.get_flash_info() +        if (addr + length) > flash_size: +            raise Exception("Cannot erase past end of device") + +        out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_ERASE_TEH_FLASHES_LOL, seq(), addr, length) +        in_pkt = self.send_and_recv(out_pkt) + +        (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + +        if pktid != update_id_t.USRP2_FW_UPDATE_ID_ERASING_TEH_FLASHES_OMG: +            raise Exception("Invalid reply %c from device." % (chr(pktid))) + +        print("Erasing %i bytes at %i" % (length, addr)) +        start_time = time.time() + +        #now wait for it to finish +        while(True): +            out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_R_U_DONE_ERASING_LOL, seq(), 0, 0) +            in_pkt = self.send_and_recv(out_pkt) + +            (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + +            if pktid == update_id_t.USRP2_FW_UPDATE_ID_IM_DONE_ERASING_OMG: break +            elif pktid != update_id_t.USRP2_FW_UPDATE_ID_NOPE_NOT_DONE_ERASING_OMG: +                raise Exception("Invalid reply %c from device." % (chr(pktid))) +            time.sleep(0.01) #decrease network overhead by waiting a bit before polling +            self._progress_cb(min(1.0, (time.time() - start_time)/(length/80e3))) + + +######################################################################## +# command line options +######################################################################## +def get_options(): +    parser = optparse.OptionParser() +    parser.add_option("--addr", type="string",                 help="USRP-N2XX device address",       default='') +    parser.add_option("--fw",   type="string",                 help="firmware image path (optional)", default='') +    parser.add_option("--fpga", type="string",                 help="fpga image path (optional)",     default='') +    parser.add_option("--reset", action="store_true",          help="reset the device after writing", default=False) +    parser.add_option("--read", action="store_true",           help="read to file instead of write from file", default=False) +    parser.add_option("--overwrite-safe", action="store_true", help="never ever use this option", default=False) +    parser.add_option("--dont-check-rev", action="store_true", help="disable revision checks", default=False) +    parser.add_option("--list", action="store_true",           help="list possible network devices", default=False) +    (options, args) = parser.parse_args() + +    return options + +######################################################################## +# main +######################################################################## +if __name__=='__main__': +    options = get_options() + +    if options.list: +        print('Possible network devices:') +        print('  ' + '\n  '.join(enumerate_devices())) +        #enumerate_devices() +        exit() + +    if not options.addr: raise Exception('no address specified') + +    if not options.fpga and not options.fw and not options.reset: raise Exception('Must specify either a firmware image or FPGA image, and/or reset.') + +    if options.overwrite_safe and not options.read: +        print("Are you REALLY, REALLY sure you want to overwrite the safe image? This is ALMOST ALWAYS a terrible idea.") +        print("If your image is faulty, your USRP2+ will become a brick until reprogrammed via JTAG.") +        response = raw_input("""Type "yes" to continue, or anything else to quit: """) +        if response != "yes": sys.exit(0) + +    burner = burner_socket(addr=options.addr,quiet=False) + +    if options.read: +        if options.fw: +            file = options.fw +            if os.path.isfile(file): +                response = raw_input("File already exists -- overwrite? (y/n) ") +                if response != "y": sys.exit(0) +            size = FW_IMAGE_SIZE_BYTES +            addr = SAFE_FW_IMAGE_LOCATION_ADDR if options.overwrite_safe else PROD_FW_IMAGE_LOCATION_ADDR +            burner.read_image(file, size, addr) + +        if options.fpga: +            file = options.fpga +            if os.path.isfile(file): +                response = input("File already exists -- overwrite? (y/n) ") +                if response != "y": sys.exit(0) +            size = FPGA_IMAGE_SIZE_BYTES +            addr = SAFE_FPGA_IMAGE_LOCATION_ADDR if options.overwrite_safe else PROD_FPGA_IMAGE_LOCATION_ADDR +            burner.read_image(file, size, addr) + +    else: burner.burn_fw(fw=options.fw, fpga=options.fpga, reset=options.reset, safe=options.overwrite_safe, check_rev=not options.dont_check_rev) diff --git a/host/utils/usrp_n2xx_net_burner_gui.py b/host/utils/usrp_n2xx_net_burner_gui.py new file mode 100755 index 000000000..a9150bd88 --- /dev/null +++ b/host/utils/usrp_n2xx_net_burner_gui.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +import usrp_n2xx_net_burner #import implementation +try: +    import tkinter, tkinter.filedialog, tkinter.font, tkinter.messagebox +except ImportError: +    import tkFileDialog, tkFont, tkMessageBox +    import Tkinter as tkinter +    tkinter.filedialog = tkFileDialog +    tkinter.font = tkFont +    tkinter.messagebox = tkMessageBox +import os + +class BinFileEntry(tkinter.Frame): +    """ +    Simple file entry widget for getting the file path of bin files. +    Combines a label, entry, and button with file dialog callback. +    """ + +    def __init__(self, root, what, def_path=''): +        self._what = what +        tkinter.Frame.__init__(self, root) +        tkinter.Label(self, text=what+":").pack(side=tkinter.LEFT) +        self._entry = tkinter.Entry(self, width=50) +        self._entry.insert(tkinter.END, def_path) +        self._entry.pack(side=tkinter.LEFT) +        tkinter.Button(self, text="...", command=self._button_cb).pack(side=tkinter.LEFT) + +    def _button_cb(self): +        filename = tkinter.filedialog.askopenfilename( +            parent=self, +            filetypes=[('bin files', '*.bin'), ('all files', '*.*')], +            title="Select bin file for %s"%self._what, +            initialdir=os.path.dirname(self.get_filename()), +        ) + +        # open file on your own +        if filename: +            self._entry.delete(0, tkinter.END) +            self._entry.insert(0, filename) + +    def get_filename(self): +        return self._entry.get() + +class ProgressBar(tkinter.Canvas): +    """ +    A simple implementation of a progress bar. +    Draws rectangle that fills from left to right. +    """ + +    def __init__(self, root, width=500, height=20): +        self._width = width +        self._height = height +        tkinter.Canvas.__init__(self, root, relief="sunken", borderwidth=2, width=self._width-2, height=self._height-2) +        self._last_fill_pixels = None +        self.set(0.0) + +    def set(self, frac): +        """ +        Update the progress where fraction is between 0.0 and 1.0 +        """ +        #determine the number of pixels to draw +        fill_pixels = int(round(self._width*frac)) +        if fill_pixels == self._last_fill_pixels: return +        self._last_fill_pixels = fill_pixels + +        #draw a rectangle representing the progress +        if frac: self.create_rectangle(0, 0, fill_pixels, self._height, fill="#357EC7") +        else:    self.create_rectangle(0, 0, self._width, self._height, fill="#E8E8E8") + +class DeviceEntryWidget(tkinter.Frame): +    """ +    Simple entry widget for getting the network device name. +    Combines a label, entry, and helpful text box with hints. +    """ + +    def __init__(self, root, text=''): +        tkinter.Frame.__init__(self, root) + +        tkinter.Button(self, text="Rescan for Devices", command=self._reload_cb).pack() + +        self._hints = tkinter.Listbox(self) +        self._hints_addrs_only = tkinter.Listbox(self) + +        self._hints.bind("<<ListboxSelect>>", self._listbox_cb) +        self._hints_addrs_only.bind("<<ListboxSelect>>", self._listbox_cb) + +        self._reload_cb() +        self._hints.pack(expand=tkinter.YES, fill=tkinter.X) + +        frame = tkinter.Frame(self) +        frame.pack() + +        tkinter.Label(frame, text="Network Address:").pack(side=tkinter.LEFT) +        self._entry = tkinter.Entry(frame, width=50) +        self._entry.insert(tkinter.END, text) +        self._entry.pack(side=tkinter.LEFT) + +    def _reload_cb(self): +        self._hints.delete(0, tkinter.END) +        for hint in usrp_n2xx_net_burner.enumerate_devices(): +            self._hints.insert(tkinter.END, hint) +            self._hints_addrs_only.insert(tkinter.END, hint.split(" (")[0]) + +    def _listbox_cb(self, event): +        try: +            sel = self._hints_addrs_only.get(self._hints.curselection()[0]) +            self._entry.delete(0, tkinter.END) +            self._entry.insert(0, sel) +        except Exception as e: print(e) + +    def get_devname(self): +        return self._entry.get() + +class SectionLabel(tkinter.Label): +    """ +    Make a text label with bold font. +    """ + +    def __init__(self, root, text): +        tkinter.Label.__init__(self, root, text=text) + +        #set the font bold +        f = tkinter.font.Font(font=self['font']) +        f['weight'] = 'bold' +        self['font'] = f.name + +class USRPN2XXNetBurnerApp(tkinter.Frame): +    """ +    The top level gui application for the usrp-n2xx network burner. +    Creates entry widgets and button with callback to write images. +    """ + +    def __init__(self, root, addr, fw, fpga): + +        tkinter.Frame.__init__(self, root) + +        #pack the file entry widgets +        SectionLabel(self, text="Select Images").pack(pady=5) +        self._fw_img_entry = BinFileEntry(self, "Firmware Image", def_path=fw) +        self._fw_img_entry.pack() +        self._fpga_img_entry = BinFileEntry(self, "FPGA Image", def_path=fpga) +        self._fpga_img_entry.pack() + +        #pack the destination entry widget +        SectionLabel(self, text="Select Device").pack(pady=5) +        self._net_dev_entry = DeviceEntryWidget(self, text=addr) +        self._net_dev_entry.pack() + +        #the do it button +        SectionLabel(self, text="").pack(pady=5) +        button = tkinter.Button(self, text="Burn Images", command=self._burn) +        self._enable_input = lambda: button.configure(state=tkinter.NORMAL) +        self._disable_input = lambda: button.configure(state=tkinter.DISABLED) +        button.pack() + +        #a progress bar to monitor the status +        progress_frame = tkinter.Frame(self) +        progress_frame.pack() +        self._status = tkinter.StringVar() +        tkinter.Label(progress_frame, textvariable=self._status).pack(side=tkinter.LEFT) +        self._pbar = ProgressBar(progress_frame) +        self._pbar.pack(side=tkinter.RIGHT, expand=True) + +    def _burn(self): +        #grab strings from the gui +        fw = self._fw_img_entry.get_filename() +        fpga = self._fpga_img_entry.get_filename() +        addr = self._net_dev_entry.get_devname() + +        #check input +        if not addr: +            tkinter.messagebox.showerror('Error:', 'No address specified!') +            return +        if not fw and not fpga: +            tkinter.messagebox.showerror('Error:', 'No images specified!') +            return +        if fw and not os.path.exists(fw): +            tkinter.messagebox.showerror('Error:', 'Firmware image not found!') +            return +        if fpga and not os.path.exists(fpga): +            tkinter.messagebox.showerror('Error:', 'FPGA image not found!') +            return + +        self._disable_input() +        try: +            #make a new burner object and attempt the burner operation +            burner = usrp_n2xx_net_burner.burner_socket(addr=addr,quiet=False) + +            for (image_type, fw_img, fpga_img) in (('FPGA', '', fpga), ('Firmware', fw, '')): +                #setup callbacks that update the gui +                def status_cb(status): +                    self._pbar.set(0.0) #status change, reset the progress +                    self._status.set("%s %s "%(status.title(), image_type)) +                    self.update() +                def progress_cb(progress): +                    self._pbar.set(progress) +                    self.update() +                burner.set_callbacks(progress_cb=progress_cb, status_cb=status_cb) +                burner.burn_fw(fw=fw_img, fpga=fpga_img, reset=False, safe=False) + +            if tkinter.messagebox.askyesno("Burn was successful!", "Reset the device?"): +                burner.reset_usrp() + +        except Exception as e: +            tkinter.messagebox.showerror('Verbose:', 'Error: %s'%str(e)) + +        #reset the progress bar +        self._pbar.set(0.0) +        self._status.set("") +        self._enable_input() + +######################################################################## +# main +######################################################################## +if __name__=='__main__': +    options = usrp_n2xx_net_burner.get_options() +    root = tkinter.Tk() +    root.title('USRP-N2XX Net Burner') +    USRPN2XXNetBurnerApp(root, addr=options.addr, fw=options.fw, fpga=options.fpga).pack() +    root.mainloop() +    exit() diff --git a/host/utils/usrp_n2xx_simple_net_burner.cpp b/host/utils/usrp_n2xx_simple_net_burner.cpp new file mode 100644 index 000000000..901842538 --- /dev/null +++ b/host/utils/usrp_n2xx_simple_net_burner.cpp @@ -0,0 +1,518 @@ +// +// Copyright 2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <iostream> +#include <map> +#include <fstream> +#include <time.h> +#include <vector> + +#include <boost/foreach.hpp> +#include <boost/asio.hpp> +#include <boost/program_options.hpp> +#include <boost/assign.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/algorithm/string/erase.hpp> +#include <boost/filesystem.hpp> +#include <boost/thread/thread.hpp> + +#include "usrp_simple_burner_utils.hpp" +#include <uhd/exception.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/images.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/safe_call.hpp> + +namespace po = boost::program_options; +using namespace boost::algorithm; +using namespace uhd; +using namespace uhd::transport; + +//Mapping revision numbers to filenames +std::map<boost::uint32_t, std::string> filename_map = boost::assign::map_list_of +    (0xa,    "n200_r3") +    (0x100a, "n200_r4") +    (0x10a,  "n210_r3") +    (0x110a, "n210_r4") +; + +//Images and image sizes, to be populated as necessary +boost::uint8_t fpga_image[FPGA_IMAGE_SIZE_BYTES]; +boost::uint8_t fw_image[FW_IMAGE_SIZE_BYTES]; +int fpga_image_size = 0; +int fw_image_size = 0; + +//For non-standard images not covered by uhd::find_image_path() +bool does_image_exist(std::string image_filepath){ + +    std::ifstream ifile((char*)image_filepath.c_str()); +    return ifile; +} + +/*********************************************************************** + * Custom filename validation functions + **********************************************************************/ + +void validate_custom_fpga_file(std::string rev_str, std::string fpga_path){ + +    //Check for existence of file +    if(!does_image_exist(fpga_path)) throw std::runtime_error(str(boost::format("No file at specified FPGA path: %s") % fpga_path)); + +    //Check to find rev_str in filename +    uhd::fs_path custom_fpga_path(fpga_path); +    if(custom_fpga_path.leaf().find("fw") != std::string::npos){ +        throw std::runtime_error(str(boost::format("Invalid FPGA image filename at path: %s\nFilename indicates that this is a firmware image.") +            % fpga_path)); +    } +    if(custom_fpga_path.leaf().find(rev_str) == std::string::npos){ +        throw std::runtime_error(str(boost::format("Invalid FPGA image filename at path: %s\nFilename must contain '%s' to be considered valid for this model.") +            % fpga_path % rev_str)); +    } +} + +void validate_custom_fw_file(std::string rev_str, std::string fw_path){ + +    //Check for existence of file +    if(!does_image_exist(fw_path)) throw std::runtime_error(str(boost::format("No file at specified firmware path: %s") % fw_path)); + +    //Check to find truncated rev_str in filename +    uhd::fs_path custom_fw_path(fw_path); +    if(custom_fw_path.leaf().find("fpga") != std::string::npos){ +        throw std::runtime_error(str(boost::format("Invalid firmware image filename at path: %s\nFilename indicates that this is an FPGA image.") +            % fw_path)); +    } +    if(custom_fw_path.leaf().find(erase_tail_copy(rev_str,3)) == std::string::npos){ +        throw std::runtime_error(str(boost::format("Invalid firmware image filename at path: %s\nFilename must contain '%s' to be considered valid for this model.") +            % fw_path % erase_tail_copy(rev_str,3))); +    } +} + +/*********************************************************************** + * Grabbing and validating image binaries + **********************************************************************/ + +int grab_fpga_image(std::string fpga_path){ + +    //Reading FPGA image from file +    std::ifstream to_read_fpga((char*)fpga_path.c_str(), std::ios::binary); +    to_read_fpga.seekg(0, std::ios::end); +    fpga_image_size = to_read_fpga.tellg(); +    to_read_fpga.seekg(0, std::ios::beg); +    char fpga_read[FPGA_IMAGE_SIZE_BYTES]; +    to_read_fpga.read(fpga_read,fpga_image_size); +    to_read_fpga.close(); +    for(int i = 0; i < fpga_image_size; i++) fpga_image[i] = (boost::uint8_t)fpga_read[i]; + +    //Checking validity of image +    if(fpga_image_size > FPGA_IMAGE_SIZE_BYTES){ +        throw std::runtime_error(str(boost::format("FPGA image is too large. %d > %d") % fpga_image_size % FPGA_IMAGE_SIZE_BYTES)); +    } + +    //Check sequence of bytes in image +    bool is_good = false; +    for(int i = 0; i < 63; i++){ +        if((boost::uint8_t)fpga_image[i] == 255) continue; +        else if((boost::uint8_t)fpga_image[i] == 170 and +                (boost::uint8_t)fpga_image[i+1] == 153){ +            is_good = true; +            break; +        } +    } + +    if(!is_good) throw std::runtime_error("Not a valid FPGA image."); + +    //Return image size +    return fpga_image_size; +} + +int grab_fw_image(std::string fw_path){ + +    //Reading firmware image from file +    std::ifstream to_read_fw((char*)fw_path.c_str(), std::ios::binary); +    to_read_fw.seekg(0, std::ios::end); +    fw_image_size = to_read_fw.tellg(); +    to_read_fw.seekg(0, std::ios::beg); +    char fw_read[FW_IMAGE_SIZE_BYTES]; +    to_read_fw.read(fw_read,fw_image_size); +    to_read_fw.close(); +    for(int i = 0; i < fw_image_size; i++) fw_image[i] = (boost::uint8_t)fw_read[i]; + +    //Checking validity of image +    if(fw_image_size > FW_IMAGE_SIZE_BYTES){ +        throw std::runtime_error(str(boost::format("Firmware image is too large. %d > %d") % fw_image_size % FW_IMAGE_SIZE_BYTES)); +    } + +    //Check first four bytes of image +    for(int i = 0; i < 4; i++) if((boost::uint8_t)fw_image[i] != 11) throw std::runtime_error("Not a valid firmware image."); + +    //Return image size +    return fw_image_size; +} + +boost::uint32_t* get_flash_info(std::string ip_addr){ + +    boost::uint32_t *flash_info = new boost::uint32_t[2]; +    boost::uint8_t usrp2_update_data_in_mem[udp_simple::mtu]; +    const usrp2_fw_update_data_t *update_data_in = reinterpret_cast<const usrp2_fw_update_data_t *>(usrp2_update_data_in_mem); + +    udp_simple::sptr udp_transport = udp_simple::make_connected(ip_addr, BOOST_STRINGIZE(USRP2_UDP_UPDATE_PORT)); +    usrp2_fw_update_data_t get_flash_info_pkt = usrp2_fw_update_data_t(); +    get_flash_info_pkt.proto_ver = htonx<boost::uint32_t>(USRP2_FW_PROTO_VERSION); +    get_flash_info_pkt.id = htonx<boost::uint32_t>(USRP2_FW_UPDATE_ID_WATS_TEH_FLASH_INFO_LOL); +    udp_transport->send(boost::asio::buffer(&get_flash_info_pkt, sizeof(get_flash_info_pkt))); + +    //Loop and receive until the timeout +    size_t len = udp_transport->recv(boost::asio::buffer(usrp2_update_data_in_mem), UDP_TIMEOUT); +    if(len > offsetof(usrp2_fw_update_data_t, data) and ntohl(update_data_in->id) == USRP2_FW_UPDATE_ID_HERES_TEH_FLASH_INFO_OMG){ +        flash_info[0] = ntohl(update_data_in->data.flash_info_args.sector_size_bytes); +        flash_info[1] = ntohl(update_data_in->data.flash_info_args.memory_size_bytes); +    } +    else if(ntohl(update_data_in->id) != USRP2_FW_UPDATE_ID_HERES_TEH_FLASH_INFO_OMG){ +        throw std::runtime_error(str(boost::format("Received invalid reply %d from device.\n") % ntohl(update_data_in->id))); +    } +     +    return flash_info; +} + +/*********************************************************************** + * Image burning functions + **********************************************************************/ + +void erase_image(udp_simple::sptr udp_transport, bool is_fw, boost::uint32_t memory_size){ + +    //Making sure this won't attempt to erase past end of device +    if(is_fw){ +        if(PROD_FW_IMAGE_LOCATION_ADDR+FW_IMAGE_SIZE_BYTES > memory_size) throw std::runtime_error("Cannot erase past end of device."); +    } +    else{ +        if(PROD_FPGA_IMAGE_LOCATION_ADDR+FPGA_IMAGE_SIZE_BYTES > memory_size) throw std::runtime_error("Cannot erase past end of device."); +    } + +    //Setting up UDP transport +    boost::uint8_t usrp2_update_data_in_mem[udp_simple::mtu]; +    const usrp2_fw_update_data_t *update_data_in = reinterpret_cast<const usrp2_fw_update_data_t *>(usrp2_update_data_in_mem); + +    //Setting up UDP packet +    usrp2_fw_update_data_t erase_pkt = usrp2_fw_update_data_t(); +    erase_pkt.id = htonx<boost::uint32_t>(USRP2_FW_UPDATE_ID_ERASE_TEH_FLASHES_LOL); +    erase_pkt.proto_ver = htonx<boost::uint32_t>(USRP2_FW_PROTO_VERSION); +    if(is_fw){ +        erase_pkt.data.flash_args.flash_addr = htonx<boost::uint32_t>(PROD_FW_IMAGE_LOCATION_ADDR); +        erase_pkt.data.flash_args.length = htonx<boost::uint32_t>(FW_IMAGE_SIZE_BYTES); +    } +    else{ +        erase_pkt.data.flash_args.flash_addr = htonx<boost::uint32_t>(PROD_FPGA_IMAGE_LOCATION_ADDR); +        erase_pkt.data.flash_args.length = htonx<boost::uint32_t>(FPGA_IMAGE_SIZE_BYTES); +    } + +    //Begin erasing +    udp_transport->send(boost::asio::buffer(&erase_pkt, sizeof(erase_pkt))); +    size_t len = udp_transport->recv(boost::asio::buffer(usrp2_update_data_in_mem), UDP_TIMEOUT); +    if(len > offsetof(usrp2_fw_update_data_t, data) and ntohl(update_data_in->id) == USRP2_FW_UPDATE_ID_ERASING_TEH_FLASHES_OMG){ +        if(is_fw) std::cout << "Erasing firmware image." << std::endl; +        else      std::cout << "Erasing FPGA image." << std::endl; +    } +    else if(ntohl(update_data_in->id) != USRP2_FW_UPDATE_ID_ERASING_TEH_FLASHES_OMG){ +        throw std::runtime_error(str(boost::format("Received invalid reply %d from device.\n") % ntohl(update_data_in->id))); +    } + +    //Check for erase completion +    erase_pkt.id = htonx<boost::uint32_t>(USRP2_FW_UPDATE_ID_R_U_DONE_ERASING_LOL); +    while(true){ +        udp_transport->send(boost::asio::buffer(&erase_pkt, sizeof(erase_pkt))); +        size_t len = udp_transport->recv(boost::asio::buffer(usrp2_update_data_in_mem), UDP_TIMEOUT); +        if(len > offsetof(usrp2_fw_update_data_t, data) and ntohl(update_data_in->id) == USRP2_FW_UPDATE_ID_IM_DONE_ERASING_OMG){ +            if(is_fw) std::cout << boost::format(" * Successfully erased %d bytes at %d.\n") % FW_IMAGE_SIZE_BYTES % PROD_FW_IMAGE_LOCATION_ADDR; +            else std::cout << boost::format(" * Successfully erased %d bytes at %d.\n") % FPGA_IMAGE_SIZE_BYTES % PROD_FPGA_IMAGE_LOCATION_ADDR; +            break; +        } +        else if(ntohl(update_data_in->id) != USRP2_FW_UPDATE_ID_NOPE_NOT_DONE_ERASING_OMG){ +            throw std::runtime_error(str(boost::format("Received invalid reply %d from device.\n") % ntohl(update_data_in->id))); +        } +    } +} + +void write_image(udp_simple::sptr udp_transport, bool is_fw, boost::uint8_t* image, boost::uint32_t memory_size, int image_size){ + +    boost::uint32_t current_addr; +    if(is_fw) current_addr = PROD_FW_IMAGE_LOCATION_ADDR; +    else current_addr = PROD_FPGA_IMAGE_LOCATION_ADDR; + +    //Making sure this won't attempt to write past end of device +    if(current_addr+image_size > memory_size) throw std::runtime_error("Cannot write past end of device."); + +    //Setting up UDP transport +    boost::uint8_t usrp2_update_data_in_mem[udp_simple::mtu]; +    const usrp2_fw_update_data_t *update_data_in = reinterpret_cast<const usrp2_fw_update_data_t *>(usrp2_update_data_in_mem); + +    //Setting up UDP packet +    usrp2_fw_update_data_t write_pkt = usrp2_fw_update_data_t(); +    write_pkt.id = htonx<boost::uint32_t>(USRP2_FW_UPDATE_ID_WRITE_TEH_FLASHES_LOL); +    write_pkt.proto_ver = htonx<boost::uint32_t>(USRP2_FW_PROTO_VERSION); +    write_pkt.data.flash_args.length = htonx<boost::uint32_t>(FLASH_DATA_PACKET_SIZE); + +    //Write image +    if(is_fw) std::cout << "Writing firmware image." << std::endl; +    else std::cout << "Writing FPGA image." << std::endl; + +    for(int i = 0; i < ((image_size/FLASH_DATA_PACKET_SIZE)+1); i++){ +        write_pkt.data.flash_args.flash_addr = htonx<boost::uint32_t>(current_addr); +        std::copy(image+(i*FLASH_DATA_PACKET_SIZE), image+((i+1)*FLASH_DATA_PACKET_SIZE), write_pkt.data.flash_args.data); + +        udp_transport->send(boost::asio::buffer(&write_pkt, sizeof(write_pkt))); +        size_t len = udp_transport->recv(boost::asio::buffer(usrp2_update_data_in_mem), UDP_TIMEOUT); +        if(len > offsetof(usrp2_fw_update_data_t, data) and ntohl(update_data_in->id) != USRP2_FW_UPDATE_ID_WROTE_TEH_FLASHES_OMG){ +            throw std::runtime_error(str(boost::format("Invalid reply %d from device.") % ntohl(update_data_in->id))); +        } + +        current_addr += FLASH_DATA_PACKET_SIZE; +    } +    std::cout << boost::format(" * Successfully wrote %d bytes.\n") % image_size; +} + +void verify_image(udp_simple::sptr udp_transport, bool is_fw, boost::uint8_t* image, boost::uint32_t memory_size, int image_size){ + +    int current_index = 0; +    boost::uint32_t current_addr; +    if(is_fw) current_addr = PROD_FW_IMAGE_LOCATION_ADDR; +    else current_addr = PROD_FPGA_IMAGE_LOCATION_ADDR; + +    //Array size needs to be known at runtime, this constant is guaranteed to be larger than any firmware or FPGA image +    boost::uint8_t from_usrp[FPGA_IMAGE_SIZE_BYTES]; + +    //Making sure this won't attempt to read past end of device +    if(current_addr+image_size > memory_size) throw std::runtime_error("Cannot read past end of device."); + +    //Setting up UDP transport +    boost::uint8_t usrp2_update_data_in_mem[udp_simple::mtu]; +    const usrp2_fw_update_data_t *update_data_in = reinterpret_cast<const usrp2_fw_update_data_t *>(usrp2_update_data_in_mem); + +    //Setting up UDP packet +    usrp2_fw_update_data_t verify_pkt = usrp2_fw_update_data_t(); +    verify_pkt.id = htonx<boost::uint32_t>(USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL); +    verify_pkt.proto_ver = htonx<boost::uint32_t>(USRP2_FW_PROTO_VERSION); +    verify_pkt.data.flash_args.length = htonx<boost::uint32_t>(FLASH_DATA_PACKET_SIZE); + +    //Verify image +    if(is_fw) std::cout << "Verifying firmware image." << std::endl; +    else std::cout << "Verifying FPGA image." << std::endl; + +    for(int i = 0; i < ((image_size/FLASH_DATA_PACKET_SIZE)+1); i++){ +        verify_pkt.data.flash_args.flash_addr = htonx<boost::uint32_t>(current_addr); + +        udp_transport->send(boost::asio::buffer(&verify_pkt, sizeof(verify_pkt))); +        size_t len = udp_transport->recv(boost::asio::buffer(usrp2_update_data_in_mem), UDP_TIMEOUT); +        if(len > offsetof(usrp2_fw_update_data_t, data) and ntohl(update_data_in->id) != USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG){ +            throw std::runtime_error(str(boost::format("Invalid reply %d from device.") % ntohl(update_data_in->id))); +        } +        for(int j = 0; j < FLASH_DATA_PACKET_SIZE; j++) from_usrp[current_index+j] = update_data_in->data.flash_args.data[j]; + +        current_addr += FLASH_DATA_PACKET_SIZE; +        current_index += FLASH_DATA_PACKET_SIZE; +    } +    for(int i = 0; i < image_size; i++) if(from_usrp[i] != image[i]) throw std::runtime_error("Image write failed."); + +    std::cout << " * Successful." << std::endl; +} + +void reset_usrp(udp_simple::sptr udp_transport){ + +    //Set up UDP transport +    boost::uint8_t usrp2_update_data_in_mem[udp_simple::mtu]; +    const usrp2_fw_update_data_t *update_data_in = reinterpret_cast<const usrp2_fw_update_data_t *>(usrp2_update_data_in_mem); + +    //Set up UDP packet +    usrp2_fw_update_data_t reset_pkt = usrp2_fw_update_data_t(); +    reset_pkt.id = htonx<boost::uint32_t>(USRP2_FW_UPDATE_ID_RESET_MAH_COMPUTORZ_LOL); +    reset_pkt.proto_ver = htonx<boost::uint32_t>(USRP2_FW_PROTO_VERSION); + +    //Reset USRP +    udp_transport->send(boost::asio::buffer(&reset_pkt, sizeof(reset_pkt))); +    size_t len = udp_transport->recv(boost::asio::buffer(usrp2_update_data_in_mem), UDP_TIMEOUT); +    if(len > offsetof(usrp2_fw_update_data_t, data) and ntohl(update_data_in->id) == USRP2_FW_UPDATE_ID_RESETTIN_TEH_COMPUTORZ_OMG){ +        throw std::runtime_error("USRP reset failed."); //There should be no response to this UDP packet +    } +    else std::cout << "Resetting USRP." << std::endl; +} + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + +    //Establish user options +    std::string fw_path; +    std::string ip_addr; +    std::string fpga_path; + +    po::options_description desc("Allowed options:"); +    desc.add_options() +        ("help", "Display this help message.") +        ("addr", po::value<std::string>(&ip_addr)->default_value("192.168.10.2"), "Specify an IP address.") +        ("fw", po::value<std::string>(&fw_path), "Specify a filepath for a custom firmware image.") +        ("fpga", po::value<std::string>(&fpga_path), "Specify a filepath for a custom FPGA image.") +        ("no_fw", "Do not burn a firmware image.") +        ("no_fpga", "Do not burn an FPGA image.") +        ("list", "List available N2XX USRP devices.") +    ; +    po::variables_map vm; +    po::store(po::parse_command_line(argc, argv, desc), vm); +    po::notify(vm); + +    //Apply options +    if(vm.count("help") > 0){ +        std::cout << boost::format("N2XX Simple Net Burner\n"); +        std::cout << boost::format("Automatically detects and burns standard firmware and FPGA images onto USRP N2XX devices.\n"); +        std::cout << boost::format("Can optionally take user input for custom images.\n\n"); +        std::cout << desc << std::endl; +        return EXIT_FAILURE; +    } + +    bool burn_fpga = (vm.count("no_fpga") == 0); +    bool burn_fw = (vm.count("no_fw") == 0); +    bool use_custom_fpga = (vm.count("fpga") > 0); +    bool use_custom_fw = (vm.count("fw") > 0); +    bool list_usrps = (vm.count("list") > 0); + +    if(!burn_fpga && !burn_fw){ +        std::cout << "No images will be burned." << std::endl; +        return EXIT_FAILURE; +    } + +    if(!burn_fw && use_custom_fw)     std::cout << boost::format("Conflicting firmware options presented. Will not burn a firmware image.\n\n"); +    if(!burn_fpga && use_custom_fpga) std::cout << boost::format("Conflicting FPGA options presented. Will not burn an FPGA image.\n\n"); + +    //Variables not from options +    boost::uint32_t hw_rev; +    bool found_it = false; +    boost::uint8_t usrp2_update_data_in_mem[udp_simple::mtu]; +    const usrp2_fw_update_data_t *update_data_in = reinterpret_cast<const usrp2_fw_update_data_t *>(usrp2_update_data_in_mem); + +    //List option +    if(list_usrps){ +        udp_simple::sptr udp_bc_transport; +        usrp2_fw_update_data_t usrp2_ack_pkt = usrp2_fw_update_data_t(); +        usrp2_ack_pkt.proto_ver = htonx<boost::uint32_t>(USRP2_FW_PROTO_VERSION); +        usrp2_ack_pkt.id = htonx<boost::uint32_t>(USRP2_FW_UPDATE_ID_OHAI_LOL); + +        std::cout << "Available USRP N2XX devices:" << std::endl; + +        //Send UDP packets to all broadcast addresses +        BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()){ +            //Avoid the loopback device +            if(if_addrs.inet == boost::asio::ip::address_v4::loopback().to_string()) continue; +            udp_bc_transport = udp_simple::make_broadcast(if_addrs.bcast, BOOST_STRINGIZE(USRP2_UDP_UPDATE_PORT)); +            udp_bc_transport->send(boost::asio::buffer(&usrp2_ack_pkt, sizeof(usrp2_ack_pkt))); + +            size_t len = udp_bc_transport->recv(boost::asio::buffer(usrp2_update_data_in_mem), UDP_TIMEOUT); +            if(len > offsetof(usrp2_fw_update_data_t, data) and ntohl(update_data_in->id) == USRP2_FW_UPDATE_ID_OHAI_OMG){ +                usrp2_ack_pkt.id = htonx<boost::uint32_t>(USRP2_FW_UPDATE_ID_I_CAN_HAS_HW_REV_LOL); +                udp_bc_transport->send(boost::asio::buffer(&usrp2_ack_pkt, sizeof(usrp2_ack_pkt))); + +                size_t len = udp_bc_transport->recv(boost::asio::buffer(usrp2_update_data_in_mem), UDP_TIMEOUT); +                if(len > offsetof(usrp2_fw_update_data_t, data) and ntohl(update_data_in->id) == USRP2_FW_UPDATE_ID_HERES_TEH_HW_REV_OMG){ +                    hw_rev = ntohl(update_data_in->data.hw_rev); +                } + +                std::cout << boost::format(" * %s (%s)\n") % udp_bc_transport->get_recv_addr() % filename_map[hw_rev]; +            } +         +        } +        return EXIT_FAILURE; +    } +    std::cout << boost::format("Searching for USRP N2XX with IP address %s.\n") % ip_addr; + +    //Address specified +    udp_simple::sptr udp_transport = udp_simple::make_connected(ip_addr, BOOST_STRINGIZE(USRP2_UDP_UPDATE_PORT)); +    usrp2_fw_update_data_t hw_info_pkt = usrp2_fw_update_data_t(); +    hw_info_pkt.proto_ver = htonx<boost::uint32_t>(USRP2_FW_PROTO_VERSION); +    hw_info_pkt.id = htonx<boost::uint32_t>(USRP2_FW_UPDATE_ID_I_CAN_HAS_HW_REV_LOL); +    udp_transport->send(boost::asio::buffer(&hw_info_pkt, sizeof(hw_info_pkt))); + +    //Loop and receive until the timeout +    size_t len = udp_transport->recv(boost::asio::buffer(usrp2_update_data_in_mem), UDP_TIMEOUT); +    if(len > offsetof(usrp2_fw_update_data_t, data) and ntohl(update_data_in->id) == USRP2_FW_UPDATE_ID_HERES_TEH_HW_REV_OMG){ +        hw_rev = ntohl(update_data_in->data.hw_rev); +        if(filename_map.find(hw_rev) != filename_map.end()){ +            std::cout << boost::format("Found %s.\n\n") % filename_map[hw_rev]; +            found_it = true; +        } +        else throw std::runtime_error("Invalid revision found."); +    } +    if(!found_it) throw std::runtime_error("No USRP N2XX found."); + +    //Determining default image filenames for validation +    std::string default_fw_filename = str(boost::format("usrp_%s_fw.bin") % erase_tail_copy(filename_map[hw_rev],3)); +    std::string default_fpga_filename = str(boost::format("usrp_%s_fpga.bin") % filename_map[hw_rev]); +    std::string default_fw_filepath = ""; +    std::string default_fpga_filepath = ""; + +    //Check validity of file locations and binaries before attempting burn +    std::cout << "Searching for specified images." << std::endl << std::endl; +    if(burn_fpga){ +        if(!use_custom_fpga) fpga_path = find_image_path(default_fpga_filename); +        else validate_custom_fpga_file(filename_map[hw_rev], fpga_path); + +        grab_fpga_image(fpga_path); +    } +    if(burn_fw){ +        if(!use_custom_fw) fw_path = find_image_path(default_fw_filename); +        else validate_custom_fw_file(filename_map[hw_rev], fw_path); + +        grab_fw_image(fw_path); +    } + +    std::cout << "Will burn the following images:" << std::endl; +    if(burn_fw) std::cout << boost::format(" * Firmware: %s\n") % fw_path; +    if(burn_fpga) std::cout << boost::format(" * FPGA:     %s\n") % fpga_path; +    std::cout << std::endl; + +    boost::uint32_t* flash_info = get_flash_info(ip_addr); +    std::cout << boost::format("Querying %s for flash information.\n") % filename_map[hw_rev]; +    std::cout << boost::format(" * Flash size:  %3.2f\n") % flash_info[1]; +    std::cout << boost::format(" * Sector size: %3.2f\n\n") % flash_info[0]; + +    //Burning images + +    if(burn_fpga){ +        erase_image(udp_transport, false, flash_info[1]); +        write_image(udp_transport, false, fpga_image, flash_info[1], fpga_image_size); +        verify_image(udp_transport, false, fpga_image, flash_info[1], fpga_image_size); +    } +    if(burn_fpga and burn_fw) std::cout << std::endl; //Formatting +    if(burn_fw){ +        erase_image(udp_transport, true, flash_info[1]); +        write_image(udp_transport, true, fw_image, flash_info[1], fw_image_size); +        verify_image(udp_transport, true, fw_image, flash_info[1], fw_image_size); +    } + +    //Prompt user to reset USRP +    std::string user_response = "foo"; +    bool reset = false; +    while(user_response != "y" and user_response != "" and user_response != "n"){ +        std::cout << std::endl << "Image burning successful. Reset USRP (Y/n)? "; +        std::getline(std::cin, user_response); +        std::transform(user_response.begin(), user_response.end(), user_response.begin(), ::tolower); +        reset = (user_response == "" or user_response == "y"); +    } +    std::cout << std::endl; //Formatting +    if(reset) reset_usrp(udp_transport); +    else return EXIT_SUCCESS; + +    return EXIT_SUCCESS; +} diff --git a/host/utils/usrp_simple_burner_utils.hpp b/host/utils/usrp_simple_burner_utils.hpp new file mode 100644 index 000000000..f386c3620 --- /dev/null +++ b/host/utils/usrp_simple_burner_utils.hpp @@ -0,0 +1,99 @@ +// +// Copyright 2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <iostream> +#include <math.h> +#include <stdint.h> + +#include <boost/foreach.hpp> +#include <boost/asio.hpp> +#include <boost/filesystem.hpp> + +#include <uhd/exception.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/utils/msg.hpp> + +#define UDP_FW_UPDATE_PORT 49154 +#define UDP_MAX_XFER_BYTES 1024 +#define UDP_TIMEOUT 3 +#define UDP_POLL_INTERVAL 0.10 //in seconds +#define USRP2_FW_PROTO_VERSION 7 //should be unused after r6 +#define USRP2_UDP_UPDATE_PORT 49154 +#define FLASH_DATA_PACKET_SIZE 256 +#define FPGA_IMAGE_SIZE_BYTES 1572864 +#define FW_IMAGE_SIZE_BYTES 31744 +#define PROD_FPGA_IMAGE_LOCATION_ADDR 0x00180000 +#define PROD_FW_IMAGE_LOCATION_ADDR 0x00300000 +#define SAFE_FPGA_IMAGE_LOCATION_ADDR 0x00000000 +#define SAFE_FW_IMAGE_LOCATION_ADDR 0x003F0000 + +using namespace uhd; +using namespace uhd::transport; +namespace asio = boost::asio; + +typedef enum { +    USRP2_FW_UPDATE_ID_WAT = ' ', + +    USRP2_FW_UPDATE_ID_OHAI_LOL = 'a', +    USRP2_FW_UPDATE_ID_OHAI_OMG = 'A', + +    USRP2_FW_UPDATE_ID_WATS_TEH_FLASH_INFO_LOL = 'f', +    USRP2_FW_UPDATE_ID_HERES_TEH_FLASH_INFO_OMG = 'F', + +    USRP2_FW_UPDATE_ID_ERASE_TEH_FLASHES_LOL = 'e', +    USRP2_FW_UPDATE_ID_ERASING_TEH_FLASHES_OMG = 'E', + +    USRP2_FW_UPDATE_ID_R_U_DONE_ERASING_LOL = 'd', +    USRP2_FW_UPDATE_ID_IM_DONE_ERASING_OMG = 'D', +    USRP2_FW_UPDATE_ID_NOPE_NOT_DONE_ERASING_OMG = 'B', + +    USRP2_FW_UPDATE_ID_WRITE_TEH_FLASHES_LOL = 'w', +    USRP2_FW_UPDATE_ID_WROTE_TEH_FLASHES_OMG = 'W', + +    USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL = 'r', +    USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG = 'R', + +    USRP2_FW_UPDATE_ID_RESET_MAH_COMPUTORZ_LOL = 's', +    USRP2_FW_UPDATE_ID_RESETTIN_TEH_COMPUTORZ_OMG = 'S', + +    USRP2_FW_UPDATE_ID_I_CAN_HAS_HW_REV_LOL = 'v', +    USRP2_FW_UPDATE_ID_HERES_TEH_HW_REV_OMG = 'V', + +    USRP2_FW_UPDATE_ID_KTHXBAI = '~'  + +} usrp2_fw_update_id_t; + +typedef struct { +    uint32_t proto_ver; +    uint32_t id;  +    uint32_t seq; +    union { +        uint32_t ip_addr; +        uint32_t hw_rev; +        struct { +            uint32_t flash_addr; +            uint32_t length; +            uint8_t  data[256]; +        } flash_args; +        struct { +            uint32_t sector_size_bytes; +            uint32_t memory_size_bytes; +        } flash_info_args; +    } data; +} usrp2_fw_update_data_t; | 
