Send OCC mode change and IPS parms after reaching active state

Change is only applicable for POWER10 PowerVM systems.
Send mode change requests to OCC when PowerMode is changed.
Retry OCC command once, if a valid response is not received.

Tested: built openpower-occ-control successfully and verified
functionality on hardware.

Change-Id: If375f8abfdb2ad82937b8744096c2ccc471263d2
Signed-off-by: Chris Cain <cjcain@us.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 407815a..b82581c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -72,6 +72,7 @@
 openpower_occ_control_LDFLAGS = $(generic_ld_flags)
 
 include pldm.mk
+include powermode.mk
 
 org/open_power/OCC/Device/error.hpp: ${top_srcdir}/org/open_power/OCC/Device.errors.yaml
 	@mkdir -p `dirname $@`
diff --git a/configure.ac b/configure.ac
index 0b96f56..892378c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -86,6 +86,18 @@
         ]
         AC_SUBST([CPPFLAGS], [$cpp_flags])
     )
+
+    AC_ARG_ENABLE([power10-support],
+        AS_HELP_STRING([--with-power10-support], [To specify if system has power10 support])
+    )
+    AM_CONDITIONAL([POWER10_SUPPORT], [test "$with_power10_support" == "yes"])
+    AS_IF([test "x$with_power10_support" == "xyes"],
+        AC_MSG_NOTICE([Enabling POWER10])
+        [
+            cpp_flags="$CPPFLAGS -DPOWER10"
+        ]
+        AC_SUBST([CPPFLAGS], [$cpp_flags])
+    )
 ])
 
 AC_ARG_VAR(OCC_CONTROL_BUSNAME, [The Dbus busname to own])
diff --git a/occ_command.cpp b/occ_command.cpp
index d07c06f..5d693e7 100644
--- a/occ_command.cpp
+++ b/occ_command.cpp
@@ -139,107 +139,139 @@
 #else
     log<level::DEBUG>("OccCommand::send: calling write()");
 #endif
-    auto rc = write(fd, command.data(), command.size());
-    if ((rc < 0) || (rc != (int)command.size()))
-    {
-        const int write_errno = errno;
-        log<level::ERR>("OccCommand::send: write failed");
-        // This would log and terminate since its not handled.
-        elog<WriteFailure>(
-            phosphor::logging::org::open_power::OCC::Device::WriteFailure::
-                CALLOUT_ERRNO(write_errno),
-            phosphor::logging::org::open_power::OCC::Device::WriteFailure::
-                CALLOUT_DEVICE_PATH(devicePath.c_str()));
-    }
-    else
-    {
-        log<level::DEBUG>("OccCommand::send: write succeeded");
-    }
 
