xref: /openbmc/phosphor-fan-presence/sensor-monitor/shutdown_alarm_monitor.cpp (revision 32c4feff9b1cd1fd4d75bb530761f70f0e5922e0)
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