Reinvent x86 power control application
The previous application that was posted here had a TON of timing
issues that made it basically unworkable, and was missing several
features (like power button override, VR timers, ect) that weren't
really possible in the old scheme.
This commit shows a reimagining of power control on the AST2500, and
seems to work much more reliably in testing across several platforms.
The key differentiators here are:
1. It gets rid of the target files. Despite _many_ attempts to make the
target file approach reliable across 1000/10000 reboot cycle testings,
it was clear that the timing differences in the activation times caused
too many hard to fix race conditions. To this end, the power state
machine has been moved into c++, where we can be very explicit about our
IO, and event timings.
2. It implements several features that were not present in the old
implementation, like soft power cycle. These were required to implement
the full Redfish ComputerSystem schema properly.
3. It implements proper handling when collisions occur. For example
when two power off requests come in at the same time. Because of #1 we
can now service both of these requests "correctly" and remove the
possibility of desyncronizing the state machine from the host state.
A majority of this work was accomplished by Jason Bills. The history is
available here, which can be pushed if needed, but I don't beleive it's
wanted.
https://github.com/Intel-BMC/intel-chassis-control/commits/master
Signed-off-by: Ed Tanous <ed.tanous@intel.com>
Change-Id: I2eb7fb1dbcab3d374df9d2e8c62407f0277e2583
diff --git a/power-control-x86/CMakeLists.txt b/power-control-x86/CMakeLists.txt
new file mode 100644
index 0000000..a2426d8
--- /dev/null
+++ b/power-control-x86/CMakeLists.txt
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 2.8.10 FATAL_ERROR)
+project(power-control CXX)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY)
+add_definitions(-DBOOST_SYSTEM_NO_DEPRECATED)
+add_definitions(-DBOOST_ALL_NO_LIB)
+add_definitions(-DBOOST_NO_RTTI)
+add_definitions(-DBOOST_NO_TYPEID)
+add_definitions(-DBOOST_ASIO_DISABLE_THREADS)
+
+set(SRC_FILES src/power_control.cpp)
+
+add_executable(${PROJECT_NAME} ${SRC_FILES})
+target_link_libraries(${PROJECT_NAME} -lstdc++fs)
+target_link_libraries(${PROJECT_NAME} chassisi2c)
+target_link_libraries(${PROJECT_NAME} i2c)
+target_link_libraries(${PROJECT_NAME} gpiodcxx)
+target_link_libraries(${PROJECT_NAME} systemd)
+target_link_libraries(${PROJECT_NAME} sdbusplus)
+
+install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-rtti")
+
+set(
+ SERVICE_FILES
+ ${PROJECT_SOURCE_DIR}/service_files/xyz.openbmc_project.Chassis.Control.Power.service
+ )
+install(FILES ${SERVICE_FILES} DESTINATION /lib/systemd/system/)
diff --git a/power-control-x86/service_files/xyz.openbmc_project.Chassis.Control.Power.service b/power-control-x86/service_files/xyz.openbmc_project.Chassis.Control.Power.service
new file mode 100644
index 0000000..a80235e
--- /dev/null
+++ b/power-control-x86/service_files/xyz.openbmc_project.Chassis.Control.Power.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Intel Power Control
+
+[Service]
+Restart=always
+RestartSec=3
+ExecStart=/usr/bin/power-control
+Type=dbus
+BusName=xyz.openbmc_project.State.Host
+
+[Install]
+WantedBy=sysinit.target
+
diff --git a/power-control-x86/src/power_control.cpp b/power-control-x86/src/power_control.cpp
new file mode 100644
index 0000000..dce3440
--- /dev/null
+++ b/power-control-x86/src/power_control.cpp
@@ -0,0 +1,2145 @@
+/*
+// 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/posix/stream_descriptor.hpp>
+#include <boost/container/flat_map.hpp>
+#include <filesystem>
+#include <fstream>
+#include <gpiod.hpp>
+#include <iostream>
+#include <sdbusplus/asio/object_server.hpp>
+#include <string_view>
+
+namespace power_control
+{
+static boost::asio::io_service io;
+std::shared_ptr<sdbusplus::asio::connection> conn;
+static std::shared_ptr<sdbusplus::asio::dbus_interface> hostIface;
+static std::shared_ptr<sdbusplus::asio::dbus_interface> chassisIface;
+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 gpiod::line powerButtonMask;
+static gpiod::line resetButtonMask;
+static bool nmiButtonMasked = false;
+static bool resetInProgress = false;
+
+const static constexpr int powerPulseTimeMs = 200;
+const static constexpr int forceOffPulseTimeMs = 15000;
+const static constexpr int resetPulseTimeMs = 500;
+const static constexpr int powerCycleTimeMs = 1000;
+const static constexpr int sioPowerGoodWatchdogTimeMs = 1000;
+const static constexpr int psPowerOKWatchdogTimeMs = 8000;
+const static constexpr int gracefulPowerOffTimeMs = 60000;
+const static constexpr int buttonMaskTimeMs = 60000;
+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 constexpr const char* nmiOutName = "NMI_OUT";
+
+// 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 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);
+
+// 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::cerr << "Beep with priority: " << (unsigned)beepPriority << "\n";
+
+ conn->async_method_call(
+ [](boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "beep returned error with "
+ "async_method_call (ec = "
+ << ec << ")\n";
+ return;
+ }
+ },
+ "xyz.openbmc_project.BeepCode", "/xyz/openbmc_project/BeepCode",
+ "xyz.openbmc_project.BeepCode", "Beep", uint8_t(beepPriority));
+}
+
+enum class PowerState
+{
+ on,
+ waitForPSPowerOK,
+ waitForSIOPowerGood,
+ failedTransitionToOn,
+ off,
+ transitionToOff,
+ gracefulTransitionToOff,
+ cycleOff,
+ transitionToCycleOff,
+ gracefulTransitionToCycleOff,
+};
+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::failedTransitionToOn:
+ return "Failed Transition to On";
+ 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;
+ default:
+ return "unknown state: " + std::to_string(static_cast<int>(state));
+ break;
+ }
+}
+static void logStateTransition(const PowerState state)
+{
+ std::cerr << "Moving to \"" << getPowerStateName(state) << "\" state.\n";
+}
+
+enum class Event
+{
+ psPowerOKAssert,
+ psPowerOKDeAssert,
+ sioPowerGoodAssert,
+ sioPowerGoodDeAssert,
+ sioS5Assert,
+ sioS5DeAssert,
+ powerButtonPressed,
+ powerCycleTimerExpired,
+ psPowerOKWatchdogTimerExpired,
+ sioPowerGoodWatchdogTimerExpired,
+ gracefulPowerOffTimerExpired,
+ powerOnRequest,
+ powerOffRequest,
+ powerCycleRequest,
+ resetRequest,
+ gracefulPowerOffRequest,
+ gracefulPowerCycleRequest,
+};
+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::powerButtonPressed:
+ return "power 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;
+ default:
+ return "unknown event: " + std::to_string(static_cast<int>(event));
+ break;
+ }
+}
+static void logEvent(const std::string_view stateHandler, const Event event)
+{
+ std::cerr << stateHandler << ": " << getEventName(event)
+ << " event received.\n";
+}
+
+// Power state handlers
+static void powerStateOn(const Event event);
+static void powerStateWaitForPSPowerOK(const Event event);
+static void powerStateWaitForSIOPowerGood(const Event event);
+static void powerStateFailedTransitionToOn(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 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::failedTransitionToOn:
+ return powerStateFailedTransitionToOn;
+ 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;
+ default:
+ return std::function<void(const Event)>{};
+ break;
+ }
+};
+
+static void sendPowerControlEvent(const Event event)
+{
+ std::function<void(const Event)> handler = getPowerStateHandler(powerState);
+ if (handler == nullptr)
+ {
+ std::cerr << "Failed to find handler for power state: "
+ << static_cast<int>(powerState) << "\n";
+ 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::transitionToOff:
+ case PowerState::gracefulTransitionToOff:
+ case PowerState::transitionToCycleOff:
+ case PowerState::gracefulTransitionToCycleOff:
+ return "xyz.openbmc_project.State.Host.HostState.Running";
+ break;
+ case PowerState::waitForPSPowerOK:
+ case PowerState::waitForSIOPowerGood:
+ case PowerState::failedTransitionToOn:
+ case PowerState::off:
+ case PowerState::cycleOff:
+ 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:
+ return "xyz.openbmc_project.State.Chassis.PowerState.On";
+ break;
+ case PowerState::waitForPSPowerOK:
+ case PowerState::waitForSIOPowerGood:
+ case PowerState::failedTransitionToOn:
+ 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::cerr << "Power-state save async_wait failed: "
+ << ec.message() << "\n";
+ }
+ 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,
+ powerPolicyOn,
+ powerPolicyRestore,
+ softReset,
+};
+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::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 setRestartCause(const RestartCause cause)
+{
+ conn->async_method_call(
+ [](boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "failed to set RestartCause\n";
+ }
+ },
+ "xyz.openbmc_project.Settings",
+ "/xyz/openbmc_project/control/host0/restart_cause",
+ "org.freedesktop.DBus.Properties", "Set",
+ "xyz.openbmc_project.Common.RestartCause", "RestartCause",
+ std::variant<std::string>(getRestartCause(cause)));
+}
+
+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::cerr << "failed to create " << powerControlDir << ": "
+ << ec.message() << "\n";
+ 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())
+ {
+ std::cerr << "Failed to open power state file\n";
+ 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::cerr << "Power restore delay expired, invoking " << policy << "\n";
+ if (policy ==
+ "xyz.openbmc_project.Control.Power.RestorePolicy.Policy.AlwaysOn")
+ {
+ sendPowerControlEvent(Event::powerOnRequest);
+ setRestartCause(RestartCause::powerPolicyOn);
+ }
+ else if (policy == "xyz.openbmc_project.Control.Power.RestorePolicy."
+ "Policy.Restore")
+ {
+ if (wasPowerDropped())
+ {
+ std::cerr << "Power was dropped, restoring Host On state\n";
+ sendPowerControlEvent(Event::powerOnRequest);
+ setRestartCause(RestartCause::powerPolicyRestore);
+ }
+ else
+ {
+ std::cerr << "No power drop, restoring Host Off state\n";
+ }
+ }
+}
+
+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::cerr << "Power restore delay of " << delay << " seconds started\n";
+ 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::cerr << "power restore policy async_wait failed: "
+ << ec.message() << "\n";
+ }
+ 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)
+ {
+ std::cerr
+ << "Unable to read power restore policy value\n";
+ 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)
+ {
+ std::cerr << "Unable to read power restore policy value\n";
+ 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()
+{
+ std::cerr << "Power restore policy started\n";
+ 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)
+ {
+ std::cerr << "Unable to read power restore delay value\n";
+ 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)
+ {
+ std::cerr << "Unable to read power restore delay value\n";
+ 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)
+ {
+ std::cerr << "Unable to read AC Boot status\n";
+ 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)
+ {
+ std::cerr << "Unable to read AC Boot status\n";
+ 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::cerr << "Failed to find the " << name << " line\n";
+ return false;
+ }
+
+ try
+ {
+ gpioLine.request(
+ {"power-control", gpiod::line_request::EVENT_BOTH_EDGES});
+ }
+ catch (std::exception&)
+ {
+ std::cerr << "Failed to request events for " << name << "\n";
+ return false;
+ }
+
+ int gpioLineFd = gpioLine.event_get_fd();
+ if (gpioLineFd < 0)
+ {
+ std::cerr << "Failed to get " << name << " fd\n";
+ 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::cerr << name << " fd handler error: " << ec.message()
+ << "\n";
+ // 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::cerr << "Failed to find the " << name << " line.\n";
+ return false;
+ }
+
+ // Request GPIO output to specified value
+ try
+ {
+ gpioLine.request({__FUNCTION__, gpiod::line_request::DIRECTION_OUTPUT},
+ value);
+ }
+ catch (std::exception&)
+ {
+ std::cerr << "Failed to request " << name << " output\n";
+ return false;
+ }
+
+ std::cerr << name << " set to " << std::to_string(value) << "\n";
+ 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::cerr << name << " set to " << std::to_string(value) << "\n";
+ 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::cerr << name << " released\n";
+ if (ec)
+ {
+ // operation_aborted is expected if timer is canceled before
+ // completion.
+ if (ec != boost::asio::error::operation_aborted)
+ {
+ std::cerr << name << " async_wait failed: " + ec.message()
+ << "\n";
+ }
+ }
+ });
+ 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_OUT")
+ {
+ return setMaskedGPIOOutputForMs(powerButtonMask, name, value,
+ durationMs);
+ }
+ if (resetButtonMask && name == "RESET_OUT")
+ {
+ 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, name](const boost::system::error_code ec) {
+ std::cerr << name << " released\n";
+ if (ec)
+ {
+ // operation_aborted is expected if timer is canceled before
+ // completion.
+ if (ec != boost::asio::error::operation_aborted)
+ {
+ std::cerr << name << " async_wait failed: " << ec.message()
+ << "\n";
+ }
+ }
+ });
+ return 0;
+}
+
+static void powerOn()
+{
+ setGPIOOutputForMs("POWER_OUT", 0, powerPulseTimeMs);
+}
+
+static void gracefulPowerOff()
+{
+ setGPIOOutputForMs("POWER_OUT", 0, powerPulseTimeMs);
+}
+
+static void forcePowerOff()
+{
+ if (setGPIOOutputForMs("POWER_OUT", 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::cerr << "Force power off async_wait failed: "
+ << ec.message() << "\n";
+ }
+ return;
+ }
+ std::cerr << "PCH Power-button override failed. Issuing Unconditional "
+ "Powerdown SMBus command.\n";
+ 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)
+ {
+ std::cerr << "Unconditional Powerdown command failed! Not sure "
+ "what to do now.\n";
+ }
+ });
+}
+
+static void reset()
+{
+ setGPIOOutputForMs("RESET_OUT", 0, resetPulseTimeMs);
+}
+
+static void gracefulPowerOffTimerStart()
+{
+ std::cerr << "Graceful power-off timer started\n";
+ gracefulPowerOffTimer.expires_after(
+ std::chrono::milliseconds(gracefulPowerOffTimeMs));
+ 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::cerr << "Graceful power-off async_wait failed: "
+ << ec.message() << "\n";
+ }
+ std::cerr << "Graceful power-off timer canceled\n";
+ return;
+ }
+ std::cerr << "Graceful power-off timer completed\n";
+ sendPowerControlEvent(Event::gracefulPowerOffTimerExpired);
+ });
+}
+
+static void powerCycleTimerStart()
+{
+ std::cerr << "Power-cycle timer started\n";
+ 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::cerr << "Power-cycle async_wait failed: " << ec.message()
+ << "\n";
+ }
+ std::cerr << "Power-cycle timer canceled\n";
+ return;
+ }
+ std::cerr << "Power-cycle timer completed\n";
+ sendPowerControlEvent(Event::powerCycleTimerExpired);
+ });
+}
+
+static void psPowerOKWatchdogTimerStart()
+{
+ std::cerr << "power supply power OK watchdog timer started\n";
+ 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::cerr
+ << "power supply power OK watchdog async_wait failed: "
+ << ec.message() << "\n";
+ }
+ std::cerr << "power supply power OK watchdog timer canceled\n";
+ return;
+ }
+ std::cerr << "power supply power OK watchdog timer expired\n";
+ sendPowerControlEvent(Event::psPowerOKWatchdogTimerExpired);
+ });
+}
+
+static void pohCounterTimerStart()
+{
+ std::cerr << "POH timer started\n";
+ // 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::cerr << "POH timer async_wait failed: " << ec.message()
+ << "\n";
+ }
+ std::cerr << "POH timer canceled\n";
+ 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)
+ {
+ std::cerr << "error to get poh counter\n";
+ return;
+ }
+ const uint32_t* pohCounter =
+ std::get_if<uint32_t>(&pohCounterProperty);
+ if (pohCounter == nullptr)
+ {
+ std::cerr << "unable to read poh counter\n";
+ return;
+ }
+
+ conn->async_method_call(
+ [](boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "failed to set poh counter\n";
+ }
+ },
+ "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()
+{
+ static auto match = sdbusplus::bus::match::match(
+ *conn,
+ "type='signal',member='PropertiesChanged', "
+ "interface='org.freedesktop.DBus.Properties', "
+ "arg0namespace='xyz.openbmc_project.State.Host'",
+ [](sdbusplus::message::message& message) {
+ std::string intfName;
+ std::map<std::string, std::variant<std::string>> properties;
+
+ message.read(intfName, properties);
+
+ std::variant<std::string> currentHostState;
+
+ try
+ {
+ currentHostState = properties.at("CurrentHostState");
+ }
+ catch (const std::out_of_range& e)
+ {
+ std::cerr << "Error in finding CurrentHostState property\n";
+
+ return;
+ }
+
+ if (std::get<std::string>(currentHostState) ==
+ "xyz.openbmc_project.State.Host.HostState.Running")
+ {
+ pohCounterTimerStart();
+ }
+ else
+ {
+ pohCounterTimer.cancel();
+ }
+ });
+}
+
+static void sioPowerGoodWatchdogTimerStart()
+{
+ std::cerr << "SIO power good watchdog timer started\n";
+ 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::cerr << "SIO power good watchdog async_wait failed: "
+ << ec.message() << "\n";
+ }
+ std::cerr << "SIO power good watchdog timer canceled\n";
+ return;
+ }
+ std::cerr << "SIO power good watchdog timer completed\n";
+ 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);
+ setRestartCause(RestartCause::softReset);
+ 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:
+ std::cerr << "No action taken.\n";
+ 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();
+ sioPowerGoodWatchdogTimerStart();
+ setPowerState(PowerState::waitForSIOPowerGood);
+ break;
+ case Event::psPowerOKWatchdogTimerExpired:
+ setPowerState(PowerState::failedTransitionToOn);
+ break;
+ default:
+ std::cerr << "No action taken.\n";
+ 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::failedTransitionToOn);
+ forcePowerOff();
+ break;
+ default:
+ std::cerr << "No action taken.\n";
+ break;
+ }
+}
+
+static void powerStateFailedTransitionToOn(const Event event)
+{
+ logEvent(__FUNCTION__, event);
+ switch (event)
+ {
+ case Event::psPowerOKAssert:
+ // We're in a failure state, so don't allow the system to turn on
+ // without a user request
+ forcePowerOff();
+ break;
+ case Event::psPowerOKDeAssert:
+ // Cancel any GPIO assertions held during the transition
+ gpioAssertTimer.cancel();
+ break;
+ case Event::powerButtonPressed:
+ psPowerOKWatchdogTimerStart();
+ setPowerState(PowerState::waitForPSPowerOK);
+ break;
+ case Event::powerOnRequest:
+ psPowerOKWatchdogTimerStart();
+ setPowerState(PowerState::waitForPSPowerOK);
+ powerOn();
+ break;
+ default:
+ std::cerr << "No action taken.\n";
+ break;
+ }
+}
+
+static void powerStateOff(const Event event)
+{
+ logEvent(__FUNCTION__, event);
+ switch (event)
+ {
+ case Event::psPowerOKAssert:
+ setPowerState(PowerState::waitForSIOPowerGood);
+ break;
+ case Event::sioS5DeAssert:
+ setPowerState(PowerState::waitForPSPowerOK);
+ break;
+ case Event::powerButtonPressed:
+ psPowerOKWatchdogTimerStart();
+ setPowerState(PowerState::waitForPSPowerOK);
+ break;
+ case Event::powerOnRequest:
+ psPowerOKWatchdogTimerStart();
+ setPowerState(PowerState::waitForPSPowerOK);
+ powerOn();
+ break;
+ default:
+ std::cerr << "No action taken.\n";
+ 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:
+ std::cerr << "No action taken.\n";
+ 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;
+ default:
+ std::cerr << "No action taken.\n";
+ break;
+ }
+}
+
+static void powerStateCycleOff(const Event event)
+{
+ logEvent(__FUNCTION__, event);
+ switch (event)
+ {
+ case Event::powerCycleTimerExpired:
+ psPowerOKWatchdogTimerStart();
+ setPowerState(PowerState::waitForPSPowerOK);
+ powerOn();
+ break;
+ default:
+ std::cerr << "No action taken.\n";
+ 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:
+ std::cerr << "No action taken.\n";
+ 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;
+ default:
+ std::cerr << "No action taken.\n";
+ 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::cerr << "power supply power OK handler error: "
+ << ec.message() << "\n";
+ 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::cerr << "SIO power good handler error: " << ec.message()
+ << "\n";
+ return;
+ }
+ sioPowerGoodHandler();
+ });
+}
+
+static void sioOnControlHandler()
+{
+ gpiod::line_event gpioLineEvent = sioOnControlLine.event_read();
+
+ bool sioOnControl =
+ gpioLineEvent.event_type == gpiod::line_event::RISING_EDGE;
+ std::cerr << "SIO_ONCONTROL value changed: " << sioOnControl << "\n";
+ sioOnControlEvent.async_wait(
+ boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "SIO ONCONTROL handler error: " << ec.message()
+ << "\n";
+ 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::cerr << "SIO S5 handler error: "
+ << ec.message() << "\n";
+ 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);
+ setRestartCause(RestartCause::powerButton);
+ }
+ else
+ {
+ std::cerr << "power button press masked\n";
+ }
+ }
+ 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::cerr << "power button handler error: " << ec.message()
+ << "\n";
+ 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)
+ {
+ resetInProgress = true;
+ setRestartCause(RestartCause::resetButton);
+ }
+ else
+ {
+ std::cerr << "reset button press masked\n";
+ }
+ }
+ 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::cerr << "reset button handler error: " << ec.message()
+ << "\n";
+ return;
+ }
+ resetButtonHandler();
+ });
+}
+
+static void nmiSetEnablePorperty(bool value)
+{
+ conn->async_method_call(
+ [](boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "failed to set NMI source\n";
+ }
+ },
+ "xyz.openbmc_project.Settings", "/com/intel/control/NMISource",
+ "org.freedesktop.DBus.Properties", "Set", "com.intel.Control.NMISource",
+ "Enabled", std::variant<bool>{value});
+}
+
+static void nmiReset(void)
+{
+ static constexpr const uint8_t value = 1;
+ const static constexpr int nmiOutPulseTimeMs = 200;
+
+ std::cerr << "NMI out action \n";
+ nmiOutLine.set_value(value);
+ std::cerr << nmiOutName << " set to " << std::to_string(value) << "\n";
+ 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::cerr << nmiOutName << " released\n";
+ if (ec)
+ {
+ // operation_aborted is expected if timer is canceled before
+ // completion.
+ if (ec != boost::asio::error::operation_aborted)
+ {
+ std::cerr << nmiOutName << " async_wait failed: " + ec.message()
+ << "\n";
+ }
+ }
+ });
+ // log to redfish
+ nmiDiagIntLog();
+ std::cerr << "NMI out action completed\n";
+ // reset Enable Property
+ nmiSetEnablePorperty(false);
+}
+
+static void nmiSourcePropertyMonitor(void)
+{
+ std::cerr << " NMI Source Property Monitor \n";
+
+ 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='com.intel.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::cerr
+ << " NMI Enabled propertiesChanged value: " << value
+ << "\n";
+ nmiEnabled = value;
+ if (nmiEnabled)
+ {
+ nmiReset();
+ }
+ }
+ }
+ catch (std::exception& e)
+ {
+ std::cerr << "Unable to read NMI source\n";
+ return;
+ }
+ });
+}
+
+static void setNmiSource()
+{
+ conn->async_method_call(
+ [](boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "failed to set NMI source\n";
+ }
+ },
+ "xyz.openbmc_project.Settings", "/com/intel/control/NMISource",
+ "org.freedesktop.DBus.Properties", "Set", "com.intel.Control.NMISource",
+ "BMCSource",
+ std::variant<std::string>{
+ "com.intel.Control.NMISource.BMCSourceSignal.FpBtn"});
+ // set Enable Property
+ nmiSetEnablePorperty(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)
+ {
+ std::cerr << "NMI button press masked\n";
+ }
+ 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::cerr << "NMI button handler error: "
+ << ec.message() << "\n";
+ 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::cerr << "ID button handler error: "
+ << ec.message() << "\n";
+ return;
+ }
+ idButtonHandler();
+ });
+}
+
+static void postCompleteHandler()
+{
+ gpiod::line_event gpioLineEvent = postCompleteLine.event_read();
+
+ bool postComplete =
+ gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE;
+ std::cerr << "POST complete value changed: " << postComplete << "\n";
+ if (postComplete)
+ {
+ osIface->set_property("OperatingSystemState", std::string("Standby"));
+ resetInProgress = false;
+ }
+ else
+ {
+ osIface->set_property("OperatingSystemState", std::string("Inactive"));
+ // Set the restart cause if POST complete de-asserted by host software
+ if (powerState == PowerState::on && !resetInProgress)
+ {
+ resetInProgress = true;
+ setRestartCause(RestartCause::softReset);
+ }
+ }
+ postCompleteEvent.async_wait(
+ boost::asio::posix::stream_descriptor::wait_read,
+ [](const boost::system::error_code ec) {
+ if (ec)
+ {
+ std::cerr << "POST complete handler error: " << ec.message()
+ << "\n";
+ return;
+ }
+ postCompleteHandler();
+ });
+}
+} // namespace power_control
+
+int main(int argc, char* argv[])
+{
+ std::cerr << "Start Chassis power control service...\n";
+ power_control::conn =
+ std::make_shared<sdbusplus::asio::connection>(power_control::io);
+
+ // Request all the dbus names
+ power_control::conn->request_name("xyz.openbmc_project.State.Host");
+ power_control::conn->request_name("xyz.openbmc_project.State.Chassis");
+ power_control::conn->request_name(
+ "xyz.openbmc_project.State.OperatingSystem");
+ power_control::conn->request_name("xyz.openbmc_project.Chassis.Buttons");
+
+ // Request PS_PWROK GPIO events
+ if (!power_control::requestGPIOEvents(
+ "PS_PWROK", power_control::psPowerOKHandler,
+ power_control::psPowerOKLine, power_control::psPowerOKEvent))
+ {
+ return -1;
+ }
+
+ // Request SIO_POWER_GOOD GPIO events
+ if (!power_control::requestGPIOEvents(
+ "SIO_POWER_GOOD", power_control::sioPowerGoodHandler,
+ power_control::sioPowerGoodLine, power_control::sioPowerGoodEvent))
+ {
+ return -1;
+ }
+
+ // Request SIO_ONCONTROL GPIO events
+ if (!power_control::requestGPIOEvents(
+ "SIO_ONCONTROL", power_control::sioOnControlHandler,
+ power_control::sioOnControlLine, power_control::sioOnControlEvent))
+ {
+ return -1;
+ }
+
+ // Request SIO_S5 GPIO events
+ if (!power_control::requestGPIOEvents("SIO_S5", power_control::sioS5Handler,
+ power_control::sioS5Line,
+ power_control::sioS5Event))
+ {
+ return -1;
+ }
+
+ // Request POWER_BUTTON GPIO events
+ if (!power_control::requestGPIOEvents(
+ "POWER_BUTTON", power_control::powerButtonHandler,
+ power_control::powerButtonLine, power_control::powerButtonEvent))
+ {
+ return -1;
+ }
+
+ // Request RESET_BUTTON GPIO events
+ if (!power_control::requestGPIOEvents(
+ "RESET_BUTTON", power_control::resetButtonHandler,
+ power_control::resetButtonLine, power_control::resetButtonEvent))
+ {
+ return -1;
+ }
+
+ // Request NMI_BUTTON GPIO events
+ if (!power_control::requestGPIOEvents(
+ "NMI_BUTTON", power_control::nmiButtonHandler,
+ power_control::nmiButtonLine, power_control::nmiButtonEvent))
+ {
+ return -1;
+ }
+
+ // Request ID_BUTTON GPIO events
+ if (!power_control::requestGPIOEvents(
+ "ID_BUTTON", power_control::idButtonHandler,
+ power_control::idButtonLine, power_control::idButtonEvent))
+ {
+ return -1;
+ }
+
+ // Request POST_COMPLETE GPIO events
+ if (!power_control::requestGPIOEvents(
+ "POST_COMPLETE", power_control::postCompleteHandler,
+ power_control::postCompleteLine, power_control::postCompleteEvent))
+ {
+ return -1;
+ }
+
+ // initialize NMI_OUT GPIO.
+ if (!power_control::setGPIOOutput(power_control::nmiOutName, 0,
+ power_control::nmiOutLine))
+ {
+ return -1;
+ }
+
+ // Initialize the power state
+ power_control::powerState = power_control::PowerState::off;
+ // Check power good
+ if (power_control::psPowerOKLine.get_value() > 0)
+ {
+ power_control::powerState = power_control::PowerState::on;
+ }
+
+ // Initialize the power state storage
+ if (power_control::initializePowerStateStorage() < 0)
+ {
+ return -1;
+ }
+
+ // Check if we need to start the Power Restore policy
+ power_control::powerRestorePolicyCheck();
+
+ power_control::nmiSourcePropertyMonitor();
+
+ std::cerr << "Initializing power state. ";
+ power_control::logStateTransition(power_control::powerState);
+
+ // Power Control Service
+ sdbusplus::asio::object_server hostServer =
+ sdbusplus::asio::object_server(power_control::conn);
+
+ // Power Control Interface
+ power_control::hostIface = hostServer.add_interface(
+ "/xyz/openbmc_project/state/host0", "xyz.openbmc_project.State.Host");
+
+ power_control::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(
+ power_control::Event::gracefulPowerOffRequest);
+ }
+ else if (requested ==
+ "xyz.openbmc_project.State.Host.Transition.On")
+ {
+ sendPowerControlEvent(power_control::Event::powerOnRequest);
+ setRestartCause(power_control::RestartCause::command);
+ }
+ else if (requested ==
+ "xyz.openbmc_project.State.Host.Transition.Reboot")
+ {
+ sendPowerControlEvent(
+ power_control::Event::gracefulPowerCycleRequest);
+ setRestartCause(power_control::RestartCause::command);
+ }
+ else
+ {
+ std::cerr << "Unrecognized host state transition request.\n";
+ throw std::invalid_argument("Unrecognized Transition Request");
+ return 0;
+ }
+ resp = requested;
+ return 1;
+ });
+ power_control::hostIface->register_property(
+ "CurrentHostState",
+ std::string(power_control::getHostState(power_control::powerState)));
+
+ power_control::currentHostStateMonitor();
+
+ power_control::hostIface->initialize();
+
+ // Chassis Control Service
+ sdbusplus::asio::object_server chassisServer =
+ sdbusplus::asio::object_server(power_control::conn);
+
+ // Chassis Control Interface
+ power_control::chassisIface =
+ chassisServer.add_interface("/xyz/openbmc_project/state/chassis0",
+ "xyz.openbmc_project.State.Chassis");
+
+ power_control::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(power_control::Event::powerOffRequest);
+ }
+ else if (requested ==
+ "xyz.openbmc_project.State.Chassis.Transition.On")
+ {
+ sendPowerControlEvent(power_control::Event::powerOnRequest);
+ setRestartCause(power_control::RestartCause::command);
+ }
+ else if (requested ==
+ "xyz.openbmc_project.State.Chassis.Transition.PowerCycle")
+ {
+ sendPowerControlEvent(power_control::Event::powerCycleRequest);
+ setRestartCause(power_control::RestartCause::command);
+ }
+ else if (requested ==
+ "xyz.openbmc_project.State.Chassis.Transition.Reset")
+ {
+ setRestartCause(power_control::RestartCause::command);
+ sendPowerControlEvent(power_control::Event::resetRequest);
+ }
+ else
+ {
+ std::cerr << "Unrecognized chassis state transition request.\n";
+ throw std::invalid_argument("Unrecognized Transition Request");
+ return 0;
+ }
+ resp = requested;
+ return 1;
+ });
+ power_control::chassisIface->register_property(
+ "CurrentPowerState",
+ std::string(power_control::getChassisState(power_control::powerState)));
+ power_control::chassisIface->register_property(
+ "LastStateChangeTime", power_control::getCurrentTimeMs());
+
+ power_control::chassisIface->initialize();
+
+ // Buttons Service
+ sdbusplus::asio::object_server buttonsServer =
+ sdbusplus::asio::object_server(power_control::conn);
+
+ // Power Button Interface
+ power_control::powerButtonIface = buttonsServer.add_interface(
+ "/xyz/openbmc_project/chassis/buttons/power",
+ "xyz.openbmc_project.Chassis.Buttons");
+
+ power_control::powerButtonIface->register_property(
+ "ButtonMasked", false, [](const bool requested, bool& current) {
+ if (requested)
+ {
+ if (power_control::powerButtonMask)
+ {
+ return 1;
+ }
+ if (!power_control::setGPIOOutput(
+ "POWER_OUT", 1, power_control::powerButtonMask))
+ {
+ throw std::runtime_error("Failed to request GPIO");
+ return 0;
+ }
+ std::cerr << "Power Button Masked.\n";
+ }
+ else
+ {
+ if (!power_control::powerButtonMask)
+ {
+ return 1;
+ }
+ std::cerr << "Power Button Un-masked\n";
+ power_control::powerButtonMask.reset();
+ }
+ // Update the mask setting
+ current = requested;
+ return 1;
+ });
+
+ // Check power button state
+ bool powerButtonPressed = power_control::powerButtonLine.get_value() == 0;
+ power_control::powerButtonIface->register_property("ButtonPressed",
+ powerButtonPressed);
+
+ power_control::powerButtonIface->initialize();
+
+ // Reset Button Interface
+ power_control::resetButtonIface = buttonsServer.add_interface(
+ "/xyz/openbmc_project/chassis/buttons/reset",
+ "xyz.openbmc_project.Chassis.Buttons");
+
+ power_control::resetButtonIface->register_property(
+ "ButtonMasked", false, [](const bool requested, bool& current) {
+ if (requested)
+ {
+ if (power_control::resetButtonMask)
+ {
+ return 1;
+ }
+ if (!power_control::setGPIOOutput(
+ "RESET_OUT", 1, power_control::resetButtonMask))
+ {
+ throw std::runtime_error("Failed to request GPIO");
+ return 0;
+ }
+ std::cerr << "Reset Button Masked.\n";
+ }
+ else
+ {
+ if (!power_control::resetButtonMask)
+ {
+ return 1;
+ }
+ std::cerr << "Reset Button Un-masked\n";
+ power_control::resetButtonMask.reset();
+ }
+ // Update the mask setting
+ current = requested;
+ return 1;
+ });
+
+ // Check reset button state
+ bool resetButtonPressed = power_control::resetButtonLine.get_value() == 0;
+ power_control::resetButtonIface->register_property("ButtonPressed",
+ resetButtonPressed);
+
+ power_control::resetButtonIface->initialize();
+
+ // NMI Button Interface
+ power_control::nmiButtonIface =
+ buttonsServer.add_interface("/xyz/openbmc_project/chassis/buttons/nmi",
+ "xyz.openbmc_project.Chassis.Buttons");
+
+ power_control::nmiButtonIface->register_property(
+ "ButtonMasked", false, [](const bool requested, bool& current) {
+ if (power_control::nmiButtonMasked == requested)
+ {
+ // NMI button mask is already set as requested, so no change
+ return 1;
+ }
+ if (requested)
+ {
+ std::cerr << "NMI Button Masked.\n";
+ power_control::nmiButtonMasked = true;
+ }
+ else
+ {
+ std::cerr << "NMI Button Un-masked.\n";
+ power_control::nmiButtonMasked = false;
+ }
+ // Update the mask setting
+ current = power_control::nmiButtonMasked;
+ return 1;
+ });
+
+ // Check NMI button state
+ bool nmiButtonPressed = power_control::nmiButtonLine.get_value() == 0;
+ power_control::nmiButtonIface->register_property("ButtonPressed",
+ nmiButtonPressed);
+
+ power_control::nmiButtonIface->initialize();
+
+ // ID Button Interface
+ power_control::idButtonIface =
+ buttonsServer.add_interface("/xyz/openbmc_project/chassis/buttons/id",
+ "xyz.openbmc_project.Chassis.Buttons");
+
+ // Check ID button state
+ bool idButtonPressed = power_control::idButtonLine.get_value() == 0;
+ power_control::idButtonIface->register_property("ButtonPressed",
+ idButtonPressed);
+
+ power_control::idButtonIface->initialize();
+
+ // OS State Service
+ sdbusplus::asio::object_server osServer =
+ sdbusplus::asio::object_server(power_control::conn);
+
+ // OS State Interface
+ power_control::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 = power_control::postCompleteLine.get_value() > 0
+ ? "Inactive"
+ : "Standby";
+
+ power_control::osIface->register_property("OperatingSystemState",
+ std::string(osState));
+
+ power_control::osIface->initialize();
+
+ power_control::io.run();
+
+ return 0;
+}