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