support multiple device trees in the same BMC image

This commit checks for current system type and if it is changed from last boot,
it loads new/appropriate device tree and reboot the system.

Tested-
=========================================
Test Case 1: first boot when env not defined

triggerred system vpd collection-
DBG: systemType is: 50001000
DBG: newDeviceTree: fw_dt2
U-Boot environment is not set. Updating...
DBG: rebooting...
root@rainier:/tmp# Connection to rain4bmc closed by remote host.
Connection to rain4bmc closed.
========================================
Test case 2-
triggerred system vpd collection-
DBG: systemType is: 50001001
DBG: newDeviceTree: fw_dt1
U-Boot environment is not updated. Updating...
DBG: rebooting...
root@rainier:/tmp# Connection to rain4bmc closed by remote host.
Connection to rain4bmc closed.

alpana07> ssh -k root@rain4bmc
root@rain4bmc's password:
printenv
...
bootside=b
devTree=fw_dt1
root@rainier:~#
=========================================
Test case 3-
triggerred system vpd collection again-
DBG: systemType is: 50001001
DBG: newDeviceTree: fw_dt1
U-Boot environment is Updated.

root@rainier:~# fw_printenv
...
bootside=b
devTree=fw_dt1
=========================================

Change-Id: I28bce8ced4a970e1818b4f4f613bd062647a4d3a
Signed-off-by: Alpana Kumari <alpankum@in.ibm.com>
diff --git a/ibm_vpd_app.cpp b/ibm_vpd_app.cpp
index 25b77f1..48b930c 100644
--- a/ibm_vpd_app.cpp
+++ b/ibm_vpd_app.cpp
@@ -11,6 +11,7 @@
 
 #include <CLI/CLI.hpp>
 #include <algorithm>
+#include <cstdarg>
 #include <exception>
 #include <filesystem>
 #include <fstream>
@@ -30,6 +31,10 @@
 using namespace openpower::vpd::memory::parser;
 using namespace openpower::vpd::parser::interface;
 
+static const deviceTreeMap deviceTreeSystemTypeMap = {
+    {RAINIER_2U, "conf@aspeed-bmc-ibm-rainier-2u.dtb"},
+    {RAINIER_4U, "conf@aspeed-bmc-ibm-rainier-4u.dtb"}};
+
 /**
  * @brief Expands location codes
  */
@@ -300,6 +305,118 @@
     return objects;
 }
 
+/* It does nothing. Just an empty function to return null
+ * at the end of variadic template args
+ */
+string getCommand()
+{
+    return "";
+}
+
+/* This function to arrange all arguments to make command
+ */
+template <typename T, typename... Types>
+string getCommand(T arg1, Types... args)
+{
+    string cmd = " " + arg1 + getCommand(args...);
+
+    return cmd;
+}
+
+/* This API takes arguments and run that command
+ * returns output of that command
+ */
+template <typename T, typename... Types>
+static vector<string> executeCmd(T& path, Types... args)
+{
+    vector<string> stdOutput;
+    array<char, 128> buffer;
+
+    string cmd = path + getCommand(args...);
+
+    unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
+    if (!pipe)
+    {
+        throw runtime_error("popen() failed!");
+    }
+    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
+    {
+        stdOutput.emplace_back(buffer.data());
+    }
+
+    return stdOutput;
+}
+
+/**
+ * @brief This API executes command to set environment variable
+ *        And then reboot the system
+ * @param[in] key   -env key to set new value
+ * @param[in] value -value to set.
+ */
+void setEnvAndReboot(const string& key, const string& value)
+{
+    // set env and reboot and break.
+    executeCmd("/sbin/fw_setenv", key, value);
+    // make dbus call to reboot
+    auto bus = sdbusplus::bus::new_default_system();
+    auto method = bus.new_method_call(
+        "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
+        "org.freedesktop.systemd1.Manager", "Reboot");
+    bus.call_noreply(method);
+}
+
+/*
+ * @brief This API checks for env var fitconfig.
+ *        If not initialised OR updated as per the current system type,
+ *        update this env var and reboot the system.
+ *
+ * @param[in] systemType IM kwd in vpd tells about which system type it is.
+ * */
+void setDevTreeEnv(const string& systemType)
+{
+    string newDeviceTree;
+
+    if (deviceTreeSystemTypeMap.find(systemType) !=
+        deviceTreeSystemTypeMap.end())
+    {
+        newDeviceTree = deviceTreeSystemTypeMap.at(systemType);
+    }
+
+    string readVarValue;
+    bool envVarFound = false;
+
+    vector<string> output = executeCmd("/sbin/fw_printenv");
+    for (const auto& entry : output)
+    {
+        size_t pos = entry.find("=");
+        string key = entry.substr(0, pos);
+        if (key != "fitconfig")
+        {
+            continue;
+        }
+
+        envVarFound = true;
+        if (pos + 1 < entry.size())
+        {
+            readVarValue = entry.substr(pos + 1);
+            if (readVarValue.find(newDeviceTree) != string::npos)
+            {
+                // fitconfig is Updated. No action needed
+                break;
+            }
+        }
+        // set env and reboot and break.
+        setEnvAndReboot(key, newDeviceTree);
+        exit(0);
+    }
+
+    // check If env var Not found
+    if (!envVarFound)
+    {
+        setEnvAndReboot("fitconfig", newDeviceTree);
+    }
+}
+
 /**
  * @brief Populate Dbus.
  * This method invokes all the populateInterface functions
@@ -312,7 +429,7 @@
  */
 template <typename T>
 static void populateDbus(const T& vpdMap, nlohmann::json& js,
-                         const string& filePath) //, const string &preIntrStr) {
+                         const string& filePath)
 {
     inventory::InterfaceMap interfaces;
     inventory::ObjectMap objects;
@@ -412,12 +529,12 @@
         }
         string imValStr = oss.str();
 
-        if (imValStr == SYSTEM_4U) // 4U
+        if (imValStr == RAINIER_4U) // 4U
         {
             target = INVENTORY_JSON_4U;
         }
 
-        else if (imValStr == SYSTEM_2U) // 2U
+        else if (imValStr == RAINIER_2U) // 2U
         {
             target = INVENTORY_JSON_2U;
         }
@@ -434,6 +551,9 @@
 
         inventory::ObjectMap primeObject = primeInventory(js, vpdMap);
         objects.insert(primeObject.begin(), primeObject.end());
+
+        // set the U-boot environment variable for device-tree
+        setDevTreeEnv(imValStr);
     }
 
     // Notify PIM
@@ -468,9 +588,7 @@
         }
 
         Binary vpdVector = getVpdDataInVector(js, file);
-
-        ParserInterface* parser =
-            ParserFactory::getParser(std::move(vpdVector));
+        ParserInterface* parser = ParserFactory::getParser(move(vpdVector));
 
         variant<KeywordVpdMap, Store> parseResult;
         parseResult = parser->parse();