Expose power cap min/max on dbus

The min and max power cap values will be read from sysfs files and then
the data will be put on the dbus. This will allow users to know the
valid range of power cap available.

The PowerCap object was moved from Manager to Status.

Change-Id: I5196cc8645f84c31a5282cf844109bae47b09bf7
Signed-off-by: Chris Cain <cjcain@us.ibm.com>
diff --git a/powercap.cpp b/powercap.cpp
index 2555d95..cd11059 100644
--- a/powercap.cpp
+++ b/powercap.cpp
@@ -1,10 +1,11 @@
+#include "occ_status.hpp"
+
 #include <fmt/core.h>
 
 #include <phosphor-logging/log.hpp>
 #include <powercap.hpp>
 
 #include <cassert>
-#include <regex>
 
 namespace open_power
 {
@@ -18,10 +19,77 @@
 
 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";
 
 using namespace phosphor::logging;
 namespace fs = std::filesystem;
 
+void PowerCap::updatePcapBounds()
+{
+    // Build the hwmon string to write the power cap bounds
+    fs::path minName = getPcapFilename(std::regex{"power\\d+_cap_min$"});
+    fs::path softMinName =
+        getPcapFilename(std::regex{"power\\d+_cap_min_soft$"});
+    fs::path maxName = getPcapFilename(std::regex{"power\\d+_cap_max$"});
+
+    // Read the power cap bounds from sysfs files
+    uint64_t cap;
+    uint32_t capSoftMin = 0, capHardMin = 0, capMax = INT_MAX;
+    std::ifstream softMinFile(softMinName, std::ios::in);
+    if (softMinFile)
+    {
+        softMinFile >> cap;
+        softMinFile.close();
+        // Convert to Watts
+        capSoftMin = cap / 1000000;
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format(
+                "updatePcapBounds: unable to find pcap_min_soft file: {} (errno={})",
+                pcapBasePathname.c_str(), errno)
+                .c_str());
+    }
+    std::ifstream minFile(minName, std::ios::in);
+    if (minFile)
+    {
+        minFile >> cap;
+        minFile.close();
+        // Convert to Watts
+        capHardMin = cap / 1000000;
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format(
+                "updatePcapBounds: unable to find cap_min file: {} (errno={})",
+                pcapBasePathname.c_str(), errno)
+                .c_str());
+    }
+    std::ifstream maxFile(maxName, std::ios::in);
+    if (maxFile)
+    {
+        maxFile >> cap;
+        maxFile.close();
+        // Convert to Watts
+        capMax = cap / 1000000;
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format(
+                "updatePcapBounds: unable to find cap_max file: {} (errno={})",
+                pcapBasePathname.c_str(), errno)
+                .c_str());
+    }
+
+    // Save the bounds to dbus
+    updateDbusPcap(capSoftMin, capHardMin, capMax);
+}
+
 uint32_t PowerCap::getOccInput(uint32_t pcap, bool pcapEnabled)
 {
     if (!pcapEnabled)
@@ -74,56 +142,65 @@
     }
 }
 
-std::string PowerCap::getPcapFilename(const fs::path& path)
+fs::path PowerCap::getPcapFilename(const std::regex& expr)
 {
-    std::regex expr{"power\\d+_cap_user$"};
-    for (auto& file : fs::directory_iterator(path))
+    if (pcapBasePathname.empty())
+    {
+        static bool tracedError = false;
+        pcapBasePathname = occStatus.getHwmonPath();
+        if (!fs::exists(pcapBasePathname) && !tracedError)
+        {
+            log<level::ERR>(fmt::format("Power Cap base filename not found: {}",
+                                        pcapBasePathname.c_str())
+                                .c_str());
+            tracedError = true;
+        }
+    }
+    for (auto& file : fs::directory_iterator(pcapBasePathname))
     {
         if (std::regex_search(file.path().string(), expr))
         {
-            return file.path().filename();
+            return file;
         }
     }
-    return std::string{};
+    // return empty path
+    return fs::path{};
 }
 
 void PowerCap::writeOcc(uint32_t pcapValue)
 {
-    // Create path out to master occ hwmon entry
-    std::unique_ptr<fs::path> fileName =
-        std::make_unique<fs::path>(OCC_HWMON_PATH);
-    *fileName /= occMasterName;
-    *fileName /= "hwmon";
-
-    // Need to get the hwmonXX directory name, there better only be 1 dir
-    assert(std::distance(fs::directory_iterator(*fileName),
-                         fs::directory_iterator{}) == 1);
-    // Now set our path to this full path, including this hwmonXX directory
-    fileName = std::make_unique<fs::path>(*fs::directory_iterator(*fileName));
-    // Append on the hwmon string where we write the user power cap
-
-    auto baseName = getPcapFilename(*fileName);
-    if (baseName.empty())
+    // Build the hwmon string to write the user power cap
+    fs::path fileName = getPcapFilename(std::regex{"power\\d+_cap_user$"});
+    if (fileName.empty())
     {
         log<level::ERR>(
             fmt::format("Could not find a power cap file to write to: {})",
-                        fileName->c_str())
+                        pcapBasePathname.c_str())
                 .c_str());
         return;
     }
