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