vpd-tool mfgClean: implement sync BIOS attributes

This commit implements --syncBiosAttributes feature in vpd-tool
--mfgClean. When --syncBiosAttributes is selected along with --mfgClean,
the VPD keywords which are used for backing up BIOS attributes are
updated with the BIOS attribute values read from BIOS Config Manager
service.

Test:
```
- Install BMC image on rainier_2s2u.
- Reboot and wait for BMC to reach Ready state.
- Using vpd-tool, check the value of all the System VPD keywords used
  for backing up BIOS attributes both on EEPROM and on D-Bus.
- Run vpd-tool --mfgClean --syncBiosAttributes/-s --yes
- Check return code
- Use pldmtool to check the current values of respective BIOS attributes
- Use vpd-tool, check the value of all the System VPD keywords used for
  backing up BIOS attributes, both on EEPROM and on D-Bus.
- The VPD values should correspond with the BIOS attribute values.
- For all other keywords for which --mfgClean applies, the value on
  EEPROM and D-Bus should be the default value from the respective
  backup_restore JSON file.
```

Change-Id: I57c5df6ac0b3c4c91533d1ef22e69efcd4c87162
Signed-off-by: Souvik Roy <souvikroyofficial10@gmail.com>
diff --git a/vpd-tool/include/tool_constants.hpp b/vpd-tool/include/tool_constants.hpp
index 7fe0190..dbc4eb2 100644
--- a/vpd-tool/include/tool_constants.hpp
+++ b/vpd-tool/include/tool_constants.hpp
@@ -46,5 +46,20 @@
 constexpr auto i2cDeviceInf =
     "xyz.openbmc_project.Inventory.Decorator.I2CDevice";
 constexpr auto vpdManagerProcessName = "vpd-manager";
+constexpr auto biosConfigMgrObjPath =
+    "/xyz/openbmc_project/bios_config/manager";
+constexpr auto biosConfigMgrInterface =
+    "xyz.openbmc_project.BIOSConfig.Manager";
+
+static constexpr auto VALUE_0 = 0;
+static constexpr auto VALUE_1 = 1;
+static constexpr auto VALUE_2 = 2;
+static constexpr auto VALUE_3 = 3;
+static constexpr auto VALUE_4 = 4;
+static constexpr auto VALUE_5 = 5;
+static constexpr auto VALUE_6 = 6;
+static constexpr auto VALUE_7 = 7;
+static constexpr auto VALUE_8 = 8;
+static constexpr auto VALUE_32 = 32;
 } // namespace constants
 } // namespace vpd
diff --git a/vpd-tool/include/tool_types.hpp b/vpd-tool/include/tool_types.hpp
index 86e942c..56088b1 100644
--- a/vpd-tool/include/tool_types.hpp
+++ b/vpd-tool/include/tool_types.hpp
@@ -4,6 +4,7 @@
 
 #include <cstdint>
 #include <tuple>
+#include <unordered_map>
 #include <variant>
 #include <vector>
 
@@ -82,5 +83,35 @@
     SkipCurrent
 };
 
+using BiosAttributeCurrentValue =
+    std::variant<std::monostate, int64_t, std::string>;
+using BiosAttributePendingValue = std::variant<int64_t, std::string>;
+using BiosGetAttrRetType = std::tuple<std::string, BiosAttributeCurrentValue,
+                                      BiosAttributePendingValue>;
+
+// VPD keyword to BIOS attribute map
+struct IpzKeyHash
+{
+    std::size_t operator()(const IpzType& i_key) const
+    {
+        return std::hash<std::string>()(std::get<0>(i_key)) ^ std::hash<std::string>()(std::get<1>(i_key));
+    }
+};
+
+struct IpzKeyEqual
+{
+    bool operator()(const IpzType& i_leftKey, const IpzType& i_rightKey) const
+    {
+        return std::get<0>(i_leftKey) == std::get<0>(i_rightKey) && std::get<1>(i_leftKey) == std::get<1>(i_rightKey);
+    }
+};
+
+// Bios attribute metadata container : {attribute name, number of bits, starting bit position, enabled value, disabled value}
+using BiosAttributeMetaData = std::tuple<std::string, uint8_t, std::optional<uint8_t>, std::optional<uint8_t>, std::optional<uint8_t>>;
+
+// IPZ keyword to BIOS attribute map
+//{Record, Keyword} -> {attribute name, number of bits in keyword, starting bit
+// position, enabled value, disabled value}
+using BiosAttributeKeywordMap = std::unordered_map<IpzType,std::vector<BiosAttributeMetaData>,IpzKeyHash,IpzKeyEqual>;
 } // namespace types
 } // namespace vpd
diff --git a/vpd-tool/include/tool_utils.hpp b/vpd-tool/include/tool_utils.hpp
index d4bde8a..16253ee 100644
--- a/vpd-tool/include/tool_utils.hpp
+++ b/vpd-tool/include/tool_utils.hpp
@@ -903,5 +903,98 @@
     return l_retVal;
 }
 
