Add Set Front Panel Button Enables command

This change overrides the chassis status and set front panel
button enables commands to use the new Buttons interface.

Tested:
ipmitool raw 0 0xa 7
ipmitool chassis status
System Power         : on
Power Overload       : false
Power Interlock      : inactive
Main Power Fault     : false
Power Control Fault  : false
Power Restore Policy : always-off
Last Power Event     :
Chassis Intrusion    : inactive
Front-Panel Lockout  : inactive
Drive Fault          : false
Cooling/Fan Fault    : false
Sleep Button Disable : not allowed
Diag Button Disable  : allowed
Reset Button Disable : allowed
Power Button Disable : allowed
Sleep Button Disabled: false
Diag Button Disabled : true
Reset Button Disabled: true
Power Button Disabled: true

ipmitool raw 0 0xa 0
ipmitool chassis status
System Power         : on
Power Overload       : false
Power Interlock      : inactive
Main Power Fault     : false
Power Control Fault  : false
Power Restore Policy : always-off
Last Power Event     :
Chassis Intrusion    : inactive
Front-Panel Lockout  : inactive
Drive Fault          : false
Cooling/Fan Fault    : false
Sleep Button Disable : not allowed
Diag Button Disable  : allowed
Reset Button Disable : allowed
Power Button Disable : allowed
Sleep Button Disabled: false
Diag Button Disabled : false
Reset Button Disabled: false
Power Button Disabled: false

Change-Id: I12b6c19483404bee2d481e08260d878bd5aa99c5
Signed-off-by: Jason M. Bills <jason.m.bills@linux.intel.com>
diff --git a/src/chassiscommands.cpp b/src/chassiscommands.cpp
index 72439f7..2fa9e23 100644
--- a/src/chassiscommands.cpp
+++ b/src/chassiscommands.cpp
@@ -16,29 +16,43 @@
 #include "xyz/openbmc_project/Common/error.hpp"
 
 #include <fstream>
+#include <iostream>
 #include <ipmid/api.hpp>
 #include <ipmid/utils.hpp>
 #include <nlohmann/json.hpp>
 #include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
 #include <regex>
 #include <sdbusplus/timer.hpp>
+#include <stdexcept>
+#include <string_view>
+#include <xyz/openbmc_project/Control/Power/RestorePolicy/server.hpp>
 
-namespace ipmi
+using namespace phosphor::logging;
+
+namespace ipmi::chassis
 {
-const static constexpr char *idButtonPath =
-    "/xyz/openbmc_project/chassis/buttons/id";
-const static constexpr char *idButtonInterface =
-    "xyz.openbmc_project.Chassis.Buttons";
-const static constexpr char *idButtonProp = "ButtonPressed";
+static constexpr const char* buttonIntf = "xyz.openbmc_project.Chassis.Buttons";
 
-const static constexpr char *ledService =
+const static constexpr char* idButtonPath =
+    "/xyz/openbmc_project/chassis/buttons/id";
+static constexpr const char* powerButtonPath =
+    "/xyz/openbmc_project/chassis/buttons/power";
+static constexpr const char* resetButtonPath =
+    "/xyz/openbmc_project/chassis/buttons/reset";
+static constexpr const char* interruptButtonPath =
+    "/xyz/openbmc_project/chassis/buttons/nmi";
+
+const static constexpr char* idButtonProp = "ButtonPressed";
+
+const static constexpr char* ledService =
     "xyz.openbmc_project.LED.GroupManager";
-const static constexpr char *ledIDOnObj =
+const static constexpr char* ledIDOnObj =
     "/xyz/openbmc_project/led/groups/enclosure_identify";
-const static constexpr char *ledIDBlinkObj =
+const static constexpr char* ledIDBlinkObj =
     "/xyz/openbmc_project/led/groups/enclosure_identify_blink";
-const static constexpr char *ledInterface = "xyz.openbmc_project.Led.Group";
-const static constexpr char *ledProp = "Asserted";
+const static constexpr char* ledInterface = "xyz.openbmc_project.Led.Group";
+const static constexpr char* ledProp = "Asserted";
 
 constexpr size_t defaultIdentifyTimeOut = 15;
 
@@ -51,7 +65,7 @@
 
 static ipmi::ServiceCache LEDService(ledInterface, ledIDBlinkObj);
 
-void enclosureIdentifyLed(const char *objName, bool isIdLedOn)
+void enclosureIdentifyLed(const char* objName, bool isIdLedOn)
 {
     auto bus = getSdBus();
 
@@ -61,15 +75,14 @@
         setDbusProperty(*bus, service, objName, ledInterface, ledProp,
                         isIdLedOn);
     }
-    catch (const std::exception &e)
+    catch (const std::exception& e)
     {
-        phosphor::logging::log<phosphor::logging::level::ERR>(
-            "enclosureIdentifyLed: can't set property",
-            phosphor::logging::entry("ERR=%s", e.what()));
+        log<level::ERR>("enclosureIdentifyLed: can't set property",
+                        entry("ERR=%s", e.what()));
     }
 }
 
