Implement generic function to get active versions

Current logic to get the Active BMC firmware
version is not synchronizing with bmcweb code.
To get the active firmware version, we are using
functional association endpoints. Endpoints will
provide all the functional firmware objects and
we retrive version information depending on
given version purpose(component). This is the same
logic followed in redfish and with change, both
ipmi and redfish pulls version with same logic.

Tested:
 - Get device id command works fine.
 - Get OEM Device information commands works fine.

Change-Id: I16ac2c52a4fadbffdc8c870cd738683435d56b1f
Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com>
diff --git a/include/appcommands.hpp b/include/appcommands.hpp
index 95d262d..d5b00b9 100644
--- a/include/appcommands.hpp
+++ b/include/appcommands.hpp
@@ -31,6 +31,11 @@
 } MetaRevision;
 #pragma pack(pop)
 
-extern std::string getActiveSoftwareVersionInfo();
-extern std::optional<MetaRevision> convertIntelVersion(std::string& s);
-} // namespace ipmi
\ No newline at end of file
+static constexpr const char *versionPurposeBMC =
+    "xyz.openbmc_project.Software.Version.VersionPurpose.BMC";
+
+extern int getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx,
+                                        const std::string &reqVersionPurpose,
+                                        std::string &version);
+extern std::optional<MetaRevision> convertIntelVersion(std::string &s);
+} // namespace ipmi
diff --git a/src/appcommands.cpp b/src/appcommands.cpp
index 539d119..485422d 100644
--- a/src/appcommands.cpp
+++ b/src/appcommands.cpp
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 */
-#include "xyz/openbmc_project/Common/error.hpp"
+#include <byteswap.h>
 
 #include <appcommands.hpp>
 #include <fstream>
@@ -22,8 +22,6 @@
 #include <nlohmann/json.hpp>
 #include <phosphor-logging/log.hpp>
 #include <regex>
-#include <xyz/openbmc_project/Software/Activation/server.hpp>
-#include <xyz/openbmc_project/Software/Version/server.hpp>
 #include <xyz/openbmc_project/State/BMC/server.hpp>
 
 namespace ipmi
@@ -31,23 +29,20 @@
 
 static void registerAPPFunctions() __attribute__((constructor));
 
-namespace Log = phosphor::logging;
-namespace Error = sdbusplus::xyz::openbmc_project::Common::Error;
-using Version = sdbusplus::xyz::openbmc_project::Software::server::Version;
-using Activation =
-    sdbusplus::xyz::openbmc_project::Software::server::Activation;
-using BMC = sdbusplus::xyz::openbmc_project::State::server::BMC;
+static constexpr const char* bmcStateIntf = "xyz.openbmc_project.State.BMC";
+static constexpr const char* softwareVerIntf =
+    "xyz.openbmc_project.Software.Version";
+static constexpr const char* softwareActivationIntf =
+    "xyz.openbmc_project.Software.Activation";
+static constexpr const char* associationIntf =
+    "xyz.openbmc_project.Association";
+static constexpr const char* softwareFunctionalPath =
+    "/xyz/openbmc_project/software/functional";
 
+using BMC = sdbusplus::xyz::openbmc_project::State::server::BMC;
 constexpr auto bmc_state_interface = "xyz.openbmc_project.State.BMC";
 constexpr auto bmc_state_property = "CurrentBMCState";
 
-static constexpr auto redundancyIntf =
-    "xyz.openbmc_project.Software.RedundancyPriority";
-static constexpr auto versionIntf = "xyz.openbmc_project.Software.Version";
-static constexpr auto activationIntf =
-    "xyz.openbmc_project.Software.Activation";
-static constexpr auto softwareRoot = "/xyz/openbmc_project/software";
-
 bool getCurrentBmcState()
 {
     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
@@ -79,85 +74,80 @@
 }
 
 /**
- * @brief Returns the Version info from primary software object
+ * @brief Returns the functional firmware version information.
  *
- * Get the Version info from the active s/w object which is having high
- * "Priority" value(a smaller number is a higher priority) and "Purpose"
- * is "BMC" from the list of all s/w objects those are implementing
- * RedundancyPriority interface from the given softwareRoot path.
+ * It reads the active firmware versions by checking functional
+ * endpoints association and matching the input version purpose string.
+ * ctx[in]                - ipmi context.
+ * reqVersionPurpose[in]  - Version purpose which need to be read.
+ * version[out]           - Output Version string.
  *
- * @return On success returns the Version info from primary software object.
+ * @return Returns '0' on success and '-1' on failure.
  *
  */