+/**
+ * @brief API to call "GetAttribute" method under BIOS Config Manager.
+ *
+ * The API reads the given attribute from BIOS Config Manager and returns a
+ * variant containing current value for that attribute if the value is found.
+ * API returns an empty variant of type BiosAttributeCurrentValue in case of any
+ * error.
+ *
+ * @param[in] i_attributeName - Attribute to be read.
+ *
+ * @return Tuple of PLDM attribute Type, current attribute value and pending
+ * attribute value.
+ */
+inline types::BiosAttributeCurrentValue biosGetAttributeMethodCall(
+    const std::string& i_attributeName) noexcept
+{
+    types::BiosGetAttrRetType l_attributeVal;
+
+    try
+    {
+        auto l_bus = sdbusplus::bus::new_default();
+        auto l_method = l_bus.new_method_call(
+            constants::biosConfigMgrService, constants::biosConfigMgrObjPath,
+            constants::biosConfigMgrInterface, "GetAttribute");
+        l_method.append(i_attributeName);
+
+        auto l_result = l_bus.call(l_method);
+        l_result.read(std::get<0>(l_attributeVal), std::get<1>(l_attributeVal),
+                      std::get<2>(l_attributeVal));
+    }
+    catch (const sdbusplus::exception::SdBusError& l_ex)
+    {
+        // TODO : enable logging when verbose is implemented
+        std::cerr << "Failed to read BIOS Attribute: " + i_attributeName +
+                         " due to error " + std::string(l_ex.what())
+                  << std::endl;
+    }
+
+    return std::get<1>(l_attributeVal);
+}
+
+/**
+ * @brief Converts string to lower case.
+ *
+ * @param [in,out] io_string - Input string.
+ *
+ * @throw std::terminate, std::bad_alloc
+ */
+inline void toLower(std::string& io_string)
+{
+    std::transform(io_string.begin(), io_string.end(), io_string.begin(),
+                   [](const unsigned char& l_char) {
+                       return std::tolower(l_char);
+                   });
+}
+
+/**
+ * @brief Converts an integral data value to a vector of bytes.
+ * The LSB of integer is copied to MSB of the vector.
+ *
+ * @param[in] i_integralData - Input integral data.
+ * @param[in] i_numBytesCopy - Number of bytes to copy.
+ *
+ * @return - On success, returns the Binary vector representation of the
+ * integral data, empty binary vector otherwise.
+ *
+ * @throw std::length_error
+ */
+template <typename T>
+    requires std::integral<T>
+inline types::BinaryVector convertIntegralTypeToBytes(
+    const T& i_integralData, size_t i_numBytesCopy = constants::VALUE_1)
+{
+    types::BinaryVector l_result;
+    constexpr auto l_byteMask{0xFF};
+
+    l_result.resize(i_numBytesCopy, constants::VALUE_0);
+
+    // sanitize number of bytes to copy
+    if (i_numBytesCopy > sizeof(T))
+    {
+        i_numBytesCopy = sizeof(T);
+    }
+
+    // LSB of source -> MSB of result
+    for (size_t l_byte = 0; l_byte < i_numBytesCopy; ++l_byte)
+    {
+        l_result[l_result.size() - (l_byte + constants::VALUE_1)] =
+            (i_integralData >> (l_byte * constants::VALUE_8)) & l_byteMask;
+    }
+    return l_result;
+}
+
 } // namespace utils
 } // namespace vpd
