chassishandler: Add restart cause support

Added support for chassis restart cause command. This will read data
from Host power state dbus interface and reply to ipmi command.

Tested: Verified executing 'ipmitool chassis restart_cause'
System restart cause: power-up via pushbutton

Signed-off-by: Vijay Khemka <vijaykhemka@fb.com>
Change-Id: I8e351035da1b8b1d85f3c34094fd7533e7af529b
diff --git a/chassishandler.cpp b/chassishandler.cpp
index 734ccde..216af6a 100644
--- a/chassishandler.cpp
+++ b/chassishandler.cpp
@@ -66,6 +66,9 @@
 static constexpr size_t ADDRTYPE_OFFSET = 16;
 static constexpr size_t IPADDR_OFFSET = 17;
 
+static constexpr uint4_t RESERVED = 0;
+static constexpr uint8_t CHANNEL_NOT_SUPPORTED = 0;
+
 static constexpr size_t encIdentifyObjectsSize = 1;
 static constexpr size_t chassisIdentifyReqLength = 2;
 static constexpr size_t identifyIntervalPos = 0;
@@ -1092,6 +1095,114 @@
         diagButtonDisableAllow, sleepButtonDisableAllow);
 }
 
+enum class IpmiRestartCause
+{
+    Unknown = 0x0,
+    RemoteCommand = 0x1,
+    ResetButton = 0x2,
+    PowerButton = 0x3,
+    WatchdogTimer = 0x4,
+    PowerPolicyAlwaysOn = 0x6,
+    PowerPolicyPreviousState = 0x7,
+    SoftReset = 0xa,
+};
+
+static IpmiRestartCause
+    restartCauseToIpmiRestartCause(State::Host::RestartCause cause)
+{
+    switch (cause)
+    {
+        case State::Host::RestartCause::Unknown:
+        {
+            return IpmiRestartCause::Unknown;
+        }
+        case State::Host::RestartCause::RemoteCommand:
+        {
+            return IpmiRestartCause::RemoteCommand;
+        }
+        case State::Host::RestartCause::ResetButton:
+        {
+            return IpmiRestartCause::ResetButton;
+        }
+        case State::Host::RestartCause::PowerButton:
+        {
+            return IpmiRestartCause::PowerButton;
+        }
+        case State::Host::RestartCause::WatchdogTimer:
+        {
+            return IpmiRestartCause::WatchdogTimer;
+        }
+        case State::Host::RestartCause::PowerPolicyAlwaysOn:
+        {
+            return IpmiRestartCause::PowerPolicyAlwaysOn;
+        }
+        case State::Host::RestartCause::PowerPolicyPreviousState:
+        {
+            return IpmiRestartCause::PowerPolicyPreviousState;
+        }
+        case State::Host::RestartCause::SoftReset:
+        {
+            return IpmiRestartCause::SoftReset;
+        }
+        default:
+        {
+            return IpmiRestartCause::Unknown;
+        }
+    }
+}
+
+/*
+ * getRestartCause
+ * helper function for Get Host restart cause Command
+ * return - optional value for RestartCause (no value on error)
+ */
+static std::optional<uint4_t> getRestartCause(ipmi::Context::ptr ctx)
+{
+    constexpr const char* restartCausePath =
+        "/xyz/openbmc_project/control/host0/restart_cause";
+    constexpr const char* restartCauseIntf =
+        "xyz.openbmc_project.Control.Host.RestartCause";
+
+    std::string service;
+    boost::system::error_code ec =
+        ipmi::getService(ctx, restartCauseIntf, restartCausePath, service);
+    if (!ec)
+    {
+        std::string restartCauseStr;
+        ec = ipmi::getDbusProperty<std::string>(
+            ctx, service, restartCausePath, restartCauseIntf, "RestartCause",
+            restartCauseStr);
+        if (!ec)
+        {
+            auto cause =
+                State::Host::convertRestartCauseFromString(restartCauseStr);
+            return restartCauseToIpmiRestartCause(cause);
+        }
+    }
+
+    log<level::ERR>("Failed to fetch RestartCause property",
+                    entry("ERROR=%s", ec.message().c_str()),
+                    entry("PATH=%s", restartCausePath),
+                    entry("INTERFACE=%s", restartCauseIntf));
+    return std::nullopt;
+}
+
+ipmi::RspType<uint4_t, // Restart Cause
+              uint4_t, // reserved
+              uint8_t  // channel number (not supported)
+              >
+    ipmiGetSystemRestartCause(ipmi::Context::ptr ctx)
+{
+    std::optional<uint4_t> cause = getRestartCause(ctx);
+    if (!cause)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    return ipmi::responseSuccess(cause.value(), RESERVED,
+                                 CHANNEL_NOT_SUPPORTED);
+}
+
 //-------------------------------------------------------------
 // Send a command to SoftPowerOff application to stop any timer
 //-------------------------------------------------------------
@@ -1865,6 +1976,11 @@
                           ipmi::chassis::cmdGetChassisStatus,
                           ipmi::Privilege::User, ipmiGetChassisStatus);
 
+    // <Chassis Get System Restart Cause>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdGetSystemRestartCause,
+                          ipmi::Privilege::User, ipmiGetSystemRestartCause);
+
     // <Chassis Control>
     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
                           ipmi::chassis::cmdChassisControl,