refactor power restore controller

Currently PowerRestorePolicy handling code uses two undocumented
interfaces:
* `xyz.openbmc_project.Control.Power.RestoreDelay`
* `xyz.openbmc_project.Common.ACBoot`

Power Restore Delay seems to be logical part of
`xyz.openbmc_project.Control.Power.RestorePolicy` interface and has
been moved there.

ACBoot depends on some custom logic that can be found only in Intel-BMC
fork.

This commit reorganize PowerRestorePolicy-related code to be more clear
and flexible, fixes interface for RestoreDelay. Use of ACBoot feature is
now optional and can be compile-time enabled.

Tested: Model power loss event with Off, On and Restore policy, verify,
        that power restored as expected.
	Test On policy with Delay set to 300000000 - ensure, power on
	delayed by 5 minutes.
	Test with ACBoot interface emulated.
Signed-off-by: Andrei Kartashev <a.kartashev@yadro.com>
Change-Id: Id8b42d2085f44418e02a7f52836cc1a6f55f50db
diff --git a/meson.build b/meson.build
index 4507df2..c1cc9b8 100644
--- a/meson.build
+++ b/meson.build
@@ -29,6 +29,9 @@
 if get_option('use-plt-rst').enabled()
   cpp_args += '-DUSE_PLT_RST'
 endif
+if get_option('use-acboot').enabled()
+  cpp_args += '-DUSE_ACBOOT'
+endif
 
 deps = [
   dependency('libgpiodcxx', fallback: ['libgpiod', 'gpiodcxx_dep'], default_options: ['bindings=cxx']),
diff --git a/meson_options.txt b/meson_options.txt
index d4c0309..25639c5 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,4 +1,6 @@
 option('chassis-system-reset', type: 'feature', value : 'disabled',
        description: 'Enable chassis system power reset to allow removing power and restoring back')
 option('use-plt-rst', type: 'feature', value : 'disabled',
-       description: 'Use the PLT_RST eSPI signal to detect warm reset instead of POST Complete')
\ No newline at end of file
+       description: 'Use the PLT_RST eSPI signal to detect warm reset instead of POST Complete')
+option('use-acboot', type: 'feature', value : 'disabled',
+       description: 'Use hardware Reset Reason to control Power Restore. Note: this only works with Intel-BMC')
diff --git a/src/power_control.cpp b/src/power_control.cpp
index b897c3b..05aa45c 100644
--- a/src/power_control.cpp
+++ b/src/power_control.cpp
@@ -37,6 +37,7 @@
 static boost::asio::io_service io;
 std::shared_ptr<sdbusplus::asio::connection> conn;
 PersistentState appState;
+PowerRestoreController powerRestore(io);
 
 static std::string node = "0";
 static const std::string appName = "power-control";
@@ -653,6 +654,7 @@
     restartCauseIface->set_property("RestartCause", cause);
 }
 
+#ifdef USE_ACBOOT
 static void resetACBootProperty()
 {
     if ((causeSet.contains(RestartCause::command)) ||
@@ -672,6 +674,7 @@
             std::variant<std::string>{"False"});
     }
 }
+#endif // USE_ACBOOT
 
 static void setRestartCause()
 {
@@ -854,31 +857,236 @@
     appStateStream << stateData.dump(indentationSize);
 }
 
