Implement PSU Event

Expose PSU Event AC Lost, Fan Fault, Failure, Predictive D-Bus interface.
Read alarm and max_alarm, min_alarm, crit_alarm, lcrit_alarm and
assert or deassert the related properties on PSU Event.

Tested By:
After run /usr/sbin/psusensor below D-Bus interface are exposed
/xyz/openbmc_project/State/Decorator/PSU1_OperationalStatus
/xyz/openbmc_project/State/Decorator/PSU2_OperationalStatus
After plugging out AC cable from PSU1, the functional property in
PSU1_OperationalStatus changes to "false" from "true".
After plugging in AC cable again, the property changes back to "true".

Change-Id: Ic21513471c4632835c39148ea313808fdcc816fa
Signed-off-by: Cheng C Yang <cheng.c.yang@linux.intel.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d0c6ce5..1d57691 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -43,7 +43,7 @@
 set (IPMB_SRC_FILES src/Utils.cpp src/Thresholds.cpp)
 
 set (PSU_SRC_FILES src/Utils.cpp src/PSUSensor.cpp src/Thresholds.cpp
-     src/PwmSensor.cpp)
+     src/PwmSensor.cpp src/PSUEvent.cpp)
 
 set (EXTERNAL_PACKAGES Boost sdbusplus-project nlohmann-json)
 set (SENSOR_LINK_LIBS -lsystemd stdc++fs sdbusplus)