-    *fileName /= baseName;
 
     uint64_t microWatts = pcapValue * 1000000ull;
 
     auto pcapString{std::to_string(microWatts)};
 
-    log<level::INFO>("Writing pcap value to hwmon",
-                     entry("PCAP_PATH=%s", fileName->c_str()),
-                     entry("PCAP_VALUE=%s", pcapString.c_str()));
     // Open the hwmon file and write the power cap
-    std::ofstream file(*fileName, std::ios::out);
-    file << pcapString;
-    file.close();
+    std::ofstream file(fileName, std::ios::out);
+    if (file)
+    {
+        log<level::INFO>(fmt::format("Writing {}uW to {}", pcapString.c_str(),
+                                     fileName.c_str())
+                             .c_str());
+        file << pcapString;
+        file.close();
+    }
+    else
+    {
+        log<level::ERR>(fmt::format("Failed writing {}uW to {} (errno={})",
+                                    microWatts, fileName.c_str(), errno)
+                            .c_str());
+    }
+
     return;
 }
 
@@ -131,7 +208,7 @@
 {
     if (!occStatus.occActive())
     {
-        // Nothing to  do
+        // Nothing to do
         return;
     }
 
@@ -142,43 +219,102 @@
     std::map<std::string, std::variant<uint32_t, bool>> msgData;
     msg.read(msgSensor, msgData);
 
-    // Retrieve which property changed via the msg and read the other one
-    auto valPropMap = msgData.find(POWER_CAP_PROP);
-    if (valPropMap != msgData.end())
+    bool changeFound = false;
+    for (const auto& [prop, value] : msgData)
     {
-        pcap = std::get<uint32_t>(valPropMap->second);
-        pcapEnabled = getPcapEnabled();
-    }
-    else
-    {
-        valPropMap = msgData.find(POWER_CAP_ENABLE_PROP);
-        if (valPropMap != msgData.end())
+        if (prop == POWER_CAP_PROP)
         {
-            pcapEnabled = std::get<bool>(valPropMap->second);
+            pcap = std::get<uint32_t>(value);
+            pcapEnabled = getPcapEnabled();
+            changeFound = true;
+        }
+        else if (prop == POWER_CAP_ENABLE_PROP)
+        {
+            pcapEnabled = std::get<bool>(value);
             pcap = getPcap();
+            changeFound = true;
         }
         else
         {
-            log<level::INFO>("Unknown power cap property changed");
-            return;
+            // Ignore other properties
+            log<level::DEBUG>(
+                fmt::format(
+                    "pcapChanged: Unknown power cap property changed {} to {}",
+                    prop.c_str(), std::get<uint32_t>(value))
+                    .c_str());
         }
     }
 
-    log<level::INFO>(
-        fmt::format("Power Cap Property Change (cap={}W (input), enabled={})",
-                    pcap, pcapEnabled ? 'y' : 'n')
-            .c_str());
+    if (changeFound)
+    {
+        log<level::INFO>(
+            fmt::format(
+                "Power Cap Property Change (cap={}W (input), enabled={})", pcap,
+                pcapEnabled ? 'y' : 'n')
+                .c_str());
 
-    // Determine desired action to write to occ
+        // Determine desired action to write to occ
 
-    auto occInput = getOccInput(pcap, pcapEnabled);
+        auto occInput = getOccInput(pcap, pcapEnabled);
 
-    // Write action to occ
-    writeOcc(occInput);
+        // Write action to occ
+        writeOcc(occInput);
+    }
 
     return;
 }
 
+// Update the Power Cap bounds on DBus
+bool PowerCap::updateDbusPcap(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::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format(
+                "updateDbusPcap: Failed to set SOFT PCAP to {}W due to {}",
+                softMin, e.what())
+                .c_str());
+        complete = false;
+    }
+
+    try
+    {
+        utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_HARD_MIN,
+                           hardMin);
+    }
+    catch (const sdbusplus::exception::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format(
+                "updateDbusPcap: Failed to set HARD PCAP to {}W due to {}",
+                hardMin, e.what())
+                .c_str());
+        complete = false;
+    }
+
+    try
+    {
+        utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_MAX, max);
+    }
+    catch (const sdbusplus::exception::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format(
+                "updateDbusPcap: Failed to set MAX PCAP to {}W due to {}", max,
+                e.what())
+                .c_str());
+        complete = false;
+    }
+
+    return complete;
+}
 } // namespace powercap
 
 } // namespace occ