-static bool wasPowerDropped()
+static constexpr char const* setingsService = "xyz.openbmc_project.Settings";
+static constexpr char const* powerRestorePolicyObject =
+    "/xyz/openbmc_project/control/host0/power_restore_policy";
+static constexpr char const* powerRestorePolicyIface =
+    "xyz.openbmc_project.Control.Power.RestorePolicy";
+#ifdef USE_ACBOOT
+static constexpr char const* powerACBootObject =
+    "/xyz/openbmc_project/control/host0/ac_boot";
+static constexpr char const* powerACBootIface =
+    "xyz.openbmc_project.Common.ACBoot";
+#endif // USE_ACBOOT
+
+namespace match_rules = sdbusplus::bus::match::rules;
+
+static int powerRestoreConfigHandler(sd_bus_message* m, void* context,
+                                     sd_bus_error*)
 {
-    std::string state = appState.get(PersistentState::Params::PowerState);
-    return state == "xyz.openbmc_project.State.Chassis.PowerState.On";
+    if (context == nullptr || m == nullptr)
+    {
+        throw std::runtime_error("Invalid match");
+    }
+    sdbusplus::message::message message(m);
+    PowerRestoreController* powerRestore =
+        static_cast<PowerRestoreController*>(context);
+
+    if (std::string(message.get_member()) == "InterfacesAdded")
+    {
+        sdbusplus::message::object_path path;
+        boost::container::flat_map<std::string, dbusPropertiesList> data;
+
+        message.read(path, data);
+
+        for (auto& [iface, properties] : data)
+        {
+            if ((iface == powerRestorePolicyIface)
+#ifdef USE_ACBOOT
+                || (iface == powerACBootIface)
+#endif // USE_ACBOOT
+            )
+            {
+                powerRestore->setProperties(properties);
+            }
+        }
+    }
+    else if (std::string(message.get_member()) == "PropertiesChanged")
+    {
+        std::string interfaceName;
+        dbusPropertiesList propertiesChanged;
+
+        message.read(interfaceName, propertiesChanged);
+
+        powerRestore->setProperties(propertiesChanged);
+    }
+    return 1;
 }
 
