Translating ME Health Sensor Platform Events to Redfish Events

Following code implements parsing logic for all ME Health Sensor Platform
Events. It is built in similar manner to existing implementation of
BIOS SEL events parsing and follows the same principles:
- try to parse event, store id and params as Redfish Event on success,
- if event is unparsable - store raw IPMI frame as generic SEL event.

As there exists many events which would share exactly the same parsing
logic, some higher level function (genericMessageHook) and some helper
functions were introduced. Idea of this is that for events which are
easily parsable (like mapping byte to concrete event) should be handled
by metadata-driven algorithm. For more complex events ability to specify
lower-level parser methods was introduced.

In general flow looks like the following:
1) event is recognized as originating from ME, me::messageHook is called
2) event comes from me::HealthSensor, health_event::messageHook is called
3) based on Platform Event offset byte either fw_status or smbus_failure
   event messageHook is called
4a) for smbus_failure : simple function called

.....

4b) for fw_status: map is used, deciding further processing based on
    event.eventData2
5) map[event.eventData2] contains : MessageId and instructions on further
   processing. It might be:
   a) empty - no extra processing,
   b) map - maps eventData3 to string value and adds it to MessageParams,
   b) function - specialized parsing routines for given eventData2.

Testing:
- injected all possible events with busctl call (faking ME)
- tested on actual platform with manually triggered events by actual ME