diff --git a/include/PSUEvent.hpp b/include/PSUEvent.hpp
new file mode 100644
index 0000000..f7834e2
--- /dev/null
+++ b/include/PSUEvent.hpp
@@ -0,0 +1,70 @@
+/*
+// 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.
+*/
+
+#pragma once
+
+#include <sdbusplus/asio/object_server.hpp>
+
+class PSUSubEvent
+{
+  public:
+    PSUSubEvent(std::shared_ptr<sdbusplus::asio::dbus_interface> eventInterface,
+                const std::string& path, boost::asio::io_service& io,
+                const std::string& eventName,
+                std::shared_ptr<std::set<std::string>> asserts,
+                std::shared_ptr<std::set<std::string>> combineEvent,
+                std::shared_ptr<bool> state);
+    ~PSUSubEvent();
+
+    std::shared_ptr<sdbusplus::asio::dbus_interface> eventInterface;
+    std::shared_ptr<std::set<std::string>> asserts;
+    std::shared_ptr<std::set<std::string>> combineEvent;
+    std::shared_ptr<bool> assertState;
+
+  private:
+    int value = 0;
+    int errCount;
+    std::string path;
+    std::string eventName;
+    boost::asio::deadline_timer waitTimer;
+    boost::asio::streambuf readBuf;
+    void setupRead(void);
+    void handleResponse(const boost::system::error_code& err);
+    void updateValue(const int& newValue);
+    boost::asio::posix::stream_descriptor inputDev;
+    static constexpr unsigned int eventPollMs = 1000;
+    static constexpr size_t warnAfterErrorCount = 10;
+};
+
+class PSUCombineEvent
+{
+  public:
+    PSUCombineEvent(
+        sdbusplus::asio::object_server& objectSever,
+        boost::asio::io_service& io, const std::string& psuName,
+        boost::container::flat_map<std::string, std::vector<std::string>>&
+            eventPathList,
+        const std::string& combineEventName);
+    ~PSUCombineEvent();
+
+    sdbusplus::asio::object_server& objServer;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> eventInterface;
+    boost::container::flat_map<std::string,
+                               std::vector<std::unique_ptr<PSUSubEvent>>>
+        events;
+    std::vector<std::shared_ptr<std::set<std::string>>> asserts;
+    std::vector<std::shared_ptr<bool>> states;
+};
diff --git a/src/PSUEvent.cpp b/src/PSUEvent.cpp
new file mode 100644
index 0000000..5bfa1e4
--- /dev/null
+++ b/src/PSUEvent.cpp
@@ -0,0 +1,200 @@
+/*
+// 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 <PSUEvent.hpp>
+#include <iostream>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+PSUCombineEvent::PSUCombineEvent(
+    sdbusplus::asio::object_server& objectServer, boost::asio::io_service& io,
+    const std::string& psuName,
+    boost::container::flat_map<std::string, std::vector<std::string>>&
+        eventPathList,
+    const std::string& combineEventName) :
+    objServer(objectServer)
+{
+    eventInterface = objServer.add_interface(
+        "/xyz/openbmc_project/State/Decorator/" + psuName + "_" +
+            combineEventName,
+        "xyz.openbmc_project.State.Decorator.OperationalStatus");
+    eventInterface->register_property("functional", bool(true));
+
+    if (!eventInterface->initialize())
+    {
+        std::cerr << "error initializing event interface\n";
+    }
+
+    std::shared_ptr<std::set<std::string>> combineEvent =
+        std::make_shared<std::set<std::string>>();
+    for (const auto& pathList : eventPathList)
+    {
+        const std::string& eventName = pathList.first;
+        std::string eventPSUName = eventName + psuName;
+        for (const auto& path : pathList.second)
+        {
+            std::shared_ptr<std::set<std::string>> assert =
+                std::make_shared<std::set<std::string>>();
+            std::shared_ptr<bool> state = std::make_shared<bool>(false);
+            events[eventPSUName].emplace_back(std::make_unique<PSUSubEvent>(
+                eventInterface, path, io, eventName, assert, combineEvent,
+                state));
+            asserts.emplace_back(assert);
+            states.emplace_back(state);
+        }
+    }
+}
+
+PSUCombineEvent::~PSUCombineEvent()
+{
+    events.clear();
+    objServer.remove_interface(eventInterface);
+}
+
+PSUSubEvent::PSUSubEvent(
+    std::shared_ptr<sdbusplus::asio::dbus_interface> eventInterface,
+    const std::string& path, boost::asio::io_service& io,
+    const std::string& eventName,
+    std::shared_ptr<std::set<std::string>> asserts,
+    std::shared_ptr<std::set<std::string>> combineEvent,
+    std::shared_ptr<bool> state) :
+    eventInterface(eventInterface),
+    inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), errCount(0),
+    path(path), eventName(eventName), assertState(state), asserts(asserts),
+    combineEvent(combineEvent)
+{
+    setupRead();
+}
+
+void PSUSubEvent::setupRead(void)
+{
+    boost::asio::async_read_until(
+        inputDev, readBuf, '\n',
+        [&](const boost::system::error_code& ec,
+            std::size_t /*bytes_transfered*/) { handleResponse(ec); });
+}
+
+PSUSubEvent::~PSUSubEvent()
+{
+    inputDev.close();
+    waitTimer.cancel();
+}
+
+void PSUSubEvent::handleResponse(const boost::system::error_code& err)
+{
+    if (err == boost::system::errc::bad_file_descriptor)
+    {
+        return;
+    }
+    std::istream responseStream(&readBuf);
+    if (!err)
+    {
+        std::string response;
+        try
+        {
+            std::getline(responseStream, response);
+            int nvalue = std::stof(response);
+            responseStream.clear();
+            if (nvalue != value)
+            {
+                updateValue(nvalue);
+            }
+            errCount = 0;
+        }
+        catch (const std::invalid_argument&)
+        {
+            errCount++;
+        }
+    }
+    else
+    {
+        errCount++;
+    }
+    if (errCount >= warnAfterErrorCount)
+    {
+        if (errCount == warnAfterErrorCount)
+        {
+            std::cerr << "Failure to read event at " << path << "\n";
+        }
+        updateValue(0);
+        errCount++;
+    }
+    responseStream.clear();
+    inputDev.close();
+    int fd = open(path.c_str(), O_RDONLY);
+    if (fd <= 0)
+    {
+        return;
+    }
+    inputDev.assign(fd);
+    waitTimer.expires_from_now(boost::posix_time::milliseconds(eventPollMs));
+    waitTimer.async_wait([&](const boost::system::error_code& ec) {
+        if (ec == boost::asio::error::operation_aborted)
+        {
+            return;
+        }
+        setupRead();
+    });
+}
+
+// Any of the sub events of one event is asserted, then the event will be
+// asserted. Only if none of the sub events are asserted, the event will be
+// deasserted.
+void PSUSubEvent::updateValue(const int& newValue)
+{
+    if (newValue == 0)
+    {
+        auto found = (*asserts).find(path);
+        if (found == (*asserts).end())
+        {
+            return;
+        }
+        (*asserts).erase(found);
+
+        if (!(*asserts).empty())
+        {
+            return;
+        }
+        if (*assertState == true)
+        {
+            *assertState = false;
+            auto foundCombine = (*combineEvent).find(eventName);
+            if (foundCombine != (*combineEvent).end())
+            {
+                return;
+            }
+            (*combineEvent).erase(eventName);
+            if ((*combineEvent).empty())
+            {
+                eventInterface->set_property("functional", true);
+            }
+        }
+    }
+    else
+    {
+        (*asserts).emplace(path);
+        if (*assertState == false)
+        {
+            *assertState = true;
+            if ((*combineEvent).empty())
+            {
+                eventInterface->set_property("functional", false);
+            }
+            (*combineEvent).emplace(eventName);
+        }
+    }
+    value = newValue;
+}
diff --git a/src/PSUSensorMain.cpp b/src/PSUSensorMain.cpp
index a23bc13..14d1a69 100644
--- a/src/PSUSensorMain.cpp
+++ b/src/PSUSensorMain.cpp
@@ -14,6 +14,7 @@
 // limitations under the License.
 */
 
