Add match threshold alarm signals for threshold events

Add SEL_LOGGER_MONITOR_THRESHOLD_ALARM_EVENTS option to enable
monitoring the alarm signals on sensor threshold interfaces:
https://gerrit.openbmc-project.xyz/39899

Tested:
Change the threshold to trigger the alarm signal:
busctl set-property xyz.openbmc_project.Hwmon-487368426.Hwmon1
/xyz/openbmc_project/sensors/temperature/cputemp
xyz.openbmc_project.Sensor.Threshold.Critical CriticalHigh d 30

trigger the event log:
1 |  Pre-Init  |0000000213| Temperature cputemp
| Upper Critical going high | Asserted
| Reading 35 > Threshold 30 degrees C

Note:
It needs to work with the following PR:
https://gerrit.openbmc-project.xyz/42212

Signed-off-by: George Hung <george.hung@quantatw.com>
Change-Id: I86a7061895ba082643d1f9e0222b52b1bd732083
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 779093b..86a85a3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,12 +32,20 @@
     OFF
 )
 
+option (
+    SEL_LOGGER_MONITOR_THRESHOLD_ALARM_EVENTS
+    "Enable SEL Logger to monitor threshold alarm signals and
+     automatically log SEL records for threshold sensor events"
+    OFF
+)
+
 target_compile_definitions (
     sel-logger PRIVATE
     $<$<BOOL:${SEL_LOGGER_MONITOR_THRESHOLD_EVENTS}>: -DSEL_LOGGER_MONITOR_THRESHOLD_EVENTS>
     $<$<BOOL:${REDFISH_LOG_MONITOR_PULSE_EVENTS}>: -DREDFISH_LOG_MONITOR_PULSE_EVENTS>
     $<$<BOOL:${SEL_LOGGER_MONITOR_WATCHDOG_EVENTS}>: -DSEL_LOGGER_MONITOR_WATCHDOG_EVENTS>
     $<$<BOOL:${SEL_LOGGER_SEND_TO_LOGGING_SERVICE}>: -DSEL_LOGGER_SEND_TO_LOGGING_SERVICE>
+    $<$<BOOL:${SEL_LOGGER_MONITOR_THRESHOLD_ALARM_EVENTS}>: -DSEL_LOGGER_MONITOR_THRESHOLD_ALARM_EVENTS>
 )
 
 target_include_directories (sel-logger PRIVATE ${CMAKE_SOURCE_DIR})
