text: /**
   * Copyright (C) 2020-2021 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 "OO_Config.h"
  #include "tools/common/XBUtilities.h"
  namespace XBU = XBUtilities;
  
  #include "core/common/device.h"
  #include "core/common/system.h"
  #include "core/common/error.h"
  #include "core/common/utils.h"
  #include "core/common/query_requests.h"
  
  // 3rd Party Library - Include Files
  #include <boost/format.hpp>
  #include <boost/program_options.hpp>
  namespace po = boost::program_options;
  
  // System - Include Files
  #include <iostream>
  #include <fstream>
  
  // =============================================================================
  
  // ------ L O C A L   F U N C T I O N S ---------------------------------------
  
  namespace {
  
  constexpr const char* config_file  = "/etc/msd.conf";
  
  enum class config_type {
      security = 0,
      clk_scaling,
      threshold_power_override,
      threshold_temp_override,
      reset,
  };
  
  enum class mem_type {
      unknown= 0,
      ddr,
      hbm, 
      
  };
  
  // Presumably a struct in anticipation of more data
  struct config
  {
    std::string host;
  
    void write(std::ostream& ostr)
    {
      ostr << "host=" << host << std::endl;
    }
  };
  
  static std::string
  get_hostname()
  {
    std::string hn;
  #ifdef __GNUC__
    char hostname[256] = {0};
    gethostname(hostname, 256);
    hn = hostname;
  #endif
    return hn;
  }
  
  static config
  get_daemon_conf()
  {
    config cfg;
    cfg.host = get_hostname();
  
    std::ifstream istr(config_file);
    if (!istr)
      return cfg;
  
    // Load persistent value, may overwrite default one
    for (std::string line; std::getline(istr, line);) {
      auto pos = line.find('=', 0);
      if (pos == std::string::npos)
        throw xrt_core::system_error(EIO, "Bad daemon config file line '" + line + "'");
      auto key = line.substr(0, pos++);
      auto value = line.substr(pos);
  
      if (key == "host") 
        cfg.host = value;
    }
  
    return cfg;
  }
  
  /*
   * helper function for option:show
   * shows daemon config
   */
  static void 
  show_daemon_conf()
  {
    auto cfg = get_daemon_conf();
    std::cout << "Daemon:" << std::endl;
    std::cout << "\t";
    cfg.write(std::cout);
  }
  
  /*
   * helper function for option:show
   * shows device config
   */
  static void 
  show_device_conf(xrt_core::device* device)
  {
    auto bdf_raw = xrt_core::device_query<xrt_core::query::pcie_bdf>(device);
    auto bdf = xrt_core::query::pcie_bdf::to_string(bdf_raw);
  
    std::cout << bdf << "\n";
  
    try {
      auto is_mfg = xrt_core::device_query<xrt_core::query::is_mfg>(device);
      auto is_recovery = xrt_core::device_query<xrt_core::query::is_recovery>(device);
      if (is_mfg || is_recovery) {
        std::cerr << "This operation is not supported with manufacturing image.\n";
        return;
      }
    }
    catch (const std::exception& ex) {
      std::cout << ex.what() << "\n";
    }
  
    try {
      auto sec_level = xrt_core::device_query<xrt_core::query::sec_level>(device);
      std::cout << boost::format("  %-33s: %s\n") % "Security level" % sec_level;
    }
    catch (...) {
      //safe to ignore. These sysfs nodes are not present for vck5000 
    }
  
    try {
      auto scaling_enabled = xrt_core::device_query<xrt_core::query::xmc_scaling_enabled>(device);
      std::cout << boost::format("  %-33s: %s\n") % "Runtime clock scaling enabled" % scaling_enabled;
    }
    catch (...) {
      //safe to ignore. These sysfs nodes are not present for u30
    }
  
    try {
      auto scaling_override = xrt_core::device_query<xrt_core::query::xmc_scaling_override>(device);
      std::cout << boost::format("  %-33s: %s\n") % "Scaling threshold power override" % scaling_override;
    }
    catch (...) {
      //safe to ignore. These sysfs nodes are not present for u30
    }
  
    try {
      auto scaling_override = xrt_core::device_query<xrt_core::query::xmc_scaling_temp_override>(device);
      std::cout << boost::format("  %-33s: %s\n") % "Scaling threshold temp override" % scaling_override;
    }
    catch (...) {
      //safe to ignore. These sysfs nodes are not present for u30
    }
  
    try {
      auto value = xrt_core::device_query<xrt_core::query::data_retention>(device);
      auto data_retention = xrt_core::query::data_retention::to_bool(value);
      std::cout << boost::format("  %-33s: %s\n") % "Data retention" % (data_retention ? "enabled" : "disabled");
    }
    catch (...) {
      //safe to ignore. These sysfs nodes are not present for vck5000 
    }
  
    std::cout << std::flush;
  }
  
  /*
   * helper function for option:daemon
   * change host name in config
   */
  static void 
  update_daemon_config(const std::string& host)
  {
    XBU::sudo_or_throw("Updating daemon configuration requires sudo");
    auto cfg = get_daemon_conf();
  
    std::ofstream cfile(config_file);
    if (!cfile)
      throw xrt_core::system_error(EINVAL, "Missing '" + std::string(config_file) + "'.  Cannot update");
  
    cfg.host = host;
    cfg.write(cfile);
  }
  
  /*
   * helper function for option:device
   * update device configuration
   */
  static void 
  update_device_conf(xrt_core::device* device, const std::string& value, config_type cfg)
  {
    switch(cfg) {
    case config_type::security:
      xrt_core::device_update<xrt_core::query::sec_level>(device, value);
      break;
    case config_type::clk_scaling:
      xrt_core::device_update<xrt_core::query::xmc_scaling_enabled>(device, value);
      break;
    case config_type::threshold_power_override:
      xrt_core::device_update<xrt_core::query::xmc_scaling_override>(device, value);
      break;
    case config_type::threshold_temp_override:
      xrt_core::device_update<xrt_core::query::xmc_scaling_temp_override>(device, value);
      break;
    case config_type::reset:
      xrt_core::device_update<xrt_core::query::xmc_scaling_reset>(device, value);
      break;
    }
  }
  
  /*
   * helper function for option:en(dis)able_retention
   * set data retention
   */
  static void 
  memory_retention(xrt_core::device* device, mem_type, bool enable)
  {
    XBU::sudo_or_throw("Updating memory retention requires sudo");
    auto value = xrt_core::query::data_retention::value_type(enable);
    xrt_core::device_update<xrt_core::query::data_retention>(device, value);
  }
  
  } // namespace
  
  
  // ----- C L A S S   M E T H O D S -------------------------------------------
  
  OO_Config::OO_Config( const std::string &_longName, bool _isHidden)
      : OptionOptions(_longName, _isHidden, "Utility to modify the memory configuration(s)")
      , m_devices({})
      , m_help(false)
      , m_daemon(false)
      , m_host("")
      , m_security("")
      , m_clk_scale("")
      , m_power_override("")
      , m_temp_override("")
      , m_cs_reset("")
      , m_show(false)
      , m_ddr(false)
      , m_hbm(false)
      , m_retention("")
  
  {
    m_optionsDescription.add_options()
      ("device,d", boost::program_options::value<decltype(m_devices)>(&m_devices)->multitoken(), "The Bus:Device.Function (e.g., 0000:d8:00.0) device of interest")
      ("retention", boost::program_options::value<decltype(m_retention)>(&m_retention),"Enables / Disables memory retention.  Valid values are: [ENABLE | DISABLE]")
      ("ddr", boost::program_options::bool_switch(&m_ddr), "Enable DDR memory for retention")
      ("help,h", boost::program_options::bool_switch(&m_help), "Help to use this sub-command")
    ;
  
    m_optionsHidden.add_options()
      ("daemon", boost::program_options::bool_switch(&m_daemon), "<add description>")
      ("host", boost::program_options::value<decltype(m_host)>(&m_host), "ip or hostname for peer")
      ("security", boost::program_options::value<decltype(m_security)>(&m_security), "<add description>")
      ("runtime_clk_scale", boost::program_options::value<decltype(m_clk_scale)>(&m_clk_scale), "<add description>")
      ("cs_threshold_power_override", boost::program_options::value<decltype(m_power_override)>(&m_power_override), "<add description>")
      ("cs_threshold_temp_override", boost::program_options::value<decltype(m_temp_override)>(&m_temp_override), "<add description>")
      ("cs_reset", boost::program_options::value<decltype(m_cs_reset)>(&m_cs_reset), "<add description>")
      ("showx", boost::program_options::bool_switch(&m_show), "<add description>")
      ("hbm", boost::program_options::bool_switch(&m_hbm), "<add description>")
    ;
  }
  
  void
  OO_Config::execute(const SubCmdOptions& _options) const
  {
  
    XBU::verbose("SubCommand option: config");
  
    XBU::verbose("Option(s):");
    for (auto & aString : _options)
      XBU::verbose(std::string(" ") + aString);
  
    // Parse sub-command ...
    po::variables_map vm;
  
    try {
      po::options_description all("All Options");
      all.add(m_optionsDescription);
      all.add(m_optionsHidden);
      //po::store(po::command_line_parser(_options).options(m_optionsDescription).run(), vm);
      po::store(po::command_line_parser(_options).options(all).run(), vm);
      po::notify(vm); // Can throw
    } catch (po::error& e) {
      std::cerr << "ERROR: " << e.what() << std::endl << std::endl;
      printHelp();
      throw xrt_core::error(std::errc::operation_canceled);
    }
  
    // Check the options
    // Help
    if (m_help) {
      printHelp();
      return;
    }
  
  
    // Check the options
    if (!m_retention.empty()) {
      boost::algorithm::to_upper(m_retention);
      if ((m_retention != "ENABLE") && 
          (m_retention != "DISABLE")) {
        std::cerr << "ERROR: Invalidate '--retention' option: " << m_retention << std::endl;
        printHelp();
        throw xrt_core::error(std::errc::operation_canceled);
      }
    }
  
    if (m_devices.empty() && !m_daemon)  {
      std::cerr << "ERROR: If the daemon is to be used (e.g., set to true) then a device must also be declared." << std::endl;
      printHelp();
      throw xrt_core::error(std::errc::operation_canceled);
    }
  
    // -- process option: device -----------------------------------------------
    std::set<std::string> deviceNames;
    xrt_core::device_collection deviceCollection;
    for (const auto & deviceName : m_devices) 
      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();
      throw xrt_core::error(std::errc::operation_canceled);
    }
  
    // enforce 1 device specification
    if(deviceCollection.size() > 1) {
      std::cerr << "\nERROR: Configuring multiple devices is not supported. Please specify a single device using --device option\n\n";
      std::cout << "List 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;
      throw xrt_core::error(std::errc::operation_canceled);
    }
  
    //Option:show
    if (m_show) {
      XBU::verbose("Sub command: --show");
      //show daemon config
      if(m_daemon)
        show_daemon_conf();
  
      //show device config
      for (const auto& dev : deviceCollection)
        show_device_conf(dev.get());
  
      return;
    }
  
    //-- process option: daemon -----------------------------------------------
    if (m_daemon) {
      XBU::verbose("Sub command: --daemon");
      if(m_host.empty())
        throw xrt_core::error("Please specify ip or hostname for peer");
  
      update_daemon_config(m_host);
      return;
    }
  
    //-- process option: device -----------------------------------------------
    if (!m_devices.empty()) {
      XBU::verbose("Sub command: --device");
      //update security
      if (!m_security.empty())
        for (const auto& dev : deviceCollection)
          update_device_conf(dev.get(), m_security, config_type::security);
  
      //clock scaling
      if (!m_clk_scale.empty())
        for (const auto& dev : deviceCollection)
          update_device_conf(dev.get(), m_clk_scale, config_type::clk_scaling);
      
      //update threshold power override
      if (!m_power_override.empty())
        for (const auto& dev : deviceCollection)
          update_device_conf(dev.get(), m_power_override, config_type::threshold_power_override);
  
      //update threshold temp override
      if (!m_temp_override.empty())
        for (const auto& dev : deviceCollection)
          update_device_conf(dev.get(), m_temp_override, config_type::threshold_temp_override);
  
      //cs_reset
      if (!m_cs_reset.empty())
        for (const auto& dev : deviceCollection)
          update_device_conf(dev.get(), m_cs_reset, config_type::reset);
  
      //  enable/disable_retention
      if (!m_retention.empty()) {
        auto mem = mem_type::unknown; 
        if(m_ddr)
          mem = mem_type::ddr;
        else if (m_hbm)
          mem = mem_type::hbm;
        else
          throw xrt_core::system_error(EINVAL, "Please specify memory type: ddr or hbm");
  
        bool enableRetention = (m_retention == "ENABLE");
        for (const auto& dev : deviceCollection)
          memory_retention(dev.get(), mem, enableRetention);
      }
    }
  }
  
  