dbus-sdr: Add hybrid sensors stack option

Because the dynamic sensor stack doesn't support non-threshold sensors,
this patch gives way for retrieving non-threshold sensors by walking
through the sensor.yaml. However, this patch filters out any threshold
sensors written in the sensor yaml file because the current dbus-sdr
already supports them.

The sensor stack has both dynamic and static in this patch, so this new
feature is named hybrid sensor stack.

Tested: Try the SDR get command
0xF9:
  entityID: 0x21
  entityInstance: 0
  interfaces:
    xyz.openbmc_project.State.Watchdog:
      ExpireAction:
        Offsets:
          0x00:
            assert: xyz.openbmc_project.State.Watchdog.Action.None
            type: string
          0x01:
            assert: xyz.openbmc_project.State.Watchdog.Action.HardReset
            type: string
          0x02:
            assert: xyz.openbmc_project.State.Watchdog.Action.PowerOff
            type: string
          0x03:
            assert: xyz.openbmc_project.State.Watchdog.Action.PowerCycle
            type: string
  mutability: Mutability::Read
  path: /xyz/openbmc_project/watchdog/host0
  readingType: assertion
  sensorNamePattern: nameLeaf
  sensorReadingType: 0x6F
  sensorType: 0x23
  serviceInterface: org.freedesktop.DBus.Properties

$ ipmitool sdr get host0
Sensor ID              : host0 (0xcd)
 Entity ID             : 33.0 (System Management Software)
 Sensor Type (Discrete): Watchdog2 (0x23)
 Sensor Reading        : 0h
 Event Message Control : Per-threshold
 States Asserted       : Watchdog2
                         [Hard reset]
 Event Status          : Event Messages Disabled
 Assertion Events      : Watchdog2
                         [Hard reset]
 Event Enable          : Event Messages Disabled
 Assertions Enabled    : Watchdog2
                         [Timer expired]
                         [Hard reset]
                         [Power down]
                         [Power cycle]
 OEM                   : 0

Signed-off-by: Scron Chang <Scron.Chang@quantatw.com>
Change-Id: I1ac16f483f2f725077de9c15595195b848a224ab
diff --git a/Makefile.am b/Makefile.am
index fbeb379..3e25794 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -168,6 +168,9 @@
 providers_LTLIBRARIES += libdynamiccmds.la
 libdynamiccmds_la_LIBADD = \
 	libipmid/libipmid.la
+if FEATURE_HYBRID_SENSORS
+libdynamiccmds_la_LIBADD += libipmi20.la
+endif
 libdynamiccmds_la_SOURCES = \
 	dbus-sdr/sensorcommands.cpp \
 	dbus-sdr/storagecommands.cpp \
diff --git a/configure.ac b/configure.ac
index dcf5e79..a7586b7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -296,6 +296,24 @@
       )
 AM_CONDITIONAL([FEATURE_DYNAMIC_SENSORS], [test x$dynamic_sensors = xtrue])
 
