Add support for power mode locking

This change adds support for preventing changes to the power mode based
on a mode setting lock. The default state of the lock is unlocked. The
state is changed from unlocked to locked using a dbus method call and
can only be unlocked by setting it back to the default state through a
factory reset.

Signed-off-by: Ben Tyner <ben.tyner@ibm.com>
Change-Id: I9d8fac5a6f74357efe36efd86c9f97776004385f
diff --git a/powermode.cpp b/powermode.cpp
index 5d2e258..e2f0837 100644
--- a/powermode.cpp
+++ b/powermode.cpp
@@ -1,13 +1,12 @@
 #include "powermode.hpp"
 
-#include "elog-errors.hpp"
-
 #include <fcntl.h>
 #include <fmt/core.h>
 #include <sys/ioctl.h>
 
 #include <com/ibm/Host/Target/server.hpp>
 #include <org/open_power/OCC/Device/error.hpp>
+#include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/log.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>
 #include <xyz/openbmc_project/Control/Power/Mode/server.hpp>
@@ -29,6 +28,9 @@
 
 using Mode = sdbusplus::xyz::openbmc_project::Control::Power::server::Mode;
 
+using NotAllowed = sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
+using Reason = xyz::openbmc_project::Common::NotAllowed::REASON;
+
 // Set the Master OCC
 void PowerMode::setMasterOcc(const std::string& masterOccPath)
 {
@@ -73,6 +75,24 @@
         auto modeEntryValue = modeEntry->second;
         propVal = std::get<std::string>(modeEntryValue);
         SysPwrMode newMode = convertStringToMode(propVal);
+
+        // If mode set was requested via direct dbus property change then we
+        // need to see if mode set is locked and ignore the request. We should
+        // not get to this path since the property change method should also be
+        // checking the mode lock status.
+        if (persistedData.getModeLock())
+        {
+            // Fix up the mode property since we are going to ignore this
+            // set mode request.
+            log<level::ERR>(
+                "PowerMode::modeChanged: mode property changed while locked");
+            SysPwrMode currentMode;
+            uint16_t oemModeData;
+            getMode(currentMode, oemModeData);
+            updateDbusMode(currentMode);
+            return;
+        }
+
         if (newMode != SysPwrMode::NO_CHANGE)
         {
             // Update persisted data with new mode
@@ -87,9 +107,33 @@
     }
 }
 
+// Set the state of power mode lock. Writing persistent data via dbus method.
+bool PowerMode::powerModeLock()
+{
+    log<level::INFO>("PowerMode::powerModeLock: locking mode change");
+    persistedData.updateModeLock(true); // write persistent data
+    return true;
+}
+
+// Get the state of power mode. Reading persistent data via dbus method.
+bool PowerMode::powerModeLockStatus()
+{
+    bool status = persistedData.getModeLock(); // read persistent data
+    log<level::INFO>(fmt::format("PowerMode::powerModeLockStatus: {}",
+                                 status ? "locked" : "unlocked")
+                         .c_str());
+    return status;
+}
+
 // Called from OCC PassThrough interface (via CE login / BMC command line)
 bool PowerMode::setMode(const SysPwrMode newMode, const uint16_t oemModeData)
 {
+    if (persistedData.getModeLock())
+    {
+        log<level::INFO>("PowerMode::setMode: mode change blocked");
+        return false;
+    }
+
     if (updateDbusMode(newMode) == false)
     {
         // Unsupported mode
@@ -593,8 +637,9 @@
     {
         log<level::INFO>(
             fmt::format(
-                "OccPersistData: Mode: 0x{:02X}, OEM Mode Data: {} (0x{:04X})",
-                modeData.mode, modeData.oemModeData, modeData.oemModeData)
+                "OccPersistData: Mode: 0x{:02X}, OEM Mode Data: {} (0x{:04X} Locked{})",
+                modeData.mode, modeData.oemModeData, modeData.oemModeData,
+                modeData.modeLocked)
                 .c_str());
     }
     if (modeData.ipsInitialized)
@@ -1060,6 +1105,22 @@
 
     return;
 }
+
+// overrides read/write to powerMode dbus property.
+Mode::PowerMode PowerMode::powerMode(Mode::PowerMode value)
+{
+    if (persistedData.getModeLock())
+    {
+        log<level::INFO>("PowerMode::powerMode: mode property change blocked");
+        elog<NotAllowed>(xyz::openbmc_project::Common::NotAllowed::REASON(
+            "mode change not allowed due to lock"));
+        return value;
+    }
+    else
+    {
+        return Mode::powerMode(value);
+    }
+}
 #endif
 
 /*  Set dbus property to SAFE mode(true) or clear(false) only if different */
diff --git a/powermode.hpp b/powermode.hpp
index f0aca51..4cbbf15 100644
--- a/powermode.hpp
+++ b/powermode.hpp
@@ -74,6 +74,7 @@
     uint16_t ipsEnterTime = 0;
     uint8_t ipsExitUtil = 0;
     uint16_t ipsExitTime = 0;
+    bool modeLocked = false;
 
     /** @brief Function specifying data to archive for cereal.
      */
@@ -81,7 +82,8 @@
     void serialize(Archive& archive)
     {
         archive(modeInitialized, mode, oemModeData, ipsInitialized, ipsEnabled,
-                ipsEnterUtil, ipsEnterTime, ipsExitUtil, ipsExitTime);
+                ipsEnterUtil, ipsEnterTime, ipsExitUtil, ipsExitTime,
+                modeLocked);
     }
 };
 
@@ -118,6 +120,15 @@
         save();
     }
 
+    /** @brief Save Power Mode Lock value to persistent file
+     *
+     *  @param[in] modeLock - desired System Power Mode Lock
+     */
+    void updateModeLock(const bool modeLock)
+    {
+        modeData.modeLocked = modeLock;
+        save();
+    }
     /** @brief Write Idle Power Saver parameters to persistent file
      *
      *  @param[in] enabled - Idle Power Save status (true = enabled)
@@ -184,6 +195,12 @@
         return true;
     }
 
+    /** @brief Return persisted mode lock */
+    bool getModeLock()
+    {
+        return modeData.modeLocked;
+    }
+
     /** @brief Return true if the power mode is available */
     bool modeAvailable()
     {
@@ -285,6 +302,18 @@
      */
     bool initPersistentData();
 
+    /** @brief Set the power mode lock (dbus method)
+     *
+     * @return true if successful
+     */
+    bool powerModeLock();
+
+    /** @brief Get the power mode lock status (dbus method)
+     *
+     * @return true if locked
+     */
+    bool powerModeLockStatus();
+
     /** @brief Set the current power mode property
      *
      * @param[in] newMode     - desired system power mode
@@ -335,6 +364,14 @@
     /** @brief Set dbus property to SAFE Mode(true) or clear SAFE Mode(false)*/
     void updateDbusSafeMode(const bool safeMode);
 
+    /** @brief override the set/get MODE function
+     *
+     *  @param[in] value - Intended value
+     *
+     *  @return          - the value or Updated value of the property
+     */
+    Base::Mode::PowerMode powerMode(Base::Mode::PowerMode value) override;
+
   private:
     /** @brief OCC manager object */
     const Manager& manager;