Expand and update location code property on BUS

This commit expands and updates the required location code
poperty on BUS in case following condition holds true.

a) if FC or SE or both keyword
under VCEN record has been modified by VPD write
application for Motherboard FRU.

OR

b) if TM or SE or both keyword
under VSYS record has been modified by VPD write
application for Motherboard FRU.

Tested on simics.
-make bus call or use VPD tool application to update
keywords as mentioned above in (a) and (b).
-Introspect that motherboard object on bus.
-Check the value of interface "com.ibm.ipzvpd.Location",
property - "LocationCode"

To build the application
meson -Dibm-parser=enabled -Dvpd-manager=Enabled build
ninja -C build

Signed-off-by: Sunny Srivastava <sunnsr25@in.ibm.com>
Change-Id: Ic964da0f058153bdd43b94679bdfc5596d7f3861
diff --git a/const.hpp b/const.hpp
index 3d631ee..a7338d4 100644
--- a/const.hpp
+++ b/const.hpp
@@ -37,9 +37,8 @@
 static constexpr auto MB_DAY_END = 10;
 static constexpr auto MB_HOUR_END = 13;
 static constexpr auto MB_MIN_END = 16;
-
+static constexpr auto SYSTEM_OBJECT = "/system/chassis/motherboard";
 static constexpr auto LOCATION_CODE_INF = "com.ibm.ipzvpd.Location";
-
 constexpr int IPZ_DATA_START = 11;
 constexpr uint8_t KW_VAL_PAIR_START_TAG = 0x84;
 constexpr uint8_t RECORD_END_TAG = 0x78;
diff --git a/ibm_vpd_app.cpp b/ibm_vpd_app.cpp
index faec395..4100d83 100644
--- a/ibm_vpd_app.cpp
+++ b/ibm_vpd_app.cpp
@@ -20,36 +20,6 @@
 using namespace vpd::keyword::parser;
 using namespace vpdFormat;
 
-/** @brief Reads a property from the inventory manager given object path,
- * intreface and property.
- */
-static auto readBusProperty(const string& obj, const string& inf,
-                            const string& prop)
-{
-    string propVal{};
-    static constexpr auto OBJ_PREFIX = "/xyz/openbmc_project/inventory";
-    string object = OBJ_PREFIX + obj;
-    auto bus = sdbusplus::bus::new_default();
-    auto properties = bus.new_method_call(
-        "xyz.openbmc_project.Inventory.Manager", object.c_str(),
-        "org.freedesktop.DBus.Properties", "Get");
-    properties.append(inf);
-    properties.append(prop);
-    auto result = bus.call(properties);
-    if (!result.is_method_error())
-    {
-        variant<Binary> val;
-        result.read(val);
-
-        if (auto pVal = get_if<Binary>(&val))
-        {
-            propVal.assign(reinterpret_cast<const char*>(pVal->data()),
-                           pVal->size());
-        }
-    }
-    return propVal;
-}
-
 /**
  * @brief Expands location codes
  */
diff --git a/meson.build b/meson.build
index 2f0ec24..e72ab81 100644
--- a/meson.build
+++ b/meson.build
@@ -16,24 +16,25 @@
 compiler = meson.get_compiler('cpp')
 python = find_program('python3', required:true)
 
-if get_option('ibm-parser').enabled()
 compiler.has_header('CLI/CLI.hpp')
 compiler.has_header('nlohmann/json.hpp')
 configure_file(output: 'config.h',
                        configuration :{
                        'INVENTORY_JSON': '"'+get_option('INVENTORY_JSON')+'"',
                        'INVENTORY_PATH': '"'+get_option('INVENTORY_PATH')+'"',
-		       'IPZ_INTERFACE': '"'+get_option('IPZ_INTERFACE')+'"',
-		       'INVENTORY_MANAGER_SERVICE': '"'+get_option('INVENTORY_MANAGER_SERVICE')+'"',
+                       'IPZ_INTERFACE': '"'+get_option('IPZ_INTERFACE')+'"',
+                       'INVENTORY_MANAGER_SERVICE': '"'+get_option('INVENTORY_MANAGER_SERVICE')+'"',
                        'BUSNAME' : '"' + get_option('BUSNAME') + '"',
                        'OBJPATH' : '"' + get_option('OBJPATH') + '"',
                        'IFACE' : '"' + get_option('IFACE') + '"',
-		       'OBJECT_MAPPER_SERVICE' : '"'+get_option('OBJECT_MAPPER_SERVICE')+'"',
-		       'OBJECT_MAPPER_OBJECT' : '"'+get_option('OBJECT_MAPPER_OBJECT')+'"',
-		       'POWER_SUPPLY_TYPE_INTERFACE' : '"'+get_option('POWER_SUPPLY_TYPE_INTERFACE')+'"',
-		       'INVENTORY_MANAGER_CACHE' : '"'+get_option('INVENTORY_MANAGER_CACHE')+'"'
+                       'OBJECT_MAPPER_SERVICE' : '"'+get_option('OBJECT_MAPPER_SERVICE')+'"',
+                       'OBJECT_MAPPER_OBJECT' : '"'+get_option('OBJECT_MAPPER_OBJECT')+'"',
+                       'POWER_SUPPLY_TYPE_INTERFACE' : '"'+get_option('POWER_SUPPLY_TYPE_INTERFACE')+'"',
+                       'INVENTORY_MANAGER_CACHE' : '"'+get_option('INVENTORY_MANAGER_CACHE')+'"'
                        }
   )
