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