// // 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 . // #include "octoclock_impl.hpp" #include "common.h" #include "kk_ihex_read.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 found; 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_convert_ihex(octoclock_session_t &session){ struct ihex_state ihex; ihex_count_t count; char buf[256]; FILE* infile = fopen(session.given_filepath.c_str(), "r"); FILE* outfile = fopen(session.actual_filepath.c_str(), "w"); uint64_t line_number = 1; ihex_begin_read(&ihex); while(fgets(buf, 256, infile)){ count = ihex_count_t(strlen(buf)); ihex_read_bytes(&ihex, buf, count, outfile); line_number += (count && buf[count - 1] == '\n'); } ihex_end_read(&ihex, outfile); // Closes outfile (void)fclose(infile); } 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(); octoclock_convert_ihex(session); 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); } static void octoclock_setup_session(octoclock_session_t &session, const uhd::device_addr_t &args, const std::string &filepath){ // See if we can find an OctoClock with the given args device_addrs_t devs = octoclock_find(args); if(devs.size() == 0){ session.found = false; return; } else if(devs.size() > 1){ std::string err_msg = "Could not resolve given args to a single OctoClock device.\n" "Applicable devices:\n"; BOOST_FOREACH(const uhd::device_addr_t &dev, devs){ std::string name = (dev["type"] == "octoclock") ? str(boost::format("OctoClock r%d") % dev.get("revision","4")) : "OctoClock Bootloader"; err_msg += str(boost::format(" * %s (addr=%s)\n") % name % dev.get("addr")); } err_msg += "\nSpecify one of these devices with the given args to load an image onto it."; throw uhd::runtime_error(err_msg); } session.dev_addr = devs[0]; // If no filepath is given, use the default if(filepath == ""){ session.given_filepath = find_image_path(str(boost::format("octoclock_r%s_fw.hex") % 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(std::rand()); const octoclock_packet_t* pkt_in = reinterpret_cast(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(std::rand()); const octoclock_packet_t* pkt_in = reinterpret_cast(session.data_in); // Tell OctoClock to prepare for burn pkt_out.len = htonx(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(std::rand()); const octoclock_packet_t* pkt_in = reinterpret_cast(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(std::rand()); const octoclock_packet_t* pkt_in = reinterpret_cast(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){ octoclock_session_t session; octoclock_setup_session(session, image_loader_args.args, image_loader_args.firmware_path ); if(!session.found or !image_loader_args.load_firmware) return false; 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.\n" "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); }