Implement IPMI ACPI set/get commands

The IPMI ACPI set/get power state commands are provided
to allow system software to tell a controller the present
ACPI power state of the system.

This is an independent setting that may not necessarily match the
actual power state of the system.

The commands are used to enable the reporting of the power state,
it does not control or change the power state.

The ACPI status will be saved into flash by settings manager.

Tested By:
Set ACPI:
ipmitool -H <IP>  -P 0penBmc -I lanplus raw 0x06 0x06 0x86 0x83
Get ACPI:
ipmitool -H <IP>  -P 0penBmc -I lanplus raw 0x06 0x07

Change-Id: I6d00c0aec931b98d6691d7c4a54698e08d317aa4
Signed-off-by: Yong Li <yong.b.li@linux.intel.com>
diff --git a/apphandler.cpp b/apphandler.cpp
index ac06f50..370e0c3 100644
--- a/apphandler.cpp
+++ b/apphandler.cpp
@@ -29,6 +29,7 @@
 #include <tuple>
 #include <vector>
 #include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Control/Power/ACPIPowerState/server.hpp>
 #include <xyz/openbmc_project/Software/Activation/server.hpp>
 #include <xyz/openbmc_project/Software/Version/server.hpp>
 #include <xyz/openbmc_project/State/BMC/server.hpp>
@@ -179,16 +180,273 @@
                variant_ns::get<std::string>(variant)) == BMC::BMCState::Ready;
 }
 