diff --git a/include/threshold_alarm_event_monitor.hpp b/include/threshold_alarm_event_monitor.hpp
new file mode 100644
index 0000000..9b942f2
--- /dev/null
+++ b/include/threshold_alarm_event_monitor.hpp
@@ -0,0 +1,261 @@
+/*
+// Copyright (c) 2021 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.
+*/
+
+#pragma once
+#include <boost/container/flat_map.hpp>
+#include <sel_logger.hpp>
+#include <sensorutils.hpp>
+
+#include <map>
+#include <string_view>
+#include <variant>
+
+using sdbusMatch = std::shared_ptr<sdbusplus::bus::match::match>;
+static sdbusMatch warningLowAssertedMatcher;
+static sdbusMatch warningLowDeassertedMatcher;
+static sdbusMatch warningHighAssertedMatcher;
+static sdbusMatch warningHighDeassertedMatcher;
+static sdbusMatch criticalLowAssertedMatcher;
+static sdbusMatch criticalLowDeassertedMatcher;
+static sdbusMatch criticalHighAssertedMatcher;
+static sdbusMatch criticalHighDeassertedMatcher;
+
+static boost::container::flat_map<std::string, sdbusMatch> matchers = {
+    {"WarningLowAlarmAsserted", warningLowAssertedMatcher},
+    {"WarningLowAlarmDeasserted", warningLowDeassertedMatcher},
+    {"WarningHighAlarmAsserted", warningHighAssertedMatcher},
+    {"WarningHighAlarmDeasserted", warningHighDeassertedMatcher},
+    {"CriticalLowAlarmAsserted", criticalLowAssertedMatcher},
+    {"CriticalLowAlarmDeasserted", criticalLowDeassertedMatcher},
+    {"CriticalHighAlarmAsserted", criticalHighAssertedMatcher},
+    {"CriticalHighAlarmDeasserted", criticalHighDeassertedMatcher}};
+
+void generateEvent(std::string signalName,
+                   std::shared_ptr<sdbusplus::asio::connection> conn,
+                   sdbusplus::message::message& msg)
+{
+    double assertValue;
+    try
+    {
+        msg.read(assertValue);
+    }
+    catch (sdbusplus::exception_t&)
+    {
+        std::cerr << "error getting assert signal data from " << msg.get_path()
+                  << "\n";
+        return;
+    }
+
+    std::string event;
+    std::string thresholdInterface;
+    std::string threshold;
+    std::string direction;
+    bool assert = false;
+    std::vector<uint8_t> eventData(selEvtDataMaxSize, selEvtDataUnspecified);
+    std::string redfishMessageID = "OpenBMC." + openBMCMessageRegistryVersion;
+
+    if (signalName == "WarningLowAlarmAsserted" ||
+        signalName == "WarningLowAlarmDeasserted")
+    {
+        event = "WarningLow";
+        thresholdInterface = "xyz.openbmc_project.Sensor.Threshold.Warning";
+        eventData[0] =
+            static_cast<uint8_t>(thresholdEventOffsets::lowerNonCritGoingLow);
+        threshold = "warning low";
+        if (signalName == "WarningLowAlarmAsserted")
+        {
+            assert = true;
+            direction = "low";
+            redfishMessageID += ".SensorThresholdWarningLowGoingLow";
+        }
+        else if (signalName == "WarningLowAlarmDeasserted")
+        {
+            direction = "high";
+            redfishMessageID += ".SensorThresholdWarningLowGoingHigh";
+        }
+    }
+    else if (signalName == "WarningHighAlarmAsserted" ||
+             signalName == "WarningHighAlarmDeasserted")
+    {
+        event = "WarningHigh";
+        thresholdInterface = "xyz.openbmc_project.Sensor.Threshold.Warning";
+        eventData[0] =
+            static_cast<uint8_t>(thresholdEventOffsets::upperNonCritGoingHigh);
+        threshold = "warning high";
+        if (signalName == "WarningHighAlarmAsserted")
+        {
+            assert = true;
+            direction = "high";
+            redfishMessageID += ".SensorThresholdWarningHighGoingHigh";
+        }
+        else if (signalName == "WarningHighAlarmDeasserted")
+        {
+            direction = "low";
+            redfishMessageID += ".SensorThresholdWarningHighGoingLow";
+        }
+    }
+    else if (signalName == "CriticalLowAlarmAsserted" ||
+             signalName == "CriticalLowAlarmDeasserted")
+    {
+        event = "CriticalLow";
+        thresholdInterface = "xyz.openbmc_project.Sensor.Threshold.Critical";
+        eventData[0] =
+            static_cast<uint8_t>(thresholdEventOffsets::lowerCritGoingLow);
+        threshold = "critical low";
+        if (signalName == "CriticalLowAlarmAsserted")
+        {
+            assert = true;
+            direction = "low";
+            redfishMessageID += ".SensorThresholdCriticalLowGoingLow";
+        }
+        else if (signalName == "CriticalLowAlarmDeasserted")
+        {
+            direction = "high";
+            redfishMessageID += ".SensorThresholdCriticalLowGoingHigh";
+        }
+    }
+    else if (signalName == "CriticalHighAlarmAsserted" ||
+             signalName == "CriticalHighAlarmDeasserted")
+    {
+        event = "CriticalHigh";
+        thresholdInterface = "xyz.openbmc_project.Sensor.Threshold.Critical";
+        eventData[0] =
+            static_cast<uint8_t>(thresholdEventOffsets::upperCritGoingHigh);
+        threshold = "critical high";
+        if (signalName == "CriticalHighAlarmAsserted")
+        {
+            assert = true;
+            direction = "high";
+            redfishMessageID += ".SensorThresholdCriticalHighGoingHigh";
+        }
+        else if (signalName == "CriticalHighAlarmDeasserted")
+        {
+            direction = "low";
+            redfishMessageID += ".SensorThresholdCriticalHighGoingLow";
+        }
+    }
+    // Indicate that bytes 2 and 3 are threshold sensor trigger values
+    eventData[0] |= thresholdEventDataTriggerReadingByte2 |
+                    thresholdEventDataTriggerReadingByte3;
+
+    // Get the sensor reading to put in the event data
+    sdbusplus::message::message getSensorValue =
+        conn->new_method_call(msg.get_sender(), msg.get_path(),
+                              "org.freedesktop.DBus.Properties", "GetAll");
+    getSensorValue.append("xyz.openbmc_project.Sensor.Value");
+    boost::container::flat_map<std::string, std::variant<double, int64_t>>
+        sensorValue;
+    try
+    {
+        sdbusplus::message::message getSensorValueResp =
+            conn->call(getSensorValue);
+        getSensorValueResp.read(sensorValue);
+    }
+    catch (sdbusplus::exception_t&)
+    {
+        std::cerr << "error getting sensor value from " << msg.get_path()
+                  << "\n";
+        return;
+    }
+    double max = 0;
+    auto findMax = sensorValue.find("MaxValue");
+    if (findMax != sensorValue.end())
+    {
+        max = std::visit(ipmi::VariantToDoubleVisitor(), findMax->second);
+    }
+    double min = 0;
+    auto findMin = sensorValue.find("MinValue");
+    if (findMin != sensorValue.end())
+    {
+        min = std::visit(ipmi::VariantToDoubleVisitor(), findMin->second);
+    }
+
+    try
+    {
+        eventData[1] = ipmi::getScaledIPMIValue(assertValue, max, min);
+    }
+    catch (const std::exception& e)
+    {
+        std::cerr << e.what();
+        eventData[1] = selEvtDataUnspecified;
+    }
+
+    // Get the threshold value to put in the event data
+    sdbusplus::message::message getThreshold =
+        conn->new_method_call(msg.get_sender(), msg.get_path(),
+                              "org.freedesktop.DBus.Properties", "Get");
+    getThreshold.append(thresholdInterface, event);
+    std::variant<double, int64_t> thresholdValue;
+    try
+    {
+        sdbusplus::message::message getThresholdResp = conn->call(getThreshold);
+        getThresholdResp.read(thresholdValue);
+    }
+    catch (sdbusplus::exception_t&)
+    {
+        std::cerr << "error getting sensor threshold from " << msg.get_path()
+                  << "\n";
+        return;
+    }
+    double thresholdVal =
+        std::visit(ipmi::VariantToDoubleVisitor(), thresholdValue);
+
+    double scale = 0;
+    auto findScale = sensorValue.find("Scale");
+    if (findScale != sensorValue.end())
+    {
+        scale = std::visit(ipmi::VariantToDoubleVisitor(), findScale->second);
+        thresholdVal *= std::pow(10, scale);
+    }
+    try
+    {
+        eventData[2] = ipmi::getScaledIPMIValue(thresholdVal, max, min);
+    }
+    catch (const std::exception& e)
+    {
+        std::cerr << e.what();
+        eventData[2] = selEvtDataUnspecified;
+    }
+
+    std::string_view sensorName(msg.get_path());
+    sensorName.remove_prefix(
+        std::min(sensorName.find_last_of("/") + 1, sensorName.size()));
+
+    std::string journalMsg(std::string(sensorName) + " sensor crossed a " +
+                           threshold + " threshold going " + direction +
+                           ". Reading=" + std::to_string(assertValue) +
+                           " Threshold=" + std::to_string(thresholdVal) + ".");
+
+    selAddSystemRecord(journalMsg, std::string(msg.get_path()), eventData,
+                       assert, selBMCGenID, "REDFISH_MESSAGE_ID=%s",
+                       redfishMessageID.c_str(),
+                       "REDFISH_MESSAGE_ARGS=%.*s,%f,%f", sensorName.length(),
+                       sensorName.data(), assertValue, thresholdVal);
+}
+
+inline static void startThresholdAlarmMonitor(
+    std::shared_ptr<sdbusplus::asio::connection> conn)
+{
+    for (auto iter = matchers.begin(); iter != matchers.end(); iter++)
+    {
+        iter->second = std::make_shared<sdbusplus::bus::match::match>(
+            static_cast<sdbusplus::bus::bus&>(*conn),
+            "type='signal',member=" + iter->first,
+            [conn, iter](sdbusplus::message::message& msg) {
+                generateEvent(iter->first, conn, msg);
+            });
+    }
+}
diff --git a/src/sel_logger.cpp b/src/sel_logger.cpp
index 99527b1..83d487a 100644
--- a/src/sel_logger.cpp
+++ b/src/sel_logger.cpp
@@ -24,6 +24,9 @@
 #include <sel_logger.hpp>
 #include <threshold_event_monitor.hpp>
 #include <watchdog_event_monitor.hpp>
+#ifdef SEL_LOGGER_MONITOR_THRESHOLD_ALARM_EVENTS
+#include <threshold_alarm_event_monitor.hpp>
+#endif
 
 #include <filesystem>
 #include <fstream>
@@ -248,6 +251,10 @@
     sdbusplus::bus::match::match watchdogEventMonitor =
         startWatchdogEventMonitor(conn);
 #endif
+
+#ifdef SEL_LOGGER_MONITOR_THRESHOLD_ALARM_EVENTS
+    startThresholdAlarmMonitor(conn);
+#endif
     io.run();
 
     return 0;