diff options
| author | Mark Meserve <mark.meserve@ni.com> | 2019-04-11 16:37:24 -0500 | 
|---|---|---|
| committer | Martin Braun <martin.braun@ettus.com> | 2019-04-11 17:10:50 -0700 | 
| commit | 2bdad498e30511d0d3452ba960f5decf591030a1 (patch) | |
| tree | dba73e78979edd10baf135915f9da5c16e4b97d9 /host | |
| parent | f4c64181e386e81829c90c9bd8d3cbc6cf37cee9 (diff) | |
| download | uhd-2bdad498e30511d0d3452ba960f5decf591030a1.tar.gz uhd-2bdad498e30511d0d3452ba960f5decf591030a1.tar.bz2 uhd-2bdad498e30511d0d3452ba960f5decf591030a1.zip  | |
b200: enable usage of custom bootloader
- Update MB EEPROM
- Add bootloader load command to fx3 util
Diffstat (limited to 'host')
| -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 | ||||
| -rw-r--r-- | host/utils/b2xx_fx3_utils.cpp | 134 | 
4 files changed, 403 insertions, 102 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 0e1e0e9ed..4389d4eed 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); +    } +} diff --git a/host/utils/b2xx_fx3_utils.cpp b/host/utils/b2xx_fx3_utils.cpp index e723b904a..c94badc9d 100644 --- a/host/utils/b2xx_fx3_utils.cpp +++ b/host/utils/b2xx_fx3_utils.cpp @@ -1,6 +1,6 @@  //  // Copyright 2010-2014 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  // @@ -32,36 +32,52 @@  namespace po = boost::program_options;  namespace fs = boost::filesystem; +namespace {  struct vid_pid_t  {      uint16_t vid;      uint16_t pid;  }; -const static vid_pid_t known_vid_pids[] = {{FX3_VID, FX3_DEFAULT_PID}, +const vid_pid_t known_vid_pids[] = {{FX3_VID, FX3_DEFAULT_PID},      {FX3_VID, FX3_REENUM_PID},      {B200_VENDOR_ID, B200_PRODUCT_ID},      {B200_VENDOR_ID, B200MINI_PRODUCT_ID},      {B200_VENDOR_ID, B205MINI_PRODUCT_ID},      {B200_VENDOR_NI_ID, B200_PRODUCT_NI_ID},      {B200_VENDOR_NI_ID, B210_PRODUCT_NI_ID}}; -const static std::vector<vid_pid_t> known_vid_pid_vector(known_vid_pids, +const std::vector<vid_pid_t> known_vid_pid_vector(known_vid_pids,      known_vid_pids + (sizeof(known_vid_pids) / sizeof(known_vid_pids[0]))); -static const size_t EEPROM_INIT_VALUE_VECTOR_SIZE = 8; -static uhd::byte_vector_t construct_eeprom_init_value_vector(uint16_t vid, uint16_t pid) +const uhd::byte_vector_t OLD_EEPROM_SIGNATURE = {0x43, 0x59, 0x14, 0xB2}; +const uhd::byte_vector_t NEW_EEPROM_SIGNATURE = {0x43, 0x59, 0x1A, 0xB0}; + +const size_t EEPROM_INIT_VALUE_VECTOR_SIZE = 8; +uhd::byte_vector_t construct_eeprom_init_value_vector(uint16_t vid, uint16_t pid)  { -    uhd::byte_vector_t init_values(EEPROM_INIT_VALUE_VECTOR_SIZE); -    init_values.at(0) = 0x43; -    init_values.at(1) = 0x59; -    init_values.at(2) = 0x14; -    init_values.at(3) = 0xB2; -    init_values.at(4) = static_cast<uint8_t>(pid & 0xff); -    init_values.at(5) = static_cast<uint8_t>(pid >> 8); -    init_values.at(6) = static_cast<uint8_t>(vid & 0xff); -    init_values.at(7) = static_cast<uint8_t>(vid >> 8); +    uhd::byte_vector_t init_values(OLD_EEPROM_SIGNATURE); +    init_values.push_back(static_cast<uint8_t>(pid & 0xff)); +    init_values.push_back(static_cast<uint8_t>(pid >> 8)); +    init_values.push_back(static_cast<uint8_t>(vid & 0xff)); +    init_values.push_back(static_cast<uint8_t>(vid >> 8));      return init_values;  } +constexpr uint8_t EEPROM_DATA_ADDR_HIGH_BYTE = 0x7F; +constexpr uint8_t EEPROM_DATA_HEADER_ADDR    = 0x00; +constexpr uint8_t EEPROM_DATA_VID_PID_ADDR   = 0x06; +constexpr uint8_t EEPROM_DATA_OLD_DATA_ADDR  = 0x0A; + +const uhd::byte_vector_t EEPROM_DATA_HEADER = { +    0x00, +    0xB2, // magic +    0x01, +    0x00, // eeprom_revision +    0x01, +    0x00 // eeprom_compat +}; + +} // namespace +  //! used with lexical cast to parse a hex string  template <class T> struct to_hex  { @@ -300,29 +316,36 @@ int erase_eeprom(b200_iface::sptr& b200)  int32_t main(int32_t argc, char* argv[])  {      uint16_t vid, pid; -    std::string pid_str, vid_str, fw_file, fpga_file, writevid_str, writepid_str; +    std::string pid_str, vid_str, fw_file, fpga_file, bl_file, writevid_str, writepid_str;      bool user_supplied_vid_pid = false; +    // clang-format off      po::options_description visible("Allowed options"); -    visible.add_options()("help,h", "help message")( +    visible.add_options()( +        "help,h", "help message")(          "vid,v", po::value<std::string>(&vid_str), "Specify VID of device to use.")(          "pid,p", po::value<std::string>(&pid_str), "Specify PID of device to use.")(          "speed,S", "Read back the USB mode currently in use.")(          "reset-device,D", "Reset the B2xx Device.")(          "reset-fpga,F", "Reset the FPGA (does not require re-programming.")( -        "reset-usb,U", "Reset the USB subsystem on your host computer.")("load-fw,W", -        po::value<std::string>(&fw_file), -        "Load a firmware (hex) file into the FX3.")("load-fpga,L", -        po::value<std::string>(&fpga_file), -        "Load a FPGA (bin) file into the FPGA."); +        "reset-usb,U", "Reset the USB subsystem on your host computer.")( +        "load-fw,W", po::value<std::string>(&fw_file), +            "Load a firmware (hex) file into the FX3.")( +        "load-fpga,L", po::value<std::string>(&fpga_file), +            "Load a FPGA (bin) file into the FPGA.")( +        "load-bootloader,B", po::value<std::string>(&bl_file), +            "Load a bootloader (img) file into the EEPROM");      // Hidden options provided for testing - use at your own risk!      po::options_description hidden("Hidden options"); -    hidden.add_options()("init-device,I", "Initialize a B2xx device.")("uninit-device", -        "Uninitialize a B2xx device.")("read-eeprom,R", "Read first 8 bytes of EEPROM")( +    hidden.add_options()( +        "init-device,I", "Initialize a B2xx device.")( +        "uninit-device", "Uninitialize a B2xx device.")( +        "read-eeprom,R", "Read first 8 bytes of EEPROM")(          "erase-eeprom,E", "Erase first 8 bytes of EEPROM")(          "write-vid", po::value<std::string>(&writevid_str), "Write VID field of EEPROM")(          "write-pid", po::value<std::string>(&writepid_str), "Write PID field of EEPROM"); +    // clang-format on      po::options_description desc;      desc.add(visible); @@ -578,6 +601,71 @@ int32_t main(int32_t argc, char* argv[])          }          std::cout << "FPGA load complete, releasing USB interface..." << std::endl; +    } else if (vm.count("load-bootloader")) { +        if (bl_file.empty()) +            bl_file = uhd::find_image_path(B200_BL_FILE_NAME); + +        if (bl_file.empty()) { +            std::cerr << "Bootloader image not found!" << std::endl; +            return -1; +        } + +        if (!(fs::exists(bl_file))) { +            std::cerr << "Invalid filepath: " << bl_file << std::endl; +            return -1; +        } + +        std::cout << "Loading Bootloader image (" << bl_file << ")" << std::endl; + +        // In the upgrade case, we need to migrate the EEPROM data to a new +        // location before loading the bootloader + +        // Use the signature to detect the old EEPROM layout +        auto signature = b200->read_eeprom(0x0, 0x0, 4); +        if (signature == OLD_EEPROM_SIGNATURE) { +            std::cout << "Old EEPROM detected. Upgrading EEPROM image to latest revision." +                      << std::endl; + +            // Read values that will be clobbered by the bootloader +            auto pidvid      = b200->read_eeprom(0x00, 0x04, 4); +            uhd::byte_vector_t vidpid = {pidvid[2], pidvid[3], pidvid[0], pidvid[1]}; +            auto eeprom_data = b200->read_eeprom(0x04, 0xDC, 36); + +            // Write in default header +            b200->write_eeprom( +                EEPROM_DATA_ADDR_HIGH_BYTE, EEPROM_DATA_HEADER_ADDR, EEPROM_DATA_HEADER); +            // Write back data to the device +            b200->write_eeprom( +                EEPROM_DATA_ADDR_HIGH_BYTE, EEPROM_DATA_VID_PID_ADDR, vidpid); +            b200->write_eeprom( +                EEPROM_DATA_ADDR_HIGH_BYTE, EEPROM_DATA_OLD_DATA_ADDR, eeprom_data); +        } + +        uint32_t fx3_state; +        try { +            fx3_state = b200->load_bootloader(bl_file); +        } // returns 0 on success, or FX3 state on error +        catch (uhd::exception& e) { +            std::cerr << "Exception while loading bootloader: " << e.what() << std::endl; +            return EXIT_FAILURE; +        } + +        if (fx3_state != 0) { +            std::cerr << std::flush << "Error loading bootloader. FX3 state (" +                      << fx3_state << "): " << b200_iface::fx3_state_string(fx3_state) +                      << std::endl; +            return EXIT_FAILURE; +        } + +        std::cout << "Bootloader load complete, resetting device..." << std::endl; + +        // reset the device +        try { +            b200->reset_fx3(); +        } catch (uhd::exception& e) { +            std::cerr << "Exception while resetting FX3: " << e.what() << std::endl; +            return EXIT_FAILURE; +        }      }      std::cout << "Operation complete!  I did it!  I did it!" << std::endl;  | 
