manager: Save and Restore BIOS Attributes

This commit saves any updates made to BIOS attributes
to VPD.

It also restores the attributes from the VPD when we
start VPD manager.

Signed-off-by: Santosh Puranik <santosh.puranik@in.ibm.com>
Change-Id: Idb28e2f89d21ccd89eb8e56490eb7f31397ff5f5
diff --git a/ibm_vpd_utils.cpp b/ibm_vpd_utils.cpp
index 4eb99cb..c7babf6 100644
--- a/ibm_vpd_utils.cpp
+++ b/ibm_vpd_utils.cpp
@@ -886,5 +886,28 @@
         map.emplace(interface, property);
     }
 }
+
+BIOSAttrValueType readBIOSAttribute(const std::string& attrName)
+{
+    std::tuple<std::string, BIOSAttrValueType, BIOSAttrValueType> attrVal;
+    auto bus = sdbusplus::bus::new_default();
+    auto method = bus.new_method_call(
+        "xyz.openbmc_project.BIOSConfigManager",
+        "/xyz/openbmc_project/bios_config/manager",
+        "xyz.openbmc_project.BIOSConfig.Manager", "GetAttribute");
+    method.append(attrName);
+    try
+    {
+        auto result = bus.call(method);
+        result.read(std::get<0>(attrVal), std::get<1>(attrVal),
+                    std::get<2>(attrVal));
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        std::cerr << "Failed to read BIOS Attribute: " << attrName << std::endl;
+        std::cerr << e.what() << std::endl;
+    }
+    return std::get<1>(attrVal);
+}
 } // namespace vpd
 } // namespace openpower
diff --git a/ibm_vpd_utils.hpp b/ibm_vpd_utils.hpp
index 47bab17..3a7d13d 100644
--- a/ibm_vpd_utils.hpp
+++ b/ibm_vpd_utils.hpp
@@ -315,5 +315,50 @@
                    const inventory::Interface& interface,
                    inventory::PropertyMap&& property);
 
+/**
+ * @brief Utility API to set a D-Bus property
+ *
+ * This calls org.freedesktop.DBus.Properties;Set method with the supplied
+ * arguments
+ *
+ * @tparam T Template type of the D-Bus property
+ * @param service[in] - The D-Bus service name.
+ * @param object[in] - The D-Bus object on which the property is to be set.
+ * @param interface[in] - The D-Bus interface to which the property belongs.
+ * @param propertyName[in] - The name of the property to set.
+ * @param propertyValue[in] - The value of the property.
+ */
+template <typename T>
+void setBusProperty(const std::string& service, const std::string& object,
+                    const std::string& interface,
+                    const std::string& propertyName,
+                    const std::variant<T>& propertyValue)
+{
+    try
+    {
+        auto bus = sdbusplus::bus::new_default();
+        auto method =
+            bus.new_method_call(service.c_str(), object.c_str(),
+                                "org.freedesktop.DBus.Properties", "Set");
+        method.append(interface);
+        method.append(propertyName);
+        method.append(propertyValue);
+
+        bus.call(method);
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        std::cerr << e.what() << std::endl;
+    }
+}
+
+/**
+ * @brief Reads BIOS Attribute by name.
+ *
+ * @param attrName[in] - The BIOS attribute name.
+ * @return std::variant<int64_t, std::string> - The BIOS attribute value.
+ */
+std::variant<int64_t, std::string>
+    readBIOSAttribute(const std::string& attrName);
 } // namespace vpd
-} // namespace openpower
+} // namespace openpower
\ No newline at end of file
diff --git a/types.hpp b/types.hpp
index f87b53c..99a1127 100644
--- a/types.hpp
+++ b/types.hpp
@@ -17,6 +17,10 @@
 static_assert((8 == CHAR_BIT), "A byte is not 8 bits!");
 using Byte = uint8_t;
 using Binary = std::vector<Byte>;
