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