Manage the RestartCause property from power-control

There were race-conditions when setting the RestartCause from
outside of power-control. This change has power-control manage
the RestartCause property directly so it can handle the race-
conditions.

The race-conditions are resolved by adding a RestartCauseRequest
property that can be set by external modules to request a restart
cause value. All requested restart causes are maintained in a set.

When the host powers-off, the set of restart causes is checked and
the highest priority cause is set as the RestartCause.

When the host powers-on, the set of restart causes is cleared for
the next set of causes to be recorded.

Tested:
Triggered a watchdog and confirmed that the request was added to the
set and that based on priority, watchdog was set as the RestartCause
when the system restarted.

Triggered a software, command, button, and power restore restart
types and confirmed that the restart cause was correctly reported.

Change-Id: If968d434ec3d827e82e033bd528acaa88f625d1e
Signed-off-by: Jason M. Bills <jason.m.bills@linux.intel.com>
diff --git a/power-control-x86/src/power_control.cpp b/power-control-x86/src/power_control.cpp
index ba33124..5348a4b 100644
--- a/power-control-x86/src/power_control.cpp
+++ b/power-control-x86/src/power_control.cpp
@@ -20,6 +20,7 @@
 
 #include <boost/asio/posix/stream_descriptor.hpp>
 #include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
 #include <filesystem>
 #include <fstream>
 #include <gpiod.hpp>
@@ -39,6 +40,7 @@
 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;
@@ -80,6 +82,8 @@
 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;
@@ -457,10 +461,12 @@
     command,
     resetButton,
     powerButton,
+    watchdog,
     powerPolicyOn,
     powerPolicyRestore,
     softReset,
 };
+static boost::container::flat_set<RestartCause> causeSet;
 static std::string getRestartCause(RestartCause cause)
 {
     switch (cause)
@@ -474,6 +480,9 @@
         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";
@@ -490,20 +499,56 @@
             break;
     }
 }
-static void setRestartCause(const RestartCause cause)
+static void addRestartCause(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)));
+    // 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::cerr << "RestartCause set to " << cause << "\n";
+    restartCauseIface->set_property("RestartCause", cause);
+}
+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()
@@ -610,7 +655,7 @@
         "xyz.openbmc_project.Control.Power.RestorePolicy.Policy.AlwaysOn")
     {
         sendPowerControlEvent(Event::powerOnRequest);
-        setRestartCause(RestartCause::powerPolicyOn);
+        setRestartCauseProperty(getRestartCause(RestartCause::powerPolicyOn));
     }
     else if (policy == "xyz.openbmc_project.Control.Power.RestorePolicy."
                        "Policy.Restore")
@@ -619,7 +664,8 @@
         {
             std::cerr << "Power was dropped, restoring Host On state\n";
             sendPowerControlEvent(Event::powerOnRequest);
-            setRestartCause(RestartCause::powerPolicyRestore);
+            setRestartCauseProperty(
+                getRestartCause(RestartCause::powerPolicyRestore));
         }
         else
         {
@@ -1235,10 +1281,14 @@
                 "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();
             }
         });
 }
@@ -1279,11 +1329,11 @@
             break;
         case Event::sioS5Assert:
             setPowerState(PowerState::transitionToOff);
-            setRestartCause(RestartCause::softReset);
+            addRestartCause(RestartCause::softReset);
             break;
         case Event::postCompleteDeAssert:
             setPowerState(PowerState::checkForWarmReset);
-            setRestartCause(RestartCause::softReset);
+            addRestartCause(RestartCause::softReset);
             warmResetCheckTimerStart();
             break;
         case Event::powerButtonPressed:
