| /* |
| // Copyright (c) 2018-2019 Intel 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 "i2c.hpp" |
| |
| #include <sys/sysinfo.h> |
| #include <systemd/sd-journal.h> |
| |
| #include <boost/asio/io_service.hpp> |
| #include <boost/asio/posix/stream_descriptor.hpp> |
| #include <boost/asio/steady_timer.hpp> |
| #include <boost/container/flat_map.hpp> |
| #include <boost/container/flat_set.hpp> |
| #include <gpiod.hpp> |
| #include <nlohmann/json.hpp> |
| #include <phosphor-logging/log.hpp> |
| #include <sdbusplus/asio/object_server.hpp> |
| |
| #include <filesystem> |
| #include <fstream> |
| #include <string_view> |
| |
| namespace power_control |
| { |
| static boost::asio::io_service io; |
| std::shared_ptr<sdbusplus::asio::connection> conn; |
| |
| static std::string node = "0"; |
| |
| static std::string powerOutName; |
| static std::string powerOkName; |
| static std::string resetOutName; |
| static std::string nmiOutName; |
| static std::string sioPwrGoodName; |
| static std::string sioOnControlName; |
| static std::string sioS5Name; |
| static std::string postCompleteName; |
| static std::string powerButtonName; |
| static std::string resetButtonName; |
| static std::string idButtonName; |
| static std::string nmiButtonName; |
| |
| static std::shared_ptr<sdbusplus::asio::dbus_interface> hostIface; |
| static std::shared_ptr<sdbusplus::asio::dbus_interface> chassisIface; |
| #ifdef CHASSIS_SYSTEM_RESET |
| static std::shared_ptr<sdbusplus::asio::dbus_interface> chassisSysIface; |
| #endif |
| static std::shared_ptr<sdbusplus::asio::dbus_interface> powerButtonIface; |
| static std::shared_ptr<sdbusplus::asio::dbus_interface> resetButtonIface; |
| static std::shared_ptr<sdbusplus::asio::dbus_interface> nmiButtonIface; |
| static std::shared_ptr<sdbusplus::asio::dbus_interface> osIface; |
| static std::shared_ptr<sdbusplus::asio::dbus_interface> idButtonIface; |
| static std::shared_ptr<sdbusplus::asio::dbus_interface> nmiOutIface; |
| static std::shared_ptr<sdbusplus::asio::dbus_interface> restartCauseIface; |
| |
| static gpiod::line powerButtonMask; |
| static gpiod::line resetButtonMask; |
| static bool nmiButtonMasked = false; |
| |
| const static constexpr int powerPulseTimeMs = 200; |
| const static constexpr int forceOffPulseTimeMs = 15000; |
| const static constexpr int resetPulseTimeMs = 500; |
| const static constexpr int powerCycleTimeMs = 5000; |
| const static constexpr int sioPowerGoodWatchdogTimeMs = 1000; |
| const static constexpr int psPowerOKWatchdogTimeMs = 8000; |
| const static constexpr int gracefulPowerOffTimeS = 5 * 60; |
| const static constexpr int warmResetCheckTimeMs = 500; |
| const static constexpr int powerOffSaveTimeMs = 7000; |
| |
| const static std::filesystem::path powerControlDir = "/var/lib/power-control"; |
| const static constexpr std::string_view powerStateFile = "power-state"; |
| |
| static bool nmiEnabled = true; |
| static bool sioEnabled = true; |
| |
| // Timers |
| // Time holding GPIOs asserted |
| static boost::asio::steady_timer gpioAssertTimer(io); |
| // Time between off and on during a power cycle |
| static boost::asio::steady_timer powerCycleTimer(io); |
| // Time OS gracefully powering off |
| static boost::asio::steady_timer gracefulPowerOffTimer(io); |
| // Time the warm reset check |
| static boost::asio::steady_timer warmResetCheckTimer(io); |
| // Time power supply power OK assertion on power-on |
| static boost::asio::steady_timer psPowerOKWatchdogTimer(io); |
| // Time SIO power good assertion on power-on |
| static boost::asio::steady_timer sioPowerGoodWatchdogTimer(io); |
| // Time power-off state save for power loss tracking |
| static boost::asio::steady_timer powerStateSaveTimer(io); |
| // POH timer |
| static boost::asio::steady_timer pohCounterTimer(io); |
| // Time when to allow restart cause updates |
| static boost::asio::steady_timer restartCauseTimer(io); |
| |
| // GPIO Lines and Event Descriptors |
| static gpiod::line psPowerOKLine; |
| static boost::asio::posix::stream_descriptor psPowerOKEvent(io); |
| static gpiod::line sioPowerGoodLine; |
| static boost::asio::posix::stream_descriptor sioPowerGoodEvent(io); |
| static gpiod::line sioOnControlLine; |
| static boost::asio::posix::stream_descriptor sioOnControlEvent(io); |
| static gpiod::line sioS5Line; |
| static boost::asio::posix::stream_descriptor sioS5Event(io); |
| static gpiod::line powerButtonLine; |
| static boost::asio::posix::stream_descriptor powerButtonEvent(io); |
| static gpiod::line resetButtonLine; |
| static boost::asio::posix::stream_descriptor resetButtonEvent(io); |
| static gpiod::line nmiButtonLine; |
| static boost::asio::posix::stream_descriptor nmiButtonEvent(io); |
| static gpiod::line idButtonLine; |
| static boost::asio::posix::stream_descriptor idButtonEvent(io); |
| static gpiod::line postCompleteLine; |
| static boost::asio::posix::stream_descriptor postCompleteEvent(io); |
| static gpiod::line nmiOutLine; |
| |
| static constexpr uint8_t beepPowerFail = 8; |
| |
| static void beep(const uint8_t& beepPriority) |
| { |
| std::string logMsg = "Beep with priority: " + std::to_string(beepPriority); |
| phosphor::logging::log<phosphor::logging::level::INFO>(logMsg.c_str()); |
| |
| conn->async_method_call( |
| [](boost::system::error_code ec) { |
| if (ec) |
| { |
| std::string errMsg = |
| "beep returned error with async_method_call (ec = " + |
| ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| return; |
| } |
| }, |
| "xyz.openbmc_project.BeepCode", "/xyz/openbmc_project/BeepCode", |
| "xyz.openbmc_project.BeepCode", "Beep", uint8_t(beepPriority)); |
| } |
| |
| enum class PowerState |
| { |
| on, |
| waitForPSPowerOK, |
| waitForSIOPowerGood, |
| off, |
| transitionToOff, |
| gracefulTransitionToOff, |
| cycleOff, |
| transitionToCycleOff, |
| gracefulTransitionToCycleOff, |
| checkForWarmReset, |
| }; |
| static PowerState powerState; |
| static std::string getPowerStateName(PowerState state) |
| { |
| switch (state) |
| { |
| case PowerState::on: |
| return "On"; |
| break; |
| case PowerState::waitForPSPowerOK: |
| return "Wait for Power Supply Power OK"; |
| break; |
| case PowerState::waitForSIOPowerGood: |
| return "Wait for SIO Power Good"; |
| break; |
| case PowerState::off: |
| return "Off"; |
| break; |
| case PowerState::transitionToOff: |
| return "Transition to Off"; |
| break; |
| case PowerState::gracefulTransitionToOff: |
| return "Graceful Transition to Off"; |
| break; |
| case PowerState::cycleOff: |
| return "Power Cycle Off"; |
| break; |
| case PowerState::transitionToCycleOff: |
| return "Transition to Power Cycle Off"; |
| break; |
| case PowerState::gracefulTransitionToCycleOff: |
| return "Graceful Transition to Power Cycle Off"; |
| break; |
| case PowerState::checkForWarmReset: |
| return "Check for Warm Reset"; |
| break; |
| default: |
| return "unknown state: " + std::to_string(static_cast<int>(state)); |
| break; |
| } |
| } |
| static void logStateTransition(const PowerState state) |
| { |
| std::string logMsg = |
| "Host0: Moving to \"" + getPowerStateName(state) + "\" state"; |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| logMsg.c_str(), |
| phosphor::logging::entry("STATE=%s", getPowerStateName(state).c_str()), |
| phosphor::logging::entry("HOST=0")); |
| } |
| |
| enum class Event |
| { |
| psPowerOKAssert, |
| psPowerOKDeAssert, |
| sioPowerGoodAssert, |
| sioPowerGoodDeAssert, |
| sioS5Assert, |
| sioS5DeAssert, |
| pltRstAssert, |
| pltRstDeAssert, |
| postCompleteAssert, |
| postCompleteDeAssert, |
| powerButtonPressed, |
| resetButtonPressed, |
| powerCycleTimerExpired, |
| psPowerOKWatchdogTimerExpired, |
| sioPowerGoodWatchdogTimerExpired, |
| gracefulPowerOffTimerExpired, |
| powerOnRequest, |
| powerOffRequest, |
| powerCycleRequest, |
| resetRequest, |
| gracefulPowerOffRequest, |
| gracefulPowerCycleRequest, |
| warmResetDetected, |
| }; |
| static std::string getEventName(Event event) |
| { |
| switch (event) |
| { |
| case Event::psPowerOKAssert: |
| return "power supply power OK assert"; |
| break; |
| case Event::psPowerOKDeAssert: |
| return "power supply power OK de-assert"; |
| break; |
| case Event::sioPowerGoodAssert: |
| return "SIO power good assert"; |
| break; |
| case Event::sioPowerGoodDeAssert: |
| return "SIO power good de-assert"; |
| break; |
| case Event::sioS5Assert: |
| return "SIO S5 assert"; |
| break; |
| case Event::sioS5DeAssert: |
| return "SIO S5 de-assert"; |
| break; |
| case Event::pltRstAssert: |
| return "PLT_RST assert"; |
| break; |
| case Event::pltRstDeAssert: |
| return "PLT_RST de-assert"; |
| break; |
| case Event::postCompleteAssert: |
| return "POST Complete assert"; |
| break; |
| case Event::postCompleteDeAssert: |
| return "POST Complete de-assert"; |
| break; |
| case Event::powerButtonPressed: |
| return "power button pressed"; |
| break; |
| case Event::resetButtonPressed: |
| return "reset button pressed"; |
| break; |
| case Event::powerCycleTimerExpired: |
| return "power cycle timer expired"; |
| break; |
| case Event::psPowerOKWatchdogTimerExpired: |
| return "power supply power OK watchdog timer expired"; |
| break; |
| case Event::sioPowerGoodWatchdogTimerExpired: |
| return "SIO power good watchdog timer expired"; |
| break; |
| case Event::gracefulPowerOffTimerExpired: |
| return "graceful power-off timer expired"; |
| break; |
| case Event::powerOnRequest: |
| return "power-on request"; |
| break; |
| case Event::powerOffRequest: |
| return "power-off request"; |
| break; |
| case Event::powerCycleRequest: |
| return "power-cycle request"; |
| break; |
| case Event::resetRequest: |
| return "reset request"; |
| break; |
| case Event::gracefulPowerOffRequest: |
| return "graceful power-off request"; |
| break; |
| case Event::gracefulPowerCycleRequest: |
| return "graceful power-cycle request"; |
| break; |
| case Event::warmResetDetected: |
| return "warm reset detected"; |
| break; |
| default: |
| return "unknown event: " + std::to_string(static_cast<int>(event)); |
| break; |
| } |
| } |
| static void logEvent(const std::string_view stateHandler, const Event event) |
| { |
| std::string logMsg{stateHandler}; |
| logMsg += ": " + getEventName(event) + " event received"; |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| logMsg.c_str(), |
| phosphor::logging::entry("EVENT=%s", getEventName(event).c_str())); |
| } |
| |
| // Power state handlers |
| static void powerStateOn(const Event event); |
| static void powerStateWaitForPSPowerOK(const Event event); |
| static void powerStateWaitForSIOPowerGood(const Event event); |
| static void powerStateOff(const Event event); |
| static void powerStateTransitionToOff(const Event event); |
| static void powerStateGracefulTransitionToOff(const Event event); |
| static void powerStateCycleOff(const Event event); |
| static void powerStateTransitionToCycleOff(const Event event); |
| static void powerStateGracefulTransitionToCycleOff(const Event event); |
| static void powerStateCheckForWarmReset(const Event event); |
| |
| static std::function<void(const Event)> getPowerStateHandler(PowerState state) |
| { |
| switch (state) |
| { |
| case PowerState::on: |
| return powerStateOn; |
| break; |
| case PowerState::waitForPSPowerOK: |
| return powerStateWaitForPSPowerOK; |
| break; |
| case PowerState::waitForSIOPowerGood: |
| return powerStateWaitForSIOPowerGood; |
| break; |
| case PowerState::off: |
| return powerStateOff; |
| break; |
| case PowerState::transitionToOff: |
| return powerStateTransitionToOff; |
| break; |
| case PowerState::gracefulTransitionToOff: |
| return powerStateGracefulTransitionToOff; |
| break; |
| case PowerState::cycleOff: |
| return powerStateCycleOff; |
| break; |
| case PowerState::transitionToCycleOff: |
| return powerStateTransitionToCycleOff; |
| break; |
| case PowerState::gracefulTransitionToCycleOff: |
| return powerStateGracefulTransitionToCycleOff; |
| break; |
| case PowerState::checkForWarmReset: |
| return powerStateCheckForWarmReset; |
| break; |
| default: |
| return nullptr; |
| break; |
| } |
| }; |
| |
| static void sendPowerControlEvent(const Event event) |
| { |
| std::function<void(const Event)> handler = getPowerStateHandler(powerState); |
| if (handler == nullptr) |
| { |
| std::string errMsg = "Failed to find handler for power state: " + |
| std::to_string(static_cast<int>(powerState)); |
| phosphor::logging::log<phosphor::logging::level::INFO>(errMsg.c_str()); |
| return; |
| } |
| handler(event); |
| } |
| |
| static uint64_t getCurrentTimeMs() |
| { |
| struct timespec time = {}; |
| |
| if (clock_gettime(CLOCK_REALTIME, &time) < 0) |
| { |
| return 0; |
| } |
| uint64_t currentTimeMs = static_cast<uint64_t>(time.tv_sec) * 1000; |
| currentTimeMs += static_cast<uint64_t>(time.tv_nsec) / 1000 / 1000; |
| |
| return currentTimeMs; |
| } |
| |
| static constexpr std::string_view getHostState(const PowerState state) |
| { |
| switch (state) |
| { |
| case PowerState::on: |
| case PowerState::gracefulTransitionToOff: |
| case PowerState::gracefulTransitionToCycleOff: |
| return "xyz.openbmc_project.State.Host.HostState.Running"; |
| break; |
| case PowerState::waitForPSPowerOK: |
| case PowerState::waitForSIOPowerGood: |
| case PowerState::off: |
| case PowerState::transitionToOff: |
| case PowerState::transitionToCycleOff: |
| case PowerState::cycleOff: |
| case PowerState::checkForWarmReset: |
| return "xyz.openbmc_project.State.Host.HostState.Off"; |
| break; |
| default: |
| return ""; |
| break; |
| } |
| }; |
| static constexpr std::string_view getChassisState(const PowerState state) |
| { |
| switch (state) |
| { |
| case PowerState::on: |
| case PowerState::transitionToOff: |
| case PowerState::gracefulTransitionToOff: |
| case PowerState::transitionToCycleOff: |
| case PowerState::gracefulTransitionToCycleOff: |
| case PowerState::checkForWarmReset: |
| return "xyz.openbmc_project.State.Chassis.PowerState.On"; |
| break; |
| case PowerState::waitForPSPowerOK: |
| case PowerState::waitForSIOPowerGood: |
| case PowerState::off: |
| case PowerState::cycleOff: |
| return "xyz.openbmc_project.State.Chassis.PowerState.Off"; |
| break; |
| default: |
| return ""; |
| break; |
| } |
| }; |
| static void savePowerState(const PowerState state) |
| { |
| powerStateSaveTimer.expires_after( |
| std::chrono::milliseconds(powerOffSaveTimeMs)); |
| powerStateSaveTimer.async_wait([state](const boost::system::error_code ec) { |
| if (ec) |
| { |
| // operation_aborted is expected if timer is canceled before |
| // completion. |
| if (ec != boost::asio::error::operation_aborted) |
| { |
| std::string errMsg = |
| "Power-state save async_wait failed: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| } |
| return; |
| } |
| std::ofstream powerStateStream(powerControlDir / powerStateFile); |
| powerStateStream << getChassisState(state); |
| }); |
| } |
| static void setPowerState(const PowerState state) |
| { |
| powerState = state; |
| logStateTransition(state); |
| |
| hostIface->set_property("CurrentHostState", |
| std::string(getHostState(powerState))); |
| |
| chassisIface->set_property("CurrentPowerState", |
| std::string(getChassisState(powerState))); |
| chassisIface->set_property("LastStateChangeTime", getCurrentTimeMs()); |
| |
| // Save the power state for the restore policy |
| savePowerState(state); |
| } |
| |
| enum class RestartCause |
| { |
| command, |
| resetButton, |
| powerButton, |
| watchdog, |
| powerPolicyOn, |
| powerPolicyRestore, |
| softReset, |
| }; |
| static boost::container::flat_set<RestartCause> causeSet; |
| static std::string getRestartCause(RestartCause cause) |
| { |
| switch (cause) |
| { |
| case RestartCause::command: |
| return "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand"; |
| break; |
| case RestartCause::resetButton: |
| return "xyz.openbmc_project.State.Host.RestartCause.ResetButton"; |
| break; |
| case RestartCause::powerButton: |
| return "xyz.openbmc_project.State.Host.RestartCause.PowerButton"; |
| break; |
| case RestartCause::watchdog: |
| return "xyz.openbmc_project.State.Host.RestartCause.WatchdogTimer"; |
| break; |
| case RestartCause::powerPolicyOn: |
| return "xyz.openbmc_project.State.Host.RestartCause." |
| "PowerPolicyAlwaysOn"; |
| break; |
| case RestartCause::powerPolicyRestore: |
| return "xyz.openbmc_project.State.Host.RestartCause." |
| "PowerPolicyPreviousState"; |
| break; |
| case RestartCause::softReset: |
| return "xyz.openbmc_project.State.Host.RestartCause.SoftReset"; |
| break; |
| default: |
| return "xyz.openbmc_project.State.Host.RestartCause.Unknown"; |
| break; |
| } |
| } |
| static void addRestartCause(const RestartCause cause) |
| { |
| // Add this to the set of causes for this restart |
| causeSet.insert(cause); |
| } |
| static void clearRestartCause() |
| { |
| // Clear the set for the next restart |
| causeSet.clear(); |
| } |
| static void setRestartCauseProperty(const std::string& cause) |
| { |
| std::string logMsg = "RestartCause set to " + cause; |
| phosphor::logging::log<phosphor::logging::level::INFO>(logMsg.c_str()); |
| restartCauseIface->set_property("RestartCause", cause); |
| } |
| |
| static void resetACBootProperty() |
| { |
| if ((causeSet.contains(RestartCause::command)) || |
| (causeSet.contains(RestartCause::softReset))) |
| { |
| conn->async_method_call( |
| [](boost::system::error_code ec) { |
| if (ec) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "failed to reset ACBoot property"); |
| } |
| }, |
| "xyz.openbmc_project.Settings", |
| "/xyz/openbmc_project/control/host0/ac_boot", |
| "org.freedesktop.DBus.Properties", "Set", |
| "xyz.openbmc_project.Common.ACBoot", "ACBoot", |
| std::variant<std::string>{"False"}); |
| } |
| } |
| |
| static void setRestartCause() |
| { |
| // Determine the actual restart cause based on the set of causes |
| std::string restartCause = |
| "xyz.openbmc_project.State.Host.RestartCause.Unknown"; |
| if (causeSet.contains(RestartCause::watchdog)) |
| { |
| restartCause = getRestartCause(RestartCause::watchdog); |
| } |
| else if (causeSet.contains(RestartCause::command)) |
| { |
| restartCause = getRestartCause(RestartCause::command); |
| } |
| else if (causeSet.contains(RestartCause::resetButton)) |
| { |
| restartCause = getRestartCause(RestartCause::resetButton); |
| } |
| else if (causeSet.contains(RestartCause::powerButton)) |
| { |
| restartCause = getRestartCause(RestartCause::powerButton); |
| } |
| else if (causeSet.contains(RestartCause::powerPolicyOn)) |
| { |
| restartCause = getRestartCause(RestartCause::powerPolicyOn); |
| } |
| else if (causeSet.contains(RestartCause::powerPolicyRestore)) |
| { |
| restartCause = getRestartCause(RestartCause::powerPolicyRestore); |
| } |
| else if (causeSet.contains(RestartCause::softReset)) |
| { |
| restartCause = getRestartCause(RestartCause::softReset); |
| } |
| |
| setRestartCauseProperty(restartCause); |
| } |
| |
| static void systemPowerGoodFailedLog() |
| { |
| sd_journal_send( |
| "MESSAGE=PowerControl: system power good failed to assert (VR failure)", |
| "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s", |
| "OpenBMC.0.1.SystemPowerGoodFailed", "REDFISH_MESSAGE_ARGS=%d", |
| sioPowerGoodWatchdogTimeMs, NULL); |
| } |
| |
| static void psPowerOKFailedLog() |
| { |
| sd_journal_send( |
| "MESSAGE=PowerControl: power supply power good failed to assert", |
| "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s", |
| "OpenBMC.0.1.PowerSupplyPowerGoodFailed", "REDFISH_MESSAGE_ARGS=%d", |
| psPowerOKWatchdogTimeMs, NULL); |
| } |
| |
| static void powerRestorePolicyLog() |
| { |
| sd_journal_send("MESSAGE=PowerControl: power restore policy applied", |
| "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s", |
| "OpenBMC.0.1.PowerRestorePolicyApplied", NULL); |
| } |
| |
| static void powerButtonPressLog() |
| { |
| sd_journal_send("MESSAGE=PowerControl: power button pressed", "PRIORITY=%i", |
| LOG_INFO, "REDFISH_MESSAGE_ID=%s", |
| "OpenBMC.0.1.PowerButtonPressed", NULL); |
| } |
| |
| static void resetButtonPressLog() |
| { |
| sd_journal_send("MESSAGE=PowerControl: reset button pressed", "PRIORITY=%i", |
| LOG_INFO, "REDFISH_MESSAGE_ID=%s", |
| "OpenBMC.0.1.ResetButtonPressed", NULL); |
| } |
| |
| static void nmiButtonPressLog() |
| { |
| sd_journal_send("MESSAGE=PowerControl: NMI button pressed", "PRIORITY=%i", |
| LOG_INFO, "REDFISH_MESSAGE_ID=%s", |
| "OpenBMC.0.1.NMIButtonPressed", NULL); |
| } |
| |
| static void nmiDiagIntLog() |
| { |
| sd_journal_send("MESSAGE=PowerControl: NMI Diagnostic Interrupt", |
| "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s", |
| "OpenBMC.0.1.NMIDiagnosticInterrupt", NULL); |
| } |
| |
| static int initializePowerStateStorage() |
| { |
| // create the power control directory if it doesn't exist |
| std::error_code ec; |
| if (!(std::filesystem::create_directories(powerControlDir, ec))) |
| { |
| if (ec.value() != 0) |
| { |
| std::string errMsg = |
| "failed to create " + powerControlDir.string() + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| return -1; |
| } |
| } |
| // Create the power state file if it doesn't exist |
| if (!std::filesystem::exists(powerControlDir / powerStateFile)) |
| { |
| std::ofstream powerStateStream(powerControlDir / powerStateFile); |
| powerStateStream << getChassisState(powerState); |
| } |
| return 0; |
| } |
| |
| static bool wasPowerDropped() |
| { |
| std::ifstream powerStateStream(powerControlDir / powerStateFile); |
| if (!powerStateStream.is_open()) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Failed to open power state file"); |
| return false; |
| } |
| |
| std::string state; |
| std::getline(powerStateStream, state); |
| return state == "xyz.openbmc_project.State.Chassis.PowerState.On"; |
| } |
| |
| static void invokePowerRestorePolicy(const std::string& policy) |
| { |
| // Async events may call this twice, but we only want to run once |
| static bool policyInvoked = false; |
| if (policyInvoked) |
| { |
| return; |
| } |
| policyInvoked = true; |
| |
| std::string logMsg = "Power restore delay expired, invoking " + policy; |
| phosphor::logging::log<phosphor::logging::level::INFO>(logMsg.c_str()); |
| if (policy == |
| "xyz.openbmc_project.Control.Power.RestorePolicy.Policy.AlwaysOn") |
| { |
| sendPowerControlEvent(Event::powerOnRequest); |
| setRestartCauseProperty(getRestartCause(RestartCause::powerPolicyOn)); |
| } |
| else if (policy == "xyz.openbmc_project.Control.Power.RestorePolicy." |
| "Policy.Restore") |
| { |
| if (wasPowerDropped()) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Power was dropped, restoring Host On state"); |
| sendPowerControlEvent(Event::powerOnRequest); |
| setRestartCauseProperty( |
| getRestartCause(RestartCause::powerPolicyRestore)); |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "No power drop, restoring Host Off state"); |
| } |
| } |
| // We're done with the previous power state for the restore policy, so store |
| // the current state |
| savePowerState(powerState); |
| } |
| |
| static void powerRestorePolicyDelay(int delay) |
| { |
| // Async events may call this twice, but we only want to run once |
| static bool delayStarted = false; |
| if (delayStarted) |
| { |
| return; |
| } |
| delayStarted = true; |
| // Calculate the delay from now to meet the requested delay |
| // Subtract the approximate uboot time |
| static constexpr const int ubootSeconds = 20; |
| delay -= ubootSeconds; |
| // Subtract the time since boot |
| struct sysinfo info = {}; |
| if (sysinfo(&info) == 0) |
| { |
| delay -= info.uptime; |
| } |
| // 0 is the minimum delay |
| delay = std::max(delay, 0); |
| |
| static boost::asio::steady_timer powerRestorePolicyTimer(io); |
| powerRestorePolicyTimer.expires_after(std::chrono::seconds(delay)); |
| std::string logMsg = |
| "Power restore delay of " + std::to_string(delay) + " seconds started"; |
| phosphor::logging::log<phosphor::logging::level::INFO>(logMsg.c_str()); |
| powerRestorePolicyTimer.async_wait([](const boost::system::error_code ec) { |
| if (ec) |
| { |
| // operation_aborted is expected if timer is canceled before |
| // completion. |
| if (ec != boost::asio::error::operation_aborted) |
| { |
| std::string errMsg = |
| "power restore policy async_wait failed: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| } |
| return; |
| } |
| // Get Power Restore Policy |
| // In case PowerRestorePolicy is not available, set a match for it |
| static std::unique_ptr<sdbusplus::bus::match::match> |
| powerRestorePolicyMatch = std::make_unique< |
| sdbusplus::bus::match::match>( |
| *conn, |
| "type='signal',interface='org.freedesktop.DBus.Properties'," |
| "member='PropertiesChanged',arg0namespace='xyz.openbmc_" |
| "project.Control.Power.RestorePolicy'", |
| [](sdbusplus::message::message& msg) { |
| std::string interfaceName; |
| boost::container::flat_map<std::string, |
| std::variant<std::string>> |
| propertiesChanged; |
| std::string policy; |
| try |
| { |
| msg.read(interfaceName, propertiesChanged); |
| policy = std::get<std::string>( |
| propertiesChanged.begin()->second); |
| } |
| catch (std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unable to read power restore policy value"); |
| powerRestorePolicyMatch.reset(); |
| return; |
| } |
| invokePowerRestorePolicy(policy); |
| powerRestorePolicyMatch.reset(); |
| }); |
| |
| // Check if it's already on DBus |
| conn->async_method_call( |
| [](boost::system::error_code ec, |
| const std::variant<std::string>& policyProperty) { |
| if (ec) |
| { |
| return; |
| } |
| powerRestorePolicyMatch.reset(); |
| const std::string* policy = |
| std::get_if<std::string>(&policyProperty); |
| if (policy == nullptr) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unable to read power restore policy value"); |
| return; |
| } |
| invokePowerRestorePolicy(*policy); |
| }, |
| "xyz.openbmc_project.Settings", |
| "/xyz/openbmc_project/control/host0/power_restore_policy", |
| "org.freedesktop.DBus.Properties", "Get", |
| "xyz.openbmc_project.Control.Power.RestorePolicy", |
| "PowerRestorePolicy"); |
| }); |
| } |
| |
| static void powerRestorePolicyStart() |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Power restore policy started"); |
| powerRestorePolicyLog(); |
| |
| // Get the desired delay time |
| // In case PowerRestoreDelay is not available, set a match for it |
| static std::unique_ptr<sdbusplus::bus::match::match> |
| powerRestoreDelayMatch = std::make_unique<sdbusplus::bus::match::match>( |
| *conn, |
| "type='signal',interface='org.freedesktop.DBus.Properties',member='" |
| "PropertiesChanged',arg0namespace='xyz.openbmc_project.Control." |
| "Power.RestoreDelay'", |
| [](sdbusplus::message::message& msg) { |
| std::string interfaceName; |
| boost::container::flat_map<std::string, std::variant<uint16_t>> |
| propertiesChanged; |
| int delay = 0; |
| try |
| { |
| msg.read(interfaceName, propertiesChanged); |
| delay = |
| std::get<uint16_t>(propertiesChanged.begin()->second); |
| } |
| catch (std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unable to read power restore delay value"); |
| powerRestoreDelayMatch.reset(); |
| return; |
| } |
| powerRestorePolicyDelay(delay); |
| powerRestoreDelayMatch.reset(); |
| }); |
| |
| // Check if it's already on DBus |
| conn->async_method_call( |
| [](boost::system::error_code ec, |
| const std::variant<uint16_t>& delayProperty) { |
| if (ec) |
| { |
| return; |
| } |
| powerRestoreDelayMatch.reset(); |
| const uint16_t* delay = std::get_if<uint16_t>(&delayProperty); |
| if (delay == nullptr) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unable to read power restore delay value"); |
| return; |
| } |
| powerRestorePolicyDelay(*delay); |
| }, |
| "xyz.openbmc_project.Settings", |
| "/xyz/openbmc_project/control/power_restore_delay", |
| "org.freedesktop.DBus.Properties", "Get", |
| "xyz.openbmc_project.Control.Power.RestoreDelay", "PowerRestoreDelay"); |
| } |
| |
| static void powerRestorePolicyCheck() |
| { |
| // In case ACBoot is not available, set a match for it |
| static std::unique_ptr<sdbusplus::bus::match::match> acBootMatch = |
| std::make_unique<sdbusplus::bus::match::match>( |
| *conn, |
| "type='signal',interface='org.freedesktop.DBus.Properties',member='" |
| "PropertiesChanged',arg0namespace='xyz.openbmc_project.Common." |
| "ACBoot'", |
| [](sdbusplus::message::message& msg) { |
| std::string interfaceName; |
| boost::container::flat_map<std::string, |
| std::variant<std::string>> |
| propertiesChanged; |
| std::string acBoot; |
| try |
| { |
| msg.read(interfaceName, propertiesChanged); |
| acBoot = std::get<std::string>( |
| propertiesChanged.begin()->second); |
| } |
| catch (std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unable to read AC Boot status"); |
| acBootMatch.reset(); |
| return; |
| } |
| if (acBoot == "Unknown") |
| { |
| return; |
| } |
| if (acBoot == "True") |
| { |
| // Start the Power Restore policy |
| powerRestorePolicyStart(); |
| } |
| acBootMatch.reset(); |
| }); |
| |
| // Check if it's already on DBus |
| conn->async_method_call( |
| [](boost::system::error_code ec, |
| const std::variant<std::string>& acBootProperty) { |
| if (ec) |
| { |
| return; |
| } |
| const std::string* acBoot = |
| std::get_if<std::string>(&acBootProperty); |
| if (acBoot == nullptr) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unable to read AC Boot status"); |
| return; |
| } |
| if (*acBoot == "Unknown") |
| { |
| return; |
| } |
| if (*acBoot == "True") |
| { |
| // Start the Power Restore policy |
| powerRestorePolicyStart(); |
| } |
| acBootMatch.reset(); |
| }, |
| "xyz.openbmc_project.Settings", |
| "/xyz/openbmc_project/control/host0/ac_boot", |
| "org.freedesktop.DBus.Properties", "Get", |
| "xyz.openbmc_project.Common.ACBoot", "ACBoot"); |
| } |
| |
| static bool requestGPIOEvents( |
| const std::string& name, const std::function<void()>& handler, |
| gpiod::line& gpioLine, |
| boost::asio::posix::stream_descriptor& gpioEventDescriptor) |
| { |
| // Find the GPIO line |
| gpioLine = gpiod::find_line(name); |
| if (!gpioLine) |
| { |
| std::string errMsg = "Failed to find the " + name + " line"; |
| phosphor::logging::log<phosphor::logging::level::ERR>(errMsg.c_str()); |
| return false; |
| } |
| |
| try |
| { |
| gpioLine.request( |
| {"power-control", gpiod::line_request::EVENT_BOTH_EDGES}); |
| } |
| catch (std::exception&) |
| { |
| std::string errMsg = "Failed to request events for " + name; |
| phosphor::logging::log<phosphor::logging::level::ERR>(errMsg.c_str()); |
| return false; |
| } |
| |
| int gpioLineFd = gpioLine.event_get_fd(); |
| if (gpioLineFd < 0) |
| { |
| std::string errMsg = "Failed to name " + name + " fd"; |
| phosphor::logging::log<phosphor::logging::level::ERR>(errMsg.c_str()); |
| return false; |
| } |
| |
| gpioEventDescriptor.assign(gpioLineFd); |
| |
| gpioEventDescriptor.async_wait( |
| boost::asio::posix::stream_descriptor::wait_read, |
| [&name, handler](const boost::system::error_code ec) { |
| if (ec) |
| { |
| std::string errMsg = |
| name + " fd handler error: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| // TODO: throw here to force power-control to restart? |
| return; |
| } |
| handler(); |
| }); |
| return true; |
| } |
| |
| static bool setGPIOOutput(const std::string& name, const int value, |
| gpiod::line& gpioLine) |
| { |
| // Find the GPIO line |
| gpioLine = gpiod::find_line(name); |
| if (!gpioLine) |
| { |
| std::string errMsg = "Failed to find the " + name + " line"; |
| phosphor::logging::log<phosphor::logging::level::ERR>(errMsg.c_str()); |
| return false; |
| } |
| |
| // Request GPIO output to specified value |
| try |
| { |
| gpioLine.request({__FUNCTION__, gpiod::line_request::DIRECTION_OUTPUT}, |
| value); |
| } |
| catch (std::exception&) |
| { |
| std::string errMsg = "Failed to request " + name + " output"; |
| phosphor::logging::log<phosphor::logging::level::ERR>(errMsg.c_str()); |
| return false; |
| } |
| |
| std::string logMsg = name + " set to " + std::to_string(value); |
| phosphor::logging::log<phosphor::logging::level::INFO>(logMsg.c_str()); |
| return true; |
| } |
| |
| static int setMaskedGPIOOutputForMs(gpiod::line& maskedGPIOLine, |
| const std::string& name, const int value, |
| const int durationMs) |
| { |
| // Set the masked GPIO line to the specified value |
| maskedGPIOLine.set_value(value); |
| std::string logMsg = name + " set to " + std::to_string(value); |
| phosphor::logging::log<phosphor::logging::level::INFO>(logMsg.c_str()); |
| gpioAssertTimer.expires_after(std::chrono::milliseconds(durationMs)); |
| gpioAssertTimer.async_wait([maskedGPIOLine, value, |
| name](const boost::system::error_code ec) { |
| // Set the masked GPIO line back to the opposite value |
| maskedGPIOLine.set_value(!value); |
| std::string logMsg = name + " released"; |
| phosphor::logging::log<phosphor::logging::level::INFO>(logMsg.c_str()); |
| if (ec) |
| { |
| // operation_aborted is expected if timer is canceled before |
| // completion. |
| if (ec != boost::asio::error::operation_aborted) |
| { |
| std::string errMsg = |
| name + " async_wait failed: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| } |
| } |
| }); |
| return 0; |
| } |
| |
| static int setGPIOOutputForMs(const std::string& name, const int value, |
| const int durationMs) |
| { |
| // If the requested GPIO is masked, use the mask line to set the output |
| if (powerButtonMask && name == power_control::powerOutName) |
| { |
| return setMaskedGPIOOutputForMs(powerButtonMask, name, value, |
| durationMs); |
| } |
| if (resetButtonMask && name == power_control::resetOutName) |
| { |
| return setMaskedGPIOOutputForMs(resetButtonMask, name, value, |
| durationMs); |
| } |
| |
| // No mask set, so request and set the GPIO normally |
| gpiod::line gpioLine; |
| if (!setGPIOOutput(name, value, gpioLine)) |
| { |
| return -1; |
| } |
| gpioAssertTimer.expires_after(std::chrono::milliseconds(durationMs)); |
| gpioAssertTimer.async_wait([gpioLine, value, |
| name](const boost::system::error_code ec) { |
| // Set the GPIO line back to the opposite value |
| gpioLine.set_value(!value); |
| std::string logMsg = name + " released"; |
| phosphor::logging::log<phosphor::logging::level::INFO>(logMsg.c_str()); |
| if (ec) |
| { |
| // operation_aborted is expected if timer is canceled before |
| // completion. |
| if (ec != boost::asio::error::operation_aborted) |
| { |
| std::string errMsg = |
| name + " async_wait failed: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| } |
| } |
| }); |
| return 0; |
| } |
| |
| static void powerOn() |
| { |
| setGPIOOutputForMs(power_control::powerOutName, 0, powerPulseTimeMs); |
| } |
| |
| static void gracefulPowerOff() |
| { |
| setGPIOOutputForMs(power_control::powerOutName, 0, powerPulseTimeMs); |
| } |
| |
| static void forcePowerOff() |
| { |
| if (setGPIOOutputForMs(power_control::powerOutName, 0, |
| forceOffPulseTimeMs) < 0) |
| { |
| return; |
| } |
| |
| // If the force off timer expires, then the PCH power-button override |
| // failed, so attempt the Unconditional Powerdown SMBus command. |
| gpioAssertTimer.async_wait([](const boost::system::error_code ec) { |
| if (ec) |
| { |
| // operation_aborted is expected if timer is canceled before |
| // completion. |
| if (ec != boost::asio::error::operation_aborted) |
| { |
| std::string errMsg = |
| "Force power off async_wait failed: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| } |
| return; |
| } |
| |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "PCH Power-button override failed. Issuing Unconditional Powerdown " |
| "SMBus command."); |
| const static constexpr size_t pchDevBusAddress = 3; |
| const static constexpr size_t pchDevSlaveAddress = 0x44; |
| const static constexpr size_t pchCmdReg = 0; |
| const static constexpr size_t pchPowerDownCmd = 0x02; |
| if (i2cSet(pchDevBusAddress, pchDevSlaveAddress, pchCmdReg, |
| pchPowerDownCmd) < 0) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unconditional Powerdown command failed! Not sure what to do " |
| "now."); |
| } |
| }); |
| } |
| |
| static void reset() |
| { |
| setGPIOOutputForMs(power_control::resetOutName, 0, resetPulseTimeMs); |
| } |
| |
| static void gracefulPowerOffTimerStart() |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Graceful power-off timer started"); |
| gracefulPowerOffTimer.expires_after( |
| std::chrono::seconds(gracefulPowerOffTimeS)); |
| gracefulPowerOffTimer.async_wait([](const boost::system::error_code ec) { |
| if (ec) |
| { |
| // operation_aborted is expected if timer is canceled before |
| // completion. |
| if (ec != boost::asio::error::operation_aborted) |
| { |
| std::string errMsg = |
| "Graceful power-off async_wait failed: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Graceful power-off timer canceled"); |
| return; |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Graceful power-off timer completed"); |
| sendPowerControlEvent(Event::gracefulPowerOffTimerExpired); |
| }); |
| } |
| |
| static void powerCycleTimerStart() |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Power-cycle timer started"); |
| powerCycleTimer.expires_after(std::chrono::milliseconds(powerCycleTimeMs)); |
| powerCycleTimer.async_wait([](const boost::system::error_code ec) { |
| if (ec) |
| { |
| // operation_aborted is expected if timer is canceled before |
| // completion. |
| if (ec != boost::asio::error::operation_aborted) |
| { |
| std::string errMsg = |
| "Power-cycle async_wait failed: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Power-cycle timer canceled"); |
| return; |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Power-cycle timer completed"); |
| sendPowerControlEvent(Event::powerCycleTimerExpired); |
| }); |
| } |
| |
| static void psPowerOKWatchdogTimerStart() |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "power supply power OK watchdog timer started"); |
| psPowerOKWatchdogTimer.expires_after( |
| std::chrono::milliseconds(psPowerOKWatchdogTimeMs)); |
| psPowerOKWatchdogTimer.async_wait([](const boost::system::error_code ec) { |
| if (ec) |
| { |
| // operation_aborted is expected if timer is canceled before |
| // completion. |
| if (ec != boost::asio::error::operation_aborted) |
| { |
| std::string errMsg = |
| "power supply power OK watchdog async_wait failed: " + |
| ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "power supply power OK watchdog timer canceled"); |
| return; |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "power supply power OK watchdog timer expired"); |
| sendPowerControlEvent(Event::psPowerOKWatchdogTimerExpired); |
| }); |
| } |
| |
| static void warmResetCheckTimerStart() |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Warm reset check timer started"); |
| warmResetCheckTimer.expires_after( |
| std::chrono::milliseconds(warmResetCheckTimeMs)); |
| warmResetCheckTimer.async_wait([](const boost::system::error_code ec) { |
| if (ec) |
| { |
| // operation_aborted is expected if timer is canceled before |
| // completion. |
| if (ec != boost::asio::error::operation_aborted) |
| { |
| std::string errMsg = |
| "Warm reset check async_wait failed: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Warm reset check timer canceled"); |
| return; |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Warm reset check timer completed"); |
| sendPowerControlEvent(Event::warmResetDetected); |
| }); |
| } |
| |
| static void pohCounterTimerStart() |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>("POH timer started"); |
| // Set the time-out as 1 hour, to align with POH command in ipmid |
| pohCounterTimer.expires_after(std::chrono::hours(1)); |
| pohCounterTimer.async_wait([](const boost::system::error_code& ec) { |
| if (ec) |
| { |
| // operation_aborted is expected if timer is canceled before |
| // completion. |
| if (ec != boost::asio::error::operation_aborted) |
| { |
| std::string errMsg = |
| "POH timer async_wait failed: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "POH timer canceled"); |
| return; |
| } |
| |
| if (getHostState(powerState) != |
| "xyz.openbmc_project.State.Host.HostState.Running") |
| { |
| return; |
| } |
| |
| conn->async_method_call( |
| [](boost::system::error_code ec, |
| const std::variant<uint32_t>& pohCounterProperty) { |
| if (ec) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "error to get poh counter"); |
| return; |
| } |
| const uint32_t* pohCounter = |
| std::get_if<uint32_t>(&pohCounterProperty); |
| if (pohCounter == nullptr) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "unable to read poh counter"); |
| return; |
| } |
| |
| conn->async_method_call( |
| [](boost::system::error_code ec) { |
| if (ec) |
| { |
| phosphor::logging::log< |
| phosphor::logging::level::INFO>( |
| "failed to set poh counter"); |
| } |
| }, |
| "xyz.openbmc_project.Settings", |
| "/xyz/openbmc_project/state/chassis0", |
| "org.freedesktop.DBus.Properties", "Set", |
| "xyz.openbmc_project.State.PowerOnHours", "POHCounter", |
| std::variant<uint32_t>(*pohCounter + 1)); |
| }, |
| "xyz.openbmc_project.Settings", |
| "/xyz/openbmc_project/state/chassis0", |
| "org.freedesktop.DBus.Properties", "Get", |
| "xyz.openbmc_project.State.PowerOnHours", "POHCounter"); |
| |
| pohCounterTimerStart(); |
| }); |
| } |
| |
| static void currentHostStateMonitor() |
| { |
| if (getHostState(powerState) == |
| "xyz.openbmc_project.State.Host.HostState.Running") |
| { |
| pohCounterTimerStart(); |
| // Clear the restart cause set for the next restart |
| clearRestartCause(); |
| } |
| else |
| { |
| pohCounterTimer.cancel(); |
| // Set the restart cause set for this restart |
| setRestartCause(); |
| } |
| |
| static auto match = sdbusplus::bus::match::match( |
| *conn, |
| "type='signal',member='PropertiesChanged', " |
| "interface='org.freedesktop.DBus.Properties', " |
| "arg0='xyz.openbmc_project.State.Host'", |
| [](sdbusplus::message::message& message) { |
| std::string intfName; |
| std::map<std::string, std::variant<std::string>> properties; |
| |
| try |
| { |
| message.read(intfName, properties); |
| } |
| catch (std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unable to read host state"); |
| return; |
| } |
| if (properties.empty()) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "ERROR: Empty PropertiesChanged signal received"); |
| return; |
| } |
| |
| // We only want to check for CurrentHostState |
| if (properties.begin()->first != "CurrentHostState") |
| { |
| return; |
| } |
| std::string* currentHostState = |
| std::get_if<std::string>(&(properties.begin()->second)); |
| if (currentHostState == nullptr) |
| { |
| std::string errMsg = |
| properties.begin()->first + " property invalid"; |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| return; |
| } |
| |
| if (*currentHostState == |
| "xyz.openbmc_project.State.Host.HostState.Running") |
| { |
| pohCounterTimerStart(); |
| // Clear the restart cause set for the next restart |
| clearRestartCause(); |
| sd_journal_send("MESSAGE=Host system DC power is on", |
| "PRIORITY=%i", LOG_INFO, |
| "REDFISH_MESSAGE_ID=%s", |
| "OpenBMC.0.1.DCPowerOn", NULL); |
| } |
| else |
| { |
| pohCounterTimer.cancel(); |
| // POST_COMPLETE GPIO event is not working in some platforms |
| // when power state is changed to OFF. This resulted in |
| // 'OperatingSystemState' to stay at 'Standby', even though |
| // system is OFF. Set 'OperatingSystemState' to 'Inactive' |
| // if HostState is trurned to OFF. |
| osIface->set_property("OperatingSystemState", |
| std::string("Inactive")); |
| |
| // Set the restart cause set for this restart |
| setRestartCause(); |
| resetACBootProperty(); |
| sd_journal_send("MESSAGE=Host system DC power is off", |
| "PRIORITY=%i", LOG_INFO, |
| "REDFISH_MESSAGE_ID=%s", |
| "OpenBMC.0.1.DCPowerOff", NULL); |
| } |
| }); |
| } |
| |
| static void sioPowerGoodWatchdogTimerStart() |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "SIO power good watchdog timer started"); |
| sioPowerGoodWatchdogTimer.expires_after( |
| std::chrono::milliseconds(sioPowerGoodWatchdogTimeMs)); |
| sioPowerGoodWatchdogTimer.async_wait( |
| [](const boost::system::error_code ec) { |
| if (ec) |
| { |
| // operation_aborted is expected if timer is canceled before |
| // completion. |
| if (ec != boost::asio::error::operation_aborted) |
| { |
| std::string errMsg = |
| "SIO power good watchdog async_wait failed: " + |
| ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "SIO power good watchdog timer canceled"); |
| return; |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "SIO power good watchdog timer completed"); |
| sendPowerControlEvent(Event::sioPowerGoodWatchdogTimerExpired); |
| }); |
| } |
| |
| static void powerStateOn(const Event event) |
| { |
| logEvent(__FUNCTION__, event); |
| switch (event) |
| { |
| case Event::psPowerOKDeAssert: |
| setPowerState(PowerState::off); |
| // DC power is unexpectedly lost, beep |
| beep(beepPowerFail); |
| break; |
| case Event::sioS5Assert: |
| setPowerState(PowerState::transitionToOff); |
| addRestartCause(RestartCause::softReset); |
| break; |
| #if USE_PLT_RST |
| case Event::pltRstAssert: |
| #else |
| case Event::postCompleteDeAssert: |
| #endif |
| setPowerState(PowerState::checkForWarmReset); |
| addRestartCause(RestartCause::softReset); |
| warmResetCheckTimerStart(); |
| break; |
| case Event::powerButtonPressed: |
| setPowerState(PowerState::gracefulTransitionToOff); |
| gracefulPowerOffTimerStart(); |
| break; |
| case Event::powerOffRequest: |
| setPowerState(PowerState::transitionToOff); |
| forcePowerOff(); |
| break; |
| case Event::gracefulPowerOffRequest: |
| setPowerState(PowerState::gracefulTransitionToOff); |
| gracefulPowerOffTimerStart(); |
| gracefulPowerOff(); |
| break; |
| case Event::powerCycleRequest: |
| setPowerState(PowerState::transitionToCycleOff); |
| forcePowerOff(); |
| break; |
| case Event::gracefulPowerCycleRequest: |
| setPowerState(PowerState::gracefulTransitionToCycleOff); |
| gracefulPowerOffTimerStart(); |
| gracefulPowerOff(); |
| break; |
| case Event::resetRequest: |
| reset(); |
| break; |
| default: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "No action taken."); |
| break; |
| } |
| } |
| |
| static void powerStateWaitForPSPowerOK(const Event event) |
| { |
| logEvent(__FUNCTION__, event); |
| switch (event) |
| { |
| case Event::psPowerOKAssert: |
| { |
| // Cancel any GPIO assertions held during the transition |
| gpioAssertTimer.cancel(); |
| psPowerOKWatchdogTimer.cancel(); |
| if (sioEnabled == true) |
| { |
| sioPowerGoodWatchdogTimerStart(); |
| setPowerState(PowerState::waitForSIOPowerGood); |
| } |
| else |
| { |
| setPowerState(PowerState::on); |
| } |
| break; |
| } |
| case Event::psPowerOKWatchdogTimerExpired: |
| setPowerState(PowerState::off); |
| psPowerOKFailedLog(); |
| break; |
| case Event::sioPowerGoodAssert: |
| psPowerOKWatchdogTimer.cancel(); |
| setPowerState(PowerState::on); |
| break; |
| default: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "No action taken."); |
| break; |
| } |
| } |
| |
| static void powerStateWaitForSIOPowerGood(const Event event) |
| { |
| logEvent(__FUNCTION__, event); |
| switch (event) |
| { |
| case Event::sioPowerGoodAssert: |
| sioPowerGoodWatchdogTimer.cancel(); |
| setPowerState(PowerState::on); |
| break; |
| case Event::sioPowerGoodWatchdogTimerExpired: |
| setPowerState(PowerState::off); |
| systemPowerGoodFailedLog(); |
| break; |
| default: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "No action taken."); |
| break; |
| } |
| } |
| |
| static void powerStateOff(const Event event) |
| { |
| logEvent(__FUNCTION__, event); |
| switch (event) |
| { |
| case Event::psPowerOKAssert: |
| { |
| if (sioEnabled == true) |
| { |
| setPowerState(PowerState::waitForSIOPowerGood); |
| } |
| else |
| { |
| setPowerState(PowerState::on); |
| } |
| break; |
| } |
| case Event::sioS5DeAssert: |
| setPowerState(PowerState::waitForPSPowerOK); |
| break; |
| case Event::sioPowerGoodAssert: |
| setPowerState(PowerState::on); |
| break; |
| case Event::powerButtonPressed: |
| psPowerOKWatchdogTimerStart(); |
| setPowerState(PowerState::waitForPSPowerOK); |
| break; |
| case Event::powerOnRequest: |
| psPowerOKWatchdogTimerStart(); |
| setPowerState(PowerState::waitForPSPowerOK); |
| powerOn(); |
| break; |
| default: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "No action taken."); |
| break; |
| } |
| } |
| |
| static void powerStateTransitionToOff(const Event event) |
| { |
| logEvent(__FUNCTION__, event); |
| switch (event) |
| { |
| case Event::psPowerOKDeAssert: |
| // Cancel any GPIO assertions held during the transition |
| gpioAssertTimer.cancel(); |
| setPowerState(PowerState::off); |
| break; |
| default: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "No action taken."); |
| break; |
| } |
| } |
| |
| static void powerStateGracefulTransitionToOff(const Event event) |
| { |
| logEvent(__FUNCTION__, event); |
| switch (event) |
| { |
| case Event::psPowerOKDeAssert: |
| gracefulPowerOffTimer.cancel(); |
| setPowerState(PowerState::off); |
| break; |
| case Event::gracefulPowerOffTimerExpired: |
| setPowerState(PowerState::on); |
| break; |
| case Event::powerOffRequest: |
| gracefulPowerOffTimer.cancel(); |
| setPowerState(PowerState::transitionToOff); |
| forcePowerOff(); |
| break; |
| case Event::powerCycleRequest: |
| gracefulPowerOffTimer.cancel(); |
| setPowerState(PowerState::transitionToCycleOff); |
| forcePowerOff(); |
| break; |
| case Event::resetRequest: |
| gracefulPowerOffTimer.cancel(); |
| setPowerState(PowerState::on); |
| reset(); |
| break; |
| default: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "No action taken."); |
| break; |
| } |
| } |
| |
| static void powerStateCycleOff(const Event event) |
| { |
| logEvent(__FUNCTION__, event); |
| switch (event) |
| { |
| case Event::psPowerOKAssert: |
| { |
| powerCycleTimer.cancel(); |
| if (sioEnabled == true) |
| { |
| setPowerState(PowerState::waitForSIOPowerGood); |
| } |
| else |
| { |
| setPowerState(PowerState::on); |
| } |
| break; |
| } |
| case Event::sioS5DeAssert: |
| powerCycleTimer.cancel(); |
| setPowerState(PowerState::waitForPSPowerOK); |
| break; |
| case Event::powerButtonPressed: |
| powerCycleTimer.cancel(); |
| psPowerOKWatchdogTimerStart(); |
| setPowerState(PowerState::waitForPSPowerOK); |
| break; |
| case Event::powerCycleTimerExpired: |
| psPowerOKWatchdogTimerStart(); |
| setPowerState(PowerState::waitForPSPowerOK); |
| powerOn(); |
| break; |
| default: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "No action taken."); |
| break; |
| } |
| } |
| |
| static void powerStateTransitionToCycleOff(const Event event) |
| { |
| logEvent(__FUNCTION__, event); |
| switch (event) |
| { |
| case Event::psPowerOKDeAssert: |
| // Cancel any GPIO assertions held during the transition |
| gpioAssertTimer.cancel(); |
| setPowerState(PowerState::cycleOff); |
| powerCycleTimerStart(); |
| break; |
| default: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "No action taken."); |
| break; |
| } |
| } |
| |
| static void powerStateGracefulTransitionToCycleOff(const Event event) |
| { |
| logEvent(__FUNCTION__, event); |
| switch (event) |
| { |
| case Event::psPowerOKDeAssert: |
| gracefulPowerOffTimer.cancel(); |
| setPowerState(PowerState::cycleOff); |
| powerCycleTimerStart(); |
| break; |
| case Event::gracefulPowerOffTimerExpired: |
| setPowerState(PowerState::on); |
| break; |
| case Event::powerOffRequest: |
| gracefulPowerOffTimer.cancel(); |
| setPowerState(PowerState::transitionToOff); |
| forcePowerOff(); |
| break; |
| case Event::powerCycleRequest: |
| gracefulPowerOffTimer.cancel(); |
| setPowerState(PowerState::transitionToCycleOff); |
| forcePowerOff(); |
| break; |
| case Event::resetRequest: |
| gracefulPowerOffTimer.cancel(); |
| setPowerState(PowerState::on); |
| reset(); |
| break; |
| default: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "No action taken."); |
| break; |
| } |
| } |
| |
| static void powerStateCheckForWarmReset(const Event event) |
| { |
| logEvent(__FUNCTION__, event); |
| switch (event) |
| { |
| case Event::sioS5Assert: |
| warmResetCheckTimer.cancel(); |
| setPowerState(PowerState::transitionToOff); |
| break; |
| case Event::warmResetDetected: |
| setPowerState(PowerState::on); |
| break; |
| case Event::psPowerOKDeAssert: |
| warmResetCheckTimer.cancel(); |
| setPowerState(PowerState::off); |
| // DC power is unexpectedly lost, beep |
| beep(beepPowerFail); |
| break; |
| default: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "No action taken."); |
| break; |
| } |
| } |
| |
| static void psPowerOKHandler() |
| { |
| gpiod::line_event gpioLineEvent = psPowerOKLine.event_read(); |
| |
| Event powerControlEvent = |
| gpioLineEvent.event_type == gpiod::line_event::RISING_EDGE |
| ? Event::psPowerOKAssert |
| : Event::psPowerOKDeAssert; |
| |
| sendPowerControlEvent(powerControlEvent); |
| psPowerOKEvent.async_wait( |
| boost::asio::posix::stream_descriptor::wait_read, |
| [](const boost::system::error_code ec) { |
| if (ec) |
| { |
| std::string errMsg = |
| "power supply power OK handler error: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| return; |
| } |
| psPowerOKHandler(); |
| }); |
| } |
| |
| static void sioPowerGoodHandler() |
| { |
| gpiod::line_event gpioLineEvent = sioPowerGoodLine.event_read(); |
| |
| Event powerControlEvent = |
| gpioLineEvent.event_type == gpiod::line_event::RISING_EDGE |
| ? Event::sioPowerGoodAssert |
| : Event::sioPowerGoodDeAssert; |
| |
| sendPowerControlEvent(powerControlEvent); |
| sioPowerGoodEvent.async_wait( |
| boost::asio::posix::stream_descriptor::wait_read, |
| [](const boost::system::error_code ec) { |
| if (ec) |
| { |
| std::string errMsg = |
| "SIO power good handler error: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| return; |
| } |
| sioPowerGoodHandler(); |
| }); |
| } |
| |
| static void sioOnControlHandler() |
| { |
| gpiod::line_event gpioLineEvent = sioOnControlLine.event_read(); |
| |
| bool sioOnControl = |
| gpioLineEvent.event_type == gpiod::line_event::RISING_EDGE; |
| std::string logMsg = |
| "SIO_ONCONTROL value changed: " + std::to_string(sioOnControl); |
| phosphor::logging::log<phosphor::logging::level::INFO>(logMsg.c_str()); |
| sioOnControlEvent.async_wait( |
| boost::asio::posix::stream_descriptor::wait_read, |
| [](const boost::system::error_code ec) { |
| if (ec) |
| { |
| std::string errMsg = |
| "SIO ONCONTROL handler error: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| return; |
| } |
| sioOnControlHandler(); |
| }); |
| } |
| |
| static void sioS5Handler() |
| { |
| gpiod::line_event gpioLineEvent = sioS5Line.event_read(); |
| |
| Event powerControlEvent = |
| gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE |
| ? Event::sioS5Assert |
| : Event::sioS5DeAssert; |
| |
| sendPowerControlEvent(powerControlEvent); |
| sioS5Event.async_wait( |
| boost::asio::posix::stream_descriptor::wait_read, |
| [](const boost::system::error_code ec) { |
| if (ec) |
| { |
| std::string errMsg = "SIO S5 handler error: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| return; |
| } |
| sioS5Handler(); |
| }); |
| } |
| |
| static void powerButtonHandler() |
| { |
| gpiod::line_event gpioLineEvent = powerButtonLine.event_read(); |
| |
| if (gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE) |
| { |
| powerButtonPressLog(); |
| powerButtonIface->set_property("ButtonPressed", true); |
| if (!powerButtonMask) |
| { |
| sendPowerControlEvent(Event::powerButtonPressed); |
| addRestartCause(RestartCause::powerButton); |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "power button press masked"); |
| } |
| } |
| else if (gpioLineEvent.event_type == gpiod::line_event::RISING_EDGE) |
| { |
| powerButtonIface->set_property("ButtonPressed", false); |
| } |
| powerButtonEvent.async_wait( |
| boost::asio::posix::stream_descriptor::wait_read, |
| [](const boost::system::error_code ec) { |
| if (ec) |
| { |
| std::string errMsg = |
| "power button handler error: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| return; |
| } |
| powerButtonHandler(); |
| }); |
| } |
| |
| static void resetButtonHandler() |
| { |
| gpiod::line_event gpioLineEvent = resetButtonLine.event_read(); |
| |
| if (gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE) |
| { |
| resetButtonPressLog(); |
| resetButtonIface->set_property("ButtonPressed", true); |
| if (!resetButtonMask) |
| { |
| sendPowerControlEvent(Event::resetButtonPressed); |
| addRestartCause(RestartCause::resetButton); |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "reset button press masked"); |
| } |
| } |
| else if (gpioLineEvent.event_type == gpiod::line_event::RISING_EDGE) |
| { |
| resetButtonIface->set_property("ButtonPressed", false); |
| } |
| resetButtonEvent.async_wait( |
| boost::asio::posix::stream_descriptor::wait_read, |
| [](const boost::system::error_code ec) { |
| if (ec) |
| { |
| std::string errMsg = |
| "reset button handler error: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| return; |
| } |
| resetButtonHandler(); |
| }); |
| } |
| |
| #ifdef CHASSIS_SYSTEM_RESET |
| static constexpr auto systemdBusname = "org.freedesktop.systemd1"; |
| static constexpr auto systemdPath = "/org/freedesktop/systemd1"; |
| static constexpr auto systemdInterface = "org.freedesktop.systemd1.Manager"; |
| static constexpr auto systemTargetName = "chassis-system-reset.target"; |
| |
| void systemReset() |
| { |
| conn->async_method_call( |
| [](boost::system::error_code ec) { |
| if (ec) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Failed to call chassis system reset", |
| phosphor::logging::entry("ERR=%s", ec.message().c_str())); |
| } |
| }, |
| systemdBusname, systemdPath, systemdInterface, "StartUnit", |
| systemTargetName, "replace"); |
| } |
| #endif |
| |
| static void nmiSetEnableProperty(bool value) |
| { |
| conn->async_method_call( |
| [](boost::system::error_code ec) { |
| if (ec) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "failed to set NMI source"); |
| } |
| }, |
| "xyz.openbmc_project.Settings", |
| "/xyz/openbmc_project/Chassis/Control/NMISource", |
| "org.freedesktop.DBus.Properties", "Set", |
| "xyz.openbmc_project.Chassis.Control.NMISource", "Enabled", |
| std::variant<bool>{value}); |
| } |
| |
| static void nmiReset(void) |
| { |
| static constexpr const uint8_t value = 1; |
| const static constexpr int nmiOutPulseTimeMs = 200; |
| |
| phosphor::logging::log<phosphor::logging::level::INFO>("NMI out action"); |
| nmiOutLine.set_value(value); |
| std::string logMsg = nmiOutName + " set to " + std::to_string(value); |
| phosphor::logging::log<phosphor::logging::level::INFO>(logMsg.c_str()); |
| gpioAssertTimer.expires_after(std::chrono::milliseconds(nmiOutPulseTimeMs)); |
| gpioAssertTimer.async_wait([](const boost::system::error_code ec) { |
| // restore the NMI_OUT GPIO line back to the opposite value |
| nmiOutLine.set_value(!value); |
| std::string logMsg = nmiOutName + " released"; |
| phosphor::logging::log<phosphor::logging::level::INFO>(logMsg.c_str()); |
| if (ec) |
| { |
| // operation_aborted is expected if timer is canceled before |
| // completion. |
| if (ec != boost::asio::error::operation_aborted) |
| { |
| std::string errMsg = |
| nmiOutName + " async_wait failed: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| } |
| } |
| }); |
| // log to redfish |
| nmiDiagIntLog(); |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "NMI out action completed"); |
| // reset Enable Property |
| nmiSetEnableProperty(false); |
| } |
| |
| static void nmiSourcePropertyMonitor(void) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "NMI Source Property Monitor"); |
| |
| static std::unique_ptr<sdbusplus::bus::match::match> nmiSourceMatch = |
| std::make_unique<sdbusplus::bus::match::match>( |
| *conn, |
| "type='signal',interface='org.freedesktop.DBus.Properties'," |
| "member='PropertiesChanged',arg0namespace='xyz.openbmc_project." |
| "Chassis.Control." |
| "NMISource'", |
| [](sdbusplus::message::message& msg) { |
| std::string interfaceName; |
| boost::container::flat_map<std::string, |
| std::variant<bool, std::string>> |
| propertiesChanged; |
| std::string state; |
| bool value = true; |
| try |
| { |
| msg.read(interfaceName, propertiesChanged); |
| if (propertiesChanged.begin()->first == "Enabled") |
| { |
| value = |
| std::get<bool>(propertiesChanged.begin()->second); |
| std::string logMsg = |
| " NMI Enabled propertiesChanged value: " + |
| std::to_string(value); |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| logMsg.c_str()); |
| nmiEnabled = value; |
| if (nmiEnabled) |
| { |
| nmiReset(); |
| } |
| } |
| } |
| catch (std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unable to read NMI source"); |
| return; |
| } |
| }); |
| } |
| |
| static void setNmiSource() |
| { |
| conn->async_method_call( |
| [](boost::system::error_code ec) { |
| if (ec) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "failed to set NMI source"); |
| } |
| }, |
| "xyz.openbmc_project.Settings", |
| "/xyz/openbmc_project/Chassis/Control/NMISource", |
| "org.freedesktop.DBus.Properties", "Set", |
| "xyz.openbmc_project.Chassis.Control.NMISource", "BMCSource", |
| std::variant<std::string>{"xyz.openbmc_project.Chassis.Control." |
| "NMISource.BMCSourceSignal.FpBtn"}); |
| // set Enable Property |
| nmiSetEnableProperty(true); |
| } |
| |
| static void nmiButtonHandler() |
| { |
| gpiod::line_event gpioLineEvent = nmiButtonLine.event_read(); |
| |
| if (gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE) |
| { |
| nmiButtonPressLog(); |
| nmiButtonIface->set_property("ButtonPressed", true); |
| if (nmiButtonMasked) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "NMI button press masked"); |
| } |
| else |
| { |
| setNmiSource(); |
| } |
| } |
| else if (gpioLineEvent.event_type == gpiod::line_event::RISING_EDGE) |
| { |
| nmiButtonIface->set_property("ButtonPressed", false); |
| } |
| nmiButtonEvent.async_wait( |
| boost::asio::posix::stream_descriptor::wait_read, |
| [](const boost::system::error_code ec) { |
| if (ec) |
| { |
| std::string errMsg = |
| "NMI button handler error: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| return; |
| } |
| nmiButtonHandler(); |
| }); |
| } |
| |
| static void idButtonHandler() |
| { |
| gpiod::line_event gpioLineEvent = idButtonLine.event_read(); |
| |
| if (gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE) |
| { |
| idButtonIface->set_property("ButtonPressed", true); |
| } |
| else if (gpioLineEvent.event_type == gpiod::line_event::RISING_EDGE) |
| { |
| idButtonIface->set_property("ButtonPressed", false); |
| } |
| idButtonEvent.async_wait( |
| boost::asio::posix::stream_descriptor::wait_read, |
| [](const boost::system::error_code& ec) { |
| if (ec) |
| { |
| std::string errMsg = "ID button handler error: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| return; |
| } |
| idButtonHandler(); |
| }); |
| } |
| |
| static void pltRstHandler(bool pltRst) |
| { |
| if (pltRst) |
| { |
| sendPowerControlEvent(Event::pltRstDeAssert); |
| } |
| else |
| { |
| sendPowerControlEvent(Event::pltRstAssert); |
| } |
| } |
| |
| static void hostMiscHandler(sdbusplus::message::message& msg) |
| { |
| std::string interfaceName; |
| boost::container::flat_map<std::string, std::variant<bool>> |
| propertiesChanged; |
| bool pltRst; |
| try |
| { |
| msg.read(interfaceName, propertiesChanged); |
| } |
| catch (std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unable to read Host Misc status"); |
| return; |
| } |
| if (propertiesChanged.empty()) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "ERROR: Empty Host.Misc PropertiesChanged signal received"); |
| return; |
| } |
| |
| for (auto& [property, value] : propertiesChanged) |
| { |
| if (property == "ESpiPlatformReset") |
| { |
| bool* pltRst = std::get_if<bool>(&value); |
| if (pltRst == nullptr) |
| { |
| std::string errMsg = property + " property invalid"; |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| return; |
| } |
| pltRstHandler(*pltRst); |
| } |
| } |
| } |
| |
| static void postCompleteHandler() |
| { |
| gpiod::line_event gpioLineEvent = postCompleteLine.event_read(); |
| |
| bool postComplete = |
| gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE; |
| if (postComplete) |
| { |
| sendPowerControlEvent(Event::postCompleteAssert); |
| osIface->set_property("OperatingSystemState", std::string("Standby")); |
| } |
| else |
| { |
| sendPowerControlEvent(Event::postCompleteDeAssert); |
| osIface->set_property("OperatingSystemState", std::string("Inactive")); |
| } |
| postCompleteEvent.async_wait( |
| boost::asio::posix::stream_descriptor::wait_read, |
| [](const boost::system::error_code ec) { |
| if (ec) |
| { |
| std::string errMsg = |
| "POST complete handler error: " + ec.message(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| errMsg.c_str()); |
| return; |
| } |
| postCompleteHandler(); |
| }); |
| } |
| |
| static int loadConfigValues() |
| { |
| const std::string configFilePath = |
| "/usr/share/x86-power-control/power-config-host" + power_control::node + |
| ".json"; |
| std::ifstream configFile(configFilePath.c_str()); |
| if (!configFile.is_open()) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "loadConfigValues : Cannot open config path"); |
| return -1; |
| } |
| auto data = nlohmann::json::parse(configFile, nullptr); |
| |
| if (data.is_discarded()) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Power config readings JSON parser failure"); |
| return -1; |
| } |
| |
| if (data.contains("IdButton")) |
| { |
| idButtonName = data["IdButton"]; |
| } |
| |
| if (data.contains("NMIButton")) |
| { |
| nmiButtonName = data["NMIButton"]; |
| } |
| |
| if (data.contains("NMIOut")) |
| { |
| nmiOutName = data["NMIOut"]; |
| } |
| |
| if (data.contains("PostComplete")) |
| { |
| postCompleteName = data["PostComplete"]; |
| } |
| |
| if (data.contains("PwrButton")) |
| { |
| powerButtonName = data["PwrButton"]; |
| } |
| |
| if (data.contains("PwrOK")) |
| { |
| powerOkName = data["PwrOK"]; |
| } |
| |
| if (data.contains("PwrOut")) |
| { |
| powerOutName = data["PwrOut"]; |
| } |
| |
| if (data.contains("RstButton")) |
| { |
| resetButtonName = data["RstButton"]; |
| } |
| |
| if (data.contains("RstOut")) |
| { |
| resetOutName = data["RstOut"]; |
| } |
| |
| if (data.contains("SIOOnCtl")) |
| { |
| sioOnControlName = data["SIOOnCtl"]; |
| } |
| |
| if (data.contains("SIOPwrGd")) |
| { |
| sioPwrGoodName = data["SIOPwrGd"]; |
| } |
| |
| if (data.contains("SIOS5")) |
| { |
| sioS5Name = data["SIOS5"]; |
| } |
| |
| return 0; |
| } |
| |
| } // namespace power_control |
| |
| int main(int argc, char* argv[]) |
| { |
| using namespace power_control; |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Start Chassis power control service..."); |
| conn = std::make_shared<sdbusplus::asio::connection>(io); |
| |
| // Load GPIO's through json config file |
| if (loadConfigValues() == -1) |
| { |
| std::string errMsg = "Host" + node + ": " + "Error in Parsing..."; |
| phosphor::logging::log<phosphor::logging::level::ERR>(errMsg.c_str()); |
| } |
| |
| // Request all the dbus names |
| conn->request_name("xyz.openbmc_project.State.Host"); |
| conn->request_name("xyz.openbmc_project.State.Chassis"); |
| conn->request_name("xyz.openbmc_project.State.OperatingSystem"); |
| conn->request_name("xyz.openbmc_project.Chassis.Buttons"); |
| conn->request_name("xyz.openbmc_project.Control.Host.NMI"); |
| conn->request_name("xyz.openbmc_project.Control.Host.RestartCause"); |
| |
| if (sioPwrGoodName.empty() || sioOnControlName.empty() || sioS5Name.empty()) |
| { |
| sioEnabled = false; |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "SIO control GPIOs not defined, disable SIO support."); |
| } |
| |
| // Request PS_PWROK GPIO events |
| if (!powerOkName.empty()) |
| { |
| if (!requestGPIOEvents(powerOkName, psPowerOKHandler, psPowerOKLine, |
| psPowerOKEvent)) |
| { |
| return -1; |
| } |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "PowerOk name should be configured from json config file"); |
| return -1; |
| } |
| |
| if (sioEnabled == true) |
| { |
| // Request SIO_POWER_GOOD GPIO events |
| if (!requestGPIOEvents(sioPwrGoodName, sioPowerGoodHandler, |
| sioPowerGoodLine, sioPowerGoodEvent)) |
| { |
| return -1; |
| } |
| |
| // Request SIO_ONCONTROL GPIO events |
| if (!requestGPIOEvents(sioOnControlName, sioOnControlHandler, |
| sioOnControlLine, sioOnControlEvent)) |
| { |
| return -1; |
| } |
| |
| // Request SIO_S5 GPIO events |
| if (!requestGPIOEvents(sioS5Name, sioS5Handler, sioS5Line, sioS5Event)) |
| { |
| return -1; |
| } |
| } |
| |
| // Request POWER_BUTTON GPIO events |
| if (!powerButtonName.empty()) |
| { |
| if (!requestGPIOEvents(powerButtonName, powerButtonHandler, |
| powerButtonLine, powerButtonEvent)) |
| { |
| return -1; |
| } |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "powerButton name should be configured from json config file"); |
| return -1; |
| } |
| |
| // Request RESET_BUTTON GPIO events |
| if (!resetButtonName.empty()) |
| { |
| if (!requestGPIOEvents(resetButtonName, resetButtonHandler, |
| resetButtonLine, resetButtonEvent)) |
| { |
| return -1; |
| } |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "ResetButton not defined..."); |
| } |
| |
| // Request NMI_BUTTON GPIO events |
| if (!nmiButtonName.empty()) |
| { |
| requestGPIOEvents(nmiButtonName, nmiButtonHandler, nmiButtonLine, |
| nmiButtonEvent); |
| } |
| |
| // Request ID_BUTTON GPIO events |
| if (!idButtonName.empty()) |
| { |
| requestGPIOEvents(idButtonName, idButtonHandler, idButtonLine, |
| idButtonEvent); |
| } |
| |
| #ifdef USE_PLT_RST |
| sdbusplus::bus::match::match pltRstMatch( |
| *conn, |
| "type='signal',interface='org.freedesktop.DBus.Properties',member='" |
| "PropertiesChanged',arg0='xyz.openbmc_project.State.Host.Misc'", |
| hostMiscHandler); |
| #endif |
| |
| // Request POST_COMPLETE GPIO events |
| if (!postCompleteName.empty()) |
| { |
| if (!requestGPIOEvents(postCompleteName, postCompleteHandler, |
| postCompleteLine, postCompleteEvent)) |
| { |
| return -1; |
| } |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "postComplete name should be configured from json config file"); |
| return -1; |
| } |
| |
| // initialize NMI_OUT GPIO. |
| setGPIOOutput(nmiOutName, 0, nmiOutLine); |
| |
| // Initialize POWER_OUT and RESET_OUT GPIO. |
| gpiod::line line; |
| if (!setGPIOOutput(powerOutName, 1, line)) |
| { |
| return -1; |
| } |
| |
| if (!setGPIOOutput(resetOutName, 1, line)) |
| { |
| return -1; |
| } |
| |
| // Release line |
| line.reset(); |
| |
| // Initialize the power state |
| powerState = PowerState::off; |
| // Check power good |
| if (psPowerOKLine.get_value() > 0) |
| { |
| powerState = PowerState::on; |
| } |
| |
| // Initialize the power state storage |
| if (initializePowerStateStorage() < 0) |
| { |
| return -1; |
| } |
| |
| // Check if we need to start the Power Restore policy |
| powerRestorePolicyCheck(); |
| |
| if (nmiOutLine) |
| nmiSourcePropertyMonitor(); |
| |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Initializing power state. "); |
| logStateTransition(powerState); |
| |
| // Power Control Service |
| sdbusplus::asio::object_server hostServer = |
| sdbusplus::asio::object_server(conn); |
| |
| // Power Control Interface |
| hostIface = hostServer.add_interface("/xyz/openbmc_project/state/host0", |
| "xyz.openbmc_project.State.Host"); |
| |
| hostIface->register_property( |
| "RequestedHostTransition", |
| std::string("xyz.openbmc_project.State.Host.Transition.Off"), |
| [](const std::string& requested, std::string& resp) { |
| if (requested == "xyz.openbmc_project.State.Host.Transition.Off") |
| { |
| sendPowerControlEvent(Event::gracefulPowerOffRequest); |
| addRestartCause(RestartCause::command); |
| } |
| else if (requested == |
| "xyz.openbmc_project.State.Host.Transition.On") |
| { |
| sendPowerControlEvent(Event::powerOnRequest); |
| addRestartCause(RestartCause::command); |
| } |
| else if (requested == |
| "xyz.openbmc_project.State.Host.Transition.Reboot") |
| { |
| sendPowerControlEvent(Event::powerCycleRequest); |
| addRestartCause(RestartCause::command); |
| } |
| else if (requested == "xyz.openbmc_project.State.Host.Transition." |
| "GracefulWarmReboot") |
| { |
| sendPowerControlEvent(Event::gracefulPowerCycleRequest); |
| addRestartCause(RestartCause::command); |
| } |
| else if (requested == "xyz.openbmc_project.State.Host.Transition." |
| "ForceWarmReboot") |
| { |
| sendPowerControlEvent(Event::resetRequest); |
| addRestartCause(RestartCause::command); |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unrecognized host state transition request."); |
| throw std::invalid_argument("Unrecognized Transition Request"); |
| return 0; |
| } |
| resp = requested; |
| return 1; |
| }); |
| hostIface->register_property("CurrentHostState", |
| std::string(getHostState(powerState))); |
| |
| hostIface->initialize(); |
| |
| // Chassis Control Service |
| sdbusplus::asio::object_server chassisServer = |
| sdbusplus::asio::object_server(conn); |
| |
| // Chassis Control Interface |
| chassisIface = |
| chassisServer.add_interface("/xyz/openbmc_project/state/chassis0", |
| "xyz.openbmc_project.State.Chassis"); |
| |
| chassisIface->register_property( |
| "RequestedPowerTransition", |
| std::string("xyz.openbmc_project.State.Chassis.Transition.Off"), |
| [](const std::string& requested, std::string& resp) { |
| if (requested == "xyz.openbmc_project.State.Chassis.Transition.Off") |
| { |
| sendPowerControlEvent(Event::powerOffRequest); |
| addRestartCause(RestartCause::command); |
| } |
| else if (requested == |
| "xyz.openbmc_project.State.Chassis.Transition.On") |
| { |
| sendPowerControlEvent(Event::powerOnRequest); |
| addRestartCause(RestartCause::command); |
| } |
| else if (requested == |
| "xyz.openbmc_project.State.Chassis.Transition.PowerCycle") |
| { |
| sendPowerControlEvent(Event::powerCycleRequest); |
| addRestartCause(RestartCause::command); |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unrecognized chassis state transition request."); |
| throw std::invalid_argument("Unrecognized Transition Request"); |
| return 0; |
| } |
| resp = requested; |
| return 1; |
| }); |
| chassisIface->register_property("CurrentPowerState", |
| std::string(getChassisState(powerState))); |
| chassisIface->register_property("LastStateChangeTime", getCurrentTimeMs()); |
| |
| chassisIface->initialize(); |
| |
| #ifdef CHASSIS_SYSTEM_RESET |
| // Chassis System Service |
| sdbusplus::asio::object_server chassisSysServer = |
| sdbusplus::asio::object_server(conn); |
| |
| // Chassis System Interface |
| chassisSysIface = chassisSysServer.add_interface( |
| "/xyz/openbmc_project/state/chassis_system0", |
| "xyz.openbmc_project.State.Chassis"); |
| |
| chassisSysIface->register_property( |
| "RequestedPowerTransition", |
| std::string("xyz.openbmc_project.State.Chassis.Transition.On"), |
| [](const std::string& requested, std::string& resp) { |
| if (requested == |
| "xyz.openbmc_project.State.Chassis.Transition.PowerCycle") |
| { |
| systemReset(); |
| addRestartCause(RestartCause::command); |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unrecognized chassis system state transition request."); |
| throw std::invalid_argument("Unrecognized Transition Request"); |
| return 0; |
| } |
| resp = requested; |
| return 1; |
| }); |
| chassisSysIface->register_property( |
| "CurrentPowerState", std::string(getChassisState(powerState))); |
| chassisSysIface->register_property("LastStateChangeTime", |
| getCurrentTimeMs()); |
| |
| chassisSysIface->initialize(); |
| #endif |
| |
| // Buttons Service |
| sdbusplus::asio::object_server buttonsServer = |
| sdbusplus::asio::object_server(conn); |
| |
| // Power Button Interface |
| powerButtonIface = buttonsServer.add_interface( |
| "/xyz/openbmc_project/chassis/buttons/power", |
| "xyz.openbmc_project.Chassis.Buttons"); |
| |
| powerButtonIface->register_property( |
| "ButtonMasked", false, [](const bool requested, bool& current) { |
| if (requested) |
| { |
| if (powerButtonMask) |
| { |
| return 1; |
| } |
| if (!setGPIOOutput(powerOutName, 1, powerButtonMask)) |
| { |
| throw std::runtime_error("Failed to request GPIO"); |
| return 0; |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Power Button Masked."); |
| } |
| else |
| { |
| if (!powerButtonMask) |
| { |
| return 1; |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Power Button Un-masked"); |
| powerButtonMask.reset(); |
| } |
| // Update the mask setting |
| current = requested; |
| return 1; |
| }); |
| |
| // Check power button state |
| bool powerButtonPressed = powerButtonLine.get_value() == 0; |
| powerButtonIface->register_property("ButtonPressed", powerButtonPressed); |
| |
| powerButtonIface->initialize(); |
| |
| // Reset Button Interface |
| if (!resetButtonName.empty()) |
| { |
| resetButtonIface = buttonsServer.add_interface( |
| "/xyz/openbmc_project/chassis/buttons/reset", |
| "xyz.openbmc_project.Chassis.Buttons"); |
| |
| resetButtonIface->register_property( |
| "ButtonMasked", false, [](const bool requested, bool& current) { |
| if (requested) |
| { |
| if (resetButtonMask) |
| { |
| return 1; |
| } |
| if (!setGPIOOutput(resetOutName, 1, resetButtonMask)) |
| { |
| throw std::runtime_error("Failed to request GPIO"); |
| return 0; |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Reset Button Masked."); |
| } |
| else |
| { |
| if (!resetButtonMask) |
| { |
| return 1; |
| } |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Reset Button Un-masked"); |
| resetButtonMask.reset(); |
| } |
| // Update the mask setting |
| current = requested; |
| return 1; |
| }); |
| |
| // Check reset button state |
| bool resetButtonPressed = resetButtonLine.get_value() == 0; |
| resetButtonIface->register_property("ButtonPressed", |
| resetButtonPressed); |
| |
| resetButtonIface->initialize(); |
| } |
| |
| if (nmiButtonLine) |
| { |
| // NMI Button Interface |
| nmiButtonIface = buttonsServer.add_interface( |
| "/xyz/openbmc_project/chassis/buttons/nmi", |
| "xyz.openbmc_project.Chassis.Buttons"); |
| |
| nmiButtonIface->register_property( |
| "ButtonMasked", false, [](const bool requested, bool& current) { |
| if (nmiButtonMasked == requested) |
| { |
| // NMI button mask is already set as requested, so no change |
| return 1; |
| } |
| if (requested) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "NMI Button Masked."); |
| nmiButtonMasked = true; |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "NMI Button Un-masked."); |
| nmiButtonMasked = false; |
| } |
| // Update the mask setting |
| current = nmiButtonMasked; |
| return 1; |
| }); |
| |
| // Check NMI button state |
| bool nmiButtonPressed = nmiButtonLine.get_value() == 0; |
| nmiButtonIface->register_property("ButtonPressed", nmiButtonPressed); |
| |
| nmiButtonIface->initialize(); |
| } |
| |
| if (nmiOutLine) |
| { |
| // NMI out Service |
| sdbusplus::asio::object_server nmiOutServer = |
| sdbusplus::asio::object_server(conn); |
| |
| // NMI out Interface |
| nmiOutIface = |
| nmiOutServer.add_interface("/xyz/openbmc_project/control/host0/nmi", |
| "xyz.openbmc_project.Control.Host.NMI"); |
| nmiOutIface->register_method("NMI", nmiReset); |
| nmiOutIface->initialize(); |
| } |
| |
| if (idButtonLine) |
| { |
| // ID Button Interface |
| idButtonIface = buttonsServer.add_interface( |
| "/xyz/openbmc_project/chassis/buttons/id", |
| "xyz.openbmc_project.Chassis.Buttons"); |
| |
| // Check ID button state |
| bool idButtonPressed = idButtonLine.get_value() == 0; |
| idButtonIface->register_property("ButtonPressed", idButtonPressed); |
| |
| idButtonIface->initialize(); |
| } |
| |
| // OS State Service |
| sdbusplus::asio::object_server osServer = |
| sdbusplus::asio::object_server(conn); |
| |
| // OS State Interface |
| osIface = osServer.add_interface( |
| "/xyz/openbmc_project/state/os", |
| "xyz.openbmc_project.State.OperatingSystem.Status"); |
| |
| // Get the initial OS state based on POST complete |
| // 0: Asserted, OS state is "Standby" (ready to boot) |
| // 1: De-Asserted, OS state is "Inactive" |
| std::string osState = |
| postCompleteLine.get_value() > 0 ? "Inactive" : "Standby"; |
| |
| osIface->register_property("OperatingSystemState", std::string(osState)); |
| |
| osIface->initialize(); |
| |
| // Restart Cause Service |
| sdbusplus::asio::object_server restartCauseServer = |
| sdbusplus::asio::object_server(conn); |
| |
| // Restart Cause Interface |
| restartCauseIface = restartCauseServer.add_interface( |
| "/xyz/openbmc_project/control/host0/restart_cause", |
| "xyz.openbmc_project.Control.Host.RestartCause"); |
| |
| restartCauseIface->register_property( |
| "RestartCause", |
| std::string("xyz.openbmc_project.State.Host.RestartCause.Unknown")); |
| |
| restartCauseIface->register_property( |
| "RequestedRestartCause", |
| std::string("xyz.openbmc_project.State.Host.RestartCause.Unknown"), |
| [](const std::string& requested, std::string& resp) { |
| if (requested == |
| "xyz.openbmc_project.State.Host.RestartCause.WatchdogTimer") |
| { |
| addRestartCause(RestartCause::watchdog); |
| } |
| else |
| { |
| throw std::invalid_argument( |
| "Unrecognized RestartCause Request"); |
| return 0; |
| } |
| |
| std::string logMsg = "RestartCause requested: " + requested; |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| logMsg.c_str()); |
| resp = requested; |
| return 1; |
| }); |
| |
| restartCauseIface->initialize(); |
| |
| currentHostStateMonitor(); |
| |
| io.run(); |
| |
| return 0; |
| } |