DPS310 temperature pressure, SI7020 temperature

Infineon DPS310 pressure and temperature sensors for the Blyth
and Storm King Panel, as well as the Silicon Laboratories
SI7020 type sensors.  This provide OpenBMC with the ability
to have the DPS310 pressure and temperature sensors also the
Silicon Laboratories SI7020 type sensors provide OpenBMC
with temperature sensor on dbus via dbus-sensors through
HwmonTempSensor.  And adds "pressure" as a type of sensor.
Changed sensorScaleFactor to have offsetValue and scaleValue
for _raw IIO devices, scaleValue is a multiplier where
sensorScaleFactor was a divisor.  Pulling this into HwmonTempSensor
came about from this Discord discussion:
https://discord.com/channels/775381525260664832/775381525260664836/869641817220595772

Tested: on Rainier's Blyth and Everest's Storm King Op Panels
busctl tree --no-pager xyz.openbmc_project.HwmonTempSensor
busctl introspect --no-pager xyz.openbmc_project.HwmonTempSensor \
/xyz/openbmc_project/sensors/temperature/Ambient_0_Temp
busctl introspect --no-pager xyz.openbmc_project.HwmonTempSensor \
/xyz/openbmc_project/sensors/temperature/Ambient_1_Temp
busctl introspect --no-pager xyz.openbmc_project.HwmonTempSensor \
/xyz/openbmc_project/sensors/temperature/Ambient_2_Temp
busctl introspect --no-pager xyz.openbmc_project.HwmonTempSensor \
/xyz/openbmc_project/sensors/temperature/PCIE_0_Temp
busctl introspect --no-pager xyz.openbmc_project.HwmonTempSensor \
/xyz/openbmc_project/sensors/temperature/PCIE_1_Temp
busctl introspect --no-pager xyz.openbmc_project.HwmonTempSensor \
/xyz/openbmc_project/sensors/pressure/Station_Pressure
Results consistent with location of the systems.

Signed-off-by: Bruce Mitchell <bruce.mitchell@linux.vnet.ibm.com>
Change-Id: I5c0d56b486a671ee507c7bfe18fea72e7eaf9ebe
diff --git a/include/HwmonTempSensor.hpp b/include/HwmonTempSensor.hpp
index 159e9b5..8ae92cf 100644
--- a/include/HwmonTempSensor.hpp
+++ b/include/HwmonTempSensor.hpp
@@ -8,6 +8,16 @@
 #include <string>
 #include <vector>
 
+struct SensorParams
+{
+    double minValue;
+    double maxValue;
+    double offsetValue;
+    double scaleValue;
+    std::string units;
+    std::string typeName;
+};
+
 class HwmonTempSensor :
     public Sensor,
     public std::enable_shared_from_this<HwmonTempSensor>
@@ -18,6 +28,7 @@
                     std::shared_ptr<sdbusplus::asio::connection>& conn,
                     boost::asio::io_service& io, const std::string& sensorName,
                     std::vector<thresholds::Threshold>&& thresholds,
+                    const struct SensorParams& thisSensorParameters,
                     const float pollRate,
                     const std::string& sensorConfiguration,
                     const PowerState powerState);
@@ -30,6 +41,8 @@
     boost::asio::deadline_timer waitTimer;
     boost::asio::streambuf readBuf;
     std::string path;
+    double offsetValue;
+    double scaleValue;
     unsigned int sensorPollMs;
 
     void handleResponse(const boost::system::error_code& err);
diff --git a/include/Utils.hpp b/include/Utils.hpp
index c08daf9..bb15bb0 100644
--- a/include/Utils.hpp
+++ b/include/Utils.hpp
@@ -12,6 +12,7 @@
 #include <functional>
 #include <iostream>
 #include <memory>
+#include <optional>
 #include <regex>
 #include <string>
 #include <tuple>
diff --git a/src/HwmonTempMain.cpp b/src/HwmonTempMain.cpp
index b3fa969..de74c39 100644
--- a/src/HwmonTempMain.cpp
+++ b/src/HwmonTempMain.cpp
@@ -38,6 +38,12 @@
 
 static constexpr float pollRateDefault = 0.5;
 
