PSUSensor: add IIO device sensor

Support IIO device on D-bus via dbus-sensors, expose path to support
multi-channel labels.
Reference:
https://gerrit.openbmc.org/c/openbmc/dbus-sensors/+/47822

This patch is able to support the IIO device ADS1015
Use with this patch.
https://gerrit.openbmc.org/c/openbmc/entity-manager/+/58505

Json config
{
    "Address": "0x49",
    "Bus": 11,
    "Labels": [
        "in_voltage1",
        "in_voltage2"
    ],
    "Name": "vr_prnt_cem",
    "EntityId": "0x07",
    "EntityInstance": "0x17",
    "in_voltage1_Name": "vr_prnt_msas1",
    "in_voltage1_Scale": 1000,
    "in_voltage2_Name": "vr_prnt_msas2",
    "in_voltage2_Scale": 1000,
    "PowerState": "On",
    "Thresholds": [
        {
            "Direction": "greater than",
            "Label": "in_voltage1",
            "Name": "upper critical",
            "Severity": 1,
            "Value": 2.0
        },
        {
            "Direction": "less than",
            "Label": "in_voltage1",
            "Name": "lower critical",
            "Severity": 1,
            "Value": 0.0
        },
        {
            "Direction": "greater than",
            "Label": "in_voltage2",
            "Name": "upper critical",
            "Severity": 1,
            "Value": 2.0
        },
        {
            "Direction": "less than",
            "Label": "in_voltage2",
            "Name": "lower critical",
            "Severity": 1,
            "Value": 0.0
        }
    ],
    "in_voltage1_Max": 2.0,
    "in_voltage1_Min": 0.0,
    "in_voltage2_Max": 2.0,
    "in_voltage2_Min": 0.0,
    "PollRate": 1.0,
    "Type": "ADS1015"
}

Tested:
root@qbmc:~# busctl tree xyz.openbmc_project.EntityManager |grep msas
  |-/xyz/openbmc_project/inventory/system/board/Yertle/vr_prnt_msas1
  `-/xyz/openbmc_project/inventory/system/board/Yertle/vr_prnt_msas2

root@qbmc:~# busctl tree xyz.openbmc_project.PSUSensor
`-/xyz
  `-/xyz/openbmc_project
    |-/xyz/openbmc_project/control
    `-/xyz/openbmc_project/sensors
      `-/xyz/openbmc_project/sensors/voltage
        |-/xyz/openbmc_project/sensors/voltage/vr_prnt_msas1
        `-/xyz/openbmc_project/sensors/voltage/vr_prnt_msas2

root@qbmc:~# busctl get-property xyz.openbmc_project.PSUSensor \
/xyz/openbmc_project/sensors/voltage/vr_prnt_msas1 \
xyz.openbmc_project.Sensor.Value Value
d 1.72

Signed-off-by: Joseph Fu <joseph.fu@quantatw.com>
Change-Id: I584f5d79850921e702b8572fb6b38e9dcfc44d25
diff --git a/src/PSUSensorMain.cpp b/src/PSUSensorMain.cpp
index 510dde4..663567d 100644
--- a/src/PSUSensorMain.cpp
+++ b/src/PSUSensorMain.cpp
@@ -48,6 +48,7 @@
     {"ADM1275", I2CDeviceType{"adm1275", true}},
     {"ADM1278", I2CDeviceType{"adm1278", true}},
     {"ADM1293", I2CDeviceType{"adm1293", true}},
+    {"ADS1015", I2CDeviceType{"ads1015", true}},
     {"ADS7830", I2CDeviceType{"ads7830", true}},
     {"AHE50DC_FAN", I2CDeviceType{"ahe50dc_fan", true}},
     {"BMR490", I2CDeviceType{"bmr490", true}},
@@ -97,6 +98,20 @@
     {"XDPE152C4", I2CDeviceType{"xdpe152c4", true}},
 };
 
+enum class DevTypes
+{
+    Unknown = 0,
+    HWMON,
+    IIO
+};
+
+struct DevParams
+{
+    unsigned int matchIndex = 0;
+    std::string matchRegEx;
+    std::string nameRegEx;
+};
+
 namespace fs = std::filesystem;
 
 static boost::container::flat_map<std::string, std::shared_ptr<PSUSensor>>
@@ -119,6 +134,7 @@
 
 static std::vector<PSUProperty> psuProperties;
 static boost::container::flat_map<size_t, bool> cpuPresence;
+static boost::container::flat_map<DevTypes, DevParams> devParamMap;
 
 // Function CheckEvent will check each attribute from eventMatch table in the
 // sysfs. If the attributes exists in sysfs, then store the complete path
@@ -293,7 +309,9 @@
     auto devices = instantiateDevices(sensorConfigs, sensors, sensorTypes);
 
     std::vector<fs::path> pmbusPaths;
-    if (!findFiles(fs::path("/sys/class/hwmon"), "name", pmbusPaths))
+    findFiles(fs::path("/sys/bus/iio/devices"), "name", pmbusPaths);
+    findFiles(fs::path("/sys/class/hwmon"), "name", pmbusPaths);
+    if (pmbusPaths.empty())
     {
         std::cerr << "No PSU sensors in system\n";
         return;
@@ -338,8 +356,18 @@
             continue; // check if path has already been searched
         }
 
-        fs::path device = directory / "device";
-        std::string deviceName = fs::canonical(device).stem();
+        DevTypes devType = DevTypes::HWMON;
+        std::string deviceName;
+        if (directory.parent_path() == "/sys/class/hwmon")
+        {
+            deviceName = fs::canonical(directory / "device").stem();
+        }
+        else
+        {
+            deviceName = fs::canonical(directory).parent_path().stem();
+            devType = DevTypes::IIO;
+        }
+
         auto findHyphen = deviceName.find('-');
         if (findHyphen == std::string::npos)
         {
@@ -509,7 +537,8 @@
         } while (findPSUName != baseConfig->end());
 
         std::vector<fs::path> sensorPaths;
-        if (!findFiles(directory, R"(\w\d+_input$)", sensorPaths, 0))
+        if (!findFiles(directory, devParamMap[devType].matchRegEx, sensorPaths,
+                       0))
         {
             std::cerr << "No PSU non-label sensor in PSU\n";
             continue;
@@ -535,7 +564,7 @@
                 std::get<std::vector<std::string>>(findLabelObj->second);
         }
 
-        std::regex sensorNameRegEx("([A-Za-z]+)[0-9]*_");
+        std::regex sensorNameRegEx(devParamMap[devType].nameRegEx);
         std::smatch matches;
 
         for (const auto& sensorPath : sensorPaths)
@@ -549,7 +578,9 @@
             {
                 // hwmon *_input filename without number:
                 // in, curr, power, temp, ...
-                sensorNameSubStr = matches[1];
+                // iio in_*_raw filename without number:
+                // voltage, temp, pressure, ...
+                sensorNameSubStr = matches[devParamMap[devType].matchIndex];
             }
             else
             {
@@ -560,61 +591,73 @@
 
             std::string labelPath;
 
-            /* find and differentiate _max and _input to replace "label" */
-            size_t pos = sensorPathStr.find('_');
-            if (pos != std::string::npos)
+            if (devType == DevTypes::HWMON)
             {
-                std::string sensorPathStrMax = sensorPathStr.substr(pos);
-                if (sensorPathStrMax == "_max")
+                /* find and differentiate _max and _input to replace "label" */
+                size_t pos = sensorPathStr.find('_');
+                if (pos != std::string::npos)
                 {
-                    labelPath = boost::replace_all_copy(sensorPathStr, "max",
-                                                        "label");
-                    maxLabel = true;
+                    std::string sensorPathStrMax = sensorPathStr.substr(pos);
+                    if (sensorPathStrMax == "_max")
+                    {
+                        labelPath = boost::replace_all_copy(sensorPathStr,
+                                                            "max", "label");
+                        maxLabel = true;
+                    }
+                    else
+                    {
+                        labelPath = boost::replace_all_copy(sensorPathStr,
+                                                            "input", "label");
+                        maxLabel = false;
+                    }
                 }
                 else
                 {
-                    labelPath = boost::replace_all_copy(sensorPathStr, "input",
-                                                        "label");
-                    maxLabel = false;
-                }
-            }
-            else
-            {
-                continue;
-            }
-
-            std::ifstream labelFile(labelPath);
-            if (!labelFile.good())
-            {
-                if constexpr (debug)
-                {
-                    std::cerr << "Input file " << sensorPath
-                              << " has no corresponding label file\n";
-                }
-                // hwmon *_input filename with number:
-                // temp1, temp2, temp3, ...
-                labelHead = sensorNameStr.substr(0, sensorNameStr.find('_'));
-            }
-            else
-            {
-                std::string label;
-                std::getline(labelFile, label);
-                labelFile.close();
-                auto findSensor = sensors.find(label);
-                if (findSensor != sensors.end())
-                {
                     continue;
                 }
 
-                // hwmon corresponding *_label file contents:
-                // vin1, vout1, ...
-                labelHead = label.substr(0, label.find(' '));
-            }
+                std::ifstream labelFile(labelPath);
+                if (!labelFile.good())
+                {
+                    if constexpr (debug)
+                    {
+                        std::cerr << "Input file " << sensorPath
+                                  << " has no corresponding label file\n";
+                    }
+                    // hwmon *_input filename with number:
+                    // temp1, temp2, temp3, ...
+                    labelHead = sensorNameStr.substr(0,
+                                                     sensorNameStr.find('_'));
+                }
+                else
+                {
+                    std::string label;
+                    std::getline(labelFile, label);
+                    labelFile.close();
+                    auto findSensor = sensors.find(label);
+                    if (findSensor != sensors.end())
+                    {
+                        continue;
+                    }
 
-            /* append "max" for labelMatch */
-            if (maxLabel)
+                    // hwmon corresponding *_label file contents:
+                    // vin1, vout1, ...
+                    labelHead = label.substr(0, label.find(' '));
+                }
+
+                /* append "max" for labelMatch */
+                if (maxLabel)
+                {
+                    labelHead.insert(0, "max");
+                }
+
+                checkPWMSensor(sensorPath, labelHead, *interfacePath,
+                               dbusConnection, objectServer, psuNames[0]);
+            }
+            else if (devType == DevTypes::IIO)
             {
-                labelHead.insert(0, "max");
+                auto findIIOHyphen = sensorNameStr.find_last_of('_');
+                labelHead = sensorNameStr.substr(0, findIIOHyphen);
             }
 
             if constexpr (debug)
