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