Signed-off-by: Agnieszka Szlendak <Agnieszka.Szlendak@intel.com>
Change-Id: Ib13e4ace10b6382ea0800d34241e98a73b6626b3
diff --git a/src/me_to_redfish_hooks.cpp b/src/me_to_redfish_hooks.cpp
new file mode 100644
index 0000000..fac2731
--- /dev/null
+++ b/src/me_to_redfish_hooks.cpp
@@ -0,0 +1,364 @@
+/*
+// 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 <boost/algorithm/string/join.hpp>
+#include <me_to_redfish_hooks.hpp>
+#include <phosphor-logging/log.hpp>
+#include <string_view>
+
+namespace intel_oem::ipmi::sel::redfish_hooks::me
+{
+namespace health_event
+{
+namespace smbus_failure
+{
+
+static bool messageHook(const SELData& selData, std::string& eventId,
+                        std::vector<std::string>& args)
+{
+    static const boost::container::flat_map<uint8_t, std::string> smlink = {
+        {0x01, "SmLink0/0B"},
+        {0x02, "SmLink1"},
+        {0x03, "SmLink2"},
+        {0x04, "SmLink3"},
+        {0x05, "SmLink4"}};
+
+    const auto errorDetails = selData.eventData3;
+    const auto faultySmlink = smlink.find(selData.eventData2);
+    if (faultySmlink == smlink.end())
+    {
+        return false;
+    }
+
+    eventId = "MeSmbusLinkFailure";
+
+    args.push_back(faultySmlink->second);
+    args.push_back(utils::toHex(errorDetails));
+
+    return true;
+}
+} // namespace smbus_failure
+
+namespace fw_status
+{
+static const boost::container::flat_map<uint8_t, std::string>
+    manufacturingError = {
+        {0x00, "Generic error"},
+        {0x01, "Wrong or missing VSCC table"},
+        {0x02, "Wrong sensor scanning period in PIA"},
+        {0x03, "Wrong device definition in PIA"},
+        {0x04, "Reserved (Wrong SMART/CLST configuration)"},
+        {0x05, "Intel ME FW configuration is inconsistent or out of range"},
+        {0x06, "Reserved"},
+        {0x07, "Intel ME FW configuration is corrupted"},
+        {0x08, "SMLink0/0B misconfiguration"}};
+
+static const boost::container::flat_map<uint8_t, std::string> peciOverDmiError =
+    {{0x01, "DRAM Init Done HECI message not received by Intel ME before EOP"},
+     {0x02, "System PCIe bus configuration not known or not valid on DID HECI "
+            "message arrival to Intel ME"},
+     {0x03, "PECI over DMI run-time failure"}};
+
+static const boost::container::flat_map<uint8_t, std::string>
+    mctpInterfaceError = {
+        {0x01, "No DID HECI message received before EOP"},
+        {0x02, "No MCTP_SET_BUS_OWNER HECI message received by Intel ME on EOP "
+               "arrival "
+               "to ME while MCTP stack is configured in Bus Owner Proxy mode"}};
+
+static const boost::container::flat_map<uint8_t, std::string>
+    unsupportedFeature = {{0x00, "Other Segment Defined Feature"},
+                          {0x01, "Fast NM limiting"},
+                          {0x02, "Volumetric Airflow and Outlet Temperature"},
+                          {0x03, "CUPS"},
+                          {0x04, "Thermal policies and Inlet Temperature"},
+                          {0x05, "Platform limiting with MICs"},
+                          {0x07, "Shared power supplies"},
+                          {0x08, "MIC Proxy"},
+                          {0x09, "Reset warning"},
+                          {0x0A, "PMBus Proxy"},
+                          {0x0B, "Always on"},
+                          {0x0C, "IPMI Intel ME FW update"},
+                          {0x0D, "MCTP bus owner"},
+                          {0x0E, "MCTP bus owner proxy"},
+                          {0x0F, "Dual BIOS"},
+                          {0x10, "Battery less"}};
+
+static const boost::container::flat_map<uint8_t, std::string> umaError = {
+    {0x00, "UMA Read integrity error. Checksum of data read from UMA differs "
+           "from expected one."},
+    {0x01, "UMA Read/Write timeout. Timeout occurred during copying data "
+           "from/to UMA."},
+    {0x02,
+     "UMA not granted. BIOS did not grant any UMA or DRAM INIT done message "
+     "was not received from BIOS before EOP. Intel ME FW goes to recovery."},
+    {0x03, "UMA size granted by BIOS differs from requested. ME FW goes to "
+           "recovery."}};
+
+static const boost::container::flat_map<uint8_t, std::string> pttHealthEvent = {
+    {0x00, "Intel PTT disabled (PTT region is not present)."},
+    {0x01, "Intel PTT downgrade (PTT data should be not available)."},
+    {0x02, "Intel PTT disabled (battery less configuration)."}};
+
+static const boost::container::flat_map<uint8_t, std::string>
+    bootGuardHealthEvent = {
+        {0x00, "Boot Guard flow error (possible reasons: verification timeout; "
+               "verification error; BIOS Protection error)."}};
+
+static const boost::container::flat_map<uint8_t, std::string> restrictedMode = {
+    {0x01, "Firmware entered restricted mode – UMA is not available. "
+           "Restricted features set."},
+    {0x02, "Firmware exited restricted mode."}};
+
+static const boost::container::flat_map<uint8_t, std::string>
+    multiPchModeMisconfig = {
+        {0x01, "BIOS did not set reset synchronization in multiPCH mode"},
+        {0x02,
+         "PMC indicates different non/legacy mode for the PCH than BMC set "
+         "on the GPIO"},
+        {0x03,
+         "Misconfiguration MPCH support enabled due to BTG support enabled"}};
+
+static const boost::container::flat_map<uint8_t, std::string>
+    flashVerificationError = {
+        {0x00, "OEM Public Key verification error"},
+        {0x01, "Flash Descriptor Region Manifest verification error"},
+        {0x02, "Soft Straps verification error"}};
+
+namespace autoconfiguration
+{
+
+bool messageHook(const SELData& selData, std::string& eventId,
+                 std::vector<std::string>& args)
+{
+    static const boost::container::flat_map<uint8_t, std::string> dcSource = {
+        {0b00, "BMC"}, {0b01, "PSU"}, {0b10, "On-board power sensor"}};
+
+    static const boost::container::flat_map<uint8_t, std::string>
+        chassisSource = {{0b00, "BMC"},
+                         {0b01, "PSU"},
+                         {0b10, "On-board power sensor"},
+                         {0b11, "Not supported"}};
+
+    static const boost::container::flat_map<uint8_t, std::string>
+        efficiencySource = {
+            {0b00, "BMC"}, {0b01, "PSU"}, {0b11, "Not supported"}};
+
+    static const boost::container::flat_map<uint8_t, std::string>
+        unmanagedSource = {{0b00, "BMC"}, {0b01, "Estimated"}};
+
+    static const boost::container::flat_map<uint8_t, std::string>
+        failureReason = {{0b00, "BMC discovery failure"},
+                         {0b01, "Insufficient factory configuration"},
+                         {0b10, "Unknown sensor type"},
+                         {0b11, "Other error encountered"}};
+
+    auto succeeded = selData.eventData3 >> 7 & 0b1;
+    if (succeeded)
+    {
+        eventId = "MeAutoConfigSuccess";
+
+        auto dc = dcSource.find(selData.eventData3 >> 5 & 0b11);
+        auto chassis = chassisSource.find(selData.eventData3 >> 3 & 0b11);
+        auto efficiency = efficiencySource.find(selData.eventData3 >> 1 & 0b11);
+        auto unmanaged = unmanagedSource.find(selData.eventData3 & 0b1);
+        if (dc == dcSource.end() || chassis == chassisSource.end() ||
+            efficiency == efficiencySource.end() ||
+            unmanaged == unmanagedSource.end())
+        {
+            return false;
+        }
+
+        args.push_back(dc->second);
+        args.push_back(chassis->second);
+        args.push_back(efficiency->second);
+        args.push_back(unmanaged->second);
+    }
+    else
+    {
+        eventId = "MeAutoConfigFailed";
+
+        const auto it = failureReason.find(selData.eventData3 >> 5 & 0b11);
+        if (it == failureReason.end())
+        {
+            return false;
+        }
+
+        args.push_back(it->second);
+    }
+
+    return true;
+}
+} // namespace autoconfiguration
+
+namespace factory_reset
+{
+bool messageHook(const SELData& selData, std::string& eventId,
+                 std::vector<std::string>& args)
+{
+    static const boost::container::flat_map<uint8_t, std::string>
+        restoreToFactoryPreset = {
+            {0x00,
+             "Flash file system error detected. Automatic restore to factory "
+             "presets has been triggered."},
+            {0x01, "Automatic restore to factory presets has been completed."},
+            {0x02,
+             "Restore to factory presets triggered by “Force ME Recovery” "
+             "IPMI command has been completed."},
+            {0x03,
+             "Restore to factory presets triggered by AC power cycle with "
+             "Recovery jumper asserted has been completed."}};
+
+    auto param = restoreToFactoryPreset.find(selData.eventData3);
+    if (param == restoreToFactoryPreset.end())
+    {
+        return false;
+    }
+
+    if (selData.eventData3 == 0x00)
+    {
+        eventId = "MeFactoryResetError";
+    }
+    else
+    {
+        eventId = "MeFactoryRestore";
+    }
+
+    args.push_back(param->second);
+    return true;
+}
+} // namespace factory_reset
+
+namespace flash_state
+{
+bool messageHook(const SELData& selData, std::string& eventId,
+                 std::vector<std::string>& args)
+{
+    static const boost::container::flat_map<uint8_t, std::string>
+        flashStateInformation = {
+            {0x00,
+             "Flash partition table, recovery image or factory presets image "
+             "corrupted"},
+            {0x01, "Flash erase limit has been reached"},
+            {0x02,
+             "Flash write limit has been reached. Writing to flash has been "
+             "disabled"},
+            {0x03, "Writing to the flash has been enabled"}};
+
+    auto param = flashStateInformation.find(selData.eventData3);
+    if (param == flashStateInformation.end())
+    {
+        return false;
+    }
+
+    if (selData.eventData3 == 0x03)
+    {
+        eventId = "MeFlashStateInformationWritingEnabled";
+    }
+    else
+    {
+        eventId = "MeFlashStateInformation";
+    }
+
+    args.push_back(param->second);
+    return true;
+}
+} // namespace flash_state
+
+static bool messageHook(const SELData& selData, std::string& eventId,
+                        std::vector<std::string>& args)
+{
+    static const boost::container::flat_map<
+        uint8_t,
+        std::pair<std::string, std::optional<std::variant<utils::ParserFunc,
+                                                          utils::MessageMap>>>>
+        eventMap = {
+            {0x00, {"MeRecoveryGpioForced", {}}},
+            {0x01, {"MeImageExecutionFailed", {}}},
+            {0x02, {"MeFlashEraseError", {}}},
+            {0x03, {{}, flash_state::messageHook}},
+            {0x04, {"MeInternalError", {}}},
+            {0x05, {"MeExceptionDuringShutdown", {}}},
+            {0x06, {"MeDirectFlashUpdateRequested", {}}},
+            {0x07, {"MeManufacturingError", manufacturingError}},
+            {0x08, {{}, factory_reset::messageHook}},
+            {0x09, {"MeFirmwareException", utils::logByteHex<2>}},
+            {0x0A, {"MeFlashWearOutWarning", utils::logByteDec<2>}},
+            {0x0D, {"MePeciOverDmiError", peciOverDmiError}},
+            {0x0E, {"MeMctpInterfaceError", mctpInterfaceError}},
+            {0x0F, {{}, autoconfiguration::messageHook}},
+            {0x10, {"MeUnsupportedFeature", unsupportedFeature}},
+            {0x12, {"MeCpuDebugCapabilityDisabled", {}}},
+            {0x13, {"MeUmaError", umaError}},
+            {0x16, {"MePttHealthEvent", pttHealthEvent}},
+            {0x17, {"MeBootGuardHealthEvent", bootGuardHealthEvent}},
+            {0x18, {"MeRestrictedMode", restrictedMode}},
+            {0x19, {"MeMultiPchModeMisconfig", multiPchModeMisconfig}},
+            {0x1A, {"MeFlashVerificationError", flashVerificationError}}};
+
+    return utils::genericMessageHook(eventMap, selData, eventId, args);
+}
+} // namespace fw_status
+
+static bool messageHook(const SELData& selData, std::string& eventId,
+                        std::vector<std::string>& args)
+{
+    const HealthEventType healthEventType =
+        static_cast<HealthEventType>(selData.offset);
+
+    switch (healthEventType)
+    {
+        case HealthEventType::FirmwareStatus:
+            return fw_status::messageHook(selData, eventId, args);
+            break;
+
+        case HealthEventType::SmbusLinkFailure:
+            return smbus_failure::messageHook(selData, eventId, args);
+            break;
+    }
+
+    return false;
+}
+} // namespace health_event
+
+/**
+ * @brief Main entry point for parsing ME IPMI Platform Events
+ *
+ * @brief selData - IPMI Platform Event structure
+ * @brief ipmiRaw - the same event in raw binary form
+ *
+ * @returns true if event was successfully parsed and consumed
+ */
+bool messageHook(const SELData& selData, const std::string& ipmiRaw)
+{
+    const EventSensor meSensor = static_cast<EventSensor>(selData.sensorNum);
+    std::string eventId;
+    std::vector<std::string> args;
+
+    switch (meSensor)
+    {
+        case EventSensor::MeFirmwareHealth:
+            if (health_event::messageHook(selData, eventId, args))
+            {
+                utils::storeRedfishEvent(ipmiRaw, eventId, args);
+                return true;
+            }
+            break;
+    }
+
+    return defaultMessageHook(ipmiRaw);
+}
+} // namespace intel_oem::ipmi::sel::redfish_hooks::me
\ No newline at end of file