Add CapLimits dbus interface to occ-control app

The power cap limits were previously hosted by Settings, but those are
system settings and the limits are not really settings (users can not
adjust these). This will allow occ-control to update these limits as
required but makes them readonly on dbus.
occ-control will now be persisting these limits.

Tested on Rainier:

'''
=> busctl -l introspect org.open_power.OCC.Control /xyz/openbmc_project/control/host0/power_cap_limits
...
xyz.openbmc_project.Control.Power.CapLimits interface -         -            -
.MaxPowerCapValue                           property  u         2777         emits-change
.MinPowerCapValue                           property  u         1286         emits-change
.MinSoftPowerCapValue                       property  u         556          emits-change

=> hexdump /var/lib//openpower-occ-control/powerCapData
0000000 0001 0000 0001 0000 022c 0000 0506 0000
0000010 0ad9 0000
0000014
'''

Change-Id: I75fd98be18c884961f417cc53213b9cb06a82947
Signed-off-by: Chris Cain <cjcain@us.ibm.com>
diff --git a/powercap.cpp b/powercap.cpp
index 7ccc6d9..573024c 100644
--- a/powercap.cpp
+++ b/powercap.cpp
@@ -18,12 +18,85 @@
 
 constexpr auto POWER_CAP_PROP = "PowerCap";
 constexpr auto POWER_CAP_ENABLE_PROP = "PowerCapEnable";
-constexpr auto POWER_CAP_SOFT_MIN = "MinSoftPowerCapValue";
-constexpr auto POWER_CAP_HARD_MIN = "MinPowerCapValue";
-constexpr auto POWER_CAP_MAX = "MaxPowerCapValue";
 
 namespace fs = std::filesystem;
 
+using CapLimits =
+    sdbusplus::xyz::openbmc_project::Control::Power::server::CapLimits;
+
+// Print the current values
+void OccPersistCapData::print()
+{
+    if (capData.initialized)
+    {
+        lg2::info(
+            "OccPersistCapData: Soft Min: {SOFT}, Hard Min: {HARD}, Max: {MAX}",
+            "SOFT", capData.softMin, "HARD", capData.hardMin, "MAX",
+            capData.max);
+    }
+}
+
+// Saves the power cap data in the filesystem.
+void OccPersistCapData::save()
+{
+    std::filesystem::path opath =
+        std::filesystem::path{OCC_CONTROL_PERSIST_PATH} / powerCapFilename;
+
+    if (!std::filesystem::exists(opath.parent_path()))
+    {
+        std::filesystem::create_directory(opath.parent_path());
+    }
+
+    try
+    {
+        std::ofstream stream{opath.c_str(), std::ios_base::binary};
+        stream.write((char*)&capData, sizeof(capData));
+    }
+    catch (const std::exception& e)
+    {
+        auto error = errno;
+        lg2::error(
+            "OccPersistCapData::save: failed to read {PATH}, errno={ERRNO}, err={ERR}",
+            "PATH", opath, "ERRNO", error, "ERR", e);
+        capData.initialized = false;
+    }
+}
+
+// Loads the power cap data from the filesystem
+void OccPersistCapData::load()
+{
+    std::filesystem::path ipath =
+        std::filesystem::path{OCC_CONTROL_PERSIST_PATH} / powerCapFilename;
+
+    if (!std::filesystem::exists(ipath))
+    {
+        capData.initialized = false;
+        return;
+    }
+
+    try
+    {
+        PowerCapData newCapData;
+        std::ifstream stream{ipath.c_str(), std::ios_base::binary};
+        stream.read((char*)&newCapData, sizeof(newCapData));
+        if (newCapData.version != PCAPDATA_FILE_VERSION)
+        {
+            lg2::warning(
+                "OccPersistCapData::load() file version was {VER} (expected {EXP})",
+                "VER", newCapData.version, "EXP", PCAPDATA_FILE_VERSION);
+        }
+        memcpy(&capData, &newCapData, sizeof(capData));
+    }
+    catch (const std::exception& e)
+    {
+        auto error = errno;
+        lg2::error(
+            "OccPersistCapData::load: failed to read {PATH}, errno={ERRNO}, err={ERR}",
+            "PATH", ipath, "ERRNO", error, "ERR", e);
+        capData.initialized = false;
+    }
+}
+
 void PowerCap::updatePcapBounds()
 {
     // Build the hwmon string to write the power cap bounds
@@ -32,12 +105,13 @@
         getPcapFilename(std::regex{"power\\d+_cap_min_soft$"});
     fs::path maxName = getPcapFilename(std::regex{"power\\d+_cap_max$"});
 
-    // Read the current cap bounds from dbus
+    // Read the current limits from persistent data
     uint32_t capSoftMin, capHardMin, capMax;
-    readDbusPcapLimits(capSoftMin, capHardMin, capMax);
+    persistedData.getCapLimits(capSoftMin, capHardMin, capMax);
 
     // Read the power cap bounds from sysfs files (from OCC)
     uint64_t cap;
+    bool parmsChanged = false;
     std::ifstream softMinFile(softMinName, std::ios::in);
     if (softMinFile)
     {
@@ -45,6 +119,7 @@
         softMinFile.close();
         // Convert to input/AC Power in Watts (round up)
         capSoftMin = ((cap / (PS_DERATING_FACTOR / 100.0) / 1000000) + 0.9);
+        parmsChanged = true;
     }
     else
     {
@@ -60,6 +135,7 @@
         minFile.close();
         // Convert to input/AC Power in Watts (round up)
         capHardMin = ((cap / (PS_DERATING_FACTOR / 100.0) / 1000000) + 0.9);
+        parmsChanged = true;
     }
     else
     {
@@ -75,6 +151,7 @@
         maxFile.close();
         // Convert to input/AC Power in Watts (truncate remainder)
         capMax = cap / (PS_DERATING_FACTOR / 100.0) / 1000000;
+        parmsChanged = true;
     }
     else
     {
@@ -83,8 +160,12 @@
             "FILE", pcapBasePathname, "ERR", errno);
     }
 