+using BIOSAttrValueType = std::variant<int64_t, std::string>;
+using PendingBIOSAttrItemType =
+    std::pair<std::string, std::tuple<std::string, BIOSAttrValueType>>;
+using PendingBIOSAttrsType = std::vector<PendingBIOSAttrItemType>;
 
 namespace inventory
 {
diff --git a/vpd-manager/bios_handler.cpp b/vpd-manager/bios_handler.cpp
new file mode 100644
index 0000000..cb251a6
--- /dev/null
+++ b/vpd-manager/bios_handler.cpp
@@ -0,0 +1,338 @@
+#include "config.h"
+
+#include "bios_handler.hpp"
+
+#include "const.hpp"
+#include "ibm_vpd_utils.hpp"
+#include "manager.hpp"
+#include "types.hpp"
+
+#include <iostream>
+#include <memory>
+#include <sdbusplus/bus.hpp>
+#include <string>
+#include <tuple>
+#include <variant>
+
+using namespace openpower::vpd;
+using namespace openpower::vpd::constants;
+
+namespace openpower
+{
+namespace vpd
+{
+namespace manager
+{
+void BiosHandler::checkAndListenPLDMService()
+{
+    // Setup a match on NameOwnerChanged to determine when PLDM is up. In
+    // the signal handler, call restoreBIOSAttribs
+    static std::shared_ptr<sdbusplus::bus::match::match> nameOwnerMatch =
+        std::make_shared<sdbusplus::bus::match::match>(
+            bus,
+            sdbusplus::bus::match::rules::nameOwnerChanged(
+                "xyz.openbmc_project.PLDM"),
+            [this](sdbusplus::message::message& msg) {
+                if (msg.is_method_error())
+                {
+                    std::cerr << "Error in reading name owner signal "
+                              << std::endl;
+                    return;
+                }
+                std::string name;
+                std::string newOwner;
+                std::string oldOwner;
+
+                msg.read(name, oldOwner, newOwner);
+                if (newOwner != "" && name == "xyz.openbmc_project.PLDM")
+                {
+                    this->restoreBIOSAttribs();
+                    // We don't need the match anymore
+                    nameOwnerMatch.reset();
+                }
+            });
+    // Check if PLDM is already running, if it is, we can go ahead and attempt
+    // to sync BIOS attributes (since PLDM would have initialized them by the
+    // time it acquires a bus name).
+    bool isPLDMRunning = false;
+    try
+    {
+        auto bus = sdbusplus::bus::new_default();
+        auto method =
+            bus.new_method_call("org.freedesktop.DBus", "/org/freedesktop/DBus",
+                                "org.freedesktop.DBus", "NameHasOwner");
+        method.append("xyz.openbmc_project.PLDM");
+
+        auto result = bus.call(method);
+        result.read(isPLDMRunning);
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        std::cerr << "Failed to check if PLDM is running, assume false"
+                  << std::endl;
+        std::cerr << e.what() << std::endl;
+    }
+
+    std::cout << "Is PLDM running: " << isPLDMRunning << std::endl;
+
+    if (isPLDMRunning)
+    {
+        nameOwnerMatch.reset();
+        restoreBIOSAttribs();
+    }
+}
+
+void BiosHandler::listenBiosAttribs()
+{
+    static std::shared_ptr<sdbusplus::bus::match::match> biosMatcher =
+        std::make_shared<sdbusplus::bus::match::match>(
+            bus,
+            sdbusplus::bus::match::rules::propertiesChanged(
+                "/xyz/openbmc_project/bios_config/manager",
+                "xyz.openbmc_project.BIOSConfig.Manager"),
+            [this](sdbusplus::message::message& msg) {
+                biosAttribsCallback(msg);
+            });
+}
+
+void BiosHandler::biosAttribsCallback(sdbusplus::message::message& msg)
+{
+    if (msg.is_method_error())
+    {
+        std::cerr << "Error in reading BIOS attribute signal " << std::endl;
+        return;
+    }
+    using BiosProperty = std::tuple<
+        std::string, bool, std::string, std::string, std::string,
+        std::variant<int64_t, std::string>, std::variant<int64_t, std::string>,
+        std::vector<
+            std::tuple<std::string, std::variant<int64_t, std::string>>>>;
+    using BiosBaseTable = std::variant<std::map<std::string, BiosProperty>>;
+    using BiosBaseTableType = std::map<std::string, BiosBaseTable>;
+
+    std::string object;
+    BiosBaseTableType propMap;
+    msg.read(object, propMap);
+    for (auto prop : propMap)
+    {
+        if (prop.first == "BaseBIOSTable")
+        {
+            auto list = std::get<0>(prop.second);
+            for (const auto& item : list)
+            {
+                std::string attributeName = std::get<0>(item);
+                if (attributeName == "hb_memory_mirror_mode")
+                {
+                    auto attrValue = std::get<5>(std::get<1>(item));
+                    auto val = std::get_if<std::string>(&attrValue);
+                    if (val)
+                    {
+                        saveAMMToVPD(*val);
+                    }
+                }
+                else if (attributeName == "hb_field_core_override")
+                {
+                    auto attrValue = std::get<5>(std::get<1>(item));
+                    auto val = std::get_if<int64_t>(&attrValue);
+                    if (val)
+                    {
+                        saveFCOToVPD(*val);
+                    }
+                }
+            }
+        }
+    }
+}
+
+void BiosHandler::saveFCOToVPD(int64_t fcoVal)
+{
+    if (fcoVal == -1)
+    {
+        std::cerr << "Invalid FCO value from BIOS: " << fcoVal << std::endl;
+        return;
+    }
+
+    Binary vpdVal = {0, 0, 0, static_cast<uint8_t>(fcoVal)};
+    auto valInVPD = readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VSYS", "RG");
+
+    if (valInVPD.size() != 4)
+    {
+        std::cerr << "Read bad size for VSYS/RG: " << valInVPD.size()
+                  << std::endl;
+        return;
+    }
+
+    if (valInVPD.at(3) != fcoVal)
+    {
+        std::cout << "Writing FCO to VPD: " << fcoVal << std::endl;
+        manager.writeKeyword(sdbusplus::message::object_path{SYSTEM_OBJECT},
+                             "VSYS", "RG", vpdVal);
+    }
+}
+
+void BiosHandler::saveAMMToVPD(const std::string& mirrorMode)
+{
+    Binary vpdVal;
+    auto valInVPD = readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.UTIL", "D0");
+
+    if (valInVPD.size() != 1)
+    {
+        std::cerr << "Read bad size for UTIL/D0: " << valInVPD.size()
+                  << std::endl;
+        return;
+    }
+
+    if (mirrorMode != "Enabled" && mirrorMode != "Disabled")
+    {
+        std::cerr << "Bad value for Mirror mode BIOS arttribute: " << mirrorMode
+                  << std::endl;
+        return;
+    }
+
+    // Write to VPD only if the value is not already what we want to write.
+    if (mirrorMode == "Enabled" && valInVPD.at(0) != 2)
+    {
+        vpdVal.emplace_back(2);
+    }
+    else if (mirrorMode == "Disabled" && valInVPD.at(0) != 1)
+    {
+        vpdVal.emplace_back(1);
+    }
+
+    if (!vpdVal.empty())
+    {
+        std::cout << "Writing AMM to VPD: " << static_cast<int>(vpdVal.at(0))
+                  << std::endl;
+        manager.writeKeyword(sdbusplus::message::object_path{SYSTEM_OBJECT},
+                             "UTIL", "D0", vpdVal);
+    }
+}
+
+int64_t BiosHandler::readBIOSFCO()
+{
+    int64_t fcoVal = -1;
+    auto val = readBIOSAttribute("hb_field_core_override");
+
+    if (auto pVal = std::get_if<int64_t>(&val))
+    {
+        fcoVal = *pVal;
+    }
+    else
+    {
+        std::cerr << "FCO is not an int" << std::endl;
+    }
+    return fcoVal;
+}
+
+std::string BiosHandler::readBIOSAMM()
+{
+    std::string ammVal{};
+    auto val = readBIOSAttribute("hb_memory_mirror_mode");
+
+    if (auto pVal = std::get_if<std::string>(&val))
+    {
+        ammVal = *pVal;
+    }
+    else
+    {
+        std::cerr << "AMM is not a string" << std::endl;
+    }
+    return ammVal;
+}
+
+void BiosHandler::saveFCOToBIOS(const std::string& fcoVal)
+{
+    if (fcoVal.size() != 4)
+    {
+        std::cerr << "Bad size for FCO in VPD: " << fcoVal.size() << std::endl;
+        return;
+    }
+    PendingBIOSAttrsType biosAttrs;
+    biosAttrs.push_back(
+        std::make_pair("hb_field_core_override",
+                       std::make_tuple("xyz.openbmc_project.BIOSConfig.Manager."
+                                       "AttributeType.Integer",
+                                       fcoVal.at(3))));
+
+    std::cout << "Set hb_field_core_override to: "
+              << static_cast<int>(fcoVal.at(3)) << std::endl;
+
+    setBusProperty<PendingBIOSAttrsType>(
+        "xyz.openbmc_project.BIOSConfigManager",
+        "/xyz/openbmc_project/bios_config/manager",
+        "xyz.openbmc_project.BIOSConfig.Manager", "PendingAttributes",
+        biosAttrs);
+}
+
+void BiosHandler::saveAMMToBIOS(const std::string& ammVal)
+{
+    if (ammVal.size() != 1)
+    {
+        std::cerr << "Bad size for AMM in VPD: " << ammVal.size() << std::endl;
+        return;
+    }
+
+    // Make sure data in VPD is sane
+    if (ammVal.at(0) != 1 && ammVal.at(0) != 2)
+    {
+        std::cerr << "Bad value for AMM read from VPD: "
+                  << static_cast<int>(ammVal.at(0)) << std::endl;
+        return;
+    }
+    PendingBIOSAttrsType biosAttrs;
+    biosAttrs.push_back(std::make_pair(
+        "hb_memory_mirror_mode",
+        std::make_tuple("xyz.openbmc_project.BIOSConfig.Manager."
+                        "AttributeType.Enumeration",
+                        (ammVal.at(0) == 2) ? "Enabled" : "Disabled")));
+
+    std::cout << "Set hb_memory_mirror_mode to: "
+              << ((ammVal.at(0) == 2) ? "Enabled" : "Disabled") << std::endl;
+
+    setBusProperty<PendingBIOSAttrsType>(
+        "xyz.openbmc_project.BIOSConfigManager",
+        "/xyz/openbmc_project/bios_config/manager",
+        "xyz.openbmc_project.BIOSConfig.Manager", "PendingAttributes",
+        biosAttrs);
+}
+
+void BiosHandler::restoreBIOSAttribs()
+{
+    // TODO: We could make this slightly more scalable by defining a table of
+    // attributes and their corresponding VPD keywords. However, that needs much
+    // more thought.
+    std::cout << "Attempting BIOS attribute reset" << std::endl;
+    // Check if the VPD contains valid data for FCO and AMM *and* that it
+    // differs from the data already in the attributes. If so, set the BIOS
+    // attributes as per the value in the VPD.
+    // If the VPD contains default data, then initialize the VPD keywords with
+    // data taken from the BIOS.
+    auto fcoInVPD = readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VSYS", "RG");
+    auto ammInVPD = readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.UTIL", "D0");
+    auto fcoInBIOS = readBIOSFCO();
+    auto ammInBIOS = readBIOSAMM();
+
+    if (fcoInVPD == "    ")
+    {
+        saveFCOToVPD(fcoInBIOS);
+    }
+    else
+    {
+        saveFCOToBIOS(fcoInVPD);
+    }
+
+    if (ammInVPD.at(0) == 0)
+    {
+        saveAMMToVPD(ammInBIOS);
+    }
+    else
+    {
+        saveAMMToBIOS(ammInVPD);
+    }
+
+    // Start listener now that we have done the restore
+    listenBiosAttribs();
+}
+} // namespace manager
+} // namespace vpd
+} // namespace openpower
\ No newline at end of file
diff --git a/vpd-manager/bios_handler.hpp b/vpd-manager/bios_handler.hpp
new file mode 100644
index 0000000..ad6f05e
--- /dev/null
+++ b/vpd-manager/bios_handler.hpp
@@ -0,0 +1,169 @@
+#pragma once
+
+#include "types.hpp"
+
+#include <stdint.h>
+
+#include <string>
+
+namespace sdbusplus
+{
+namespace bus
+{
+class bus;
+} // namespace bus
+} // namespace sdbusplus
+
+namespace sdbusplus
+{
+namespace message
+{
+class message;
+} // namespace message
+} // namespace sdbusplus
+
+namespace openpower
+{
+namespace vpd
+{
+namespace manager
+{
+
+class Manager;
+/**
+ * @brief A class that handles changes to BIOS attributes backed by VPD.
+ *
+ * This class has APIs that handle updates to BIOS attributes that need to
+ * be backed up to VPD. It mainly does the following:
+ * 1) Checks if the VPD keywords that BIOS attributes are backed to are
+ * uninitialized. If so, it initializes them.
+ * 2) Listens for changes to BIOS attributes and synchronizes them to the
+ * appropriate VPD keyword.
+ *
+ * Since on a factory reset like scenario, the BIOS attributes are initialized
+ * by PLDM, this code waits until PLDM has grabbed a bus name before attempting
+ * any syncs.
+ */
+class BiosHandler
+{
+  public:
+    // Some default and deleted constructors and assignments.
+    BiosHandler() = delete;
+    BiosHandler(const BiosHandler&) = delete;
+    BiosHandler& operator=(const BiosHandler&) = delete;
+    BiosHandler(Manager&&) = delete;
+    BiosHandler& operator=(BiosHandler&&) = delete;
+    ~BiosHandler() = default;
+
+    BiosHandler(sdbusplus::bus::bus& bus, Manager& manager) :
+        bus(bus), manager(manager)
+    {
+        checkAndListenPLDMService();
+    }
+
+  private:
+    /**
+     * @brief Check if PLDM service is running and run BIOS sync
+     *
+     * This API checks if the PLDM service is running and if yes it will start
+     * an immediate sync of BIOS attributes. If the service is not running, it
+     * registers a listener to be notified when the service starts so that a
+     * restore can be performed.
+     */
+    void checkAndListenPLDMService();
+
+    /**
+     * @brief Register listener for changes to BIOS Attributes.
+     *
+     * The VPD manager needs to listen to changes to certain BIOS attributes
+     * that are backed by VPD. When the attributes we are interested in
+     * change, the VPD manager will make sure that we write them back to the
+     * VPD keywords that back them up.
+     */
+    void listenBiosAttribs();
+
+    /**
+     * @brief Callback for BIOS Attribute changes
+     *
+     * Checks if the BIOS attribute(s) changed are those backed up by VPD. If
+     * yes, it will update the VPD with the new attribute value.
+     * @param[in] msg - The callback message.
+     */
+    void biosAttribsCallback(sdbusplus::message::message& msg);
+
+    /**
+     * @brief Persistently saves the Memory mirror mode
+     *
+     * Memory mirror mode setting is saved to the UTIL/D0 keyword in the
+     * motherboard VPD. If the mirror mode in BIOS is "Disabled", set D0 to 1,
+     * if "Enabled" set D0 to 2
+     *
+     * @param[in] mirrorMode - The mirror mode BIOS attribute.
+     */
+    void saveAMMToVPD(const std::string& mirrorMode);
+
+    /**
+     * @brief Persistently saves the Field Core Override setting
+     *
+     * Saves the field core override value (FCO) into the VSYS/RG keyword in
+     * the motherboard VPD.
+     *
+     * @param[in] fcoVal - The FCO value as an integer.
+     */
+    void saveFCOToVPD(int64_t fcoVal);
+
+    /**
+     * @brief Writes Memory mirror mode to BIOS
+     *
+     * Writes to the hb_memory_mirror_mode BIOS attribute.
+     *
+     * @param[in] ammVal - The mirror mode as read from VPD.
+     */
+    void saveAMMToBIOS(const std::string& ammVal);
+
+    /**
+     * @brief Writes Field Core Override to BIOS
+     *
+     * Writes to the hb_field_core_override BIOS attribute.
+     *
+     * @param[in] fcoVal - The FCO value as read from VPD.
+     */
+    void saveFCOToBIOS(const std::string& fcoVal);
+
+    /**
+     * @brief Reads the hb_memory_mirror_mode attribute
+     *
+     * @return int64_t - The AMM BIOS attribute. -1 on failure.
+     */
+    std::string readBIOSAMM();
+
+    /**
+     * @brief Reads the hb_field_core_override attribute
+     *
+     * @return std::string - The FCO BIOS attribute. Empty string on failure.
+     */
+    int64_t readBIOSFCO();
+
+    /**
+     * @brief Restore BIOS attributes
+     *
+     * This function checks if we are coming out of a factory reset. If yes,
+     * it checks the VPD cache for valid backed up copy of the FCO and AMM
+     * BIOS attributes. If valid values are found in the VPD, it will apply
+     * those to the BIOS attributes.
+     */
+    void restoreBIOSAttribs();
+
+    /**
+     * @brief Reference to the bus.
+     */
+    sdbusplus::bus::bus& bus;
+
+    /**
+     * @brief Reference to the manager.
+     */
+    Manager& manager;
+}; // class BiosHandler
+} // namespace manager
+} // namespace vpd
+} // namespace openpower
\ No newline at end of file
diff --git a/vpd-manager/manager.cpp b/vpd-manager/manager.cpp
index 932cc9d..de40edf 100644
--- a/vpd-manager/manager.cpp
+++ b/vpd-manager/manager.cpp
@@ -2,6 +2,7 @@
 
 #include "manager.hpp"
 
+#include "bios_handler.hpp"
 #include "common_utility.hpp"
 #include "editor_impl.hpp"
 #include "gpioMonitor.hpp"
@@ -45,6 +46,9 @@
         listenHostState();
         listenAssetTag();
 
+        // Create an instance of the BIOS handler
+        BiosHandler biosHandler{_bus, *this};
+
         auto event = sdeventplus::Event::get_default();
         GpioMonitor gpioMon1(jsonFile, event);
 
diff --git a/vpd-manager/meson.build b/vpd-manager/meson.build
index 604f2b6..9b5c5c9 100644
--- a/vpd-manager/meson.build
+++ b/vpd-manager/meson.build
@@ -11,6 +11,7 @@
                       'editor_impl.cpp',
                       'reader_impl.cpp',
                       'gpioMonitor.cpp',
+                      'bios_handler.cpp',
                       '../impl.cpp',
                       '../vpd-parser/ipz_parser.cpp',
                       '../ibm_vpd_utils.cpp',