| /* | 
 | // 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; | 
 | } |