-    // Save the power cap bounds to dbus
-    updateDbusPcapLimits(capSoftMin, capHardMin, capMax);
+    if (parmsChanged)
+    {
+        // Save the power cap bounds to dbus
+        updateDbusPcapLimits(capSoftMin, capHardMin, capMax);
+        persistedData.updateCapLimits(capSoftMin, capHardMin, capMax);
+    }
 
     // Validate user power cap (if enabled) is within the bounds
     const uint32_t dbusUserCap = getPcap();
@@ -334,7 +415,7 @@
 
     // Validate the cap is within supported range
     uint32_t capSoftMin, capHardMin, capMax;
-    readDbusPcapLimits(capSoftMin, capHardMin, capMax);
+    persistedData.getCapLimits(capSoftMin, capHardMin, capMax);
     if (((pcap > 0) && (pcap < capSoftMin)) || ((pcap == 0) && (pcapEnabled)))
     {
         lg2::error(
@@ -368,101 +449,12 @@
 }
 
 // Update the Power Cap bounds on DBus
-bool PowerCap::updateDbusPcapLimits(uint32_t softMin, uint32_t hardMin,
+void PowerCap::updateDbusPcapLimits(uint32_t softMin, uint32_t hardMin,
                                     uint32_t max)
 {
-    bool complete = true;
-
-    try
-    {
-        utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_SOFT_MIN,
-                           softMin);
-    }
-    catch (const sdbusplus::exception_t& e)
-    {
-        lg2::error(
-            "updateDbusPcapLimits: Failed to set SOFT PCAP to {MIN}W due to {ERR}",
-            "MIN", softMin, "ERR", e.what());
-        complete = false;
-    }
-
-    try
-    {
-        utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_HARD_MIN,
-                           hardMin);
-    }
-    catch (const sdbusplus::exception_t& e)
-    {
-        lg2::error(
-            "updateDbusPcapLimits: Failed to set HARD PCAP to {MIN}W due to {ERR}",
-            "MIN", hardMin, "ERR", e.what());
-        complete = false;
-    }
-
-    try
-    {
-        utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_MAX, max);
-    }
-    catch (const sdbusplus::exception_t& e)
-    {
-        lg2::error(
-            "updateDbusPcapLimits: Failed to set MAX PCAP to {MAX}W due to {ERR}",
-            "MAX", max, "ERR", e.what());
-        complete = false;
-    }
-
-    return complete;
-}
-
-// Read the Power Cap bounds from DBus
-bool PowerCap::readDbusPcapLimits(uint32_t& softMin, uint32_t& hardMin,
-                                  uint32_t& max)
-{
-    bool complete = true;
-    utils::PropertyValue prop{};
-
-    try
-    {
-        prop =
-            utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_SOFT_MIN);
-        softMin = std::get<uint32_t>(prop);
-    }
-    catch (const sdbusplus::exception_t& e)
-    {
-        lg2::error("readDbusPcapLimits: Failed to get SOFT PCAP due to {ERR}",
-                   "ERR", e.what());
-        softMin = 0;
-        complete = false;
-    }
-
-    try
-    {
-        prop =
-            utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_HARD_MIN);
-        hardMin = std::get<uint32_t>(prop);
-    }
-    catch (const sdbusplus::exception_t& e)
-    {
-        lg2::error("readDbusPcapLimits: Failed to get HARD PCAP due to {ERR}",
-                   "ERR", e.what());
-        hardMin = 0;
-        complete = false;
-    }
-
-    try
-    {
-        prop = utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_MAX);
-        max = std::get<uint32_t>(prop);
-    }
-    catch (const sdbusplus::exception_t& e)
-    {
-        lg2::error("readDbusPcapLimits: Failed to get MAX PCAP due to {ERR}",
-                   "ERR", e.what());
-        max = INT_MAX;
-        complete = false;
-    }
-
-    return complete;
+    CapLimits::minSoftPowerCapValue(softMin);
+    CapLimits::minPowerCapValue(hardMin);
+    CapLimits::maxPowerCapValue(max);
 }
 
 } // namespace powercap