+#include <PSUEvent.hpp>
 #include <PSUSensor.hpp>
 #include <Utils.hpp>
 #include <boost/algorithm/string/predicate.hpp>
@@ -21,6 +22,7 @@
 #include <boost/container/flat_set.hpp>
 #include <filesystem>
 #include <fstream>
+#include <iostream>
 #include <regex>
 #include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/asio/object_server.hpp>
@@ -32,11 +34,75 @@
 
 static boost::container::flat_map<std::string, std::unique_ptr<PSUSensor>>
     sensors;
+static boost::container::flat_map<std::string, std::unique_ptr<PSUCombineEvent>>
+    combineEvents;
 static boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>
     pwmSensors;
 static boost::container::flat_map<std::string, std::string> sensorTable;
 static boost::container::flat_map<std::string, PSUProperty> labelMatch;
 static boost::container::flat_map<std::string, std::string> pwmTable;
+static boost::container::flat_map<std::string, std::vector<std::string>>
+    eventMatch;
+static boost::container::flat_map<std::string, std::vector<std::string>>
+    limitEventMatch;
+
+// Function CheckEvent will check each attribute from eventMatch table in the
+// sysfs. If the attributes exists in sysfs, then store the complete path
+// of the attribute into eventPathList.
+void checkEvent(
+    const std::string& directory,
+    const boost::container::flat_map<std::string, std::vector<std::string>>&
+        eventMatch,
+    boost::container::flat_map<std::string, std::vector<std::string>>&
+        eventPathList)
+{
+    for (const auto& match : eventMatch)
+    {
+        const std::vector<std::string>& eventAttrs = match.second;
+        const std::string& eventName = match.first;
+        for (const auto& eventAttr : eventAttrs)
+        {
+            auto eventPath = directory + "/" + eventAttr;
+
+            std::ifstream eventFile(eventPath);
+            if (!eventFile.good())
+            {
+                continue;
+            }
+
+            eventPathList[eventName].push_back(eventPath);
+        }
+    }
+}
+
+// Function checkEventLimits will check all the psu related xxx_input attributes
+// in sysfs to see if xxx_crit_alarm xxx_lcrit_alarm xxx_max_alarm
+// xxx_min_alarm exist, then store the existing paths of the alarm attributes
+// to eventPathList.
+void checkEventLimits(
+    const std::string& sensorPathStr,
+    const boost::container::flat_map<std::string, std::vector<std::string>>&
+        limitEventMatch,
+    boost::container::flat_map<std::string, std::vector<std::string>>&
+        eventPathList)
+{
+    for (const auto& limitMatch : limitEventMatch)
+    {
+        const std::vector<std::string>& limitEventAttrs = limitMatch.second;
+        const std::string& eventName = limitMatch.first;
+        for (const auto& limitEventAttr : limitEventAttrs)
+        {
+            auto limitEventPath =
+                boost::replace_all_copy(sensorPathStr, "input", limitEventAttr);
+            std::ifstream eventFile(limitEventPath);
+            if (!eventFile.good())
+            {
+                continue;
+            }
+            eventPathList[eventName].push_back(limitEventPath);
+        }
+    }
+}
 
 static void checkPWMSensor(const fs::path& sensorPath, std::string& labelHead,
                            const std::string& interfacePath,
@@ -79,6 +145,8 @@
     ManagedObjectType sensorConfigs;
     bool useCache = false;
 
+    // TODO may need only modify the ones that need to be changed.
+    sensors.clear();
     for (const char* type : sensorTypes)
     {
         if (!getSensorConfiguration(type, dbusConnection, sensorConfigs,
@@ -100,13 +168,31 @@
     boost::container::flat_set<std::string> directories;
     for (const auto& pmbusPath : pmbusPaths)
     {
-        const std::string pathStr = pmbusPath.string();
+        boost::container::flat_map<std::string, std::vector<std::string>>
+            eventPathList;
+
+        std::ifstream nameFile(pmbusPath);
+        if (!nameFile.good())
+        {
+            std::cerr << "Failure reading " << pmbusPath << "\n";
+            continue;
+        }
+
+        std::string pmbusName;
+        std::getline(nameFile, pmbusName);
+        nameFile.close();
+        if (pmbusName != "pmbus")
+        {
+            continue;
+        }
+
+        const std::string* psuName;
         auto directory = pmbusPath.parent_path();
 
         auto ret = directories.insert(directory.string());
         if (!ret.second)
         {
-            continue; // check if path i1 already searched
+            continue; // check if path has already been searched
         }
 
         auto device = fs::path(directory / "device");
@@ -133,21 +219,6 @@
             continue;
         }
 
-        std::ifstream nameFile(pmbusPath);
-        if (!nameFile.good())
-        {
-            std::cerr << "Failure reading " << pmbusPath << "\n";
-            continue;
-        }
-
-        std::string pmbusName;
-        std::getline(nameFile, pmbusName);
-        nameFile.close();
-        if (pmbusName != "pmbus")
-        {
-            continue;
-        }
-
         const std::pair<std::string, boost::container::flat_map<
                                          std::string, BasicVariantType>>*
             baseConfig = nullptr;
@@ -182,12 +253,21 @@
             if (configBus == baseConfig->second.end() ||
                 configAddress == baseConfig->second.end())
             {
-                std::cerr << "error finding necessary entry in configuration";
+                std::cerr << "error finding necessary entry in configuration\n";
                 continue;
             }
 
-            if (std::get<uint64_t>(configBus->second) != bus ||
-                std::get<uint64_t>(configAddress->second) != addr)
+            const uint64_t* confBus;
+            const uint64_t* confAddr;
+            if (!(confBus = std::get_if<uint64_t>(&(configBus->second))) ||
+                !(confAddr = std::get_if<uint64_t>(&(configAddress->second))))
+            {
+                std::cerr
+                    << "Canot get bus or address, invalid configuration\n";
+                continue;
+            }
+
+            if ((*confBus != bus) || (*confAddr != addr))
             {
                 continue;
             }
@@ -209,6 +289,13 @@
             continue;
         }
 
+        if (!(psuName = std::get_if<std::string>(&(findPSUName->second))))
+        {
+            std::cerr << "Cannot find psu name, invalid configuration\n";
+            continue;
+        }
+        checkEvent(directory.string(), eventMatch, eventPathList);
+
         std::vector<fs::path> sensorPaths;
         if (!findFiles(fs::path(directory), R"(\w\d+_input$)", sensorPaths, 0))
         {
@@ -275,6 +362,8 @@
                 continue;
             }
 
+            checkEventLimits(sensorPathStr, limitEventMatch, eventPathList);
+
             unsigned int factor =
                 std::pow(10, findProperty->second.sensorScaleFactor);
             if (sensorThresholds.empty())
@@ -294,17 +383,19 @@
             }
 
             std::string sensorName =
-                std::get<std::string>(findPSUName->second) + " " +
-                findProperty->second.labelTypeName;
+                *psuName + " " + findProperty->second.labelTypeName;
 
-            auto& newSensor = sensors[sensorName];
-            newSensor = nullptr; // destroy old one if it exists
-            newSensor = std::make_unique<PSUSensor>(
+            sensors[sensorName] = std::make_unique<PSUSensor>(
                 sensorPathStr, sensorType, objectServer, dbusConnection, io,
                 sensorName, std::move(sensorThresholds), *interfacePath,
                 findSensorType->second, factor, findProperty->second.maxReading,
                 findProperty->second.minReading);
         }
+
+        // OperationalStatus event
+        combineEvents[*psuName + "OperationalStatus"] =
+            std::make_unique<PSUCombineEvent>(
+                objectServer, io, *psuName, eventPathList, "OperationalStatus");
     }
     return;
 }
@@ -327,6 +418,15 @@
                   {"fan2", PSUProperty("Fan Speed 2", 30000, 0, 0)}};
 
     pwmTable = {{"fan1", "Fan_1"}, {"fan2", "Fan_2"}};
+
+    limitEventMatch = {{"PredictiveFailure", {"max_alarm", "min_alarm"}},
+                       {"Failure", {"crit_alarm", "lcrit_alarm"}}};
+
+    eventMatch = {
+        {"PredictiveFailure", {"power1_alarm"}},
+        {"Failure", {"in2_alarm"}},
+        {"ACLost", {"in1_alarm", "in1_lcrit_alarm"}},
+        {"FanFault", {"fan1_alarm", "fan2_alarm", "fan1_fault", "fan2_fault"}}};
 }
 
 int main(int argc, char** argv)