|  | /* | 
|  | // 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 "SensorPaths.hpp" | 
|  | #include "Utils.hpp" | 
|  |  | 
|  | #include <boost/asio/buffer.hpp> | 
|  | #include <boost/asio/error.hpp> | 
|  | #include <boost/asio/io_context.hpp> | 
|  | #include <boost/asio/random_access_file.hpp> | 
|  | #include <boost/container/flat_map.hpp> | 
|  | #include <phosphor-logging/lg2.hpp> | 
|  | #include <sdbusplus/asio/connection.hpp> | 
|  | #include <sdbusplus/asio/object_server.hpp> | 
|  |  | 
|  | #include <array> | 
|  | #include <chrono> | 
|  | #include <cstddef> | 
|  | #include <memory> | 
|  | #include <set> | 
|  | #include <stdexcept> | 
|  | #include <string> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | PSUCombineEvent::PSUCombineEvent( | 
|  | sdbusplus::asio::object_server& objectServer, | 
|  | std::shared_ptr<sdbusplus::asio::connection>& conn, | 
|  | boost::asio::io_context& io, const std::string& psuName, | 
|  | const PowerState& powerState, EventPathList& eventPathList, | 
|  | GroupEventPathList& groupEventPathList, const std::string& combineEventName, | 
|  | double pollRate) : objServer(objectServer) | 
|  | { | 
|  | std::string psuNameEscaped = sensor_paths::escapePathForDbus(psuName); | 
|  | eventInterface = objServer.add_interface( | 
|  | "/xyz/openbmc_project/State/Decorator/" + psuNameEscaped + "_" + | 
|  | combineEventName, | 
|  | "xyz.openbmc_project.State.Decorator.OperationalStatus"); | 
|  | eventInterface->register_property("functional", true); | 
|  |  | 
|  | if (!eventInterface->initialize()) | 
|  | { | 
|  | lg2::error("error initializing event interface"); | 
|  | } | 
|  |  | 
|  | std::shared_ptr<std::set<std::string>> combineEvent = | 
|  | std::make_shared<std::set<std::string>>(); | 
|  | for (const auto& [eventName, paths] : eventPathList) | 
|  | { | 
|  | 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); | 
|  |  | 
|  | std::string eventPSUName = eventName + psuName; | 
|  | for (const auto& path : paths) | 
|  | { | 
|  | auto p = std::make_shared<PSUSubEvent>( | 
|  | eventInterface, path, conn, io, powerState, eventName, | 
|  | eventName, assert, combineEvent, state, psuName, pollRate); | 
|  | p->setupRead(); | 
|  |  | 
|  | events[eventPSUName].emplace_back(p); | 
|  | asserts.emplace_back(assert); | 
|  | states.emplace_back(state); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (const auto& [eventName, groupEvents] : groupEventPathList) | 
|  | { | 
|  | for (const auto& [groupEventName, paths] : groupEvents) | 
|  | { | 
|  | 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); | 
|  |  | 
|  | std::string eventPSUName = groupEventName + psuName; | 
|  | for (const auto& path : paths) | 
|  | { | 
|  | auto p = std::make_shared<PSUSubEvent>( | 
|  | eventInterface, path, conn, io, powerState, groupEventName, | 
|  | eventName, assert, combineEvent, state, psuName, pollRate); | 
|  | p->setupRead(); | 
|  | events[eventPSUName].emplace_back(p); | 
|  |  | 
|  | asserts.emplace_back(assert); | 
|  | states.emplace_back(state); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | PSUCombineEvent::~PSUCombineEvent() | 
|  | { | 
|  | // Clear unique_ptr first | 
|  | for (auto& [psuName, subEvents] : events) | 
|  | { | 
|  | for (auto& subEventPtr : subEvents) | 
|  | { | 
|  | subEventPtr.reset(); | 
|  | } | 
|  | } | 
|  | events.clear(); | 
|  | objServer.remove_interface(eventInterface); | 
|  | } | 
|  |  | 
|  | static boost::container::flat_map<std::string, | 
|  | std::pair<std::string, std::string>> | 
|  | logID = { | 
|  | {"PredictiveFailure", | 
|  | {"OpenBMC.0.1.PowerSupplyFailurePredicted", | 
|  | "OpenBMC.0.1.PowerSupplyPredictedFailureRecovered"}}, | 
|  | {"Failure", | 
|  | {"OpenBMC.0.1.PowerSupplyFailed", "OpenBMC.0.1.PowerSupplyRecovered"}}, | 
|  | {"ACLost", | 
|  | {"OpenBMC.0.1.PowerSupplyPowerLost", | 
|  | "OpenBMC.0.1.PowerSupplyPowerRestored"}}, | 
|  | {"FanFault", | 
|  | {"OpenBMC.0.1.PowerSupplyFanFailed", | 
|  | "OpenBMC.0.1.PowerSupplyFanRecovered"}}, | 
|  | {"ConfigureError", | 
|  | {"OpenBMC.0.1.PowerSupplyConfigurationError", | 
|  | "OpenBMC.0.1.PowerSupplyConfigurationErrorRecovered"}}}; | 
|  |  | 
|  | PSUSubEvent::PSUSubEvent( | 
|  | std::shared_ptr<sdbusplus::asio::dbus_interface> eventInterface, | 
|  | const std::string& path, std::shared_ptr<sdbusplus::asio::connection>& conn, | 
|  | boost::asio::io_context& io, const PowerState& powerState, | 
|  | const std::string& groupEventName, 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, const std::string& psuName, double pollRate) : | 
|  | eventInterface(std::move(eventInterface)), asserts(std::move(asserts)), | 
|  | combineEvent(std::move(combineEvent)), assertState(std::move(state)), | 
|  | path(path), eventName(eventName), readState(powerState), | 
|  | inputDev(io, path, boost::asio::random_access_file::read_only), | 
|  | waitTimer(io), psuName(psuName), groupEventName(groupEventName), | 
|  | systemBus(conn) | 
|  | { | 
|  | buffer = std::make_shared<std::array<char, 128>>(); | 
|  | if (pollRate > 0.0) | 
|  | { | 
|  | eventPollMs = static_cast<unsigned int>(pollRate * 1000); | 
|  | } | 
|  |  | 
|  | auto found = logID.find(eventName); | 
|  | if (found == logID.end()) | 
|  | { | 
|  | assertMessage.clear(); | 
|  | deassertMessage.clear(); | 
|  | } | 
|  | else | 
|  | { | 
|  | assertMessage = found->second.first; | 
|  | deassertMessage = found->second.second; | 
|  | } | 
|  |  | 
|  | auto fanPos = path.find("fan"); | 
|  | if (fanPos != std::string::npos) | 
|  | { | 
|  | fanName = path.substr(fanPos); | 
|  | auto fanNamePos = fanName.find('_'); | 
|  | if (fanNamePos != std::string::npos) | 
|  | { | 
|  | fanName = fanName.substr(0, fanNamePos); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | PSUSubEvent::~PSUSubEvent() | 
|  | { | 
|  | waitTimer.cancel(); | 
|  | inputDev.close(); | 
|  | } | 
|  |  | 
|  | void PSUSubEvent::setupRead() | 
|  | { | 
|  | if (!readingStateGood(readState)) | 
|  | { | 
|  | // Deassert the event | 
|  | updateValue(0); | 
|  | restartRead(); | 
|  | return; | 
|  | } | 
|  | if (!buffer) | 
|  | { | 
|  | lg2::error("Buffer was invalid?"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::weak_ptr<PSUSubEvent> weakRef = weak_from_this(); | 
|  | inputDev.async_read_some_at( | 
|  | 0, boost::asio::buffer(buffer->data(), buffer->size() - 1), | 
|  | [weakRef, buffer{buffer}](const boost::system::error_code& ec, | 
|  | std::size_t bytesTransferred) { | 
|  | std::shared_ptr<PSUSubEvent> self = weakRef.lock(); | 
|  | if (self) | 
|  | { | 
|  | self->handleResponse(ec, bytesTransferred); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | void PSUSubEvent::restartRead() | 
|  | { | 
|  | std::weak_ptr<PSUSubEvent> weakRef = weak_from_this(); | 
|  | waitTimer.expires_after(std::chrono::milliseconds(eventPollMs)); | 
|  | waitTimer.async_wait([weakRef](const boost::system::error_code& ec) { | 
|  | if (ec == boost::asio::error::operation_aborted) | 
|  | { | 
|  | return; | 
|  | } | 
|  | std::shared_ptr<PSUSubEvent> self = weakRef.lock(); | 
|  | if (self) | 
|  | { | 
|  | self->setupRead(); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | void PSUSubEvent::handleResponse(const boost::system::error_code& err, | 
|  | size_t bytesTransferred) | 
|  | { | 
|  | if (err == boost::asio::error::operation_aborted) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if ((err == boost::system::errc::bad_file_descriptor) || | 
|  | (err == boost::asio::error::misc_errors::not_found)) | 
|  | { | 
|  | return; | 
|  | } | 
|  | if (!buffer) | 
|  | { | 
|  | lg2::error("Buffer was invalid?"); | 
|  | return; | 
|  | } | 
|  | // null terminate the string so we don't walk off the end | 
|  | std::array<char, 128>& bufferRef = *buffer; | 
|  | bufferRef[bytesTransferred] = '\0'; | 
|  |  | 
|  | if (!err) | 
|  | { | 
|  | try | 
|  | { | 
|  | int nvalue = std::stoi(bufferRef.data()); | 
|  | updateValue(nvalue); | 
|  | errCount = 0; | 
|  | } | 
|  | catch (const std::invalid_argument&) | 
|  | { | 
|  | errCount++; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | errCount++; | 
|  | } | 
|  | if (errCount >= warnAfterErrorCount) | 
|  | { | 
|  | if (errCount == warnAfterErrorCount) | 
|  | { | 
|  | lg2::error("Failure to read event at '{PATH}'", "PATH", path); | 
|  | } | 
|  | updateValue(0); | 
|  | errCount++; | 
|  | } | 
|  | restartRead(); | 
|  | } | 
|  |  | 
|  | // 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) | 
|  | { | 
|  | // Take no action if value already equal | 
|  | // Same semantics as Sensor::updateValue(const double&) | 
|  | if (newValue == value) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (newValue == 0) | 
|  | { | 
|  | // log deassert only after all asserts are gone | 
|  | if (!(*asserts).empty()) | 
|  | { | 
|  | auto found = (*asserts).find(path); | 
|  | if (found == (*asserts).end()) | 
|  | { | 
|  | return; | 
|  | } | 
|  | (*asserts).erase(path); | 
|  |  | 
|  | return; | 
|  | } | 
|  | if (*assertState) | 
|  | { | 
|  | *assertState = false; | 
|  | auto foundCombine = (*combineEvent).find(groupEventName); | 
|  | if (foundCombine == (*combineEvent).end()) | 
|  | { | 
|  | return; | 
|  | } | 
|  | (*combineEvent).erase(groupEventName); | 
|  | if (!deassertMessage.empty()) | 
|  | { | 
|  | // Fan Failed has two args | 
|  | if (deassertMessage == "OpenBMC.0.1.PowerSupplyFanRecovered") | 
|  | { | 
|  | lg2::info("'{EVENT}' deassert", "EVENT", eventName, | 
|  | "REDFISH_MESSAGE_ID", deassertMessage, | 
|  | "REDFISH_MESSAGE_ARGS", | 
|  | (psuName + ',' + fanName)); | 
|  | } | 
|  | else | 
|  | { | 
|  | lg2::info("'{EVENT}' deassert", "EVENT", eventName, | 
|  | "REDFISH_MESSAGE_ID", deassertMessage, | 
|  | "REDFISH_MESSAGE_ARGS", psuName); | 
|  | } | 
|  | } | 
|  |  | 
|  | if ((*combineEvent).empty()) | 
|  | { | 
|  | eventInterface->set_property("functional", true); | 
|  | } | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | lg2::error("PSUSubEvent asserted by '{PATH}'", "PATH", path); | 
|  |  | 
|  | if ((!*assertState) && ((*asserts).empty())) | 
|  | { | 
|  | *assertState = true; | 
|  | if (!assertMessage.empty()) | 
|  | { | 
|  | // Fan Failed has two args | 
|  | if (assertMessage == "OpenBMC.0.1.PowerSupplyFanFailed") | 
|  | { | 
|  | lg2::warning("'{EVENT}' assert", "EVENT", eventName, | 
|  | "REDFISH_MESSAGE_ID", assertMessage, | 
|  | "REDFISH_MESSAGE_ARGS", | 
|  | (psuName + ',' + fanName)); | 
|  | } | 
|  | else | 
|  | { | 
|  | lg2::warning("'{EVENT}' assert", "EVENT", eventName, | 
|  | "REDFISH_MESSAGE_ID", assertMessage, | 
|  | "REDFISH_MESSAGE_ARGS", psuName); | 
|  | } | 
|  | } | 
|  | if ((*combineEvent).empty()) | 
|  | { | 
|  | eventInterface->set_property("functional", false); | 
|  | } | 
|  | (*combineEvent).emplace(groupEventName); | 
|  | } | 
|  | (*asserts).emplace(path); | 
|  | } | 
|  | value = newValue; | 
|  | } |