+static constexpr double maxValuePressure = 120000; // Pascals
+static constexpr double minValuePressure = 30000;  // Pascals
+
+static constexpr double maxValueTemperature = 127;  // DegreesC
+static constexpr double minValueTemperature = -128; // DegreesC
+
 namespace fs = std::filesystem;
 static auto sensorTypes{
     std::to_array<const char*>({"xyz.openbmc_project.Configuration.EMC1412",
@@ -57,8 +63,81 @@
                                 "xyz.openbmc_project.Configuration.LM75A",
                                 "xyz.openbmc_project.Configuration.TMP75",
                                 "xyz.openbmc_project.Configuration.W83773G",
+                                "xyz.openbmc_project.Configuration.DPS310",
+                                "xyz.openbmc_project.Configuration.SI7020",
                                 "xyz.openbmc_project.Configuration.JC42"})};
 
+static struct SensorParams
+    getSensorParameters(const std::filesystem::path& path)
+{
+    // offset is to default to 0 and scale to 1, see lore
+    // https://lore.kernel.org/linux-iio/5c79425f-6e88-36b6-cdfe-4080738d039f@metafoo.de/
+    struct SensorParams tmpSensorParameters = {.minValue = minValueTemperature,
+                                               .maxValue = maxValueTemperature,
+                                               .offsetValue = 0.0,
+                                               .scaleValue = 1.0,
+                                               .units = "DegreesC",
+                                               .typeName = "temperature"};
+
+    // For IIO RAW sensors we get a raw_value, an offset, and scale
+    // to compute the value = (raw_value + offset) * scale
+    // with a _raw IIO device we need to get the
+    // offsetValue and scaleValue from the driver
+    // these are used to compute the reading in
+    // units that have yet to be scaled for D-Bus.
+    const std::string pathStr = path.string();
+    if (pathStr.ends_with("_raw"))
+    {
+        std::string pathOffsetStr =
+            pathStr.substr(0, pathStr.size() - 4) + "_offset";
+        std::optional<double> tmpOffsetValue = readFile(pathOffsetStr, 1.0);
+        // In case there is nothing to read skip this device
+        // This is not an error condition see lore
+        // https://lore.kernel.org/linux-iio/5c79425f-6e88-36b6-cdfe-4080738d039f@metafoo.de/
+        if (tmpOffsetValue)
+        {
+            tmpSensorParameters.offsetValue = *tmpOffsetValue;
+        }
+
+        std::string pathScaleStr =
+            pathStr.substr(0, pathStr.size() - 4) + "_scale";
+        std::optional<double> tmpScaleValue = readFile(pathScaleStr, 1.0);
+        // In case there is nothing to read skip this device
+        // This is not an error condition see lore
+        // https://lore.kernel.org/linux-iio/5c79425f-6e88-36b6-cdfe-4080738d039f@metafoo.de/
+        if (tmpScaleValue)
+        {
+            tmpSensorParameters.scaleValue = *tmpScaleValue;
+        }
+    }
+
+    // Temperatures are read in milli degrees Celsius, we need
+    // degrees Celsius. Pressures are read in kilopascal, we need
+    // Pascals.  On D-Bus for Open BMC we use the International
+    // System of Units without prefixes. Links to the kernel
+    // documentation:
+    // https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface
+    // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-iio
+    if (path.filename() == "in_pressure_input" ||
+        path.filename() == "in_pressure_raw")
+    {
+        tmpSensorParameters.minValue = minValuePressure;
+        tmpSensorParameters.maxValue = maxValuePressure;
+        // Pressures are read in kilopascal, we need Pascals.
+        tmpSensorParameters.scaleValue *= 1000.0;
+        tmpSensorParameters.typeName = "pressure";
+        tmpSensorParameters.units = "Pascals";
+    }
+    else
+    {
+        // Temperatures are read in milli degrees Celsius,
+        // we need degrees Celsius.
+        tmpSensorParameters.scaleValue *= 0.001;
+    }
+
+    return tmpSensorParameters;
+}
+
 void createSensors(
     boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
     boost::container::flat_map<std::string, std::shared_ptr<HwmonTempSensor>>&
@@ -73,31 +152,45 @@
          sensorsChanged](const ManagedObjectType& sensorConfigurations) {
             bool firstScan = sensorsChanged == nullptr;
 
+            // IIO _raw devices look like this on sysfs:
+            //     /sys/bus/iio/devices/iio:device0/in_temp_raw
+            //     /sys/bus/iio/devices/iio:device0/in_temp_offset
+            //     /sys/bus/iio/devices/iio:device0/in_temp_scale
+            //
+            // Other IIO devices look like this on sysfs:
+            //     /sys/bus/iio/devices/iio:device1/in_temp_input
+            //     /sys/bus/iio/devices/iio:device1/in_pressure_input
             std::vector<fs::path> paths;
-            if (!findFiles(fs::path("/sys/class/hwmon"), R"(temp\d+_input)",
-                           paths))
+            fs::path root("/sys/bus/iio/devices");
+            findFiles(root, R"(in_temp\d*_(input|raw))", paths);
+            findFiles(root, R"(in_pressure\d*_(input|raw))", paths);
+            findFiles(fs::path("/sys/class/hwmon"), R"(temp\d+_input)", paths);
+
+            if (paths.empty())
             {
-                std::cerr << "No temperature sensors in system\n";
                 return;
             }
 
-            boost::container::flat_set<std::string> directories;
-
-            // iterate through all found temp sensors, and try to match them
-            // with configuration
+            // iterate through all found temp and pressure sensors,
+            // and try to match them with configuration
             for (auto& path : paths)
             {
                 std::smatch match;
+                const std::string pathStr = path.string();
                 auto directory = path.parent_path();
+                fs::path device;
 
-                auto ret = directories.insert(directory.string());
-                if (!ret.second)
+                std::string deviceName;
+                if (pathStr.starts_with("/sys/bus/iio/devices"))
                 {
-                    continue; // already searched this path
+                    device = fs::canonical(directory);
+                    deviceName = device.parent_path().stem();
                 }
-
-                fs::path device = directory / "device";
-                std::string deviceName = fs::canonical(device).stem();
+                else
+                {
+                    device = directory / "device";
+                    deviceName = fs::canonical(device).stem();
+                }
                 auto findHyphen = deviceName.find('-');
                 if (findHyphen == std::string::npos)
                 {
@@ -124,6 +217,8 @@
                 const SensorBaseConfiguration* baseConfiguration = nullptr;
                 const SensorBaseConfigMap* baseConfigMap = nullptr;
 
+                auto thisSensorParameters = getSensorParameters(path);
+
                 for (const std::pair<sdbusplus::message::object_path,
                                      SensorData>& sensor : sensorConfigurations)
                 {
@@ -173,7 +268,13 @@
                     continue;
                 }
 
+                // Temperature has "Name", pressure has "Name1"
                 auto findSensorName = baseConfigMap->find("Name");
+                if (thisSensorParameters.typeName == "pressure")
+                {
+                    findSensorName = baseConfigMap->find("Name1");
+                }
+
                 if (findSensorName == baseConfigMap->end())
                 {
                     std::cerr << "could not determine configuration name for "
@@ -241,12 +342,17 @@
                 sensor = nullptr;
                 auto hwmonFile = getFullHwmonFilePath(directory.string(),
                                                       "temp1", permitSet);
+                if (pathStr.starts_with("/sys/bus/iio/devices"))
+                {
+                    hwmonFile = pathStr;
+                }
                 if (hwmonFile)
                 {
                     sensor = std::make_shared<HwmonTempSensor>(
                         *hwmonFile, sensorType, objectServer, dbusConnection,
-                        io, sensorName, std::move(sensorThresholds), pollRate,
-                        *interfacePath, readState);
+                        io, sensorName, std::move(sensorThresholds),
+                        thisSensorParameters, pollRate, *interfacePath,
+                        readState);
                     sensor->setupRead();
                 }
                 // Looking for keys like "Name1" for temp2_input,
@@ -266,6 +372,10 @@
                     hwmonFile = getFullHwmonFilePath(
                         directory.string(), "temp" + std::to_string(i + 1),
                         permitSet);
+                    if (pathStr.starts_with("/sys/bus/iio/devices"))
+                    {
+                        continue;
+                    }
                     if (hwmonFile)
                     {
                         // To look up thresholds for these additional sensors,
@@ -288,8 +398,8 @@
                         sensor = std::make_shared<HwmonTempSensor>(
                             *hwmonFile, sensorType, objectServer,
                             dbusConnection, io, sensorName,
-                            std::move(thresholds), pollRate, *interfacePath,
-                            readState);
+                            std::move(thresholds), thisSensorParameters,
+                            pollRate, *interfacePath, readState);
                         sensor->setupRead();
                     }
                 }
diff --git a/src/HwmonTempSensor.cpp b/src/HwmonTempSensor.cpp
index 549fff7..ba632a2 100644
--- a/src/HwmonTempSensor.cpp
+++ b/src/HwmonTempSensor.cpp
@@ -38,46 +38,49 @@
 // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-iio
 // For IIO RAW sensors we get a raw_value, an offset, and scale to compute
 // the value = (raw_value + offset) * scale
-static constexpr double sensorOffset = 0.0;
-static constexpr double sensorScale = 0.001;
-static constexpr size_t warnAfterErrorCount = 10;
-
-static constexpr double maxReading = 127;
-static constexpr double minReading = -128;
 
 HwmonTempSensor::HwmonTempSensor(
     const std::string& path, const std::string& objectType,
     sdbusplus::asio::object_server& objectServer,
     std::shared_ptr<sdbusplus::asio::connection>& conn,
     boost::asio::io_service& io, const std::string& sensorName,
-    std::vector<thresholds::Threshold>&& thresholdsIn, const float pollRate,
+    std::vector<thresholds::Threshold>&& thresholdsIn,
+    const struct SensorParams& thisSensorParameters, const float pollRate,
     const std::string& sensorConfiguration, const PowerState powerState) :
-    Sensor(escapeName(sensorName), std::move(thresholdsIn), sensorConfiguration,
-           objectType, false, false, maxReading, minReading, conn, powerState),
+    Sensor(boost::replace_all_copy(sensorName, " ", "_"),
+           std::move(thresholdsIn), sensorConfiguration, objectType, false,
+           false, thisSensorParameters.maxValue, thisSensorParameters.minValue,
+           conn, powerState),
     std::enable_shared_from_this<HwmonTempSensor>(), objServer(objectServer),
     inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), path(path),
