diff options
author | Nicholas Corgan <nick.corgan@ettus.com> | 2014-07-17 11:50:50 -0700 |
---|---|---|
committer | Nicholas Corgan <nick.corgan@ettus.com> | 2014-07-23 07:37:32 -0700 |
commit | a6e18604befdb6a954542f7722c8d55424065621 (patch) | |
tree | 22168e6f4c41c931e38ccd07ff8881b56c8cd88a /host | |
parent | 7423d1691fff3af08f8e42e3e09d8c8d9ec99fe8 (diff) | |
download | uhd-a6e18604befdb6a954542f7722c8d55424065621.tar.gz uhd-a6e18604befdb6a954542f7722c8d55424065621.tar.bz2 uhd-a6e18604befdb6a954542f7722c8d55424065621.zip |
OctoClock firmware upgrade, added host driver
* OctoClock can communicate with UHD over Ethernet
* Can read NMEA strings from GPSDO and send to host
* Added multi_usrp_clock class for clock devices
* uhd::device can now filter to return only USRP devices or clock devices
* New OctoClock bootloader can accept firmware download over Ethernet
* Added octoclock_burn_eeprom,octoclock_firmware_burner utilities
* Added test_clock_synch example to show clock API
Diffstat (limited to 'host')
48 files changed, 2602 insertions, 76 deletions
diff --git a/host/CMakeLists.txt b/host/CMakeLists.txt index 340f9ba4d..0f62d9721 100644 --- a/host/CMakeLists.txt +++ b/host/CMakeLists.txt @@ -226,16 +226,16 @@ LIBUHD_REGISTER_COMPONENT("Tests" ENABLE_TESTS ON "ENABLE_LIBUHD" OFF) ######################################################################## ADD_SUBDIRECTORY(docs) +IF(ENABLE_LIBUHD) + ADD_SUBDIRECTORY(lib) +ENDIF(ENABLE_LIBUHD) + ADD_SUBDIRECTORY(include) IF(ENABLE_EXAMPLES) ADD_SUBDIRECTORY(examples) ENDIF(ENABLE_EXAMPLES) -IF(ENABLE_LIBUHD) - ADD_SUBDIRECTORY(lib) -ENDIF(ENABLE_LIBUHD) - IF(ENABLE_TESTS) ADD_SUBDIRECTORY(tests) ENDIF(ENABLE_TESTS) diff --git a/host/docs/CMakeLists.txt b/host/docs/CMakeLists.txt index cd061975b..84ed88281 100644 --- a/host/docs/CMakeLists.txt +++ b/host/docs/CMakeLists.txt @@ -94,6 +94,7 @@ ENDIF(ENABLE_MANUAL_OR_DOXYGEN) ######################################################################## SET(man_page_sources + octoclock_firmware_burner.1 uhd_cal_rx_iq_balance.1 uhd_cal_tx_dc_offset.1 uhd_cal_tx_iq_balance.1 diff --git a/host/docs/mainpage.dox b/host/docs/mainpage.dox index 535a00e22..47be99411 100644 --- a/host/docs/mainpage.dox +++ b/host/docs/mainpage.dox @@ -2,7 +2,7 @@ \tableofcontents -Welcome to the manual pages for the Universal Software Radio Peripherals (USRP) and their driver suite, the USRP Hardware Driver (UHD). Here, you will find information on how to use the USRPs and how to use the API to connect to them through your own software. +Welcome to the manual pages for the USRP Hardware Driver (UHD), the host driver for Ettus Research devices. Here, you will find information on how to use the devices and how to use the API to connect to them through your own software. # Building and Installing UHD @@ -50,6 +50,10 @@ Welcome to the manual pages for the Universal Software Radio Peripherals (USRP) \li \subpage page_usrp2 +## OctoClock + +\li \subpage page_octoclock + # API Documentation \li \subpage page_coding diff --git a/host/docs/octoclock.dox b/host/docs/octoclock.dox new file mode 100644 index 000000000..d4c6161a1 --- /dev/null +++ b/host/docs/octoclock.dox @@ -0,0 +1,126 @@ +/*! \page page_octoclock OctoClock Device Manual + +\tableofcontents + +\section octoclock_features Feature list + +- Hardware Capabilities: + - Fully integrated timing source with 8-Way distribution (10 MHz and 1 PPS) + - User selection between internal GPSDO (when present) or external 10 MHz/1 PPS source + - Source detection with automatic switch over in case of failure or disconnect + - Streaming GPS time and NMEA strings over Ethernet (OctoClock-G only) + +\section octoclock_load Loading Firmware onto the Octoclock + +\subsection bootloader OctoClock bootloader + +If you purchased your OctoClock device before Ethernet functionality was introduced, or if your unit's +bootloader has somehow become corrupted, you must burn the bootloader onto the device before you can load +the primary firmware. + +To load the bootloader onto the OctoClock, two things are needed: + +- AVR programmer +- AVRdude software + +Connect the AVR programmer to J108, as specified on the <a href="http://files.ettus.com/schematics/octoclock/octoclock.pdf"> +schematics</a>. Once you verify that the programmer is properly connected, run the following commands to burn the firmware: + + cd <install path>/share/uhd/images + avrdude -p atmega128 -c <programmer name> -P usb -U efuse:w:0xFF:m -U hfuse:w:0x80:m -U lfuse:w:0xFF:m -U flash:w:octoclock_bootloader.hex:i + +**Note:** On Linux, **sudo** must be used with the **avrdude** command. + +Once the bootloader has been burned, power-cycle your OctoClock device and refer to the below instructions on burning the OctoClock's +primary firmware. + +\subsection application Primary Octoclock firmware + +To load firmware onto the OctoClock, you must use the *octoclock_firmware_burner* utility, specifying the IP +address of the OctoClock device, as follows: + + octoclock_firmware_burner --addr=192.168.10.3 + +\section octoclock_network Setting Up Networking + +\subsection host_interface Setting up the host interface + +The OctoClock communicates with the host machine at the UDP layer over Gigabit Ethernet. The default device +of the OctoClock is **192.168.10.3**. You will need to configure the host machine's Ethernet interface with +a static IP address to enable communication. An address of **192.168.10.1** and a subnet mask of +**255.255.255.0** is recommended. + +**Note:** When using UHD software, if an IP address for the OctoClock is not specified, the software will +use UDP broadcast packets to locate the OctoClock. On some systems, the firewall will block UDP broadcast +packets. It is recommended that you change your firewall settings. + +\subsection changing_ip Changing the OctoClock's IP address + +You may need to change the OctoClock's IP address for various reasons. + +- To satisfy your particular network configuration +- To use multiple OctoClocks on the same host computer + +To change the OctoClock's IP address, run the following commands (using the default IP address as an example): + + cd <install path>/lib/uhd/utils + ./octoclock_burn_eeprom --args="<optional device args>" --values="ip-addr=192.168.10.3" + +\section addressing Addressing the Device + +There are two ways to address the OctoClock from UHD software: the IP address, and the serial. + +To use the IP address, address it as follows: + + "addr=<ip address>" + +If you want to use multiple OctoClock devices, address it as follows: + + "addr0=<ip address 1>,addr1=<ip address 2>" + +To use the serial, address it as follows: + + "serial=<serial>" + +\section hardware_setup Hardware Setup Notes + +\subsection front_panel_leds Front Panel LEDs + +The LEDs on the front panel show the current status of the device. Each LED is described below: + +- **Internal:** the device is using the internal GPSDO +- **External:** the device is using an external reference +- **Status:** the device is successfully distributing a 10 MHz and PPS signal +- **PPS:** lights up when a PPS signal is detected +- **GPS Lock:** the internal GPSDO has achieved a lock (not necessary to distribute the signals) +- **Power:** the device is receiving power + +\subsection front_panel_switch Front Panel Switch + +The front panel switch, marked **Primary Ref** determines which reference the OctoClock will prefer to use. If it is receiving +both an internal and external reference, the device will use whichever one the switch specifies. + +However, if it is only receiving an internal reference, it will use this reference no matter what position the switch is in. +The same applies for an external signal. + +\section misc Miscellaneous + +\subsection available_sensors Available Sensors + +The following sensors are available on both the OctoClock and Octoclock-G; these can be queried through the +<a href="classuhd_1_1octoclock.html">API</a>. + +- **ext_ref_detected:** whether or not the device detects an external reference +- **gps_detected:** whether or not the device detects an internal GPSDO +- **using_ref:** which reference the device is using (internal or external) +- **switch_pos:** the position of the front switch (internal or external) + +On the OctoClock-G, the following sensors are added: + +- **gps_gpgga:** the latest GPGGA string sent by the GPSDO +- **gps_gprmc:** the latest GPRMC string sent by the GPSDO +- **gps_time:** the time reported by the GPSDO +- **gps_locked:** whether or not the GPSDO is locked (true/false) +- **gps_servo:** the latest debug trace information sent by the GPSDO + +*/ diff --git a/host/docs/octoclock_firmware_burner.1 b/host/docs/octoclock_firmware_burner.1 new file mode 100644 index 000000000..0494b3b37 --- /dev/null +++ b/host/docs/octoclock_firmware_burner.1 @@ -0,0 +1,45 @@ +.TH "octoclock_firmware_burner" 1 "3.7.1" UHD "User Commands" +.SH NAME +octoclock_firmware_burner - OctoClock Firmware Burner +.SH DESCRIPTION +Burn firmware images onto an Ettus Research OctoClock device over Ethernet. +.SH SYNOPSIS +.B octoclock_firmware_burner [OPTIONS] +.SH OPTIONS +This program works best when only an IP address is specified. +.IP "Device IP Address:" +--addr=\fI"Address"\fR +.IP "This help information:" +--help +.IP "Custom Firmware Filepath:" +--fw-path=\fI"filepath"\fR +.IP "List all OctoClock devices without burning" +--list +.SH EXAMPLES +.SS Selecting a custom firmware path +.sp +octoclock_firmware_burner --addr=192.168.10.3 --fw-path=~/custom_octoclock_image.bin +.ft +.fi +.SH SEE ALSO +UHD documentation: +.B http://files.ettus.com/uhd_docs/manual/html/index.html +.LP +Other UHD programs: +.sp +uhd_images_downloader(1) usrp2_card_burner(1) usrp_n2xx_simple_net_burner(1) usrp_x3xx_fpga_burner(1) +.SH AUTHOR +This manual page was written by Nicholas Corgan +for the Debian project (but may be used by others). +.SH COPYRIGHT +Copyright (c) 2014 Ettus Research LLC +.LP +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. +.LP +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. diff --git a/host/docs/uhd_find_devices.1 b/host/docs/uhd_find_devices.1 index 416f807b9..7f92966be 100644 --- a/host/docs/uhd_find_devices.1 +++ b/host/docs/uhd_find_devices.1 @@ -2,7 +2,7 @@ .SH NAME uhd_find_devices \- USRP Hardware Driver Discovery Utility .SH DESCRIPTION -Find UHD-supporting Software Radio Peripherals attached by USB, +Find UHD-supporting Ettus Research products attached by USB, network or embedded configuration. .LP The UHD package is the universal hardware driver for Ettus Research @@ -16,7 +16,7 @@ the UHD driver standalone or with 3rd party applications. --args \fIarg\fR .IP "This help information:" --help -.SH IDENTIFYING USRP DEVICES +.SH IDENTIFYING DEVICES .sp Devices are addressed through key/value string pairs. These string pairs can be used to narrow down the search for a specific device or group of devices. @@ -28,36 +28,42 @@ Every device has several ways of identifying it on the host system. .SS Identifying by hardware identifier .sp -All USRP devices can be found through their hardware series identifier, which match to USRP +All Ettus Research devices can be found through their hardware series identifier, which match to devices as follows: -Argument | Device +Argument | Device -type=usrp1 | USRP1 +type=usrp1 | USRP1 -type=usrp2 | USRP2, USRP N200, USRP N210 +type=usrp2 | USRP2, USRP N200, USRP N210 -type=b100 | USRP B100 +type=b100 | USRP B100 -type=e100 | USRP E100, USRP E110 +type=b200 | USRP B200, USRP B210 + +type=e100 | USRP E100, USRP E110 + +type=x300 | USRP X300, USRP X310 + +type=octoclock | OctoClock .SS Identifying by serial number -All USRP devices are given a unique serial number, which can be used to identify a device as follows: +All Ettus Research devices are given a unique serial number, which can be used to identify a device as follows: serial=12345678 .SS Identifying by IP address -USRP2, USRP N200, and USRP N210 devices connected to the host machine can all be found through their +USRP2, USRP N200, USRP N210, USRP X300, USRP X310, and OctoClock devices connected to the host machine can all be found through their IP addresses, as follows: addr=192.168.10.2 .SS Identifying by name -Users have the option of giving their USRP devices short names using the usrp_burn_mb_eeprom utility -in lib/uhd/utils. Devices that have been given a name can be identified as follows: +Users have the option of giving their devices short names using the usrp_burn_mb_eeprom and octoclock_burn_eeprom +utilities in lib/uhd/utils. Devices that have been given a name can be identified as follows: name=foo diff --git a/host/docs/uhd_images_downloader.1 b/host/docs/uhd_images_downloader.1 index dd0fe208d..cbdc5c26e 100644 --- a/host/docs/uhd_images_downloader.1 +++ b/host/docs/uhd_images_downloader.1 @@ -33,7 +33,7 @@ GR-UHD documentation: .LP Other UHD programs: .sp -usrp2_card_burner(1) usrp_n2xx_simple_net_burner(1) +usrp2_card_burner(1) usrp_n2xx_simple_net_burner(1) usrp_x3xx_fpga_burner(1) octoclock_firmware_burner(1) .SH AUTHOR This manual page was written by Maitland Bottoms and Nicholas Corgan for the Debian project (but may be used by others). diff --git a/host/docs/uhd_usrp_probe.1 b/host/docs/uhd_usrp_probe.1 index ee30b2350..50ecab5c1 100644 --- a/host/docs/uhd_usrp_probe.1 +++ b/host/docs/uhd_usrp_probe.1 @@ -47,8 +47,12 @@ type=usrp2 | USRP2, USRP N200, USRP N210 type=b100 | USRP B100 +type=b200 | USRP B200, USRP B210 + type=e100 | USRP E100, USRP E110 +type=x300 | USRP X300, USRP X310 + .SS Identifying by serial number All USRP devices are given a unique serial number, which can be used to identify a device as follows: @@ -57,7 +61,7 @@ serial=12345678 .SS Identifying by IP address -USRP2, USRP N200, and USRP N210 devices connected to the host machine can all be found through their +USRP2, USRP N200, USRP N210, USRP X300, and USRP X310 devices connected to the host machine can all be found through their IP addresses, as follows: addr=192.168.10.2 diff --git a/host/docs/usrp_n2xx_simple_net_burner.1 b/host/docs/usrp_n2xx_simple_net_burner.1 index 8df38b7bd..d32b88fae 100644 --- a/host/docs/usrp_n2xx_simple_net_burner.1 +++ b/host/docs/usrp_n2xx_simple_net_burner.1 @@ -19,9 +19,9 @@ This program works best when only an IP address is specified. .IP "Custom FPGA Filepath:" --fpga=\fI"filepath"\fR .IP "Don't burn firmware:" ---no_fw +--no-fw .IP "Don't burn FPGA:" ---no_fpga +--no-fpga .IP "Automatically reboot USRP device after burning" --auto_reboot .IP "List all USRP devices without burning" @@ -41,7 +41,7 @@ GR-UHD documentation: .LP Other UHD programs: .sp -uhd_images_downloader(1) usrp2_card_burner(1) usrp_x3xx_fpga_burner(1) +uhd_images_downloader(1) usrp2_card_burner(1) usrp_x3xx_fpga_burner(1) octoclock_firmware_burner(1) .SH AUTHOR This manual page was written by Nicholas Corgan for the Debian project (but may be used by others). diff --git a/host/docs/usrp_x3xx_fpga_burner.1 b/host/docs/usrp_x3xx_fpga_burner.1 index 3a8a27e69..4ca6fd6dc 100644 --- a/host/docs/usrp_x3xx_fpga_burner.1 +++ b/host/docs/usrp_x3xx_fpga_burner.1 @@ -47,7 +47,7 @@ GR-UHD documentation: .LP Other UHD programs: .sp -uhd_images_downloader(1) usrp2_card_burner(1) usrp_n2xx_simple_net_burner(1) +uhd_images_downloader(1) usrp2_card_burner(1) usrp_n2xx_simple_net_burner(1) octoclock_firmware_burner(1) .SH AUTHOR This manual page was written by Nicholas Corgan for the Debian project (but may be used by others). diff --git a/host/examples/CMakeLists.txt b/host/examples/CMakeLists.txt index 4f394bbef..1e6f2f013 100644 --- a/host/examples/CMakeLists.txt +++ b/host/examples/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2010-2013 Ettus Research LLC +# Copyright 2010-2014 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 @@ -39,6 +39,10 @@ SET(example_sources fpgpio.cpp ) +IF(ENABLE_OCTOCLOCK) + LIST(APPEND example_sources test_clock_synch.cpp) +ENDIF(ENABLE_OCTOCLOCK) + #for each source: build an executable and install FOREACH(example_source ${example_sources}) GET_FILENAME_COMPONENT(example_name ${example_source} NAME_WE) diff --git a/host/examples/benchmark_rate.cpp b/host/examples/benchmark_rate.cpp index 9e9aa67e9..03d8f3477 100644 --- a/host/examples/benchmark_rate.cpp +++ b/host/examples/benchmark_rate.cpp @@ -215,7 +215,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //create a usrp device std::cout << std::endl; - uhd::device_addrs_t device_addrs = uhd::device::find(args); + uhd::device_addrs_t device_addrs = uhd::device::find(args, uhd::device::USRP); if (not device_addrs.empty() and device_addrs.at(0).get("type", "") == "usrp1"){ std::cerr << "*** Warning! ***" << std::endl; std::cerr << "Benchmark results will be inaccurate on USRP1 due to insufficient features.\n" << std::endl; diff --git a/host/examples/test_clock_synch.cpp b/host/examples/test_clock_synch.cpp new file mode 100644 index 000000000..7a4226345 --- /dev/null +++ b/host/examples/test_clock_synch.cpp @@ -0,0 +1,165 @@ +// +// Copyright 2014 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 <boost/format.hpp> +#include <boost/program_options.hpp> +#include <boost/thread/thread.hpp> + +#include <uhd/device.hpp> +#include <uhd/exception.hpp> +#include <uhd/usrp_clock/multi_usrp_clock.hpp> +#include <uhd/types/time_spec.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/thread_priority.hpp> + +namespace po = boost::program_options; + +using namespace uhd::usrp_clock; +using namespace uhd::usrp; + +void wait_for_pps(multi_usrp::sptr usrp, size_t chan, double timeout){ + boost::uint32_t last_pps_time = usrp->get_time_last_pps(chan).get_full_secs(); + boost::uint32_t system_time = uhd::time_spec_t::get_system_time().get_full_secs(); + boost::uint32_t exit_time = system_time + timeout; + bool detected_pps = false; + + //Otherwise, this would hang if the USRP doesn't detect any PPS + while(uhd::time_spec_t::get_system_time().get_full_secs() < exit_time){ + boost::uint32_t time_now = usrp->get_time_last_pps(chan).get_full_secs(); + if(last_pps_time < time_now){ + detected_pps = true; + break; + } + else last_pps_time = time_now; + } + if(not detected_pps) throw uhd::runtime_error(str(boost::format("%s did not detect a PPS signal.") + % usrp->get_usrp_tx_info()["mboard_serial"])); + +} + +void get_usrp_time(multi_usrp::sptr usrp, size_t chan, std::vector<boost::uint32_t> *times){ + wait_for_pps(usrp, chan, 2); + (*times)[chan] = usrp->get_time_now(chan).get_full_secs(); +} + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //Variables to be set by command line options + std::string clock_args, usrp_args; + boost::uint32_t max_interval, num_tests; + + //Set up program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "Display this help message") + ("clock-args", po::value<std::string>(&clock_args), "Clock device arguments") + ("usrp-args", po::value<std::string>(&usrp_args), "USRP device arguments") + ("max-interval", po::value<boost::uint32_t>(&max_interval)->default_value(10000), "Maximum interval between comparisons (in ms)") + ("num-tests", po::value<boost::uint32_t>(&num_tests)->default_value(10), "Number of times to compare device times") + ; + 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 << std::endl << "Test Clock Synchronization" << std::endl << std::endl; + + std::cout << "This example shows how to use a clock device to" << std::endl + << "synchronize the time on multiple USRP devices." << std::endl << std::endl; + + std::cout << desc << std::endl; + return EXIT_SUCCESS; + } + + //Create a Multi-USRP-Clock device (currently OctoClock only) + std::cout << boost::format("\nCreating the Clock device with: %s") % clock_args << std::endl; + multi_usrp_clock::sptr clock = multi_usrp_clock::make(clock_args); + + //Make sure Clock configuration is correct + if(clock->get_sensor("gps_detected").value == "false"){ + throw uhd::runtime_error("No GPSDO detected on Clock."); + } + if(clock->get_sensor("using_ref").value != "internal"){ + throw uhd::runtime_error("Clock must be using an internal reference."); + } + + //Create a Multi-USRP device + std::cout << boost::format("\nCreating the USRP device with: %s") % usrp_args << std::endl; + multi_usrp::sptr usrp = multi_usrp::make(usrp_args); + + //Store USRP device serials for useful output + std::vector<std::string> serials; + for(size_t ch = 0; ch < usrp->get_num_mboards(); ch++){ + serials.push_back(usrp->get_usrp_tx_info(ch)["mboard_serial"]); + } + + std::cout << std::endl << "Checking USRP devices for lock." << std::endl; + bool all_locked = true; + for(size_t ch = 0; ch < usrp->get_num_mboards(); ch++){ + std::string ref_locked = usrp->get_mboard_sensor("ref_locked",ch).value; + std::cout << boost::format(" * %s: %s") % serials[ch] % ref_locked << std::endl; + + if(ref_locked != "true") all_locked = false; + } + if(not all_locked) std::cout << std::endl << "WARNING: One or more devices not locked." << std::endl; + + //Get GPS time to initially set USRP devices + std::cout << std::endl << "Querying Clock for time and setting USRP times..." << std::endl << std::endl; + boost::uint32_t clock_time = clock->get_time(); + usrp->set_time_unknown_pps(uhd::time_spec_t(double(clock_time+2))); + + //Wait for next PPS to start polling + wait_for_pps(usrp, 0, 2); + + srand(time(NULL)); + + std::cout << boost::format("\nRunning %d comparisons at random intervals.") % num_tests << std::endl << std::endl; + boost::uint32_t num_matches = 0; + for(size_t i = 0; i < num_tests; i++){ + //Wait random time before querying + boost::uint16_t wait_time = rand() % max_interval; + boost::this_thread::sleep(boost::posix_time::milliseconds(wait_time)); + + //Get all times before output + std::vector<boost::uint32_t> usrp_times(usrp->get_num_mboards()); + boost::thread_group thread_group; + clock_time = clock->get_time(); + for(size_t j = 0; j < usrp->get_num_mboards(); j++){ + thread_group.create_thread(boost::bind(&get_usrp_time, usrp, j, &usrp_times)); + } + //Wait for threads to complete + thread_group.join_all(); + + std::cout << boost::format("Comparison #%d") % (i+1) << std::endl; + bool all_match = true; + std::cout << boost::format(" * Clock time: %d") % clock_time << std::endl; + for(size_t j = 0; j < usrp->get_num_mboards(); j++){ + std::cout << boost::format(" * %s time: %d") % serials[j] % usrp_times[j] << std::endl; + if(usrp_times[j] != clock_time) all_match = false; + } + if(all_match) num_matches++; + } + + std::cout << std::endl << boost::format("Number of matches: %d/%d") % num_matches % num_tests << std::endl; + + return EXIT_SUCCESS; +} diff --git a/host/examples/test_dboard_coercion.cpp b/host/examples/test_dboard_coercion.cpp index e23390506..cf6c08359 100644 --- a/host/examples/test_dboard_coercion.cpp +++ b/host/examples/test_dboard_coercion.cpp @@ -362,7 +362,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //Create a USRP device std::cout << std::endl; - uhd::device_addrs_t device_addrs = uhd::device::find(args); + uhd::device_addrs_t device_addrs = uhd::device::find(args, uhd::device::USRP); 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); std::cout << std::endl << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; diff --git a/host/examples/transport_hammer.cpp b/host/examples/transport_hammer.cpp index 3f233b2a5..32e344e3e 100644 --- a/host/examples/transport_hammer.cpp +++ b/host/examples/transport_hammer.cpp @@ -213,7 +213,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //create a usrp device std::cout << std::endl; - uhd::device_addrs_t device_addrs = uhd::device::find(args); + uhd::device_addrs_t device_addrs = uhd::device::find(args, uhd::device::USRP); if (not device_addrs.empty() and device_addrs.at(0).get("type", "") == "usrp1"){ std::cerr << "*** Warning! ***" << std::endl; std::cerr << "Results will be inaccurate on USRP1 due to insufficient features.\n" << std::endl; diff --git a/host/include/uhd/CMakeLists.txt b/host/include/uhd/CMakeLists.txt index 2827cb826..318577b7c 100644 --- a/host/include/uhd/CMakeLists.txt +++ b/host/include/uhd/CMakeLists.txt @@ -1,5 +1,5 @@ - -# Copyright 2010-2011,2013 Ettus Research LLC +# +# Copyright 2010-2011,2013-2014 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 @@ -15,10 +15,10 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # - ADD_SUBDIRECTORY(transport) ADD_SUBDIRECTORY(types) ADD_SUBDIRECTORY(usrp) +ADD_SUBDIRECTORY(usrp_clock) ADD_SUBDIRECTORY(utils) UHD_INSTALL(FILES diff --git a/host/include/uhd/device.hpp b/host/include/uhd/device.hpp index b54ffc5f7..5b4a2fe07 100644 --- a/host/include/uhd/device.hpp +++ b/host/include/uhd/device.hpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2011 Ettus Research LLC +// Copyright 2010-2011,2014 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 @@ -32,8 +32,8 @@ namespace uhd{ class property_tree; //forward declaration /*! - * The usrp device interface represents the usrp hardware. - * The api allows for discovery, configuration, and streaming. + * The device interface represents the hardware. + * The API allows for discovery, configuration, and streaming. */ class UHD_API device : boost::noncopyable{ @@ -42,6 +42,13 @@ public: typedef boost::function<device_addrs_t(const device_addr_t &)> find_t; typedef boost::function<sptr(const device_addr_t &)> make_t; + //! Device type, used as a filter in make + enum device_filter_t { + ANY, + USRP, + CLOCK + }; + /*! * Register a device into the discovery and factory system. * @@ -50,32 +57,35 @@ public: */ static void register_device( const find_t &find, - const make_t &make + const make_t &make, + const device_filter_t filter ); /*! - * \brief Find usrp devices attached to the host. + * \brief Find devices attached to the host. * * The hint device address should be used to narrow down the search * to particular transport types and/or transport arguments. * * \param hint a partially (or fully) filled in device address - * \return a vector of device addresses for all usrps on the system + * \param filter an optional filter to exclude USRP or clock devices + * \return a vector of device addresses for all devices on the system */ - static device_addrs_t find(const device_addr_t &hint); + static device_addrs_t find(const device_addr_t &hint, device_filter_t filter = ANY); /*! - * \brief Create a new usrp device from the device address hint. + * \brief Create a new device from the device address hint. * * The make routine will call find and pick one of the results. * By default, the first result will be used to create a new device. * Use the which parameter as an index into the list of results. * * \param hint a partially (or fully) filled in device address + * \param filter an optional filter to exclude USRP or clock devices * \param which which address to use when multiple are found * \return a shared pointer to a new device instance */ - static sptr make(const device_addr_t &hint, size_t which = 0); + static sptr make(const device_addr_t &hint, device_filter_t filter = ANY, size_t which = 0); /*! \brief Make a new receive streamer from the streamer arguments * @@ -94,10 +104,14 @@ public: //! Get access to the underlying property structure uhd::property_tree::sptr get_tree(void) const; + //! Get device type + device_filter_t get_device_type() const; + #include <uhd/device_deprecated.ipp> protected: uhd::property_tree::sptr _tree; + device_filter_t _type; }; } //namespace uhd diff --git a/host/include/uhd/usrp_clock/CMakeLists.txt b/host/include/uhd/usrp_clock/CMakeLists.txt new file mode 100644 index 000000000..7cd5aa9d3 --- /dev/null +++ b/host/include/uhd/usrp_clock/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright 2014 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/>. +# + +UHD_INSTALL(FILES + octoclock_eeprom.hpp + multi_usrp_clock.hpp + DESTINATION ${INCLUDE_DIR}/uhd/octoclock + COMPONENT headers +) diff --git a/host/include/uhd/usrp_clock/multi_usrp_clock.hpp b/host/include/uhd/usrp_clock/multi_usrp_clock.hpp new file mode 100644 index 000000000..0b50b32ae --- /dev/null +++ b/host/include/uhd/usrp_clock/multi_usrp_clock.hpp @@ -0,0 +1,105 @@ +// +// Copyright 2014 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/>. +// + +#ifndef INCLUDED_UHD_MULTI_USRP_CLOCK_HPP +#define INCLUDED_UHD_MULTI_USRP_CLOCK_HPP + +#include <string> +#include <vector> + +#include <uhd/config.hpp> +#include <uhd/device.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/types/sensors.hpp> + +namespace uhd{ namespace usrp_clock{ + +/*! + * The Multi-USRP-Clock device class: + * + * This class facilitates ease-of-use for must use-case scenarios when + * using clock devices with UHD. This class can be used with a + * single clock device or with multiple clock devices connected to the same + * host. + * + * To create a multi_usrp_clock out of a single USRP Clock: + * + * <pre> + * device_addr_t dev; + * dev["addr"] = 192.168.10.3; + * multi_usrp_clock::sptr clock = multi_usrp_clock::make(dev); + * </pre> + * + * To create a multi_usrp_clock out of multiple clock devices: + * + * <pre> + * device_addr_t dev; + * dev["addr0"] = 192.168.10.3; + * dev["addr1"] = 192.168.10.4; + * multi_usrp_clock::sptr clock = multi_usrp_clock::make(dev); + * </pre> + */ +class UHD_API multi_usrp_clock : boost::noncopyable { +public: + typedef boost::shared_ptr<multi_usrp_clock> sptr; + + /*! + * Make a new Multi-USRP-Clock from the given device address. + * \param dev_addr the device address + * \return a new Multi-USRP-Clock object + */ + static sptr make(const device_addr_t &dev_addr); + + /*! + * Return the underlying device. + * This allows direct access to the EEPROM and sensors. + * \return the device object within this Multi-USRP-Clock + */ + virtual device::sptr get_device(void) = 0; + + /*! + * Get a printable summary for this USRP Clock configuration. + * \return a printable string + */ + virtual std::string get_pp_string(void) = 0; + + //! Get the number of USRP Clocks in this configuration. + virtual size_t get_num_boards(void) = 0; + + //! Get time from device + virtual boost::uint32_t get_time(size_t board = 0) = 0; + + /*! + * Get a USRP Clock sensor value. + * \param name the name of the sensor + * \param board the board index (0 to M-1) + * \return a sensor value object + */ + virtual sensor_value_t get_sensor(const std::string &name, size_t board = 0) = 0; + + /*! + * Get a list of possible USRP Clock sensor names. + * \param board the board index (0 to M-1) + * \return a vector of sensor names + */ + virtual std::vector<std::string> get_sensor_names(size_t board = 0) = 0; +}; + +} //namespace +} //namespace + +#endif /* INCLUDED_UHD_MULTI_USRP_CLOCK_HPP */ diff --git a/host/include/uhd/usrp_clock/octoclock_eeprom.hpp b/host/include/uhd/usrp_clock/octoclock_eeprom.hpp new file mode 100644 index 000000000..a521000dd --- /dev/null +++ b/host/include/uhd/usrp_clock/octoclock_eeprom.hpp @@ -0,0 +1,61 @@ +// +// Copyright 2014 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/>. +// + +#ifndef INCLUDED_UHD_USRP_CLOCK_OCTOCLOCK_EEPROM_HPP +#define INCLUDED_UHD_USRP_CLOCK_OCTOCLOCK_EEPROM_HPP + +#include <uhd/config.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/dict.hpp> +#include <string> + +namespace uhd{ namespace usrp_clock{ + +/*! + * The OctoClock EEPROM object: + * Knows how to read and write the OctoClock EEPROM. + * The class inherits from a string, string dictionary. + * Use the dictionary interface to get and set values. + * Commit to the EEPROM to save changed settings. + */ +class UHD_API octoclock_eeprom_t : public uhd::dict<std::string, std::string>{ +public: + //! Make a new empty OctoClock EEPROM handler + octoclock_eeprom_t(void); + + /*! + * Make a new OctoClock EEPROM handler. + * \param transport the UDP transport to the OctoClock + */ + octoclock_eeprom_t(transport::udp_simple::sptr transport); + + /*! + * Write the contents of this object to the EEPROM. + */ + void commit() const; + +private: + transport::udp_simple::sptr xport; + void _load(); + void _store() const; + +}; + +} //namespace +} //namespace + +#endif /* INCLUDED_UHD_USRP_CLOCK_OCTOCLOCK_EEPROM_HPP */ diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt index 4ca06af9a..eed8b642c 100644 --- a/host/lib/CMakeLists.txt +++ b/host/lib/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2010-2013 Ettus Research LLC +# Copyright 2010-2014 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 @@ -72,6 +72,7 @@ INCLUDE_SUBDIRECTORY(types) INCLUDE_SUBDIRECTORY(convert) INCLUDE_SUBDIRECTORY(transport) INCLUDE_SUBDIRECTORY(usrp) +INCLUDE_SUBDIRECTORY(usrp_clock) INCLUDE_SUBDIRECTORY(utils) ######################################################################## diff --git a/host/lib/device.cpp b/host/lib/device.cpp index ff5163f2d..bd7bf5637 100644 --- a/host/lib/device.cpp +++ b/host/lib/device.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2011 Ettus Research LLC +// Copyright 2010-2011,2014 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 @@ -57,35 +57,38 @@ static size_t hash_device_addr( /*********************************************************************** * Registration **********************************************************************/ -typedef boost::tuple<device::find_t, device::make_t> dev_fcn_reg_t; +typedef boost::tuple<device::find_t, device::make_t, device::device_filter_t> dev_fcn_reg_t; // instantiate the device function registry container UHD_SINGLETON_FCN(std::vector<dev_fcn_reg_t>, get_dev_fcn_regs) void device::register_device( const find_t &find, - const make_t &make + const make_t &make, + const device_filter_t filter ){ UHD_LOGV(always) << "registering device" << std::endl; - get_dev_fcn_regs().push_back(dev_fcn_reg_t(find, make)); + get_dev_fcn_regs().push_back(dev_fcn_reg_t(find, make, filter)); } /*********************************************************************** * Discover **********************************************************************/ -device_addrs_t device::find(const device_addr_t &hint){ +device_addrs_t device::find(const device_addr_t &hint, device_filter_t filter){ boost::mutex::scoped_lock lock(_device_mutex); device_addrs_t device_addrs; BOOST_FOREACH(const dev_fcn_reg_t &fcn, get_dev_fcn_regs()){ try{ - device_addrs_t discovered_addrs = fcn.get<0>()(hint); - device_addrs.insert( - device_addrs.begin(), - discovered_addrs.begin(), - discovered_addrs.end() - ); + if(filter == ANY or fcn.get<2>() == filter){ + device_addrs_t discovered_addrs = fcn.get<0>()(hint); + device_addrs.insert( + device_addrs.begin(), + discovered_addrs.begin(), + discovered_addrs.end() + ); + } } catch(const std::exception &e){ UHD_MSG(error) << "Device discovery error: " << e.what() << std::endl; @@ -98,16 +101,18 @@ device_addrs_t device::find(const device_addr_t &hint){ /*********************************************************************** * Make **********************************************************************/ -device::sptr device::make(const device_addr_t &hint, size_t which){ +device::sptr device::make(const device_addr_t &hint, device_filter_t filter, size_t which){ boost::mutex::scoped_lock lock(_device_mutex); typedef boost::tuple<device_addr_t, make_t> dev_addr_make_t; std::vector<dev_addr_make_t> dev_addr_makers; BOOST_FOREACH(const dev_fcn_reg_t &fcn, get_dev_fcn_regs()){ - BOOST_FOREACH(device_addr_t dev_addr, fcn.get<0>()(hint)){ - //append the discovered address and its factory function - dev_addr_makers.push_back(dev_addr_make_t(dev_addr, fcn.get<1>())); + if(filter == ANY or fcn.get<2>() == filter){ + BOOST_FOREACH(device_addr_t dev_addr, fcn.get<0>()(hint)){ + //append the discovered address and its factory function + dev_addr_makers.push_back(dev_addr_make_t(dev_addr, fcn.get<1>())); + } } } @@ -159,3 +164,7 @@ device::get_tree(void) const { return _tree; } + +device::device_filter_t device::get_device_type() const { + return _type; +} diff --git a/host/lib/usrp/b100/b100_impl.cpp b/host/lib/usrp/b100/b100_impl.cpp index baf2b6ae3..26b0a5aea 100644 --- a/host/lib/usrp/b100/b100_impl.cpp +++ b/host/lib/usrp/b100/b100_impl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2012-2013 Ettus Research LLC +// Copyright 2012-2014 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 @@ -137,7 +137,7 @@ static device::sptr b100_make(const device_addr_t &device_addr){ } UHD_STATIC_BLOCK(register_b100_device){ - device::register_device(&b100_find, &b100_make); + device::register_device(&b100_find, &b100_make, device::USRP); } /*********************************************************************** @@ -148,6 +148,7 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ b100_impl_constructor_begin: initialization_count++; + _type = device::USRP; _tree = property_tree::make(); //extract the FPGA path for the B100 diff --git a/host/lib/usrp/b200/b200_impl.cpp b/host/lib/usrp/b200/b200_impl.cpp index bf5fdd251..245fbf4a9 100644 --- a/host/lib/usrp/b200/b200_impl.cpp +++ b/host/lib/usrp/b200/b200_impl.cpp @@ -146,7 +146,7 @@ static device::sptr b200_make(const device_addr_t &device_addr) UHD_STATIC_BLOCK(register_b200_device) { - device::register_device(&b200_find, &b200_make); + device::register_device(&b200_find, &b200_make, device::USRP); } /*********************************************************************** @@ -155,6 +155,7 @@ UHD_STATIC_BLOCK(register_b200_device) b200_impl::b200_impl(const device_addr_t &device_addr) { _tree = property_tree::make(); + _type = device::USRP; const fs_path mb_path = "/mboards/0"; //try to match the given device address with something on the USB bus diff --git a/host/lib/usrp/e100/e100_impl.cpp b/host/lib/usrp/e100/e100_impl.cpp index b49ba64a2..e59df7708 100644 --- a/host/lib/usrp/e100/e100_impl.cpp +++ b/host/lib/usrp/e100/e100_impl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2012 Ettus Research LLC +// Copyright 2010-2012,2014 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 @@ -96,7 +96,7 @@ static device::sptr e100_make(const device_addr_t &device_addr){ } UHD_STATIC_BLOCK(register_e100_device){ - device::register_device(&e100_find, &e100_make); + device::register_device(&e100_find, &e100_make, device::USRP); } static const uhd::dict<std::string, std::string> model_to_fpga_file_name = boost::assign::map_list_of @@ -109,6 +109,7 @@ static const uhd::dict<std::string, std::string> model_to_fpga_file_name = boost **********************************************************************/ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ _tree = property_tree::make(); + _type = device::USRP; //read the eeprom so we can determine the hardware _dev_i2c_iface = e100_ctrl::make_dev_i2c_iface(E100_I2C_DEV_NODE); diff --git a/host/lib/usrp/multi_usrp.cpp b/host/lib/usrp/multi_usrp.cpp index 71b1f8995..7d19fcb8b 100644 --- a/host/lib/usrp/multi_usrp.cpp +++ b/host/lib/usrp/multi_usrp.cpp @@ -246,7 +246,7 @@ static double derive_freq_from_xx_subdev_and_dsp( class multi_usrp_impl : public multi_usrp{ public: multi_usrp_impl(const device_addr_t &addr){ - _dev = device::make(addr); + _dev = device::make(addr, device::USRP); _tree = _dev->get_tree(); } diff --git a/host/lib/usrp/usrp1/usrp1_impl.cpp b/host/lib/usrp/usrp1/usrp1_impl.cpp index 0ba2e1e4a..709092e42 100644 --- a/host/lib/usrp/usrp1/usrp1_impl.cpp +++ b/host/lib/usrp/usrp1/usrp1_impl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2012 Ettus Research LLC +// Copyright 2010-2012,2014 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 @@ -139,7 +139,7 @@ static device::sptr usrp1_make(const device_addr_t &device_addr){ } UHD_STATIC_BLOCK(register_usrp1_device){ - device::register_device(&usrp1_find, &usrp1_make); + device::register_device(&usrp1_find, &usrp1_make, device::USRP); } /*********************************************************************** @@ -147,6 +147,7 @@ UHD_STATIC_BLOCK(register_usrp1_device){ **********************************************************************/ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ UHD_MSG(status) << "Opening a USRP1 device..." << std::endl; + _type = device::USRP; //extract the FPGA path for the USRP1 std::string usrp1_fpga_image = find_image_path( diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp index 918f3e892..ae2a6f81d 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.cpp +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2012 Ettus Research LLC +// Copyright 2010-2012,2014 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 @@ -199,7 +199,7 @@ static device::sptr usrp2_make(const device_addr_t &device_addr){ } UHD_STATIC_BLOCK(register_usrp2_device){ - device::register_device(&usrp2_find, &usrp2_make); + device::register_device(&usrp2_find, &usrp2_make, device::USRP); } /*********************************************************************** @@ -367,6 +367,7 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr){ // create controller objects and initialize the properties tree //////////////////////////////////////////////////////////////////// _tree = property_tree::make(); + _type = device::USRP; _tree->create<std::string>("/name").set("USRP2 / N-Series Device"); for (size_t mbi = 0; mbi < device_args.size(); mbi++){ diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp index 0de81e11f..bb59763ee 100644 --- a/host/lib/usrp/x300/x300_impl.cpp +++ b/host/lib/usrp/x300/x300_impl.cpp @@ -326,7 +326,7 @@ static device::sptr x300_make(const device_addr_t &device_addr) UHD_STATIC_BLOCK(register_x300_device) { - device::register_device(&x300_find, &x300_make); + device::register_device(&x300_find, &x300_make, device::USRP); } static void x300_load_fw(wb_iface::sptr fw_reg_ctrl, const std::string &file_name) @@ -355,6 +355,7 @@ static void x300_load_fw(wb_iface::sptr fw_reg_ctrl, const std::string &file_nam x300_impl::x300_impl(const uhd::device_addr_t &dev_addr) { UHD_MSG(status) << "X300 initialization sequence..." << std::endl; + _type = device::USRP; _async_md.reset(new async_md_type(1000/*messages deep*/)); _tree = uhd::property_tree::make(); _tree->create<std::string>("/name").set("X-Series Device"); diff --git a/host/lib/usrp_clock/CMakeLists.txt b/host/lib/usrp_clock/CMakeLists.txt new file mode 100644 index 000000000..8a58aa9ac --- /dev/null +++ b/host/lib/usrp_clock/CMakeLists.txt @@ -0,0 +1,26 @@ +# +# Copyright 2011-2014 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/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/multi_usrp_clock.cpp +) + +INCLUDE_SUBDIRECTORY(octoclock) diff --git a/host/lib/usrp_clock/multi_usrp_clock.cpp b/host/lib/usrp_clock/multi_usrp_clock.cpp new file mode 100644 index 000000000..77489e13b --- /dev/null +++ b/host/lib/usrp_clock/multi_usrp_clock.cpp @@ -0,0 +1,93 @@ +// +// Copyright 2010-2013 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/property_tree.hpp> +#include <uhd/usrp_clock/multi_usrp_clock.hpp> +#include <uhd/usrp_clock/octoclock_eeprom.hpp> + +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> + +using namespace uhd; +using namespace uhd::usrp_clock; + +/*********************************************************************** + * Multi USRP Clock implementation + **********************************************************************/ +class multi_usrp_clock_impl : public multi_usrp_clock{ +public: + multi_usrp_clock_impl(const device_addr_t &addr){ + _dev = device::make(addr, device::CLOCK); + _tree = _dev->get_tree(); + } + + device::sptr get_device(void){ + return _dev; + } + + std::string get_pp_string(void){ + std::string buff = str(boost::format("%s USRP Clock Device\n") + % ((get_num_boards() > 1) ? "Multi" : "Single") + ); + for(size_t i = 0; i < get_num_boards(); i++){ + buff += str(boost::format(" Board %s\n") % i); + buff += str(boost::format(" Reference: %s\n") + % (get_sensor("using_ref", i).value) + ); + } + + return buff; + } + + size_t get_num_boards(void){ + return _tree->list("/mboards").size(); + } + + boost::uint32_t get_time(size_t board){ + std::string board_str = str(boost::format("/mboards/%d") % board); + + return _tree->access<boost::uint32_t>(board_str / "time").get(); + } + + sensor_value_t get_sensor(const std::string &name, size_t board){ + std::string board_str = str(boost::format("/mboards/%d") % board); + + return _tree->access<sensor_value_t>(board_str / "sensors" / name).get(); + } + + std::vector<std::string> get_sensor_names(size_t board){ + std::string board_str = str(boost::format("/mboards/%d") % board); + + return _tree->list(board_str / "sensors"); + } + +private: + device::sptr _dev; + property_tree::sptr _tree; +}; + +/*********************************************************************** + * Multi USRP Clock factory function + **********************************************************************/ +multi_usrp_clock::sptr multi_usrp_clock::make(const device_addr_t &dev_addr){ + UHD_LOG << "multi_usrp_clock::make with args " << dev_addr.to_pp_string() << std::endl; + return sptr(new multi_usrp_clock_impl(dev_addr)); +} diff --git a/host/lib/usrp_clock/octoclock/CMakeLists.txt b/host/lib/usrp_clock/octoclock/CMakeLists.txt new file mode 100644 index 000000000..e363bb9da --- /dev/null +++ b/host/lib/usrp_clock/octoclock/CMakeLists.txt @@ -0,0 +1,33 @@ +# +# Copyright 2011-2014 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/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the OctoClock support +######################################################################## +LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF) + +IF(ENABLE_OCTOCLOCK) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/octoclock_eeprom.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/octoclock_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/octoclock_uart.cpp + ) +ENDIF(ENABLE_OCTOCLOCK) diff --git a/host/lib/usrp_clock/octoclock/common.h b/host/lib/usrp_clock/octoclock/common.h new file mode 100644 index 000000000..96acbb30f --- /dev/null +++ b/host/lib/usrp_clock/octoclock/common.h @@ -0,0 +1,149 @@ +/* + * Copyright 2014 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/>. + */ + +#ifndef _OCTOCLOCK_COMMON_H_ +#define _OCTOCLOCK_COMMON_H_ + +#include <stdint.h> + +/* + * C++ macros used for code cleanliness and extern "C" declaration. + */ +#ifdef __cplusplus + +#define UHD_OCTOCLOCK_SEND_AND_RECV(xport, pkt_code, pkt_out, len, data) pkt_out.proto_ver = OCTOCLOCK_FW_COMPAT_NUM; \ + pkt_out.code = pkt_code; \ + xport->send(boost::asio::buffer(&pkt_out, sizeof(octoclock_packet_t))); \ + len = xport->recv(boost::asio::buffer(data), 2); + +#define UHD_OCTOCLOCK_PACKET_MATCHES(pkt_code, pkt_out, pkt_in, len) (len > offsetof(octoclock_packet_t, data) and \ + pkt_in->sequence == pkt_out.sequence and \ + pkt_in->code == pkt_code) + +extern "C" { +#endif + +/* + * This code is used by both the C firmware and C++ host driver, so + * only valid C code should go in this section. + */ + +//These values are placed in the octoclock_packet_t.proto_ver field +#define OCTOCLOCK_BOOTLOADER_PROTO_VER 1234 +#define OCTOCLOCK_FW_COMPAT_NUM 2 + +//UDP ports assigned for different tasks +#define OCTOCLOCK_UDP_CTRL_PORT 50000 +#define OCTOCLOCK_UDP_GPSDO_PORT 50001 +#define OCTOCLOCK_UDP_FW_PORT 50002 +#define OCTOCLOCK_UDP_EEPROM_PORT 50003 + +typedef enum { + NO_CODE, + + OCTOCLOCK_QUERY_CMD, + OCTOCLOCK_QUERY_ACK, + + SEND_EEPROM_CMD, + SEND_EEPROM_ACK, + BURN_EEPROM_CMD, + BURN_EEPROM_SUCCESS_ACK, + BURN_EEPROM_FAILURE_ACK, + CLEAR_EEPROM_CMD, + CLEAR_EEPROM_ACK, + + SEND_STATE_CMD, + SEND_STATE_ACK, + + RESET_CMD, + RESET_ACK, + + HOST_SEND_TO_GPSDO_CMD, + HOST_SEND_TO_GPSDO_ACK, + SEND_POOLSIZE_CMD, + SEND_POOLSIZE_ACK, + SEND_CACHE_STATE_CMD, + SEND_CACHE_STATE_ACK, + SEND_GPSDO_CACHE_CMD, + SEND_GPSDO_CACHE_ACK, + + PREPARE_FW_BURN_CMD, + FW_BURN_READY_ACK, + FILE_TRANSFER_CMD, + FILE_TRANSFER_ACK, + READ_FW_CMD, + READ_FW_ACK, + FINALIZE_BURNING_CMD, + FINALIZE_BURNING_ACK, +} packet_code_t; + +typedef enum { + NO_REF, + INTERNAL, + EXTERNAL +} ref_t; + +typedef enum { + UP, + DOWN +} switch_pos_t; + +#pragma pack(push,1) + +// Structure of values in EEPROM, starting in location 0 +typedef struct { + uint8_t mac_addr[6]; + uint32_t ip_addr; + uint32_t dr_addr; + uint32_t netmask; + uint8_t serial[10]; + uint8_t name[10]; + uint8_t revision; +} octoclock_fw_eeprom_t; + +typedef struct { + uint8_t external_detected; + uint8_t gps_detected; + uint8_t which_ref; + uint8_t switch_pos; +} octoclock_state_t; + +typedef struct { + uint8_t num_wraps; + uint8_t pos; +} gpsdo_cache_state_t; + +typedef struct { + uint32_t proto_ver; + uint32_t sequence; + uint8_t code; + union { + uint16_t len; + gpsdo_cache_state_t state; + uint16_t poolsize; + uint16_t addr; + }; + uint8_t data[256]; +} octoclock_packet_t; + +#pragma pack(pop) + +#ifdef __cplusplus +} +#endif + +#endif /* _OCTOCLOCK_COMMON_H_ */ diff --git a/host/lib/usrp_clock/octoclock/octoclock_eeprom.cpp b/host/lib/usrp_clock/octoclock/octoclock_eeprom.cpp new file mode 100644 index 000000000..6d54cac70 --- /dev/null +++ b/host/lib/usrp_clock/octoclock/octoclock_eeprom.cpp @@ -0,0 +1,184 @@ +// +// Copyright 2014 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/exception.hpp> +#include <uhd/usrp_clock/octoclock_eeprom.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/types/mac_addr.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/asio.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/foreach.hpp> + +#include <iostream> + +#include "common.h" + +typedef boost::asio::ip::address_v4 ip_v4; + +using namespace uhd; +using namespace uhd::usrp_clock; +using namespace uhd::transport; + +/*********************************************************************** + * Utility functions + **********************************************************************/ + +//! A wrapper around std::copy that takes ranges instead of iterators. +template<typename RangeSrc, typename RangeDst> inline +void byte_copy(const RangeSrc &src, RangeDst &dst){ + std::copy(boost::begin(src), boost::end(src), boost::begin(dst)); +} + +//! create a string from a byte vector, return empty if invalid ascii +static const std::string bytes_to_string(const byte_vector_t &bytes){ + std::string out; + BOOST_FOREACH(boost::uint8_t byte, bytes){ + if (byte < 32 or byte > 127) return out; + out += byte; + } + return out; +} + +/*********************************************************************** + * Implementation + **********************************************************************/ +void octoclock_eeprom_t::_load(){ + boost::uint32_t octoclock_data[udp_simple::mtu]; + const octoclock_packet_t *pkt_in = reinterpret_cast<const octoclock_packet_t*>(octoclock_data); + const octoclock_fw_eeprom_t *eeprom_in = reinterpret_cast<const octoclock_fw_eeprom_t*>(pkt_in->data); + + octoclock_packet_t pkt_out; + pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + size_t len = 0; + + UHD_OCTOCLOCK_SEND_AND_RECV(xport, SEND_EEPROM_CMD, pkt_out, len, octoclock_data); + if(UHD_OCTOCLOCK_PACKET_MATCHES(SEND_EEPROM_ACK, pkt_out, pkt_in, len)){ + //MAC address + byte_vector_t mac_bytes(eeprom_in->mac_addr, eeprom_in->mac_addr+6); + (*this)["mac-addr"] = mac_addr_t::from_bytes(mac_bytes).to_string(); + + //IP address + boost::uint32_t ip_addr = uhd::htonx<boost::uint32_t>(eeprom_in->ip_addr); + ip_v4::bytes_type ip_addr_bytes; + memcpy(&ip_addr_bytes, &ip_addr, 4); + (*this)["ip-addr"] = ip_v4(ip_addr_bytes).to_string(); + + //Default router + boost::uint32_t dr_addr = uhd::htonx<boost::uint32_t>(eeprom_in->dr_addr); + ip_v4::bytes_type dr_addr_bytes; + memcpy(&dr_addr_bytes, &dr_addr, 4); + (*this)["gateway"] = ip_v4(dr_addr_bytes).to_string(); + + //Netmask + boost::uint32_t netmask = uhd::htonx<boost::uint32_t>(eeprom_in->netmask); + ip_v4::bytes_type netmask_bytes; + memcpy(&netmask_bytes, &netmask, 4); + (*this)["netmask"] = ip_v4(netmask_bytes).to_string(); + + //Serial + std::string raw_serial((char*)eeprom_in->serial, 10); + byte_vector_t serial_bytes(raw_serial.begin(), raw_serial.end()); + (*this)["serial"] = bytes_to_string(serial_bytes); + + //Name + std::string raw_name((char*)eeprom_in->name, 10); + byte_vector_t name_bytes(raw_name.begin(), raw_name.end()); + (*this)["name"] = bytes_to_string(name_bytes); + + //Revision + (*this)["revision"] = boost::lexical_cast<std::string>(int(eeprom_in->revision)); + } + else throw uhd::runtime_error("Error loading OctoClock EEPROM."); +} + +void octoclock_eeprom_t::_store() const { + boost::uint32_t octoclock_data[udp_simple::mtu]; + const octoclock_packet_t *pkt_in = reinterpret_cast<const octoclock_packet_t *>(octoclock_data); + + octoclock_packet_t pkt_out; + pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + pkt_out.len = sizeof(octoclock_fw_eeprom_t); + size_t len = 0; + + octoclock_fw_eeprom_t *eeprom_out = reinterpret_cast<octoclock_fw_eeprom_t *>(&pkt_out.data); + memset(eeprom_out, 0xFF, sizeof(octoclock_fw_eeprom_t)); + + //MAC address + if((*this).has_key("mac-addr")){ + byte_copy(mac_addr_t::from_string((*this)["mac-addr"]).to_bytes(), eeprom_out->mac_addr); + } + + //IP address + if((*this).has_key("ip-addr")){ + ip_v4::bytes_type ip_addr_bytes = ip_v4::from_string((*this)["ip-addr"]).to_bytes(); + memcpy(&eeprom_out->ip_addr, &ip_addr_bytes, 4); + eeprom_out->ip_addr = uhd::htonx<boost::uint32_t>(eeprom_out->ip_addr); + } + + //Default router + if((*this).has_key("gateway")){ + ip_v4::bytes_type dr_addr_bytes = ip_v4::from_string((*this)["gateway"]).to_bytes(); + memcpy(&eeprom_out->dr_addr, &dr_addr_bytes, 4); + eeprom_out->dr_addr = uhd::htonx<boost::uint32_t>(eeprom_out->dr_addr); + } + + //Netmask + if((*this).has_key("netmask")){ + ip_v4::bytes_type netmask_bytes = ip_v4::from_string((*this)["netmask"]).to_bytes(); + memcpy(&eeprom_out->netmask, &netmask_bytes, 4); + eeprom_out->netmask = uhd::htonx<boost::uint32_t>(eeprom_out->netmask); + } + + //Serial + if((*this).has_key("serial")){ + byte_copy(byte_vector_t((*this)["serial"].begin(), (*this)["serial"].end()), eeprom_out->serial); + } + + //Name + if((*this).has_key("name")){ + byte_copy(byte_vector_t((*this)["name"].begin(), (*this)["name"].end()), eeprom_out->name); + } + + //Revision + if((*this).has_key("revision")){ + eeprom_out->revision = (*this)["revision"][0]-'0'; + } + + UHD_OCTOCLOCK_SEND_AND_RECV(xport, BURN_EEPROM_CMD, pkt_out, len, octoclock_data); + if(not UHD_OCTOCLOCK_PACKET_MATCHES(BURN_EEPROM_SUCCESS_ACK, pkt_out, pkt_in, len)) + throw uhd::runtime_error("Error writing to OctoClock EEPROM."); +} + +/*********************************************************************** + * Implementation of OctoClock EEPROM + **********************************************************************/ +octoclock_eeprom_t::octoclock_eeprom_t(void){ + /* NOP */ +} + +octoclock_eeprom_t::octoclock_eeprom_t(udp_simple::sptr transport){ + xport = transport; + _load(); +} + +void octoclock_eeprom_t::commit() const{ + if(!xport) throw uhd::runtime_error("There is no set device communication."); + _store(); +} diff --git a/host/lib/usrp_clock/octoclock/octoclock_impl.cpp b/host/lib/usrp_clock/octoclock/octoclock_impl.cpp new file mode 100644 index 000000000..add5d7931 --- /dev/null +++ b/host/lib/usrp_clock/octoclock/octoclock_impl.cpp @@ -0,0 +1,436 @@ +// +// Copyright 2014 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 <boost/asio.hpp> +#include <boost/assign.hpp> +#include <boost/cstdint.hpp> +#include <boost/filesystem.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> + +#include <uhd/device.hpp> +#include <uhd/exception.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/usrp_clock/octoclock_eeprom.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/images.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/static.hpp> + +#include "octoclock_impl.hpp" +#include "octoclock_uart.hpp" +#include "common.h" + +using namespace uhd; +using namespace uhd::usrp_clock; +using namespace uhd::transport; +namespace asio = boost::asio; +namespace fs = boost::filesystem; + +/*********************************************************************** + * Discovery + **********************************************************************/ +device_addrs_t octoclock_find(const device_addr_t &hint){ + //Handle the multi-device discovery + device_addrs_t hints = separate_device_addr(hint); + if (hints.size() > 1){ + device_addrs_t found_devices; + std::string error_msg; + BOOST_FOREACH(const device_addr_t &hint_i, hints){ + device_addrs_t found_devices_i = octoclock_find(hint_i); + if (found_devices_i.size() != 1) error_msg += str(boost::format( + "Could not resolve device hint \"%s\" to a single device." + ) % hint_i.to_string()); + else found_devices.push_back(found_devices_i[0]); + } + if (found_devices.empty()) return device_addrs_t(); + if (not error_msg.empty()) throw uhd::value_error(error_msg); + return device_addrs_t(1, combine_device_addrs(found_devices)); + } + + //Initialize the hint for a single device case + UHD_ASSERT_THROW(hints.size() <= 1); + hints.resize(1); //In case it was empty + device_addr_t _hint = hints[0]; + device_addrs_t octoclock_addrs; + + //return an empty list of addresses when type is set to non-usrp2 + if (hint.has_key("type") and hint["type"].find("octoclock") == std::string::npos) return octoclock_addrs; + + //Return an empty list of addresses when a resource is specified, + //since a resource is intended for a different, non-USB, device. + if (hint.has_key("resource")) return octoclock_addrs; + + //If no address was specified, send a broadcast on each interface + if (not _hint.has_key("addr")){ + BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()){ + //avoid the loopback device + if (if_addrs.inet == asio::ip::address_v4::loopback().to_string()) continue; + + //create a new hint with this broadcast address + device_addr_t new_hint = hint; + new_hint["addr"] = if_addrs.bcast; + + //call discover with the new hint and append results + device_addrs_t new_octoclock_addrs = octoclock_find(new_hint); + octoclock_addrs.insert(octoclock_addrs.begin(), + new_octoclock_addrs.begin(), new_octoclock_addrs.end() + ); + } + return octoclock_addrs; + } + + //Create a UDP transport to communicate + udp_simple::sptr udp_transport = udp_simple::make_broadcast( + _hint["addr"], + BOOST_STRINGIZE(OCTOCLOCK_UDP_CTRL_PORT) + ); + + //Send a query packet + octoclock_packet_t pkt_out; + pkt_out.proto_ver = OCTOCLOCK_FW_COMPAT_NUM; + pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + pkt_out.len = 0; + pkt_out.code = OCTOCLOCK_QUERY_CMD; + try{ + udp_transport->send(boost::asio::buffer(&pkt_out, sizeof(pkt_out))); + } + catch(const std::exception &ex){ + UHD_MSG(error) << "OctoClock network discovery error - " << ex.what() << std::endl; + } + catch(...){ + UHD_MSG(error) << "OctoClock network discovery unknown error" << std::endl; + } + + boost::uint8_t octoclock_data[udp_simple::mtu]; + const octoclock_packet_t *pkt_in = reinterpret_cast<octoclock_packet_t*>(octoclock_data); + + while(true){ + size_t len = udp_transport->recv(asio::buffer(octoclock_data), 2); + if(UHD_OCTOCLOCK_PACKET_MATCHES(OCTOCLOCK_QUERY_ACK, pkt_out, pkt_in, len)){ + device_addr_t new_addr; + new_addr["type"] = (pkt_in->proto_ver == OCTOCLOCK_FW_COMPAT_NUM) ? "octoclock" + : "octoclock-bootloader"; + new_addr["addr"] = udp_transport->get_recv_addr(); + + //Attempt direct communication with OctoClock + udp_simple::sptr ctrl_xport = udp_simple::make_connected( + new_addr["addr"], + BOOST_STRINGIZE(OCTOCLOCK_UDP_CTRL_PORT) + ); + UHD_OCTOCLOCK_SEND_AND_RECV(ctrl_xport, OCTOCLOCK_QUERY_CMD, pkt_out, len, octoclock_data); + if(UHD_OCTOCLOCK_PACKET_MATCHES(OCTOCLOCK_QUERY_ACK, pkt_out, pkt_in, len)){ + //If the OctoClock is in its bootloader, don't ask for details + if(pkt_in->proto_ver == OCTOCLOCK_BOOTLOADER_PROTO_VER){ + octoclock_addrs.push_back(new_addr); + } + else{ + octoclock_eeprom_t oc_eeprom(ctrl_xport); + new_addr["name"] = oc_eeprom["name"]; + new_addr["serial"] = oc_eeprom["serial"]; + + //Filter based on optional keys (if any) + if( + (not _hint.has_key("name") or (_hint["name"] == new_addr["name"])) and + (not _hint.has_key("serial") or (_hint["serial"] == new_addr["serial"])) + ){ + octoclock_addrs.push_back(new_addr); + } + } + } + else continue; + } + + if(len == 0) break; + } + + return octoclock_addrs; +} + +device::sptr octoclock_make(const device_addr_t &device_addr){ + return device::sptr(new octoclock_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_octoclock_device){ + device::register_device(&octoclock_find, &octoclock_make, device::CLOCK); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +octoclock_impl::octoclock_impl(const device_addr_t &_device_addr){ + UHD_MSG(status) << "Opening an OctoClock device..." << std::endl; + _type = device::CLOCK; + device_addrs_t device_args = separate_device_addr(_device_addr); + _sequence = std::rand(); + + //////////////////////////////////////////////////////////////////// + // Initialize the property tree + //////////////////////////////////////////////////////////////////// + _tree = property_tree::make(); + _tree->create<std::string>("/name").set("OctoClock Device"); + + for(size_t oci = 0; oci < device_args.size(); oci++){ + const device_addr_t device_args_i = device_args[oci]; + const std::string addr = device_args_i["addr"]; + //Can't make a device out of an OctoClock in bootloader state + if(device_args_i["type"] == "octoclock-bootloader"){ + throw uhd::runtime_error(str(boost::format( + "\n\nThis device is in its bootloader state and cannot be used by UHD.\n" + "This may mean the firmware on the device has been corrupted and will\n" + "need to be burned again.\n\n" + "%s\n" + ) % _get_images_help_message(addr))); + } + + const std::string oc = boost::lexical_cast<std::string>(oci); + + //////////////////////////////////////////////////////////////////// + // Set up UDP transports + //////////////////////////////////////////////////////////////////// + _oc_dict[oc].ctrl_xport = udp_simple::make_connected(addr, BOOST_STRINGIZE(OCTOCLOCK_UDP_CTRL_PORT)); + _oc_dict[oc].gpsdo_xport = udp_simple::make_connected(addr, BOOST_STRINGIZE(OCTOCLOCK_UDP_GPSDO_PORT)); + + const fs_path oc_path = "/mboards/" + oc; + _tree->create<std::string>(oc_path / "name").set("OctoClock"); + + //////////////////////////////////////////////////////////////////// + // Check the firmware compatibility number + //////////////////////////////////////////////////////////////////// + boost::uint32_t fw_version = _get_fw_version(oc); + if(fw_version != OCTOCLOCK_FW_COMPAT_NUM){ + throw uhd::runtime_error(str(boost::format( + "\n\nPlease update your OctoClock's firmware.\n" + "Expected firmware compatibility number %d, but got %d:\n" + "The firmware build is not compatible with the host code build.\n\n" + "%s\n" + ) % int(OCTOCLOCK_FW_COMPAT_NUM) % int(fw_version) % _get_images_help_message(addr))); + } + _tree->create<std::string>(oc_path / "fw_version").set(boost::lexical_cast<std::string>(int(fw_version))); + + //////////////////////////////////////////////////////////////////// + // Set up EEPROM + //////////////////////////////////////////////////////////////////// + _oc_dict[oc].eeprom = octoclock_eeprom_t(_oc_dict[oc].ctrl_xport); + _tree->create<octoclock_eeprom_t>(oc_path / "eeprom") + .set(_oc_dict[oc].eeprom) + .subscribe(boost::bind(&octoclock_impl::_set_eeprom, this, oc, _1)); + + //////////////////////////////////////////////////////////////////// + // Initialize non-GPSDO sensors + //////////////////////////////////////////////////////////////////// + _tree->create<boost::uint32_t>(oc_path / "time") + .publish(boost::bind(&octoclock_impl::_get_time, this, oc)); + _tree->create<sensor_value_t>(oc_path / "sensors/ext_ref_detected") + .publish(boost::bind(&octoclock_impl::_ext_ref_detected, this, oc)); + _tree->create<sensor_value_t>(oc_path / "sensors/gps_detected") + .publish(boost::bind(&octoclock_impl::_gps_detected, this, oc)); + _tree->create<sensor_value_t>(oc_path / "sensors/using_ref") + .publish(boost::bind(&octoclock_impl::_which_ref, this, oc)); + _tree->create<sensor_value_t>(oc_path / "sensors/switch_pos") + .publish(boost::bind(&octoclock_impl::_switch_pos, this, oc)); + + //////////////////////////////////////////////////////////////////// + // Check reference and GPSDO + //////////////////////////////////////////////////////////////////// + std::string asterisk = (device_args.size() > 1) ? " * " : ""; + + if(device_args.size() > 1){ + UHD_MSG(status) << std::endl << "Checking status of " << addr << ":" << std::endl; + } + UHD_MSG(status) << boost::format("%sDetecting internal GPSDO...") % asterisk << std::flush; + + _get_state(oc); + if(_oc_dict[oc].state.gps_detected){ + try{ + _oc_dict[oc].gps = gps_ctrl::make(octoclock_make_uart_iface(_oc_dict[oc].gpsdo_xport)); + + if(_oc_dict[oc].gps and _oc_dict[oc].gps->gps_detected()){ + BOOST_FOREACH(const std::string &name, _oc_dict[oc].gps->get_sensors()){ + _tree->create<sensor_value_t>(oc_path / "sensors" / name) + .publish(boost::bind(&gps_ctrl::get_sensor, _oc_dict[oc].gps, name)); + } + } + else{ + //If GPSDO communication failed, set gps_detected to false + _oc_dict[oc].state.gps_detected = 0; + UHD_MSG(warning) << "Device reports that it has a GPSDO, but we cannot communicate with it." << std::endl; + std::cout << std::endl; + } + } + catch(std::exception &e){ + UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl; + } + } + else UHD_MSG(status) << "No GPSDO found" << std::endl; + UHD_MSG(status) << boost::format("%sDetecting external reference...%s") % asterisk + % _ext_ref_detected(oc).value + << std::endl; + UHD_MSG(status) << boost::format("%sDetecting switch position...%s") % asterisk + % _switch_pos(oc).value + << std::endl; + std::string ref = _which_ref(oc).value; + if(ref == "none") UHD_MSG(status) << boost::format("%sDevice is not using any reference") % asterisk << std::endl; + else UHD_MSG(status) << boost::format("%sDevice is using %s reference") % asterisk + % _which_ref(oc).value + << std::endl; + } +} + +rx_streamer::sptr octoclock_impl::get_rx_stream(UHD_UNUSED(const stream_args_t &args)){ + throw uhd::not_implemented_error("This function is incompatible with this device."); +} + +tx_streamer::sptr octoclock_impl::get_tx_stream(UHD_UNUSED(const stream_args_t &args)){ + throw uhd::not_implemented_error("This function is incompatible with this device."); +} + +bool octoclock_impl::recv_async_msg(UHD_UNUSED(uhd::async_metadata_t&), UHD_UNUSED(double)){ + throw uhd::not_implemented_error("This function is incompatible with this device."); +} + +void octoclock_impl::_set_eeprom(const std::string &oc, const octoclock_eeprom_t &oc_eeprom){ + /* + * The OctoClock needs a full octoclock_eeprom_t so as to not erase + * what it currently has in the EEPROM, so store the relevant values + * from the user's input and send that instead. + */ + BOOST_FOREACH(const std::string &key, oc_eeprom.keys()){ + if(_oc_dict[oc].eeprom.has_key(key)) _oc_dict[oc].eeprom[key] = oc_eeprom[key]; + } + _oc_dict[oc].eeprom.commit(); +} + +boost::uint32_t octoclock_impl::_get_fw_version(const std::string &oc){ + octoclock_packet_t pkt_out; + pkt_out.sequence = _sequence++; + pkt_out.len = 0; + size_t len; + + boost::uint8_t octoclock_data[udp_simple::mtu]; + const octoclock_packet_t *pkt_in = reinterpret_cast<octoclock_packet_t*>(octoclock_data); + + UHD_OCTOCLOCK_SEND_AND_RECV(_oc_dict[oc].ctrl_xport, OCTOCLOCK_QUERY_CMD, pkt_out, len, octoclock_data); + if(UHD_OCTOCLOCK_PACKET_MATCHES(OCTOCLOCK_QUERY_ACK, pkt_out, pkt_in, len)){ + return pkt_in->proto_ver; + } + else throw uhd::runtime_error("Failed to retrive firmware version from OctoClock."); +} + +void octoclock_impl::_get_state(const std::string &oc){ + octoclock_packet_t pkt_out; + pkt_out.sequence = _sequence++; + pkt_out.len = 0; + size_t len = 0; + + boost::uint8_t octoclock_data[udp_simple::mtu]; + const octoclock_packet_t *pkt_in = reinterpret_cast<octoclock_packet_t*>(octoclock_data); + + UHD_OCTOCLOCK_SEND_AND_RECV(_oc_dict[oc].ctrl_xport, SEND_STATE_CMD, pkt_out, len, octoclock_data); + if(UHD_OCTOCLOCK_PACKET_MATCHES(SEND_STATE_ACK, pkt_out, pkt_in, len)){ + const octoclock_state_t *state = reinterpret_cast<const octoclock_state_t*>(pkt_in->data); + _oc_dict[oc].state = *state; + } + else throw uhd::runtime_error("Failed to retrieve state information from OctoClock."); +} + +uhd::dict<ref_t, std::string> _ref_strings = boost::assign::map_list_of + (NO_REF, "none") + (INTERNAL, "internal") + (EXTERNAL, "external") +; + +uhd::dict<switch_pos_t, std::string> _switch_pos_strings = boost::assign::map_list_of + (UP, "Prefer internal") + (DOWN, "Prefer external") +; + +sensor_value_t octoclock_impl::_ext_ref_detected(const std::string &oc){ + _get_state(oc); + + return sensor_value_t("External reference detected", (_oc_dict[oc].state.external_detected > 0), + "true", "false"); +} + +sensor_value_t octoclock_impl::_gps_detected(const std::string &oc){ + //Don't check, this shouldn't change once device is turned on + + return sensor_value_t("GPSDO detected", (_oc_dict[oc].state.gps_detected > 0), + "true", "false"); +} + +sensor_value_t octoclock_impl::_which_ref(const std::string &oc){ + _get_state(oc); + + if(not _ref_strings.has_key(ref_t(_oc_dict[oc].state.which_ref))){ + throw uhd::runtime_error("Invalid reference detected."); + } + + return sensor_value_t("Using reference", _ref_strings[ref_t(_oc_dict[oc].state.which_ref)], ""); +} + +sensor_value_t octoclock_impl::_switch_pos(const std::string &oc){ + _get_state(oc); + + if(not _switch_pos_strings.has_key(switch_pos_t(_oc_dict[oc].state.switch_pos))){ + throw uhd::runtime_error("Invalid switch position detected."); + } + + return sensor_value_t("Switch position", _switch_pos_strings[switch_pos_t(_oc_dict[oc].state.switch_pos)], ""); +} + +boost::uint32_t octoclock_impl::_get_time(const std::string &oc){ + if(_oc_dict[oc].state.gps_detected){ + std::string time_str = _oc_dict[oc].gps->get_sensor("gps_time").value; + return boost::lexical_cast<boost::uint32_t>(time_str); + } + else throw uhd::runtime_error("This device cannot return a time."); +} + +std::string octoclock_impl::_get_images_help_message(const std::string &addr){ + const std::string image_name = "octoclock_r4_fw.bin"; + + //Check to see if image is in default location + std::string image_location; + try{ + image_location = uhd::find_image_path(image_name); + } + catch(const std::exception &e){ + return str(boost::format("Could not find %s in your images path.\n%s") + % image_name + % uhd::print_images_error()); + } + + //Get escape character + #ifdef UHD_PLATFORM_WIN32 + const std::string ml = "^\n "; + #else + const std::string ml = "\\\n "; + #endif + + //Get burner command + const std::string burner_path = (fs::path(uhd::get_pkg_path()) / "bin" / "octoclock_firmware_burner").string(); + const std::string burner_cmd = str(boost::format("%s %s--addr=\"%s\"") % burner_path % ml % addr); + return str(boost::format("%s\n%s") % uhd::print_images_error() % burner_cmd); +} diff --git a/host/lib/usrp_clock/octoclock/octoclock_impl.hpp b/host/lib/usrp_clock/octoclock/octoclock_impl.hpp new file mode 100644 index 000000000..ab45cd5f0 --- /dev/null +++ b/host/lib/usrp_clock/octoclock/octoclock_impl.hpp @@ -0,0 +1,80 @@ +// +// Copyright 2014 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/>. +// + +#ifndef INCLUDED_OCTOCLOCK_IMPL_HPP +#define INCLUDED_OCTOCLOCK_IMPL_HPP + +#include <boost/shared_ptr.hpp> +#include <boost/thread.hpp> + +#include <uhd/device.hpp> +#include <uhd/stream.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/usrp_clock/octoclock_eeprom.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/sensors.hpp> + +#include "common.h" + +/*! + * OctoClock implementation guts + */ +class octoclock_impl : public uhd::device{ +public: + octoclock_impl(const uhd::device_addr_t &); + ~octoclock_impl(void) {}; + + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args); + + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); + + bool recv_async_msg(uhd::async_metadata_t&, double); + +private: + struct oc_container_type{ + uhd::usrp_clock::octoclock_eeprom_t eeprom; + octoclock_state_t state; + uhd::transport::udp_simple::sptr ctrl_xport; + uhd::transport::udp_simple::sptr gpsdo_xport; + uhd::gps_ctrl::sptr gps; + }; + uhd::dict<std::string, oc_container_type> _oc_dict; + boost::uint32_t _sequence; + + void _set_eeprom(const std::string &oc, const uhd::usrp_clock::octoclock_eeprom_t &oc_eeprom); + + boost::uint32_t _get_fw_version(const std::string &oc); + + void _get_state(const std::string &oc); + + uhd::sensor_value_t _ext_ref_detected(const std::string &oc); + + uhd::sensor_value_t _gps_detected(const std::string &oc); + + uhd::sensor_value_t _which_ref(const std::string &oc); + + uhd::sensor_value_t _switch_pos(const std::string &oc); + + boost::uint32_t _get_time(const std::string &oc); + + std::string _get_images_help_message(const std::string &addr); + + boost::mutex _device_mutex; +}; + +#endif /* INCLUDED_OCTOCLOCK_IMPL_HPP */ diff --git a/host/lib/usrp_clock/octoclock/octoclock_uart.cpp b/host/lib/usrp_clock/octoclock/octoclock_uart.cpp new file mode 100644 index 000000000..eb3f40d9c --- /dev/null +++ b/host/lib/usrp_clock/octoclock/octoclock_uart.cpp @@ -0,0 +1,162 @@ +// +// Copyright 2014 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 <string> +#include <string.h> + +#include <boost/algorithm/string.hpp> +#include <boost/asio.hpp> +#include <boost/cstdint.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> + +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> + +#include "common.h" +#include "octoclock_uart.hpp" + +namespace asio = boost::asio; +using namespace uhd::transport; + +#define NUM_WRAPS_EQUAL (_state.num_wraps == _device_state.num_wraps) +#define POS_EQUAL (_state.pos == _device_state.pos) +#define STATES_EQUAL (NUM_WRAPS_EQUAL && POS_EQUAL) +#define LOCAL_STATE_AHEAD (_state.num_wraps > _device_state.num_wraps || \ + (NUM_WRAPS_EQUAL && _state.pos > _device_state.pos)) + +namespace uhd{ + octoclock_uart_iface::octoclock_uart_iface(udp_simple::sptr udp): uart_iface(){ + _udp = udp; + _state.num_wraps = 0; + _state.pos = 0; + _device_state.num_wraps = 0; + _device_state.pos = 0; + size_t len = 0; + + //Get pool size from device + octoclock_packet_t pkt_out; + pkt_out.sequence = uhd::htonx<boost::uint16_t>(std::rand()); + pkt_out.len = 0; + + boost::uint8_t octoclock_data[udp_simple::mtu]; + const octoclock_packet_t *pkt_in = reinterpret_cast<octoclock_packet_t*>(octoclock_data); + + UHD_OCTOCLOCK_SEND_AND_RECV(_udp, SEND_POOLSIZE_CMD, pkt_out, len, octoclock_data); + if(UHD_OCTOCLOCK_PACKET_MATCHES(SEND_POOLSIZE_ACK, pkt_out, pkt_in, len)){ + _poolsize = pkt_in->poolsize; + _cache.resize(_poolsize); + } + else throw uhd::runtime_error("Failed to communicate with GPSDO."); + } + + void octoclock_uart_iface::write_uart(const std::string &buf){ + std::string to_send = boost::algorithm::replace_all_copy(buf, "\n", "\r\n"); + size_t len = 0; + + octoclock_packet_t pkt_out; + pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + pkt_out.len = to_send.size(); + memcpy(pkt_out.data, to_send.c_str(), to_send.size()); + + boost::uint8_t octoclock_data[udp_simple::mtu]; + const octoclock_packet_t *pkt_in = reinterpret_cast<octoclock_packet_t*>(octoclock_data); + + UHD_OCTOCLOCK_SEND_AND_RECV(_udp, HOST_SEND_TO_GPSDO_CMD, pkt_out, len, octoclock_data); + if(not UHD_OCTOCLOCK_PACKET_MATCHES(HOST_SEND_TO_GPSDO_ACK, pkt_out, pkt_in, len)){ + throw uhd::runtime_error("Failed to send commands to GPSDO."); + } + } + + std::string octoclock_uart_iface::read_uart(double timeout){ + std::string result; + + boost::system_time exit_time = boost::get_system_time() + boost::posix_time::milliseconds(long(timeout*1e3)); + + while(boost::get_system_time() < exit_time){ + _update_cache(); + + for(char ch = _getchar(); ch != -1; ch = _getchar()){ + if(ch == '\r') continue; //Skip carriage returns + _rxbuff += ch; + + //If newline found, return string + if(ch == '\n'){ + result = _rxbuff; + _rxbuff.clear(); + return result; + } + } + boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + } + + return result; + } + + void octoclock_uart_iface::_update_cache(){ + octoclock_packet_t pkt_out; + pkt_out.len = 0; + size_t len = 0; + + boost::uint8_t octoclock_data[udp_simple::mtu]; + const octoclock_packet_t *pkt_in = reinterpret_cast<octoclock_packet_t*>(octoclock_data); + + if(STATES_EQUAL or LOCAL_STATE_AHEAD){ + pkt_out.sequence++; + UHD_OCTOCLOCK_SEND_AND_RECV(_udp, SEND_GPSDO_CACHE_CMD, pkt_out, len, octoclock_data); + if(UHD_OCTOCLOCK_PACKET_MATCHES(SEND_GPSDO_CACHE_ACK, pkt_out, pkt_in, len)){ + memcpy(&_cache[0], pkt_in->data, _poolsize); + _device_state = pkt_in->state; + } + + boost::uint8_t delta_wraps = (_device_state.num_wraps - _state.num_wraps); + if(delta_wraps > 1 or + ((delta_wraps == 1) and (_device_state.pos >= _state.pos))){ + + _state.pos = (_device_state.pos+1) % _poolsize; + _state.num_wraps = (_device_state.num_wraps-1); + + while((_cache[_state.pos] != '\n') and (_state.pos != _device_state.pos)){ + _state.pos = (_state.pos+1) % _poolsize; + //We may have wrapped around locally + if(_state.pos == 0) _state.num_wraps++; + } + _state.pos = (_state.pos+1) % _poolsize; + //We may have wrapped around locally + if(_state.pos == 0) _state.num_wraps++; + } + } + } + + char octoclock_uart_iface::_getchar(){ + if(LOCAL_STATE_AHEAD){ + return -1; + } + + char ch = _cache[_state.pos]; + _state.pos = ((_state.pos+1) % _poolsize); + //We may have wrapped around locally + if(_state.pos == 0) _state.num_wraps++; + + return ch; + } + + uart_iface::sptr octoclock_make_uart_iface(udp_simple::sptr udp){ + return uart_iface::sptr(new octoclock_uart_iface(udp)); + } +} diff --git a/host/lib/usrp_clock/octoclock/octoclock_uart.hpp b/host/lib/usrp_clock/octoclock/octoclock_uart.hpp new file mode 100644 index 000000000..05d1bb121 --- /dev/null +++ b/host/lib/usrp_clock/octoclock/octoclock_uart.hpp @@ -0,0 +1,57 @@ +// +// Copyright 2014 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 uart_ifaceied 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/>. +// + +#ifndef INCLUDED_OCTOCLOCK_UART_HPP +#define INCLUDED_OCTOCLOCK_UART_HPP + +#include <vector> + +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/serial.hpp> + +/*! + * The OctoClock doesn't take UART input per se but reads a specific + * packet type and sends the string from there through its own serial + * functions. + */ +namespace uhd{ +class octoclock_uart_iface : public uhd::uart_iface{ +public: + octoclock_uart_iface(uhd::transport::udp_simple::sptr udp); + ~octoclock_uart_iface(void) {}; + + void write_uart(const std::string &buf); + std::string read_uart(double timeout); + +private: + uhd::transport::udp_simple::sptr _udp; + + boost::uint16_t _poolsize; + gpsdo_cache_state_t _state; + gpsdo_cache_state_t _device_state; + std::vector<boost::uint8_t> _cache; + std::string _rxbuff; + + void _update_cache(); + char _getchar(); +}; + +uart_iface::sptr octoclock_make_uart_iface(uhd::transport::udp_simple::sptr udp); + +} + +#endif /* INCLUDED_OCTOCLOCK_UART_HPP */ diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt index 7604a7d37..0d9f306b9 100644 --- a/host/utils/CMakeLists.txt +++ b/host/utils/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2010-2013 Ettus Research LLC +# Copyright 2010-2014 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 @@ -64,6 +64,20 @@ IF(ENABLE_USB) # Additional include directories for b2xx_fx3_utils INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../lib/usrp/b200 ${CMAKE_CURRENT_SOURCE_DIR}/../lib/usrp/common) ENDIF(ENABLE_USB) +IF(ENABLE_OCTOCLOCK) + LIST(APPEND util_share_sources + octoclock_burn_eeprom.cpp + ) + + SET(octoclock_burner_sources + octoclock_firmware_burner.cpp + ihexcvt.cpp + ) + + ADD_EXECUTABLE(octoclock_firmware_burner ${octoclock_burner_sources}) + TARGET_LINK_LIBRARIES(octoclock_firmware_burner uhd ${Boost_LIBRARIES}) + UHD_INSTALL(TARGETS octoclock_firmware_burner RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT utilities) +ENDIF(ENABLE_OCTOCLOCK) IF(LINUX AND ENABLE_USB) UHD_INSTALL(FILES diff --git a/host/utils/fx2_init_eeprom.cpp b/host/utils/fx2_init_eeprom.cpp index 701092a5d..5711b73e0 100644 --- a/host/utils/fx2_init_eeprom.cpp +++ b/host/utils/fx2_init_eeprom.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010 Ettus Research LLC +// Copyright 2010,2014 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 @@ -72,7 +72,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //find and create a control transport to do the writing. - uhd::device_addrs_t found_addrs = uhd::device::find(device_addr); + uhd::device_addrs_t found_addrs = uhd::device::find(device_addr, uhd::device::USRP); if (found_addrs.size() == 0){ std::cerr << "No USRP devices found" << std::endl; @@ -82,7 +82,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ 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::device::sptr dev = uhd::device::make(found_addrs[i], uhd::device::USRP); uhd::property_tree::sptr tree = dev->get_tree(); tree->access<std::string>("/mboards/0/load_eeprom").set(vm["image"].as<std::string>()); } diff --git a/host/utils/ihexcvt.cpp b/host/utils/ihexcvt.cpp new file mode 100644 index 000000000..0605ee61c --- /dev/null +++ b/host/utils/ihexcvt.cpp @@ -0,0 +1,250 @@ +/* IHexCvt - Intel HEX File <=> Binary Converter (C++) + Copyright (C) 2014 Ali Nakisaee + +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 2 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, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.*/ + +//Include needed stuff from C++ +#include <iostream> +#include <fstream> +#include <string> + +//... and also from C +#include <stdio.h> + +#include <boost/filesystem.hpp> + +#include <uhd/exception.hpp> +#include "ihexcvt.hpp" + +//Avoid repeating 'std::': +using namespace std; + +//The following function reads a hexadecimal number from a text file. +template <class T> +static bool ReadValueFromHex(ifstream& InputFile, T& outCh, unsigned char* ApplyChecksum) +{ + char V, L; + T X = 0; + outCh = 0; + + //Get the characters one by one. + //Remember: These values are big-endian. + //Remember: Every two hex characters (0-9/A-F) indicate ONE byte. + for (size_t i = 0; i < 2 * sizeof(T); i++) + { + InputFile.get( V ); + if (InputFile.fail()) + return false; + + X <<= 4; + if (V >= '0' && V <= '9') + L = (V - '0'); + else if (V >= 'a' && V <= 'f') + L = (V - 'a' + 10); + else if (V >= 'A' && V <= 'F') + L = (V - 'A' + 10); + else + return false; + X |= L; + + //Apply this character to the checksum + if (ApplyChecksum && i % 2 == 1) *ApplyChecksum += X & 0xFF; + } + + //Return... + outCh = X; + return true; +} + +//The following function writes a hexadecimal number from a text file. +template <class T> +static bool WriteHexValue(ofstream& OutFile, T Value, unsigned char* CalcChecksum) +{ + unsigned char V0 = 0; + char C; + + //Remember: These values are big-endian. + for (size_t i = 0; i < sizeof(T); i++) + { + //Get byte #i from the value. + V0 = (Value >> ((sizeof(T) - i - 1) * 8)) & 0xFF; + + //Extract the high nibble (4-bits) + if ((V0 & 0xF0) <= 0x90) + C = (V0 >> 4) + '0'; + else + C = (V0 >> 4) + ('A' - 10); + OutFile.put( C ); + + //Extract the low nibble (4-bits) + if ((V0 & 0xF) <= 0x9) + C = (V0 & 0xF) + '0'; + else + C = (V0 & 0xF) + ('A' - 10); + OutFile.put( C ); + + //Calculate the checksum + if (CalcChecksum) *CalcChecksum += V0; + } + return true; +} + +//Skip any incoming whitespaces +static void SkipWhitespace(ifstream& InputFile) +{ + for (;;) + { + char C; + InputFile.get(C); + if (InputFile.eof() || InputFile.fail()) break; + if (!(C == '\n' || C == '\r' || C == ' ' || C == '\t' || C == '\v')) + { + InputFile.putback(C); + break; + } + } +} + +//The function responsible for conversion from HEX files to BINary. +void Hex2Bin(const char* SrcName, const char* DstName, bool IgnoreChecksum) +{ + ifstream Src(SrcName); + if (Src.bad()) + { + throw uhd::runtime_error("Could not convert Intel .hex file to binary."); + } + + ofstream Dst(DstName, ios_base::binary); + if (Dst.bad()) + { + throw uhd::runtime_error("Could not convert Intel .hex file to binary."); + } + + char Ch; + int LineIdx = 1; + + unsigned char ByteCount; + unsigned short AddressLow; + unsigned short Extra; + unsigned long ExtraL; + unsigned long AddressOffset = 0; + unsigned char RecordType; + unsigned char Data[255]; + unsigned char CurChecksum; + unsigned char FileChecksum; + bool EOFMarker = false; + bool EOFWarn = false; + + for ( ;; ) + { + Src.get(Ch); + if (Src.eof()) + break; + if (EOFMarker && !EOFWarn) + { + throw uhd::runtime_error("Could not convert Intel .hex file to binary."); + } + if (Ch != ':') goto genericErr; + + CurChecksum = 0; + if (!ReadValueFromHex( Src, ByteCount, &CurChecksum )) goto genericErr; + if (!ReadValueFromHex( Src, AddressLow, &CurChecksum )) goto genericErr; + if (!ReadValueFromHex( Src, RecordType, &CurChecksum )) goto genericErr; + + switch (RecordType) + { + case 0x00: //Data record + for (int i = 0; i < ByteCount; i++) + if (!ReadValueFromHex( Src, Data[i], &CurChecksum )) goto genericErr; + break; + case 0x01: //End Marker + if ( ByteCount != 0 ) + { + goto onErrExit; + } + EOFMarker = true; + break; + case 0x02: //Extended Segment Address + if ( ByteCount != 2 || AddressLow != 0 ) + { + goto onErrExit; + } + if (!ReadValueFromHex( Src, Extra, &CurChecksum )) goto genericErr; + AddressOffset = (unsigned long)Extra << 4; + break; + case 0x03: //Start Segment Address + if ( ByteCount != 4 || AddressLow != 0 ) + { + goto onErrExit; + } + if (!ReadValueFromHex( Src, ExtraL, &CurChecksum )) goto genericErr; + break; + case 0x04: //Extended Linear Address + if ( ByteCount != 2 || AddressLow != 0 ) + { + goto onErrExit; + } + if (!ReadValueFromHex( Src, Extra, &CurChecksum )) goto genericErr; + AddressOffset = (unsigned long)Extra << 16; + break; + case 0x05: //Start Linear Address + if ( ByteCount != 4 || AddressLow != 0 ) + { + goto onErrExit; + } + if (!ReadValueFromHex( Src, ExtraL, &CurChecksum )) goto genericErr; + break; + } + + //Verify checksum + CurChecksum = (~(CurChecksum & 0xFF) + 1) & 0xFF; + if (!ReadValueFromHex( Src, FileChecksum, NULL )) goto genericErr; + if (CurChecksum != FileChecksum) + { + if (!IgnoreChecksum) goto onErrExit; + } + + //Put Data + if (RecordType == 0x00) + { + Dst.seekp( AddressLow + AddressOffset ); + for (int i = 0; i < ByteCount; i++) + { + Dst.put( Data[i] ); + } + } + + //Skip any white space + SkipWhitespace( Src ); + + LineIdx++; + } + + Dst << flush; + Dst.close(); + + return; + +genericErr: + throw uhd::runtime_error("Invalid Intel .hex file detected."); + +onErrExit: + Dst.close(); + Src.close(); + boost::filesystem::remove(DstName); + throw uhd::runtime_error("Could not convert Intel .hex file to binary."); +} diff --git a/host/utils/ihexcvt.hpp b/host/utils/ihexcvt.hpp new file mode 100644 index 000000000..d577ece1f --- /dev/null +++ b/host/utils/ihexcvt.hpp @@ -0,0 +1,22 @@ +// +// Copyright 2014 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/>. +// +#ifndef _IHEXCVT_HPP_ +#define _IHEXCVT_HPP_ + +void Hex2Bin(const char* SrcName, const char* DstName, bool IgnoreChecksum); + +#endif /* _IHEXCVT_HPP_ */ diff --git a/host/utils/octoclock_burn_eeprom.cpp b/host/utils/octoclock_burn_eeprom.cpp new file mode 100644 index 000000000..033a8f499 --- /dev/null +++ b/host/utils/octoclock_burn_eeprom.cpp @@ -0,0 +1,95 @@ +// +// Copyright 2014 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/device.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp_clock/octoclock_eeprom.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/types/device_addr.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <vector> + +namespace po = boost::program_options; + +using namespace uhd; +using namespace uhd::usrp_clock; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + std::string args, input_str, 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 = \"\"]") + ("values", po::value<std::string>(&input_str), "keys+values to read/write, separate multiple by \",\"") + ("read-all", "Read all motherboard EEPROM values without writing") + ; + + 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("values") and not vm.count("read-all"))){ + std::cout << boost::format("OctoClock Burn 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 EXIT_FAILURE; + } + + std::cout << "Creating OctoClock device from args: " + args << std::endl; + device::sptr oc = device::make(args, device::CLOCK); + property_tree::sptr tree = oc->get_tree(); + octoclock_eeprom_t oc_eeprom = tree->access<octoclock_eeprom_t>("/mboards/0/eeprom").get(); + std::cout << std::endl; + + std::vector<std::string> keys_vec, vals_vec; + if(vm.count("read-all")) keys_vec = oc_eeprom.keys(); //Leaving vals_vec empty will force utility to only read + else if(vm.count("values")){ + //uhd::device_addr_t properly parses input values + device_addr_t vals(input_str); + keys_vec = vals.keys(); + vals_vec = vals.vals(); + } + else throw std::runtime_error("Must specify --values or --read-all option!"); + + std::cout << "Fetching current settings from EEPROM..." << std::endl; + for(size_t i = 0; i < keys_vec.size(); i++){ + if (not oc_eeprom.has_key(keys_vec[i])){ + std::cerr << boost::format("Cannot find value for EEPROM[\"%s\"]") % keys_vec[i] << std::endl; + return EXIT_FAILURE; + } + std::cout << boost::format(" EEPROM [\"%s\"] is \"%s\"") % keys_vec[i] % oc_eeprom[keys_vec[i]] << std::endl; + } + if(!vm.count("read-all")){ + std::cout << std::endl; + for(size_t i = 0; i < vals_vec.size(); i++){ + if(vals_vec[i] != ""){ + oc_eeprom[keys_vec[i]] = vals_vec[i]; + std::cout << boost::format("Setting EEPROM [\"%s\"] to \"%s\"...") % keys_vec[i] % vals_vec[i] << std::endl; + } + } + tree->access<octoclock_eeprom_t>("/mboards/0/eeprom").set(oc_eeprom); + } + std::cout << std::endl << "Power-cycle your device to allow any changes to take effect." << std::endl; + return EXIT_SUCCESS; +} diff --git a/host/utils/octoclock_firmware_burner.cpp b/host/utils/octoclock_firmware_burner.cpp new file mode 100644 index 000000000..b8b8a3c1d --- /dev/null +++ b/host/utils/octoclock_firmware_burner.cpp @@ -0,0 +1,351 @@ +// +// Copyright 2014 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 <algorithm> +#include <csignal> +#include <iostream> +#include <fstream> +#include <stdexcept> +#include <vector> + +#include <boost/foreach.hpp> +#include <boost/asio.hpp> +#include <boost/program_options.hpp> +#include <boost/assign.hpp> +#include <boost/cstdint.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/filesystem.hpp> +#include <boost/thread.hpp> + +#include <uhd/device.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/types/time_spec.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/images.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/safe_main.hpp> + +#include "../lib/usrp_clock/octoclock/common.h" +#include "ihexcvt.hpp" + +#define MAX_FIRMWARE_SIZE 1024*120 +#define BLOCK_SIZE 256 +#define UDP_TIMEOUT 5 + +namespace fs = boost::filesystem; +namespace po = boost::program_options; + +using namespace uhd; +using namespace uhd::transport; + +static int num_ctrl_c = 0; +void sig_int_handler(int){ + num_ctrl_c++; + if(num_ctrl_c == 1){ + std::cout << std::endl << "Are you sure you want to abort the image burning? If you do, your " + "OctoClock device will be bricked!" << std::endl + << "Press Ctrl+C again to abort the image burning procedure." << std::endl << std::endl; + } + else{ + std::cout << std::endl << "Aborting. Your OctoClock device will be bricked." << std::endl + << "Refer to http://files.ettus.com/uhd_docs/doxymanual/html/octoclock.html" << std::endl + << "for details on restoring your device." << std::endl; + exit(EXIT_FAILURE); + } +} + +boost::uint8_t firmware_image[MAX_FIRMWARE_SIZE]; +size_t firmware_size = 0; +boost::uint8_t octoclock_data[udp_simple::mtu]; +octoclock_packet_t *pkt_in = reinterpret_cast<octoclock_packet_t *>(octoclock_data); +std::string firmware_path; +size_t num_blocks = 0; + +/* + * Functions + */ +void list_octoclocks(){ + device_addrs_t found_octoclocks = device::find(uhd::device_addr_t(), device::CLOCK); + + std::cout << "Available OctoClock devices:" << std::endl; + BOOST_FOREACH(const device_addr_t &oc, found_octoclocks){ + std::cout << " * " << oc["addr"] << std::endl; + } +} + +/* + * Manually find bootloader. This sends multiple packets in order to increase chances of getting + * bootloader before it switches to the application. + */ +device_addrs_t bootloader_find(std::string ip_addr){ + udp_simple::sptr udp_transport = udp_simple::make_connected(ip_addr, BOOST_STRINGIZE(OCTOCLOCK_UDP_CTRL_PORT)); + + octoclock_packet_t pkt_out; + pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + pkt_out.code = OCTOCLOCK_QUERY_CMD; + pkt_out.len = 0; + size_t len = 0; + + device_addrs_t addrs; + + boost::system_time comm_timeout = boost::get_system_time() + boost::posix_time::milliseconds(3000); + + while(boost::get_system_time() < comm_timeout){ + UHD_OCTOCLOCK_SEND_AND_RECV(udp_transport, OCTOCLOCK_QUERY_CMD, pkt_out, len, octoclock_data); + if(UHD_OCTOCLOCK_PACKET_MATCHES(OCTOCLOCK_QUERY_ACK, pkt_out, pkt_in, len) and + pkt_in->proto_ver == OCTOCLOCK_BOOTLOADER_PROTO_VER){ + addrs.push_back(device_addr_t()); + addrs[0]["type"] = "octoclock-bootloader"; + addrs[0]["addr"] = udp_transport->get_recv_addr(); + break; + } + } + + return addrs; +} + +void read_firmware(){ + std::ifstream firmware_file(firmware_path.c_str(), std::ios::binary); + firmware_file.seekg(0, std::ios::end); + firmware_size = firmware_file.tellg(); + if(firmware_size > MAX_FIRMWARE_SIZE){ + firmware_file.close(); + throw uhd::runtime_error(str(boost::format("Firmware file too large: %d > %d") + % firmware_size % (MAX_FIRMWARE_SIZE))); + } + firmware_file.seekg(0, std::ios::beg); + firmware_file.read((char*)firmware_image, firmware_size); + firmware_file.close(); + + num_blocks = (firmware_size % BLOCK_SIZE) ? (firmware_size / BLOCK_SIZE) + : ((firmware_size / BLOCK_SIZE) + 1); +} + +void burn_firmware(udp_simple::sptr udp_transport){ + octoclock_packet_t pkt_out; + pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + pkt_out.len = uhd::htonx<boost::uint16_t>((boost::uint16_t)firmware_size); + size_t len = 0, current_pos = 0; + + //Tell OctoClock not to jump to application, wait for us instead + std::cout << "Telling OctoClock to prepare for firmware download..." << std::flush; + UHD_OCTOCLOCK_SEND_AND_RECV(udp_transport, PREPARE_FW_BURN_CMD, pkt_out, len, octoclock_data); + if(UHD_OCTOCLOCK_PACKET_MATCHES(FW_BURN_READY_ACK, pkt_out, pkt_in, len)) std::cout << "ready." << std::endl; + else{ + std::cout << std::endl; + throw uhd::runtime_error("Could not get OctoClock in valid state for firmware download."); + } + + std::cout << std::endl << "Burning firmware." << std::endl; + pkt_out.code = FILE_TRANSFER_CMD; + + //Actual burning below + for(size_t i = 0; i < num_blocks; i++){ + pkt_out.sequence++; + pkt_out.addr = i*BLOCK_SIZE; + std::cout << "\r * Progress: " << int(double(i)/double(num_blocks)*100) + << "% (" << (i+1) << "/" << num_blocks << " blocks)" << std::flush; + + memset(pkt_out.data, 0, BLOCK_SIZE); + memcpy((void*)(pkt_out.data), &firmware_image[i*BLOCK_SIZE], std::min(int(firmware_size-current_pos), BLOCK_SIZE)); + UHD_OCTOCLOCK_SEND_AND_RECV(udp_transport, FILE_TRANSFER_CMD, pkt_out, len, octoclock_data); + if(not (UHD_OCTOCLOCK_PACKET_MATCHES(FILE_TRANSFER_ACK, pkt_out, pkt_in, len))){ + std::cout << std::endl; + throw uhd::runtime_error("Failed to burn firmware to OctoClock!"); + } + + current_pos += BLOCK_SIZE; + } + + std::cout << "\r * Progress: 100% (" << num_blocks << "/" << num_blocks << " blocks)" << std::endl; +} + +void verify_firmware(udp_simple::sptr udp_transport){ + octoclock_packet_t pkt_out; + pkt_out.proto_ver = OCTOCLOCK_FW_COMPAT_NUM; + pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + size_t len = 0, current_pos = 0; + + std::cout << "Verifying firmware." << std::endl; + + for(size_t i = 0; i < num_blocks; i++){ + pkt_out.sequence++; + pkt_out.addr = i*BLOCK_SIZE; + std::cout << "\r * Progress: " << int(double(i)/double(num_blocks)*100) + << "% (" << (i+1) << "/" << num_blocks << " blocks)" << std::flush; + + UHD_OCTOCLOCK_SEND_AND_RECV(udp_transport, READ_FW_CMD, pkt_out, len, octoclock_data); + if(UHD_OCTOCLOCK_PACKET_MATCHES(READ_FW_ACK, pkt_out, pkt_in, len)){ + if(memcmp((void*)(pkt_in->data), &firmware_image[i*BLOCK_SIZE], + std::min(int(firmware_size-current_pos), BLOCK_SIZE))){ + std::cout << std::endl; + throw uhd::runtime_error("Failed to verify OctoClock firmware!"); + } + } + else{ + std::cout << std::endl; + throw uhd::runtime_error("Failed to verify OctoClock firmware!"); + } + } + + std::cout << "\r * Progress: 100% (" << num_blocks << "/" << num_blocks << " blocks)" << std::endl; +} + +void reset_octoclock(const std::string &ip_addr){ + udp_simple::sptr udp_transport = udp_simple::make_connected(ip_addr, BOOST_STRINGIZE(OCTOCLOCK_UDP_CTRL_PORT)); + + octoclock_packet_t pkt_out; + pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + size_t len; + + UHD_OCTOCLOCK_SEND_AND_RECV(udp_transport, RESET_CMD, pkt_out, len, octoclock_data); + if(UHD_OCTOCLOCK_PACKET_MATCHES(RESET_ACK, pkt_out, pkt_in, len)) std::cout << "done." << std::endl; + else throw uhd::runtime_error("Failed to place device in state to receive firmware."); +} + +void finalize(udp_simple::sptr udp_transport){ + octoclock_packet_t pkt_out; + pkt_out.len = 0; + pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + size_t len = 0; + + std::cout << std::endl << "Telling OctoClock to load application..." << std::flush; + UHD_OCTOCLOCK_SEND_AND_RECV(udp_transport, FINALIZE_BURNING_CMD, pkt_out, len, octoclock_data); + if(UHD_OCTOCLOCK_PACKET_MATCHES(FINALIZE_BURNING_ACK, pkt_out, pkt_in, len)) std::cout << "done." << std::endl; + else std::cout << "no ACK. Device may not have loaded application." << std::endl; +} + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + std::string ip_addr; + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "Display this help message.") + ("addr", po::value<std::string>(&ip_addr), "Specify an IP address.") + ("fw-path", po::value<std::string>(&firmware_path), "Specify a custom firmware path.") + ("list", "List all available OctoClock devices.") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //Print help message + if(vm.count("help")){ + std::cout << "OctoClock Firmware Burner" << std::endl << std::endl; + + std::cout << "Burns a firmware image file onto an OctoClock device. Specify" << std::endl + << "the address of the OctoClock with the --addr option. To burn" << std::endl + << "a custom firmware image, use the --fw-path option. Otherwise, the" << std::endl + << "utility will use the default image. To list all available" << std::endl + << "OctoClock devices without burning firmware, use the --list" << std::endl + << "option." << std::endl << std::endl; + + std::cout << desc << std::endl; + return EXIT_SUCCESS; + } + + //List all available devices + if(vm.count("list")){ + list_octoclocks(); + return EXIT_SUCCESS; + } + + if(not (vm.count("addr"))){ + throw uhd::runtime_error("You must specify an address with the --addr option!"); + } + udp_simple::sptr udp_transport = udp_simple::make_connected(ip_addr, BOOST_STRINGIZE(OCTOCLOCK_UDP_FW_PORT)); + + //If custom path given, make sure it exists + if(vm.count("fw-path")){ + //Expand tilde usage if applicable + #ifndef UHD_PLATFORM_WIN32 + if(firmware_path.find("~/") == 0) firmware_path.replace(0,1,getenv("HOME")); + #endif + + if(not fs::exists(firmware_path)){ + throw uhd::runtime_error(str(boost::format("This filepath does not exist: %s") % firmware_path)); + } + } + else firmware_path = find_image_path("octoclock_r4_fw.bin"); + + //If Intel hex file detected, convert to binary + std::string ext = fs::extension(firmware_path); + if(ext == ".hex"){ + std::cout << "Found firmware at path: " << firmware_path << std::endl; + + //Write firmware .bin file to temporary directory + fs::path temp_bin = fs::path(fs::path(get_tmp_path()) / str(boost::format("octoclock_fw_%d.bin") + % time_spec_t::get_system_time().get_full_secs())); + Hex2Bin(firmware_path.c_str(), temp_bin.string().c_str(), false); + + firmware_path = temp_bin.string(); + } + else if(ext == ".bin"){ + std::cout << "Found firmware at path: " << firmware_path << std::endl; + } + else throw uhd::runtime_error("The firmware file has in improper extension (must be .hex or .bin)."); + + std::cout << std::endl << boost::format("Searching for OctoClock with IP address %s...") % ip_addr << std::flush; + device_addrs_t octoclocks = device::find(str(boost::format("addr=%s") % ip_addr), device::CLOCK); + if(octoclocks.size() == 1){ + //If in application, quietly reset into bootloader and try to find again + if(octoclocks[0]["type"] == "octoclock"){ + reset_octoclock(ip_addr); + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + octoclocks = bootloader_find(ip_addr); + if(octoclocks.size() == 1) std::cout << "found." << std::endl; + else{ + std::cout << std::endl; + throw uhd::runtime_error("Could not find OctoClock with given IP address!"); + } + } + else std::cout << "found." << std::endl; + } + else{ + std::cout << std::endl; + throw uhd::runtime_error("Could not find OctoClock with given IP address!"); + } + + read_firmware(); + + std::signal(SIGINT, &sig_int_handler); + + burn_firmware(udp_transport); + verify_firmware(udp_transport); + finalize(udp_transport); + + std::cout << "Waiting for OctoClock to reinitialize..." << std::flush; + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + octoclocks = device::find(str(boost::format("addr=%s") % ip_addr), device::CLOCK); + if(octoclocks.size() == 1){ + if(octoclocks[0]["type"] == "octoclock-bootloader"){ + std::cout << std::endl; + throw uhd::runtime_error("OctoClock failed to leave bootloader state."); + } + else{ + std::cout << "found." << std::endl << std::endl + << "Successfully burned firmware." << std::endl << std::endl; + } + } + else{ + std::cout << std::endl; + throw uhd::runtime_error("Failed to reinitialize OctoClock."); + } + + return EXIT_SUCCESS; +} diff --git a/host/utils/uhd_usrp_probe.cpp b/host/utils/uhd_usrp_probe.cpp index 04ccc1e5a..cfbfe5f20 100644 --- a/host/utils/uhd_usrp_probe.cpp +++ b/host/utils/uhd_usrp_probe.cpp @@ -210,7 +210,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ return EXIT_SUCCESS; } - device::sptr dev = device::make(vm["args"].as<std::string>()); + device::sptr dev = device::make(vm["args"].as<std::string>(), device::USRP); property_tree::sptr tree = dev->get_tree(); if (vm.count("string")){ diff --git a/host/utils/usrp_burn_db_eeprom.cpp b/host/utils/usrp_burn_db_eeprom.cpp index 3ca953115..7b483d2e7 100644 --- a/host/utils/usrp_burn_db_eeprom.cpp +++ b/host/utils/usrp_burn_db_eeprom.cpp @@ -62,7 +62,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ } //make the device and extract the dboard w/ property - device::sptr dev = device::make(args); + device::sptr dev = device::make(args, device::USRP); 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); diff --git a/host/utils/usrp_burn_mb_eeprom.cpp b/host/utils/usrp_burn_mb_eeprom.cpp index c631c9c09..de74d6807 100644 --- a/host/utils/usrp_burn_mb_eeprom.cpp +++ b/host/utils/usrp_burn_mb_eeprom.cpp @@ -56,7 +56,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ } std::cout << "Creating USRP device from address: " + args << std::endl; - uhd::device::sptr dev = uhd::device::make(args); + uhd::device::sptr dev = uhd::device::make(args, uhd::device::USRP); uhd::property_tree::sptr tree = dev->get_tree(); uhd::usrp::mboard_eeprom_t mb_eeprom = tree->access<uhd::usrp::mboard_eeprom_t>("/mboards/0/eeprom").get(); std::cout << std::endl; diff --git a/host/utils/usrp_x3xx_fpga_burner.cpp b/host/utils/usrp_x3xx_fpga_burner.cpp index 2f0202e84..0c8ea7465 100644 --- a/host/utils/usrp_x3xx_fpga_burner.cpp +++ b/host/utils/usrp_x3xx_fpga_burner.cpp @@ -118,7 +118,7 @@ boost::uint8_t bitswap(uint8_t b){ } void list_usrps(){ - device_addrs_t found_devices = device::find(device_addr_t("type=x300")); + device_addrs_t found_devices = device::find(device_addr_t("type=x300"), device::USRP); std::cout << "Available X3x0 devices:" << std::endl; BOOST_FOREACH(const device_addr_t &dev, found_devices){ @@ -142,7 +142,7 @@ void list_usrps(){ device_addr_t find_usrp_with_ethernet(std::string ip_addr, bool output){ if(output) std::cout << "Attempting to find X3x0 with IP address: " << ip_addr << std::endl; const device_addr_t dev = device_addr_t(str(boost::format("addr=%s") % ip_addr)); - device_addrs_t found_devices = device::find(dev); + device_addrs_t found_devices = device::find(dev, device::USRP); if(found_devices.size() < 1) { throw std::runtime_error("Could not find X3x0 with the specified address!"); @@ -161,7 +161,7 @@ device_addr_t find_usrp_with_ethernet(std::string ip_addr, bool output){ device_addr_t find_usrp_with_pcie(std::string resource, bool output){ if(output) std::cout << "Attempting to find X3x0 with resource: " << resource << std::endl; const device_addr_t dev = device_addr_t(str(boost::format("resource=%s") % resource)); - device_addrs_t found_devices = device::find(dev); + device_addrs_t found_devices = device::find(dev, device::USRP); if(found_devices.size() < 1) { throw std::runtime_error("Could not find X3x0 with the specified resource!"); |