Restore system VPD if EEPROM data is blank.

This commit implement changes which enables restoring system VPD.
If at the time of VPD parsing for backplane it is found that data
related to system VPD is blank on EEPROM but available on cache,
then the data from Dbus will be used to restore data in EEPROM.

There are another scenarios in restoring the system VPD but this
commit only implement changes to restore system VPD when the data
is available on cache but blank on EEPROM.

This commit also implements creation and logging of PEL in case blank
system VPD is found both on EEPROM and Dbus in the process of
system VPD restore.

Meson Build: OK.
Tested on Simics: OK.

Signed-off-by: Sunny Srivastava <sunnsr25@in.ibm.com>
Change-Id: I32a872b3c3a74b79a9b8173c712b50f72fd7588c
diff --git a/ibm_vpd_app.cpp b/ibm_vpd_app.cpp
index b09ed79..f723613 100644
--- a/ibm_vpd_app.cpp
+++ b/ibm_vpd_app.cpp
@@ -8,6 +8,7 @@
 #include "utils.hpp"
 #include "vpd_exceptions.hpp"
 
+#include <assert.h>
 #include <ctype.h>
 
 #include <CLI/CLI.hpp>
@@ -580,6 +581,154 @@
 }
 
 /**
+ * @brief API to call VPD manager to write VPD to EEPROM.
+ * @param[in] Object path.
+ * @param[in] record to be updated.
+ * @param[in] keyword to be updated.
+ * @param[in] keyword data to be updated
+ */
+void updateHardware(const string& objectName, const string& recName,
+                    const string& kwdName, const Binary& data)
+{
+    try
+    {
+        auto bus = sdbusplus::bus::new_default();
+        auto properties =
+            bus.new_method_call(BUSNAME, OBJPATH, IFACE, "WriteKeyword");
+        properties.append(
+            static_cast<sdbusplus::message::object_path>(objectName));
+        properties.append(recName);
+        properties.append(kwdName);
+        properties.append(data);
+        bus.call(properties);
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        std::string what =
+            "VPDManager WriteKeyword api failed for inventory path " +
+            objectName;
+        what += " record " + recName;
+        what += " keyword " + kwdName;
+        what += " with bus error = " + std::string(e.what());
+
+        // map to hold additional data in case of logging pel
+        PelAdditionalData additionalData{};
+        additionalData.emplace("CALLOUT_INVENTORY_PATH", objectName);
+        additionalData.emplace("DESCRIPTION", what);
+        createPEL(additionalData, errIntfForBusFailure);
+    }
+}
+
+/**
+ * @brief API to check if we need to restore system VPD
+ * This functionality is only applicable for IPZ VPD data.
+ * @param[in] vpdMap - IPZ vpd map
+ * @param[in] objectPath - Object path for the FRU
+ * @return EEPROMs with records and keywords updated at standby
+ */
+std::vector<RestoredEeproms> restoreSystemVPD(Parsed& vpdMap,
+                                              const string& objectPath)
+{
+    // the list of keywords for VSYS record is as per the S0 system. Should be
+    // updated for another type of systems
+    static std::unordered_map<std::string, std::vector<std::string>> svpdKwdMap{
+        {"VSYS", {"BR", "TM", "SE", "SU", "RB"}},
+        {"VCEN", {"FC", "SE"}},
+        {"LXR0", {"LX"}}};
+
+    // vector to hold all the EEPROMs updated at standby
+    std::vector<RestoredEeproms> updatedEeproms = {};
+
+    for (const auto& systemRecKwdPair : svpdKwdMap)
+    {
+        auto it = vpdMap.find(systemRecKwdPair.first);
+
+        // check if record is found in map we got by parser
+        if (it != vpdMap.end())
+        {
+            const auto& kwdListForRecord = systemRecKwdPair.second;
+            for (const auto& keyword : kwdListForRecord)
+            {
+                DbusPropertyMap& kwdValMap = it->second;
+                auto iterator = kwdValMap.find(keyword);
+
+                if (iterator != kwdValMap.end())
+                {
+                    string& kwdValue = iterator->second;
+
+                    // check bus data
+                    const string& recordName = systemRecKwdPair.first;
+                    const string& busValue = readBusProperty(
+                        objectPath, ipzVpdInf + recordName, keyword);
+
+                    if (busValue.find_first_not_of(' ') != string::npos)
+                    {
+                        if (kwdValue.find_first_not_of(' ') != string::npos)
+                        {
+                            // both the data are present, check for mismatch
+                            if (busValue != kwdValue)
+                            {
+                                string errMsg = "VPD data mismatch on cache "
+                                                "and hardware for record: ";
+                                errMsg += (*it).first;
+                                errMsg += " and keyword: ";
+                                errMsg += keyword;
+
+                                // data mismatch
+                                PelAdditionalData additionalData;
+                                additionalData.emplace("CALLOUT_INVENTORY_PATH",
+                                                       objectPath);
+
+                                additionalData.emplace("DESCRIPTION", errMsg);
+
+                                createPEL(additionalData, errIntfForInvalidVPD);
+                            }
+                        }
+                        else
+                        {
+                            // implies hardware data is blank
+                            // update the map
+                            Binary busData(busValue.begin(), busValue.end());
+
+                            updatedEeproms.push_back(std::make_tuple(
+                                objectPath, recordName, keyword, busData));
+                        }
+
+                        // update the map as well, so that cache data is not
+                        // updated as blank while populating VPD map on Dbus in
+                        // populateDBus Api
+                        kwdValue = busValue;
+                        continue;
+                    }
+                    else if (kwdValue.find_first_not_of(' ') == string::npos)
+                    {
+                        string errMsg = "VPD is blank on both cache and "
+                                        "hardware for record: ";
+                        errMsg += (*it).first;
+                        errMsg += " and keyword: ";
+                        errMsg += keyword;
+                        errMsg += ". SSR need to update hardware VPD.";
+
+                        // both the data are blanks, log PEL
+                        PelAdditionalData additionalData;
+                        additionalData.emplace("CALLOUT_INVENTORY_PATH",
+                                               objectPath);
+
+                        additionalData.emplace("DESCRIPTION", errMsg);
+
+                        // log PEL TODO: Block IPL
+                        createPEL(additionalData, errIntfForBlankSystemVPD);
+                        continue;
+                    }
+                }
+            }
+        }
+    }
+
+    return updatedEeproms;
+}
+
+/**
  * @brief Populate Dbus.
  * This method invokes all the populateInterface functions
  * and notifies PIM about dbus object.
@@ -590,19 +739,22 @@
  * @param[in] preIntrStr - Interface string
  */
 template <typename T>
