/** * Copyright © 2021 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #include "shutdown_alarm_monitor.hpp" #include #include #include #include namespace sensor::monitor { using namespace phosphor::logging; using namespace phosphor::fan::util; using namespace phosphor::fan; namespace fs = std::filesystem; const std::map shutdownInterfaces{ {ShutdownType::hard, "xyz.openbmc_project.Sensor.Threshold.HardShutdown"}, {ShutdownType::soft, "xyz.openbmc_project.Sensor.Threshold.SoftShutdown"}}; const std::map> alarmProperties{ {ShutdownType::hard, {{AlarmType::low, "HardShutdownAlarmLow"}, {AlarmType::high, "HardShutdownAlarmHigh"}}}, {ShutdownType::soft, {{AlarmType::low, "SoftShutdownAlarmLow"}, {AlarmType::high, "SoftShutdownAlarmHigh"}}}}; const std::map shutdownDelays{ {ShutdownType::hard, std::chrono::milliseconds{SHUTDOWN_ALARM_HARD_SHUTDOWN_DELAY_MS}}, {ShutdownType::soft, std::chrono::milliseconds{SHUTDOWN_ALARM_SOFT_SHUTDOWN_DELAY_MS}}}; const std::map> alarmEventLogs{ {ShutdownType::hard, {{AlarmType::high, "xyz.openbmc_project.Sensor.Threshold.Error.HardShutdownAlarmHigh"}, {AlarmType::low, "xyz.openbmc_project.Sensor.Threshold.Error." "HardShutdownAlarmLow"}}}, {ShutdownType::soft, {{AlarmType::high, "xyz.openbmc_project.Sensor.Threshold.Error.SoftShutdownAlarmHigh"}, {AlarmType::low, "xyz.openbmc_project.Sensor.Threshold.Error." "SoftShutdownAlarmLow"}}}}; const std::map> alarmClearEventLogs{ {ShutdownType::hard, {{AlarmType::high, "xyz.openbmc_project.Sensor.Threshold.Error." "HardShutdownAlarmHighClear"}, {AlarmType::low, "xyz.openbmc_project.Sensor.Threshold.Error." "HardShutdownAlarmLowClear"}}}, {ShutdownType::soft, {{AlarmType::high, "xyz.openbmc_project.Sensor.Threshold.Error." "SoftShutdownAlarmHighClear"}, {AlarmType::low, "xyz.openbmc_project.Sensor.Threshold.Error." "SoftShutdownAlarmLowClear"}}}}; constexpr auto systemdService = "org.freedesktop.systemd1"; constexpr auto systemdPath = "/org/freedesktop/systemd1"; constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager"; constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value"; constexpr auto valueProperty = "Value"; const auto loggingService = "xyz.openbmc_project.Logging"; const auto loggingPath = "/xyz/openbmc_project/logging"; const auto loggingCreateIface = "xyz.openbmc_project.Logging.Create"; using namespace sdbusplus::bus::match; ShutdownAlarmMonitor::ShutdownAlarmMonitor( sdbusplus::bus::bus& bus, sdeventplus::Event& event, std::shared_ptr powerState) : bus(bus), event(event), _powerState(std::move(powerState)), hardShutdownMatch(bus, "type='signal',member='PropertiesChanged'," "path_namespace='/xyz/openbmc_project/sensors'," "arg0='" + shutdownInterfaces.at(ShutdownType::soft) + "'", std::bind(&ShutdownAlarmMonitor::propertiesChanged, this, std::placeholders::_1)), softShutdownMatch(bus, "type='signal',member='PropertiesChanged'," "path_namespace='/xyz/openbmc_project/sensors'," "arg0='" + shutdownInterfaces.at(ShutdownType::hard) + "'", std::bind(&ShutdownAlarmMonitor::propertiesChanged, this, std::placeholders::_1)) { _powerState->addCallback("shutdownMon", std::bind(&ShutdownAlarmMonitor::powerStateChanged, this, std::placeholders::_1)); findAlarms(); if (_powerState->isPowerOn()) { checkAlarms(); // Get rid of any previous saved timestamps that don't // apply anymore. timestamps.prune(alarms); } else { timestamps.clear(); } } void ShutdownAlarmMonitor::findAlarms() { // Find all shutdown threshold ifaces currently on D-Bus. for (const auto& [shutdownType, interface] : shutdownInterfaces) { auto paths = SDBusPlus::getSubTreePathsRaw(bus, "/", interface, 0); std::for_each( paths.begin(), paths.end(), [this, shutdownType](const auto& path) { alarms.emplace(AlarmKey{path, shutdownType, AlarmType::high}, nullptr); alarms.emplace(AlarmKey{path, shutdownType, AlarmType::low}, nullptr); }); } } void ShutdownAlarmMonitor::checkAlarms() { for (auto& [alarmKey, timer] : alarms) { const auto& [sensorPath, shutdownType, alarmType] = alarmKey; const auto& interface = shutdownInterfaces.at(shutdownType); auto propertyName = alarmProperties.at(shutdownType).at(alarmType); bool value; try { value = SDBusPlus::getProperty(bus, sensorPath, interface, propertyName); } catch (const DBusServiceError& e) { // The sensor isn't on D-Bus anymore log(fmt::format("No {} interface on {} anymore.", interface, sensorPath) .c_str()); continue; } checkAlarm(value, alarmKey); } } void ShutdownAlarmMonitor::propertiesChanged( sdbusplus::message::message& message) { std::map> properties; std::string interface; if (!_powerState->isPowerOn()) { return; } message.read(interface, properties); auto type = getShutdownType(interface); if (!type) { return; } std::string sensorPath = message.get_path(); const auto& lowAlarmName = alarmProperties.at(*type).at(AlarmType::low); if (properties.count(lowAlarmName) > 0) { AlarmKey alarmKey{sensorPath, *type, AlarmType::low}; auto alarm = alarms.find(alarmKey); if (alarm == alarms.end()) { alarms.emplace(alarmKey, nullptr); } checkAlarm(std::get(properties.at(lowAlarmName)), alarmKey); } const auto& highAlarmName = alarmProperties.at(*type).at(AlarmType::high); if (properties.count(highAlarmName) > 0) { AlarmKey alarmKey{sensorPath, *type, AlarmType::high}; auto alarm = alarms.find(alarmKey); if (alarm == alarms.end()) { alarms.emplace(alarmKey, nullptr); } checkAlarm(std::get(properties.at(highAlarmName)), alarmKey); } } void ShutdownAlarmMonitor::checkAlarm(bool value, const AlarmKey& alarmKey) { auto alarm = alarms.find(alarmKey); if (alarm == alarms.end()) { return; } // Start or stop the timer if necessary. auto& timer = alarm->second; if (value) { if (!timer) { startTimer(alarmKey); } } else { if (timer) { stopTimer(alarmKey); } } } void ShutdownAlarmMonitor::startTimer(const AlarmKey& alarmKey) { const auto& [sensorPath, shutdownType, alarmType] = alarmKey; const auto& propertyName = alarmProperties.at(shutdownType).at(alarmType); std::chrono::milliseconds shutdownDelay{shutdownDelays.at(shutdownType)}; std::optional value; auto alarm = alarms.find(alarmKey); if (alarm == alarms.end()) { throw std::runtime_error("Couldn't find alarm inside startTimer"); } try { value = SDBusPlus::getProperty(bus, sensorPath, valueInterface, valueProperty); } catch (const DBusServiceError& e) { // If the sensor was just added, the Value interface for it may // not be in the mapper yet. This could only happen if the sensor // application was started with power up and the value exceeded the // threshold immediately. } createEventLog(alarmKey, true, value); uint64_t now = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); // If there is a saved timestamp for this timer, then we were restarted // while the timer was running. Calculate the remaining time to use // for the timer. auto previousStartTime = timestamps.get().find(alarmKey); if (previousStartTime != timestamps.get().end()) { const uint64_t& original = previousStartTime->second; log(fmt::format("Found previously running {} timer " "for {} with start time {}", propertyName, sensorPath, original) .c_str()); // Sanity check it isn't total garbage. if (now > original) { uint64_t remainingTime = 0; auto elapsedTime = now - original; if (elapsedTime < static_cast(shutdownDelay.count())) { remainingTime = static_cast(shutdownDelay.count()) - elapsedTime; } shutdownDelay = std::chrono::milliseconds{remainingTime}; } else { log( fmt::format( "Restarting {} shutdown timer for {} for full " "time because saved time {} is after current time {}", propertyName, original, now) .c_str()); } } log( fmt::format("Starting {}ms {} shutdown timer due to sensor {} value {}", shutdownDelay.count(), propertyName, sensorPath, *value) .c_str()); auto& timer = alarm->second; timer = std::make_unique< sdeventplus::utility::Timer>( event, std::bind(&ShutdownAlarmMonitor::timerExpired, this, alarmKey)); timer->restartOnce(shutdownDelay); // Note that if this key is already in the timestamps map because // the timer was already running the timestamp wil not be updated. timestamps.add(alarmKey, now); } void ShutdownAlarmMonitor::stopTimer(const AlarmKey& alarmKey) { const auto& [sensorPath, shutdownType, alarmType] = alarmKey; const auto& propertyName = alarmProperties.at(shutdownType).at(alarmType); auto value = SDBusPlus::getProperty(bus, sensorPath, valueInterface, valueProperty); auto alarm = alarms.find(alarmKey); if (alarm == alarms.end()) { throw std::runtime_error("Couldn't find alarm inside stopTimer"); } createEventLog(alarmKey, false, value); log( fmt::format("Stopping {} shutdown timer due to sensor {} value {}", propertyName, sensorPath, value) .c_str()); auto& timer = alarm->second; timer->setEnabled(false); timer.reset(); timestamps.erase(alarmKey); } void ShutdownAlarmMonitor::timerExpired(const AlarmKey& alarmKey) { const auto& [sensorPath, shutdownType, alarmType] = alarmKey; const auto& propertyName = alarmProperties.at(shutdownType).at(alarmType); auto value = SDBusPlus::getProperty(bus, sensorPath, valueInterface, valueProperty); log( fmt::format( "The {} shutdown timer expired for sensor {}, shutting down", propertyName, sensorPath) .c_str()); // Re-send the event log. If someone didn't want this it could be // wrapped by a compile option. createEventLog(alarmKey, true, value, true); SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface, "StartUnit", "obmc-chassis-hard-poweroff@0.target", "replace"); timestamps.erase(alarmKey); } void ShutdownAlarmMonitor::powerStateChanged(bool powerStateOn) { if (powerStateOn) { checkAlarms(); } else { timestamps.clear(); // Cancel and delete all timers std::for_each(alarms.begin(), alarms.end(), [](auto& alarm) { auto& timer = alarm.second; if (timer) { timer->setEnabled(false); timer.reset(); } }); } } void ShutdownAlarmMonitor::createEventLog( const AlarmKey& alarmKey, bool alarmValue, const std::optional& sensorValue, bool isPowerOffError) { using namespace sdbusplus::xyz::openbmc_project::Logging::server; const auto& [sensorPath, shutdownType, alarmType] = alarmKey; std::map ad{{"SENSOR_NAME", sensorPath}, {"_PID", std::to_string(getpid())}}; std::string errorName = (alarmValue) ? alarmEventLogs.at(shutdownType).at(alarmType) : alarmClearEventLogs.at(shutdownType).at(alarmType); // severity = Critical if a power off // severity = Error if alarm was asserted // severity = Informational if alarm was deasserted Entry::Level severity = Entry::Level::Error; if (isPowerOffError) { severity = Entry::Level::Critical; } else if (!alarmValue) { severity = Entry::Level::Informational; } if (sensorValue) { ad.emplace("SENSOR_VALUE", std::to_string(*sensorValue)); } // If this is a power off, specify that it's a power // fault and a system termination. This is used by some // implementations for service reasons. if (isPowerOffError) { ad.emplace("POWER_THERMAL_CRITICAL_FAULT", "TRUE"); ad.emplace("SEVERITY_DETAIL", "SYSTEM_TERM"); } SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface, "Create", errorName, convertForMessage(severity), ad); } std::optional ShutdownAlarmMonitor::getShutdownType(const std::string& interface) const { auto it = std::find_if( shutdownInterfaces.begin(), shutdownInterfaces.end(), [interface](const auto& a) { return a.second == interface; }); if (it == shutdownInterfaces.end()) { return std::nullopt; } return it->first; } } // namespace sensor::monitor