-    // Now read the response. This would be the content of occ-sram
-    while (1)
+    int retries = 1; // Allow a retry if a command fails to get valid response
+    do
     {
-        uint8_t data{};
-        auto len = read(fd, &data, sizeof(data));
-        const int read_errno = errno;
-        if (len > 0)
+        auto rc = write(fd, command.data(), command.size());
+        if ((rc < 0) || (rc != (int)command.size()))
         {
-            response.emplace_back(data);
-        }
-        else if (len < 0 && read_errno == EAGAIN)
-        {
-            // We may have data coming still.
-            // This driver does not need a sleep for a retry.
-            continue;
-        }
-        else if (len == 0)
-        {
-            log<level::DEBUG>("OccCommand::send: read completed");
-            // We have read all that we can.
-            status = CmdStatus::SUCCESS;
-            break;
-        }
-        else
-        {
-            log<level::ERR>("OccCommand::send: read failed");
+            const int write_errno = errno;
+            log<level::ERR>("OccCommand::send: write failed");
             // This would log and terminate since its not handled.
-            elog<ReadFailure>(
-                phosphor::logging::org::open_power::OCC::Device::ReadFailure::
-                    CALLOUT_ERRNO(read_errno),
-                phosphor::logging::org::open_power::OCC::Device::ReadFailure::
+            elog<WriteFailure>(
+                phosphor::logging::org::open_power::OCC::Device::WriteFailure::
+                    CALLOUT_ERRNO(write_errno),
+                phosphor::logging::org::open_power::OCC::Device::WriteFailure::
                     CALLOUT_DEVICE_PATH(devicePath.c_str()));
         }
-    }
+        else
+        {
+            log<level::DEBUG>("OccCommand::send: write succeeded");
+        }
 
-    if (response.size() > 2)
-    {
+        // Now read the response. This would be the content of occ-sram
+        while (1)
+        {
+            uint8_t data{};
+            auto len = read(fd, &data, sizeof(data));
+            const int read_errno = errno;
+            if (len > 0)
+            {
+                response.emplace_back(data);
+            }
+            else if (len < 0 && read_errno == EAGAIN)
+            {
+                // We may have data coming still.
+                // This driver does not need a sleep for a retry.
+                continue;
+            }
+            else if (len == 0)
+            {
+                log<level::DEBUG>("OccCommand::send: read completed");
+                // We have read all that we can.
+                status = CmdStatus::SUCCESS;
+                break;
+            }
+            else
+            {
+                log<level::ERR>("OccCommand::send: read failed");
+                // This would log and terminate since its not handled.
+                elog<ReadFailure>(
+                    phosphor::logging::org::open_power::OCC::Device::
+                        ReadFailure::CALLOUT_ERRNO(read_errno),
+                    phosphor::logging::org::open_power::OCC::Device::
+                        ReadFailure::CALLOUT_DEVICE_PATH(devicePath.c_str()));
+            }
+        }
+
+        if (response.size() > 2)
+        {
 #ifdef TRACE_PACKETS
-        log<level::INFO>(
-            fmt::format(
-                "OCC{}: Received 0x{:02X} response (length={} w/checksum)",
-                occInstance, cmd_type, response.size())
-                .c_str());
-        dump_hex(response, 64);
+            log<level::INFO>(
+                fmt::format(
+                    "OCC{}: Received 0x{:02X} response (length={} w/checksum)",
+                    occInstance, cmd_type, response.size())
+                    .c_str());
+            dump_hex(response, 64);
 #endif
 
-        // Validate checksum (last 2 bytes of response)
-        const unsigned int csumIndex = response.size() - 2;
-        const uint32_t rspChecksum =
-            (response[csumIndex] << 8) + response[csumIndex + 1];
-        uint32_t calcChecksum = 0;
-        for (unsigned int index = 0; index < csumIndex; ++index)
-        {
-            calcChecksum += response[index];
-        }
-        while (calcChecksum > 0xFFFF)
-        {
-            calcChecksum = (calcChecksum & 0xFFFF) + (calcChecksum >> 16);
-        }
-        if (calcChecksum != rspChecksum)
-        {
-            log<level::ERR>(fmt::format("OCC{}: Checksum Mismatch: response "
-                                        "0x{:04X}, calculated 0x{:04X}",
-                                        occInstance, rspChecksum, calcChecksum)
-                                .c_str());
-            dump_hex(response);
-            status = CmdStatus::INVALID_CHECKSUM;
+            // Validate checksum (last 2 bytes of response)
+            const unsigned int csumIndex = response.size() - 2;
+            const uint32_t rspChecksum =
+                (response[csumIndex] << 8) + response[csumIndex + 1];
+            uint32_t calcChecksum = 0;
+            for (unsigned int index = 0; index < csumIndex; ++index)
+            {
+                calcChecksum += response[index];
+            }
+            while (calcChecksum > 0xFFFF)
+            {
+                calcChecksum = (calcChecksum & 0xFFFF) + (calcChecksum >> 16);
+            }
+            if (calcChecksum != rspChecksum)
+            {
+                log<level::ERR>(
+                    fmt::format("OCC{}: Checksum Mismatch: response "
+                                "0x{:04X}, calculated 0x{:04X}",
+                                occInstance, rspChecksum, calcChecksum)
+                        .c_str());
+                dump_hex(response);
+                status = CmdStatus::INVALID_CHECKSUM;
+            }
+            else
+            {
+                // Validate response was for the specified command
+                if (command[0] == response[1])
+                {
+                    // Valid response received
+
+                    // Strip off 2 byte checksum
+                    response.pop_back();
+                    response.pop_back();
+                    break;
+                }
+                else
+                {
+                    log<level::ERR>(
+                        fmt::format(
+                            "OccCommand::send: Response command mismatch "
+                            "(sent: "
+                            "0x{:02X}, rsp: 0x{:02X}, rsp seq#: 0x{:02X}",
+                            command[0], response[1], response[0])
+                            .c_str());
+                    dump_hex(response, 64);
+                }
+            }
         }
         else
         {
-            // Strip off 2 byte checksum
-            response.pop_back();
-            response.pop_back();
+            log<level::ERR>(
+                fmt::format(
+                    "OccCommand::send: Invalid OCC{} response length: {}",
+                    occInstance, response.size())
+                    .c_str());
+            status = CmdStatus::FAILURE;
+            dump_hex(response);
         }
-    }
-    else
-    {
-        log<level::ERR>(
-            fmt::format("OccCommand::send: Invalid OCC{} response length: {}",
-                        occInstance, response.size())
-                .c_str());
-        status = CmdStatus::FAILURE;
-        dump_hex(response);
-    }
+
+        if (retries > 0)
+        {
+            log<level::ERR>("OccCommand::send: Command will be retried");
+            response.clear();
+        }
+
+    } while (retries-- > 0);
 
     closeDevice();
 
diff --git a/occ_command.hpp b/occ_command.hpp
index 2c925f6..42962d9 100644
--- a/occ_command.hpp
+++ b/occ_command.hpp
@@ -16,6 +16,60 @@
 // For waiting on signals
 namespace sdbusRule = sdbusplus::bus::match::rules;
 
+enum class CmdType
+{
+    POLL = 0x00,
+    CLEAR_ERROR_LOG = 0x12,
+    SET_MODE_AND_STATE = 0x20,
+    SET_CONFIG_DATA = 0x21,
+    SET_USER_PCAP = 0x22,
+    RESET_PREP = 0x25,
+    DEBUG_PASS_THROUGH = 0x40,
+    AME_PASS_THROUGH = 0x41,
+    GET_FIELD_DEBUG_DATA = 0x42,
+    MFG_TEST = 0x53
+};
+
+enum class OccState
+{
+    NO_CHANGE = 0x00,
+    STANDBY = 0x01,
+    OBSERVATION = 0x02,
+    ACTIVE = 0x03,
+    SAFE = 0x04,
+    CHARACTERIZATION = 0x05,
+};
+
+enum class SysPwrMode
+{
+    NO_CHANGE = 0,
+    DISABLE = 0x01,      // Disable / Static Base Frequencey
+    SFP = 0x03,          // Static Frequency Point (requires freqPt)
+    SAFE = 0x04,         // reported when system is in SAFE mode (not settable)
+    POWER_SAVING = 0x05, // Static Power Saving
+    DYNAMIC_PERF = 0x0A, // Dynamic / Balanced Performance
+    FFO = 0x0B,          // Fixed Frequency Override (requires freqPt)
+    MAX_PERF = 0x0C      // Maximum Performance
+};
+
+// Only some of the SysPwrModes are currently supported and allowed to be set
+#define VALID_POWER_MODE_SETTING(mode)                                         \
+    ((mode == SysPwrMode::DISABLE) || (mode == SysPwrMode::POWER_SAVING) ||    \
+     (mode == SysPwrMode::DYNAMIC_PERF) || (mode == SysPwrMode::MAX_PERF))
+
+enum class RspStatus
+{
+    SUCCESS = 0x00,
+    CONDITIONAL_SUCCESS = 0x01,
+    INVALID_COMMAND = 0x11,
+    INVALID_COMMAND_LENGTH = 0x12,
+    INVALID_DATA_FIELD = 0x13,
+    CHECKSUM_FAILURE = 0x14,
+    INTERNAL_ERROR = 0x15,
+    PRESENT_STATE_PROHIBITS = 0x16,
+    COMMAND_IN_PROGRESS = 0xFF
+};
+
 enum class CmdStatus
 {
     SUCCESS,
diff --git a/occ_device.hpp b/occ_device.hpp
index 937c6dc..50dd11d 100644
--- a/occ_device.hpp
+++ b/occ_device.hpp
@@ -140,6 +140,9 @@
      */
     static std::string getPathBack(const fs::path& path);
 
+    /** @brief Returns true if device represents the master OCC */
+    bool master() const;
+
   private:
     /** @brief Config value to be used to do bind and unbind */
     const std::string config;
@@ -187,9 +190,6 @@
         return;
     }
 
-    /** @brief Returns if device represents the master OCC */
-    bool master() const;
-
     /** @brief callback for the proc temp throttle event
      *
      *  @param[in] error - True if an error is reported, false otherwise
diff --git a/occ_manager.cpp b/occ_manager.cpp
index 4b209da..3ebd90a 100644
--- a/occ_manager.cpp
+++ b/occ_manager.cpp
@@ -68,6 +68,15 @@
         pcap = std::make_unique<open_power::occ::powercap::PowerCap>(
             *statusObjects.front());
     }
+
+#ifdef POWER10
+    // Create the power mode monitor object for master occ (0)
+    if (!pmode)
+    {
+        pmode = std::make_unique<open_power::occ::powermode::PowerMode>(
+            *statusObjects.front());
+    }
+#endif
 }
 
 void Manager::statusCallBack(bool status)
@@ -132,6 +141,10 @@
     // The first device is master occ
     pcap = std::make_unique<open_power::occ::powercap::PowerCap>(
         *statusObjects.front(), occMasterName);
+#ifdef POWER10
+    pmode = std::make_unique<open_power::occ::powermode::PowerMode>(
+        *statusObjects.front());
+#endif
 }
 #endif
 
diff --git a/occ_manager.hpp b/occ_manager.hpp
index c4218c9..ece7fb7 100644
--- a/occ_manager.hpp
+++ b/occ_manager.hpp
@@ -7,6 +7,9 @@
 #endif
 #include "powercap.hpp"
 #include "utils.hpp"
+#ifdef POWER10
+#include "powermode.hpp"
+#endif
 
 #include <cstring>
 #include <functional>
@@ -119,6 +122,11 @@
     /** @brief Power cap monitor and occ notification object */
     std::unique_ptr<open_power::occ::powercap::PowerCap> pcap;
 
+#ifdef POWER10
+    /** @brief Power mode monitor and notification object */
+    std::unique_ptr<open_power::occ::powermode::PowerMode> pmode;
+#endif
+
     /** @brief sbdbusplus match objects */
     std::vector<sdbusplus::bus::match_t> cpuMatches;
 
diff --git a/occ_status.cpp b/occ_status.cpp
index f4bf8a9..1ac5212 100644
--- a/occ_status.cpp
+++ b/occ_status.cpp
@@ -1,15 +1,21 @@
 #include "occ_status.hpp"
 
 #include "occ_sensor.hpp"
+#include "powermode.hpp"
 #include "utils.hpp"
 
 #include <fmt/core.h>
 
+#ifdef POWER10
+#include <com/ibm/Host/Target/server.hpp>
+#endif
 #include <phosphor-logging/log.hpp>
+
 namespace open_power
 {
 namespace occ
 {
+
 using namespace phosphor::logging;
 
 // Handles updates to occActive property
@@ -135,8 +141,6 @@
 // status of the executed command
 void Status::hostControlEvent(sdbusplus::message::message& msg)
 {
-    using namespace phosphor::logging;
-
     std::string cmdCompleted{};
     std::string cmdStatus{};
 
@@ -186,6 +190,14 @@
                             instance, state)
                     .c_str());
             lastState = state;
+
+#ifdef POWER10
+            if ((OccState(state) == OccState::ACTIVE) && (device.master()))
+            {
+                // Kernel detected that the master OCC went to active state
+                occsWentActive();
+            }
+#endif
         }
         file.close();
     }