@@ -1625,7 +1675,7 @@
         if (!powerButtonMask)
         {
             sendPowerControlEvent(Event::powerButtonPressed);
-            setRestartCause(RestartCause::powerButton);
+            addRestartCause(RestartCause::powerButton);
         }
         else
         {
@@ -1660,7 +1710,7 @@
         if (!resetButtonMask)
         {
             sendPowerControlEvent(Event::resetButtonPressed);
-            setRestartCause(RestartCause::resetButton);
+            addRestartCause(RestartCause::resetButton);
         }
         else
         {
@@ -1889,6 +1939,8 @@
         "xyz.openbmc_project.State.OperatingSystem");
     power_control::conn->request_name("xyz.openbmc_project.Chassis.Buttons");
     power_control::conn->request_name("xyz.openbmc_project.Control.Host.NMI");
+    power_control::conn->request_name(
+        "xyz.openbmc_project.Control.Host.RestartCause");
 
     // Request PS_PWROK GPIO events
     if (!power_control::requestGPIOEvents(
@@ -2014,19 +2066,20 @@
             {
                 sendPowerControlEvent(
                     power_control::Event::gracefulPowerOffRequest);
+                addRestartCause(power_control::RestartCause::command);
             }
             else if (requested ==
                      "xyz.openbmc_project.State.Host.Transition.On")
             {
                 sendPowerControlEvent(power_control::Event::powerOnRequest);
-                setRestartCause(power_control::RestartCause::command);
+                addRestartCause(power_control::RestartCause::command);
             }
             else if (requested ==
                      "xyz.openbmc_project.State.Host.Transition.Reboot")
             {
                 sendPowerControlEvent(
                     power_control::Event::gracefulPowerCycleRequest);
-                setRestartCause(power_control::RestartCause::command);
+                addRestartCause(power_control::RestartCause::command);
             }
             else
             {
@@ -2061,23 +2114,24 @@
             if (requested == "xyz.openbmc_project.State.Chassis.Transition.Off")
             {
                 sendPowerControlEvent(power_control::Event::powerOffRequest);
+                addRestartCause(power_control::RestartCause::command);
             }
             else if (requested ==
                      "xyz.openbmc_project.State.Chassis.Transition.On")
             {
                 sendPowerControlEvent(power_control::Event::powerOnRequest);
-                setRestartCause(power_control::RestartCause::command);
+                addRestartCause(power_control::RestartCause::command);
             }
             else if (requested ==
                      "xyz.openbmc_project.State.Chassis.Transition.PowerCycle")
             {
                 sendPowerControlEvent(power_control::Event::powerCycleRequest);
-                setRestartCause(power_control::RestartCause::command);
+                addRestartCause(power_control::RestartCause::command);
             }
             else if (requested ==
                      "xyz.openbmc_project.State.Chassis.Transition.Reset")
             {
-                setRestartCause(power_control::RestartCause::command);
+                addRestartCause(power_control::RestartCause::command);
                 sendPowerControlEvent(power_control::Event::resetRequest);
             }
             else
@@ -2275,6 +2329,43 @@
 
     power_control::osIface->initialize();
 
+    // Restart Cause Service
+    sdbusplus::asio::object_server restartCauseServer =
+        sdbusplus::asio::object_server(power_control::conn);
+
+    // Restart Cause Interface
+    power_control::restartCauseIface = restartCauseServer.add_interface(
+        "/xyz/openbmc_project/control/host0/restart_cause",
+        "xyz.openbmc_project.Control.Host.RestartCause");
+
+    power_control::restartCauseIface->register_property(
+        "RestartCause",
+        std::string("xyz.openbmc_project.State.Host.RestartCause.Unknown"));
+
+    power_control::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")
+            {
+                power_control::addRestartCause(
+                    power_control::RestartCause::watchdog);
+            }
+            else
+            {
+                throw std::invalid_argument(
+                    "Unrecognized RestartCause Request");
+                return 0;
+            }
+
+            std::cerr << "RestartCause requested: " << requested << "\n";
+            resp = requested;
+            return 1;
+        });
+
+    power_control::restartCauseIface->initialize();
+
     power_control::io.run();
 
     return 0;