@@ -623,9 +666,6 @@
                           << "\" label=\"" << labelHead << "\"\n";
             }
 
-            checkPWMSensor(sensorPath, labelHead, *interfacePath,
-                           dbusConnection, objectServer, psuNames[0]);
-
             if (!findLabels.empty())
             {
                 /* Check if this labelHead is enabled in config file */
@@ -817,7 +857,10 @@
                 }
             }
 
-            checkEventLimits(sensorPathStr, limitEventMatch, eventPathList);
+            if (devType == DevTypes::HWMON)
+            {
+                checkEventLimits(sensorPathStr, limitEventMatch, eventPathList);
+            }
 
             // Similarly, if sensor scaling factor is being customized,
             // then the below power-of-10 constraint becomes unnecessary,
@@ -932,13 +975,16 @@
             }
         }
 
-        // OperationalStatus event
-        combineEvents[*psuName + "OperationalStatus"] = nullptr;
-        combineEvents[*psuName + "OperationalStatus"] =
-            std::make_unique<PSUCombineEvent>(objectServer, dbusConnection, io,
-                                              *psuName, readState,
-                                              eventPathList, groupEventPathList,
-                                              "OperationalStatus", pollRate);
+        if (devType == DevTypes::HWMON)
+        {
+            // OperationalStatus event
+            combineEvents[*psuName + "OperationalStatus"] = nullptr;
+            combineEvents[*psuName + "OperationalStatus"] =
+                std::make_unique<PSUCombineEvent>(
+                    objectServer, dbusConnection, io, *psuName, readState,
+                    eventPathList, groupEventPathList, "OperationalStatus",
+                    pollRate);
+        }
     }
 
     if constexpr (debug)
@@ -974,6 +1020,7 @@
                    {"curr", sensor_paths::unitAmperes},
                    {"temp", sensor_paths::unitDegreesC},
                    {"in", sensor_paths::unitVolts},
+                   {"voltage", sensor_paths::unitVolts},
                    {"fan", sensor_paths::unitRPMs}};
 
     labelMatch = {
@@ -992,6 +1039,10 @@
         {"vin1", PSUProperty("Input Voltage", 300, 0, 3, 0)},
         {"vin2", PSUProperty("Input Voltage", 300, 0, 3, 0)},
         {"maxvin", PSUProperty("Max Input Voltage", 300, 0, 3, 0)},
+        {"in_voltage0", PSUProperty("Output Voltage", 255, 0, 3, 0)},
+        {"in_voltage1", PSUProperty("Output Voltage", 255, 0, 3, 0)},
+        {"in_voltage2", PSUProperty("Output Voltage", 255, 0, 3, 0)},
+        {"in_voltage3", PSUProperty("Output Voltage", 255, 0, 3, 0)},
         {"vout1", PSUProperty("Output Voltage", 255, 0, 3, 0)},
         {"vout2", PSUProperty("Output Voltage", 255, 0, 3, 0)},
         {"vout3", PSUProperty("Output Voltage", 255, 0, 3, 0)},
@@ -1085,6 +1136,11 @@
                          {"fan2", {"fan2_alarm", "fan2_fault"}},
                          {"fan3", {"fan3_alarm", "fan3_fault"}},
                          {"fan4", {"fan4_alarm", "fan4_fault"}}}}};
+
+    devParamMap = {
+        {DevTypes::HWMON, {1, R"(\w\d+_input$)", "([A-Za-z]+)[0-9]*_"}},
+        {DevTypes::IIO,
+         {2, R"(\w+_(raw|input)$)", "^(in|out)_([A-Za-z]+)[0-9]*_"}}};
 }
 
 static void powerStateChanged(