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 <fmt/format.h>
21 
22 #include <phosphor-logging/log.hpp>
23 #include <xyz/openbmc_project/Logging/Entry/server.hpp>
24 
25 namespace sensor::monitor
26 {
27 using namespace phosphor::logging;
28 using namespace phosphor::fan::util;
29 using namespace phosphor::fan;
30 namespace fs = std::filesystem;
31 
32 const std::map<ShutdownType, std::string> shutdownInterfaces{
33     {ShutdownType::hard, "xyz.openbmc_project.Sensor.Threshold.HardShutdown"},
34     {ShutdownType::soft, "xyz.openbmc_project.Sensor.Threshold.SoftShutdown"}};
35 
36 const std::map<ShutdownType, std::map<AlarmType, std::string>> alarmProperties{
37     {ShutdownType::hard,
38      {{AlarmType::low, "HardShutdownAlarmLow"},
39       {AlarmType::high, "HardShutdownAlarmHigh"}}},
40     {ShutdownType::soft,
41      {{AlarmType::low, "SoftShutdownAlarmLow"},
42       {AlarmType::high, "SoftShutdownAlarmHigh"}}}};
43 
44 const std::map<ShutdownType, std::chrono::milliseconds> shutdownDelays{
45     {ShutdownType::hard,
46      std::chrono::milliseconds{SHUTDOWN_ALARM_HARD_SHUTDOWN_DELAY_MS}},
47     {ShutdownType::soft,
48      std::chrono::milliseconds{SHUTDOWN_ALARM_SOFT_SHUTDOWN_DELAY_MS}}};
49 
50 const std::map<ShutdownType, std::map<AlarmType, std::string>> alarmEventLogs{
51     {ShutdownType::hard,
52      {{AlarmType::high,
53        "xyz.openbmc_project.Sensor.Threshold.Error.HardShutdownAlarmHigh"},
54       {AlarmType::low, "xyz.openbmc_project.Sensor.Threshold.Error."
55                        "HardShutdownAlarmLow"}}},
56     {ShutdownType::soft,
57      {{AlarmType::high,
58        "xyz.openbmc_project.Sensor.Threshold.Error.SoftShutdownAlarmHigh"},
59       {AlarmType::low, "xyz.openbmc_project.Sensor.Threshold.Error."
60                        "SoftShutdownAlarmLow"}}}};
61 
62 const std::map<ShutdownType, std::map<AlarmType, std::string>>
63     alarmClearEventLogs{
64         {ShutdownType::hard,
65          {{AlarmType::high, "xyz.openbmc_project.Sensor.Threshold.Error."
66                             "HardShutdownAlarmHighClear"},
67           {AlarmType::low, "xyz.openbmc_project.Sensor.Threshold.Error."
68                            "HardShutdownAlarmLowClear"}}},
69         {ShutdownType::soft,
70          {{AlarmType::high, "xyz.openbmc_project.Sensor.Threshold.Error."
71                             "SoftShutdownAlarmHighClear"},
72           {AlarmType::low, "xyz.openbmc_project.Sensor.Threshold.Error."
73                            "SoftShutdownAlarmLowClear"}}}};
74 
75 constexpr auto systemdService = "org.freedesktop.systemd1";
76 constexpr auto systemdPath = "/org/freedesktop/systemd1";
77 constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager";
78 constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value";
79 constexpr auto valueProperty = "Value";
80 const auto loggingService = "xyz.openbmc_project.Logging";
81 const auto loggingPath = "/xyz/openbmc_project/logging";
82 const auto loggingCreateIface = "xyz.openbmc_project.Logging.Create";
83 
84 using namespace sdbusplus::bus::match;
85 
86 ShutdownAlarmMonitor::ShutdownAlarmMonitor(sdbusplus::bus::bus& bus,
87                                            sdeventplus::Event& event) :
88     bus(bus),
89     event(event),
90     hardShutdownMatch(bus,
91                       "type='signal',member='PropertiesChanged',"
92                       "path_namespace='/xyz/openbmc_project/sensors',"
93                       "arg0='" +
94                           shutdownInterfaces.at(ShutdownType::soft) + "'",
95                       std::bind(&ShutdownAlarmMonitor::propertiesChanged, this,
96                                 std::placeholders::_1)),
97     softShutdownMatch(bus,
98                       "type='signal',member='PropertiesChanged',"
99                       "path_namespace='/xyz/openbmc_project/sensors',"
100                       "arg0='" +
101                           shutdownInterfaces.at(ShutdownType::hard) + "'",
102                       std::bind(&ShutdownAlarmMonitor::propertiesChanged, this,
103                                 std::placeholders::_1)),
104     _powerState(std::make_unique<PGoodState>(
105         bus, std::bind(&ShutdownAlarmMonitor::powerStateChanged, this,
106                        std::placeholders::_1)))
107 {
108     findAlarms();
109 
110     if (_powerState->isPowerOn())
111     {
112         checkAlarms();
113 
114         // Get rid of any previous saved timestamps that don't
115         // apply anymore.
116         timestamps.prune(alarms);
117     }
118     else
119     {
120         timestamps.clear();
121     }
122 }
123 
124 void ShutdownAlarmMonitor::findAlarms()
125 {
126     // Find all shutdown threshold ifaces currently on D-Bus.
127     for (const auto& [shutdownType, interface] : shutdownInterfaces)
128     {
129         auto paths = SDBusPlus::getSubTreePathsRaw(bus, "/", interface, 0);
130 
131         std::for_each(
132             paths.begin(), paths.end(), [this, shutdownType](const auto& path) {
133                 alarms.emplace(AlarmKey{path, shutdownType, AlarmType::high},
134                                nullptr);
135                 alarms.emplace(AlarmKey{path, shutdownType, AlarmType::low},
136                                nullptr);
137             });
138     }
139 }
140 
141 void ShutdownAlarmMonitor::checkAlarms()
142 {
143     for (auto& [alarmKey, timer] : alarms)
144     {
145         const auto& [sensorPath, shutdownType, alarmType] = alarmKey;
146         const auto& interface = shutdownInterfaces.at(shutdownType);
147         auto propertyName = alarmProperties.at(shutdownType).at(alarmType);
148         bool value;
149 
150         try
151         {
152             value = SDBusPlus::getProperty<bool>(bus, sensorPath, interface,
153                                                  propertyName);
154         }
155         catch (const DBusServiceError& e)
156         {
157             // The sensor isn't on D-Bus anymore
158             log<level::INFO>(fmt::format("No {} interface on {} anymore.",
159                                          interface, sensorPath)
160                                  .c_str());
161             continue;
162         }
163 
164         checkAlarm(value, alarmKey);
165     }
166 }
167 
168 void ShutdownAlarmMonitor::propertiesChanged(
169     sdbusplus::message::message& 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 
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 
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         log<level::INFO>(fmt::format("Found previously running {} timer "
281                                      "for {} with start time {}",
282                                      propertyName, sensorPath, original)
283                              .c_str());
284 
285         // Sanity check it isn't total garbage.
286         if (now > original)
287         {
288             uint64_t remainingTime = 0;
289             auto elapsedTime = now - original;
290 
291             if (elapsedTime < static_cast<uint64_t>(shutdownDelay.count()))
292             {
293                 remainingTime =
294                     static_cast<uint64_t>(shutdownDelay.count()) - elapsedTime;
295             }
296 
297             shutdownDelay = std::chrono::milliseconds{remainingTime};
298         }
299         else
300         {
301             log<level::WARNING>(
302                 fmt::format(
303                     "Restarting {} shutdown timer for {} for full "
304                     "time because saved time {} is after current time {}",
305                     propertyName, original, now)
306                     .c_str());
307         }
308     }
309 
310     log<level::INFO>(
311         fmt::format("Starting {}ms {} shutdown timer due to sensor {} value {}",
312                     shutdownDelay.count(), propertyName, sensorPath, *value)
313             .c_str());
314 
315     auto& timer = alarm->second;
316 
317     timer = std::make_unique<
318         sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>(
319         event, std::bind(&ShutdownAlarmMonitor::timerExpired, this, alarmKey));
320 
321     timer->restartOnce(shutdownDelay);
322 
323     // Note that if this key is already in the timestamps map because
324     // the timer was already running the timestamp wil not be updated.
325     timestamps.add(alarmKey, now);
326 }
327 
328 void ShutdownAlarmMonitor::stopTimer(const AlarmKey& alarmKey)
329 {
330     const auto& [sensorPath, shutdownType, alarmType] = alarmKey;
331     const auto& propertyName = alarmProperties.at(shutdownType).at(alarmType);
332 
333     auto value = SDBusPlus::getProperty<double>(bus, sensorPath, valueInterface,
334                                                 valueProperty);
335 
336     auto alarm = alarms.find(alarmKey);
337     if (alarm == alarms.end())
338     {
339         throw std::runtime_error("Couldn't find alarm inside stopTimer");
340     }
341 
342     createEventLog(alarmKey, false, value);
343 
344     log<level::INFO>(
345         fmt::format("Stopping {} shutdown timer due to sensor {} value {}",
346                     propertyName, sensorPath, value)
347             .c_str());
348 
349     auto& timer = alarm->second;
350     timer->setEnabled(false);
351     timer.reset();
352 
353     timestamps.erase(alarmKey);
354 }
355 
356 void ShutdownAlarmMonitor::timerExpired(const AlarmKey& alarmKey)
357 {
358     const auto& [sensorPath, shutdownType, alarmType] = alarmKey;
359     const auto& propertyName = alarmProperties.at(shutdownType).at(alarmType);
360 
361     auto value = SDBusPlus::getProperty<double>(bus, sensorPath, valueInterface,
362                                                 valueProperty);
363 
364     log<level::ERR>(
365         fmt::format(
366             "The {} shutdown timer expired for sensor {}, shutting down",
367             propertyName, sensorPath)
368             .c_str());
369 
370     // Re-send the event log.  If someone didn't want this it could be
371     // wrapped by a compile option.
372     createEventLog(alarmKey, true, value);
373 
374     SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface,
375                           "StartUnit", "obmc-chassis-hard-poweroff@0.target",
376                           "replace");
377 
378     timestamps.erase(alarmKey);
379 }
380 
381 void ShutdownAlarmMonitor::powerStateChanged(bool powerStateOn)
382 {
383     if (powerStateOn)
384     {
385         checkAlarms();
386     }
387     else
388     {
389         timestamps.clear();
390 
391         // Cancel and delete all timers
392         std::for_each(alarms.begin(), alarms.end(), [](auto& alarm) {
393             auto& timer = alarm.second;
394             if (timer)
395             {
396                 timer->setEnabled(false);
397                 timer.reset();
398             }
399         });
400     }
401 }
402 
403 void ShutdownAlarmMonitor::createEventLog(
404     const AlarmKey& alarmKey, bool alarmValue,
405     const std::optional<double>& sensorValue)
406 {
407     using namespace sdbusplus::xyz::openbmc_project::Logging::server;
408     const auto& [sensorPath, shutdownType, alarmType] = alarmKey;
409     std::map<std::string, std::string> ad{{"SENSOR_NAME", sensorPath}};
410 
411     std::string errorName =
412         (alarmValue) ? alarmEventLogs.at(shutdownType).at(alarmType)
413                      : alarmClearEventLogs.at(shutdownType).at(alarmType);
414 
415     Entry::Level severity =
416         (alarmValue) ? Entry::Level::Error : Entry::Level::Informational;
417 
418     if (sensorValue)
419     {
420         ad.emplace("SENSOR_VALUE", std::to_string(*sensorValue));
421     }
422 
423     SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface,
424                           "Create", errorName, convertForMessage(severity), ad);
425 }
426 
427 std::optional<ShutdownType>
428     ShutdownAlarmMonitor::getShutdownType(const std::string& interface) const
429 {
430     auto it = std::find_if(
431         shutdownInterfaces.begin(), shutdownInterfaces.end(),
432         [interface](const auto& a) { return a.second == interface; });
433 
434     if (it == shutdownInterfaces.end())
435     {
436         return std::nullopt;
437     }
438 
439     return it->first;
440 }
441 
442 } // namespace sensor::monitor
443