@@ -200,5 +212,265 @@
     }
 }
 
+#ifdef POWER10
+// Check if Hypervisor target is PowerVM
+bool Status::isPowerVM()
+{
+    using namespace open_power::occ::powermode;
+    namespace Hyper = sdbusplus::com::ibm::Host::server;
+    constexpr auto HYPE_PATH = "/com/ibm/host0/hypervisor";
+    constexpr auto HYPE_INTERFACE = "com.ibm.Host.Target";
+    constexpr auto HYPE_PROP = "Target";
+
+    bool powerVmTarget = false;
+
+    // This will throw exception on failure
+    auto& bus = utils::getBus();
+    auto service = utils::getService(HYPE_PATH, HYPE_INTERFACE);
+    auto method = bus.new_method_call(service.c_str(), HYPE_PATH,
+                                      "org.freedesktop.DBus.Properties", "Get");
+    method.append(HYPE_INTERFACE, HYPE_PROP);
+    auto reply = bus.call(method);
+
+    std::variant<std::string> hyperEntryValue;
+    reply.read(hyperEntryValue);
+    auto propVal = std::get<std::string>(hyperEntryValue);
+    if (Hyper::Target::convertHypervisorFromString(propVal) ==
+        Hyper::Target::Hypervisor::PowerVM)
+    {
+        powerVmTarget = true;
+    }
+
+    log<level::DEBUG>(
+        fmt::format("Status::isPowerVM returning {}", powerVmTarget).c_str());
+
+    return powerVmTarget;
+}
+
+// Get the requested power mode
+SysPwrMode Status::getMode()
+{
+    using namespace open_power::occ::powermode;
+    SysPwrMode pmode = SysPwrMode::NO_CHANGE;
+
+    // This will throw exception on failure
+    auto& bus = utils::getBus();
+    auto service = utils::getService(PMODE_PATH, PMODE_INTERFACE);
+    auto method = bus.new_method_call(service.c_str(), PMODE_PATH,
+                                      "org.freedesktop.DBus.Properties", "Get");
+    method.append(PMODE_INTERFACE, POWER_MODE_PROP);
+    auto reply = bus.call(method);
+
+    std::variant<std::string> stateEntryValue;
+    reply.read(stateEntryValue);
+    auto propVal = std::get<std::string>(stateEntryValue);
+    pmode = powermode::convertStringToMode(propVal);
+
+    log<level::DEBUG>(
+        fmt::format("Status::getMode returning {}", pmode).c_str());
+
+    return pmode;
+}
+
+// Special processing that needs to happen once the OCCs change to ACTIVE state
+void Status::occsWentActive()
+{
+    CmdStatus status = CmdStatus::SUCCESS;
+
+    status = sendModeChange();
+    if (status != CmdStatus::SUCCESS)
+    {
+        log<level::ERR>(fmt::format("Status::occsWentActive: OCC mode "
+                                    "change failed with status {}",
+                                    status)
+                            .c_str());
+    }
+
+    status = sendIpsData();
+    if (status != CmdStatus::SUCCESS)
+    {
+        log<level::ERR>(
+            fmt::format(
+                "Status::occsWentActive: Sending Idle Power Save Config data"
+                " failed with status {}",
+                status)
+                .c_str());
+    }
+}
+
+// Send mode change request to the master OCC
+CmdStatus Status::sendModeChange()
+{
+    CmdStatus status = CmdStatus::FAILURE;
+
+    if (!device.master())
+    {
+        log<level::ERR>(
+            fmt::format("Status::sendModeChange: MODE CHANGE does not "
+                        "get sent to slave OCC{}",
+                        instance)
+                .c_str());
+        return status;
+    }
+    if (!isPowerVM())
+    {
+        // Mode change is only supported on PowerVM systems
+        log<level::DEBUG>("Status::sendModeChange: MODE CHANGE does not "
+                          "get sent on non-PowerVM systems");
+        return CmdStatus::SUCCESS;
+    }
+
+    const SysPwrMode newMode = getMode();
+
+    if (VALID_POWER_MODE_SETTING(newMode))
+    {
+        std::vector<std::uint8_t> cmd, rsp;
+        cmd.push_back(uint8_t(CmdType::SET_MODE_AND_STATE));
+        cmd.push_back(0x00); // Data Length (2 bytes)
+        cmd.push_back(0x06);
+        cmd.push_back(0x30); // Data (Version)
+        cmd.push_back(uint8_t(OccState::NO_CHANGE));
+        cmd.push_back(uint8_t(newMode));
+        cmd.push_back(0x00); // Mode Data (Freq Point)
+        cmd.push_back(0x00); //
+        cmd.push_back(0x00); // reserved
+        log<level::INFO>(fmt::format("Status::sendModeChange: SET_MODE({}) "
+                                     "command to OCC{} ({} bytes)",
+                                     newMode, instance, cmd.size())
+                             .c_str());
+        status = occCmd.send(cmd, rsp);
+        if (status == CmdStatus::SUCCESS)
+        {
+            if (rsp.size() == 5)
+            {
+                if (RspStatus::SUCCESS == RspStatus(rsp[2]))
+                {
+                    log<level::DEBUG>("Status::sendModeChange: - Mode change "
+                                      "completed successfully");
+                }
+                else
+                {
+                    log<level::ERR>(
+                        fmt::format("Status::sendModeChange: SET MODE "
+                                    "failed with status 0x{:02X}",
+                                    rsp[2])
+                            .c_str());
+                }
+            }
+            else
+            {
+                log<level::ERR>(
+                    "Status::sendModeChange: INVALID SET MODE response");
+                dump_hex(rsp);
+            }
+        }
+        else
+        {
+            if (status == CmdStatus::OPEN_FAILURE)
+            {
+                log<level::WARNING>(
+                    "Status::sendModeChange: OCC not active yet");
+            }
+            else
+            {
+                log<level::ERR>("Status::sendModeChange: SET_MODE FAILED!");
+            }
+        }
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format(
+                "Status::sendModeChange: Unable to set power mode to {}",
+                newMode)
+                .c_str());
+    }
+
+    return status;
+}
+
+// Send Idle Power Saver config data to the master OCC
+CmdStatus Status::sendIpsData()
+{
+    CmdStatus status = CmdStatus::FAILURE;
+
+    if (!device.master())
+    {
+        log<level::ERR>(
+            fmt::format("Status::sendIpsData: SET_CFG_DATA[IPS] does not "
+                        "get sent to slave OCC{}",
+                        instance)
+                .c_str());
+        return status;
+    }
+    if (!isPowerVM())
+    {
+        // Idle Power Saver data is only supported on PowerVM systems
+        log<level::DEBUG>("Status::sendIpsData: SET_CFG_DATA[IPS] does not "
+                          "get sent on non-PowerVM systems");
+        return CmdStatus::SUCCESS;
+    }
+
+    std::vector<std::uint8_t> cmd, rsp;
+    cmd.push_back(uint8_t(CmdType::SET_CONFIG_DATA));
+    cmd.push_back(0x00); // Data Length (2 bytes)
+    cmd.push_back(0x09);
+    // Data:
+    cmd.push_back(0x11); // Config Format: IPS Settings
+    cmd.push_back(0x00); // Version
+    cmd.push_back(0x01); // IPS Enable: enabled
+    cmd.push_back(0x00); // Enter Delay Time (240s)
+    cmd.push_back(0xF0); //
+    cmd.push_back(0x08); // Enter Utilization (8%)
+    cmd.push_back(0x00); // Exit Delay Time (10s)
+    cmd.push_back(0x0A); //
+    cmd.push_back(0x0C); // Exit Utilization (12%)
+    log<level::INFO>(fmt::format("Status::sendIpsData: SET_CFG_DATA[IPS] "
+                                 "command to OCC{} ({} bytes)",
+                                 instance, cmd.size())
+                         .c_str());
+    status = occCmd.send(cmd, rsp);
+    if (status == CmdStatus::SUCCESS)
+    {
+        if (rsp.size() == 5)
+        {
+            if (RspStatus::SUCCESS == RspStatus(rsp[2]))
+            {
+                log<level::DEBUG>("Status::sendIpsData: - SET_CFG_DATA[IPS] "
+                                  "completed successfully");
+            }
+            else
+            {
+                log<level::ERR>(
+                    fmt::format("Status::sendIpsData: SET_CFG_DATA[IPS] "
+                                "failed with status 0x{:02X}",
+                                rsp[2])
+                        .c_str());
+            }
+        }
+        else
+        {
+            log<level::ERR>(
+                "Status::sendIpsData: INVALID SET_CFG_DATA[IPS] response");
+            dump_hex(rsp);
+        }
+    }
+    else
+    {
+        if (status == CmdStatus::OPEN_FAILURE)
+        {
+            log<level::WARNING>("Status::sendIpsData: OCC not active yet");
+        }
+        else
+        {
+            log<level::ERR>("Status::sendIpsData: SET_CFG_DATA[IPS] FAILED!");
+        }
+    }
+
+    return status;
+}
+
+#endif // POWER10
+
 } // namespace occ
 } // namespace open_power
