1 /**
2 * Copyright © 2021 IBM 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 #include "config.h"
17
18 #include "shutdown_alarm_monitor.hpp"
19
20 #include <unistd.h>
21
22 #include <phosphor-logging/lg2.hpp>
23 #include <xyz/openbmc_project/Logging/Entry/server.hpp>
24
25 namespace sensor::monitor
26 {
27 using namespace phosphor::fan::util;
28 using namespace phosphor::fan;
29 namespace fs = std::filesystem;
30
31 const std::map<ShutdownType, std::string> shutdownInterfaces{
32 {ShutdownType::hard, "xyz.openbmc_project.Sensor.Threshold.HardShutdown"},
33 {ShutdownType::soft, "xyz.openbmc_project.Sensor.Threshold.SoftShutdown"}};
34
35 const std::map<ShutdownType, std::map<AlarmType, std::string>> alarmProperties{
36 {ShutdownType::hard,
37 {{AlarmType::low, "HardShutdownAlarmLow"},
38 {AlarmType::high, "HardShutdownAlarmHigh"}}},
39 {ShutdownType::soft,
40 {{AlarmType::low, "SoftShutdownAlarmLow"},
41 {AlarmType::high, "SoftShutdownAlarmHigh"}}}};
42
43 const std::map<ShutdownType, std::chrono::milliseconds> shutdownDelays{
44 {ShutdownType::hard,
45 std::chrono::milliseconds{SHUTDOWN_ALARM_HARD_SHUTDOWN_DELAY_MS}},
46 {ShutdownType::soft,
47 std::chrono::milliseconds{SHUTDOWN_ALARM_SOFT_SHUTDOWN_DELAY_MS}}};
48
49 const std::map<ShutdownType, std::map<AlarmType, std::string>> alarmEventLogs{
50 {ShutdownType::hard,
51 {{AlarmType::high,
52 "xyz.openbmc_project.Sensor.Threshold.Error.HardShutdownAlarmHigh"},
53 {AlarmType::low, "xyz.openbmc_project.Sensor.Threshold.Error."
54 "HardShutdownAlarmLow"}}},
55 {ShutdownType::soft,
56 {{AlarmType::high,
57 "xyz.openbmc_project.Sensor.Threshold.Error.SoftShutdownAlarmHigh"},
58 {AlarmType::low, "xyz.openbmc_project.Sensor.Threshold.Error."
59 "SoftShutdownAlarmLow"}}}};
60
61 const std::map<ShutdownType, std::map<AlarmType, std::string>>
62 alarmClearEventLogs{
63 {ShutdownType::hard,
64 {{AlarmType::high, "xyz.openbmc_project.Sensor.Threshold.Error."
65 "HardShutdownAlarmHighClear"},
66 {AlarmType::low, "xyz.openbmc_project.Sensor.Threshold.Error."
67 "HardShutdownAlarmLowClear"}}},
68 {ShutdownType::soft,
69 {{AlarmType::high, "xyz.openbmc_project.Sensor.Threshold.Error."
70 "SoftShutdownAlarmHighClear"},
71 {AlarmType::low, "xyz.openbmc_project.Sensor.Threshold.Error."
72 "SoftShutdownAlarmLowClear"}}}};
73
74 constexpr auto systemdService = "org.freedesktop.systemd1";
75 constexpr auto systemdPath = "/org/freedesktop/systemd1";
76 constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager";
77 constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value";
78 constexpr auto valueProperty = "Value";
79 const auto loggingService = "xyz.openbmc_project.Logging";
80 const auto loggingPath = "/xyz/openbmc_project/logging";
81 const auto loggingCreateIface = "xyz.openbmc_project.Logging.Create";
82
83 using namespace sdbusplus::bus::match;
84
ShutdownAlarmMonitor(sdbusplus::bus_t & bus,sdeventplus::Event & event,std::shared_ptr<PowerState> powerState)85 ShutdownAlarmMonitor::ShutdownAlarmMonitor(
86 sdbusplus::bus_t& bus, sdeventplus::Event& event,
87 std::shared_ptr<PowerState> powerState) :
88 bus(bus), event(event), _powerState(std::move(powerState)),
89 hardShutdownMatch(bus,
90 "type='signal',member='PropertiesChanged',"
91 "path_namespace='/xyz/openbmc_project/sensors',"
92 "arg0='" +
93 shutdownInterfaces.at(ShutdownType::hard) + "'",
94 std::bind(&ShutdownAlarmMonitor::propertiesChanged, this,
95 std::placeholders::_1)),
96 softShutdownMatch(bus,
97 "type='signal',member='PropertiesChanged',"
98 "path_namespace='/xyz/openbmc_project/sensors',"
99 "arg0='" +
100 shutdownInterfaces.at(ShutdownType::soft) + "'",
101 std::bind(&ShutdownAlarmMonitor::propertiesChanged, this,
102 std::placeholders::_1))
103 {
104 _powerState->addCallback("shutdownMon",
105 std::bind(&ShutdownAlarmMonitor::powerStateChanged,
106 this, std::placeholders::_1));
107 findAlarms();
108
109 if (_powerState->isPowerOn())
110 {
111 checkAlarms();
112
113 // Get rid of any previous saved timestamps that don't
114 // apply anymore.
115 timestamps.prune(alarms);
116 }
117 else
118 {
119 timestamps.clear();
120 }
121 }
122
findAlarms()123 void ShutdownAlarmMonitor::findAlarms()
124 {
125 // Find all shutdown threshold ifaces currently on D-Bus.
126 for (const auto& [shutdownType, interface] : shutdownInterfaces)
127 {
128 auto paths = SDBusPlus::getSubTreePathsRaw(bus, "/", interface, 0);
129
130 auto shutdownType2 = shutdownType;
131
132 std::for_each(
133 paths.begin(), paths.end(),
134 [this, shutdownType2](const auto& path) {
135 alarms.emplace(AlarmKey{path, shutdownType2, AlarmType::high},
136 nullptr);
137 alarms.emplace(AlarmKey{path, shutdownType2, AlarmType::low},
138 nullptr);
139 });
140 }
141 }
142
checkAlarms()143 void ShutdownAlarmMonitor::checkAlarms()
144 {
145 for (auto& [alarmKey, timer] : alarms)
146 {
147 const auto& [sensorPath, shutdownType, alarmType] = alarmKey;
148 const auto& interface = shutdownInterfaces.at(shutdownType);
149 auto propertyName = alarmProperties.at(shutdownType).at(alarmType);
150 bool value;
151
152 try
153 {
154 value = SDBusPlus::getProperty<bool>(bus, sensorPath, interface,
155 propertyName);
156 }
157 catch (const DBusServiceError& e)
158 {
159 // The sensor isn't on D-Bus anymore
160 lg2::info("No {INTERFACE} interface on {SENSOR_PATH} anymore.",
161 "INTERFACE", interface, "SENSOR_PATH", sensorPath);
162 continue;
163 }
164
165 checkAlarm(value, alarmKey);
166 }
167 }
168
propertiesChanged(sdbusplus::message_t & message)169 void ShutdownAlarmMonitor::propertiesChanged(sdbusplus::message_t& message)
170 {
171 std::map<std::string, std::variant<bool>> properties;
172 std::string interface;
173
174 if (!_powerState->isPowerOn())
175 {
176 return;
177 }
178
179 message.read(interface, properties);
180
181 auto type = getShutdownType(interface);
182 if (!type)
183 {
184 return;
185 }
186
187 std::string sensorPath = message.get_path();
188
189 const auto& lowAlarmName = alarmProperties.at(*type).at(AlarmType::low);
190 if (properties.count(lowAlarmName) > 0)
191 {
192 AlarmKey alarmKey{sensorPath, *type, AlarmType::low};
193 auto alarm = alarms.find(alarmKey);
194 if (alarm == alarms.end())
195 {
196 alarms.emplace(alarmKey, nullptr);
197 }
198 checkAlarm(std::get<bool>(properties.at(lowAlarmName)), alarmKey);
199 }
200
201 const auto& highAlarmName = alarmProperties.at(*type).at(AlarmType::high);
202 if (properties.count(highAlarmName) > 0)
203 {
204 AlarmKey alarmKey{sensorPath, *type, AlarmType::high};
205 auto alarm = alarms.find(alarmKey);
206 if (alarm == alarms.end())
207 {
208 alarms.emplace(alarmKey, nullptr);
209 }
210 checkAlarm(std::get<bool>(properties.at(highAlarmName)), alarmKey);
211 }
212 }
213
checkAlarm(bool value,const AlarmKey & alarmKey)214 void ShutdownAlarmMonitor::checkAlarm(bool value, const AlarmKey& alarmKey)
215 {
216 auto alarm = alarms.find(alarmKey);
217 if (alarm == alarms.end())
218 {
219 return;
220 }
221
222 // Start or stop the timer if necessary.
223 auto& timer = alarm->second;
224 if (value)
225 {
226 if (!timer)
227 {
228 startTimer(alarmKey);
229 }
230 }
231 else
232 {
233 if (timer)
234 {
235 stopTimer(alarmKey);
236 }
237 }
238 }
239
startTimer(const AlarmKey & alarmKey)240 void ShutdownAlarmMonitor::startTimer(const AlarmKey& alarmKey)
241 {
242 const auto& [sensorPath, shutdownType, alarmType] = alarmKey;
243 const auto& propertyName = alarmProperties.at(shutdownType).at(alarmType);
244 std::chrono::milliseconds shutdownDelay{shutdownDelays.at(shutdownType)};
245 std::optional<double> value;
246
247 auto alarm = alarms.find(alarmKey);
248 if (alarm == alarms.end())
249 {
250 throw std::runtime_error("Couldn't find alarm inside startTimer");
251 }
252
253 try
254 {
255 value = SDBusPlus::getProperty<double>(bus, sensorPath, valueInterface,
256 valueProperty);
257 }
258 catch (const DBusServiceError& e)
259 {
260 // If the sensor was just added, the Value interface for it may
261 // not be in the mapper yet. This could only happen if the sensor
262 // application was started with power up and the value exceeded the
263 // threshold immediately.
264 }
265
266 createEventLog(alarmKey, true, value);
267
268 uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(
269 std::chrono::system_clock::now().time_since_epoch())
270 .count();
271
272 // If there is a saved timestamp for this timer, then we were restarted
273 // while the timer was running. Calculate the remaining time to use
274 // for the timer.
275 auto previousStartTime = timestamps.get().find(alarmKey);
276 if (previousStartTime != timestamps.get().end())
277 {
278 const uint64_t& original = previousStartTime->second;
279
280 lg2::info("Found previously running {PROPERTY_NAME} timer "
281 "for {SENSOR_PATH} with start time {START_TIME}",
282 "PROPERTY_NAME", propertyName, "SENSOR_PATH", sensorPath,
283 "START_TIME", original);
284 // Sanity check it isn't total garbage.
285 if (now > original)
286 {
287 uint64_t remainingTime = 0;
288 auto elapsedTime = now - original;
289
290 if (elapsedTime < static_cast<uint64_t>(shutdownDelay.count()))
291 {
292 remainingTime = static_cast<uint64_t>(shutdownDelay.count()) -
293 elapsedTime;
294 }
295
296 shutdownDelay = std::chrono::milliseconds{remainingTime};
297 }
298 else
299 {
300 lg2::warning(
301 "Restarting {PROPERTY_NAME} shutdown timer for {SENSOR_PATH} for full "
302 "time because saved time {SAVED_TIME} is after current time {CURRENT_TIME}",
303 "PROPERTY_NAME", propertyName, "SENSOR_PATH", sensorPath,
304 "SAVED_TIME", original, "CURRENT_TIME", now);
305 }
306 }
307
308 lg2::info(
309 "Starting {TIME}ms {PROPERTY_NAME} shutdown timer due to sensor {SENSOR_PATH} value {VALUE}",
310 "TIME", shutdownDelay.count(), "PROPERTY_NAME", propertyName,
311 "SENSOR_PATH", sensorPath, "VALUE", *value);
312
313 auto& timer = alarm->second;
314
315 timer = std::make_unique<
316 sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>(
317 event, std::bind(&ShutdownAlarmMonitor::timerExpired, this, alarmKey));
318
319 timer->restartOnce(shutdownDelay);
320
321 // Note that if this key is already in the timestamps map because
322 // the timer was already running the timestamp wil not be updated.
323 timestamps.add(alarmKey, now);
324 }
325
stopTimer(const AlarmKey & alarmKey)326 void ShutdownAlarmMonitor::stopTimer(const AlarmKey& alarmKey)
327 {
328 const auto& [sensorPath, shutdownType, alarmType] = alarmKey;
329 const auto& propertyName = alarmProperties.at(shutdownType).at(alarmType);
330
331 auto value = SDBusPlus::getProperty<double>(bus, sensorPath, valueInterface,
332 valueProperty);
333
334 auto alarm = alarms.find(alarmKey);
335 if (alarm == alarms.end())
336 {
337 throw std::runtime_error("Couldn't find alarm inside stopTimer");
338 }
339
340 createEventLog(alarmKey, false, value);
341
342 lg2::info(
343 "Stopping {PROPERTY_NAME} shutdown timer due to sensor {SENSOR_PATH} value {VALUE}",
344 "PROPERTY_NAME", propertyName, "SENSOR_PATH", sensorPath, "VALUE",
345 value);
346 auto& timer = alarm->second;
347 timer->setEnabled(false);
348 timer.reset();
349
350 timestamps.erase(alarmKey);
351 }
352
createBmcDump() const353 void ShutdownAlarmMonitor::createBmcDump() const
354 {
355 try
356 {
357 util::SDBusPlus::callMethod(
358 "xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump/bmc",
359 "xyz.openbmc_project.Dump.Create", "CreateDump",
360 std::vector<
361 std::pair<std::string, std::variant<std::string, uint64_t>>>());
362 }
363 catch (const std::exception& e)
364 {
365 lg2::error("Caught exception while creating BMC dump: {ERROR}", "ERROR",
366 e);
367 }
368 }
369
timerExpired(const AlarmKey & alarmKey)370 void ShutdownAlarmMonitor::timerExpired(const AlarmKey& alarmKey)
371 {
372 const auto& [sensorPath, shutdownType, alarmType] = alarmKey;
373 const auto& propertyName = alarmProperties.at(shutdownType).at(alarmType);
374
375 auto value = SDBusPlus::getProperty<double>(bus, sensorPath, valueInterface,
376 valueProperty);
377
378 lg2::error(
379 "The {PROPERTY_NAME} shutdown timer expired for sensor {SENSOR_PATH}, shutting down",
380 "PROPERTY_NAME", propertyName, "SENSOR_PATH", sensorPath);
381
382 // Re-send the event log. If someone didn't want this it could be
383 // wrapped by a compile option.
384 createEventLog(alarmKey, true, value, true);
385
386 SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface,
387 "StartUnit", "obmc-chassis-hard-poweroff@0.target",
388 "replace");
389
390 timestamps.erase(alarmKey);
391 createBmcDump();
392 }
393
powerStateChanged(bool powerStateOn)394 void ShutdownAlarmMonitor::powerStateChanged(bool powerStateOn)
395 {
396 if (powerStateOn)
397 {
398 checkAlarms();
399 }
400 else
401 {
402 timestamps.clear();
403
404 // Cancel and delete all timers
405 std::for_each(alarms.begin(), alarms.end(), [](auto& alarm) {
406 auto& timer = alarm.second;
407 if (timer)
408 {
409 timer->setEnabled(false);
410 timer.reset();
411 }
412 });
413 }
414 }
415
createEventLog(const AlarmKey & alarmKey,bool alarmValue,const std::optional<double> & sensorValue,bool isPowerOffError)416 void ShutdownAlarmMonitor::createEventLog(
417 const AlarmKey& alarmKey, bool alarmValue,
418 const std::optional<double>& sensorValue, bool isPowerOffError)
419 {
420 using namespace sdbusplus::xyz::openbmc_project::Logging::server;
421 const auto& [sensorPath, shutdownType, alarmType] = alarmKey;
422 std::map<std::string, std::string> ad{{"SENSOR_NAME", sensorPath},
423 {"_PID", std::to_string(getpid())}};
424
425 std::string errorName =
426 (alarmValue) ? alarmEventLogs.at(shutdownType).at(alarmType)
427 : alarmClearEventLogs.at(shutdownType).at(alarmType);
428
429 // severity = Critical if a power off
430 // severity = Error if alarm was asserted
431 // severity = Informational if alarm was deasserted
432 Entry::Level severity = Entry::Level::Error;
433 if (isPowerOffError)
434 {
435 severity = Entry::Level::Critical;
436 }
437 else if (!alarmValue)
438 {
439 severity = Entry::Level::Informational;
440 }
441
442 if (sensorValue)
443 {
444 ad.emplace("SENSOR_VALUE", std::to_string(*sensorValue));
445 }
446
447 // If this is a power off, specify that it's a power
448 // fault and a system termination. This is used by some
449 // implementations for service reasons.
450 if (isPowerOffError)
451 {
452 ad.emplace("SEVERITY_DETAIL", "SYSTEM_TERM");
453 }
454
455 SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface,
456 "Create", errorName, convertForMessage(severity), ad);
457 }
458
getShutdownType(const std::string & interface) const459 std::optional<ShutdownType> ShutdownAlarmMonitor::getShutdownType(
460 const std::string& interface) const
461 {
462 auto it = std::find_if(
463 shutdownInterfaces.begin(), shutdownInterfaces.end(),
464 [interface](const auto& a) { return a.second == interface; });
465
466 if (it == shutdownInterfaces.end())
467 {
468 return std::nullopt;
469 }
470
471 return it->first;
472 }
473
474 } // namespace sensor::monitor
475