diff --git a/vpd-tool/include/vpd_tool.hpp b/vpd-tool/include/vpd_tool.hpp
index 0004949..ed2aa00 100644
--- a/vpd-tool/include/vpd_tool.hpp
+++ b/vpd-tool/include/vpd_tool.hpp
@@ -217,8 +217,19 @@
      * @throw std::terminate, std::bad_alloc
      */
     types::BinaryVector getVpdValueInBiosConfigManager(
-        [[maybe_unused]] const std::string& i_recordName,
-        [[maybe_unused]] const std::string& i_keywordName) const;
+        const std::string& i_recordName,
+        const std::string& i_keywordName) const;
+
+    /**
+     * @brief VPD keyword to BIOS attribute map
+     *
+     * This map specifies which VPD keyword is used to backup which BIOS
+     * attribute.
+     * {Record, Keyword} -> {attribute name, number of bits in keyword, starting
+     * bit position, enabled value, disabled value}
+     *
+     */
+    static const types::BiosAttributeKeywordMap m_biosAttributeVpdKeywordMap;
 
   public:
     /**
diff --git a/vpd-tool/src/vpd_tool.cpp b/vpd-tool/src/vpd_tool.cpp
index 9e2df42..7108942 100644
--- a/vpd-tool/src/vpd_tool.cpp
+++ b/vpd-tool/src/vpd_tool.cpp
@@ -12,6 +12,24 @@
 #include <tuple>
 namespace vpd
 {
+// {Record, Keyword} -> {attribute name, number of bits in keyword, starting bit
+// position, enabled value, disabled value}
+// Note: we do not care about min/max value for the BIOS attribute here.
+const types::BiosAttributeKeywordMap VpdTool::m_biosAttributeVpdKeywordMap = {
+    {{"UTIL", "D0"},
+     {{"hb_memory_mirror_mode", constants::VALUE_8, std::nullopt,
+       constants::VALUE_2, constants::VALUE_1}}},
+    {{"UTIL", "D1"},
+     {{"pvm_keep_and_clear", constants::VALUE_1, constants::VALUE_0,
+       constants::VALUE_1, constants::VALUE_0},
+      {"pvm_create_default_lpar", constants::VALUE_1, constants::VALUE_1,
+       constants::VALUE_1, constants::VALUE_0},
+      {"pvm_clear_nvram", constants::VALUE_1, constants::VALUE_2,
+       constants::VALUE_1, constants::VALUE_0}}},
+    {{"VSYS", "RG"},
+     {{"hb_field_core_override", constants::VALUE_32, std::nullopt,
+       std::nullopt, std::nullopt}}}};
+
 int VpdTool::readKeyword(
     const std::string& i_vpdPath, const std::string& i_recordName,
     const std::string& i_keywordName, const bool i_onHardware,
@@ -1428,13 +1446,93 @@
 }
 
 types::BinaryVector VpdTool::getVpdValueInBiosConfigManager(
-    [[maybe_unused]] const std::string& i_recordName,
-    [[maybe_unused]] const std::string& i_keywordName) const
+    const std::string& i_recordName, const std::string& i_keywordName) const
 {
     types::BinaryVector l_result;
-    // TODO: Use Record name, Keyword name to identify BIOS attribute.
-    //  Get BIOS attribute value from BIOS Config Manager.
-    //  Convert BIOS attribute value to VPD format value in binary.
+    const auto l_itrToBiosAttributeKeywordMap =
+        m_biosAttributeVpdKeywordMap.find(
+            types::IpzType(i_recordName, i_keywordName));
+
+    if (l_itrToBiosAttributeKeywordMap != m_biosAttributeVpdKeywordMap.end())
+    {
+        const auto& l_biosAttributeList =
+            l_itrToBiosAttributeKeywordMap->second;
+        for (const auto& l_biosAttributeEntry : l_biosAttributeList)
+        {
+            // get the attribute name
+            const std::string l_attributeName =
+                std::get<0>(l_biosAttributeEntry);
+
+            // get the number of bits used to store the value in VPD
+            const size_t l_numBitsKeyword = std::get<1>(l_biosAttributeEntry);
+
+            auto l_attrValueVariant =
+                utils::biosGetAttributeMethodCall(l_attributeName);
+
+            if (auto l_attrVal = std::get_if<int64_t>(&l_attrValueVariant))
+            {
+                // multiple bytes update
+
+                size_t l_numBytesKeyword =
+                    l_numBitsKeyword / constants::VALUE_8;
+
+                // convert to VPD format
+                l_result = utils::convertIntegralTypeToBytes(*l_attrVal,
+                                                             l_numBytesKeyword);
+            }
+            else if (auto l_attrVal =
+                         std::get_if<std::string>(&l_attrValueVariant))
+            {
+                utils::toLower(*l_attrVal);
+
+                // Since we are doing mfgClean, we do not
+                // care about reading the current VPD keyword value before
+                // writing to it.
+                if (l_numBitsKeyword == constants::VALUE_1)
+                {
+                    // single bit update.
+
+                    // get the bit position
+                    const uint8_t l_bitPosition =
+                        std::get<2>(l_biosAttributeEntry).has_value()
+                            ? std::get<2>(l_biosAttributeEntry).value()
+                            : constants::VALUE_0;
+
+                    l_result.resize(constants::VALUE_1, constants::VALUE_0);
+
+                    if (l_attrVal->compare("enabled") ==
+                        constants::STR_CMP_SUCCESS)
+                    {
+                        l_result.at(constants::VALUE_0) |=
+                            (constants::VALUE_1 << l_bitPosition);
+                    }
+                    else
+                    {
+                        l_result.at(constants::VALUE_0) &=
+                            ~(constants::VALUE_1 << l_bitPosition);
+                    }
+                }
+                else
+                {
+                    // single byte update
+                    const auto l_enabledValue =
+                        std::get<3>(l_biosAttributeEntry).has_value()
+                            ? std::get<3>(l_biosAttributeEntry).value()
+                            : constants::VALUE_1;
+
+                    const auto l_disabledValue =
+                        std::get<4>(l_biosAttributeEntry).has_value()
+                            ? std::get<4>(l_biosAttributeEntry).value()
+                            : constants::VALUE_0;
+
+                    l_result.emplace_back((l_attrVal->compare("enabled") ==
+                                           constants::STR_CMP_SUCCESS)
+                                              ? l_enabledValue
+                                              : l_disabledValue);
+                }
+            }
+        } // BIOS attribute loop end
+    }
     return l_result;
 }
 } // namespace vpd