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)