-std::string getActiveSoftwareVersionInfo()
+int getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx,
+                                 const std::string& reqVersionPurpose,
+                                 std::string& version)
 {
-    auto busp = getSdBus();
-
-    std::string revision{};
-    ipmi::ObjectTree objectTree;
-    try
+    std::vector<std::string> activeEndPoints;
+    boost::system::error_code ec = ipmi::getDbusProperty(
+        ctx, ipmi::MAPPER_BUS_NAME, softwareFunctionalPath, associationIntf,
+        "endpoints", activeEndPoints);
+    if (ec)
     {
-        objectTree =
-            ipmi::getAllDbusObjects(*busp, softwareRoot, redundancyIntf);
-    }
-    catch (sdbusplus::exception::SdBusError& e)
-    {
-        Log::log<Log::level::ERR>("Failed to fetch redundancy object from dbus",
-                                  Log::entry("INTERFACE=%s", redundancyIntf),
-                                  Log::entry("ERRMSG=%s", e.what()));
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Failed to get Active firmware version endpoints.");
+        return -1;
     }
 
-    auto objectFound = false;
-    for (auto& softObject : objectTree)
+    for (auto& activeEndPoint : activeEndPoints)
     {
-        auto service =
-            ipmi::getService(*busp, redundancyIntf, softObject.first);
-        auto objValueTree =
-            ipmi::getManagedObjects(*busp, service, softwareRoot);
-
-        auto minPriority = 0xFF;
-        for (const auto& objIter : objValueTree)
+        std::string serviceName;
+        ec = ipmi::getService(ctx, softwareActivationIntf, activeEndPoint,
+                              serviceName);
+        if (ec)
         {
-            try
-            {
-                auto& intfMap = objIter.second;
-                auto& redundancyPriorityProps = intfMap.at(redundancyIntf);
-                auto& versionProps = intfMap.at(versionIntf);
-                auto& activationProps = intfMap.at(activationIntf);
-                auto priority =
-                    std::get<uint8_t>(redundancyPriorityProps.at("Priority"));
-                auto purpose =
-                    std::get<std::string>(versionProps.at("Purpose"));
-                auto activation =
-                    std::get<std::string>(activationProps.at("Activation"));
-                auto version =
-                    std::get<std::string>(versionProps.at("Version"));
-                if ((Version::convertVersionPurposeFromString(purpose) ==
-                     Version::VersionPurpose::BMC) &&
-                    (Activation::convertActivationsFromString(activation) ==
-                     Activation::Activations::Active))
-                {
-                    if (priority < minPriority)
-                    {
-                        minPriority = priority;
-                        objectFound = true;
-                        revision = std::move(version);
-                    }
-                }
-            }
-            catch (const std::exception& e)
-            {
-                Log::log<Log::level::ERR>(e.what());
-            }
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Failed to perform getService.",
+                phosphor::logging::entry("OBJPATH=%s", activeEndPoint.c_str()));
+            continue;
+        }
+
+        PropertyMap propMap;
+        ec = ipmi::getAllDbusProperties(ctx, serviceName, activeEndPoint,
+                                        softwareVerIntf, propMap);
+        if (ec)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Failed to perform GetAll on Version interface.",
+                phosphor::logging::entry("SERVICE=%s", serviceName.c_str()),
+                phosphor::logging::entry("PATH=%s", activeEndPoint.c_str()));
+            continue;
+        }
+
+        std::string* purposeProp =
+            std::get_if<std::string>(&propMap["Purpose"]);
+        std::string* versionProp =
+            std::get_if<std::string>(&propMap["Version"]);
+        if (!purposeProp || !versionProp)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Failed to get version or purpose property");
+            continue;
+        }
+
+        // Check for requested version information and return if found.
+        if (*purposeProp == reqVersionPurpose)
+        {
+            version = *versionProp;
+            return 0;
         }
     }
 
-    if (!objectFound)
-    {
-        Log::log<Log::level::ERR>("Could not find an BMC software object");
-    }
-
-    return revision;
+    phosphor::logging::log<phosphor::logging::level::INFO>(
+        "Failed to find version information.",
+        phosphor::logging::entry("PURPOSE=%s", reqVersionPurpose.c_str()));
+    return -1;
 }
 
 // Support both 2 solutions:
@@ -194,9 +184,9 @@
                 rev.platform + ":" + std::to_string(rev.major) + ":" +
                 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) +
                 ":" + rev.openbmcHash + ":" + rev.metaHash;
-            Log::log<Log::level::INFO>(
+            phosphor::logging::log<phosphor::logging::level::INFO>(
                 "Get BMC version",
-                Log::entry("VERSION=%s", versionString.c_str()));
+                phosphor::logging::entry("VERSION=%s", versionString.c_str()));
             return rev;
         }
     }