+
+if get_option('ibm-parser').enabled()
         ibm_read_vpd_SOURCES = ['ibm_vpd_app.cpp',
                                 'ibm_vpd_type_check.cpp',
                                 'parser.cpp',
diff --git a/types.hpp b/types.hpp
index 09217af..2b6dd06 100644
--- a/types.hpp
+++ b/types.hpp
@@ -33,7 +33,9 @@
 using ObjectMap = std::map<Object, InterfaceMap>;
 
 using VPDfilepath = std::string;
-using FrusMap = std::unordered_map<Path, VPDfilepath>;
+using FruIsMotherboard = bool;
+using FrusMap =
+    std::unordered_map<Path, std::pair<VPDfilepath, FruIsMotherboard>>;
 
 using namespace std::string_literals;
 constexpr auto pimPath = "/xyz/openbmc_project/inventory";
diff --git a/utils.cpp b/utils.cpp
index 6f2bfe4..558e728 100644
--- a/utils.cpp
+++ b/utils.cpp
@@ -1,3 +1,5 @@
+#include "config.h"
+
 #include "utils.hpp"
 
 #include "defines.hpp"
@@ -116,5 +118,30 @@
         return string(kw.begin(), kw.end());
     }
 }
+
+string readBusProperty(const string& obj, const string& inf, const string& prop)
+{
+    std::string propVal{};
+    std::string object = INVENTORY_PATH + obj;
+    auto bus = sdbusplus::bus::new_default();
+    auto properties = bus.new_method_call(
+        "xyz.openbmc_project.Inventory.Manager", object.c_str(),
+        "org.freedesktop.DBus.Properties", "Get");
+    properties.append(inf);
+    properties.append(prop);
+    auto result = bus.call(properties);
+    if (!result.is_method_error())
+    {
+        variant<Binary> val;
+        result.read(val);
+
+        if (auto pVal = get_if<Binary>(&val))
+        {
+            propVal.assign(reinterpret_cast<const char*>(pVal->data()),
+                           pVal->size());
+        }
+    }
+    return propVal;
+}
 } // namespace vpd
 } // namespace openpower
diff --git a/utils.hpp b/utils.hpp
index fd12698..da6546c 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -48,5 +48,15 @@
  *  @param[in] encoding - required for kwd data
  */
 string encodeKeyword(const string& kw, const string& encoding);
+
+/** @brief Reads a property from the inventory manager given object path,
+ *         intreface and property.
+ *  @param[in] obj - object path
+ *  @param[in] inf - interface
+ *  @param[in] prop - property whose value is fetched
+ *  @return [out] - value of the property
+ */
+string readBusProperty(const string& obj, const string& inf,
+                       const string& prop);
 } // namespace vpd
 } // namespace openpower
diff --git a/vpd-manager/editor_impl.cpp b/vpd-manager/editor_impl.cpp
index 70eb453..d69d7fa 100644
--- a/vpd-manager/editor_impl.cpp
+++ b/vpd-manager/editor_impl.cpp
@@ -364,6 +364,73 @@
     }
 }
 