diff --git a/powercap.hpp b/powercap.hpp
index 45d1203..c46b6b9 100644
--- a/powercap.hpp
+++ b/powercap.hpp
@@ -6,6 +6,7 @@
 
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/bus/match.hpp>
+#include <xyz/openbmc_project/Control/Power/CapLimits/server.hpp>
 
 #include <filesystem>
 #include <regex>
@@ -22,6 +23,95 @@
 namespace sdbusRule = sdbusplus::bus::match::rules;
 namespace fs = std::filesystem;
 
+constexpr auto PCAPLIMITS_PATH =
+    "/xyz/openbmc_project/control/host0/power_cap_limits";
+
+namespace Base = sdbusplus::xyz::openbmc_project::Control::Power::server;
+using CapLimitsInterface = sdbusplus::server::object_t<Base::CapLimits>;
+
+constexpr auto PCAPDATA_FILE_VERSION = 1;
+struct PowerCapData
+{
+    uint32_t version = PCAPDATA_FILE_VERSION;
+    bool initialized = false;
+    uint32_t softMin = 0x0000;
+    uint32_t hardMin = 0x0000;
+    uint32_t max = UINT_MAX;
+};
+
+/** @class OccPersistCapData
+ *  @brief Provides persistent container to store data for OCC
+ *
+ * Data is stored in filesystem
+ */
+class OccPersistCapData
+{
+  public:
+    ~OccPersistCapData() = default;
+    OccPersistCapData(const OccPersistCapData&) = default;
+    OccPersistCapData& operator=(const OccPersistCapData&) = default;
+    OccPersistCapData(OccPersistCapData&&) = default;
+    OccPersistCapData& operator=(OccPersistCapData&&) = default;
+
+    /** @brief Loads any saved power cap data */
+    OccPersistCapData()
+    {
+        load();
+    }
+
+    /** @brief Save Power Mode data to persistent file
+     *
+     *  @param[in] softMin - soft minimum power cap in Watts
+     *  @param[in] hardMin - hard minimum power cap in Watts
+     *  @param[in] max     - maximum power cap in Watts
+     */
+    void updateCapLimits(const uint32_t softMin, const uint32_t hardMin,
+                         const uint32_t max)
+    {
+        capData.softMin = softMin;
+        capData.hardMin = hardMin;
+        capData.max = max;
+        capData.initialized = true;
+        save();
+    }
+
+    /** @brief Return the power cap limits
+     *
+     *  @param[out] softMin - soft minimum power cap in Watts
+     *  @param[out] hardMin - hard minimum power cap in Watts
+     *  @param[out] max     - maximum power cap in Watts
+     */
+    void getCapLimits(uint32_t& softMin, uint32_t& hardMin, uint32_t& max) const
+    {
+        // If not initialized yet, still return PowerCapData defaults
+        softMin = capData.softMin;
+        hardMin = capData.hardMin;
+        max = capData.max;
+    }
+
+    /** @brief Return true if the power cap limits are available */
+    bool limitsAvailable()
+    {
+        return (capData.initialized);
+    }
+
+    /** @brief Saves the Power Mode data in the filesystem. */
+    void save();
+
+    /** @brief Trace the Power Mode and IPS parameters. */
+    void print();
+
+  private:
+    /** @brief Power Mode data filename to store persistent data */
+    static constexpr auto powerCapFilename = "powerCapData";
+
+    /** @brief Power Mode data object to be persisted */
+    PowerCapData capData;
+
+    /** @brief Loads the persisted power cap data from the filesystem. */
+    void load();
+};
+
 /** @class PowerCap
  *  @brief Monitors for changes to the power cap and notifies occ
  *
@@ -30,7 +120,7 @@
  *  the power cap to the OCC if the cap is changed while the occ is active.
  */
 