@@ -216,9 +206,9 @@
                 rev.platform + ":" + std::to_string(rev.major) + ":" +
                 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) +
                 ":" + rev.openbmcHash + ":" + rev.metaHash;
-            Log::log<Log::level::INFO>(
+            phosphor::logging::log<phosphor::logging::level::INFO>(
                 "Get BMC version",
-                Log::entry("VERSION=%s", versionString.c_str()));
+                phosphor::logging::entry("VERSION=%s", versionString.c_str()));
             return rev;
         }
     }
@@ -228,7 +218,8 @@
 
 RspType<uint8_t,  // Device ID
         uint8_t,  // Device Revision
-        uint8_t,  // Firmware Revision Major
+        uint7_t,  // Firmware Revision Major
+        bool,     // Device available(0=NormalMode,1=DeviceFirmware)
         uint8_t,  // Firmware Revision minor
         uint8_t,  // IPMI version
         uint8_t,  // Additional device support
@@ -236,13 +227,15 @@
         uint16_t, // Product ID
         uint32_t  // AUX info
         >
-    ipmiAppGetDeviceId()
+    ipmiAppGetDeviceId(ipmi::Context::ptr ctx)
 {
     static struct
     {
         uint8_t id;
         uint8_t revision;
-        uint8_t fw[2];
+        uint7_t fwMajor;
+        bool devBusy;
+        uint8_t fwMinor;
         uint8_t ipmiVer = 2;
         uint8_t addnDevSupport;
         uint24_t manufId;
@@ -254,50 +247,35 @@
     static bool defaultActivationSetting = false;
     const char* filename = "/usr/share/ipmi-providers/dev_id.json";
     const char* prodIdFilename = "/var/cache/private/prodID";
-    constexpr auto ipmiDevIdStateShift = 7;
-    constexpr auto ipmiDevIdFw1Mask = ~(1 << ipmiDevIdStateShift);
-    constexpr auto ipmiDevIdBusy = (1 << ipmiDevIdStateShift);
-
     if (!fwVerInitialized)
     {
-        std::optional<MetaRevision> rev;
-        try
+        std::string versionString;
+        if (!getActiveSoftwareVersionInfo(ctx, versionPurposeBMC,
+                                          versionString))
         {
-            auto version = getActiveSoftwareVersionInfo();
-            rev = convertIntelVersion(version);
-        }
-        catch (const std::exception& e)
-        {
-            Log::log<Log::level::ERR>("Failed to get active version info",
-                                      Log::entry("ERROR=%s", e.what()));
-        }
-
-        if (rev.has_value())
-        {
-            // bit7 identifies if the device is available
-            // 0=normal operation
-            // 1=device firmware, SDR update,
-            // or self-initialization in progress.
-            // The availability may change in run time, so mask here
-            // and initialize later.
-            MetaRevision revision = rev.value();
-            devId.fw[0] = revision.major & ipmiDevIdFw1Mask;
-
-            revision.minor = (revision.minor > 99 ? 99 : revision.minor);
-            devId.fw[1] = revision.minor % 10 + (revision.minor / 10) * 16;
-            try
+            std::optional<MetaRevision> rev =
+                convertIntelVersion(versionString);
+            if (rev.has_value())
             {
-                uint32_t hash = std::stoul(revision.metaHash, 0, 16);
-                hash = ((hash & 0xff000000) >> 24) |
-                       ((hash & 0x00FF0000) >> 8) | ((hash & 0x0000FF00) << 8) |
-                       ((hash & 0xFF) << 24);
-                devId.aux = (revision.buildNo & 0xFF) + (hash & 0xFFFFFF00);
-                fwVerInitialized = true;
-            }
-            catch (const std::exception& e)
-            {
-                Log::log<Log::level::ERR>("Failed to convert git hash",
-                                          Log::entry("ERROR=%s", e.what()));
+                MetaRevision revision = rev.value();
+                devId.fwMajor = static_cast<uint7_t>(revision.major);
+
+                revision.minor = (revision.minor > 99 ? 99 : revision.minor);
+                devId.fwMinor =
+                    revision.minor % 10 + (revision.minor / 10) * 16;
+                try
+                {
+                    uint32_t hash = std::stoul(revision.metaHash, 0, 16);
+                    hash = bswap_32(hash);
+                    devId.aux = (revision.buildNo & 0xFF) + (hash & 0xFFFFFF00);
+                    fwVerInitialized = true;
+                }
+                catch (const std::exception& e)
+                {
+                    phosphor::logging::log<phosphor::logging::level::ERR>(
+                        "Failed to convert git hash",
+                        phosphor::logging::entry("ERROR=%s", e.what()));
+                }
             }
         }
     }
@@ -317,13 +295,15 @@
             }
             else
             {
-                Log::log<Log::level::ERR>("Device ID JSON parser failure");
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "Device ID JSON parser failure");
                 return ipmi::responseUnspecifiedError();
             }
         }
         else
         {
-            Log::log<Log::level::ERR>("Device ID file not found");
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Device ID file not found");
             return ipmi::responseUnspecifiedError();
         }
 