-static void populateDbus(const T& vpdMap, nlohmann::json& js,
-                         const string& filePath)
+static void populateDbus(T& vpdMap, nlohmann::json& js, const string& filePath)
 {
     inventory::InterfaceMap interfaces;
     inventory::ObjectMap objects;
     inventory::PropertyMap prop;
 
+    // map to hold all the keywords whose value has been changed at standby
+    vector<RestoredEeproms> updatedEeproms = {};
+
     bool isSystemVpd = false;
     for (const auto& item : js["frus"][filePath])
     {
         const auto& objectPath = item["inventoryPath"];
         sdbusplus::message::object_path object(objectPath);
         isSystemVpd = item.value("isSystemVpd", false);
+
         // Populate the VPD keywords and the common interfaces only if we
         // are asked to inherit that data from the VPD, else only add the
         // extraInterfaces.
@@ -610,6 +762,30 @@
         {
             if constexpr (is_same<T, Parsed>::value)
             {
+                if (isSystemVpd)
+                {
+                    std::vector<std::string> interfaces = {
+                        motherBoardInterface};
+                    // call mapper to check for object path creation
+                    MapperResponse subTree =
+                        getObjectSubtreeForInterfaces(pimPath, 0, interfaces);
+
+                    // Skip system vpd restore if object path is not generated
+                    // for motherboard, Implies first boot.
+                    if (subTree.size() != 0)
+                    {
+                        assert(
+                            (subTree.find(pimPath + std::string(objectPath)) !=
+                             subTree.end()));
+
+                        updatedEeproms = restoreSystemVPD(vpdMap, objectPath);
+                    }
+                    else
+                    {
+                        log<level::ERR>("No object path found");
+                    }
+                }
+
                 // Each record in the VPD becomes an interface and all
                 // keyword within the record are properties under that
                 // interface.
@@ -649,7 +825,6 @@
                 }
             }
         }
-
         if (item.value("inheritEI", true))
         {
             // Populate interfaces and properties that are common to every FRU
@@ -721,6 +896,13 @@
 
         // set the U-boot environment variable for device-tree
         setDevTreeEnv(imValStr);
+
+        // if system VPD has been restored at standby, update the EEPROM
+        for (const auto& item : updatedEeproms)
+        {
+            updateHardware(get<0>(item), get<1>(item), get<2>(item),
+                           get<3>(item));
+        }
     }
 
     // Notify PIM