-static void invokePowerRestorePolicy(const std::string& policy)
+void PowerRestoreController::run()
 {
-    // Async events may call this twice, but we only want to run once
-    static bool policyInvoked = false;
+    powerRestorePolicyLog();
+    // this list only needs to be created once
+    if (matches.empty())
+    {
+        matches.emplace_back(
+            *conn,
+            match_rules::interfacesAdded() +
+                match_rules::argNpath(0, powerRestorePolicyObject) +
+                match_rules::sender(setingsService),
+            powerRestoreConfigHandler, this);
+#ifdef USE_ACBOOT
+        matches.emplace_back(*conn,
+                             match_rules::interfacesAdded() +
+                                 match_rules::argNpath(0, powerACBootObject) +
+                                 match_rules::sender(setingsService),
+                             powerRestoreConfigHandler, this);
+        matches.emplace_back(*conn,
+                             match_rules::propertiesChanged(powerACBootObject,
+                                                            powerACBootIface) +
+                                 match_rules::sender(setingsService),
+                             powerRestoreConfigHandler, this);
+#endif // USE_ACBOOT
+    }
+
+    // Check if it's already on DBus
+    conn->async_method_call(
+        [this](boost::system::error_code ec,
+               const dbusPropertiesList properties) {
+            if (ec)
+            {
+                return;
+            }
+            setProperties(properties);
+        },
+        setingsService, powerRestorePolicyObject,
+        "org.freedesktop.DBus.Properties", "GetAll", powerRestorePolicyIface);
+
+#ifdef USE_ACBOOT
+    // Check if it's already on DBus
+    conn->async_method_call(
+        [this](boost::system::error_code ec,
+               const dbusPropertiesList properties) {
+            if (ec)
+            {
+                return;
+            }
+            setProperties(properties);
+        },
+        setingsService, powerACBootObject, "org.freedesktop.DBus.Properties",
+        "GetAll", powerACBootIface);
+#endif
+}
+
+void PowerRestoreController::setProperties(const dbusPropertiesList& props)
+{
+    for (auto& [property, propValue] : props)
+    {
+        if (property == "PowerRestorePolicy")
+        {
+            const std::string* value = std::get_if<std::string>(&propValue);
+            if (value == nullptr)
+            {
+                lg2::error("Unable to read Power Restore Policy");
+                continue;
+            }
+            powerRestorePolicy = *value;
+        }
+        else if (property == "PowerRestoreDelay")
+        {
+            const uint64_t* value = std::get_if<uint64_t>(&propValue);
+            if (value == nullptr)
+            {
+                lg2::error("Unable to read Power Restore Delay");
+                continue;
+            }
+            powerRestoreDelay = *value / 1000000; // usec to sec
+        }
+#ifdef USE_ACBOOT
+        else if (property == "ACBoot")
+        {
+            const std::string* value = std::get_if<std::string>(&propValue);
+            if (value == nullptr)
+            {
+                lg2::error("Unable to read AC Boot status");
+                continue;
+            }
+            acBoot = *value;
+        }
+#endif // USE_ACBOOT
+    }
+    invokeIfReady();
+}
+
+void PowerRestoreController::invokeIfReady()
+{
+    if ((powerRestorePolicy.empty()) || (powerRestoreDelay < 0))
+    {
+        return;
+    }
+#ifdef USE_ACBOOT
+    if (acBoot.empty() || acBoot == "Unknown")
+    {
+        return;
+    }
+#endif
+
+    matches.clear();
+    if (!timerFired)
+    {
+        // Calculate the delay from now to meet the requested delay
+        // Subtract the approximate uboot time
+        static constexpr const int ubootSeconds = 20;
+        int delay = powerRestoreDelay - ubootSeconds;
+        // Subtract the time since boot
+        struct sysinfo info = {};
+        if (sysinfo(&info) == 0)
+        {
+            delay -= info.uptime;
+        }
+
+        if (delay > 0)
+        {
+            powerRestoreTimer.expires_after(std::chrono::seconds(delay));
+            lg2::info("Power Restore delay of {DELAY} seconds started", "DELAY",
+                      delay);
+            powerRestoreTimer.async_wait([this](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)
+                    {
+                        return;
+                    }
+                    lg2::error(
+                        "power restore policy async_wait failed: {ERROR_MSG}",
+                        "ERROR_MSG", ec.message());
+                }
+                else
+                {
+                    lg2::info("Power Restore delay timer expired");
+                }
+                invoke();
+            });
+            timerFired = true;
+        }
+        else
+        {
+            invoke();
+        }
+    }
+}
+
+void PowerRestoreController::invoke()
+{
+    // we want to run Power Restore only once
     if (policyInvoked)
     {
         return;
     }
     policyInvoked = true;
 
-    lg2::info("Power restore delay expired, invoking {POLICY}", "POLICY",
-              policy);
-    if (policy ==
+    lg2::info("Invoking Power Restore Policy {POLICY}", "POLICY",
+              powerRestorePolicy);
+    if (powerRestorePolicy ==
         "xyz.openbmc_project.Control.Power.RestorePolicy.Policy.AlwaysOn")
     {
         sendPowerControlEvent(Event::powerOnRequest);
         setRestartCauseProperty(getRestartCause(RestartCause::powerPolicyOn));
     }
-    else if (policy ==
+    else if (powerRestorePolicy ==
              "xyz.openbmc_project.Control.Power.RestorePolicy.Policy.Restore")
     {
         if (wasPowerDropped())
@@ -898,232 +1106,10 @@
     savePowerState(powerState);
 }
 
-static void powerRestorePolicyDelay(int delay)
+bool PowerRestoreController::wasPowerDropped()
 {
-    // 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));
-    lg2::info("Power restore delay of {DELAY} seconds started", "DELAY", delay);
-    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)
-            {
-                lg2::error(
-                    "power restore policy async_wait failed: {ERROR_MSG}",
-                    "ERROR_MSG", ec.message());
-            }
-            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 (const std::exception& e)
-                        {
-                            lg2::error(
-                                "Unable to read restore policy value: {ERROR}",
-                                "ERROR", e);
-                            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)
-                {
-                    lg2::error("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()
-{
-    lg2::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 (const std::exception& e)
-                {
-                    lg2::error(
-                        "Unable to read power restore delay value: {ERROR}",
-                        "ERROR", e);
-                    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)
-            {
-                lg2::error("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 (const std::exception& e)
-                {
-                    lg2::error("Unable to read AC Boot status: {ERROR}",
-                               "ERROR", e);
-                    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)
-            {
-                lg2::error("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");
+    std::string state = appState.get(PersistentState::Params::PowerState);
+    return state == "xyz.openbmc_project.State.Chassis.PowerState.On";
 }
 
 static void waitForGPIOEvent(const std::string& name,
@@ -1637,7 +1623,9 @@
 
                 // Set the restart cause set for this restart
                 setRestartCause();
+#ifdef USE_ACBOOT
                 resetACBootProperty();
+#endif // USE_ACBOOT
                 sd_journal_send("MESSAGE=Host system DC power is off",
                                 "PRIORITY=%i", LOG_INFO,
                                 "REDFISH_MESSAGE_ID=%s",
@@ -2785,7 +2773,10 @@
         }
     }
     // Check if we need to start the Power Restore policy
-    powerRestorePolicyCheck();
+    if (powerState != PowerState::on)
+    {
+        powerRestore.run();
+    }
 
     if (nmiOutLine)
         nmiSourcePropertyMonitor();
diff --git a/src/power_control.hpp b/src/power_control.hpp
index 5dda797..9e54184 100644
--- a/src/power_control.hpp
+++ b/src/power_control.hpp
@@ -5,7 +5,11 @@
 
 #pragma once
 
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <boost/container/flat_map.hpp>
 #include <nlohmann/json.hpp>
+#include <sdbusplus/asio/object_server.hpp>
 
 #include <filesystem>
 #include <string_view>
@@ -21,6 +25,70 @@
  * default values, hardcoded in getDefault() method.
  * @note: currently only string parameters supported
  */
+using dbusPropertiesList =
+    boost::container::flat_map<std::string,
+                               std::variant<std::string, uint64_t>>;
+/**
+ * @brief The class contains functions to invoke power restore policy.
+ *
+ * This class only exists to unite all PowerRestore-related code. It supposed
+ * to run only once on application startup.
+ */
+class PowerRestoreController
+{
+  public:
+    PowerRestoreController(boost::asio::io_service& io) :
+        policyInvoked(false), powerRestoreDelay(-1), powerRestoreTimer(io),
+        timerFired(false)
+    {}
+    /**
+     * @brief Power Restore entry point.
+     *
+     * Call this to start Power Restore algorithm.
+     */
+    void run();
+    /**
+     * @brief Initialize configuration parameters.
+     *
+     * Parse list of properties, received from dbus, to set Power Restore
+     * algorithm configuration.
+     * @param props - map of property names and values
+     */
+    void setProperties(const dbusPropertiesList& props);
+
+  private:
+    bool policyInvoked;
+    std::string powerRestorePolicy;
+    int powerRestoreDelay;
+    std::list<sdbusplus::bus::match::match> matches;
+    boost::asio::steady_timer powerRestoreTimer;
+    bool timerFired;
+#ifdef USE_ACBOOT
+    std::string acBoot;
+#endif // USE_ACBOOT
+
+    /**
+     * @brief Check if all required algorithms parameters are set
+     *
+     * Call this after set any of Power Restore algorithm parameters. Once all
+     * parameters are set this will run invoke() function.
+     */
+    void invokeIfReady();
+    /**
+     * @brief Actually perform power restore actions.
+     *
+     * Take Power Restore actions according to Policy and other parameters.
+     */
+    void invoke();
+    /**
+     * @brief Check if power was dropped.
+     *
+     * Read last saved power state to determine if host power was enabled before
+     * last BMC reboot.
+     */
+    bool wasPowerDropped();
+};
+
 class PersistentState
 {
   public: