// // Copyright 2015-2017 Ettus Research // Copyright 2018 Ettus Research, a National Instruments Company // // SPDX-License-Identifier: GPL-3.0-or-later // #include "cdecode.h" #include "x300_fw_common.h" #include "x300_impl.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 15878032 #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 /* * Bitstream header pattern */ static const uint8_t X300_FPGA_BIT_HEADER[] = {0x00, 0x09, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x00, 0x00, 0x01, 0x61, 0x00}; /* * Packet structure */ typedef struct { uint32_t flags; uint32_t sector; uint32_t index; uint32_t size; union { uint8_t data8[X300_PACKET_SIZE_BYTES]; 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 download; // Host will read the FPGA image on the device to a file bool lvbitx; uhd::device_addr_t dev_addr; std::string ip_addr; std::string fpga_type; std::string resource; std::string filepath; std::string outpath; std::string rpc_port; udp_simple::sptr write_xport; udp_simple::sptr read_xport; size_t size; uint8_t data_in[udp_simple::mtu]; std::vector bitstream; // .bin image extracted from .lvbitx file } 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("Bitfile.Bitstream")); std::vector 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") { 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, const std::string& outpath) { device_addrs_t devs = x300_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 X-Series device.\n" "Applicable devices:\n"; for (const uhd::device_addr_t& dev : devs) { std::string identifier = dev.has_key("addr") ? "addr" : "resource"; err_msg += str(boost::format(" * %s (%s=%s)\n") % dev.get("product", "X3XX") % identifier % dev.get(identifier)); } err_msg += "\nSpecify one of these devices with the given args to load an image " "onto it."; throw uhd::runtime_error(err_msg); } session.found = true; 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.write_xport = udp_simple::make_connected( session.ip_addr, BOOST_STRINGIZE(X300_FPGA_PROG_UDP_PORT)); session.read_xport = udp_simple::make_connected( session.ip_addr, BOOST_STRINGIZE(X300_FPGA_READ_UDP_PORT)); session.verify = args.has_key("verify"); session.download = args.has_key("download"); } 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; /* * The user can specify an output image path, or UHD will use the * system temporary path by default */ if (outpath == "") { 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."); } std::string filename = str(boost::format("usrp_%s_fpga_%s") % (to_lower_copy(session.dev_addr["product"])) % session.fpga_type); session.outpath = get_tmp_path() + "/" + filename; } else { session.outpath = outpath; } // Validate image x300_validate_image(session); } /* * Ethernet communication functions */ static UHD_INLINE size_t x300_send_and_recv(udp_simple::sptr xport, uint32_t pkt_code, x300_fpga_update_data_t* pkt_out, uint8_t* data) { pkt_out->flags = uhd::htonx(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(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(session.data_in); // Initialize write session uint32_t flags = X300_FPGA_PROG_FLAGS_ACK | X300_FPGA_PROG_FLAGS_INIT; size_t len = x300_send_and_recv(session.write_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(X300_FPGA_SECTOR_START + (i / X300_FLASH_SECTOR_SIZE)); pkt_out.index = htonx((j % X300_FLASH_SECTOR_SIZE) / 2); pkt_out.size = htonx(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(pkt_out.data16[k]); } len = x300_send_and_recv(session.write_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.write_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.write_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; } std::cout << str(boost::format("Power-cycle the USRP %s to use the new image.") % session.dev_addr.get("product", "")) << std::endl; } static void x300_ethernet_read(x300_session_t& session) { // UDP receive buffer x300_fpga_update_data_t pkt_out; memset(pkt_out.data8, 0, X300_PACKET_SIZE_BYTES); x300_fpga_update_data_t* pkt_in = reinterpret_cast(session.data_in); // Initialize read session uint32_t flags = X300_FPGA_READ_FLAGS_ACK | X300_FPGA_READ_FLAGS_INIT; size_t len = x300_send_and_recv(session.read_xport, flags, &pkt_out, session.data_in); if (x300_recv_ok(pkt_in, len)) { std::cout << "-- Initializing FPGA reading..." << 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; // Read the first packet // Acknowledge receipt of the FPGA image data flags = X300_FPGA_READ_FLAGS_ACK; // Set the initial burn location pkt_out.sector = htonx(X300_FPGA_SECTOR_START); pkt_out.index = 0; pkt_out.size = htonx(X300_PACKET_SIZE_BYTES / 2); len = x300_send_and_recv(session.read_xport, flags, &pkt_out, session.data_in); if (len == 0) { throw uhd::runtime_error("Timed out waiting for reply from device."); } else if ((ntohl(pkt_in->flags) & X300_FPGA_READ_FLAGS_ERROR)) { throw uhd::runtime_error("Device reported an error."); } // Data must be bitswapped and byteswapped for (size_t k = 0; k < X300_PACKET_SIZE_BYTES; k++) { x300_bitswap(&pkt_in->data8[k]); } for (size_t k = 0; k < (X300_PACKET_SIZE_BYTES / 2); k++) { pkt_in->data16[k] = htonx(pkt_in->data16[k]); } // Assume the largest size first size_t image_size = X300_FPGA_BIT_SIZE_BYTES; size_t sectors = (image_size / X300_FLASH_SECTOR_SIZE); std::string extension(".bit"); // Check for the beginning header sequence to determine // the total amount of data (.bit vs .bin) on the flash // The .bit file format includes header information not part of a .bin for (size_t i = 0; i < sizeof(X300_FPGA_BIT_HEADER); i++) { if (pkt_in->data8[i] != X300_FPGA_BIT_HEADER[i]) { std::cout << "-- No *.bit header detected, FPGA image is a raw stream (*.bin)!" << std::endl; image_size = X300_FPGA_BIN_SIZE_BYTES; sectors = (image_size / X300_FLASH_SECTOR_SIZE); extension = std::string(".bin"); break; } } session.outpath += extension; std::ofstream image(session.outpath.c_str(), std::ios::binary); std::cout << boost::format("-- Output FPGA file: %s\n") % session.outpath; // Write the first packet image.write((char*)pkt_in->data8, X300_PACKET_SIZE_BYTES); // Each sector size_t pkt_count = X300_PACKET_SIZE_BYTES; for (size_t i = 0; i < image_size; i += X300_FLASH_SECTOR_SIZE) { // Once we determine the image size, print the progress percentage std::cout << boost::format("\r-- Reading %s FPGA image: %d%% (%d/%d sectors)") % session.fpga_type % (int(double(i) / double(image_size) * 100.0)) % (i / X300_FLASH_SECTOR_SIZE) % sectors << std::flush; // Each packet while (pkt_count < image_size and pkt_count < (i + X300_FLASH_SECTOR_SIZE)) { // Set burn location pkt_out.sector = htonx(X300_FPGA_SECTOR_START + (i / X300_FLASH_SECTOR_SIZE)); pkt_out.index = htonx((pkt_count % X300_FLASH_SECTOR_SIZE) / 2); len = x300_send_and_recv(session.read_xport, flags, &pkt_out, session.data_in); if (len == 0) { image.close(); throw uhd::runtime_error("Timed out waiting for reply from device."); } else if ((ntohl(pkt_in->flags) & X300_FPGA_READ_FLAGS_ERROR)) { image.close(); throw uhd::runtime_error("Device reported an error."); } // Data must be bitswapped and byteswapped for (size_t k = 0; k < X300_PACKET_SIZE_BYTES; k++) { x300_bitswap(&pkt_in->data8[k]); } for (size_t k = 0; k < (X300_PACKET_SIZE_BYTES / 2); k++) { pkt_in->data16[k] = htonx(pkt_in->data16[k]); } // Calculate the number of bytes to write // If this is the last packet, get rid of the extra zero padding // due to packet size size_t nbytes = X300_PACKET_SIZE_BYTES; if (pkt_count > (image_size - X300_PACKET_SIZE_BYTES)) { nbytes = (image_size - pkt_count); } // Write the incoming piece of the image to a file image.write((char*)pkt_in->data8, nbytes); // Increment the data count pkt_count += X300_PACKET_SIZE_BYTES; } pkt_count = i + X300_FLASH_SECTOR_SIZE; } std::cout << boost::format("\r-- Reading %s FPGA image: 100%% (%d/%d sectors)") % session.fpga_type % sectors % sectors << std::endl; // Cleanup image.close(); flags = (X300_FPGA_READ_FLAGS_CLEANUP | X300_FPGA_READ_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 read for verification..." << std::flush; len = x300_send_and_recv(session.read_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_READ_FLAGS_ERROR)) { std::cout << "failed." << std::endl; throw uhd::runtime_error("Device reported an error during cleanup."); } else std::cout << "successful image read." << 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; std::cout << str(boost::format("Power-cycle the USRP %s to use the new image.") % session.dev_addr.get("product", "")) << 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) return false; x300_session_t session; x300_setup_session(session, image_loader_args.args, image_loader_args.fpga_path, image_loader_args.out_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; // Download the FPGA image to a file if (image_loader_args.download) { std::cout << "Attempting to download the FPGA image ..." << std::endl; x300_ethernet_read(session); } if (not image_loader_args.load_fpga) return true; 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); }