+    offsetValue(thisSensorParameters.offsetValue),
+    scaleValue(thisSensorParameters.scaleValue),
     sensorPollMs(static_cast<unsigned int>(pollRate * 1000))
 {
     sensorInterface = objectServer.add_interface(
-        "/xyz/openbmc_project/sensors/temperature/" + name,
+        "/xyz/openbmc_project/sensors/" + thisSensorParameters.typeName + "/" +
+            name,
         "xyz.openbmc_project.Sensor.Value");
 
     if (thresholds::hasWarningInterface(thresholds))
     {
         thresholdInterfaceWarning = objectServer.add_interface(
-            "/xyz/openbmc_project/sensors/temperature/" + name,
+            "/xyz/openbmc_project/sensors/" + thisSensorParameters.typeName +
+                "/" + name,
             "xyz.openbmc_project.Sensor.Threshold.Warning");
     }
     if (thresholds::hasCriticalInterface(thresholds))
     {
         thresholdInterfaceCritical = objectServer.add_interface(
-            "/xyz/openbmc_project/sensors/temperature/" + name,
+            "/xyz/openbmc_project/sensors/" + thisSensorParameters.typeName +
+                "/" + name,
             "xyz.openbmc_project.Sensor.Threshold.Critical");
     }
-    association = objectServer.add_interface(
-        "/xyz/openbmc_project/sensors/temperature/" + name,
-        association::interface);
-    setInitialProperties(conn, sensor_paths::unitDegreesC);
+    association = objectServer.add_interface("/xyz/openbmc_project/sensors/" +
+                                                 thisSensorParameters.typeName +
+                                                 "/" + name,
+                                             association::interface);
+    setInitialProperties(conn, thisSensorParameters.units);
 }
 
 HwmonTempSensor::~HwmonTempSensor()
@@ -149,7 +152,7 @@
         try
         {
             rawValue = std::stod(response);
-            double nvalue = (rawValue + sensorOffset) * sensorScale;
+            double nvalue = (rawValue + offsetValue) * scaleValue;
             updateValue(nvalue);
         }
         catch (const std::invalid_argument&)