@@ -349,21 +329,18 @@
         }
     }
 
-    // Set availability to the actual current BMC state
-    devId.fw[0] &= ipmiDevIdFw1Mask;
-    if (!getCurrentBmcStateWithFallback(defaultActivationSetting))
-    {
-        devId.fw[0] |= ipmiDevIdBusy;
-    }
+    // Set availability to the actual current BMC state.
+    // The availability may change in run time so don't cache.
+    bool bmcDevBusy = getCurrentBmcStateWithFallback(defaultActivationSetting);
 
-    return ipmi::responseSuccess(
-        devId.id, devId.revision, devId.fw[0], devId.fw[1], devId.ipmiVer,
-        devId.addnDevSupport, devId.manufId, devId.prodId, devId.aux);
+    return ipmi::responseSuccess(devId.id, devId.revision, devId.fwMajor,
+                                 bmcDevBusy, devId.fwMinor, devId.ipmiVer,
+                                 devId.addnDevSupport, devId.manufId,
+                                 devId.prodId, devId.aux);
 }
 
 static void registerAPPFunctions(void)
 {
-    Log::log<Log::level::INFO>("Registering App commands");
     // <Get Device ID>
     registerHandler(prioOemBase, netFnApp, app::cmdGetDeviceId, Privilege::User,
                     ipmiAppGetDeviceId);
diff --git a/src/oemcommands.cpp b/src/oemcommands.cpp
index 072a6c5..55f9568 100644
--- a/src/oemcommands.cpp
+++ b/src/oemcommands.cpp
@@ -295,21 +295,17 @@
     return IPMI_CC_OK;
 }
 
-bool getSwVerInfo(uint8_t& bmcMajor, uint8_t& bmcMinor, uint8_t& meMajor,
-                  uint8_t& meMinor)
+bool getSwVerInfo(ipmi::Context::ptr ctx, uint8_t& bmcMajor, uint8_t& bmcMinor,
+                  uint8_t& meMajor, uint8_t& meMinor)
 {
     // step 1 : get BMC Major and Minor numbers from its DBUS property
-    std::optional<MetaRevision> rev{};
-    try
-    {
-        std::string version = getActiveSoftwareVersionInfo();
-        rev = convertIntelVersion(version);
-    }
-    catch (const std::exception& e)
+    std::string bmcVersion;
+    if (getActiveSoftwareVersionInfo(ctx, versionPurposeBMC, bmcVersion))
     {
         return false;
     }
 
+    std::optional<MetaRevision> rev = convertIntelVersion(bmcVersion);
     if (rev.has_value())
     {
         MetaRevision revision = rev.value();
@@ -347,6 +343,9 @@
     }
     catch (sdbusplus::exception::SdBusError& e)
     {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Exception caught in ME version conversion",
+            phosphor::logging::entry("MSG=%s", e.what()));
         return false;
     }
     return true;
@@ -358,7 +357,8 @@
                             std::array<uint8_t, 2>, std::array<uint8_t, 2>,
                             std::array<uint8_t, 2>, std::array<uint8_t, 2>>,
                  std::tuple<uint8_t, std::array<uint8_t, 2>>>>
-    ipmiOEMGetDeviceInfo(uint8_t entityType, std::optional<uint8_t> countToRead,
+    ipmiOEMGetDeviceInfo(ipmi::Context::ptr ctx, uint8_t entityType,
+                         std::optional<uint8_t> countToRead,
                          std::optional<uint8_t> offset)
 {
     if (entityType > static_cast<uint8_t>(OEMDevEntityType::sdrVer))
@@ -430,7 +430,7 @@
             std::array<uint8_t, verLen> hsc2Buf = {0xff, 0xff};
             // data0/1: BMC version number; data6/7: ME version number
             // the others: HSC0/1/2 version number, not avaible.
-            if (true != getSwVerInfo(bmcBuf[0], bmcBuf[1], meBuf[0], meBuf[1]))
+            if (!getSwVerInfo(ctx, bmcBuf[0], bmcBuf[1], meBuf[0], meBuf[1]))
             {
                 return ipmi::responseUnspecifiedError();
             }