OEM command SendFwUpdateStatus

It is used by BIOS/ME to report firmware update status.
Orignal implement uses OEM SEL. In openbmc, OEM SEL is no longer
supported. We use redfish visiable logging instead.

Tested:
1.ipmitool xxx raw 0x30 0x44 0x0 0x10 0x1 0x2 0x30 0x31 0x32 0x33
2. check 'journal -ef -overbose'
expect message with tag 'REDFISH_MESSAGE_ID' is observed
3. check redfish interface
/redfish/v1/Systems/system/LogServices/EventLog/Entries/
expect entries match what we create

Change-Id: I8746e9250bcfb4d8c4a905a1ccaedcc7efab2f51
Signed-off-by: Jia, chunhui <chunhui.jia@linux.intel.com>
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/include/oemcommands.hpp b/include/oemcommands.hpp
index cd51038..3255da1 100644
--- a/include/oemcommands.hpp
+++ b/include/oemcommands.hpp
@@ -22,6 +22,7 @@
     cmdGetOEMDeviceInfo = 0x27,
     cmdGetAICSlotFRUIDSlotPosRecords = 0x31,
     cmdSetSystemGUID = 0x41,
+    cmdSendEmbeddedFWUpdStatus = 0x44,
     cmdSetPowerRestoreDelay = 0x54,
     cmdGetPowerRestoreDelay = 0x55,
     cmdSetShutdownPolicy = 0x60,
@@ -143,6 +144,12 @@
 // 3: host serial port 1 and 2 high speed
 static constexpr const uint8_t HostSerialCfgParamMax = 3;
 
+static constexpr const uint8_t selEvtTargetMask = 0xF0;
+static constexpr const uint8_t selEvtTargetShift = 4;
+
+static constexpr const uint8_t targetInstanceMask = 0x0E;
+static constexpr const uint8_t targetInstanceShift = 1;
+
 enum class IPMINetfnIntelOEMAppCmd
 {
     mdrStatus = 0x20,
@@ -172,6 +179,14 @@
     sdrVer,
 };
 
+enum class FWUpdateTarget : uint8_t
+{
+    targetBMC = 0x0,
+    targetBIOS = 0x1,
+    targetME = 0x2,
+    targetOEMEWS = 0x4,
+};
+
 #pragma pack(push, 1)
 struct GUIDData
 {
@@ -308,4 +323,4 @@
     pwmOffset = 0x2,
     maxPwm = 0x3,
     cfm = 0x4
-};
\ No newline at end of file
+};
diff --git a/src/oemcommands.cpp b/src/oemcommands.cpp
index dfd4985..2f46f13 100644
--- a/src/oemcommands.cpp
+++ b/src/oemcommands.cpp
@@ -18,6 +18,7 @@
 #include "xyz/openbmc_project/Led/Physical/server.hpp"
 
 #include <ipmid/api.h>
+#include <systemd/sd-journal.h>
 
 #include <array>
 #include <boost/container/flat_map.hpp>
@@ -25,6 +26,8 @@
 #include <boost/process/io.hpp>
 #include <commandutils.hpp>
 #include <iostream>
+#include <ipmid/api.hpp>
+#include <ipmid/registration.hpp>
 #include <ipmid/utils.hpp>
 #include <oemcommands.hpp>
 #include <phosphor-logging/log.hpp>
@@ -306,6 +309,103 @@
     return IPMI_CC_OK;
 }
 
