aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp
diff options
context:
space:
mode:
authorMark Meserve <mark.meserve@ni.com>2019-04-11 16:37:24 -0500
committermichael-west <michael.west@ettus.com>2019-05-21 16:11:35 -0700
commit45e2f0e116d2b14532e8e06304e2489b1ab0d9cf (patch)
tree9bab2f1e0632018ebaf958ff8bd749f7ae326981 /host/lib/usrp
parent4f57ecab13e37f132c99ec797d412def3f1e2a66 (diff)
downloaduhd-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/usrp')
-rw-r--r--host/lib/usrp/b200/b200_iface.cpp170
-rw-r--r--host/lib/usrp/b200/b200_iface.hpp6
-rw-r--r--host/lib/usrp/b200/b200_mb_eeprom.cpp195
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);
+ }
+}