Fix "get Device id" command

1. implement convertIntelVersion to get BMC version (Intel versioning)
2. add support for aux version
3. add product id support

Test:
1.tested on reference platform.
  product id is correct
2.ipmitool mc info.
  aux version field has value. BMC version is none-zero

Change-Id: I06c16563588f2b0461d48043191751d1a7806e98
Signed-off-by: Jia, chunhui <chunhui.jia@linux.intel.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ac3687f..93663c0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -90,7 +90,7 @@
 
 add_library (
     zinteloemcmds SHARED src/oemcommands.cpp src/sensorcommands.cpp
-    src/storagecommands.cpp src/multinodecommands.cpp src/firmware-update.cpp
+    src/storagecommands.cpp src/multinodecommands.cpp src/firmware-update.cpp src/appcommands.cpp
     src/smbioshandler.cpp src/smbiosmdrv2handler.cpp
     src/manufacturingcommands.cpp src/bmccontrolservices.cpp
     src/bridgingcommands.cpp src/ipmi_to_redfish_hooks.cpp
diff --git a/src/appcommands.cpp b/src/appcommands.cpp
new file mode 100644
index 0000000..39fc7d5
--- /dev/null
+++ b/src/appcommands.cpp
@@ -0,0 +1,375 @@
+/*

+// Copyright (c) 2019 Intel Corporation

+//

+// Licensed under the Apache License, Version 2.0 (the "License");

+// you may not use this file except in compliance with the License.

+// You may obtain a copy of the License at

+//

+//      http://www.apache.org/licenses/LICENSE-2.0

+//

+// Unless required by applicable law or agreed to in writing, software

+// distributed under the License is distributed on an "AS IS" BASIS,

+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+// See the License for the specific language governing permissions and

+// limitations under the License.

+*/

+#include "xyz/openbmc_project/Common/error.hpp"

+

+#include <fstream>

+#include <ipmid/api.hpp>

+#include <ipmid/utils.hpp>

+#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

+{

+

+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;

+

+constexpr auto bmc_state_interface = "xyz.openbmc_project.State.BMC";

+constexpr auto bmc_state_property = "CurrentBMCState";

+

+namespace

+{

+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()};

+

+    // Get the Inventory object implementing the BMC interface

+    ipmi::DbusObjectInfo bmcObject =

+        ipmi::getDbusObject(bus, bmc_state_interface);

+    auto variant =

+        ipmi::getDbusProperty(bus, bmcObject.second, bmcObject.first,

+                              bmc_state_interface, bmc_state_property);

+

+    return std::holds_alternative<std::string>(variant) &&

+           BMC::convertBMCStateFromString(std::get<std::string>(variant)) ==

+               BMC::BMCState::Ready;

+}

+

+bool getCurrentBmcStateWithFallback(const bool fallbackAvailability)

+{

+    try

+    {

+        return getCurrentBmcState();

+    }

+    catch (...)

+    {

+        // Nothing provided the BMC interface, therefore return whatever was

+        // configured as the default.

+        return fallbackAvailability;

+    }

+}

+/**

+ * @brief Returns the Version info from primary s/w object

+ *

+ * 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.

+ *

+ * @return On success returns the Version info from primary s/w object.

+ *

+ */

+std::string getActiveSoftwareVersionInfo()

+{

+    auto busp = getSdBus();

+

+    std::string revision{};

+    ipmi::ObjectTree objectTree;

+    try

+    {

+        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()));

+    }

+

+    auto objectFound = false;

+    for (auto& softObject : objectTree)

+    {

+        auto service =

+            ipmi::getService(*busp, redundancyIntf, softObject.first);

+        auto objValueTree =

+            ipmi::getManagedObjects(*busp, service, softwareRoot);

+

+        auto minPriority = 0xFF;

+        for (const auto& objIter : objValueTree)

+        {

+            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());

+            }

+        }

+    }

+

+    if (!objectFound)

+    {

+        Log::log<Log::level::ERR>("Could not found an BMC software Object");

+    }

+

+    return revision;

+}

+

+typedef struct

+{

+    std::string platform;

+    uint8_t major;

+    uint8_t minor;

+    uint32_t buildNo;

+    std::string openbmcHash;

+    std::string metaHash;

+    std::string buildType;

+} MetaRevision;

+

+// Support both 2 solutions:

+// 1.Current solution  2.7.0-dev-533-g14dc00e79-5e7d997

+//   openbmcTag  2.7.0-dev

+//   BuildNo     533

+//   openbmcHash g14dc00e79

+//   MetaHasg    5e7d997

+//

+// 2.New solution  whtref-0.1-45-023125-a1295e-release

+//   IdStr        whtref

+//   Major        0

+//   Minor        1

+//   buildNo      45

+//   openbmcHash  023125

+//   MetaHash     a1295e

+//   BuildType    release/CI/<devloperId>

+std::optional<MetaRevision> convertIntelVersion(std::string& s)

+{

+    std::smatch results;

+    MetaRevision rev;

+    std::regex pattern1("(\\d+?).(\\d+?).\\d+?-\\w*?-(\\d+?)-(\\w+?)-(\\w+?)");

+    constexpr size_t matchedPhosphor = 6;

+    if (std::regex_match(s, results, pattern1))

+    {

+        if (results.size() == matchedPhosphor)

+        {

+            rev.platform = "whtref";

+            rev.major = static_cast<uint8_t>(std::stoi(results[1]));

+            rev.minor = static_cast<uint8_t>(std::stoi(results[2]));

+            rev.buildNo = static_cast<uint32_t>(std::stoi(results[3]));

+            rev.openbmcHash = results[4];

+            rev.metaHash = results[5];

+            rev.buildType = "release";

+            std::string versionString =

+                rev.platform + ":" + std::to_string(rev.major) + ":" +

+                std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) +

+                ":" + rev.openbmcHash + ":" + rev.metaHash + ":" +

+                rev.buildType;

+            phosphor::logging::log<phosphor::logging::level::INFO>(

+                versionString.c_str());

+            return rev;

+        }

+    }