diff --git a/occ_status.hpp b/occ_status.hpp
index df2ef51..415e0b7 100644
--- a/occ_status.hpp
+++ b/occ_status.hpp
@@ -149,6 +149,16 @@
     /** @brief Read OCC state (will trigger kernel to poll the OCC) */
     void readOccState();
 
+#ifdef POWER10
+    /** @brief Handle additional tasks when the OCCs reach active state */
+    void occsWentActive();
+
+    /** @brief Send mode change command to the master OCC
+     *  @return SUCCESS on success
+     */
+    CmdStatus sendModeChange();
+#endif // POWER10
+
   private:
     /** @brief OCC dbus object path */
     std::string path;
@@ -206,6 +216,23 @@
         return (path.empty() ? 0 : path.back() - '0');
     }
 
+#ifdef POWER10
+    /** @brief Query the current Hypervisor target
+     * @return true if the current Hypervisor target is PowerVM
+     */
+    bool isPowerVM();
+
+    /** @brief Get the requested power mode property
+     * @return Power mode
+     */
+    SysPwrMode getMode();
+
+    /** @brief Send Idle Power Saver config data to the master OCC
+     *  @return SUCCESS on success
+     */
+    CmdStatus sendIpsData();
+#endif // POWER10
+
     /** @brief Override the sensor name with name from the definition.
      *  @param[in]  estimatedPath - Estimated OCC Dbus object path
      *  @return  Fixed OCC DBus object path
diff --git a/powermode.cpp b/powermode.cpp
new file mode 100644
index 0000000..af739cb
--- /dev/null
+++ b/powermode.cpp
@@ -0,0 +1,87 @@
+#include <fmt/core.h>
+
+#include <cassert>
+#include <phosphor-logging/log.hpp>
+#include <powermode.hpp>
+#include <regex>
+#include <xyz/openbmc_project/Control/Power/Mode/server.hpp>
+
+namespace open_power
+{
+namespace occ
+{
+namespace powermode
+{
+
+using namespace phosphor::logging;
+using Mode = sdbusplus::xyz::openbmc_project::Control::Power::server::Mode;
+
+void PowerMode::modeChanged(sdbusplus::message::message& msg)
+{
+    if (!occStatus.occActive())
+    {
+        // Nothing to  do
+        return;
+    }
+
+    SysPwrMode pmode = SysPwrMode::NO_CHANGE;
+
+    std::map<std::string, std::variant<std::string>> properties{};
+    std::string interface;
+    std::string propVal;
+    msg.read(interface, properties);
+    const auto modeEntry = properties.find(POWER_MODE_PROP);
+    if (modeEntry != properties.end())
+    {
+        auto modeEntryValue = modeEntry->second;
+        propVal = std::get<std::string>(modeEntryValue);
+        pmode = convertStringToMode(propVal);
+
+        if (pmode != SysPwrMode::NO_CHANGE)
+        {
+            log<level::INFO>(
+                fmt::format("Power Mode Change Requested: {}", propVal)
+                    .c_str());
+
+            // Trigger mode change to OCC
+            occStatus.sendModeChange();
+        }
+    }
+
+    return;
+}
+
+// Convert PowerMode string to OCC SysPwrMode
+SysPwrMode convertStringToMode(const std::string& i_modeString)
+{
+    SysPwrMode pmode = SysPwrMode::NO_CHANGE;
+
+    Mode::PowerMode mode = Mode::convertPowerModeFromString(i_modeString);
+    if (mode == Mode::PowerMode::MaximumPerformance)
+    {
+        pmode = SysPwrMode::MAX_PERF;
+    }
+    else if (mode == Mode::PowerMode::PowerSaving)
+    {
+        pmode = SysPwrMode::POWER_SAVING;
+    }
+    else if (mode == Mode::PowerMode::Static)
+    {
+        pmode = SysPwrMode::DISABLE;
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format("convertStringToMode: Invalid Power Mode specified: {}",
+                        i_modeString)
+                .c_str());
+    }
+
+    return pmode;
+}
+
+} // namespace powermode
+
+} // namespace occ
+
+} // namespace open_power
diff --git a/powermode.hpp b/powermode.hpp
new file mode 100644
index 0000000..472fd3e
--- /dev/null
+++ b/powermode.hpp
@@ -0,0 +1,79 @@
+#pragma once
+
+#ifdef POWER10
+#include "config.h"
+
+#include "occ_status.hpp"
+
+#include <experimental/filesystem>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+namespace open_power
+{
+namespace occ
+{
+namespace powermode
+{
+
+constexpr auto PMODE_PATH = "/xyz/openbmc_project/control/host0/power_mode";
+constexpr auto PMODE_INTERFACE = "xyz.openbmc_project.Control.Power.Mode";
+constexpr auto POWER_MODE_PROP = "PowerMode";
+
+/** @brief Convert power mode string to OCC SysPwrMode value
+ *
+ * @param[in] i_modeString - power mode string
+ *
+ * @return  SysPwrMode or SysPwrMode::NO_CHANGE if not found
+ */
+SysPwrMode convertStringToMode(const std::string& i_modeString);
+
+/** @class PowerMode
+ *  @brief Monitors for changes to the power mode and notifies occ
+ *
+ *  The customer power mode is provided to the OCC by host TMGT when the occ
+ *  first goes active or is reset.  This code is responsible for sending
+ *  the power mode to the OCC if the mode is changed while the occ is active.
+ */
+
+class PowerMode
+{
+  public:
+    /** @brief PowerMode object to inform occ of changes to mode
+     *
+     * This object will monitor for changes to the power mode setting.
+     * If a change is detected, and the occ is active, then this object will
+     * notify the OCC of the change.
+     *
+     * @param[in] occStatus - The occ status object
+     */
+    PowerMode(Status& occStatus) :
+        occStatus(occStatus),
+        pmodeMatch(utils::getBus(),
+                   sdbusplus::bus::match::rules::propertiesChanged(
+                       PMODE_PATH, PMODE_INTERFACE),
+                   [this](auto& msg) { this->modeChanged(msg); }){};
+
+  private:
+    /** @brief Callback for pmode setting changes
+     *
+     * Process change and inform OCC
+     *
+     * @param[in]  msg       - Data associated with pmode change signal
+     *
+     */
+    void modeChanged(sdbusplus::message::message& msg);
+
+    /* @brief OCC Status object */
+    Status& occStatus;
+
+    /** @brief Used to subscribe to dbus pmode property changes **/
+    sdbusplus::bus::match_t pmodeMatch;
+};
+
+} // namespace powermode
+
+} // namespace occ
+
+} // namespace open_power
+#endif
diff --git a/powermode.mk b/powermode.mk
new file mode 100644
index 0000000..ffa3aa9
--- /dev/null
+++ b/powermode.mk
@@ -0,0 +1,8 @@
+if POWER10_SUPPORT

+

+noinst_HEADERS += \

+	powermode.hpp

+libocc_control_la_SOURCES += \

+	powermode.cpp

+

+endif