-bool getIDState(const char *objName, bool &state)
+bool getIDState(const char* objName, bool& state)
 {
     auto bus = getSdBus();
 
@@ -80,12 +93,10 @@
             getDbusProperty(*bus, service, objName, ledInterface, ledProp);
         state = std::get<bool>(enabled);
     }
-    catch (sdbusplus::exception::SdBusError &e)
+    catch (sdbusplus::exception::SdBusError& e)
     {
-        phosphor::logging::log<phosphor::logging::level::ERR>(
-            "Fail to get property",
-            phosphor::logging::entry("PATH=%s", objName),
-            phosphor::logging::entry("ERROR=%s", e.what()));
+        log<level::ERR>("Fail to get property", entry("PATH=%s", objName),
+                        entry("ERROR=%s", e.what()));
         return false;
     }
     return true;
@@ -96,7 +107,7 @@
     enclosureIdentifyLed(ledIDBlinkObj, false);
 }
 
-void idButtonPropChanged(sdbusplus::message::message &msg)
+void idButtonPropChanged(sdbusplus::message::message& msg)
 {
     bool asserted = false;
     bool buttonPressed = false;
@@ -106,7 +117,7 @@
     std::string iface;
     msg.read(iface, props, inval);
 
-    for (const auto &t : props)
+    for (const auto& t : props)
     {
         auto key = t.first;
         auto value = t.second;
@@ -122,8 +133,7 @@
     {
         if (identifyTimer->isRunning())
         {
-            phosphor::logging::log<phosphor::logging::level::INFO>(
-                "ID timer is running");
+            log<level::INFO>("ID timer is running");
         }
 
         // make sure timer is stopped
@@ -189,10 +199,307 @@
     return ipmi::responseSuccess();
 }
 
+namespace power_policy
+{
+/* helper function for Get Chassis Status Command
+ */
+std::optional<uint2_t> getPowerRestorePolicy()
+{
+    constexpr const char* powerRestorePath =
+        "/xyz/openbmc_project/control/host0/power_restore_policy";
+    constexpr const char* powerRestoreIntf =
+        "xyz.openbmc_project.Control.Power.RestorePolicy";
+    uint2_t restorePolicy = 0;
+    std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
+
+    try
+    {
+        auto service =
+            ipmi::getService(*busp, powerRestoreIntf, powerRestorePath);
+
+        ipmi::Value result =
+            ipmi::getDbusProperty(*busp, service, powerRestorePath,
+                                  powerRestoreIntf, "PowerRestorePolicy");
+        auto powerRestore = sdbusplus::xyz::openbmc_project::Control::Power::
+            server::RestorePolicy::convertPolicyFromString(
+                std::get<std::string>(result));
+
+        using namespace sdbusplus::xyz::openbmc_project::Control::Power::server;
+        switch (powerRestore)
+        {
+            case RestorePolicy::Policy::AlwaysOff:
+                restorePolicy = 0x00;
+                break;
+            case RestorePolicy::Policy::Restore:
+                restorePolicy = 0x01;
+                break;
+            case RestorePolicy::Policy::AlwaysOn:
+                restorePolicy = 0x02;
+                break;
+        }
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>("Failed to fetch PowerRestorePolicy property",
+                        entry("ERROR=%s", e.what()),
+                        entry("PATH=%s", powerRestorePath),
+                        entry("INTERFACE=%s", powerRestoreIntf));
+        return std::nullopt;
+    }
+    return std::make_optional(restorePolicy);
+}
+
+/*
+ * getPowerStatus
+ * helper function for Get Chassis Status Command
+ * return - optional value for pgood (no value on error)
+ */
+std::optional<bool> getPowerStatus()
+{
+    bool powerGood = false;
+    std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
+    try
+    {
+        constexpr const char* chassisStatePath =
+            "/xyz/openbmc_project/state/chassis0";
+        constexpr const char* chassisStateIntf =
+            "xyz.openbmc_project.State.Chassis";
+        auto service =
+            ipmi::getService(*busp, chassisStateIntf, chassisStatePath);
+
+        ipmi::Value variant =
+            ipmi::getDbusProperty(*busp, service, chassisStatePath,
+                                  chassisStateIntf, "CurrentPowerState");
+        std::string powerState = std::get<std::string>(variant);
+        if (powerState == "xyz.openbmc_project.State.Chassis.PowerState.On")
+        {
+            powerGood = true;
+        }
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>("Failed to fetch power state property",
+                        entry("ERROR=%s", e.what()));
+        return std::nullopt;
+    }
+    return std::make_optional(powerGood);
+}
+
+/*
+ * getACFailStatus
+ * helper function for Get Chassis Status Command
+ * return - bool value for ACFail (false on error)
+ */
+bool getACFailStatus()
+{
+    constexpr const char* powerControlObj =
+        "/xyz/openbmc_project/Chassis/Control/Power0";
+    constexpr const char* powerControlIntf =
+        "xyz.openbmc_project.Chassis.Control.Power";
+    bool acFail = false;
+    std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus();
+    try
+    {
+        auto service =
+            ipmi::getService(*bus, powerControlIntf, powerControlObj);
+
+        ipmi::Value variant = ipmi::getDbusProperty(
+            *bus, service, powerControlObj, powerControlIntf, "PFail");
+        acFail = std::get<bool>(variant);
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>("Failed to fetch PFail property",
+                        entry("ERROR=%s", e.what()),
+                        entry("PATH=%s", powerControlObj),
+                        entry("INTERFACE=%s", powerControlIntf));
+    }
+    return acFail;
+}
+} // namespace power_policy
+
+static std::optional<bool> getButtonEnabled(const std::string& buttonPath)
+{
+    bool buttonDisabled = false;
+    std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
+    try
+    {
+        auto service = ipmi::getService(*getSdBus(), buttonIntf, buttonPath);
+        ipmi::Value disabled = ipmi::getDbusProperty(
+            *busp, service, buttonPath, buttonIntf, "ButtonMasked");
+        buttonDisabled = std::get<bool>(disabled);
+    }
+    catch (sdbusplus::exception::SdBusError& e)
+    {
+        log<level::ERR>("Fail to get button disabled property",
+                        entry("PATH=%s", buttonPath.c_str()),
+                        entry("ERROR=%s", e.what()));
+        return std::nullopt;
+    }
+    return std::make_optional(buttonDisabled);
+}
+
+static bool setButtonEnabled(const std::string& buttonPath, const bool disabled)
+{
+    try
+    {
+        auto service = ipmi::getService(*getSdBus(), buttonIntf, buttonPath);
+        ipmi::setDbusProperty(*getSdBus(), service, buttonPath, buttonIntf,
+                              "ButtonMasked", disabled);
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>("Failed to set button disabled",
+                        entry("EXCEPTION=%s, REQUEST=%x", e.what(), disabled));
+        return -1;
+    }
+
+    return 0;
+}
+
+//----------------------------------------------------------------------
+// Get Chassis Status commands
+//----------------------------------------------------------------------
+ipmi::RspType<bool,    // Power is on
+              bool,    // Power overload
+              bool,    // Interlock
+              bool,    // power fault
+              bool,    // power control fault
+              uint2_t, // power restore policy
+              bool,    // reserved
+
+              bool, // AC failed
+              bool, // last power down caused by a Power overload
+              bool, // last power down caused by a power interlock
+              bool, // last power down caused by power fault
+              bool, // last ‘Power is on’ state was entered via IPMI command
+              uint3_t, // reserved
+
+              bool,    // Chassis intrusion active
+              bool,    // Front Panel Lockout active
+              bool,    // Drive Fault
+              bool,    // Cooling/fan fault detected
+              uint2_t, // Chassis Identify State
+              bool,    // Chassis Identify command and state info supported
+              bool,    // reserved
+
+              bool, // Power off button disabled
+              bool, // Reset button disabled
+              bool, // Diagnostic Interrupt button disabled
+              bool, // Standby (sleep) button disabled
+              bool, // Power off button disable allowed
+              bool, // Reset button disable allowed
+              bool, // Diagnostic Interrupt button disable allowed
+              bool  // Standby (sleep) button disable allowed
+              >
+    ipmiGetChassisStatus()
+{
+    std::optional<uint2_t> restorePolicy =
+        power_policy::getPowerRestorePolicy();
+    std::optional<bool> powerGood = power_policy::getPowerStatus();
+    if (!restorePolicy || !powerGood)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    //  Front Panel Button Capabilities and disable/enable status(Optional)
+    std::optional<bool> powerButtonReading = getButtonEnabled(powerButtonPath);
+    // allow disable if the interface is present
+    bool powerButtonDisableAllow = static_cast<bool>(powerButtonReading);
+    // default return the button is enabled (not disabled)
+    bool powerButtonDisabled = false;
+    if (powerButtonDisableAllow)
+    {
+        // return the real value of the button status, if present
+        powerButtonDisabled = *powerButtonReading;
+    }
+
+    std::optional<bool> resetButtonReading = getButtonEnabled(resetButtonPath);
+    // allow disable if the interface is present
+    bool resetButtonDisableAllow = static_cast<bool>(resetButtonReading);
+    // default return the button is enabled (not disabled)
+    bool resetButtonDisabled = false;
+    if (resetButtonDisableAllow)
+    {
+        // return the real value of the button status, if present
+        resetButtonDisabled = *resetButtonReading;
+    }
+
+    std::optional<bool> interruptButtonReading =
+        getButtonEnabled(interruptButtonPath);
+    // allow disable if the interface is present
+    bool interruptButtonDisableAllow =
+        static_cast<bool>(interruptButtonReading);
+    // default return the button is enabled (not disabled)
+    bool interruptButtonDisabled = false;
+    if (interruptButtonDisableAllow)
+    {
+        // return the real value of the button status, if present
+        interruptButtonDisabled = *interruptButtonReading;
+    }
+
+    bool powerDownAcFailed = power_policy::getACFailStatus();
+
+    // This response has a lot of hard-coded, unsupported fields
+    // They are set to false or 0
+    constexpr bool powerOverload = false;
+    constexpr bool chassisInterlock = false;
+    constexpr bool powerFault = false;
+    constexpr bool powerControlFault = false;
+    constexpr bool powerDownOverload = false;
+    constexpr bool powerDownInterlock = false;
+    constexpr bool powerDownPowerFault = false;
+    constexpr bool powerStatusIPMI = false;
+    constexpr bool chassisIntrusionActive = false;
+    constexpr bool frontPanelLockoutActive = false;
+    constexpr bool driveFault = false;
+    constexpr bool coolingFanFault = false;
+    // chassisIdentifySupport set because this command is implemented
+    constexpr bool chassisIdentifySupport = true;
+    uint2_t chassisIdentifyState = 0;
+    constexpr bool sleepButtonDisabled = false;
+    constexpr bool sleepButtonDisableAllow = false;
+
+    return ipmi::responseSuccess(
+        *powerGood, powerOverload, chassisInterlock, powerFault,
+        powerControlFault, *restorePolicy,
+        false, // reserved
+
+        powerDownAcFailed, powerDownOverload, powerDownInterlock,
+        powerDownPowerFault, powerStatusIPMI,
+        uint3_t(0), // reserved
+
+        chassisIntrusionActive, frontPanelLockoutActive, driveFault,
+        coolingFanFault, chassisIdentifyState, chassisIdentifySupport,
+        false, // reserved
+
+        powerButtonDisabled, resetButtonDisabled, interruptButtonDisabled,
+        sleepButtonDisabled, powerButtonDisableAllow, resetButtonDisableAllow,
+        interruptButtonDisableAllow, sleepButtonDisableAllow);
+}
+
+ipmi::RspType<> ipmiSetFrontPanelButtonEnables(bool disablePowerButton,
+                                               bool disableResetButton,
+                                               bool disableInterruptButton,
+                                               bool disableSleepButton,
+                                               uint4_t reserved)
+{
+    bool error = false;
+
+    error |= setButtonEnabled(powerButtonPath, disablePowerButton);
+    error |= setButtonEnabled(resetButtonPath, disableResetButton);
+    error |= setButtonEnabled(interruptButtonPath, disableInterruptButton);
+
+    if (error)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+    return ipmi::responseSuccess();
+}
+
 static void registerChassisFunctions(void)
 {
-    phosphor::logging::log<phosphor::logging::level::INFO>(
-        "Registering Chassis commands");
+    log<level::INFO>("Registering Chassis commands");
 
     createIdentifyTimer();
 
@@ -204,7 +511,7 @@
         matchPtr = std::make_unique<sdbusplus::bus::match_t>(
             *bus,
             sdbusplus::bus::match::rules::propertiesChanged(idButtonPath,
-                                                            idButtonInterface),
+                                                            buttonIntf),
             std::bind(idButtonPropChanged, std::placeholders::_1));
     }
 
@@ -212,6 +519,15 @@
     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
                           ipmi::chassis::cmdChassisIdentify,
                           ipmi::Privilege::Operator, ipmiChassisIdentify);
+    // <Get Chassis Status>
+    ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdGetChassisStatus,
+                          ipmi::Privilege::User, ipmiGetChassisStatus);
+    // <Set Front Panel Enables>
+    ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdSetFrontPanelButtonEnables,
+                          ipmi::Privilege::User,
+                          ipmiSetFrontPanelButtonEnables);
 }
 
-} // namespace ipmi
+} // namespace ipmi::chassis