+    constexpr size_t matchedIntel = 8;

+    std::regex pattern2(

+        "(\\w+?)-(\\d+?).(\\d+?)-(\\d+?)-(\\w+?)-(\\w+?)-(\\w+?)");

+    if (std::regex_match(s, results, pattern2))

+    {

+        if (results.size() == matchedIntel)

+        {

+            rev.platform = results[1];

+            rev.major = static_cast<uint8_t>(std::stoi(results[2]));

+            rev.minor = static_cast<uint8_t>(std::stoi(results[3]));

+            rev.buildNo = static_cast<uint32_t>(std::stoi(results[4]));

+            rev.openbmcHash = results[5];

+            rev.metaHash = results[6];

+            rev.buildType = results[7];

+            std::string versionString =

+                rev.platform + ":" + std::to_string(rev.major) + ":" +

+                std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) +

+                ":" + rev.openbmcHash + ":" + rev.metaHash + ":" +

+                rev.buildType;

+            phosphor::logging::log<phosphor::logging::level::INFO>(

+                versionString.c_str());

+            return rev;

+        }

+    }

+

+    return std::nullopt;

+}

+} // namespace

+auto ipmiAppGetDeviceId() -> ipmi::RspType<uint8_t, // Device ID

+                                           uint8_t, // Device Revision

+                                           uint8_t, // Firmware Revision Major

+                                           uint8_t, // Firmware Revision minor

+                                           uint8_t, // IPMI version

+                                           uint8_t, // Additional device support

+                                           uint24_t, // MFG ID

+                                           uint16_t, // Product ID

+                                           uint32_t  // AUX info

+                                           >

+{

+    std::optional<MetaRevision> rev;

+    static struct

+    {

+        uint8_t id;

+        uint8_t revision;

+        uint8_t fw[2];

+        uint8_t ipmiVer;

+        uint8_t addnDevSupport;

+        uint24_t manufId;

+        uint16_t prodId;

+        uint32_t aux;

+    } devId;

+    static bool dev_id_initialized = false;

+    static bool defaultActivationSetting = true;

+    const char* filename = "/usr/share/ipmi-providers/dev_id.json";

+    constexpr auto ipmiDevIdStateShift = 7;

+    constexpr auto ipmiDevIdFw1Mask = ~(1 << ipmiDevIdStateShift);

+

+    if (!dev_id_initialized)

+    {

+        try

+        {

+            auto version = getActiveSoftwareVersionInfo();

+            rev = convertIntelVersion(version);

+        }

+        catch (const std::exception& e)

+        {

+            Log::log<Log::level::ERR>(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;

+            devId.aux = revision.buildNo;

+        }

+

+        // IPMI Spec version 2.0

+        devId.ipmiVer = 2;

+

+        std::ifstream devIdFile(filename);

+        if (devIdFile.is_open())

+        {

+            auto data = nlohmann::json::parse(devIdFile, nullptr, false);

+            if (!data.is_discarded())

+            {

+                devId.id = data.value("id", 0);

+                devId.revision = data.value("revision", 0);

+                devId.addnDevSupport = data.value("addn_dev_support", 0);

+                devId.manufId = data.value("manuf_id", 0);

+

+                try

+                {

+                    auto busp = getSdBus();

+                    const ipmi::DbusObjectInfo& object = ipmi::getDbusObject(

+                        *busp, "xyz.openbmc_project.Inventory.Item.Board",

+                        "/xyz/openbmc_project/inventory/system/board/",

+                        "Baseboard");

+                    const ipmi::Value& propValue = ipmi::getDbusProperty(

+                        *busp, object.second, object.first,

+                        "xyz.openbmc_project.Inventory.Item.Board",

+                        "ProductId");

+                    devId.prodId =

+                        static_cast<uint8_t>(std::get<uint64_t>(propValue));

+                }

+                catch (std::exception& e)

+                {

+                    devId.prodId = data.value("prod_id", 0);

+                }

+

+                // Set the availablitity of the BMC.

+                defaultActivationSetting = data.value("availability", true);

+

+                // Don't read the file every time if successful

+                dev_id_initialized = true;

+            }

+            else

+            {

+                Log::log<Log::level::ERR>("Device ID JSON parser failure");

+                return ipmi::responseUnspecifiedError();

+            }

+        }

+        else

+        {

+            Log::log<Log::level::ERR>("Device ID file not found");

+            return ipmi::responseUnspecifiedError();

+        }

+    }

+

+    // Set availability to the actual current BMC state

+    devId.fw[0] &= ipmiDevIdFw1Mask;

+    if (!getCurrentBmcStateWithFallback(defaultActivationSetting))

+    {

+        devId.fw[0] |= (1 << ipmiDevIdStateShift);

+    }

+

+    return ipmi::responseSuccess(

+        devId.id, devId.revision, devId.fw[0], devId.fw[1], 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>

+    ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnApp,

+                          ipmi::app::cmdGetDeviceId, ipmi::Privilege::User,

+                          ipmiAppGetDeviceId);

+    return;

+}

+

+} // namespace ipmi