-class PowerCap
+class PowerCap : public CapLimitsInterface
 {
   public:
     /** @brief PowerCap object to inform occ of changes to cap
@@ -42,6 +132,8 @@
      * @param[in] occStatus - The occ status object
      */
     explicit PowerCap(Status& occStatus) :
+        CapLimitsInterface(utils::getBus(), PCAPLIMITS_PATH,
+                           CapLimitsInterface::action::defer_emit),
         occStatus(occStatus),
         pcapMatch(
             utils::getBus(),
@@ -51,7 +143,16 @@
                 sdbusRule::argN(0, "xyz.openbmc_project.Control.Power.Cap") +
                 sdbusRule::interface("org.freedesktop.DBus.Properties"),
             std::bind(std::mem_fn(&PowerCap::pcapChanged), this,
-                      std::placeholders::_1)) {};
+                      std::placeholders::_1))
+    {
+        //  Read the current limits from persistent data
+        uint32_t capSoftMin, capHardMin, capMax;
+        persistedData.getCapLimits(capSoftMin, capHardMin, capMax);
+        // Update limits on dbus
+        updateDbusPcapLimits(capSoftMin, capHardMin, capMax);
+        // CapLimit interface is now ready
+        this->emit_object_added();
+    };
 
     /** @brief Return the appropriate value to write to the OCC (output/DC
      * power)
@@ -67,6 +168,9 @@
     void updatePcapBounds();
 
   private:
+    /** @brief Persisted power cap limits */
+    OccPersistCapData persistedData;
+
     /** @brief Callback for pcap setting changes
      *
      * Process change and inform OCC
@@ -126,24 +230,9 @@
      * @param[in]  softMin - soft minimum power cap in Watts
      * @param[in]  hardMin - hard minimum power cap in Watts
      * @param[in]  pcapMax - maximum power cap in Watts
-     *
-     * @return true if all parms were written successfully
      */
-    bool updateDbusPcapLimits(uint32_t softMin, uint32_t hardMin,
+    void updateDbusPcapLimits(uint32_t softMin, uint32_t hardMin,
                               uint32_t pcapMax);
-
-    /** @brief Read the power cap bounds from DBus
-     *
-     * @param[out]  softMin - soft minimum power cap in Watts
-     * @param[out]  hardMin - hard minimum power cap in Watts
-     * @param[out]  pcapMax - maximum power cap in Watts
-     *
-     * @return true if all parms were read successfully
-     *         If a parm is not successfully read, it will default to 0 for the
-     *           Min parameter and INT_MAX for the Max parameter
-     */
-    bool readDbusPcapLimits(uint32_t& softMin, uint32_t& hardMin,
-                            uint32_t& max);
 };
 
 } // namespace powercap
diff --git a/test/utest.cpp b/test/utest.cpp
index 89a3d6e..f06ad7b 100644
--- a/test/utest.cpp
+++ b/test/utest.cpp
@@ -41,18 +41,6 @@
     powercap::PowerCap pcap;
 };
 
-TEST_F(VerifyOccInput, PcapDisabled)
-{
-    uint32_t occInput = pcap.getOccInput(100, false);
-    EXPECT_EQ(occInput, 0);
-}
-
-TEST_F(VerifyOccInput, PcapEnabled)
-{
-    uint32_t occInput = pcap.getOccInput(100, true);
-    EXPECT_EQ(occInput, 90);
-}
-
 TEST(VerifyPathParsing, EmptyPath)
 {
     std::filesystem::path path = "";