+static uint8_t bcdToDec(uint8_t val)
+{
+    return ((val / 16 * 10) + (val % 16));
+}
+
+// Allows an update utility or system BIOS to send the status of an embedded
+// firmware update attempt to the BMC. After received, BMC will create a logging
+// record.
+ipmi::RspType<> ipmiOEMSendEmbeddedFwUpdStatus(uint8_t status, uint8_t target,
+                                               uint8_t majorRevision,
+                                               uint8_t minorRevision,
+                                               uint32_t auxInfo)
+{
+    std::string firmware;
+    target = (target & selEvtTargetMask) >> selEvtTargetShift;
+
+    /* make sure the status is 0, 1, or 2 as per the spec */
+    if (status > 2)
+    {
+        return ipmi::response(ipmi::ccInvalidFieldRequest);
+    }
+    /*orignal OEM command is to record OEM SEL.
+    But openbmc does not support OEM SEL, so we redirect it to redfish event
+    logging. */
+    std::string buildInfo;
+    std::string action;
+    switch (FWUpdateTarget(target))
+    {
+        case FWUpdateTarget::targetBMC:
+            firmware = "BMC";
+            buildInfo = " major: " + std::to_string(majorRevision) +
+                        " minor: " +
+                        std::to_string(bcdToDec(minorRevision)) + // BCD encoded
+                        " BuildID: " + std::to_string(auxInfo);
+            buildInfo += std::to_string(auxInfo);
+            break;
+        case FWUpdateTarget::targetBIOS:
+            firmware = "BIOS";
+            buildInfo =
+                " major: " +
+                std::to_string(bcdToDec(majorRevision)) + // BCD encoded
+                " minor: " +
+                std::to_string(bcdToDec(minorRevision)) + // BCD encoded
+                " ReleaseNumber: " +                      // ASCII encoded
+                std::to_string(static_cast<uint8_t>(auxInfo >> 0) - '0') +
+                std::to_string(static_cast<uint8_t>(auxInfo >> 8) - '0') +
+                std::to_string(static_cast<uint8_t>(auxInfo >> 16) - '0') +
+                std::to_string(static_cast<uint8_t>(auxInfo >> 24) - '0');
+            break;
+        case FWUpdateTarget::targetME:
+            firmware = "ME";
+            buildInfo =
+                " major: " + std::to_string(majorRevision) + " minor1: " +
+                std::to_string(bcdToDec(minorRevision)) + // BCD encoded
+                " minor2: " +
+                std::to_string(bcdToDec(static_cast<uint8_t>(auxInfo >> 0))) +
+                " build1: " +
+                std::to_string(bcdToDec(static_cast<uint8_t>(auxInfo >> 8))) +
+                " build2: " +
+                std::to_string(bcdToDec(static_cast<uint8_t>(auxInfo >> 16)));
+            break;
+        case FWUpdateTarget::targetOEMEWS:
+            firmware = "EWS";
+            buildInfo = " major: " + std::to_string(majorRevision) +
+                        " minor: " +
+                        std::to_string(bcdToDec(minorRevision)) + // BCD encoded
+                        " BuildID: " + std::to_string(auxInfo);
+            break;
+    }
+
+    switch (status)
+    {
+        case 0x0:
+            action = "update started";
+            break;
+        case 0x1:
+            action = "update completed successfully";
+            break;
+        case 0x2:
+            action = "update failure";
+            break;
+        default:
+            action = "unknown";
+            break;
+    }
+
+    std::string message(
+        "[firmware update] " + firmware + " instance: " +
+        std::to_string((target & targetInstanceMask) >> targetInstanceShift) +
+        " status: <" + action + ">" + buildInfo);
+    static constexpr const char* redfishMsgId = "FirmwareUpdate";
+
+    sd_journal_send("MESSAGE=%s", message.c_str(), "PRIORITY=%i", LOG_INFO,
+                    "REDFISH_MESSAGE_ID=%s", redfishMsgId, NULL);
+    return ipmi::responseSuccess();
+}
+
 ipmi_ret_t ipmiOEMSetPowerRestoreDelay(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
                                        ipmi_request_t request,
                                        ipmi_response_t response,
@@ -1132,6 +1232,13 @@
         static_cast<ipmi_cmd_t>(
             IPMINetfnIntelOEMGeneralCmd::cmdGetAICSlotFRUIDSlotPosRecords),
         NULL, ipmiOEMGetAICFRU, PRIVILEGE_USER);
+
+    ipmi::registerHandler(
+        ipmi::prioOpenBmcBase, ipmi::netFnOemOne,
+        static_cast<ipmi::Cmd>(
+            IPMINetfnIntelOEMGeneralCmd::cmdSendEmbeddedFWUpdStatus),
+        ipmi::Privilege::Operator, ipmiOEMSendEmbeddedFwUpdStatus);
+
     ipmiPrintAndRegister(
         netfnIntcOEMGeneral,
         static_cast<ipmi_cmd_t>(