+void EditorImpl::expandLocationCode(const std::string& locationCodeType)
+{
+    std::string propertyFCorTM{};
+    std::string propertySE{};
+
+    if (locationCodeType == "fcs")
+    {
+        propertyFCorTM =
+            readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VCEN", "FC");
+        propertySE =
+            readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VCEN", "SE");
+    }
+    else if (locationCodeType == "mts")
+    {
+        propertyFCorTM =
+            readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VSYS", "TM");
+        propertySE =
+            readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VSYS", "SE");
+    }
+
+    const nlohmann::json& groupFRUS =
+        jsonFile["frus"].get_ref<const nlohmann::json::object_t&>();
+    for (const auto& itemFRUS : groupFRUS.items())
+    {
+        const std::vector<nlohmann::json>& groupEEPROM =
+            itemFRUS.value().get_ref<const nlohmann::json::array_t&>();
+        for (const auto& itemEEPROM : groupEEPROM)
+        {
+            // check if the given item implements location code interface
+            if (itemEEPROM["extraInterfaces"].find(LOCATION_CODE_INF) !=
+                itemEEPROM["extraInterfaces"].end())
+            {
+                const std::string& unexpandedLocationCode =
+                    itemEEPROM["extraInterfaces"][LOCATION_CODE_INF]
+                              ["LocationCode"]
+                                  .get_ref<const nlohmann::json::string_t&>();
+                std::size_t idx = unexpandedLocationCode.find(locationCodeType);
+                if (idx != std::string::npos)
+                {
+                    std::string expandedLoctionCode(unexpandedLocationCode);
+
+                    if (locationCodeType == "fcs")
+                    {
+                        expandedLoctionCode.replace(
+                            idx, 3,
+                            propertyFCorTM.substr(0, 4) + ".ND0." + propertySE);
+                    }
+                    else if (locationCodeType == "mts")
+                    {
+                        std::replace(propertyFCorTM.begin(),
+                                     propertyFCorTM.end(), '-', '.');
+                        expandedLoctionCode.replace(
+                            idx, 3, propertyFCorTM + "." + propertySE);
+                    }
+
+                    // update the DBUS interface
+                    makeDbusCall<std::string>(
+                        (INVENTORY_PATH +
+                         itemEEPROM["inventoryPath"]
+                             .get_ref<const nlohmann::json::string_t&>()),
+                        LOCATION_CODE_INF, "LocationCode", expandedLoctionCode);
+                }
+            }
+        }
+    }
+}
+
 void EditorImpl::updateKeyword(const Binary& kwdData)
 {
     vpdFileStream.open(vpdFilePath,
diff --git a/vpd-manager/editor_impl.hpp b/vpd-manager/editor_impl.hpp
index 8e113b1..ac8909c 100644
--- a/vpd-manager/editor_impl.hpp
+++ b/vpd-manager/editor_impl.hpp
@@ -65,6 +65,11 @@
      */
     void updateKeyword(const Binary& kwdData);
 
+    /** @brief Expands location code on DBUS
+     *  @param[in] locationCodeType - "fcs" or "mts"
+     */
+    void expandLocationCode(const std::string& locationCodeType);
+
   private:
     /** @brief read VTOC record from the vpd file
      */
diff --git a/vpd-manager/manager.cpp b/vpd-manager/manager.cpp
index 37a8055..61a392a 100644
--- a/vpd-manager/manager.cpp
+++ b/vpd-manager/manager.cpp
@@ -65,9 +65,16 @@
             itemFRUS.value().get_ref<const nlohmann::json::array_t&>();
         for (const auto& itemEEPROM : groupEEPROM)
         {
+            bool isMotherboard = false;
+            if (itemEEPROM["extraInterfaces"].find(
+                    "xyz.openbmc_project.Inventory.Item.Board.Motherboard") !=
+                itemEEPROM["extraInterfaces"].end())
+            {
+                isMotherboard = true;
+            }
             frus.emplace(itemEEPROM["inventoryPath"]
                              .get_ref<const nlohmann::json::string_t&>(),
-                         itemFRUS.key());
+                         std::make_pair(itemFRUS.key(), isMotherboard));
         }
     }
 }
@@ -83,12 +90,26 @@
             throw std::runtime_error("Inventory path not found");
         }
 
-        inventory::Path vpdFilePath = frus.find(path)->second;
+        inventory::Path vpdFilePath = frus.find(path)->second.first;
 
         // instantiate editor class to update the data
         EditorImpl edit(vpdFilePath, jsonFile, recordName, keyword);
         edit.updateKeyword(value);
 
+        // if it is a motehrboard FRU need to check for location expansion
+        if (frus.find(path)->second.second)
+        {
+            if (recordName == "VCEN" && (keyword == "FC" || keyword == "SE"))
+            {
+                edit.expandLocationCode("fcs");
+            }
+            else if (recordName == "VSYS" &&
+                     (keyword == "TM" || keyword == "SE"))
+            {
+                edit.expandLocationCode("mts");
+            }
+        }
+
         return;
     }
     catch (const std::exception& e)