aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp/mpmd/mpmd_image_loader.cpp
blob: ba0f45fadd9d0ec6104d93eb2f282603f81087d5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
//
// Copyright 2017 Ettus Research, a National Instruments Company
// Copyright 2019 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//

#include "mpmd_impl.hpp"
#include <uhd/config.hpp>
#include <uhd/device.hpp>
#include <uhd/exception.hpp>
#include <uhd/image_loader.hpp>
#include <uhd/rfnoc/radio_control.hpp>
#include <uhd/rfnoc_graph.hpp>
#include <uhd/types/component_file.hpp>
#include <uhd/types/eeprom.hpp>
#include <uhd/utils/paths.hpp>
#include <uhd/utils/static.hpp>
#include <uhdlib/features/fpga_load_notification_iface.hpp>
#include <uhdlib/utils/prefs.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/filesystem/convenience.hpp>
#include <boost/optional.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <cctype>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>

using namespace uhd;

namespace uhd { namespace /*anon*/ {
const size_t MD5LEN = 32; // Length of a MD5 hash in chars

/*
 * Helper function to generate a component_file_t using the input ID and path to file.
 */
uhd::usrp::component_file_t generate_component(
    const std::string& id, const std::string& filepath)
{
    uhd::usrp::component_file_t component_file;
    // Add an ID to the metadata
    component_file.metadata["id"] = id;
    UHD_LOG_TRACE(
        "MPMD IMAGE LOADER", "Component ID added to the component dictionary: " << id);
    // Add the filename to the metadata
    // Remove the path to the filename
    component_file.metadata["filename"] =
        boost::filesystem::path(filepath).filename().string();
    UHD_LOG_TRACE("MPMD IMAGE LOADER",
        "Component filename added to the component dictionary: " << filepath);
    // Add the hash, if a hash file exists
    const std::string component_hash_filepath = filepath + ".md5";
    std::ifstream component_hash_ifstream(
        component_hash_filepath.c_str(), std::ios::binary);
    std::string component_hash;
    if (component_hash_ifstream.is_open()) {
        // TODO: Verify that the hash read is valid, ie only contains 0-9a-f.
        component_hash.resize(MD5LEN);
        component_hash_ifstream.read(&component_hash[0], MD5LEN);
        component_hash_ifstream.close();
        component_file.metadata["md5"] = component_hash;
        UHD_LOG_TRACE("MPMD IMAGE LOADER",
            "Added component file hash to the component dictionary.");
    } else {
        // If there is no hash file, don't worry about it too much
        UHD_LOG_DEBUG("MPMD IMAGE LOADER",
            "Could not open component file hash file: " << component_hash_filepath);
    }

    // Read the component file image into a structure suitable to sent as a binary string
    // to MPM
    std::vector<uint8_t> data;
    std::ifstream component_ifstream(filepath.c_str(), std::ios::binary);
    if (component_ifstream.is_open()) {
        data.insert(data.begin(),
            std::istreambuf_iterator<char>(component_ifstream),
            std::istreambuf_iterator<char>());
        component_ifstream.close();
    } else {
        const std::string err_msg("Component file does not exist: " + filepath);
        throw uhd::runtime_error(err_msg);
    }
    component_file.data = data;
    return component_file;
}

/*
 * Helper function to generate a component_file_t using the input ID and contents.
 */
uhd::usrp::component_file_t generate_component(const std::string& id,
    const std::vector<uint8_t>& contents,
    const uhd::dict<std::string, std::string>& metadata)
{
    uhd::usrp::component_file_t component_file;
    // Add an ID to the metadata
    component_file.metadata["id"] = id;
    component_file.metadata.update(metadata);
    UHD_LOG_TRACE(
        "MPMD IMAGE LOADER", "Component ID added to the component dictionary: " << id);
    component_file.data = contents;
    return component_file;
}

boost::optional<std::vector<uint8_t>> parse_dts_from_lvbitx(
    const boost::property_tree::ptree& pt)
{
    std::string dts;
    try {
        dts = pt.get<std::string>("Bitfile.Project.CompilationResultsTree."
                                  "CompilationResults.deviceTreeOverlay");
    } catch (boost::property_tree::ptree_error&) {
        UHD_LOG_WARNING(
            "MPMD IMAGE LOADER", "Could not find DTS in .lvbitx file, not including it");
        return boost::none;
    }

    if (dts.size() % 2 != 0) {
        throw uhd::runtime_error(
            "The deviceTreeOverlay is corrupt in the specified .lvbitx file");
    }

    std::vector<uint8_t> text;
    for (size_t i = 0; i < dts.size() / 2; i++) {
        const char s[3]     = {dts[i * 2], dts[i * 2 + 1], '\0'};
        const uint8_t value = static_cast<uint8_t>(strtoul(s, nullptr, 16));
        text.push_back(value);
    }

    return text;
}

std::vector<uint8_t> parse_bitstream_from_lvbitx(const boost::property_tree::ptree& pt)
{
    std::string encoded_bitstream = pt.get<std::string>("Bitfile.Bitstream");

    // Strip the whitespace
    encoded_bitstream.erase(std::remove_if(encoded_bitstream.begin(),
                                encoded_bitstream.end(),
                                [](char c) { return std::isspace(c); }),
        encoded_bitstream.end());

    // Base64-decode the result
    namespace bai    = boost::archive::iterators;
    using base64_dec = bai::transform_width<bai::binary_from_base64<char*>, 8, 6>;

    std::vector<uint8_t> bitstream(base64_dec(encoded_bitstream.data()),
        base64_dec(encoded_bitstream.data() + encoded_bitstream.length()));

    // Remove null bytes that were formed from the padding
    const size_t pad_count =
        std::count(encoded_bitstream.begin(), encoded_bitstream.end(), '=');
    bitstream.erase(bitstream.end() - pad_count, bitstream.end());

    return bitstream;
}

static std::string get_fpga_path(
    const image_loader::image_loader_args_t& image_loader_args,
    device_addr_t dev_addr,
    uhd::property_tree::sptr tree)
{
    // If the user provided a path to an fpga image, use that
    if (not image_loader_args.fpga_path.empty()) {
        if (boost::filesystem::exists(image_loader_args.fpga_path)) {
            return image_loader_args.fpga_path;
        } else {
            throw uhd::runtime_error(
                "FPGA file provided does not exist: " + image_loader_args.fpga_path);
        }
    }
    // Otherwise, we need to generate one
    else {
        /*
         * The user can specify an FPGA type (HG, XG, AA), 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.
         */
        const auto fpga_type = [image_loader_args, tree]() -> std::string {
            // If the user didn't provide a type, use the type of currently
            // loaded image on the device
            if (image_loader_args.args.has_key("fpga")) {
                return image_loader_args.args.get("fpga");
            } else if (tree->exists("/mboards/0/components/fpga")) {
                // Pull the FPGA info from the property tree
                // The getter should return a vector of a single
                // component_file_t, so grab the metadata from that
                auto fpga_metadata = tree->access<uhd::usrp::component_files_t>(
                                             "/mboards/0/components/fpga")
                                         .get()[0]
                                         .metadata;
                return fpga_metadata.get("type", "");
            }
            return "";
        }(); // generate_fpga_type lambda function
        UHD_LOG_TRACE("MPMD IMAGE LOADER", "FPGA type: " << fpga_type);

        if (!dev_addr.has_key("product")) {
            throw uhd::runtime_error("Found a device but could not "
                                     "auto-generate an image filename.");
        } else if (fpga_type.empty()) {
            return find_image_path("usrp_"
                                   + boost::algorithm::to_lower_copy(dev_addr["product"])
                                   + "_fpga.bit");
        } else {
            return find_image_path("usrp_"
                                   + boost::algorithm::to_lower_copy(dev_addr["product"])
                                   + "_fpga_" + fpga_type + ".bit");
        }
    }
}

static uhd::usrp::component_files_t lvbitx_to_component_files(
    std::string fpga_path, bool delay_reload)
{
    uhd::usrp::component_files_t all_component_files;

    boost::property_tree::ptree pt;
    try {
        boost::property_tree::xml_parser::read_xml(fpga_path, pt);
    } catch (const boost::property_tree::xml_parser::xml_parser_error& ex) {
        throw uhd::runtime_error(
            std::string("Got error parsing lvbitx file: ") + ex.what());
    }

    const auto bitstream = parse_bitstream_from_lvbitx(pt);

    uhd::dict<std::string, std::string> fpga_metadata;
    fpga_metadata.set("filename", "usrp_x410_fpga_LV.bin");
    fpga_metadata.set("reset", delay_reload ? "false" : "true");

    uhd::usrp::component_file_t comp_fpga =
        generate_component("fpga", bitstream, fpga_metadata);
    all_component_files.push_back(comp_fpga);

    const auto maybe_dts = parse_dts_from_lvbitx(pt);
    if (maybe_dts) {
        const auto dts = maybe_dts.get();

        uhd::dict<std::string, std::string> dts_metadata;
        dts_metadata.set("filename", "usrp_x410_fpga_LV.dts");
        dts_metadata.set("reset", delay_reload ? "false" : "true");

        uhd::usrp::component_file_t comp_dts =
            generate_component("dts", dts, dts_metadata);
        all_component_files.push_back(comp_dts);
    }

    return all_component_files;
}

static uhd::usrp::component_files_t bin_dts_to_component_files(
    std::string fpga_path, bool delay_reload)
{
    uhd::usrp::component_files_t all_component_files;

    uhd::usrp::component_file_t comp_fpga = generate_component("fpga", fpga_path);

    // If we want to delay the image reloading, explicitly turn off the
    // component reset flag
    if (delay_reload) {
        comp_fpga.metadata.set("reset", "false");
    }
    all_component_files.push_back(comp_fpga);
    // DTS component struct
    // First, we need to determine the name
    const std::string base_name =
        boost::filesystem::path(fpga_path).replace_extension("").string();
    if (base_name == fpga_path) {
        const std::string err_msg(
            "Can't cut extension from FPGA filename... " + fpga_path);
        throw uhd::runtime_error(err_msg);
    }
    UHD_LOG_TRACE("MPMD IMAGE LOADER", "base_name = " << base_name);
    const std::string dts_path = base_name + ".dts";
    // Then try to generate it
    try {
        uhd::usrp::component_file_t comp_dts = generate_component("dts", dts_path);
        // If we want to delay the image reloading, explicitly turn off the
        // component reset flag
        if (delay_reload) {
            comp_dts.metadata.set("reset", "false");
        }
        all_component_files.push_back(comp_dts);
        UHD_LOG_TRACE("MPMD IMAGE LOADER", "FPGA and DTS images read from file.");
    } catch (const uhd::runtime_error& ex) {
        // If we can't find the DTS file, that's fine, continue without it
        UHD_LOG_WARNING("MPMD IMAGE LOADER", ex.what());
        UHD_LOG_TRACE("MPMD IMAGE LOADER", "FPGA images read from file.");
    }

    return all_component_files;
}

static void mpmd_send_fpga_to_device(
    const image_loader::image_loader_args_t& image_loader_args, device_addr_t dev_addr)
{
    // Skip initializing the device
    dev_addr["skip_init"] = "1";

    // Make the device
    uhd::device::sptr usrp        = uhd::device::make(dev_addr, uhd::device::USRP);
    uhd::property_tree::sptr tree = usrp->get_tree();

    // Generate the component files
    uhd::usrp::component_files_t all_component_files;

    // Determine if we need to just reload the currently-loaded components.
    // Typically called after doing delayed reload.
    if (image_loader_args.just_reload) {
        UHD_LOG_TRACE("MPMD IMAGE LOADER",
            "Just reloading components. Creating stub components for reset operation.");
        uhd::usrp::component_file_t comp_fpga_stub;
        comp_fpga_stub.metadata["id"] = "fpga";
        // Set the "just_reload" field to force a MPM reset leading to
        // component reload.
        comp_fpga_stub.metadata["just_reload"] = "true";

        all_component_files.push_back(comp_fpga_stub);
    } else if (not image_loader_args.id.empty()
               && not image_loader_args.component.empty()) {
        uhd::usrp::component_file_t component = generate_component(image_loader_args.id,
            image_loader_args.component,
            image_loader_args.metadata);
        all_component_files.push_back(component);
    } else {
        // Determine if we need to delay the reload of fpga/dts components.
        const bool delay_reload = image_loader_args.delay_reload;
        UHD_LOG_TRACE(
            "MPMD IMAGE LOADER", "Delay reload?: " << (delay_reload ? "Yes" : "No"));

        // FPGA component struct
        const auto fpga_path = get_fpga_path(image_loader_args, dev_addr, tree);
        UHD_LOG_TRACE("MPMD IMAGE LOADER", "FPGA path: " << fpga_path);

        // If the fpga_path is a lvbitx file, parse it as such
        if (boost::filesystem::path(fpga_path).extension() == ".lvbitx") {
            all_component_files = lvbitx_to_component_files(fpga_path, delay_reload);
        } else {
            all_component_files = bin_dts_to_component_files(fpga_path, delay_reload);
        }
    }

    // Call RPC to update the component
    UHD_LOG_INFO("MPMD IMAGE LOADER", "Starting update. This may take a while.");
    tree->access<uhd::usrp::component_files_t>("/mboards/0/components/fpga")
        .set(all_component_files);
    UHD_LOG_INFO("MPMD IMAGE LOADER", "Update component function succeeded.");
}

/*
 * Function to be registered with uhd_image_loader
 */
static bool mpmd_image_loader(const image_loader::image_loader_args_t& image_loader_args)
{
    // See if any MPM devices with the given args are found
    device_addr_t find_hint = prefs::get_usrp_args(image_loader_args.args);
    find_hint.set("find_all", "1"); // We need to find all devices
    device_addrs_t devs = mpmd_find(find_hint);

    if (devs.size() != 1) {
        UHD_LOG_ERROR(
            "MPMD IMAGE LOADER", "mpmd_image_loader only supports a single device.");
        return false;
    }
    // Grab the first device_addr
    device_addr_t dev_addr(devs[0]);

    mpmd_send_fpga_to_device(image_loader_args, dev_addr);

    {
        // All MPM devices use RFNoC
        auto graph = rfnoc::rfnoc_graph::make(find_hint);
        for (size_t mb_index = 0; mb_index < graph->get_num_mboards(); mb_index++) {
            auto mboard = graph->get_mb_controller(mb_index);
            if (mboard->has_feature<uhd::features::fpga_load_notification_iface>()) {
                auto& notifier =
                    mboard->get_feature<uhd::features::fpga_load_notification_iface>();
                notifier.onload();
            }
        }
    }

    return true;
}

}} // namespace uhd

UHD_STATIC_BLOCK(register_mpm_image_loader)
{
    // TODO: Update recovery instructions
    const std::string recovery_instructions =
        "Aborting. Your USRP MPM-enabled device's update may or may not have\n"
        "completed. The contents of the image files may have been corrupted.\n"
        "Please verify those files as soon as possible.";

    // TODO: 'n3xx' doesn't really fit the MPM abstraction, but this is simpler for the
    // time being
    image_loader::register_image_loader("n3xx", mpmd_image_loader, recovery_instructions);
    image_loader::register_image_loader("e3xx", mpmd_image_loader, recovery_instructions);
    image_loader::register_image_loader("x4xx", mpmd_image_loader, recovery_instructions);
}