+# hybrid sensors stack is disabled by default; offer a way to enable it
+AC_ARG_ENABLE([hybrid-sensors],
+    [ --enable-hybrid-sensors   Enable/disable Hybrid Sensors stack],
+    [case "${enableval}" in
+      yes) hybrid_sensors=true ;;
+      no) hybrid_sensors=false ;;
+      *) AC_MSG_ERROR([bad value ${enableval} for --enable-hybrid-sensors]) ;;
+      esac],[hybrid-sensors=false]
+      )
+AM_CONDITIONAL([FEATURE_HYBRID_SENSORS], [test x$hybrid_sensors = xtrue])
+
+AS_IF([test x$hybrid_sensors = xtrue],
+    AC_MSG_NOTICE([Enabling hybrid sensors stack])
+    [cpp_flags="$cpp_flags -DFEATURE_HYBRID_SENSORS"]
+    AC_SUBST([CPPFLAGS], [$cpp_flags]),
+    AC_MSG_WARN([Disabling hybrid sensors feature])
+)
+
 # Create configured output
 AC_CONFIG_FILES([
     Makefile
diff --git a/dbus-sdr/sdrutils.cpp b/dbus-sdr/sdrutils.cpp
index 5ea88d8..7788050 100644
--- a/dbus-sdr/sdrutils.cpp
+++ b/dbus-sdr/sdrutils.cpp
@@ -16,6 +16,19 @@
 
 #include "dbus-sdr/sdrutils.hpp"
 
+#ifdef FEATURE_HYBRID_SENSORS
+
+#include <ipmid/utils.hpp>
+namespace ipmi
+{
+namespace sensor
+{
+extern const IdInfoMap sensors;
+} // namespace sensor
+} // namespace ipmi
+
+#endif
+
 namespace details
 {
 uint16_t getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree)
@@ -88,6 +101,31 @@
     bool sensorRez =
         lbdUpdateSensorTree("/xyz/openbmc_project/sensors", sensorInterfaces);
 
+#ifdef FEATURE_HYBRID_SENSORS
+
+    if (!ipmi::sensor::sensors.empty())
+    {
+        for (const auto& sensor : ipmi::sensor::sensors)
+        {
+            // Threshold sensors should not be emplaced in here.
+            if (boost::starts_with(sensor.second.sensorPath,
+                                   "/xyz/openbmc_project/sensors/"))
+            {
+                continue;
+            }
+
+            // The bus service name is not listed in ipmi::sensor::Info. Give it
+            // an empty string. For those function using non-threshold sensors,
+            // the bus service name will be retrieved in an alternative way.
+            boost::container::flat_map<std::string, std::vector<std::string>>
+                connectionMap{
+                    {"", {sensor.second.propertyInterfaces.begin()->first}}};
+            sensorTreePtr->emplace(sensor.second.sensorPath, connectionMap);
+        }
+    }
+
+#endif
+
     // Error if searching for sensors failed.
     if (!sensorRez)
     {
@@ -167,6 +205,19 @@
     return true;
 }
 
+#ifdef FEATURE_HYBRID_SENSORS
+// Static sensors are listed in sensor-gen.cpp.
+ipmi::sensor::IdInfoMap::const_iterator
+    findStaticSensor(const std::string& path)
+{
+    return std::find_if(
+        ipmi::sensor::sensors.begin(), ipmi::sensor::sensors.end(),
+        [&path](const ipmi::sensor::IdInfoMap::value_type& findSensor) {
+            return findSensor.second.sensorPath == path;
+        });
+}
+#endif
+
 std::string getSensorTypeStringFromPath(const std::string& path)
 {
     // get sensor type string from path, path is defined as
diff --git a/dbus-sdr/sensorcommands.cpp b/dbus-sdr/sensorcommands.cpp
index 1f07299..bda56df 100644
--- a/dbus-sdr/sensorcommands.cpp
+++ b/dbus-sdr/sensorcommands.cpp
@@ -41,6 +41,18 @@
 #include <utility>
 #include <variant>
 
+#ifdef FEATURE_HYBRID_SENSORS
+
+#include "sensordatahandler.hpp"
+namespace ipmi
+{
+namespace sensor
+{
+extern const IdInfoMap sensors;
+} // namespace sensor
+} // namespace ipmi
+#endif
+
 namespace ipmi
 {
 
@@ -226,6 +238,19 @@
                          std::string sensorPath, DbusInterfaceMap& sensorMap,
                          int updatePeriod = sensorMapUpdatePeriod)
 {
+#ifdef FEATURE_HYBRID_SENSORS
+    if (auto sensor = findStaticSensor(sensorPath);
+        sensor != ipmi::sensor::sensors.end() &&
+        getSensorEventTypeFromPath(sensorPath) !=
+            static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+    {
+        // If the incoming sensor is a discrete sensor, it might fail in
+        // getManagedObjects(), return true, and use its own getFunc to get
+        // value.
+        return true;
+    }
+#endif
+
     static boost::container::flat_map<
         std::string, std::chrono::time_point<std::chrono::steady_clock>>
         updateTimeMap;
@@ -615,6 +640,53 @@
         return ipmi::response(status);
     }
 
+#ifdef FEATURE_HYBRID_SENSORS
+    if (auto sensor = findStaticSensor(path);
+        sensor != ipmi::sensor::sensors.end() &&
+        getSensorEventTypeFromPath(path) !=
+            static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+    {
+        if (ipmi::sensor::Mutability::Read !=
+            (sensor->second.mutability & ipmi::sensor::Mutability::Read))
+        {
+            return ipmi::responseIllegalCommand();
+        }
+
+        uint8_t operation;
+        try
+        {
+            ipmi::sensor::GetSensorResponse getResponse =
+                sensor->second.getFunc(sensor->second);
+
+            if (getResponse.readingOrStateUnavailable)
+            {
+                operation |= static_cast<uint8_t>(
+                    IPMISensorReadingByte2::readingStateUnavailable);
+            }
+            if (getResponse.scanningEnabled)
+            {
+                operation |= static_cast<uint8_t>(
+                    IPMISensorReadingByte2::sensorScanningEnable);
+            }
+            if (getResponse.allEventMessagesEnabled)
+            {
+                operation |= static_cast<uint8_t>(
+                    IPMISensorReadingByte2::eventMessagesEnable);
+            }
+            return ipmi::responseSuccess(
+                getResponse.reading, operation,
+                getResponse.thresholdLevelsStates,
+                getResponse.discreteReadingSensorStates);
+        }
+        catch (const std::exception& e)
+        {
+            operation |= static_cast<uint8_t>(
+                IPMISensorReadingByte2::readingStateUnavailable);
+            return ipmi::responseSuccess(0, operation, 0, std::nullopt);
+        }
+    }
+#endif
+
     DbusInterfaceMap sensorMap;
     if (!getSensorMap(ctx, connection, path, sensorMap))
     {
@@ -1103,6 +1175,31 @@
         return ipmi::response(status);
     }
 
+#ifdef FEATURE_HYBRID_SENSORS
+    if (auto sensor = findStaticSensor(path);
+        sensor != ipmi::sensor::sensors.end() &&
+        getSensorEventTypeFromPath(path) !=
+            static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+    {
+        enabled = static_cast<uint8_t>(
+            IPMISensorEventEnableByte2::sensorScanningEnable);
+        uint16_t assertionEnabled = 0;
+        for (auto& offsetValMap : sensor->second.propertyInterfaces.begin()
+                                      ->second.begin()
+                                      ->second.second)
+        {
+            assertionEnabled |= (1 << offsetValMap.first);
+        }
+        assertionEnabledLsb = static_cast<uint8_t>((assertionEnabled & 0xFF));
+        assertionEnabledMsb =
+            static_cast<uint8_t>(((assertionEnabled >> 8) & 0xFF));
+
+        return ipmi::responseSuccess(enabled, assertionEnabledLsb,
+                                     assertionEnabledMsb, deassertionEnabledLsb,
+                                     deassertionEnabledMsb);
+    }
+#endif
+
     DbusInterfaceMap sensorMap;
     if (!getSensorMap(ctx, connection, path, sensorMap))
     {
@@ -1198,6 +1295,40 @@
         return ipmi::response(status);
     }
 
+#ifdef FEATURE_HYBRID_SENSORS
+    if (auto sensor = findStaticSensor(path);
+        sensor != ipmi::sensor::sensors.end() &&
+        getSensorEventTypeFromPath(path) !=
+            static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+    {
+        auto response = ipmi::sensor::get::mapDbusToAssertion(
+            sensor->second, path, sensor->second.sensorInterface);
+        std::bitset<16> assertions;
+        // deassertions are not used.
+        std::bitset<16> deassertions = 0;
+        uint8_t sensorEventStatus;
+        if (response.readingOrStateUnavailable)
+        {
+            sensorEventStatus |= static_cast<uint8_t>(
+                IPMISensorReadingByte2::readingStateUnavailable);
+        }
+        if (response.scanningEnabled)
+        {
+            sensorEventStatus |= static_cast<uint8_t>(
+                IPMISensorReadingByte2::sensorScanningEnable);
+        }
+        if (response.allEventMessagesEnabled)
+        {
+            sensorEventStatus |= static_cast<uint8_t>(
+                IPMISensorReadingByte2::eventMessagesEnable);
+        }
+        assertions |= response.discreteReadingSensorStates << 8;
+        assertions |= response.thresholdLevelsStates;
+        return ipmi::responseSuccess(sensorEventStatus, assertions,
+                                     deassertions);
+    }
+#endif
+
     DbusInterfaceMap sensorMap;
     if (!getSensorMap(ctx, connection, path, sensorMap))
     {
@@ -1554,6 +1685,46 @@
     return true;
 }
 
+#ifdef FEATURE_HYBRID_SENSORS
+// Construct a type 1 SDR for discrete Sensor typed sensor.
+void constructStaticSensorSdr(ipmi::Context::ptr ctx, uint16_t sensorNum,
+                              uint16_t recordID,
+                              ipmi::sensor::IdInfoMap::const_iterator sensor,
+                              get_sdr::SensorDataFullRecord& record)
+{
+    constructSensorSdrHeaderKey(sensorNum, recordID, record);
+
+    record.body.entity_id = sensor->second.entityType;
+    record.body.sensor_type = sensor->second.sensorType;
+    record.body.event_reading_type = sensor->second.sensorReadingType;
+    record.body.entity_instance = sensor->second.instance;
+    if (ipmi::sensor::Mutability::Write ==
+        (sensor->second.mutability & ipmi::sensor::Mutability::Write))
+    {
+        get_sdr::body::init_settable_state(true, &(record.body));
+    }
+
+    auto id_string = sensor->second.sensorName;
+
+    if (id_string.empty())
+    {
+        id_string = sensor->second.sensorNameFunc(sensor->second);
+    }
+
+    if (id_string.length() > FULL_RECORD_ID_STR_MAX_LENGTH)
+    {
+        get_sdr::body::set_id_strlen(FULL_RECORD_ID_STR_MAX_LENGTH,
+                                     &(record.body));
+    }
+    else
+    {
+        get_sdr::body::set_id_strlen(id_string.length(), &(record.body));
+    }
+    std::strncpy(record.body.id_string, id_string.c_str(),
+                 get_sdr::body::get_id_strlen(&(record.body)));
+}
+#endif
+
 // Construct type 3 SDR header and key (for VR and other discrete sensors)
 void constructEventSdrHeaderKey(uint16_t sensorNum, uint16_t recordID,
                                 get_sdr::SensorDataEventRecord& record)
@@ -1729,6 +1900,32 @@
         return 0;
     }
 
+#ifdef FEATURE_HYBRID_SENSORS
+    if (auto sensor = findStaticSensor(path);
+        sensor != ipmi::sensor::sensors.end() &&
+        getSensorEventTypeFromPath(path) !=
+            static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+    {
+        get_sdr::SensorDataFullRecord record = {0};
+
+        // If the request doesn't read SDR body, construct only header and key
+        // part to avoid additional DBus transaction.
+        if (readBytes <= sizeof(record.header) + sizeof(record.key))
+        {
+            constructSensorSdrHeaderKey(sensorNum, recordID, record);
+        }
+        else
+        {
+            constructStaticSensorSdr(ctx, sensorNum, recordID, sensor, record);
+        }
+
+        recordData.insert(recordData.end(), (uint8_t*)&record,
+                          ((uint8_t*)&record) + sizeof(record));
+
+        return 0;
+    }
+#endif
+
     // Contruct SDR type 3 record for VR sensor (daemon)
     if (std::find(interfaces.begin(), interfaces.end(), sensor::vrInterface) !=
         interfaces.end())
diff --git a/include/dbus-sdr/sdrutils.hpp b/include/dbus-sdr/sdrutils.hpp
index d4ed6f5..3933245 100644
--- a/include/dbus-sdr/sdrutils.hpp
+++ b/include/dbus-sdr/sdrutils.hpp
@@ -231,6 +231,11 @@
 
 bool getSensorSubtree(SensorSubTree& subtree);
 
+#ifdef FEATURE_HYBRID_SENSORS
+ipmi::sensor::IdInfoMap::const_iterator
+    findStaticSensor(const std::string& path);
+#endif
+
 struct CmpStr
 {
     bool operator()(const char* a, const char* b) const