xref: /openbmc/dbus-sensors/src/PSUEvent.cpp (revision eacbfdd1)
1 /*
2 // Copyright (c) 2019 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include "PSUEvent.hpp"
18 
19 #include "SensorPaths.hpp"
20 #include "Utils.hpp"
21 
22 #include <boost/asio/buffer.hpp>
23 #include <boost/asio/error.hpp>
24 #include <boost/asio/io_context.hpp>
25 #include <boost/asio/random_access_file.hpp>
26 #include <boost/container/flat_map.hpp>
27 #include <phosphor-logging/lg2.hpp>
28 #include <sdbusplus/asio/connection.hpp>
29 #include <sdbusplus/asio/object_server.hpp>
30 
31 #include <array>
32 #include <chrono>
33 #include <cstddef>
34 #include <iostream>
35 #include <memory>
36 #include <set>
37 #include <stdexcept>
38 #include <string>
39 #include <utility>
40 #include <vector>
41 
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)42 PSUCombineEvent::PSUCombineEvent(
43     sdbusplus::asio::object_server& objectServer,
44     std::shared_ptr<sdbusplus::asio::connection>& conn,
45     boost::asio::io_context& io, const std::string& psuName,
46     const PowerState& powerState, EventPathList& eventPathList,
47     GroupEventPathList& groupEventPathList, const std::string& combineEventName,
48     double pollRate) :
49     objServer(objectServer)
50 {
51     std::string psuNameEscaped = sensor_paths::escapePathForDbus(psuName);
52     eventInterface = objServer.add_interface(
53         "/xyz/openbmc_project/State/Decorator/" + psuNameEscaped + "_" +
54             combineEventName,
55         "xyz.openbmc_project.State.Decorator.OperationalStatus");
56     eventInterface->register_property("functional", true);
57 
58     if (!eventInterface->initialize())
59     {
60         std::cerr << "error initializing event interface\n";
61     }
62 
63     std::shared_ptr<std::set<std::string>> combineEvent =
64         std::make_shared<std::set<std::string>>();
65     for (const auto& [eventName, paths] : eventPathList)
66     {
67         std::shared_ptr<std::set<std::string>> assert =
68             std::make_shared<std::set<std::string>>();
69         std::shared_ptr<bool> state = std::make_shared<bool>(false);
70 
71         std::string eventPSUName = eventName + psuName;
72         for (const auto& path : paths)
73         {
74             auto p = std::make_shared<PSUSubEvent>(
75                 eventInterface, path, conn, io, powerState, eventName,
76                 eventName, assert, combineEvent, state, psuName, pollRate);
77             p->setupRead();
78 
79             events[eventPSUName].emplace_back(p);
80             asserts.emplace_back(assert);
81             states.emplace_back(state);
82         }
83     }
84 
85     for (const auto& [eventName, groupEvents] : groupEventPathList)
86     {
87         for (const auto& [groupEventName, paths] : groupEvents)
88         {
89             std::shared_ptr<std::set<std::string>> assert =
90                 std::make_shared<std::set<std::string>>();
91             std::shared_ptr<bool> state = std::make_shared<bool>(false);
92 
93             std::string eventPSUName = groupEventName + psuName;
94             for (const auto& path : paths)
95             {
96                 auto p = std::make_shared<PSUSubEvent>(
97                     eventInterface, path, conn, io, powerState, groupEventName,
98                     eventName, assert, combineEvent, state, psuName, pollRate);
99                 p->setupRead();
100                 events[eventPSUName].emplace_back(p);
101 
102                 asserts.emplace_back(assert);
103                 states.emplace_back(state);
104             }
105         }
106     }
107 }
108 
~PSUCombineEvent()109 PSUCombineEvent::~PSUCombineEvent()
110 {
111     // Clear unique_ptr first
112     for (auto& [psuName, subEvents] : events)
113     {
114         for (auto& subEventPtr : subEvents)
115         {
116             subEventPtr.reset();
117         }
118     }
119     events.clear();
120     objServer.remove_interface(eventInterface);
121 }
122 
123 static boost::container::flat_map<std::string,
124                                   std::pair<std::string, std::string>>
125     logID = {
126         {"PredictiveFailure",
127          {"OpenBMC.0.1.PowerSupplyFailurePredicted",
128           "OpenBMC.0.1.PowerSupplyPredictedFailureRecovered"}},
129         {"Failure",
130          {"OpenBMC.0.1.PowerSupplyFailed", "OpenBMC.0.1.PowerSupplyRecovered"}},
131         {"ACLost",
132          {"OpenBMC.0.1.PowerSupplyPowerLost",
133           "OpenBMC.0.1.PowerSupplyPowerRestored"}},
134         {"FanFault",
135          {"OpenBMC.0.1.PowerSupplyFanFailed",
136           "OpenBMC.0.1.PowerSupplyFanRecovered"}},
137         {"ConfigureError",
138          {"OpenBMC.0.1.PowerSupplyConfigurationError",
139           "OpenBMC.0.1.PowerSupplyConfigurationErrorRecovered"}}};
140 
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)141 PSUSubEvent::PSUSubEvent(
142     std::shared_ptr<sdbusplus::asio::dbus_interface> eventInterface,
143     const std::string& path, std::shared_ptr<sdbusplus::asio::connection>& conn,
144     boost::asio::io_context& io, const PowerState& powerState,
145     const std::string& groupEventName, const std::string& eventName,
146     std::shared_ptr<std::set<std::string>> asserts,
147     std::shared_ptr<std::set<std::string>> combineEvent,
148     std::shared_ptr<bool> state, const std::string& psuName, double pollRate) :
149     eventInterface(std::move(eventInterface)),
150     asserts(std::move(asserts)), combineEvent(std::move(combineEvent)),
151     assertState(std::move(state)), path(path), eventName(eventName),
152     readState(powerState), waitTimer(io),
153 
154     inputDev(io, path, boost::asio::random_access_file::read_only),
155     psuName(psuName), groupEventName(groupEventName), systemBus(conn)
156 {
157     buffer = std::make_shared<std::array<char, 128>>();
158     if (pollRate > 0.0)
159     {
160         eventPollMs = static_cast<unsigned int>(pollRate * 1000);
161     }
162 
163     auto found = logID.find(eventName);
164     if (found == logID.end())
165     {
166         assertMessage.clear();
167         deassertMessage.clear();
168     }
169     else
170     {
171         assertMessage = found->second.first;
172         deassertMessage = found->second.second;
173     }
174 
175     auto fanPos = path.find("fan");
176     if (fanPos != std::string::npos)
177     {
178         fanName = path.substr(fanPos);
179         auto fanNamePos = fanName.find('_');
180         if (fanNamePos != std::string::npos)
181         {
182             fanName = fanName.substr(0, fanNamePos);
183         }
184     }
185 }
186 
~PSUSubEvent()187 PSUSubEvent::~PSUSubEvent()
188 {
189     waitTimer.cancel();
190     inputDev.close();
191 }
192 
setupRead()193 void PSUSubEvent::setupRead()
194 {
195     if (!readingStateGood(readState))
196     {
197         // Deassert the event
198         updateValue(0);
199         restartRead();
200         return;
201     }
202     if (!buffer)
203     {
204         std::cerr << "Buffer was invalid?";
205         return;
206     }
207 
208     std::weak_ptr<PSUSubEvent> weakRef = weak_from_this();
209     inputDev.async_read_some_at(
210         0, boost::asio::buffer(buffer->data(), buffer->size() - 1),
211         [weakRef, buffer{buffer}](const boost::system::error_code& ec,
212                                   std::size_t bytesTransferred) {
213         std::shared_ptr<PSUSubEvent> self = weakRef.lock();
214         if (self)
215         {
216             self->handleResponse(ec, bytesTransferred);
217         }
218     });
219 }
220 
restartRead()221 void PSUSubEvent::restartRead()
222 {
223     std::weak_ptr<PSUSubEvent> weakRef = weak_from_this();
224     waitTimer.expires_after(std::chrono::milliseconds(eventPollMs));
225     waitTimer.async_wait([weakRef](const boost::system::error_code& ec) {
226         if (ec == boost::asio::error::operation_aborted)
227         {
228             return;
229         }
230         std::shared_ptr<PSUSubEvent> self = weakRef.lock();
231         if (self)
232         {
233             self->setupRead();
234         }
235     });
236 }
237 
handleResponse(const boost::system::error_code & err,size_t bytesTransferred)238 void PSUSubEvent::handleResponse(const boost::system::error_code& err,
239                                  size_t bytesTransferred)
240 {
241     if (err == boost::asio::error::operation_aborted)
242     {
243         return;
244     }
245 
246     if ((err == boost::system::errc::bad_file_descriptor) ||
247         (err == boost::asio::error::misc_errors::not_found))
248     {
249         return;
250     }
251     if (!buffer)
252     {
253         std::cerr << "Buffer was invalid?";
254         return;
255     }
256     // null terminate the string so we don't walk off the end
257     std::array<char, 128>& bufferRef = *buffer;
258     bufferRef[bytesTransferred] = '\0';
259 
260     if (!err)
261     {
262         try
263         {
264             int nvalue = std::stoi(bufferRef.data());
265             updateValue(nvalue);
266             errCount = 0;
267         }
268         catch (const std::invalid_argument&)
269         {
270             errCount++;
271         }
272     }
273     else
274     {
275         errCount++;
276     }
277     if (errCount >= warnAfterErrorCount)
278     {
279         if (errCount == warnAfterErrorCount)
280         {
281             std::cerr << "Failure to read event at " << path << "\n";
282         }
283         updateValue(0);
284         errCount++;
285     }
286     restartRead();
287 }
288 
289 // Any of the sub events of one event is asserted, then the event will be
290 // asserted. Only if none of the sub events are asserted, the event will be
291 // deasserted.
updateValue(const int & newValue)292 void PSUSubEvent::updateValue(const int& newValue)
293 {
294     // Take no action if value already equal
295     // Same semantics as Sensor::updateValue(const double&)
296     if (newValue == value)
297     {
298         return;
299     }
300 
301     if (newValue == 0)
302     {
303         // log deassert only after all asserts are gone
304         if (!(*asserts).empty())
305         {
306             auto found = (*asserts).find(path);
307             if (found == (*asserts).end())
308             {
309                 return;
310             }
311             (*asserts).erase(path);
312 
313             return;
314         }
315         if (*assertState)
316         {
317             *assertState = false;
318             auto foundCombine = (*combineEvent).find(groupEventName);
319             if (foundCombine == (*combineEvent).end())
320             {
321                 return;
322             }
323             (*combineEvent).erase(groupEventName);
324             if (!deassertMessage.empty())
325             {
326                 // Fan Failed has two args
327                 if (deassertMessage == "OpenBMC.0.1.PowerSupplyFanRecovered")
328                 {
329                     lg2::info("{EVENT} deassert", "EVENT", eventName,
330                               "REDFISH_MESSAGE_ID", deassertMessage,
331                               "REDFISH_MESSAGE_ARGS",
332                               (psuName + ',' + fanName));
333                 }
334                 else
335                 {
336                     lg2::info("{EVENT} deassert", "EVENT", eventName,
337                               "REDFISH_MESSAGE_ID", deassertMessage,
338                               "REDFISH_MESSAGE_ARGS", psuName);
339                 }
340             }
341 
342             if ((*combineEvent).empty())
343             {
344                 eventInterface->set_property("functional", true);
345             }
346         }
347     }
348     else
349     {
350         std::cerr << "PSUSubEvent asserted by " << path << "\n";
351 
352         if ((!*assertState) && ((*asserts).empty()))
353         {
354             *assertState = true;
355             if (!assertMessage.empty())
356             {
357                 // Fan Failed has two args
358                 if (assertMessage == "OpenBMC.0.1.PowerSupplyFanFailed")
359                 {
360                     lg2::warning("{EVENT} assert", "EVENT", eventName,
361                                  "REDFISH_MESSAGE_ID", assertMessage,
362                                  "REDFISH_MESSAGE_ARGS",
363                                  (psuName + ',' + fanName));
364                 }
365                 else
366                 {
367                     lg2::warning("{EVENT} assert", "EVENT", eventName,
368                                  "REDFISH_MESSAGE_ID", assertMessage,
369                                  "REDFISH_MESSAGE_ARGS", psuName);
370                 }
371             }
372             if ((*combineEvent).empty())
373             {
374                 eventInterface->set_property("functional", false);
375             }
376             (*combineEvent).emplace(groupEventName);
377         }
378         (*asserts).emplace(path);
379     }
380     value = newValue;
381 }
382