diff options
Diffstat (limited to 'host/lib')
30 files changed, 2173 insertions, 101 deletions
diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt index 3fa8ed22f..3d4ba8a68 100644 --- a/host/lib/CMakeLists.txt +++ b/host/lib/CMakeLists.txt @@ -89,6 +89,7 @@ CONFIGURE_FILE( LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/deprecated.cpp ${CMAKE_CURRENT_SOURCE_DIR}/device.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/image_loader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/stream.cpp ${CMAKE_CURRENT_SOURCE_DIR}/exception.cpp ${CMAKE_CURRENT_SOURCE_DIR}/property_tree.cpp diff --git a/host/lib/image_loader.cpp b/host/lib/image_loader.cpp new file mode 100644 index 000000000..91dd325dd --- /dev/null +++ b/host/lib/image_loader.cpp @@ -0,0 +1,87 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <iostream> +#include <map> +#include <utility> + +#include <boost/filesystem.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> + +#include <uhd/exception.hpp> +#include <uhd/image_loader.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/static.hpp> + +namespace fs = boost::filesystem; + +typedef std::map<std::string, uhd::image_loader::loader_fcn_t> loader_fcn_map_t; +typedef std::map<std::string, std::string> string_map_t; + +// Nice typedefs for iterating over std::map +typedef std::pair<std::string, uhd::image_loader::loader_fcn_t> loader_fcn_pair_t; +typedef std::pair<std::string, std::string> string_pair_t; + +UHD_SINGLETON_FCN(loader_fcn_map_t, get_image_loaders); +UHD_SINGLETON_FCN(string_map_t, get_recovery_strings); + +/* + * Registration + */ +void uhd::image_loader::register_image_loader(const std::string &device_type, + const loader_fcn_t &loader_fcn, + const std::string &recovery_instructions){ + UHD_LOGV(always) << "Registering image loader and recovery instructions for " + << device_type << std::endl; + + get_image_loaders().insert(loader_fcn_pair_t(device_type, loader_fcn)); + get_recovery_strings().insert(string_pair_t(device_type, recovery_instructions)); +} + +/* + * Actual loading + */ +bool uhd::image_loader::load(const uhd::image_loader::image_loader_args_t &image_loader_args){ + + // If "type=foo" given in args, see if we have an image loader for that + if(image_loader_args.args.has_key("type")){ + std::string type = image_loader_args.args.get("type"); + if(get_image_loaders().find(type) == get_image_loaders().end()){ + throw uhd::runtime_error(str(boost::format("There is no image loader registered for given type \"%s\".") + % type)); + } + else return get_image_loaders().at(type)(image_loader_args); + } + else{ + BOOST_FOREACH(const loader_fcn_pair_t &loader_fcn_pair, get_image_loaders()){ + if(loader_fcn_pair.second(image_loader_args)) return true; + } + return false; + } +} + +/* + * Get recovery instructions for particular device + */ +std::string uhd::image_loader::get_recovery_instructions(const std::string &device_type){ + if(get_recovery_strings().count(device_type) == 0){ + return "A firmware or FPGA loading process was interrupted by the user. This can leave your device in a non-working state."; + } + else return get_recovery_strings().at(device_type); +} diff --git a/host/lib/usrp/b200/CMakeLists.txt b/host/lib/usrp/b200/CMakeLists.txt index ce89b5d80..cd8ebcba7 100644 --- a/host/lib/usrp/b200/CMakeLists.txt +++ b/host/lib/usrp/b200/CMakeLists.txt @@ -26,6 +26,7 @@ LIBUHD_REGISTER_COMPONENT("B200" ENABLE_B200 ON "ENABLE_LIBUHD;ENABLE_USB" OFF) IF(ENABLE_B200) LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/b200_image_loader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/b200_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/b200_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/b200_io_impl.cpp diff --git a/host/lib/usrp/b200/b200_iface.cpp b/host/lib/usrp/b200/b200_iface.cpp index 270d3bb4b..4754a6357 100644 --- a/host/lib/usrp/b200/b200_iface.cpp +++ b/host/lib/usrp/b200/b200_iface.cpp @@ -511,7 +511,7 @@ public: throw uhd::io_error((boost::format("Short write on set FPGA hash (expecting: %d, returned: %d)") % bytes_to_send % ret).str()); } - boost::uint32_t load_fpga(const std::string filestring) { + boost::uint32_t load_fpga(const std::string filestring, bool force) { boost::uint8_t fx3_state = 0; boost::uint32_t wait_count; @@ -522,7 +522,7 @@ public: hash_type hash = generate_hash(filename); hash_type loaded_hash; usrp_get_fpga_hash(loaded_hash); - if (hash == loaded_hash) return 0; + if (hash == loaded_hash and !force) return 0; // Establish default largest possible control request transfer size based on operating USB speed int transfer_size = VREQ_DEFAULT_SIZE; diff --git a/host/lib/usrp/b200/b200_iface.hpp b/host/lib/usrp/b200/b200_iface.hpp index 1d123439a..0c7ee6b9e 100644 --- a/host/lib/usrp/b200/b200_iface.hpp +++ b/host/lib/usrp/b200/b200_iface.hpp @@ -97,7 +97,7 @@ public: virtual void set_fpga_reset_pin(const bool reset) = 0; //! load an FPGA image - virtual boost::uint32_t load_fpga(const std::string filestring) = 0; + virtual boost::uint32_t load_fpga(const std::string filestring, bool force=false) = 0; virtual void write_eeprom(boost::uint16_t addr, boost::uint16_t offset, const uhd::byte_vector_t &bytes) = 0; diff --git a/host/lib/usrp/b200/b200_image_loader.cpp b/host/lib/usrp/b200/b200_image_loader.cpp new file mode 100644 index 000000000..87010244c --- /dev/null +++ b/host/lib/usrp/b200/b200_image_loader.cpp @@ -0,0 +1,125 @@ +// +// Copyright 2014-2015 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 <boost/assign.hpp> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> + +#include <uhd/exception.hpp> +#include <uhd/image_loader.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/static.hpp> + +#include "b200_iface.hpp" +#include "b200_impl.hpp" + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +namespace uhd{ + +static b200_iface::sptr get_b200_iface(const image_loader::image_loader_args_t &image_loader_args, + mboard_eeprom_t &mb_eeprom, + bool user_specified){ + + std::vector<usb_device_handle::sptr> dev_handles = get_b200_device_handles(image_loader_args.args); + b200_iface::sptr iface; + + if(dev_handles.size() > 0){ + BOOST_FOREACH(usb_device_handle::sptr dev_handle, dev_handles){ + if(dev_handle->firmware_loaded()){ + iface = b200_iface::make(usb_control::make(dev_handle,0)); + mb_eeprom = mboard_eeprom_t(*iface, "B200"); + if(user_specified){ + if(image_loader_args.args.has_key("serial") and + mb_eeprom.get("serial") != image_loader_args.args.get("serial")){ + continue; + } + if(image_loader_args.args.has_key("name") and + mb_eeprom.get("name") != image_loader_args.args.get("name")){ + continue; + } + return iface; + } + else return iface; // Just return first found + } + } + } + + // No applicable devices found, return empty sptr so we can exit + iface.reset(); + mb_eeprom = mboard_eeprom_t(); + return iface; +} + +static bool b200_image_loader(const image_loader::image_loader_args_t &image_loader_args){ + if(!image_loader_args.load_fpga) + return false; + + bool user_specified = (image_loader_args.args.has_key("serial") or + image_loader_args.args.has_key("name")); + + // See if a B2x0 with the given args is found + mboard_eeprom_t mb_eeprom; + b200_iface::sptr iface = get_b200_iface(image_loader_args, mb_eeprom, user_specified); + if(!iface) return false; // No initialized B2x0 found + + std::string fpga_path; + if(image_loader_args.fpga_path == ""){ + /* + * Normally, we can auto-generate the FPGA filename from what's in the EEPROM, + * but if the applicable value is not in the EEPROM, the user must give a specific + * filename for us to use. + */ + std::string product = mb_eeprom.get("product"); + if(not B2X0_PRODUCT_ID.has_key(boost::lexical_cast<boost::uint16_t>(product))){ + if(user_specified){ + // The user specified a bad device but expects us to know what it is + throw uhd::runtime_error("Could not determine model. You must manually specify an FPGA image filename."); + } + else{ + return false; + } + } + else{ + fpga_path = find_image_path(B2X0_FPGA_FILE_NAME.get(get_b200_type(mb_eeprom))); + } + } + else fpga_path = image_loader_args.fpga_path; + + std::cout << boost::format("Unit: USRP %s (%s)") + % B2X0_STR_NAMES.get(get_b200_type(mb_eeprom), "B2XX") + % mb_eeprom.get("serial") + << std::endl; + + iface->load_fpga(fpga_path, true); + + return true; +} + +UHD_STATIC_BLOCK(register_b200_image_loader){ + std::string recovery_instructions = "This device is likely in an unusable state. Power-cycle the\n" + "device, and the firmware/FPGA will be reloaded the next time\n" + "UHD uses the device."; + + image_loader::register_image_loader("b200", b200_image_loader, recovery_instructions); +} + +} /* namespace uhd */ diff --git a/host/lib/usrp/b200/b200_impl.cpp b/host/lib/usrp/b200/b200_impl.cpp index 7c672fd46..17086de02 100644 --- a/host/lib/usrp/b200/b200_impl.cpp +++ b/host/lib/usrp/b200/b200_impl.cpp @@ -76,7 +76,7 @@ public: //! Look up the type of B-Series device we're currently running. // If the product ID stored in mb_eeprom is invalid, throws a // uhd::runtime_error. -static b200_type_t get_b200_type(const mboard_eeprom_t &mb_eeprom) +b200_type_t get_b200_type(const mboard_eeprom_t &mb_eeprom) { if (mb_eeprom["product"].empty()) { throw uhd::runtime_error("B200: Missing product ID on EEPROM."); @@ -91,6 +91,21 @@ static b200_type_t get_b200_type(const mboard_eeprom_t &mb_eeprom) return B2X0_PRODUCT_ID[product_id]; } +std::vector<usb_device_handle::sptr> get_b200_device_handles(const device_addr_t &hint) +{ + std::vector<usb_device_handle::vid_pid_pair_t> vid_pid_pair_list; + + if(hint.has_key("vid") && hint.has_key("pid") && hint.has_key("type") && hint["type"] == "b200") { + vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(uhd::cast::hexstr_cast<boost::uint16_t>(hint.get("vid")), + uhd::cast::hexstr_cast<boost::uint16_t>(hint.get("pid")))); + } else { + vid_pid_pair_list = b200_vid_pid_pairs; + } + + //find the usrps and load firmware + return usb_device_handle::get_device_list(vid_pid_pair_list); +} + static device_addrs_t b200_find(const device_addr_t &hint) { device_addrs_t b200_addrs; @@ -104,28 +119,14 @@ static device_addrs_t b200_find(const device_addr_t &hint) if (hint_i.has_key("addr") || hint_i.has_key("resource")) return b200_addrs; } - size_t found = 0; - std::vector<usb_device_handle::vid_pid_pair_t> vid_pid_pair_list;//vid pid pair search list for devices. - - if(hint.has_key("vid") && hint.has_key("pid") && hint.has_key("type") && hint["type"] == "b200") { - vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(uhd::cast::hexstr_cast<boost::uint16_t>(hint.get("vid")), - uhd::cast::hexstr_cast<boost::uint16_t>(hint.get("pid")))); - } else { - vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(B200_VENDOR_ID, B200_PRODUCT_ID)); - vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(B200_VENDOR_NI_ID, B200_PRODUCT_NI_ID)); - vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(B200_VENDOR_NI_ID, B210_PRODUCT_NI_ID)); - } - // Important note: // The get device list calls are nested inside the for loop. // This allows the usb guts to decontruct when not in use, // so that re-enumeration after fw load can occur successfully. // This requirement is a courtesy of libusb1.0 on windows. - - //find the usrps and load firmware - std::vector<usb_device_handle::sptr> uhd_usb_device_vector = usb_device_handle::get_device_list(vid_pid_pair_list); - - BOOST_FOREACH(usb_device_handle::sptr handle, uhd_usb_device_vector) { + size_t found = 0; + std::vector<usb_device_handle::sptr> b200_device_handles = get_b200_device_handles(hint); + BOOST_FOREACH(usb_device_handle::sptr handle, b200_device_handles) { //extract the firmware path for the b200 std::string b200_fw_image; try{ @@ -156,7 +157,7 @@ static device_addrs_t b200_find(const device_addr_t &hint) //search for the device until found or timeout while (boost::get_system_time() < timeout_time and b200_addrs.empty() and found != 0) { - BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid_pid_pair_list)) + BOOST_FOREACH(usb_device_handle::sptr handle, b200_device_handles) { usb_control::sptr control; try{control = usb_control::make(handle, 0);} diff --git a/host/lib/usrp/b200/b200_impl.hpp b/host/lib/usrp/b200/b200_impl.hpp index 11c3da0d9..a3c93e22e 100644 --- a/host/lib/usrp/b200/b200_impl.hpp +++ b/host/lib/usrp/b200/b200_impl.hpp @@ -43,6 +43,7 @@ #include <uhd/usrp/gps_ctrl.hpp> #include <uhd/transport/usb_zero_copy.hpp> #include <uhd/transport/bounded_buffer.hpp> +#include <boost/assign.hpp> #include <boost/weak_ptr.hpp> #include "recv_packet_demuxer_3000.hpp" static const boost::uint8_t B200_FW_COMPAT_NUM_MAJOR = 8; @@ -82,9 +83,18 @@ static const boost::uint32_t B200_RX_GPS_UART_SID = FLIP_SID(B200_TX_GPS_UART_SI static const boost::uint32_t B200_LOCAL_CTRL_SID = 0x00000040; static const boost::uint32_t B200_LOCAL_RESP_SID = FLIP_SID(B200_LOCAL_CTRL_SID); -/*********************************************************************** - * The B200 Capability Constants - **********************************************************************/ +/* + * VID/PID pairs for all B2xx products + */ +static std::vector<uhd::transport::usb_device_handle::vid_pid_pair_t> b200_vid_pid_pairs = + boost::assign::list_of + (uhd::transport::usb_device_handle::vid_pid_pair_t(B200_VENDOR_ID, B200_PRODUCT_ID)) + (uhd::transport::usb_device_handle::vid_pid_pair_t(B200_VENDOR_NI_ID, B200_PRODUCT_NI_ID)) + (uhd::transport::usb_device_handle::vid_pid_pair_t(B200_VENDOR_NI_ID, B210_PRODUCT_NI_ID)) + ; + +b200_type_t get_b200_type(const uhd::usrp::mboard_eeprom_t &mb_eeprom); +std::vector<uhd::transport::usb_device_handle::sptr> get_b200_device_handles(const uhd::device_addr_t &hint); //! Implementation guts class b200_impl : public uhd::device diff --git a/host/lib/usrp/e100/e100_impl.cpp b/host/lib/usrp/e100/e100_impl.cpp index ac419e0e0..6d3c08534 100644 --- a/host/lib/usrp/e100/e100_impl.cpp +++ b/host/lib/usrp/e100/e100_impl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2012,2014 Ettus Research LLC +// Copyright 2010-2012,2014-2015 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 @@ -28,6 +28,7 @@ #include <boost/functional/hash.hpp> #include <boost/assign/list_of.hpp> #include <fstream> +#include <iostream> #include <ctime> using namespace uhd; @@ -45,7 +46,7 @@ namespace fs = boost::filesystem; /*********************************************************************** * Discovery **********************************************************************/ -static device_addrs_t e100_find(const device_addr_t &hint){ +device_addrs_t e100_find(const device_addr_t &hint){ device_addrs_t e100_addrs; //return an empty list of addresses when type is set to non-usrp-e @@ -104,17 +105,10 @@ static const uhd::dict<std::string, std::string> model_to_fpga_file_name = boost ("E110", "usrp_e110_fpga.bin") ; -/*********************************************************************** - * Structors - **********************************************************************/ -e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ - _tree = property_tree::make(); - _type = device::USRP; - _ignore_cal_file = device_addr.has_key("ignore-cal-file"); - +std::string get_default_e1x0_fpga_image(const uhd::device_addr_t &device_addr){ //read the eeprom so we can determine the hardware - _dev_i2c_iface = e100_ctrl::make_dev_i2c_iface(E100_I2C_DEV_NODE); - const mboard_eeprom_t mb_eeprom(*_dev_i2c_iface, E100_EEPROM_MAP_KEY); + uhd::i2c_iface::sptr dev_i2c_iface = e100_ctrl::make_dev_i2c_iface(E100_I2C_DEV_NODE); + const mboard_eeprom_t mb_eeprom(*dev_i2c_iface, E100_EEPROM_MAP_KEY); //determine the model string for this device const std::string model = device_addr.get("model", mb_eeprom.get("model", "")); @@ -126,7 +120,21 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ ) % model)); //extract the fpga path and compute hash - const std::string default_fpga_file_name = model_to_fpga_file_name[model]; + return model_to_fpga_file_name[model]; +} + +/*********************************************************************** + * Structors + **********************************************************************/ +e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ + _tree = property_tree::make(); + _type = device::USRP; + _ignore_cal_file = device_addr.has_key("ignore-cal-file"); + + _dev_i2c_iface = e100_ctrl::make_dev_i2c_iface(E100_I2C_DEV_NODE); + const mboard_eeprom_t mb_eeprom(*_dev_i2c_iface, E100_EEPROM_MAP_KEY); + const std::string default_fpga_file_name = get_default_e1x0_fpga_image(device_addr); + const std::string model = device_addr["model"]; std::string e100_fpga_image; try{ e100_fpga_image = find_image_path(device_addr.get("fpga", default_fpga_file_name)); diff --git a/host/lib/usrp/e100/e100_impl.hpp b/host/lib/usrp/e100/e100_impl.hpp index 4efc21427..d00668224 100644 --- a/host/lib/usrp/e100/e100_impl.hpp +++ b/host/lib/usrp/e100/e100_impl.hpp @@ -29,6 +29,7 @@ #include "recv_packet_demuxer.hpp" #include <uhd/device.hpp> #include <uhd/property_tree.hpp> +#include <uhd/types/device_addr.hpp> #include <uhd/usrp/subdev_spec.hpp> #include <uhd/usrp/dboard_eeprom.hpp> #include <uhd/usrp/mboard_eeprom.hpp> @@ -68,6 +69,9 @@ uhd::usrp::dboard_iface::sptr make_e100_dboard_iface( e100_codec_ctrl::sptr codec ); +uhd::device_addrs_t e100_find(const uhd::device_addr_t &hint); +std::string get_default_e1x0_fpga_image(const uhd::device_addr_t &device_addr); + /*! * USRP-E100 implementation guts: * The implementation details are encapsulated here. diff --git a/host/lib/usrp/e100/fpga_downloader.cpp b/host/lib/usrp/e100/fpga_downloader.cpp index c9d77f560..9abde32f7 100644 --- a/host/lib/usrp/e100/fpga_downloader.cpp +++ b/host/lib/usrp/e100/fpga_downloader.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2011,2014 Ettus Research LLC +// Copyright 2010-2011,2014-2015 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 @@ -17,8 +17,16 @@ #include <uhd/config.hpp> #ifdef UHD_DLL_EXPORTS +#include <boost/filesystem.hpp> +#include <boost/format.hpp> #include <uhd/exception.hpp> +#include <uhd/device.hpp> +#include <uhd/image_loader.hpp> +#include <uhd/types/device_addr.hpp> #include <uhd/utils/msg.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/static.hpp> +#include "e100_impl.hpp" #else //special case when this file is externally included #include <stdexcept> #include <iostream> @@ -270,3 +278,34 @@ void e100_load_fpga(const std::string &bin_file){ } +#ifdef UHD_DLL_EXPORTS +namespace fs = boost::filesystem; + +static bool e100_image_loader(const uhd::image_loader::image_loader_args_t &image_loader_args){ + // Make sure this is an E1x0 + uhd::device_addrs_t devs = e100_find(uhd::device_addr_t()); + if(devs.size() == 0 or !image_loader_args.load_fpga) return false; + + std::string fpga_filename; + if(image_loader_args.fpga_path == ""){ + fpga_filename = uhd::find_image_path(get_default_e1x0_fpga_image(devs[0])); + } + else{ + if(not fs::exists(image_loader_args.fpga_path)){ + throw uhd::runtime_error(str(boost::format("The path \"%s\" does not exist.") + % image_loader_args.fpga_path)); + } + else fpga_filename = image_loader_args.fpga_path; + } + + e100_load_fpga(fpga_filename); + return true; +} + +UHD_STATIC_BLOCK(register_e100_image_loader){ + std::string recovery_instructions = "The default FPGA image will be loaded the next time " + "UHD uses this device."; + + uhd::image_loader::register_image_loader("e100", e100_image_loader, recovery_instructions); +} +#endif /* UHD_DLL_EXPORTS */ diff --git a/host/lib/usrp/e300/e300_common.cpp b/host/lib/usrp/e300/e300_common.cpp index db5b37055..29117e21f 100644 --- a/host/lib/usrp/e300/e300_common.cpp +++ b/host/lib/usrp/e300/e300_common.cpp @@ -1,5 +1,5 @@ // -// Copyright 2014 Ettus Research LLC +// Copyright 2014-2015 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 @@ -14,8 +14,12 @@ // 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/image_loader.hpp> #include <uhd/utils/msg.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/static.hpp> +#include "e300_impl.hpp" #include "e300_fifo_config.hpp" #include "e300_fifo_config.hpp" @@ -23,6 +27,7 @@ #include <boost/filesystem.hpp> #include <fstream> +#include <string> namespace uhd { namespace usrp { namespace e300 { @@ -54,6 +59,34 @@ void load_fpga_image(const std::string &path) UHD_MSG(status) << " done" << std::endl; } +static bool e300_image_loader(const image_loader::image_loader_args_t &image_loader_args) { + // Make sure this is an E3x0 and we don't want to use anything connected + uhd::device_addrs_t devs = e300_find(image_loader_args.args); + if(devs.size() == 0 or !image_loader_args.load_fpga) return false; + + std::string fpga_filename, idle_image; // idle_image never used, just needed for function + if(image_loader_args.fpga_path == "") { + get_e3x0_fpga_images(devs[0], fpga_filename, idle_image); + } + else { + if(not boost::filesystem::exists(image_loader_args.fpga_path)) { + throw uhd::runtime_error(str(boost::format("The path \"%s\" does not exist.") + % image_loader_args.fpga_path)); + } + else fpga_filename = image_loader_args.fpga_path; + } + + load_fpga_image(fpga_filename); + return true; +} + +UHD_STATIC_BLOCK(register_e300_image_loader) { + std::string recovery_instructions = "The default FPGA image will be loaded the next " + "time UHD uses this device."; + + image_loader::register_image_loader("e3x0", e300_image_loader, recovery_instructions); +} + } }}} diff --git a/host/lib/usrp/e300/e300_impl.cpp b/host/lib/usrp/e300/e300_impl.cpp index de2357100..5e3f4c575 100644 --- a/host/lib/usrp/e300/e300_impl.cpp +++ b/host/lib/usrp/e300/e300_impl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2013-2014 Ettus Research LLC +// Copyright 2013-2015 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 @@ -121,7 +121,7 @@ static bool is_loopback(const if_addrs_t &if_addrs) return if_addrs.inet == asio::ip::address_v4::loopback().to_string(); } -static device_addrs_t e300_find(const device_addr_t &multi_dev_hint) +device_addrs_t e300_find(const device_addr_t &multi_dev_hint) { // handle multi device discovery device_addrs_t hints = separate_device_addr(multi_dev_hint); @@ -268,6 +268,36 @@ static device::sptr e300_make(const device_addr_t &device_addr) return device::sptr(new e300_impl(device_addr)); } +// Common code used by e300_impl and e300_image_loader +void get_e3x0_fpga_images(const uhd::device_addr_t &device_addr, + std::string &fpga_image, + std::string &idle_image){ + const boost::uint16_t pid = boost::lexical_cast<boost::uint16_t>( + device_addr["product"]); + + //extract the FPGA path for the e300 + switch(e300_eeprom_manager::get_mb_type(pid)) { + case e300_eeprom_manager::USRP_E310_MB: + fpga_image = device_addr.cast<std::string>("fpga", + find_image_path(E310_FPGA_FILE_NAME)); + idle_image = find_image_path(E310_FPGA_IDLE_FILE_NAME); + break; + case e300_eeprom_manager::USRP_E300_MB: + fpga_image = device_addr.cast<std::string>("fpga", + find_image_path(E300_FPGA_FILE_NAME)); + idle_image = find_image_path(E300_FPGA_IDLE_FILE_NAME); + break; + case e300_eeprom_manager::UNKNOWN: + default: + UHD_MSG(warning) << "Unknown motherboard type, loading e300 image." + << std::endl; + fpga_image = device_addr.cast<std::string>("fpga", + find_image_path(E300_FPGA_FILE_NAME)); + idle_image = find_image_path(E300_FPGA_IDLE_FILE_NAME); + break; + } +} + /*********************************************************************** * Structors **********************************************************************/ @@ -286,33 +316,10 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) if (_xport_path == AXI) { _do_not_reload = device_addr.has_key("no_reload_fpga"); if (not _do_not_reload) { - // Load FPGA image if provided via args - const boost::uint16_t pid = boost::lexical_cast<boost::uint16_t>( - device_addr["product"]); - std::string fpga_image; - - //extract the FPGA path for the e300 - switch(e300_eeprom_manager::get_mb_type(pid)) { - case e300_eeprom_manager::USRP_E310_MB: - fpga_image = device_addr.cast<std::string>("fpga", - find_image_path(E310_FPGA_FILE_NAME)); - _idle_image = find_image_path(E310_FPGA_IDLE_FILE_NAME); - break; - case e300_eeprom_manager::USRP_E300_MB: - fpga_image = device_addr.cast<std::string>("fpga", - find_image_path(E300_FPGA_FILE_NAME)); - _idle_image = find_image_path(E300_FPGA_IDLE_FILE_NAME); - break; - case e300_eeprom_manager::UNKNOWN: - default: - UHD_MSG(warning) << "Unknown motherboard type, loading e300 image." - << std::endl; - fpga_image = device_addr.cast<std::string>("fpga", - find_image_path(E300_FPGA_FILE_NAME)); - _idle_image = find_image_path(E300_FPGA_IDLE_FILE_NAME); - break; - } + get_e3x0_fpga_images(device_addr, + fpga_image, + _idle_image); common::load_fpga_image(fpga_image); } } diff --git a/host/lib/usrp/e300/e300_impl.hpp b/host/lib/usrp/e300/e300_impl.hpp index c530a5d72..3ed133489 100644 --- a/host/lib/usrp/e300/e300_impl.hpp +++ b/host/lib/usrp/e300/e300_impl.hpp @@ -1,5 +1,5 @@ // -// Copyright 2013-2014 Ettus Research LLC +// Copyright 2013-2015 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 @@ -20,6 +20,7 @@ #include <uhd/device.hpp> #include <uhd/property_tree.hpp> +#include <uhd/types/device_addr.hpp> #include <uhd/usrp/subdev_spec.hpp> #include <uhd/usrp/mboard_eeprom.hpp> #include <uhd/usrp/dboard_eeprom.hpp> @@ -28,6 +29,7 @@ #include <uhd/types/sensors.hpp> #include <boost/weak_ptr.hpp> #include <boost/thread/mutex.hpp> +#include <string> #include "e300_fifo_config.hpp" #include "radio_ctrl_core_3000.hpp" #include "rx_frontend_core_200.hpp" @@ -98,6 +100,10 @@ static const size_t E300_R1_CTRL_STREAM = (1 << 2) | E300_RADIO_DEST_PREFIX_C static const size_t E300_R1_TX_DATA_STREAM = (1 << 2) | E300_RADIO_DEST_PREFIX_TX; static const size_t E300_R1_RX_DATA_STREAM = (1 << 2) | E300_RADIO_DEST_PREFIX_RX; +uhd::device_addrs_t e300_find(const uhd::device_addr_t &multi_dev_hint); +void get_e3x0_fpga_images(const uhd::device_addr_t &device_args, + std::string &fpga_image, + std::string &idle_image); /*! * USRP-E300 implementation guts: diff --git a/host/lib/usrp/usrp2/CMakeLists.txt b/host/lib/usrp/usrp2/CMakeLists.txt index c6257c7fe..bd302895b 100644 --- a/host/lib/usrp/usrp2/CMakeLists.txt +++ b/host/lib/usrp/usrp2/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2011-2012,2014 Ettus Research LLC +# Copyright 2011-2012,2014-2015 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 @@ -25,18 +25,6 @@ LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 ON "ENABLE_LIBUHD" OFF) IF(ENABLE_USRP2) - ######################################################################## - # Define UHD_PKG_DATA_PATH for usrp2_iface.cpp - ######################################################################## - FILE(TO_NATIVE_PATH ${CMAKE_INSTALL_PREFIX} UHD_PKG_PATH) - STRING(REPLACE "\\" "\\\\" UHD_PKG_PATH ${UHD_PKG_PATH}) - - SET_SOURCE_FILES_PROPERTIES( - ${CMAKE_CURRENT_SOURCE_DIR}/usrp2_iface.cpp - PROPERTIES COMPILE_DEFINITIONS - "UHD_LIB_DIR=\"lib${LIB_SUFFIX}\"" - ) - LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/clock_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp @@ -45,5 +33,6 @@ IF(ENABLE_USRP2) ${CMAKE_CURRENT_SOURCE_DIR}/usrp2_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/usrp2_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/usrp2_fifo_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n200_image_loader.cpp ) ENDIF(ENABLE_USRP2) diff --git a/host/lib/usrp/usrp2/n200_image_loader.cpp b/host/lib/usrp/usrp2/n200_image_loader.cpp new file mode 100644 index 000000000..ce956c22c --- /dev/null +++ b/host/lib/usrp/usrp2/n200_image_loader.cpp @@ -0,0 +1,616 @@ +// +// Copyright 2015 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 <cstring> +#include <iostream> +#include <fstream> + +#include <boost/asio/ip/address_v4.hpp> +#include <boost/assign.hpp> +#include <boost/filesystem.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/algorithm/string/erase.hpp> + +#include <uhd/config.hpp> +#include <uhd/image_loader.hpp> +#include <uhd/exception.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/types/dict.hpp> + +#include "fw_common.h" +#include "usrp2_iface.hpp" +#include "usrp2_impl.hpp" + +typedef boost::asio::ip::address_v4 ip_v4; + +namespace fs = boost::filesystem; +using namespace boost::algorithm; + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +/* + * Constants + */ + +#define N200_FLASH_DATA_PACKET_SIZE 256 +#define N200_UDP_FW_UPDATE_PORT 49154 +#define UDP_TIMEOUT 0.5 + +#define N200_FW_MAX_SIZE_BYTES 31744 +#define N200_PROD_FW_IMAGE_ADDR 0x00300000 +#define N200_SAFE_FW_IMAGE_ADDR 0x003F0000 + +#define N200_FPGA_MAX_SIZE_BYTES 1572864 +#define N200_PROD_FPGA_IMAGE_ADDR 0x00180000 +#define N200_SAFE_FPGA_IMAGE_ADDR 0x00000000 + +/* + * Packet codes + */ +typedef enum { + UNKNOWN = ' ', + + N200_QUERY = 'a', + N200_ACK = 'A', + + GET_FLASH_INFO_CMD = 'f', + GET_FLASH_INFO_ACK = 'F', + + ERASE_FLASH_CMD = 'e', + ERASE_FLASH_ACK = 'E', + + CHECK_ERASING_DONE_CMD = 'd', + DONE_ERASING_ACK = 'D', + NOT_DONE_ERASING_ACK = 'B', + + WRITE_FLASH_CMD = 'w', + WRITE_FLASH_ACK = 'W', + + READ_FLASH_CMD = 'r', + READ_FLASH_ACK = 'R', + + RESET_CMD = 's', + RESET_ACK = 'S', + + GET_HW_REV_CMD = 'v', + GET_HW_REV_ACK = 'V', +} n200_fw_update_id_t; + +/* + * Mapping revision numbers to names + */ +static const uhd::dict<boost::uint32_t, std::string> n200_filename_map = boost::assign::map_list_of + (0, "n2xx") // Is an N-Series, but the EEPROM value is invalid + (0xa, "n200_r3") + (0x100a, "n200_r4") + (0x10a, "n210_r3") + (0x110a, "n210_r4") +; + +/* + * Packet structure + */ +typedef struct { + boost::uint32_t proto_ver; + boost::uint32_t id; + boost::uint32_t seq; + union { + boost::uint32_t ip_addr; + boost::uint32_t hw_rev; + struct { + boost::uint32_t flash_addr; + boost::uint32_t length; + boost::uint8_t data[256]; + } flash_args; + struct { + boost::uint32_t sector_size_bytes; + boost::uint32_t memory_size_bytes; + } flash_info_args; + } data; +} n200_fw_update_data_t; + +/* + * N-Series burn session + */ +typedef struct { + bool fw; + bool overwrite_safe; + bool reset; + uhd::device_addr_t dev_addr; + std::string burn_type; + std::string filepath; + boost::uint8_t data_in[udp_simple::mtu]; + boost::uint32_t size; + boost::uint32_t max_size; + boost::uint32_t flash_addr; + udp_simple::sptr xport; +} n200_session_t; + +/*********************************************************************** + * uhd::image_loader functionality + **********************************************************************/ + +static void print_usrp2_error(const image_loader::image_loader_args_t &image_loader_args){ + #ifdef UHD_PLATFORM_WIN32 + std::string usrp2_card_burner_gui = "\""; + const std::string nl = " ^\n "; + #else + std::string usrp2_card_burner_gui = "sudo \""; + const std::string nl = " \\\n "; + #endif + + usrp2_card_burner_gui += find_utility("usrp2_card_burner_gui.py"); + usrp2_card_burner_gui += "\""; + + if(image_loader_args.load_firmware){ + usrp2_card_burner_gui += str(boost::format("%s--fw=\"%s\"") + % nl + % ((image_loader_args.firmware_path == "") + ? find_image_path("usrp2_fw.bin") + : image_loader_args.firmware_path)); + } + if(image_loader_args.load_fpga){ + usrp2_card_burner_gui += str(boost::format("%s--fpga=\"%s\"") + % nl + % ((image_loader_args.fpga_path == "") + ? find_image_path("usrp2_fpga.bin") + : image_loader_args.fpga_path)); + } + + throw uhd::runtime_error(str(boost::format("The specified device is a USRP2, which is not supported by this utility.\n" + "Instead, plug the device's SD card into your machine and run this command:\n\n" + "%s" + ) % usrp2_card_burner_gui)); +} + +/* + * Ethernet communication functions + */ +static UHD_INLINE size_t n200_send_and_recv(udp_simple::sptr xport, + n200_fw_update_id_t pkt_code, + n200_fw_update_data_t *pkt_out, + boost::uint8_t* data){ + pkt_out->proto_ver = htonx<boost::uint32_t>(USRP2_FW_COMPAT_NUM); + pkt_out->id = htonx<boost::uint32_t>(pkt_code); + xport->send(boost::asio::buffer(pkt_out, sizeof(*pkt_out))); + return xport->recv(boost::asio::buffer(data, udp_simple::mtu), UDP_TIMEOUT); +} + +static UHD_INLINE bool n200_response_matches(const n200_fw_update_data_t *pkt_in, + n200_fw_update_id_t pkt_code, + size_t len){ + return (len > offsetof(n200_fw_update_data_t, data) and + ntohl(pkt_in->id) == pkt_code); +} + +static uhd::device_addr_t n200_find(const image_loader::image_loader_args_t &image_loader_args){ + bool user_specified = image_loader_args.args.has_key("addr") or + image_loader_args.args.has_key("serial") or + image_loader_args.args.has_key("name"); + + uhd::device_addrs_t found = usrp2_find(image_loader_args.args); + if(found.size() > 0){ + uhd::device_addr_t ret = found[0]; + + /* + * Make sure the device found is an N-Series and not a USRP2. A USRP2 + * will not respond to this query. If the user supplied specific + * arguments that led to a USRP2, throw an error. + */ + udp_simple::sptr rev_xport = udp_simple::make_connected( + ret["addr"], + BOOST_STRINGIZE(N200_UDP_FW_UPDATE_PORT) + ); + + n200_fw_update_data_t pkt_out; + boost::uint8_t data_in[udp_simple::mtu]; + const n200_fw_update_data_t *pkt_in = reinterpret_cast<const n200_fw_update_data_t*>(data_in); + + size_t len = n200_send_and_recv(rev_xport, GET_HW_REV_CMD, &pkt_out, data_in); + if(n200_response_matches(pkt_in, GET_HW_REV_ACK, len)){ + boost::uint32_t rev = ntohl(pkt_in->data.hw_rev); + ret["hw_rev"] = n200_filename_map.get(rev, "n2xx"); + return ret; + } + else if(len > offsetof(n200_fw_update_data_t, data) and ntohl(pkt_in->id) != GET_HW_REV_ACK){ + throw uhd::runtime_error(str(boost::format("Received invalid reply %d from device.") + % ntohl(pkt_in->id))); + } + else if(user_specified){ + // At this point, we haven't received any response, so assume it's a USRP2 + print_usrp2_error(image_loader_args); + } + } + + return uhd::device_addr_t(); +} + +/* + * Validate and read firmware image + */ +static void n200_validate_firmware_image(n200_session_t &session){ + if(not fs::exists(session.filepath)){ + throw uhd::runtime_error(str(boost::format("Could not find image at path \"%s\".") + % session.filepath)); + } + + session.size = fs::file_size(session.filepath); + session.max_size = N200_FW_MAX_SIZE_BYTES; + + if(session.size > session.max_size){ + throw uhd::runtime_error(str(boost::format("The specified FPGA image is too large: %d vs. %d") + % session.size % session.max_size)); + } + + // File must have proper header + std::ifstream image_file(session.filepath.c_str(), std::ios::binary); + boost::uint8_t test_bytes[4]; + image_file.seekg(0, std::ios::beg); + image_file.read((char*)test_bytes,4); + image_file.close(); + for(int i = 0; i < 4; i++) if(test_bytes[i] != 11){ + throw uhd::runtime_error(str(boost::format("The file at path \"%s\" is not a valid firmware image.") + % session.filepath)); + } +} + +/* + * Validate and validate FPGA image + */ +static void n200_validate_fpga_image(n200_session_t &session){ + if(not fs::exists(session.filepath)){ + throw uhd::runtime_error(str(boost::format("Could not find image at path \"%s\".") + % session.filepath)); + } + + session.size = fs::file_size(session.filepath); + session.max_size = N200_FPGA_MAX_SIZE_BYTES; + + if(session.size > session.max_size){ + throw uhd::runtime_error(str(boost::format("The specified FPGA image is too large: %d vs. %d") + % session.size % session.max_size)); + } + + // File must have proper header + std::ifstream image_file(session.filepath.c_str(), std::ios::binary); + boost::uint8_t test_bytes[63]; + image_file.seekg(0, std::ios::beg); + image_file.read((char*)test_bytes, 63); + bool is_good = false; + for(int i = 0; i < 63; i++){ + if(test_bytes[i] == 255) continue; + else if(test_bytes[i] == 170 and + test_bytes[i+1] == 153){ + is_good = true; + break; + } + } + image_file.close(); + if(not is_good){ + throw uhd::runtime_error(str(boost::format("The file at path \"%s\" is not a valid FPGA image.") + % session.filepath)); + } +} + +/* + * Set up a session for burning an N-Series image. This session info + * will be passed into the erase, burn, and verify functions. + */ +static void n200_setup_session(n200_session_t &session, + const image_loader::image_loader_args_t &image_loader_args, + bool fw){ + + + session.fw = fw; + session.reset = image_loader_args.args.has_key("reset"); + + /* + * If no filepath is given, attempt to determine the default image by + * querying the device for its revision. If the device has a corrupt + * EEPROM or is otherwise unable to provide its revision, this is + * impossible, and the user must manually provide a firmware file. + */ + if((session.fw and image_loader_args.firmware_path == "") or + image_loader_args.fpga_path == ""){ + if(session.dev_addr["hw_rev"] == "n2xx"){ + throw uhd::runtime_error("This device's revision cannot be determined. " + "You must manually specify a filepath."); + } + else{ + session.filepath = session.fw ? find_image_path(str(boost::format("usrp_%s_fw.bin") + % erase_tail_copy(session.dev_addr["hw_rev"],3))) + : find_image_path(str(boost::format("usrp_%s_fpga.bin") + % session.dev_addr["hw_rev"])); + } + } + else{ + session.filepath = session.fw ? image_loader_args.firmware_path + : image_loader_args.fpga_path; + } + if(session.fw) n200_validate_firmware_image(session); + else n200_validate_fpga_image(session); + + session.overwrite_safe = image_loader_args.args.has_key("overwrite-safe"); + if(session.overwrite_safe){ + session.flash_addr = session.fw ? N200_SAFE_FW_IMAGE_ADDR + : N200_SAFE_FPGA_IMAGE_ADDR; + session.burn_type = session.fw ? "firmware safe" + : "FPGA safe"; + } + else{ + session.flash_addr = session.fw ? N200_PROD_FW_IMAGE_ADDR + : N200_PROD_FPGA_IMAGE_ADDR; + session.burn_type = session.fw ? "firmware" + : "FPGA"; + } + + session.xport = udp_simple::make_connected(session.dev_addr["addr"], + BOOST_STRINGIZE(N200_UDP_FW_UPDATE_PORT)); +} + +static void n200_erase_image(n200_session_t &session){ + + // UDP receive buffer + n200_fw_update_data_t pkt_out; + const n200_fw_update_data_t *pkt_in = reinterpret_cast<const n200_fw_update_data_t*>(session.data_in); + + // Setting up UDP packet + pkt_out.data.flash_args.flash_addr = htonx<boost::uint32_t>(session.flash_addr); + pkt_out.data.flash_args.length = htonx<boost::uint32_t>(session.size); + + // Begin erasing + size_t len = n200_send_and_recv(session.xport, ERASE_FLASH_CMD, &pkt_out, session.data_in); + if(n200_response_matches(pkt_in, ERASE_FLASH_ACK, len)){ + std::cout << boost::format("-- Erasing %s image...") % session.burn_type << std::flush; + } + else if(len < offsetof(n200_fw_update_data_t, data)){ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Timed out waiting for reply from device."); + } + else if(ntohl(pkt_in->id) != ERASE_FLASH_ACK){ + std::cout << "failed." << std::endl; + throw uhd::runtime_error(str(boost::format("Received invalid reply %d from device.\n") + % ntohl(pkt_in->id))); + } + else{ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Did not receive response from device."); + } + + // Check for erase completion + while(true){ + len = n200_send_and_recv(session.xport, CHECK_ERASING_DONE_CMD, &pkt_out, session.data_in); + if(n200_response_matches(pkt_in, DONE_ERASING_ACK, len)){ + std::cout << "successful." << std::endl; + break; + } + else if(len < offsetof(n200_fw_update_data_t, data)){ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Timed out waiting for reply from device."); + } + else if(ntohl(pkt_in->id) != NOT_DONE_ERASING_ACK){ + std::cout << "failed." << std::endl; + throw uhd::runtime_error(str(boost::format("Received invalid reply %d from device.\n") + % ntohl(pkt_in->id))); + } + } +} + +static void n200_write_image(n200_session_t &session){ + + // UDP receive buffer + n200_fw_update_data_t pkt_out; + const n200_fw_update_data_t *pkt_in = reinterpret_cast<const n200_fw_update_data_t*>(session.data_in); + size_t len = 0; + + // Write image + std::ifstream image(session.filepath.c_str(), std::ios::binary); + boost::uint32_t current_addr = session.flash_addr; + pkt_out.data.flash_args.length = htonx<boost::uint32_t>(N200_FLASH_DATA_PACKET_SIZE); + for(size_t i = 0; i < ((session.size/N200_FLASH_DATA_PACKET_SIZE)+1); i++){ + pkt_out.data.flash_args.flash_addr = htonx<boost::uint32_t>(current_addr); + memset(pkt_out.data.flash_args.data, 0x0, N200_FLASH_DATA_PACKET_SIZE); + image.read((char*)pkt_out.data.flash_args.data, N200_FLASH_DATA_PACKET_SIZE); + + len = n200_send_and_recv(session.xport, WRITE_FLASH_CMD, &pkt_out, session.data_in); + if(n200_response_matches(pkt_in, WRITE_FLASH_ACK, len)){ + std::cout << boost::format("\r-- Writing %s image (%d%%)") + % session.burn_type + % int((double(current_addr-session.flash_addr)/double(session.size))*100) + << std::flush; + } + else if(len < offsetof(n200_fw_update_data_t, data)){ + image.close(); + std::cout << boost::format("\r--Writing %s image..failed at %d%%.") + % session.burn_type + % int((double(current_addr-session.flash_addr)/double(session.size))*100) + << std::endl; + throw uhd::runtime_error("Timed out waiting for reply from device."); + } + else if(ntohl(pkt_in->id) != WRITE_FLASH_ACK){ + image.close(); + std::cout << boost::format("\r--Writing %s image..failed at %d%%.") + % session.burn_type + % int((double(current_addr-session.flash_addr)/double(session.size))*100) + << std::endl; + throw uhd::runtime_error(str(boost::format("Received invalid reply %d from device.\n") + % ntohl(pkt_in->id))); + } + + current_addr += N200_FLASH_DATA_PACKET_SIZE; + } + std::cout << boost::format("\r-- Writing %s image...successful.") + % session.burn_type + << std::endl; + + image.close(); +} + +static void n200_verify_image(n200_session_t &session){ + + // UDP receive buffer + n200_fw_update_data_t pkt_out; + const n200_fw_update_data_t *pkt_in = reinterpret_cast<const n200_fw_update_data_t*>(session.data_in); + size_t len = 0; + + // Read and verify image + std::ifstream image(session.filepath.c_str(), std::ios::binary); + boost::uint8_t image_part[N200_FLASH_DATA_PACKET_SIZE]; + boost::uint32_t current_addr = session.flash_addr; + pkt_out.data.flash_args.length = htonx<boost::uint32_t>(N200_FLASH_DATA_PACKET_SIZE); + boost::uint16_t cmp_len = 0; + for(size_t i = 0; i < ((session.size/N200_FLASH_DATA_PACKET_SIZE)+1); i++){ + memset(image_part, 0x0, N200_FLASH_DATA_PACKET_SIZE); + memset((void*)pkt_in->data.flash_args.data, 0x0, N200_FLASH_DATA_PACKET_SIZE); + + pkt_out.data.flash_args.flash_addr = htonx<boost::uint32_t>(current_addr); + image.read((char*)image_part, N200_FLASH_DATA_PACKET_SIZE); + cmp_len = image.gcount(); + + len = n200_send_and_recv(session.xport, READ_FLASH_CMD, &pkt_out, session.data_in); + if(n200_response_matches(pkt_in, READ_FLASH_ACK, len)){ + std::cout << boost::format("\r-- Verifying %s image (%d%%)") + % session.burn_type + % int((double(current_addr-session.flash_addr)/double(session.size))*100) + << std::flush; + + if(memcmp(image_part, pkt_in->data.flash_args.data, cmp_len)){ + std::cout << boost::format("\r-- Verifying %s image...failed at %d%%.") + % session.burn_type + % int((double(current_addr-session.flash_addr)/double(session.size))*100) + << std::endl; + throw uhd::runtime_error(str(boost::format("Failed to verify %s image.") + % session.burn_type)); + } + } + else if(len < offsetof(n200_fw_update_data_t, data)){ + image.close(); + std::cout << boost::format("\r-- Verifying %s image...failed at %d%%.") + % session.burn_type + % int((double(current_addr-session.flash_addr)/double(session.size))*100) + << std::endl; + throw uhd::runtime_error("Timed out waiting for reply from device."); + } + else if(ntohl(pkt_in->id) != READ_FLASH_ACK){ + image.close(); + std::cout << boost::format("\r-- Verifying %s image...failed at %d%%.") + % session.burn_type + % int((double(current_addr-session.flash_addr)/double(session.size))*100) + << std::endl; + throw uhd::runtime_error(str(boost::format("Received invalid reply %d from device.\n") + % ntohl(pkt_in->id))); + } + + current_addr += N200_FLASH_DATA_PACKET_SIZE; + } + std::cout << boost::format("\r-- Verifying %s image...successful.") % session.burn_type + << std::endl; + + image.close(); +} + +static void n200_reset(n200_session_t &session){ + + // UDP receive buffer + n200_fw_update_data_t pkt_out; + + // There should be no response + std::cout << "-- Resetting device..." << std::flush; + size_t len = n200_send_and_recv(session.xport, RESET_CMD, &pkt_out, session.data_in); + if(len > 0){ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Failed to reset N200."); + } + std::cout << "successful." << std::endl; +} + +// n210_r4 -> N210 r4 +static std::string nice_name(const std::string &fw_rev){ + std::string ret = fw_rev; + ret[0] = ::toupper(ret[0]); + + size_t pos = 0; + if((pos = fw_rev.find("_")) != std::string::npos){ + ret[pos] = ' '; + } + + return ret; +} + +static bool n200_image_loader(const image_loader::image_loader_args_t &image_loader_args){ + // See if any N2x0 with the given args is found + // This will throw if specific args lead to a USRP2 + n200_session_t session; + session.dev_addr = n200_find(image_loader_args); + if(session.dev_addr.size() == 0 or (!image_loader_args.load_firmware and !image_loader_args.load_fpga)){ + return false; + } + + std::cout << boost::format("Unit: USRP %s (%s, %s)") + % nice_name(session.dev_addr.get("hw_rev")) + % session.dev_addr.get("serial") + % session.dev_addr.get("addr") + << std::endl; + + if(image_loader_args.load_firmware){ + n200_setup_session(session, + image_loader_args, + true + ); + + std::cout << "Firmware image: " << session.filepath << std::endl; + + n200_erase_image(session); + n200_write_image(session); + n200_verify_image(session); + if(session.reset and !image_loader_args.load_fpga){ + n200_reset(session); + } + } + if(image_loader_args.load_fpga){ + n200_setup_session(session, + image_loader_args, + false + ); + + std::cout << "FPGA image: " << session.filepath << std::endl; + + n200_erase_image(session); + n200_write_image(session); + n200_verify_image(session); + if(session.reset){ + n200_reset(session); + } + } + + return true; +} + +UHD_STATIC_BLOCK(register_n200_image_loader){ + std::string recovery_instructions = "Aborting. Your USRP-N Series unit will likely be unusable.\n" + "Refer to http://files.ettus.com/manual/page_usrp2.html#usrp2_loadflash_brick\n" + "for details on restoring your device."; + + image_loader::register_image_loader("usrp2", n200_image_loader, recovery_instructions); +} diff --git a/host/lib/usrp/usrp2/usrp2_iface.cpp b/host/lib/usrp/usrp2/usrp2_iface.cpp index 1d41173f8..2b382ae38 100644 --- a/host/lib/usrp/usrp2/usrp2_iface.cpp +++ b/host/lib/usrp/usrp2/usrp2_iface.cpp @@ -387,15 +387,15 @@ public: //create the burner commands if (this->get_rev() == USRP2_REV3 or this->get_rev() == USRP2_REV4){ - const std::string card_burner = (fs::path(uhd::get_pkg_path()) / UHD_LIB_DIR / "uhd" / "utils" / "usrp2_card_burner_gui.py").string(); - const std::string card_burner_cmd = str(boost::format("\"%s%s\" %s--fpga=\"%s\" %s--fw=\"%s\"") % sudo % card_burner % ml % fpga_image_path % ml % fw_image_path); + const std::string card_burner = uhd::find_utility("usrp2_card_burner_gui.py"); + const std::string card_burner_cmd = str(boost::format(" %s\"%s\" %s--fpga=\"%s\" %s--fw=\"%s\"") % sudo % card_burner % ml % fpga_image_path % ml % fw_image_path); return str(boost::format("%s\n%s") % print_utility_error("uhd_images_downloader.py") % card_burner_cmd); } else{ const std::string addr = _ctrl_transport->get_recv_addr(); - const std::string net_burner_path = (fs::path(uhd::get_pkg_path()) / UHD_LIB_DIR / "uhd" / "utils" / "usrp_n2xx_simple_net_burner").string(); - const std::string net_burner_cmd = str(boost::format("\"%s\" %s--addr=\"%s\"") % net_burner_path % ml % addr); - return str(boost::format("%s\n%s") % print_utility_error("uhd_images_downloader.py") % net_burner_cmd); + const std::string image_loader_path = (fs::path(uhd::get_pkg_path()) / "bin" / "uhd_image_loader").string(); + const std::string image_loader_cmd = str(boost::format(" \"%s\" %s--args=\"type=usrp2,addr=%s\"") % image_loader_path % ml % addr); + return str(boost::format("%s\n%s") % print_utility_error("uhd_images_downloader.py") % image_loader_cmd); } } diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp index 1acc1dad3..6073ec1c0 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.cpp +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -48,7 +48,7 @@ static const size_t DEFAULT_NUM_FRAMES = 32; /*********************************************************************** * Discovery over the udp transport **********************************************************************/ -static device_addrs_t usrp2_find(const device_addr_t &hint_){ +device_addrs_t usrp2_find(const device_addr_t &hint_){ //handle the multi-device discovery device_addrs_t hints = separate_device_addr(hint_); if (hints.size() > 1){ diff --git a/host/lib/usrp/usrp2/usrp2_impl.hpp b/host/lib/usrp/usrp2/usrp2_impl.hpp index 701403029..07cd98b4c 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.hpp +++ b/host/lib/usrp/usrp2/usrp2_impl.hpp @@ -42,6 +42,7 @@ #include <uhd/transport/vrt_if_packet.hpp> #include <uhd/transport/udp_simple.hpp> #include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/types/device_addr.hpp> #include <uhd/usrp/dboard_manager.hpp> #include <uhd/usrp/subdev_spec.hpp> #include <boost/weak_ptr.hpp> @@ -55,6 +56,8 @@ static const boost::uint32_t USRP2_TX_ASYNC_SID = 2; static const boost::uint32_t USRP2_RX_SID_BASE = 3; static const std::string USRP2_EEPROM_MAP_KEY = "N100"; +uhd::device_addrs_t usrp2_find(const uhd::device_addr_t &hint_); + //! Make a usrp2 dboard interface. uhd::usrp::dboard_iface::sptr make_usrp2_dboard_iface( uhd::timed_wb_iface::sptr wb_iface, diff --git a/host/lib/usrp/x300/CMakeLists.txt b/host/lib/usrp/x300/CMakeLists.txt index a588f901b..15af44721 100644 --- a/host/lib/usrp/x300/CMakeLists.txt +++ b/host/lib/usrp/x300/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2013 Ettus Research LLC +# Copyright 2013,2015 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 @@ -34,5 +34,7 @@ IF(ENABLE_X300) ${CMAKE_CURRENT_SOURCE_DIR}/x300_io_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_dboard_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_clock_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_image_loader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cdecode.c ) ENDIF(ENABLE_X300) diff --git a/host/lib/usrp/x300/cdecode.c b/host/lib/usrp/x300/cdecode.c new file mode 100644 index 000000000..1d09cbe22 --- /dev/null +++ b/host/lib/usrp/x300/cdecode.c @@ -0,0 +1,80 @@ +/* +cdecoder.c - c source to a base64 decoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include "cdecode.h" + +int base64_decode_value(char value_in){ + static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const char decoding_size = sizeof(decoding); + value_in -= 43; + if ((signed char)value_in < 0 || value_in > decoding_size) return -1; + return decoding[(int)value_in]; +} + +void base64_init_decodestate(base64_decodestate* state_in){ + state_in->step = step_a; + state_in->plainchar = 0; +} + +size_t base64_decode_block(const char* code_in, const size_t length_in, char* plaintext_out, base64_decodestate* state_in){ + const char* codechar = code_in; + char* plainchar = plaintext_out; + char fragment; + + *plainchar = state_in->plainchar; + + switch (state_in->step){ + while (1){ + case step_a: + do{ + if (codechar == code_in+length_in){ + state_in->step = step_a; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while ((signed char)fragment < 0); + *plainchar = (fragment & 0x03f) << 2; + + case step_b: + do{ + if (codechar == code_in+length_in){ + state_in->step = step_b; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while ((signed char)fragment < 0); + *plainchar++ |= (fragment & 0x030) >> 4; + *plainchar = (fragment & 0x00f) << 4; + case step_c: + do{ + if (codechar == code_in+length_in) + { + state_in->step = step_c; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while ((signed char)fragment < 0); + *plainchar++ |= (fragment & 0x03c) >> 2; + *plainchar = (fragment & 0x003) << 6; + case step_d: + do{ + if (codechar == code_in+length_in){ + state_in->step = step_d; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while ((signed char)fragment < 0); + *plainchar++ |= (fragment & 0x03f); + } + } + /* control should not reach here */ + return plainchar - plaintext_out; +} diff --git a/host/lib/usrp/x300/cdecode.h b/host/lib/usrp/x300/cdecode.h new file mode 100644 index 000000000..b8da55aa1 --- /dev/null +++ b/host/lib/usrp/x300/cdecode.h @@ -0,0 +1,36 @@ +/* +cdecode.h - c header for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CDECODE_H +#define BASE64_CDECODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stddef.h> + +typedef enum{ + step_a, step_b, step_c, step_d +} base64_decodestep; + +typedef struct{ + base64_decodestep step; + char plainchar; +} base64_decodestate; + +void base64_init_decodestate(base64_decodestate* state_in); + +int base64_decode_value(char value_in); + +size_t base64_decode_block(const char* code_in, const size_t length_in, char* plaintext_out, base64_decodestate* state_in); + +#ifdef __cplusplus +} +#endif + +#endif /* BASE64_CDECODE_H */ diff --git a/host/lib/usrp/x300/x300_image_loader.cpp b/host/lib/usrp/x300/x300_image_loader.cpp new file mode 100644 index 000000000..9d92e7932 --- /dev/null +++ b/host/lib/usrp/x300/x300_image_loader.cpp @@ -0,0 +1,402 @@ +// +// Copyright 2015 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 <fstream> +#include <vector> + +#include <boost/algorithm/string.hpp> +#include <boost/filesystem.hpp> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/xml_parser.hpp> + +#include <uhd/config.hpp> +#include <uhd/device.hpp> +#include <uhd/image_loader.hpp> +#include <uhd/exception.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/transport/nirio/niusrprio_session.h> +#include <uhd/transport/nirio/status.h> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/static.hpp> + +#include "x300_impl.hpp" +#include "x300_fw_common.h" +#include "cdecode.h" + +namespace fs = boost::filesystem; + +using namespace boost::algorithm; +using namespace uhd; +using namespace uhd::transport; + +/* + * Constants + */ +#define X300_FPGA_BIN_SIZE_BYTES 15877916 +#define X300_FPGA_BIT_SIZE_BYTES 15878022 +#define X300_FPGA_PROG_UDP_PORT 49157 +#define X300_FLASH_SECTOR_SIZE 131072 +#define X300_PACKET_SIZE_BYTES 256 +#define X300_FPGA_SECTOR_START 32 +#define X300_MAX_RESPONSE_BYTES 128 +#define UDP_TIMEOUT 3 +#define FPGA_LOAD_TIMEOUT 15 + +/* + * Packet structure + */ +typedef struct { + boost::uint32_t flags; + boost::uint32_t sector; + boost::uint32_t index; + boost::uint32_t size; + union { + boost::uint8_t data8[X300_PACKET_SIZE_BYTES]; + boost::uint16_t data16[X300_PACKET_SIZE_BYTES/2]; + }; +} x300_fpga_update_data_t; + +/* + * X-Series burn session + */ +typedef struct { + bool found; + bool ethernet; + bool configure; // Reload FPGA after burning to flash (Ethernet only) + bool verify; // Device will verify the download along the way (Ethernet only) + bool lvbitx; + uhd::device_addr_t dev_addr; + std::string ip_addr; + std::string fpga_type; + std::string resource; + std::string filepath; + std::string rpc_port; + boost::uint32_t size; + udp_simple::sptr xport; + std::vector<char> bitstream; // .bin image extracted from .lvbitx file + boost::uint8_t data_in[udp_simple::mtu]; +} x300_session_t; + +/* + * Extract the .bin image from the given LVBITX file. + */ +static void extract_from_lvbitx(x300_session_t &session){ + boost::property_tree::ptree pt; + boost::property_tree::xml_parser::read_xml(session.filepath.c_str(), pt, + boost::property_tree::xml_parser::no_comments | + boost::property_tree::xml_parser::trim_whitespace); + const std::string encoded_bitstream(pt.get<std::string>("Bitfile.Bitstream")); + std::vector<char> decoded_bitstream(encoded_bitstream.size()); + + base64_decodestate decode_state; + base64_init_decodestate(&decode_state); + const size_t decoded_size = base64_decode_block(encoded_bitstream.c_str(), + encoded_bitstream.size(), &decoded_bitstream.front(), &decode_state); + decoded_bitstream.resize(decoded_size); + session.bitstream.swap(decoded_bitstream); + + session.size = session.bitstream.size(); +} + +/* + * Validate X300 image and extract if LVBITX. + */ +static void x300_validate_image(x300_session_t &session){ + if(not fs::exists(session.filepath)){ + throw uhd::runtime_error(str(boost::format("Could not find image at path \"%s\".") + % session.filepath)); + } + + std::string extension = fs::extension(session.filepath); + session.lvbitx = (extension == ".lvbitx"); + + if(session.lvbitx){ + extract_from_lvbitx(session); + if(session.size > X300_FPGA_BIN_SIZE_BYTES){ + throw uhd::runtime_error(str(boost::format("The specified FPGA image is too large: %d vs. %d") + % session.size % X300_FPGA_BIN_SIZE_BYTES)); + } + + /* + * PCIe burning just takes a filepath, even for a .lvbitx file, + * so just extract it to validate the size. + */ + if(!session.ethernet) session.bitstream.clear(); + } + else if(extension == ".bin" or extension == ".bit"){ + boost::uint32_t max_size = (extension == ".bin") ? X300_FPGA_BIN_SIZE_BYTES + : X300_FPGA_BIT_SIZE_BYTES; + + session.size = fs::file_size(session.filepath); + if(session.size > max_size){ + throw uhd::runtime_error(str(boost::format("The specified FPGA image is too large: %d vs. %d") + % session.size % max_size)); + return; + } + } + else{ + throw uhd::runtime_error(str(boost::format("Invalid extension \"%s\". Extension must be .bin, .bit, or .lvbitx.") + % extension)); + } +} + +static void x300_setup_session(x300_session_t &session, + const device_addr_t &args, + const std::string &filepath){ + device_addr_t find_args; + find_args["type"] = "x300"; + if(args.has_key("name")) find_args["name"] = args["name"]; + if(args.has_key("serial")) find_args["serial"] = args["serial"]; + if(args.has_key("ip-addr")) find_args["addr"] = args["ip-addr"]; + else if(args.has_key("resource")) find_args["resource"] = args["resource"]; + + device_addrs_t devs = x300_find(args); + session.found = (devs.size() > 0); + if(!session.found) return; + + session.dev_addr = devs[0]; + session.ethernet = session.dev_addr.has_key("addr"); + if(session.ethernet){ + session.ip_addr = session.dev_addr["addr"]; + session.configure = args.has_key("configure"); + session.xport = udp_simple::make_connected(session.ip_addr, + BOOST_STRINGIZE(X300_FPGA_PROG_UDP_PORT)); + session.verify = args.has_key("verify"); + } + else{ + session.resource = session.dev_addr["resource"]; + session.rpc_port = args.get("rpc-port", "5444"); + } + + /* + * The user can specify an FPGA type (1G, HGS, XGS), rather than a filename. If the user + * does not specify one, this will default to the type currently on the device. If this + * cannot be determined, then the user is forced to specify a filename. + */ + session.fpga_type = args.get("fpga", session.dev_addr.get("fpga", "")); + if(filepath == ""){ + if(!session.dev_addr.has_key("product") or session.fpga_type == ""){ + throw uhd::runtime_error("Found a device but could not auto-generate an image filename."); + } + else session.filepath = find_image_path(str(boost::format("usrp_%s_fpga_%s.bit") + % (to_lower_copy(session.dev_addr["product"])) + % session.fpga_type)); + } + else session.filepath = filepath; + + // Validate image + x300_validate_image(session); +} + +/* + * Ethernet communication functions + */ +static UHD_INLINE size_t x300_send_and_recv(udp_simple::sptr xport, + boost::uint32_t pkt_code, + x300_fpga_update_data_t *pkt_out, + boost::uint8_t* data){ + pkt_out->flags = uhd::htonx<boost::uint32_t>(pkt_code); + xport->send(boost::asio::buffer(pkt_out, sizeof(*pkt_out))); + return xport->recv(boost::asio::buffer(data, udp_simple::mtu), UDP_TIMEOUT); +} + +static UHD_INLINE bool x300_recv_ok(const x300_fpga_update_data_t *pkt_in, + size_t len){ + return (len > 0 and + ((ntohl(pkt_in->flags) & X300_FPGA_PROG_FLAGS_ERROR) != X300_FPGA_PROG_FLAGS_ERROR)); +} + +// Image data needs to be bitswapped +static UHD_INLINE void x300_bitswap(boost::uint8_t *num){ + *num = ((*num & 0xF0) >> 4) | ((*num & 0x0F) << 4); + *num = ((*num & 0xCC) >> 2) | ((*num & 0x33) << 2); + *num = ((*num & 0xAA) >> 1) | ((*num & 0x55) << 1); +} + +static void x300_ethernet_load(x300_session_t &session){ + + // UDP receive buffer + x300_fpga_update_data_t pkt_out; + const x300_fpga_update_data_t *pkt_in = reinterpret_cast<const x300_fpga_update_data_t*>(session.data_in); + + // Initialize write session + boost::uint32_t flags = X300_FPGA_PROG_FLAGS_ACK | X300_FPGA_PROG_FLAGS_INIT; + size_t len = x300_send_and_recv(session.xport, flags, &pkt_out, session.data_in); + if(x300_recv_ok(pkt_in, len)){ + std::cout << "-- Initializing FPGA loading..." << std::flush; + } + else if(len == 0){ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Timed out waiting for reply from device."); + } + else{ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Device reported an error during initialization."); + } + + std::cout << "successful." << std::endl; + if(session.verify){ + std::cout << "-- NOTE: Device is verifying the image it is receiving, increasing the loading time." << std::endl; + } + + size_t current_pos = 0; + size_t sectors = (session.size / X300_FLASH_SECTOR_SIZE); + std::ifstream image(session.filepath.c_str(), std::ios::binary); + + // Each sector + for(size_t i = 0; i < session.size; i += X300_FLASH_SECTOR_SIZE){ + + // Print progress percentage at beginning of each sector + std::cout << boost::format("\r-- Loading %s FPGA image: %d%% (%d/%d sectors)") + % session.fpga_type + % (int(double(i) / double(session.size) * 100.0)) + % (i / X300_FLASH_SECTOR_SIZE) + % sectors + << std::flush; + + // Each packet + for(size_t j = i; (j < session.size and j < (i+X300_FLASH_SECTOR_SIZE)); j += X300_PACKET_SIZE_BYTES){ + flags = X300_FPGA_PROG_FLAGS_ACK; + if(j == i) flags |= X300_FPGA_PROG_FLAGS_ERASE; // Erase at beginning of sector + if(session.verify) flags |= X300_FPGA_PROG_FLAGS_VERIFY; + + // Set burn location + pkt_out.sector = htonx<boost::uint32_t>(X300_FPGA_SECTOR_START + (i/X300_FLASH_SECTOR_SIZE)); + pkt_out.index = htonx<boost::uint32_t>((j % X300_FLASH_SECTOR_SIZE) / 2); + pkt_out.size = htonx<boost::uint32_t>(X300_PACKET_SIZE_BYTES / 2); + + // Read next piece of image + memset(pkt_out.data8, 0, X300_PACKET_SIZE_BYTES); + if(session.lvbitx){ + memcpy(pkt_out.data8, &session.bitstream[current_pos], X300_PACKET_SIZE_BYTES); + current_pos += X300_PACKET_SIZE_BYTES; + } + else{ + image.read((char*)pkt_out.data8, X300_PACKET_SIZE_BYTES); + } + + // Data must be bitswapped and byteswapped + for(size_t k = 0; k < X300_PACKET_SIZE_BYTES; k++){ + x300_bitswap(&pkt_out.data8[k]); + } + for(size_t k = 0; k < (X300_PACKET_SIZE_BYTES/2); k++){ + pkt_out.data16[k] = htonx<boost::uint16_t>(pkt_out.data16[k]); + } + + len = x300_send_and_recv(session.xport, flags, &pkt_out, session.data_in); + if(len == 0){ + if(!session.lvbitx) image.close(); + throw uhd::runtime_error("Timed out waiting for reply from device."); + } + else if((ntohl(pkt_in->flags) & X300_FPGA_PROG_FLAGS_ERROR)){ + if(!session.lvbitx) image.close(); + throw uhd::runtime_error("Device reported an error."); + } + } + } + if(!session.lvbitx){ + image.close(); + } + + std::cout << boost::format("\r-- Loading %s FPGA image: 100%% (%d/%d sectors)") + % session.fpga_type + % sectors + % sectors + << std::endl; + + // Cleanup + if(!session.lvbitx) image.close(); + flags = (X300_FPGA_PROG_FLAGS_CLEANUP | X300_FPGA_PROG_FLAGS_ACK); + pkt_out.sector = pkt_out.index = pkt_out.size = 0; + memset(pkt_out.data8, 0, X300_PACKET_SIZE_BYTES); + std::cout << "-- Finalizing image load..." << std::flush; + len = x300_send_and_recv(session.xport, flags, &pkt_out, session.data_in); + if(len == 0){ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Timed out waiting for reply from device."); + } + else if((ntohl(pkt_in->flags) & X300_FPGA_PROG_FLAGS_ERROR)){ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Device reported an error during cleanup."); + } + else std::cout << "successful." << std::endl; + + // Save new FPGA image (if option set) + if(session.configure){ + flags = (X300_FPGA_PROG_CONFIGURE | X300_FPGA_PROG_FLAGS_ACK); + x300_send_and_recv(session.xport, flags, &pkt_out, session.data_in); + std::cout << "-- Saving image onto device..." << std::flush; + if(len == 0){ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Timed out waiting for reply from device."); + } + else if((ntohl(pkt_in->flags) & X300_FPGA_PROG_FLAGS_ERROR)){ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Device reported an error while saving the image."); + } + else std::cout << "successful." << std::endl; + } +} + +static void x300_pcie_load(x300_session_t &session){ + + std::cout << boost::format("\r-- Loading %s FPGA image (this will take 5-10 minutes)...") + % session.fpga_type + << std::flush; + + nirio_status status = NiRio_Status_Success; + niusrprio::niusrprio_session fpga_session(session.resource, session.rpc_port); + nirio_status_chain(fpga_session.download_bitstream_to_flash(session.filepath), status); + + if(nirio_status_fatal(status)){ + std::cout << "failed." << std::endl; + niusrprio::nirio_status_to_exception(status, "NI-RIO reported the following error:"); + } + else std::cout << "successful." << std::endl; +} + +static bool x300_image_loader(const image_loader::image_loader_args_t &image_loader_args){ + // See if any X3x0 with the given args is found + device_addrs_t devs = x300_find(image_loader_args.args); + if(devs.size() == 0 or !image_loader_args.load_fpga) return false; + + x300_session_t session; + x300_setup_session(session, + image_loader_args.args, + image_loader_args.fpga_path + ); + if(!session.found) return false; + + std::cout << boost::format("Unit: USRP %s (%s, %s)\nFPGA Image: %s\n") + % session.dev_addr["product"] + % session.dev_addr["serial"] + % session.dev_addr[session.ethernet ? "addr" : "resource"] + % session.filepath; + + if(session.ethernet) x300_ethernet_load(session); + else x300_pcie_load(session); + return true; +} + +UHD_STATIC_BLOCK(register_x300_image_loader){ + std::string recovery_instructions = "Aborting. Your USRP X-Series device will likely be unusable. Visit\n" + "http://files.ettus.com/manual/page_usrp_x3x0.html#x3x0_load_fpga_imgs_jtag\n" + "for details on restoring your device."; + + image_loader::register_image_loader("x300", x300_image_loader, recovery_instructions); +} diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp index 91e6ded26..8c565a252 100644 --- a/host/lib/usrp/x300/x300_impl.cpp +++ b/host/lib/usrp/x300/x300_impl.cpp @@ -235,7 +235,7 @@ static device_addrs_t x300_find_pcie(const device_addr_t &hint, bool explicit_qu return addrs; } -static device_addrs_t x300_find(const device_addr_t &hint_) +device_addrs_t x300_find(const device_addr_t &hint_) { //handle the multi-device discovery device_addrs_t hints = separate_device_addr(hint_); @@ -509,7 +509,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) //check compat numbers //check fpga compat before fw compat because the fw is a subset of the fpga image - this->check_fpga_compat(mb_path, mb.zpu_ctrl); + this->check_fpga_compat(mb_path, mb); this->check_fw_compat(mb_path, mb.zpu_ctrl); //store which FPGA image is loaded @@ -1696,25 +1696,33 @@ void x300_impl::check_fw_compat(const fs_path &mb_path, wb_iface::sptr iface) % compat_major % compat_minor)); } -void x300_impl::check_fpga_compat(const fs_path &mb_path, wb_iface::sptr iface) +void x300_impl::check_fpga_compat(const fs_path &mb_path, const mboard_members_t &members) { - boost::uint32_t compat_num = iface->peek32(SR_ADDR(SET0_BASE, ZPU_RB_COMPAT_NUM)); + boost::uint32_t compat_num = members.zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_COMPAT_NUM)); boost::uint32_t compat_major = (compat_num >> 16); boost::uint32_t compat_minor = (compat_num & 0xffff); if (compat_major != X300_FPGA_COMPAT_MAJOR) { + std::string image_loader_path = (fs::path(uhd::get_pkg_path()) / "bin" / "uhd_image_loader").string(); + std::string image_loader_cmd = str(boost::format("\"%s\" --args=\"type=x300,%s=%s\"") + % image_loader_path + % (members.xport_path == "eth" ? "addr" + : "resource") + % members.addr); + throw uhd::runtime_error(str(boost::format( "Expected FPGA compatibility number %d, but got %d:\n" "The FPGA image on your device is not compatible with this host code build.\n" "Download the appropriate FPGA images for this version of UHD.\n" "%s\n\n" "Then burn a new image to the on-board flash storage of your\n" - "USRP X3xx device using the burner utility. %s\n\n" + "USRP X3xx device using the image loader utility. Use this command:\n\n%s\n\n" "For more information, refer to the UHD manual:\n\n" " http://files.ettus.com/manual/page_usrp_x3x0.html#x3x0_flash" ) % int(X300_FPGA_COMPAT_MAJOR) % compat_major - % print_utility_error("uhd_images_downloader.py") % print_utility_error("usrp_x3xx_fpga_burner"))); + % print_utility_error("uhd_images_downloader.py") + % image_loader_cmd)); } _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") % compat_major % compat_minor)); diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp index dca6360b8..64f14cae6 100644 --- a/host/lib/usrp/x300/x300_impl.hpp +++ b/host/lib/usrp/x300/x300_impl.hpp @@ -142,6 +142,8 @@ uhd::uart_iface::sptr x300_make_uart_iface(uhd::wb_iface::sptr iface); uhd::wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp); uhd::wb_iface::sptr x300_make_ctrl_iface_pcie(uhd::niusrprio::niriok_proxy::sptr drv_proxy); +uhd::device_addrs_t x300_find(const uhd::device_addr_t &hint_); + class x300_impl : public uhd::device { public: @@ -398,7 +400,7 @@ private: void set_mb_eeprom(uhd::i2c_iface::sptr i2c, const uhd::usrp::mboard_eeprom_t &); void check_fw_compat(const uhd::fs_path &mb_path, uhd::wb_iface::sptr iface); - void check_fpga_compat(const uhd::fs_path &mb_path, uhd::wb_iface::sptr iface); + void check_fpga_compat(const uhd::fs_path &mb_path, const mboard_members_t &members); void update_atr_leds(gpio_core_200_32wo::sptr, const std::string &ant); boost::uint32_t get_fp_gpio(gpio_core_200::sptr); diff --git a/host/lib/usrp_clock/octoclock/CMakeLists.txt b/host/lib/usrp_clock/octoclock/CMakeLists.txt index e363bb9da..c489657e2 100644 --- a/host/lib/usrp_clock/octoclock/CMakeLists.txt +++ b/host/lib/usrp_clock/octoclock/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2011-2014 Ettus Research LLC +# Copyright 2013-2015 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 @@ -16,17 +16,15 @@ # ######################################################################## -# 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}/ihexcvt.cpp ${CMAKE_CURRENT_SOURCE_DIR}/octoclock_eeprom.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/octoclock_image_loader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/octoclock_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/octoclock_uart.cpp ) diff --git a/host/lib/usrp_clock/octoclock/ihexcvt.cpp b/host/lib/usrp_clock/octoclock/ihexcvt.cpp new file mode 100644 index 000000000..0605ee61c --- /dev/null +++ b/host/lib/usrp_clock/octoclock/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/lib/usrp_clock/octoclock/ihexcvt.hpp b/host/lib/usrp_clock/octoclock/ihexcvt.hpp new file mode 100644 index 000000000..d577ece1f --- /dev/null +++ b/host/lib/usrp_clock/octoclock/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/lib/usrp_clock/octoclock/octoclock_image_loader.cpp b/host/lib/usrp_clock/octoclock/octoclock_image_loader.cpp new file mode 100644 index 000000000..c88177f0f --- /dev/null +++ b/host/lib/usrp_clock/octoclock/octoclock_image_loader.cpp @@ -0,0 +1,340 @@ +// +// Copyright 2015 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 <cstring> +#include <fstream> +#include <iostream> +#include <string> + +#include <boost/cstdint.hpp> +#include <boost/filesystem.hpp> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/thread.hpp> + +#include <uhd/device.hpp> +#include <uhd/image_loader.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/paths.hpp> +#include <uhd/utils/static.hpp> + +#include "octoclock_impl.hpp" +#include "common.h" +#include "ihexcvt.hpp" + +namespace fs = boost::filesystem; +using namespace uhd; +using namespace uhd::usrp_clock; +using namespace uhd::transport; + +#define OCTOCLOCK_FIRMWARE_MAX_SIZE_BYTES (1024*120) // Last 8 MB are for bootloader +#define OCTOCLOCK_BLOCK_SIZE 256 + +/* + * OctoClock burn session + */ +typedef struct { + bool valid; + uhd::device_addr_t dev_addr; + std::string given_filepath; + std::string actual_filepath; // If using a .hex, this is the converted .bin + bool from_hex; + boost::uint32_t size; + boost::uint16_t crc; + boost::uint16_t num_blocks; + udp_simple::sptr ctrl_xport; + udp_simple::sptr fw_xport; + boost::uint8_t data_in[udp_simple::mtu]; +} octoclock_session_t; + +static void octoclock_calculate_crc(octoclock_session_t &session){ + std::ifstream ifile(session.actual_filepath.c_str()); + boost::uint8_t temp_image[OCTOCLOCK_FIRMWARE_MAX_SIZE_BYTES]; + ifile.read((char*)temp_image, session.size); + + session.crc = 0xFFFF; + for(size_t i = 0; i < session.size; i++){ + session.crc ^= temp_image[i]; + for(boost::uint8_t j = 0; j < 8; ++j){ + if(session.crc & 1) session.crc = (session.crc >> 1) ^ 0xA001; + else session.crc = (session.crc >> 1); + } + } + + ifile.close(); +} + +static void octoclock_validate_firmware_image(octoclock_session_t &session){ + if(not fs::exists(session.given_filepath)){ + throw uhd::runtime_error(str(boost::format("Could not find image at path \"%s\"") + % session.given_filepath)); + } + + std::string extension = fs::extension(session.given_filepath); + if(extension == ".bin"){ + session.actual_filepath = session.given_filepath; + session.from_hex = false; + } + else if(extension == ".hex"){ + session.actual_filepath = fs::path(fs::path(uhd::get_tmp_path()) / + str(boost::format("octoclock_fw_%d.bin") + % time_spec_t::get_system_time().get_full_secs()) + ).string(); + + Hex2Bin(session.given_filepath.c_str(), session.actual_filepath.c_str(), false); + session.from_hex = true; + } + else throw uhd::runtime_error(str(boost::format("Invalid extension \"%s\". Extension must be .hex or .bin."))); + + session.size = fs::file_size(session.actual_filepath); + if(session.size > OCTOCLOCK_FIRMWARE_MAX_SIZE_BYTES){ + throw uhd::runtime_error(str(boost::format("The specified firmware image is too large: %d vs. %d") + % session.size % OCTOCLOCK_FIRMWARE_MAX_SIZE_BYTES)); + } + + session.num_blocks = (session.size % OCTOCLOCK_BLOCK_SIZE) ? ((session.size / OCTOCLOCK_BLOCK_SIZE) + 1) + : (session.size / OCTOCLOCK_BLOCK_SIZE); + + octoclock_calculate_crc(session); + session.valid = true; +} + +static void octoclock_setup_session(octoclock_session_t &session, + const std::string &filepath){ + + // If no filepath is given, use the default + if(filepath == ""){ + session.given_filepath = find_image_path(str(boost::format("octoclock_r%d_fw.hex") + % boost::lexical_cast<std::string>( + session.dev_addr.get("revision","4") + ))); + } + else session.given_filepath = filepath; + + octoclock_validate_firmware_image(session); + + session.ctrl_xport = udp_simple::make_connected(session.dev_addr["addr"], + BOOST_STRINGIZE(OCTOCLOCK_UDP_CTRL_PORT)); + session.fw_xport = udp_simple::make_connected(session.dev_addr["addr"], + BOOST_STRINGIZE(OCTOCLOCK_UDP_FW_PORT)); +} + +static void octoclock_reset_into_bootloader(octoclock_session_t &session){ + + // Already in bootloader + if(session.dev_addr["type"] == "octoclock-bootloader") + return; + + octoclock_packet_t pkt_out; + pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + const octoclock_packet_t* pkt_in = reinterpret_cast<const octoclock_packet_t*>(session.data_in); + size_t len; + + std::cout << " -- Resetting into bootloader..." << std::flush; + UHD_OCTOCLOCK_SEND_AND_RECV(session.ctrl_xport, RESET_CMD, pkt_out, len, session.data_in); + if(UHD_OCTOCLOCK_PACKET_MATCHES(RESET_ACK, pkt_out, pkt_in, len)){ + + // Make sure this device is now in its bootloader + boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + uhd::device_addrs_t octoclocks = uhd::device::find( + uhd::device_addr_t(str(boost::format("addr=%s") + % session.dev_addr["addr"] + ))); + if(octoclocks.size() == 0){ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Failed to reset OctoClock."); + } + else if(octoclocks[0]["type"] != "octoclock-bootloader"){ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Failed to reset OctoClock."); + } + else{ + std::cout << "successful." << std::endl; + session.dev_addr = octoclocks[0]; + } + } + else{ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Failed to reset OctoClock."); + } +} + +static void octoclock_burn(octoclock_session_t &session){ + + // Make sure we're in the bootloader for this + octoclock_reset_into_bootloader(session); + + octoclock_packet_t pkt_out; + pkt_out.sequence = htonx<boost::uint32_t>(std::rand()); + const octoclock_packet_t* pkt_in = reinterpret_cast<const octoclock_packet_t*>(session.data_in); + + // Tell OctoClock to prepare for burn + pkt_out.len = htonx<boost::uint16_t>(session.size); + size_t len = 0; + std::cout << " -- Preparing OctoClock for firmware load..." << std::flush; + pkt_out.len = session.size; + pkt_out.crc = session.crc; + UHD_OCTOCLOCK_SEND_AND_RECV(session.fw_xport, PREPARE_FW_BURN_CMD, pkt_out, len, session.data_in); + if(UHD_OCTOCLOCK_PACKET_MATCHES(FW_BURN_READY_ACK, pkt_out, pkt_in, len)){ + std::cout << "successful." << std::endl; + } + else{ + std::cout << "failed." << std::endl; + if(session.from_hex){ + fs::remove(session.actual_filepath); + } + throw uhd::runtime_error("Failed to prepare OctoClock for firmware load."); + } + + // Start burning + std::ifstream image(session.actual_filepath.c_str(), std::ios::binary); + for(size_t i = 0; i < session.num_blocks; i++){ + pkt_out.sequence++; + pkt_out.addr = i * OCTOCLOCK_BLOCK_SIZE; + + std::cout << str(boost::format("\r -- Loading firmware: %d%% (%d/%d blocks)") + % int((double(i)/double(session.num_blocks))*100) + % i % session.num_blocks) + << std::flush; + + memset(pkt_out.data, 0, OCTOCLOCK_BLOCK_SIZE); + image.read((char*)pkt_out.data, OCTOCLOCK_BLOCK_SIZE); + UHD_OCTOCLOCK_SEND_AND_RECV(session.fw_xport, FILE_TRANSFER_CMD, pkt_out, len, session.data_in); + if(not UHD_OCTOCLOCK_PACKET_MATCHES(FILE_TRANSFER_ACK, pkt_out, pkt_in, len)){ + image.close(); + std::cout << std::endl; + if(session.from_hex){ + fs::remove(session.actual_filepath); + } + throw uhd::runtime_error("Failed to load firmware."); + } + } + + std::cout << str(boost::format("\r -- Loading firmware: 100%% (%d/%d blocks)") + % session.num_blocks % session.num_blocks) + << std::endl; + image.close(); +} + +static void octoclock_verify(octoclock_session_t &session){ + + octoclock_packet_t pkt_out; + pkt_out.sequence = htonx<boost::uint32_t>(std::rand()); + const octoclock_packet_t* pkt_in = reinterpret_cast<const octoclock_packet_t*>(session.data_in); + size_t len = 0; + + std::ifstream image(session.actual_filepath.c_str(), std::ios::binary); + boost::uint8_t image_part[OCTOCLOCK_BLOCK_SIZE]; + boost::uint16_t cmp_len = 0; + for(size_t i = 0; i < session.num_blocks; i++){ + pkt_out.sequence++; + pkt_out.addr = i * OCTOCLOCK_BLOCK_SIZE; + + std::cout << str(boost::format("\r -- Verifying firmware load: %d%% (%d/%d blocks)") + % int((double(i)/double(session.num_blocks))*100) + % i % session.num_blocks) + << std::flush; + + memset(image_part, 0, OCTOCLOCK_BLOCK_SIZE); + image.read((char*)image_part, OCTOCLOCK_BLOCK_SIZE); + cmp_len = image.gcount(); + + UHD_OCTOCLOCK_SEND_AND_RECV(session.fw_xport, READ_FW_CMD, pkt_out, len, session.data_in); + if(UHD_OCTOCLOCK_PACKET_MATCHES(READ_FW_ACK, pkt_out, pkt_in, len)){ + if(memcmp(pkt_in->data, image_part, cmp_len)){ + std::cout << std::endl; + image.close(); + if(session.from_hex){ + fs::remove(session.actual_filepath); + } + throw uhd::runtime_error("Failed to verify OctoClock firmware."); + } + } + else{ + std::cout << std::endl; + image.close(); + if(session.from_hex){ + fs::remove(session.actual_filepath); + } + throw uhd::runtime_error("Failed to verify OctoClock firmware."); + } + } + + image.close(); + if(session.from_hex){ + fs::remove(session.actual_filepath); + } + std::cout << str(boost::format("\r -- Verifying firmware load: 100%% (%d/%d blocks)") + % session.num_blocks % session.num_blocks) + << std::endl; +} + +static void octoclock_finalize(octoclock_session_t &session){ + + octoclock_packet_t pkt_out; + pkt_out.sequence = htonx<boost::uint32_t>(std::rand()); + const octoclock_packet_t* pkt_in = reinterpret_cast<const octoclock_packet_t*>(session.data_in); + size_t len = 0; + + std::cout << " -- Finalizing firmware load..." << std::flush; + UHD_OCTOCLOCK_SEND_AND_RECV(session.fw_xport, FINALIZE_BURNING_CMD, pkt_out, len, session.data_in); + if(UHD_OCTOCLOCK_PACKET_MATCHES(FINALIZE_BURNING_ACK, pkt_out, pkt_in, len)){ + std::cout << "successful." << std::endl; + } + else{ + std::cout << "failed." << std::endl; + throw uhd::runtime_error("Failed to finalize OctoClock firmware load."); + } +} + +bool octoclock_image_loader(const image_loader::image_loader_args_t &image_loader_args){ + // See if we can find an OctoClock with the given args + device_addrs_t devs = octoclock_find(image_loader_args.args); + if(devs.size() == 0 or !image_loader_args.load_firmware) return false; + + octoclock_session_t session; + session.dev_addr = devs[0]; + octoclock_setup_session(session, + image_loader_args.firmware_path + ); + + std::cout << boost::format("Unit: OctoClock (%s)") + % session.dev_addr["addr"] + << std::endl; + std::cout << "Firmware: " << session.given_filepath << std::endl; + + octoclock_burn(session); + octoclock_verify(session); + octoclock_finalize(session); + + return true; +} + +UHD_STATIC_BLOCK(register_octoclock_image_loader){ + std::string recovery_instructions = "Aborting. Your OctoClock firmware is now corrupt. The bootloader\n" + "is functional, but the device will not have functional clock distribution." + "Run this utility again to restore functionality or refer to:\n\n" + "http://files.ettus.com/manual/page_octoclock.html\n\n" + "for alternative setups."; + + image_loader::register_image_loader("octoclock", + octoclock_image_loader, + recovery_instructions); +} diff --git a/host/lib/usrp_clock/octoclock/octoclock_impl.hpp b/host/lib/usrp_clock/octoclock/octoclock_impl.hpp index ab45cd5f0..453e75ec5 100644 --- a/host/lib/usrp_clock/octoclock/octoclock_impl.hpp +++ b/host/lib/usrp_clock/octoclock/octoclock_impl.hpp @@ -31,6 +31,8 @@ #include "common.h" +uhd::device_addrs_t octoclock_find(const uhd::device_addr_t &hint); + /*! * OctoClock implementation guts */ |