+namespace acpi_state
+{
+using namespace sdbusplus::xyz::openbmc_project::Control::Power::server;
+
+const static constexpr char* acpiObjPath =
+    "/xyz/openbmc_project/control/host0/acpi_power_state";
+const static constexpr char* acpiInterface =
+    "xyz.openbmc_project.Control.Power.ACPIPowerState";
+const static constexpr char* sysACPIProp = "SysACPIStatus";
+const static constexpr char* devACPIProp = "DevACPIStatus";
+
+enum class PowerStateType : uint8_t
+{
+    sysPowerState = 0x00,
+    devPowerState = 0x01,
+};
+
+// Defined in 20.6 of ipmi doc
+enum class PowerState : uint8_t
+{
+    s0G0D0 = 0x00,
+    s1D1 = 0x01,
+    s2D2 = 0x02,
+    s3D3 = 0x03,
+    s4 = 0x04,
+    s5G2 = 0x05,
+    s4S5 = 0x06,
+    g3 = 0x07,
+    sleep = 0x08,
+    g1Sleep = 0x09,
+    override = 0x0a,
+    legacyOn = 0x20,
+    legacyOff = 0x21,
+    unknown = 0x2a,
+    noChange = 0x7f,
+};
+
+static constexpr uint8_t stateChanged = 0x80;
+
+struct ACPIState
+{
+    uint8_t sysACPIState;
+    uint8_t devACPIState;
+} __attribute__((packed));
+
+std::map<ACPIPowerState::ACPI, PowerState> dbusToIPMI = {
+    {ACPIPowerState::ACPI::S0_G0_D0, PowerState::s0G0D0},
+    {ACPIPowerState::ACPI::S1_D1, PowerState::s1D1},
+    {ACPIPowerState::ACPI::S2_D2, PowerState::s2D2},
+    {ACPIPowerState::ACPI::S3_D3, PowerState::s3D3},
+    {ACPIPowerState::ACPI::S4, PowerState::s4},
+    {ACPIPowerState::ACPI::S5_G2, PowerState::s5G2},
+    {ACPIPowerState::ACPI::S4_S5, PowerState::s4S5},
+    {ACPIPowerState::ACPI::G3, PowerState::g3},
+    {ACPIPowerState::ACPI::SLEEP, PowerState::sleep},
+    {ACPIPowerState::ACPI::G1_SLEEP, PowerState::g1Sleep},
+    {ACPIPowerState::ACPI::OVERRIDE, PowerState::override},
+    {ACPIPowerState::ACPI::LEGACY_ON, PowerState::legacyOn},
+    {ACPIPowerState::ACPI::LEGACY_OFF, PowerState::legacyOff},
+    {ACPIPowerState::ACPI::Unknown, PowerState::unknown}};
+
+bool isValidACPIState(acpi_state::PowerStateType type, uint8_t state)
+{
+    if (type == acpi_state::PowerStateType::sysPowerState)
+    {
+        if ((state <= static_cast<uint8_t>(acpi_state::PowerState::override)) ||
+            (state == static_cast<uint8_t>(acpi_state::PowerState::legacyOn)) ||
+            (state ==
+             static_cast<uint8_t>(acpi_state::PowerState::legacyOff)) ||
+            (state == static_cast<uint8_t>(acpi_state::PowerState::unknown)) ||
+            (state == static_cast<uint8_t>(acpi_state::PowerState::noChange)))
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+    else if (type == acpi_state::PowerStateType::devPowerState)
+    {
+        if ((state <= static_cast<uint8_t>(acpi_state::PowerState::s3D3)) ||
+            (state == static_cast<uint8_t>(acpi_state::PowerState::unknown)) ||
+            (state == static_cast<uint8_t>(acpi_state::PowerState::noChange)))
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+    else
+    {
+        return false;
+    }
+    return false;
+}
+} // namespace acpi_state
+
 ipmi_ret_t ipmi_app_set_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
                                          ipmi_request_t request,
                                          ipmi_response_t response,
                                          ipmi_data_len_t data_len,
                                          ipmi_context_t context)
 {
+    auto s = static_cast<uint8_t>(acpi_state::PowerState::unknown);
     ipmi_ret_t rc = IPMI_CC_OK;
+
+    sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
+
+    auto value = acpi_state::ACPIPowerState::ACPI::Unknown;
+
+    auto* req = reinterpret_cast<acpi_state::ACPIState*>(request);
+
+    if (*data_len != sizeof(acpi_state::ACPIState))
+    {
+        log<level::ERR>("set_acpi invalid len");
+        *data_len = 0;
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
     *data_len = 0;
 
-    log<level::DEBUG>("IPMI SET ACPI STATE Ignoring for now\n");
+    if (req->sysACPIState & acpi_state::stateChanged)
+    {
+        // set system power state
+        s = req->sysACPIState & ~acpi_state::stateChanged;
+
+        if (!acpi_state::isValidACPIState(
+                acpi_state::PowerStateType::sysPowerState, s))
+        {
+            log<level::ERR>("set_acpi_power sys invalid input",
+                            entry("S=%x", s));
+            return IPMI_CC_PARM_OUT_OF_RANGE;
+        }
+
+        // valid input
+        if (s == static_cast<uint8_t>(acpi_state::PowerState::noChange))
+        {
+            log<level::DEBUG>("No change for system power state");
+        }
+        else
+        {
+            auto found = std::find_if(
+                acpi_state::dbusToIPMI.begin(), acpi_state::dbusToIPMI.end(),
+                [&s](const auto& iter) {
+                    return (static_cast<uint8_t>(iter.second) == s);
+                });
+
+            value = found->first;
+
+            try
+            {
+                auto acpiObject =
+                    ipmi::getDbusObject(bus, acpi_state::acpiInterface);
+                ipmi::setDbusProperty(bus, acpiObject.second, acpiObject.first,
+                                      acpi_state::acpiInterface,
+                                      acpi_state::sysACPIProp,
+                                      convertForMessage(value));
+            }
+            catch (const InternalFailure& e)
+            {
+                log<level::ERR>("Failed in set ACPI system property",
+                                entry("EXCEPTION=%s", e.what()));
+                return IPMI_CC_UNSPECIFIED_ERROR;
+            }
+        }
+    }
+    else
+    {
+        log<level::DEBUG>("Do not change system power state");
+    }
+
+    if (req->devACPIState & acpi_state::stateChanged)
+    {
+        // set device power state
+        s = req->devACPIState & ~acpi_state::stateChanged;
+        if (!acpi_state::isValidACPIState(
+                acpi_state::PowerStateType::devPowerState, s))
+        {
+            log<level::ERR>("set_acpi_power dev invalid input",
+                            entry("S=%x", s));
+            return IPMI_CC_PARM_OUT_OF_RANGE;
+        }
+
+        // valid input
+        if (s == static_cast<uint8_t>(acpi_state::PowerState::noChange))
+        {
+            log<level::DEBUG>("No change for device power state");
+        }
+        else
+        {
+            auto found = std::find_if(
+                acpi_state::dbusToIPMI.begin(), acpi_state::dbusToIPMI.end(),
+                [&s](const auto& iter) {
+                    return (static_cast<uint8_t>(iter.second) == s);
+                });
+
+            value = found->first;
+
+            try
+            {
+                auto acpiObject =
+                    ipmi::getDbusObject(bus, acpi_state::acpiInterface);
+                ipmi::setDbusProperty(bus, acpiObject.second, acpiObject.first,
+                                      acpi_state::acpiInterface,
+                                      acpi_state::devACPIProp,
+                                      convertForMessage(value));
+            }
+            catch (const InternalFailure& e)
+            {
+                log<level::ERR>("Failed in set ACPI device property",
+                                entry("EXCEPTION=%s", e.what()));
+                return IPMI_CC_UNSPECIFIED_ERROR;
+            }
+        }
+    }
+    else
+    {
+        log<level::DEBUG>("Do not change device power state");
+    }
+
+    return rc;
+}
+
+ipmi_ret_t ipmi_app_get_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+                                         ipmi_request_t request,
+                                         ipmi_response_t response,
+                                         ipmi_data_len_t data_len,
+                                         ipmi_context_t context)
+{
+    ipmi_ret_t rc = IPMI_CC_OK;
+
+    auto* res = reinterpret_cast<acpi_state::ACPIState*>(response);
+
+    sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
+
+    *data_len = 0;
+
+    try
+    {
+        auto acpiObject = ipmi::getDbusObject(bus, acpi_state::acpiInterface);
+
+        auto sysACPIVal = ipmi::getDbusProperty(
+            bus, acpiObject.second, acpiObject.first, acpi_state::acpiInterface,
+            acpi_state::sysACPIProp);
+        auto sysACPI = acpi_state::ACPIPowerState::convertACPIFromString(
+            sysACPIVal.get<std::string>());
+        res->sysACPIState =
+            static_cast<uint8_t>(acpi_state::dbusToIPMI.at(sysACPI));
+
+        auto devACPIVal = ipmi::getDbusProperty(
+            bus, acpiObject.second, acpiObject.first, acpi_state::acpiInterface,
+            acpi_state::devACPIProp);
+        auto devACPI = acpi_state::ACPIPowerState::convertACPIFromString(
+            devACPIVal.get<std::string>());
+        res->devACPIState =
+            static_cast<uint8_t>(acpi_state::dbusToIPMI.at(devACPI));
+
+        *data_len = sizeof(acpi_state::ACPIState);
+    }
+    catch (const InternalFailure& e)
+    {
+        log<level::ERR>("Failed in get ACPI property");
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
     return rc;
 }
 
@@ -860,6 +1118,10 @@
     ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_ACPI, NULL,
                            ipmi_app_set_acpi_power_state, PRIVILEGE_ADMIN);
 
+    // <Get ACPI Power State>
+    ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_ACPI, NULL,
+                           ipmi_app_get_acpi_power_state, PRIVILEGE_ADMIN);
+
     // <Get Channel Access>
     ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_CHANNEL_ACCESS, NULL,
                            ipmi_get_channel_access, PRIVILEGE_USER);