text: /**
   * Copyright (C) 2020 Xilinx, Inc
   *
   * Licensed under the Apache License, Version 2.0 (the "License"). You may
   * not use this file except in compliance with the License. A copy of the
   * License is located at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   * License for the specific language governing permissions and limitations
   * under the License.
   */
  
  // ------ I N C L U D E   F I L E S -------------------------------------------
  // Local - Include Files
  #include "SubCmdProgram.h"
  #include "tools/common/XBUtilities.h"
  #include "tools/common/XBHelpMenus.h"
  #include "tools/common/ProgressBar.h"
  namespace XBU = XBUtilities;
  
  #include "xrt.h"
  #include "core/common/system.h"
  #include "core/common/device.h"
  #include "core/common/error.h"
  #include "core/common/query_requests.h"
  #include "core/common/message.h"
  #include "core/common/utils.h"
  #include "flash/flasher.h"
  
  // 3rd Party Library - Include Files
  #include <boost/format.hpp>
  #include <boost/tokenizer.hpp>
  #include <boost/filesystem.hpp>
  #include <boost/program_options.hpp>
  #include <boost/algorithm/string.hpp>
  namespace po = boost::program_options;
  
  // ---- Reports ------
  #include "tools/common/Report.h"
  #include "tools/common/ReportHost.h"
  #include "ReportPlatform.h"
  
  // System - Include Files
  #include <iostream>
  #include <fstream>
  #include <thread>
  #include <chrono>
  #include <ctime>
  #include <locale>
  #include <fcntl.h>
  
  #ifdef _WIN32
  #pragma warning(disable : 4996) //std::asctime
  #endif
  
  
  // =============================================================================
  
  // ------ L O C A L   F U N C T I O N S ---------------------------------------
  
  namespace {
  /*
   * Update shell on the board for auto flash
   */
  static void 
  update_shell(unsigned int index, const std::string& primary, const std::string& secondary)
  {
    Flasher flasher(index);
    if(!flasher.isValid())
      throw xrt_core::error(boost::str(boost::format("%d is an invalid index") % index));
  
    if (primary.empty())
      throw xrt_core::error("Shell not specified");
  
    auto pri = std::make_unique<firmwareImage>(primary.c_str(), MCS_FIRMWARE_PRIMARY);
    if (pri->fail())
      throw xrt_core::error(boost::str(boost::format("Failed to read %s") % primary));
  
    std::unique_ptr<firmwareImage> sec;
    if (!secondary.empty()) {
      sec = std::make_unique<firmwareImage>(secondary.c_str(),
        MCS_FIRMWARE_SECONDARY);
      if (sec->fail())
        sec = nullptr;
    }
    
    if (flasher.upgradeFirmware("", pri.get(), sec.get()) != 0)
      throw xrt_core::error("Failed to update shell");
    
    std::cout << boost::format("%-8s : %s \n") % "INFO" % "Shell is updated successfully.";
  }
  
  /*
   * Update shell on the board for manual flash
   */
  static void 
  update_shell(unsigned int index, const std::string& flashType,
    const std::string& primary, const std::string& secondary)
  {
    if (!flashType.empty()) {
        xrt_core::message::send(xrt_core::message::severity_level::warning, "XRT", 
          "Overriding flash mode is not recommended.\nYou may damage your card with this option.");
    }
  
    Flasher flasher(index);
    if(!flasher.isValid())
      throw xrt_core::error(boost::str(boost::format("%d is an invalid index") % index));
  
    if (primary.empty())
      throw xrt_core::error("Shell not specified");
  
    auto pri = std::make_unique<firmwareImage>(primary.c_str(), MCS_FIRMWARE_PRIMARY);
    if (pri->fail())
      throw xrt_core::error(boost::str(boost::format("Failed to read %s") % primary));
  
    std::unique_ptr<firmwareImage> sec;
    if (!secondary.empty()) {
      sec = std::make_unique<firmwareImage>(secondary.c_str(), MCS_FIRMWARE_SECONDARY);
      if (sec->fail())
        throw xrt_core::error(boost::str(boost::format("Failed to read %s") % secondary));
    }
  
    if (flasher.upgradeFirmware(flashType, pri.get(), sec.get()) != 0)
      throw xrt_core::error("Failed to update shell");
    
    std::cout << boost::format("%-8s : %s \n") % "INFO" % "Shell is updated successfully.";
    std::cout << "****************************************************\n";
    std::cout << "Cold reboot machine to load the new image on card.\n";
    std::cout << "****************************************************\n";
  }
  
  /*
   * Update SC firmware on the board
   */
  static void 
  update_SC(unsigned int  index, const std::string& file)
  {
    Flasher flasher(index);
    if(!flasher.isValid())
      throw xrt_core::error(boost::str(boost::format("%d is an invalid index") % index));
  
    std::unique_ptr<firmwareImage> bmc =
      std::make_unique<firmwareImage>(file.c_str(), BMC_FIRMWARE);
    if (bmc->fail())
      throw xrt_core::error(boost::str(boost::format("Failed to read %s") % file));
  
    if (flasher.upgradeBMCFirmware(bmc.get()) != 0)
      throw xrt_core::error("Failed to update SC");
  }
  
  /* 
   * Helper function for header info
   */
  static std::string 
  file_size(const std::string & _file) 
  {
    std::ifstream in(_file.c_str(), std::ifstream::ate | std::ifstream::binary);
    auto total_size = std::to_string(in.tellg()); 
    int strSize = static_cast<int>(total_size.size());
  
    //if file size is > 3 digits, insert commas after every 3 digits
    static const int COMMA_SPACING = 3;
    if (strSize > COMMA_SPACING) {
     for (int i = (strSize - COMMA_SPACING);  i > 0;  i -= COMMA_SPACING)
       total_size.insert(static_cast<size_t>(i), 1, ',');
    }
    
    return total_size + std::string(" bytes");
  }
  
  /* 
   * Helper function for header info
   */
  static std::pair<std::string, std::string> 
  deployment_path_and_filename(std::string file)
  {
    using tokenizer = boost::tokenizer< boost::char_separator<char> >;
    boost::char_separator<char> sep("\\/");
    tokenizer tokens(file, sep);
    std::string dsafile = "";
    for (auto tok_iter = tokens.begin(); tok_iter != tokens.end(); ++tok_iter) {
    	if ((std::string(*tok_iter).find(XSABIN_FILE_SUFFIX) != std::string::npos) 
            || (std::string(*tok_iter).find(DSABIN_FILE_SUFFIX) != std::string::npos))
            dsafile = *tok_iter;
    }
    auto pos = file.rfind('/') != std::string::npos ? file.rfind('/') : file.rfind('\\');
    std::string path = file.erase(pos);
  
    return std::make_pair(dsafile, path);
  }
  
  /* 
   * Helper function for header info
   */
  static std::string 
  get_file_timestamp(const std::string & _file) 
  {
    boost::filesystem::path p(_file);
  	if (!boost::filesystem::exists(p)) {
  		throw xrt_core::error("Invalid platform path.");
  	}
    std::time_t ftime = boost::filesystem::last_write_time(boost::filesystem::path(_file));
    std::string timeStr(std::asctime(std::localtime(&ftime)));
    timeStr.pop_back();  // Remove the new-line character that gets inserted by asctime.
    return timeStr;
  }
  
  static void
  pretty_print_platform_info(const boost::property_tree::ptree& _ptDevice)
  {
    std::cout << boost::format("%s : [%s]\n") % "Device" % _ptDevice.get<std::string>("platform.bdf");
    std::cout << std::endl;
    std::cout << "Current Configuration\n";
  
    std::cout << boost::format("  %-20s : %s\n") % "Platform" % _ptDevice.get<std::string>("platform.current_shell.vbnv", "N/A");
    std::cout << boost::format("  %-20s : %s\n") % "SC Version" % _ptDevice.get<std::string>("platform.current_shell.sc_version", "N/A");
    std::cout << boost::format("  %-20s : %s\n") % "Platform ID" % _ptDevice.get<std::string>("platform.current_shell.id", "N/A");
    std::cout << std::endl;
    std::cout << "\nIncoming Configuration\n";
    const boost::property_tree::ptree& available_shells = _ptDevice.get_child("platform.available_shells");
    // if multiple shells are installed, do not proceed
    if( available_shells.size() > 1)
        throw xrt_core::error("Auto update is not possible when multiple shells are installed on the system. Please use --image option to specify the path of a particular flash image.");
  
    const boost::property_tree::ptree& available_shell = available_shells.front().second;
    std::pair <std::string, std::string> s = deployment_path_and_filename(available_shell.get<std::string>("file"));
    std::cout << boost::format("  %-20s : %s\n") % "Deployment File" % s.first;
    std::cout << boost::format("  %-20s : %s\n") % "Deployment Directory" % s.second;
    std::cout << boost::format("  %-20s : %s\n") % "Size" % file_size(available_shell.get<std::string>("file").c_str());
    std::cout << boost::format("  %-20s : %s\n\n") % "Timestamp" % get_file_timestamp(available_shell.get<std::string>("file").c_str());
  
    std::cout << boost::format("  %-20s : %s\n") % "Platform" % available_shell.get<std::string>("vbnv", "N/A");
    std::cout << boost::format("  %-20s : %s\n") % "SC Version" % available_shell.get<std::string>("sc_version", "N/A");
    std::cout << boost::format("  %-20s : %s\n") % "Platform ID" % available_shell.get<std::string>("id", "N/A");
  }
  
  static void
  report_status(xrt_core::device_collection& deviceCollection, boost::property_tree::ptree& _pt) 
  {
    std::vector<std::string> elementsFilter;
    //get platform report for all the devices
    std::cout << "----------------------------------------------------\n";
    for (const auto & device : deviceCollection) {
      boost::property_tree::ptree _ptDevice;
      auto _rep = std::make_unique<ReportPlatform>();
      _rep->getPropertyTreeInternal(device.get(), _ptDevice);
      _pt.push_back(std::make_pair(std::to_string(device->get_device_id()), _ptDevice));
      pretty_print_platform_info(_ptDevice);
      std::cout << "----------------------------------------------------\n";
    }
  
    std::stringstream action_list;
    for (const auto & device : deviceCollection) {
      if (!_pt.get<bool>(std::to_string(device->get_device_id()) + ".platform.status.shell"))
        action_list << boost::format("  [%s] : Program shell (FLASH) image\n") % _pt.get<std::string>(std::to_string(device->get_device_id())+".platform.bdf");
      if (!_pt.get<bool>(std::to_string(device->get_device_id())+".platform.status.sc"))
        action_list << boost::format("  [%s] : Program Satellite Controller (SC) image\n") % _pt.get<std::string>(std::to_string(device->get_device_id())+".platform.bdf");
    }
    
    if(!action_list.str().empty()) {
      std::cout << "Actions to perform:\n" << action_list.str();
      std::cout << "----------------------------------------------------\n";
    }
  
  }
  
  /* 
   * Flash shell and sc firmware
   * Helper method for auto_flash
   */
  static int 
  updateShellAndSC(unsigned int  boardIdx, DSAInfo& candidate, bool& reboot)
  {
    reboot = false;
  
    Flasher flasher(boardIdx);
  
    bool same_dsa = false;
    bool same_bmc = false;
    DSAInfo current = flasher.getOnBoardDSA();
    if (!current.name.empty()) {
      same_dsa = (candidate.name == current.name &&
        candidate.matchId(current));
  
      // Always update Arista devices
      if (candidate.vendor_id == ARISTA_ID)
          same_dsa = false;
  
      // getOnBoardDSA() returns an empty bmcVer in the case there is no SC,
      // so do not update
      if (current.bmcVer.empty())
          same_bmc = true;
      else
          same_bmc = (candidate.bmcVer == current.bmcVer);
    }
    if (same_dsa && same_bmc) {
      std::cout << "update not needed" << std::endl;
      return 0;
    }
  
    if (!same_bmc) {
      std::cout << "Updating SC firmware on card[" << flasher.sGetDBDF() <<
        "]" << std::endl;
      update_SC(boardIdx, candidate.file);
    }
  
    if (!same_dsa) {
      std::cout << boost::format("[%s] : Updating shell\n") % flasher.sGetDBDF();
      update_shell(boardIdx, candidate.file, candidate.file);
      reboot = true;
    }
  
    if (!same_dsa && !reboot)
      return -EINVAL;
  
    return 0;
  }
  
  static std::string 
  getBDF(unsigned int index)
  {
    auto dev =xrt_core::get_mgmtpf_device(index);
    auto bdf = xrt_core::device_query<xrt_core::query::pcie_bdf>(dev);
    return xrt_core::query::pcie_bdf::to_string(bdf);
  }
  
  /* 
   * Update shell and sc firmware on the card automatically
   */
  static void 
  auto_flash(xrt_core::device_collection& deviceCollection, bool force) 
  {
    //report status of all the devices
    boost::property_tree::ptree _pt;
    report_status(deviceCollection, _pt);
  
    // Collect all indexes of boards need updating
    std::vector<std::pair<unsigned int , DSAInfo>> boardsToUpdate;
    for (const auto & device : deviceCollection) {
      DSAInfo dsa(_pt.get_child(std::to_string(device->get_device_id()) + ".platform.available_shells").front().second.get<std::string>("file"));
      //if the shell is not up-to-date and dsa has a flash image, queue the board for update
      bool same_shell = _pt.get<bool>(std::to_string(device->get_device_id()) + ".platform.status.shell");
      bool same_sc = _pt.get<bool>(std::to_string(device->get_device_id()) + ".platform.status.sc");
  
      // Always update Arista devices
      auto vendor = xrt_core::device_query<xrt_core::query::pcie_vendor>(device);
      if (vendor == ARISTA_ID)
          same_shell = false;
  
      if (!same_shell || !same_sc) {
        if(!dsa.hasFlashImage)
          throw xrt_core::error("Flash image is not available");
        boardsToUpdate.push_back(std::make_pair(device->get_device_id(), dsa));
      }
    }
  
    // Continue to flash whatever we have collected in boardsToUpdate.
    uint16_t success = 0;
    bool needreboot = false;
    std::stringstream report_status;
    if (!boardsToUpdate.empty()) {
  
      // Prompt user about what boards will be updated and ask for permission.
      if(!force && !XBU::can_proceed())
        return;
  
      // Perform DSA and BMC updating
      for (auto& p : boardsToUpdate) {
        bool reboot;
        std::cout << std::endl;
        try {
          updateShellAndSC(p.first, p.second, reboot);
          report_status << boost::format("  [%s] : Successfully flashed\n") % getBDF(p.first);
          success++;
        } catch (const xrt_core::error& e) {
          std::cerr << boost::format("ERROR: %s\n") % e.what();
        }
        needreboot |= reboot;
      }
    }
    std::cout << "----------------------------------------------------\n";
    std::cout << "Report\n";
    //report status of cards
    std::cout << report_status.str();
    if (boardsToUpdate.size() == 0) {
      std::cout << "\nCard(s) up-to-date and do not need to be flashed." << std::endl;
      return;
    }
  
    if (success != 0) {
      std::cout << "\n" << success << " Card(s) flashed successfully." << std::endl; 
    } else {
      std::cout << "\nNo cards were flashed." << std::endl; 
    }
  
    if (needreboot) {
      std::cout << "****************************************************\n";
      std::cout << "Cold reboot machine to load the new image on card(s).\n";
      std::cout << "****************************************************\n";
    }
  
    if (success != boardsToUpdate.size()) {
      std::cout << "WARNING:" << boardsToUpdate.size()-success << " Card(s) not flashed. " << std::endl;
    }
  }
  
  static void
  program_plp(const xrt_core::device* dev, const std::string& partition)
  {
    std::ifstream stream(partition.c_str(), std::ios_base::binary);
    if (!stream.is_open())
      throw xrt_core::error(boost::str(boost::format("Cannot open %s") % partition));
  
    //size of the stream
    stream.seekg(0, stream.end);
    int total_size = static_cast<int>(stream.tellg());
    stream.seekg(0, stream.beg);
  
    //copy stream into a vector
    std::vector<char> buffer(total_size);
    stream.read(buffer.data(), total_size);
  
    xrt_core::program_plp(dev, buffer);
    std::cout << "Programmed PLP successfully" << std::endl;
  }
  
  static std::vector<std::string>
  find_flash_image_paths(const std::vector<std::string> image_list)
  {
    std::vector<std::string> path_list;
    auto installedShells = firmwareImage::getIntalledDSAs();
    
    //iterates over installed shells
    //checks the vbnv against the vnbv passed in by the user
    auto get_shell_file = [installedShells] (std::string shell_name) {
      std::string path;
      int multiple_shells = 0;
      
      for(auto const& shell : installedShells) {
        if(shell_name.compare(shell.name) == 0) {
          multiple_shells++;
          path = shell.file;
        }
      }
  
      //error-handling
      if(multiple_shells == 0) 
        throw xrt_core::error("Specified shell not found on the system");
      
      //if multiple shells with the same vbnv are installed on the system, we don't want to 
      //blindly update the device. in this case, the user needs to specify the complete path
      if(multiple_shells > 1) 
        throw xrt_core::error("Specified shell matched mutiple installed shells. Please specify the full path.");
  
      return path;
    };
  
    for(const auto& img : image_list) {
      // check if the passed in image is absolute path
      if(boost::filesystem::exists(boost::filesystem::path(img))) {
        path_list.push_back(img);
      }
      else { //search installed shells and find the image path by checking the vbnv
        path_list.push_back(get_shell_file(img));
      }
    }
    return path_list;
  }
  
  }
  //end anonymous namespace
  
  // ----- C L A S S   M E T H O D S -------------------------------------------
  
  SubCmdProgram::SubCmdProgram(bool _isHidden, bool _isDepricated, bool _isPreliminary)
      : SubCmd("program", 
               "Update device and/or Satallite Controler (SC) firmware image for a given device")
  {
    const std::string longDescription = "Updates the flash image for the device and/or the Satallite Controller (SC) firmware image for a given device.";
    setLongDescription(longDescription);
    setExampleSyntax("");
    setIsHidden(_isHidden);
    setIsDeprecated(_isDepricated);
    setIsPreliminary(_isPreliminary);
  }
  
  void
  SubCmdProgram::execute(const SubCmdOptions& _options) const
  // Reference Command:  [-d card] [-r region] -p xclbin
  //                     Download the accelerator program for card 2
  //                       xbutil program -d 2 -p a.xclbin
  {
    XBU::verbose("SubCommand: program");
  
    XBU::verbose("Option(s):");
    for (auto & aString : _options) {
      std::string msg = "   ";
      msg += aString;
      XBU::verbose(msg);
    }
  
    // -- Retrieve and parse the subcommand options -----------------------------
    std::vector<std::string> device;
    std::string plp = "";
    std::string update = "";
    std::string flashType = "";
    std::vector<std::string> image;
    bool revertToGolden = false;
    bool help = false;
    bool force = false;
  
    po::options_description commonOptions("Common Options");  
    commonOptions.add_options()
      ("device,d", boost::program_options::value<decltype(device)>(&device)->multitoken(), "The Bus:Device.Function (e.g., 0000:d8:00.0) device of interest.  A value of 'all' indicates that every found device should be examined.")
      ("partition", boost::program_options::value<decltype(plp)>(&plp), "The partition to be loaded.  Valid values:\n"
                                                                        "  Name (and path) of the partition.")
      ("update", boost::program_options::value<decltype(update)>(&update)->implicit_value("all"), "Update the persistent images.  Value values:\n"
                                                                           "  ALL   - All images will be updated"
                                                                       /*  "  FLASH - Flash image\n"
                                                                           "  SC    - Satellite controller"*/)
      ("force,f", boost::program_options::bool_switch(&force), "Force update the flash image")
      ("revert-to-golden", boost::program_options::bool_switch(&revertToGolden), "Resets the FPGA PROM back to the factory image. Note: The Satellite Control (MSP432) will not be reverted for a golden image does not exist.")
      ("help,h", boost::program_options::bool_switch(&help), "Help to use this sub-command")
    ;
  
    po::options_description hiddenOptions("Hidden Options");  
    hiddenOptions.add_options()
      ("flash-type", boost::program_options::value<decltype(flashType)>(&flashType), "Overrides the flash mode. Use with caution.  Value values:\n"
                                                                      "  ospi\n"
                                                                      "  ospi_versal")
      ("image", boost::program_options::value<decltype(image)>(&image)->multitoken(), "Specifies an image to use used to update the persistent device(s).  Value values:\n"
                                                                      "  Name (and path) to the mcs image on disk\n"
                                                                      "  Name (and path) to the xsabin image on disk\n"
                                                                      "Note: Multiple images can be specified separated by a space")
    ;
  
    po::options_description allOptions("All Options");  
  
    allOptions.add(commonOptions);
    allOptions.add(hiddenOptions);
  
  
    // Parse sub-command ...
    po::variables_map vm;
  
    try {
      po::store(po::command_line_parser(_options).options(allOptions).run(), vm);
      po::notify(vm); // Can throw
    } catch (po::error& e) {
      std::cerr << "ERROR: " << e.what() << std::endl << std::endl;
      printHelp(commonOptions, hiddenOptions);
      return;
    }
  
    // Check to see if help was requested or no command was found
    if (help == true)  {
      printHelp(commonOptions, hiddenOptions);
      return;
    }
  
    // -- Now process the subcommand --------------------------------------------
    XBU::verbose(boost::str(boost::format("  Update: %s") % update));
  
    // -- process "device" option -----------------------------------------------
    //enforce device specification
    if(device.empty()) {
      std::cout << "\nERROR: Device not specified.\n";
      std::cout << "\nList of available devices:" << std::endl;
      boost::property_tree::ptree available_devices = XBU::get_available_devices(false);
      for(auto& kd : available_devices) {
        boost::property_tree::ptree& dev = kd.second;
        std::cout << boost::format("  [%s] : %s\n") % dev.get<std::string>("bdf") % dev.get<std::string>("vbnv");
      }
      std::cout << std::endl;
      return;
    }
  
    // Collect all of the devices of interest
    std::set<std::string> deviceNames;
    xrt_core::device_collection deviceCollection;
    for (const auto & deviceName : device) 
      deviceNames.insert(boost::algorithm::to_lower_copy(deviceName));
  
    try {
      XBU::collect_devices(deviceNames, false /*inUserDomain*/, deviceCollection);
    } catch (const std::runtime_error& e) {
      // Catch only the exceptions that we have generated earlier
      std::cerr << boost::format("ERROR: %s\n") % e.what();
      return;
    }
  
    if(force) {
      //force is a sub-option of update
      if(update.empty())
        throw xrt_core::error("Usage: xbmgmt program --device='0000:00:00.0' --update --force'");
    }
  
    // TODO: Added mutually exclusive code for image, update, and revert-to-golden action.
  
    if(!image.empty()) {
      //image is a sub-option of update
      if(update.empty())
        throw xrt_core::error("Usage: xbmgmt program --device='0000:00:00.0' --update --image='/path/to/flash_image' OR shell_name");
      //allow only 1 device to be manually flashed at a time
      if(deviceCollection.size() != 1)
        throw xrt_core::error("Please specify a single device to be flashed");
      //we support only 2 flash images atm
      if(image.size() > 2)
        throw xrt_core::error("Please specify either 1 or 2 flash images");
      //find the absolute path to the specified image
      auto image_paths = find_flash_image_paths(image);
      if(!XBU::can_proceed())
        return;
      update_shell(deviceCollection.front()->get_device_id(), flashType, image_paths.front(), (image_paths.size() == 2 ? image_paths[1]: ""));
      return;
    }
  
    if (!update.empty()) {
      XBU::verbose("Sub command: --update");
      std::string empty = "";
      if(update.compare("all") == 0)
        auto_flash(deviceCollection, force);
      else if(update.compare("flash") == 0)
        throw xrt_core::error("Platform only update is not supported");
      else if(update.compare("sc") == 0)
        throw xrt_core::error("SC only update is not supported");
      else 
        throw xrt_core::error("Please specify a valid value");
      return;
    }
    
    // -- process "revert-to-golden" option ---------------------------------------
    if(revertToGolden) {
      XBU::verbose("Sub command: --revert-to-golden");
  
      std::vector<Flasher> flasher_list;
      for (const auto & dev : deviceCollection) {
        //collect information of all the cards that will be reset
        Flasher flasher(dev->get_device_id());
        if(!flasher.isValid()) {
          xrt_core::error(boost::str(boost::format("%d is an invalid index") % dev->get_device_id()));
          continue;
        }
        std::cout << boost::format("%-8s : %s %s %s \n") % "INFO" % "Resetting card [" 
          % flasher.sGetDBDF() % "] back to factory mode.";
        flasher_list.push_back(flasher);
      }
      
      //ask user's permission
      if(!XBU::can_proceed())
        return;
      
      for(auto& f : flasher_list) {
        f.upgradeFirmware("", nullptr, nullptr);
        std::cout << boost::format("%-8s : %s %s %s\n") % "INFO" % "Shell on [" % f.sGetDBDF() % "] is reset successfully." ;
      }
  
      std::cout << "****************************************************\n";
      std::cout << "Cold reboot machine to load the new image on card(s).\n";
      std::cout << "****************************************************\n";
      return;
    }
  
    // -- process "plp" option ---------------------------------------
    if(!plp.empty()) {
      XBU::verbose(boost::str(boost::format("  plp: %s") % plp));
      //only 1 card and name
      if(deviceCollection.size() > 1)
        throw xrt_core::error("Please specify a single device");
      auto dev = deviceCollection.front();
  
      Flasher flasher(dev->get_device_id());
      if(!flasher.isValid())
        throw xrt_core::error(boost::str(boost::format("%d is an invalid index") % dev->get_device_id()));
  
      if(xrt_core::device_query<xrt_core::query::interface_uuids>(dev).empty())
        throw xrt_core::error("Can not get BLP interface uuid. Please make sure corresponding BLP package is installed.");
  
      // Check if file exists
      if (!boost::filesystem::exists(plp))
        throw xrt_core::error("File not found. Please specify the correct path");
  
      DSAInfo dsa(plp);
      //TO_DO: add a report for plp before asking permission to proceed. Replace following 2 lines
      std::cout << "Programming PLP on Card [" << flasher.sGetDBDF() << "]..." << std::endl;
      std::cout << "Partition file: " << dsa.file << std::endl;
      
      //ask user's permission
      for (const auto& uuid : dsa.uuids) {
        //check if plp is compatible with the installed blp
        if (xrt_core::device_query<xrt_core::query::interface_uuids>(dev).front().compare(uuid) == 0) {
          program_plp(dev.get(), dsa.file);
          return;
        }
      }
      throw xrt_core::error("uuid does not match BLP");
    }
  
    std::cout << "\nERROR: Missing flash operation.  No action taken.\n\n";
    printHelp(commonOptions, hiddenOptions);
  }
  