// // Copyright 2012-2013 Ettus Research LLC // Copyright 2018-2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // #include "b200_iface.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //! libusb_error_name is only in newer API #ifndef HAVE_LIBUSB_ERROR_NAME # define libusb_error_name(code) str(boost::format("LIBUSB_ERROR_CODE %d") % code) #endif using namespace uhd; using namespace uhd::transport; static const bool load_img_msg = true; const static uint8_t FX3_FIRMWARE_LOAD = 0xA0; // 32 KB - 256 bytes for EEPROM storage constexpr size_t BOOTLOADER_MAX_SIZE = 32512; const static uint8_t VRT_VENDOR_OUT = (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT); const static uint8_t VRT_VENDOR_IN = (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN); const static uint8_t B200_VREQ_FPGA_START = 0x02; const static uint8_t B200_VREQ_FPGA_DATA = 0x12; const static uint8_t B200_VREQ_GET_COMPAT = 0x15; const static uint8_t B200_VREQ_SET_FPGA_HASH = 0x1C; const static uint8_t B200_VREQ_GET_FPGA_HASH = 0x1D; const static uint8_t B200_VREQ_SET_FW_HASH = 0x1E; const static uint8_t B200_VREQ_GET_FW_HASH = 0x1F; const static uint8_t B200_VREQ_LOOP = 0x22; const static uint8_t B200_VREQ_FPGA_CONFIG = 0x55; // const static uint8_t B200_VREQ_FPGA_RESET = 0x62; const static uint8_t B200_VREQ_GPIF_RESET = 0x72; const static uint8_t B200_VREQ_GET_USB = 0x80; const static uint8_t B200_VREQ_GET_STATUS = 0x83; const static uint8_t B200_VREQ_FX3_RESET = 0x99; const static uint8_t B200_VREQ_EEPROM_WRITE = 0xBA; const static uint8_t B200_VREQ_EEPROM_READ = 0xBB; const static uint8_t FX3_STATE_UNDEFINED = 0x00; const static uint8_t FX3_STATE_FPGA_READY = 0x01; const static uint8_t FX3_STATE_CONFIGURING_FPGA = 0x02; const static uint8_t FX3_STATE_BUSY = 0x03; const static uint8_t FX3_STATE_RUNNING = 0x04; const static uint8_t FX3_STATE_UNCONFIGURED = 0x05; const static uint8_t FX3_STATE_ERROR = 0x06; const static int VREQ_MAX_SIZE_USB2 = 64; const static int VREQ_MAX_SIZE_USB3 = 512; const static int VREQ_DEFAULT_SIZE = VREQ_MAX_SIZE_USB2; const static int VREQ_MAX_SIZE = VREQ_MAX_SIZE_USB3; typedef uint32_t hash_type; /*********************************************************************** * Helper Functions **********************************************************************/ /*! * Create a file hash * The hash will be used to identify the loaded fpga image * \param filename file used to generate hash value * \return hash value in a uint32_t type */ static hash_type generate_hash(const char* filename) { if (filename == NULL) return hash_type(0); std::ifstream file(filename); if (not file) { throw uhd::io_error(std::string("cannot open input file ") + filename); } hash_type hash = 0; char ch; long long count = 0; while (file.get(ch)) { count++; // hash algorithm derived from boost hash_combine // http://www.boost.org/doc/libs/1_35_0/doc/html/boost/hash_combine_id241013.html hash ^= ch + 0x9e3779b9 + (hash << 6) + (hash >> 2); } if (count == 0) { throw uhd::io_error(std::string("empty input file ") + filename); } if (not file.eof()) { throw uhd::io_error(std::string("file error ") + filename); } file.close(); return hash_type(hash); } /*********************************************************************** * The implementation class **********************************************************************/ class b200_iface_impl : public b200_iface { public: b200_iface_impl(usb_control::sptr usb_ctrl) : _usb_ctrl(usb_ctrl) { // NOP } int fx3_control_write(uint8_t request, uint16_t value, uint16_t index, unsigned char* buff, uint16_t length, uint32_t timeout = 0) { return _usb_ctrl->submit(VRT_VENDOR_OUT, // bmReqeustType request, // bRequest value, // wValue index, // wIndex buff, // data length, // wLength timeout); // timeout } int fx3_control_read(uint8_t request, uint16_t value, uint16_t index, unsigned char* buff, uint16_t length, uint32_t timeout = 0) { return _usb_ctrl->submit(VRT_VENDOR_IN, // bmReqeustType request, // bRequest value, // wValue index, // wIndex buff, // data length, // wLength timeout); // timeout } void write_i2c( UHD_UNUSED(uint16_t addr), UHD_UNUSED(const byte_vector_t& bytes)) override { throw uhd::not_implemented_error("b200 write i2c"); } byte_vector_t read_i2c( UHD_UNUSED(uint16_t addr), UHD_UNUSED(size_t num_bytes)) override { throw uhd::not_implemented_error("b200 read i2c"); } void write_eeprom(uint16_t addr, uint16_t offset, const byte_vector_t& bytes) override { int ret = fx3_control_write(B200_VREQ_EEPROM_WRITE, 0, offset | (uint16_t(addr) << 8), (unsigned char*)&bytes[0], bytes.size()); if (ret < 0) throw uhd::io_error((boost::format("Failed to write EEPROM (%d: %s)") % ret % libusb_error_name(ret)) .str()); else if ((size_t)ret != bytes.size()) throw uhd::io_error(( boost::format("Short write on write EEPROM (expecting: %d, returned: %d)") % bytes.size() % ret) .str()); } byte_vector_t read_eeprom(uint16_t addr, uint16_t offset, size_t num_bytes) override { byte_vector_t recv_bytes(num_bytes); int bytes_read = fx3_control_read(B200_VREQ_EEPROM_READ, 0, offset | (uint16_t(addr) << 8), (unsigned char*)&recv_bytes[0], num_bytes); if (bytes_read < 0) throw uhd::io_error((boost::format("Failed to read EEPROM (%d: %s)") % bytes_read % libusb_error_name(bytes_read)) .str()); else if ((size_t)bytes_read != num_bytes) throw uhd::io_error( (boost::format("Short read on read EEPROM (expecting: %d, returned: %d)") % num_bytes % bytes_read) .str()); return recv_bytes; } void load_firmware( const std::string filestring, UHD_UNUSED(bool force) = false) override { if (load_img_msg) { UHD_LOGGER_INFO("B200") << "Loading firmware image: " << filestring << "..."; } ihex_reader file_reader(filestring); try { file_reader.read(std::bind(&b200_iface_impl::fx3_control_write, this, FX3_FIRMWARE_LOAD, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, 0)); } catch (const uhd::io_error& e) { throw uhd::io_error( str(boost::format("Could not load firmware: \n%s") % e.what())); } // TODO // usrp_set_firmware_hash(hash); //set hash before reset /* Success! Let the system settle. */ // TODO: Replace this with a polling loop in the FX3, or find out // what the actual, correct timeout value is. std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } void reset_fx3(void) override { unsigned char data[4]; memset(data, 0x00, sizeof(data)); const int bytes_to_send = sizeof(data); int ret = fx3_control_write(B200_VREQ_FX3_RESET, 0x00, 0x00, data, bytes_to_send); if (ret < 0) throw uhd::io_error((boost::format("Failed to reset FX3 (%d: %s)") % ret % libusb_error_name(ret)) .str()); else if (ret != bytes_to_send) throw uhd::io_error( (boost::format("Short write on reset FX3 (expecting: %d, returned: %d)") % bytes_to_send % ret) .str()); } void reset_gpif(void) override { unsigned char data[4]; memset(data, 0x00, sizeof(data)); const int bytes_to_send = sizeof(data); int ret = fx3_control_write(B200_VREQ_GPIF_RESET, 0x00, 0x00, data, bytes_to_send); if (ret < 0) throw uhd::io_error((boost::format("Failed to reset GPIF (%d: %s)") % ret % libusb_error_name(ret)) .str()); else if (ret != bytes_to_send) throw uhd::io_error( (boost::format("Short write on reset GPIF (expecting: %d, returned: %d)") % bytes_to_send % ret) .str()); } void set_fpga_reset_pin(const bool reset) override { unsigned char data[4]; memset(data, (reset) ? 0xFF : 0x00, sizeof(data)); UHD_THROW_INVALID_CODE_PATH(); // Below is dead code as long as UHD_THROW_INVALID_CODE_PATH(); is declared above. // It is preserved here in a comment in case it is needed later: /* const int bytes_to_send = sizeof(data); int ret = fx3_control_write(B200_VREQ_FPGA_RESET, 0x00, 0x00, data, bytes_to_send); if (ret < 0) throw uhd::io_error((boost::format("Failed to reset FPGA (%d: %s)") % ret % libusb_error_name(ret)).str()); else if (ret != bytes_to_send) throw uhd::io_error((boost::format("Short write on reset FPGA (expecting: %d, returned: %d)") % bytes_to_send % ret).str()); */ } uint8_t get_usb_speed(void) override { unsigned char rx_data[1]; memset(rx_data, 0x00, sizeof(rx_data)); const int bytes_to_recv = sizeof(rx_data); int ret = fx3_control_read(B200_VREQ_GET_USB, 0x00, 0x00, rx_data, bytes_to_recv); if (ret < 0) throw uhd::io_error((boost::format("Failed to get USB speed (%d: %s)") % ret % libusb_error_name(ret)) .str()); else if (ret != bytes_to_recv) throw uhd::io_error(( boost::format("Short read on get USB speed (expecting: %d, returned: %d)") % bytes_to_recv % ret) .str()); return boost::lexical_cast(rx_data[0]); } uint8_t get_fx3_status(void) override { unsigned char rx_data[1]; memset(rx_data, 0x00, sizeof(rx_data)); const int bytes_to_recv = sizeof(rx_data); int ret = fx3_control_read(B200_VREQ_GET_STATUS, 0x00, 0x00, rx_data, bytes_to_recv); if (ret < 0) throw uhd::io_error((boost::format("Failed to get FX3 status (%d: %s)") % ret % libusb_error_name(ret)) .str()); else if (ret != bytes_to_recv) throw uhd::io_error( (boost::format( "Short read on get FX3 status (expecting: %d, returned: %d)") % bytes_to_recv % ret) .str()); return boost::lexical_cast(rx_data[0]); } uint16_t get_compat_num(void) override { unsigned char rx_data[2]; memset(rx_data, 0x00, sizeof(rx_data)); const int bytes_to_recv = sizeof(rx_data); int ret = fx3_control_read(B200_VREQ_GET_COMPAT, 0x00, 0x00, rx_data, bytes_to_recv); if (ret < 0) throw uhd::io_error((boost::format("Failed to get compat num (%d: %s)") % ret % libusb_error_name(ret)) .str()); else if (ret != bytes_to_recv) throw uhd::io_error( (boost::format( "Short read on get compat num (expecting: %d, returned: %d)") % bytes_to_recv % ret) .str()); return (((uint16_t)rx_data[0]) << 8) | rx_data[1]; } void usrp_get_firmware_hash(hash_type& hash) { const int bytes_to_recv = 4; if (sizeof(hash_type) != bytes_to_recv) throw uhd::type_error( (boost::format("hash_type is %d bytes but transfer length is %d bytes") % sizeof(hash_type) % bytes_to_recv) .str()); int ret = fx3_control_read( B200_VREQ_GET_FW_HASH, 0x00, 0x00, (unsigned char*)&hash, bytes_to_recv, 500); if (ret < 0) throw uhd::io_error((boost::format("Failed to get firmware hash (%d: %s)") % ret % libusb_error_name(ret)) .str()); else if (ret != bytes_to_recv) throw uhd::io_error( (boost::format( "Short read on get firmware hash (expecting: %d, returned: %d)") % bytes_to_recv % ret) .str()); } void usrp_set_firmware_hash(hash_type hash) { const int bytes_to_send = 4; if (sizeof(hash_type) != bytes_to_send) throw uhd::type_error( (boost::format("hash_type is %d bytes but transfer length is %d bytes") % sizeof(hash_type) % bytes_to_send) .str()); int ret = fx3_control_write( B200_VREQ_SET_FW_HASH, 0x00, 0x00, (unsigned char*)&hash, bytes_to_send); if (ret < 0) throw uhd::io_error((boost::format("Failed to set firmware hash (%d: %s)") % ret % libusb_error_name(ret)) .str()); else if (ret != bytes_to_send) throw uhd::io_error( (boost::format( "Short write on set firmware hash (expecting: %d, returned: %d)") % bytes_to_send % ret) .str()); } void usrp_get_fpga_hash(hash_type& hash) { const int bytes_to_recv = 4; if (sizeof(hash_type) != bytes_to_recv) throw uhd::type_error( (boost::format("hash_type is %d bytes but transfer length is %d bytes") % sizeof(hash_type) % bytes_to_recv) .str()); int ret = fx3_control_read(B200_VREQ_GET_FPGA_HASH, 0x00, 0x00, (unsigned char*)&hash, bytes_to_recv, 500); if (ret < 0) throw uhd::io_error((boost::format("Failed to get FPGA hash (%d: %s)") % ret % libusb_error_name(ret)) .str()); else if (ret != bytes_to_recv) throw uhd::io_error(( boost::format("Short read on get FPGA hash (expecting: %d, returned: %d)") % bytes_to_recv % ret) .str()); } void usrp_set_fpga_hash(hash_type hash) { const int bytes_to_send = 4; if (sizeof(hash_type) != bytes_to_send) throw uhd::type_error( (boost::format("hash_type is %d bytes but transfer length is %d bytes") % sizeof(hash_type) % bytes_to_send) .str()); int ret = fx3_control_write( B200_VREQ_SET_FPGA_HASH, 0x00, 0x00, (unsigned char*)&hash, bytes_to_send); if (ret < 0) throw uhd::io_error((boost::format("Failed to set FPGA hash (%d: %s)") % ret % libusb_error_name(ret)) .str()); else if (ret != bytes_to_send) throw uhd::io_error( (boost::format( "Short write on set FPGA hash (expecting: %d, returned: %d)") % bytes_to_send % ret) .str()); } // Establish default largest possible control request transfer size based on operating // USB speed int _get_transfer_size() { switch (get_usb_speed()) { case 2: return VREQ_DEFAULT_SIZE; case 3: return VREQ_MAX_SIZE_USB3; default: throw uhd::io_error( "load_fpga: get_usb_speed returned invalid USB speed (not 2 or 3)."); } } size_t _get_file_size(const char* filename) { boost::filesystem::path path(filename); auto filesize = boost::filesystem::file_size(path); return static_cast(filesize); } uint32_t load_fpga(const std::string filestring, bool force) override { uint8_t fx3_state = 0; uint32_t wait_count; int ret = 0; int bytes_to_xfer = 0; const char* filename = filestring.c_str(); hash_type hash = generate_hash(filename); hash_type loaded_hash; usrp_get_fpga_hash(loaded_hash); if (hash == loaded_hash and !force) return 0; const int transfer_size = _get_transfer_size(); UHD_ASSERT_THROW(transfer_size <= VREQ_MAX_SIZE); unsigned char out_buff[VREQ_MAX_SIZE]; // Request loopback read, which will indicate the firmware's current control // request buffer size int nread = fx3_control_read(B200_VREQ_LOOP, 0, 0, out_buff, transfer_size, 1000); if (nread < 0) throw uhd::io_error( (boost::format( "load_fpga: unable to complete firmware loopback request (%d: %s)") % nread % libusb_error_name(nread)) .str()); else if (nread != transfer_size) throw uhd::io_error( (boost::format("load_fpga: short read on firmware loopback request " "(expecting: %d, returned: %d)") % transfer_size % nread) .str()); const size_t file_size = _get_file_size(filename); std::ifstream file; file.open(filename, std::ios::in | std::ios::binary); if (!file.good()) { throw uhd::io_error("load_fpga: cannot open FPGA input file."); } // Zero the hash, in case we abort programming another image and revert to the // previously programmed image usrp_set_fpga_hash(0); memset(out_buff, 0x00, sizeof(out_buff)); bytes_to_xfer = 1; ret = fx3_control_write(B200_VREQ_FPGA_CONFIG, 0, 0, out_buff, bytes_to_xfer, 1000); if (ret < 0) throw uhd::io_error((boost::format("Failed to start FPGA config (%d: %s)") % ret % libusb_error_name(ret)) .str()); else if (ret != bytes_to_xfer) throw uhd::io_error( (boost::format( "Short write on start FPGA config (expecting: %d, returned: %d)") % bytes_to_xfer % ret) .str()); wait_count = 0; do { fx3_state = get_fx3_status(); if ((wait_count >= 500) || (fx3_state == FX3_STATE_ERROR) || (fx3_state == FX3_STATE_UNDEFINED)) { return fx3_state; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); wait_count++; } while (fx3_state != FX3_STATE_FPGA_READY); if (load_img_msg) { UHD_LOGGER_INFO("B200") << "Loading FPGA image: " << filestring << "..."; } bytes_to_xfer = 1; ret = fx3_control_write(B200_VREQ_FPGA_START, 0, 0, out_buff, bytes_to_xfer, 1000); if (ret < 0) throw uhd::io_error((boost::format("Failed to start FPGA bitstream (%d: %s)") % ret % libusb_error_name(ret)) .str()); else if (ret != bytes_to_xfer) throw uhd::io_error( (boost::format( "Short write on start FPGA bitstream (expecting: %d, returned: %d)") % bytes_to_xfer % ret) .str()); wait_count = 0; do { fx3_state = get_fx3_status(); if ((wait_count >= 1000) || (fx3_state == FX3_STATE_ERROR) || (fx3_state == FX3_STATE_UNDEFINED)) { return fx3_state; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); wait_count++; } while (fx3_state != FX3_STATE_CONFIGURING_FPGA); size_t bytes_sent = 0; while (!file.eof()) { file.read((char*)out_buff, transfer_size); const std::streamsize n = file.gcount(); if (n == 0) continue; uint16_t transfer_count = uint16_t(n); /* Send the data to the device. */ int nwritten = fx3_control_write( B200_VREQ_FPGA_DATA, 0, 0, out_buff, transfer_count, 5000); if (nwritten < 0) throw uhd::io_error( (boost::format("load_fpga: cannot write bitstream to FX3 (%d: %s)") % nwritten % libusb_error_name(nwritten)) .str()); else if (nwritten != transfer_count) throw uhd::io_error( (boost::format("load_fpga: short write while transferring bitstream " "to FX3 (expecting: %d, returned: %d)") % transfer_count % nwritten) .str()); const size_t LOG_GRANULARITY = 10; // %. Keep this an integer divisor of 100. if (load_img_msg) { if (bytes_sent == 0) UHD_LOGGER_DEBUG("B200") << "FPGA load: 0%" << std::flush; const size_t percent_before = size_t((bytes_sent * 100) / file_size) - (size_t((bytes_sent * 100) / file_size) % LOG_GRANULARITY); bytes_sent += transfer_count; const size_t percent_after = size_t((bytes_sent * 100) / file_size) - (size_t((bytes_sent * 100) / file_size) % LOG_GRANULARITY); if (percent_before != percent_after) { UHD_LOGGER_DEBUG("B200") << "FPGA load: " << std::setw(3) << percent_after << "%"; } } } file.close(); wait_count = 0; do { fx3_state = get_fx3_status(); if ((wait_count >= 500) || (fx3_state == FX3_STATE_ERROR) || (fx3_state == FX3_STATE_UNDEFINED)) { return fx3_state; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); wait_count++; } while (fx3_state != FX3_STATE_RUNNING); usrp_set_fpga_hash(hash); if (load_img_msg) { UHD_LOGGER_DEBUG("B200") << "FPGA image loaded!"; } return 0; } uint32_t load_bootloader(const std::string filestring) override { // open bootloader file const char* filename = filestring.c_str(); const size_t file_size = _get_file_size(filename); if (file_size > BOOTLOADER_MAX_SIZE) { throw uhd::runtime_error( (boost::format("Bootloader img file is too large for EEPROM! (expecting: " "less than %d actual: %d") % BOOTLOADER_MAX_SIZE % file_size) .str()); } std::ifstream file; file.open(filename, std::ios::in | std::ios::binary); if (!file.good()) { throw uhd::io_error("load_bootloader: cannot open bootloader input file."); } // allocate buffer const int transfer_size = _get_transfer_size(); UHD_ASSERT_THROW(transfer_size <= VREQ_MAX_SIZE); std::vector out_buff(transfer_size); // Request loopback read, which will indicate the firmware's current control // request buffer size int nread = fx3_control_read(B200_VREQ_LOOP, 0, 0, out_buff.data(), transfer_size, 1000); if (nread < 0) { throw uhd::io_error((boost::format("load_bootloader: unable to complete " "firmware loopback request (%d: %s)") % nread % libusb_error_name(nread)) .str()); } else if (nread != transfer_size) { throw uhd::io_error( (boost::format("load_bootloader: short read on firmware loopback request " "(expecting: %d, returned: %d)") % transfer_size % nread) .str()); } // ensure FX3 is in non-error state { uint8_t fx3_state = get_fx3_status(); if (fx3_state == FX3_STATE_ERROR or fx3_state == FX3_STATE_UNDEFINED) { return fx3_state; } } UHD_LOGGER_INFO("B200") << "Loading bootloader image: " << filestring << "..."; size_t bytes_sent = 0; while (!file.eof()) { file.read((char*)&out_buff[0], transfer_size); const std::streamsize n = file.gcount(); if (n == 0) continue; uint16_t transfer_count = uint16_t(n); // Send the data to the device int nwritten = fx3_control_write(B200_VREQ_EEPROM_WRITE, 0, bytes_sent, out_buff.data(), transfer_count, 5000); if (nwritten < 0) { throw uhd::io_error( (boost::format( "load_bootloader: cannot write bitstream to FX3 (%d: %s)") % nwritten % libusb_error_name(nwritten)) .str()); } else if (nwritten != transfer_count) { throw uhd::io_error( (boost::format( "load_bootloader: short write while transferring bitstream " "to FX3 (expecting: %d, returned: %d)") % transfer_count % nwritten) .str()); } const size_t LOG_GRANULARITY = 10; // %. Keep this an integer divisor of 100. if (bytes_sent == 0) UHD_LOGGER_DEBUG("B200") << "Bootloader load: 0%" << std::flush; const size_t percent_before = size_t((bytes_sent * 100) / file_size) - (size_t((bytes_sent * 100) / file_size) % LOG_GRANULARITY); bytes_sent += transfer_count; const size_t percent_after = size_t((bytes_sent * 100) / file_size) - (size_t((bytes_sent * 100) / file_size) % LOG_GRANULARITY); if (percent_before != percent_after) { UHD_LOGGER_DEBUG("B200") << "Bootloader load: " << std::setw(3) << percent_after << "%"; } } file.close(); // ensure FX3 is in non-error state { uint8_t fx3_state = get_fx3_status(); if (fx3_state == FX3_STATE_ERROR or fx3_state == FX3_STATE_UNDEFINED) { return fx3_state; } } UHD_LOGGER_DEBUG("B200") << "Bootloader image loaded!"; return 0; } private: usb_control::sptr _usb_ctrl; }; std::string b200_iface::fx3_state_string(uint8_t state) { switch (state) { case FX3_STATE_FPGA_READY: return std::string("Ready"); case FX3_STATE_CONFIGURING_FPGA: return std::string("Configuring FPGA"); case FX3_STATE_BUSY: return std::string("Busy"); case FX3_STATE_RUNNING: return std::string("Running"); case FX3_STATE_UNCONFIGURED: return std::string("Unconfigured"); case FX3_STATE_ERROR: return std::string("Error"); default: break; } return std::string("Unknown"); } /*********************************************************************** * Make an instance of the implementation **********************************************************************/ b200_iface::sptr b200_iface::make(usb_control::sptr usb_ctrl) { return sptr(new b200_iface_impl(usb_ctrl)); }