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