diff options
author | Mark Meserve <mark.meserve@ni.com> | 2019-04-11 16:37:24 -0500 |
---|---|---|
committer | michael-west <michael.west@ettus.com> | 2019-05-21 16:11:35 -0700 |
commit | 45e2f0e116d2b14532e8e06304e2489b1ab0d9cf (patch) | |
tree | 9bab2f1e0632018ebaf958ff8bd749f7ae326981 /host/lib | |
parent | 4f57ecab13e37f132c99ec797d412def3f1e2a66 (diff) | |
download | uhd-45e2f0e116d2b14532e8e06304e2489b1ab0d9cf.tar.gz uhd-45e2f0e116d2b14532e8e06304e2489b1ab0d9cf.tar.bz2 uhd-45e2f0e116d2b14532e8e06304e2489b1ab0d9cf.zip |
b200: enable usage of custom bootloader
- Update MB EEPROM
- Add bootloader load command to fx3 util
Diffstat (limited to 'host/lib')
-rw-r--r-- | host/lib/usrp/b200/b200_iface.cpp | 170 | ||||
-rw-r--r-- | host/lib/usrp/b200/b200_iface.hpp | 6 | ||||
-rw-r--r-- | host/lib/usrp/b200/b200_mb_eeprom.cpp | 195 |
3 files changed, 292 insertions, 79 deletions
diff --git a/host/lib/usrp/b200/b200_iface.cpp b/host/lib/usrp/b200/b200_iface.cpp index 432201429..082be071c 100644 --- a/host/lib/usrp/b200/b200_iface.cpp +++ b/host/lib/usrp/b200/b200_iface.cpp @@ -1,6 +1,6 @@ // // Copyright 2012-2013 Ettus Research LLC -// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2018-2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // @@ -15,6 +15,7 @@ #include <boost/functional/hash.hpp> #include <boost/lexical_cast.hpp> #include <boost/format.hpp> +#include <boost/filesystem.hpp> #include <libusb.h> #include <fstream> #include <string> @@ -37,6 +38,10 @@ 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 @@ -365,6 +370,29 @@ public: 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<size_t>(filesize); + } + uint32_t load_fpga(const std::string filestring, bool force) { uint8_t fx3_state = 0; @@ -378,33 +406,20 @@ public: hash_type loaded_hash; usrp_get_fpga_hash(loaded_hash); 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; - int current_usb_speed = get_usb_speed(); - if (current_usb_speed == 3) - transfer_size = VREQ_MAX_SIZE_USB3; - else if (current_usb_speed != 2) - throw uhd::io_error("load_fpga: get_usb_speed returned invalid USB speed (not 2 or 3)."); + 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 - // Make sure that if operating as USB2, requested length is within spec - int ntoread = std::min(transfer_size, (int)sizeof(out_buff)); - int nread = fx3_control_read(B200_VREQ_LOOP, 0, 0, out_buff, ntoread, 1000); + 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 != ntoread) - throw uhd::io_error((boost::format("load_fpga: short read on firmware loopback request (expecting: %d, returned: %d)") % ntoread % nread).str()); - transfer_size = std::min(transfer_size, nread); // Select the smaller value + 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()); - size_t file_size = 0; - { - std::ifstream file(filename, std::ios::in | std::ios::binary | std::ios::ate); - file_size = size_t(file.tellg()); - } + const size_t file_size = _get_file_size(filename); std::ifstream file; file.open(filename, std::ios::in | std::ios::binary); @@ -481,7 +496,7 @@ public: 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") << " 0%" << std::flush; + 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); @@ -491,7 +506,8 @@ public: (size_t((bytes_sent*100)/file_size) % LOG_GRANULARITY); if (percent_before != percent_after) { - UHD_LOGGER_DEBUG("B200") << std::setw(3) << percent_after << "%"; + UHD_LOGGER_DEBUG("B200") + << "FPGA load: " << std::setw(3) << percent_after << "%"; } } } @@ -520,6 +536,118 @@ public: return 0; } + uint32_t load_bootloader(const std::string filestring) + { + // 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<uint8_t> 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; }; diff --git a/host/lib/usrp/b200/b200_iface.hpp b/host/lib/usrp/b200/b200_iface.hpp index 86307bc21..a61e03075 100644 --- a/host/lib/usrp/b200/b200_iface.hpp +++ b/host/lib/usrp/b200/b200_iface.hpp @@ -1,6 +1,6 @@ // // Copyright 2012-2013 Ettus Research LLC -// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2018-2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // @@ -45,6 +45,7 @@ static const uhd::dict<uint16_t, b200_product_t> B2XX_PID_TO_PRODUCT = boost::as ; static const std::string B200_FW_FILE_NAME = "usrp_b200_fw.hex"; +static const std::string B200_BL_FILE_NAME = "usrp_b200_bl.img"; //! Map the EEPROM product ID codes to the product static const uhd::dict<uint16_t, b200_product_t> B2XX_PRODUCT_ID = boost::assign::map_list_of @@ -111,6 +112,9 @@ public: //! load an FPGA image virtual uint32_t load_fpga(const std::string filestring, bool force=false) = 0; + //! load a bootloader image onto device EEPROM + virtual uint32_t load_bootloader(const std::string filestring) = 0; + virtual void write_eeprom(uint16_t addr, uint16_t offset, const uhd::byte_vector_t &bytes) = 0; virtual uhd::byte_vector_t read_eeprom(uint16_t addr, uint16_t offset, size_t num_bytes) = 0; diff --git a/host/lib/usrp/b200/b200_mb_eeprom.cpp b/host/lib/usrp/b200/b200_mb_eeprom.cpp index 2be014fd5..5a37cc9c1 100644 --- a/host/lib/usrp/b200/b200_mb_eeprom.cpp +++ b/host/lib/usrp/b200/b200_mb_eeprom.cpp @@ -1,82 +1,163 @@ // -// Copyright 2017 Ettus Research (National Instruments Corp.) +// Copyright 2017-2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // #include "b200_impl.hpp" -#include <uhdlib/utils/eeprom_utils.hpp> #include <uhd/usrp/mboard_eeprom.hpp> +#include <uhdlib/utils/eeprom_utils.hpp> -namespace { - /* On the B200, this field indicates the slave address. From the FX3, this - * address is always 0. */ - static const uint8_t B200_EEPROM_SLAVE_ADDR = 0x04; - - //use char array so we dont need to attribute packed - struct b200_eeprom_map{ - unsigned char _r[220]; - unsigned char revision[2]; - unsigned char product[2]; - unsigned char name[NAME_MAX_LEN]; - unsigned char serial[SERIAL_LEN]; - }; -} +#include <unordered_map> using namespace uhd; using uhd::usrp::mboard_eeprom_t; +namespace { + +constexpr auto LOG_ID = "B2xx_EEPROM"; + +struct eeprom_field_t +{ + size_t offset; + size_t length; +}; + +// EEPROM map information is duplicated in common_const.h for the +// firmware and bootloader code. +// EEPROM map information is duplicated in b2xx_fx3_utils.cpp + +constexpr uint16_t SIGNATURE_ADDR = 0x0000; +constexpr size_t SIGNATURE_LENGTH = 4; + +constexpr auto EXPECTED_MAGIC = "45568"; // 0xB200 +constexpr auto EXPECTED_COMPAT = "1"; + +constexpr uint32_t REV1_SIGNATURE = 0xB01A5943; +constexpr uint16_t REV1_BASE_ADDR = 0x7F00; +constexpr size_t REV1_LENGTH = 46; + +const std::unordered_map<std::string, eeprom_field_t> B200_REV1_MAP = { + {"magic", {0, 2}}, + {"eeprom_revision", {2, 2}}, + {"eeprom_compat", {4, 2}}, + {"vendor_id", {6, 2}}, + {"product_id", {8, 2}}, + {"revision", {10, 2}}, + {"product", {12, 2}}, + {"name", {14, NAME_MAX_LEN}}, + {"serial", {14 + NAME_MAX_LEN, SERIAL_LEN}}, + // pad of 210 bytes +}; + +constexpr uint32_t REV0_SIGNATURE = 0xB2145943; +constexpr uint16_t REV0_BASE_ADDR = 0x04DC; +constexpr size_t REV0_LENGTH = 36; + +const std::unordered_map<std::string, eeprom_field_t> B200_REV0_MAP = { + // front pad of 220 bytes + {"revision", {0, 2}}, + {"product", {2, 2}}, + {"name", {4, NAME_MAX_LEN}}, + {"serial", {4 + NAME_MAX_LEN, SERIAL_LEN}}, +}; + +constexpr int UNKNOWN_REV = -1; + +int _get_rev(uhd::i2c_iface::sptr iface) +{ + auto bytes = + iface->read_eeprom(SIGNATURE_ADDR >> 8, SIGNATURE_ADDR & 0xFF, SIGNATURE_LENGTH); + uint32_t signature = bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0]; + if (signature == REV0_SIGNATURE) { + return 0; + } else if (signature == REV1_SIGNATURE) { + return 1; + } else { + UHD_LOG_WARNING(LOG_ID, "Unknown signature! 0x" << std::hex << signature); + return UNKNOWN_REV; + } +} + +byte_vector_t _get_eeprom(uhd::i2c_iface::sptr iface) +{ + const auto rev = _get_rev(iface); + if (rev == UNKNOWN_REV) + { + return byte_vector_t(); + } + + const uint16_t addr = (rev == 0) ? REV0_BASE_ADDR : REV1_BASE_ADDR; + const size_t length = (rev == 0) ? REV0_LENGTH : REV1_LENGTH; + + return iface->read_eeprom(addr >> 8, addr & 0xFF, length); +} + +} + mboard_eeprom_t b200_impl::get_mb_eeprom(uhd::i2c_iface::sptr iface) { + auto rev = _get_rev(iface); + auto bytes = _get_eeprom(iface); mboard_eeprom_t mb_eeprom; + if (rev == UNKNOWN_REV or bytes.empty()) { + return mb_eeprom; + } - //extract the revision number - mb_eeprom["revision"] = uint16_bytes_to_string( - iface->read_eeprom(B200_EEPROM_SLAVE_ADDR, offsetof(b200_eeprom_map, revision), 2) - ); + auto eeprom_map = (rev == 0) ? B200_REV0_MAP : B200_REV1_MAP; - //extract the product code - mb_eeprom["product"] = uint16_bytes_to_string( - iface->read_eeprom(B200_EEPROM_SLAVE_ADDR, offsetof(b200_eeprom_map, product), 2) - ); + for (const auto &element : eeprom_map) + { + // There is an assumption here that fields of length 2 are uint16's and + // lengths other than 2 are strings. Update this code if that + // assumption changes. + byte_vector_t element_bytes = byte_vector_t(bytes.begin() + element.second.offset, + bytes.begin() + element.second.offset + element.second.length); - //extract the serial - mb_eeprom["serial"] = bytes_to_string(iface->read_eeprom( - B200_EEPROM_SLAVE_ADDR, offsetof(b200_eeprom_map, serial), SERIAL_LEN - )); + mb_eeprom[element.first] = (element.second.length == 2) + ? uint16_bytes_to_string(element_bytes) + : bytes_to_string(element_bytes); + } - //extract the name - mb_eeprom["name"] = bytes_to_string(iface->read_eeprom( - B200_EEPROM_SLAVE_ADDR, offsetof(b200_eeprom_map, name), NAME_MAX_LEN - )); + if (rev > 0) { + if (mb_eeprom["magic"] != EXPECTED_MAGIC) + { + throw uhd::runtime_error( + str(boost::format("EEPROM magic value mismatch. Device returns %s, but " + "should have been %s.") + % mb_eeprom["magic"] % EXPECTED_MAGIC)); + } + if (mb_eeprom["eeprom_compat"] != EXPECTED_COMPAT) { + throw uhd::runtime_error( + str(boost::format("EEPROM compat value mismatch. Device returns %s, but " + "should have been %s.") + % mb_eeprom["eeprom_compat"] % EXPECTED_COMPAT)); + } + } return mb_eeprom; } -void b200_impl::set_mb_eeprom(const mboard_eeprom_t &mb_eeprom) +void b200_impl::set_mb_eeprom(const mboard_eeprom_t& mb_eeprom) { - //parse the revision number - if (mb_eeprom.has_key("revision")) _iface->write_eeprom( - B200_EEPROM_SLAVE_ADDR, offsetof(b200_eeprom_map, revision), - string_to_uint16_bytes(mb_eeprom["revision"]) - ); - - //parse the product code - if (mb_eeprom.has_key("product")) _iface->write_eeprom( - B200_EEPROM_SLAVE_ADDR, offsetof(b200_eeprom_map, product), - string_to_uint16_bytes(mb_eeprom["product"]) - ); - - //store the serial - if (mb_eeprom.has_key("serial")) _iface->write_eeprom( - B200_EEPROM_SLAVE_ADDR, offsetof(b200_eeprom_map, serial), - string_to_bytes(mb_eeprom["serial"], SERIAL_LEN) - ); - - //store the name - if (mb_eeprom.has_key("name")) _iface->write_eeprom( - B200_EEPROM_SLAVE_ADDR, offsetof(b200_eeprom_map, name), - string_to_bytes(mb_eeprom["name"], NAME_MAX_LEN) - ); -} + const auto rev = _get_rev(_iface); + auto eeprom_map = (rev == 0) ? B200_REV0_MAP : B200_REV1_MAP; + auto base_addr = (rev == 0) ? REV0_BASE_ADDR : REV1_BASE_ADDR; + for (const auto& key : mb_eeprom.keys()) + { + if (eeprom_map.find(key) == eeprom_map.end()) + { + UHD_LOG_WARNING( + LOG_ID, "Unknown key in mb_eeprom during set_mb_eeprom: " << key); + continue; + } + // There is an assumption here that fields of length 2 are uint16's and + // lengths other than 2 are strings. Update this code if that + // assumption changes. + auto field = eeprom_map.at(key); + auto bytes = (field.length == 2) ? string_to_uint16_bytes(mb_eeprom[key]) + : string_to_bytes(mb_eeprom[key], field.length); + _iface->write_eeprom(base_addr >> 8, (base_addr & 0xFF) + field.offset, bytes); + } +} |