dcmi: Support fully power limit setting commands

The correction time, sampling periodic and exception action of power
limit feature have been defined in the phosphor-dbus-interface [1]. This
commit supports configuration power limit attributes via setting dbus
properties of Power.Cap interface.

[1] https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/yaml/xyz/openbmc_project/Control/Power/Cap.interface.yaml

Tested:
   1. Request to update attributes of power limit via ipmitool
      $ipmitool dcmi power set_limit <parameter> <value>
   2. Get the power limit configuration's parameters.
      $ipmitool dcmi power get_limit
   3. The result as the step 1.

Change-Id: Ic753e9943029a37dc392b4a500d0fb13b9cb5899
Signed-off-by: Thang Tran <thuutran@amperecomputing.com>
diff --git a/dcmihandler.cpp b/dcmihandler.cpp
index 769081a..e6cb102 100644
--- a/dcmihandler.cpp
+++ b/dcmihandler.cpp
@@ -26,11 +26,13 @@
 
 void registerNetFnDcmiFunctions() __attribute__((constructor));
 
-constexpr auto pcapPath = "/xyz/openbmc_project/control/host0/power_cap";
 constexpr auto pcapInterface = "xyz.openbmc_project.Control.Power.Cap";
 
 constexpr auto powerCapProp = "PowerCap";
 constexpr auto powerCapEnableProp = "PowerCapEnable";
+constexpr auto powerCapExceptActProp = "ExceptionAction";
+constexpr auto powerCapSamplPeriodProp = "SamplingPeriod";
+constexpr auto powerCapCorrectTimeProp = "CorrectionTime";
 
 using namespace phosphor::logging;
 
@@ -66,6 +68,49 @@
     DHCPTiming3,
 };
 
+/*
+ * The list of Exception action options. Please refer to table 6-17 of DCMI
+ * specification version 1.5 for more information.
+ * https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/dcmi-v1-5-rev-spec.pdf
+ */
+enum class ExceptActOptions : uint8_t
+{
+    NoAction = 0x00,
+    HardPowerOff,
+    Oem02,
+    Oem03,
+    Oem04,
+    Oem05,
+    Oem06,
+    Oem07,
+    Oem08,
+    Oem09,
+    Oem0A,
+    Oem0B,
+    Oem0C,
+    Oem0D,
+    Oem0E,
+    Oem0F,
+    Oem10,
+    LogEventOnly,
+};
+
+/*
+ * The PDIs only supports NoAction/HardPowerOff/LogEventOnly/Oem options for
+ * exception action.
+ */
+namespace DbusExceptAct
+{
+constexpr auto noAction =
+    "xyz.openbmc_project.Control.Power.Cap.ExceptionActions.NoAction";
+constexpr auto hardPowerOff =
+    "xyz.openbmc_project.Control.Power.Cap.ExceptionActions.HardPowerOff";
+constexpr auto logEventOnly =
+    "xyz.openbmc_project.Control.Power.Cap.ExceptionActions.LogEventOnly";
+constexpr auto oem =
+    "xyz.openbmc_project.Control.Power.Cap.ExceptionActions.Oem";
+} // namespace DbusExceptAct
+
 // Refer Table 6-14, DCMI Entity ID Extension, DCMI v1.5 spec
 static const std::map<uint8_t, std::string> entityIdToName{
     {0x40, "inlet"}, {0x37, "inlet"},     {0x41, "cpu"},
@@ -104,81 +149,198 @@
     return supported;
 }
 
-std::optional<uint32_t> getPcap(ipmi::Context::ptr& ctx)
+std::optional<ipmi::DbusObjectInfo> getPCapObject(ipmi::Context::ptr& ctx)
 {
-    std::string service{};
+    static std::optional<ipmi::DbusObjectInfo> pcapObject = std::nullopt;
+
+    if (pcapObject != std::nullopt)
+    {
+        return pcapObject;
+    }
+
+    ipmi::DbusObjectInfo objectPath{};
     boost::system::error_code ec =
-        ipmi::getService(ctx, pcapInterface, pcapPath, service);
+        ipmi::getDbusObject(ctx, pcapInterface, objectPath);
+
     if (ec.value())
     {
         return std::nullopt;
     }
+
+    pcapObject = objectPath;
+
+    return pcapObject;
+}
+
+std::optional<uint32_t> getPcap(ipmi::Context::ptr& ctx)
+{
+    std::optional<ipmi::DbusObjectInfo> pcapObject = getPCapObject(ctx);
+
+    if (pcapObject == std::nullopt)
+    {
+        return std::nullopt;
+    }
+
     uint32_t pcap{};
-    ec = ipmi::getDbusProperty(ctx, service, pcapPath, pcapInterface,
-                               powerCapProp, pcap);
+    boost::system::error_code ec = ipmi::getDbusProperty(
+        ctx, pcapObject.value().second.c_str(),
+        pcapObject.value().first.c_str(), pcapInterface, powerCapProp, pcap);
+
     if (ec.value())
     {
         lg2::error("Error in getPcap prop: {ERROR}", "ERROR", ec.message());
         elog<InternalFailure>();
         return std::nullopt;
     }
+
     return pcap;
 }
 
 std::optional<bool> getPcapEnabled(ipmi::Context::ptr& ctx)
 {
-    std::string service{};
-    boost::system::error_code ec =
-        ipmi::getService(ctx, pcapInterface, pcapPath, service);
-    if (ec.value())
+    std::optional<ipmi::DbusObjectInfo> pcapObject = getPCapObject(ctx);
+
+    if (pcapObject == std::nullopt)
     {
         return std::nullopt;
     }
+
     bool pcapEnabled{};
-    ec = ipmi::getDbusProperty(ctx, service, pcapPath, pcapInterface,
-                               powerCapEnableProp, pcapEnabled);
+    boost::system::error_code ec =
+        ipmi::getDbusProperty(ctx, pcapObject.value().second.c_str(),
+                              pcapObject.value().first.c_str(), pcapInterface,
+                              powerCapEnableProp, pcapEnabled);
+
     if (ec.value())
     {
-        lg2::error("Error in getPcap prop");
+        lg2::error("Error in getPcapEnabled prop: {ERROR}", "ERROR",
+                   ec.message());
         elog<InternalFailure>();
         return std::nullopt;
     }
+
     return pcapEnabled;
 }
 
+std::optional<std::string> getPcapExceptAction(ipmi::Context::ptr& ctx)
+{
+    std::optional<ipmi::DbusObjectInfo> pcapObject = getPCapObject(ctx);
+
+    if (pcapObject == std::nullopt)
+    {
+        return std::nullopt;
+    }
+
+    std::string exceptActStr{};
+
+    boost::system::error_code ec =
+        ipmi::getDbusProperty(ctx, pcapObject.value().second.c_str(),
+                              pcapObject.value().first.c_str(), pcapInterface,
+                              powerCapExceptActProp, exceptActStr);
+
+    if (ec.value())
+    {
+        lg2::error("Error in getPcapExceptAction prop: {ERROR}", "ERROR",
+                   ec.message());
+        elog<InternalFailure>();
+        return std::nullopt;
+    }
+
+    return exceptActStr;
+}
+
+std::optional<uint32_t> getPcapCorrectTime(ipmi::Context::ptr& ctx)
+{
+    std::optional<ipmi::DbusObjectInfo> pcapObject = getPCapObject(ctx);
+
+    if (pcapObject == std::nullopt)
+    {
+        return std::nullopt;
+    }
+
+    uint64_t pcapCorrectTimeUs{};
+    boost::system::error_code ec =
+        ipmi::getDbusProperty(ctx, pcapObject.value().second.c_str(),
+                              pcapObject.value().first.c_str(), pcapInterface,
+                              powerCapCorrectTimeProp, pcapCorrectTimeUs);
+    if (ec.value())
+    {
+        lg2::error("Error in getPcapCorrectTime prop: {ERROR}", "ERROR",
+                   ec.message());
+        elog<InternalFailure>();
+        return std::nullopt;
+    }
+
+    return (uint32_t)(std::chrono::duration_cast<std::chrono::milliseconds>(
+                          std::chrono::microseconds(pcapCorrectTimeUs)))
+        .count();
+}
+
+std::optional<uint16_t> getPcapSamplPeriod(ipmi::Context::ptr& ctx)
+{
+    std::optional<ipmi::DbusObjectInfo> pcapObject = getPCapObject(ctx);
+
+    if (pcapObject == std::nullopt)
+    {
+        return std::nullopt;
+    }
+
+    uint64_t pcapSamplPeriodUs{};
+    boost::system::error_code ec =
+        ipmi::getDbusProperty(ctx, pcapObject.value().second.c_str(),
+                              pcapObject.value().first.c_str(), pcapInterface,
+                              powerCapSamplPeriodProp, pcapSamplPeriodUs);
+    if (ec.value())
+    {
+        lg2::error("Error in getPcapSamplPeriod prop: {ERROR}", "ERROR",
+                   ec.message());
+        elog<InternalFailure>();
+        return std::nullopt;
+    }
+
+    return (uint16_t)(std::chrono::duration_cast<std::chrono::seconds>(
+                          std::chrono::microseconds(pcapSamplPeriodUs)))
+        .count();
+}
+
 bool setPcap(ipmi::Context::ptr& ctx, const uint32_t powerCap)
 {
-    std::string service{};
-    boost::system::error_code ec =
-        ipmi::getService(ctx, pcapInterface, pcapPath, service);
-    if (ec.value())
+    std::optional<ipmi::DbusObjectInfo> pcapObject = getPCapObject(ctx);
+
+    if (pcapObject == std::nullopt)
     {
         return false;
     }
 
-    ec = ipmi::setDbusProperty(ctx, service, pcapPath, pcapInterface,
-                               powerCapProp, powerCap);
+    boost::system::error_code ec =
+        ipmi::setDbusProperty(ctx, pcapObject.value().second.c_str(),
+                              pcapObject.value().first.c_str(), pcapInterface,
+                              powerCapProp, powerCap);
+
     if (ec.value())
     {
         lg2::error("Error in setPcap property: {ERROR}", "ERROR", ec.message());
         elog<InternalFailure>();
         return false;
     }
+
     return true;
 }
 
 bool setPcapEnable(ipmi::Context::ptr& ctx, bool enabled)
 {
-    std::string service{};
-    boost::system::error_code ec =
-        ipmi::getService(ctx, pcapInterface, pcapPath, service);
-    if (ec.value())
+    std::optional<ipmi::DbusObjectInfo> pcapObject = getPCapObject(ctx);
+
+    if (pcapObject == std::nullopt)
     {
         return false;
     }
 
-    ec = ipmi::setDbusProperty(ctx, service, pcapPath, pcapInterface,
-                               powerCapEnableProp, enabled);
+    boost::system::error_code ec =
+        ipmi::setDbusProperty(ctx, pcapObject.value().second.c_str(),
+                              pcapObject.value().first.c_str(), pcapInterface,
+                              powerCapEnableProp, enabled);
+
     if (ec.value())
     {
         lg2::error("Error in setPcapEnabled property: {ERROR}", "ERROR",
@@ -189,6 +351,95 @@
     return true;
 }
 
+bool setPcapExceptAction(ipmi::Context::ptr& ctx,
+                         const std::string& pcapExceptAct)
+{
+    std::optional<ipmi::DbusObjectInfo> pcapObject = getPCapObject(ctx);
+
+    if (pcapObject == std::nullopt)
+    {
+        return false;
+    }
+
+    boost::system::error_code ec =
+        ipmi::setDbusProperty(ctx, pcapObject.value().second.c_str(),
+                              pcapObject.value().first.c_str(), pcapInterface,
+                              powerCapExceptActProp, pcapExceptAct);
+    if (ec.value())
+    {
+        lg2::error("Error in setPcapExceptAction property: {ERROR}", "ERROR",
+                   ec.message());
+        elog<InternalFailure>();
+        return false;
+    }
+
+    return true;
+}
+
+bool setPcapCorrectTime(ipmi::Context::ptr& ctx, uint32_t pcapCorrectTime)
+{
+    std::optional<ipmi::DbusObjectInfo> pcapObject = getPCapObject(ctx);
+
+    if (pcapObject == std::nullopt)
+    {
+        return false;
+    }
+
+    /*
+     * Dbus is storing Correction time in microseconds unit.
+     * Therefore, we have to convert it from milisecond to microseconds.
+     */
+    uint64_t pcapCorrectTimeUs =
+        (uint64_t)(std::chrono::duration_cast<std::chrono::microseconds>(
+                       std::chrono::milliseconds(pcapCorrectTime)))
+            .count();
+    boost::system::error_code ec =
+        ipmi::setDbusProperty(ctx, pcapObject.value().second.c_str(),
+                              pcapObject.value().first.c_str(), pcapInterface,
+                              powerCapCorrectTimeProp, pcapCorrectTimeUs);
+    if (ec.value())
+    {
+        lg2::error("Error in setPcapCorrectTime property: {ERROR}", "ERROR",
+                   ec.message());
+        elog<InternalFailure>();
+        return false;
+    }
+
+    return true;
+}
+
+bool setPcapSamplPeriod(ipmi::Context::ptr& ctx, uint16_t pcapSamplPeriod)
+{
+    std::optional<ipmi::DbusObjectInfo> pcapObject = getPCapObject(ctx);
+
+    if (pcapObject == std::nullopt)
+    {
+        return false;
+    }
+
+    /*
+     * Dbus is storing Sampling periodic in microseconds unit.
+     * Therefore, we have to convert it from seconds to microseconds unit.
+     */
+    uint64_t pcapSamplPeriodUs =
+        (uint64_t)(std::chrono::duration_cast<std::chrono::microseconds>(
+                       std::chrono::seconds(pcapSamplPeriod)))
+            .count();
+    boost::system::error_code ec =
+        ipmi::setDbusProperty(ctx, pcapObject.value().second.c_str(),
+                              pcapObject.value().first.c_str(), pcapInterface,
+                              powerCapSamplPeriodProp, pcapSamplPeriodUs);
+    if (ec.value())
+    {
+        lg2::error("Error in setPcapSamplPeriod property: {ERROR}", "ERROR",
+                   ec.message());
+        elog<InternalFailure>();
+        return false;
+    }
+
+    return true;
+}
+
 std::optional<std::string> readAssetTag(ipmi::Context::ptr& ctx)
 {
     // Read the object tree with the inventory root to figure out the object
@@ -345,7 +596,6 @@
 
 } // namespace dcmi
 
-constexpr uint8_t exceptionPowerOff = 0x01;
 ipmi::RspType<uint16_t, // reserved
               uint8_t,  // exception actions
               uint16_t, // power limit requested in watts
@@ -366,34 +616,54 @@
 
     std::optional<uint16_t> pcapValue = dcmi::getPcap(ctx);
     std::optional<bool> pcapEnable = dcmi::getPcapEnabled(ctx);
-    if (!pcapValue || !pcapEnable)
+    std::optional<uint32_t> pcapCorrectTime = dcmi::getPcapCorrectTime(ctx);
+    std::optional<uint16_t> pcapSamplPeriod = dcmi::getPcapSamplPeriod(ctx);
+    std::optional<std::string> pcapExceptAct = dcmi::getPcapExceptAction(ctx);
+
+    if (!pcapValue || !pcapEnable || !pcapCorrectTime || !pcapSamplPeriod ||
+        !pcapExceptAct)
     {
         return ipmi::responseUnspecifiedError();
     }
 
     constexpr uint16_t reserved1{};
     constexpr uint16_t reserved2{};
-    /*
-     * Exception action if power limit is exceeded and cannot be controlled
-     * with the correction time limit is hardcoded to Hard Power Off system
-     * and log event to SEL.
-     */
-    constexpr uint8_t exception = exceptionPowerOff;
-    /*
-     * Correction time limit and Statistics sampling period is currently not
-     * populated.
-     */
-    constexpr uint32_t correctionTime{};
-    constexpr uint16_t statsPeriod{};
-    if (*pcapEnable == false)
+    uint8_t exception;
+
+    std::string exceptAct = pcapExceptAct.value();
+
+    if (exceptAct == dcmi::DbusExceptAct::noAction)
+    {
+        exception = static_cast<uint8_t>(dcmi::ExceptActOptions::NoAction);
+    }
+    else if (exceptAct == dcmi::DbusExceptAct::hardPowerOff)
+    {
+        exception = static_cast<uint8_t>(dcmi::ExceptActOptions::HardPowerOff);
+    }
+    else if (exceptAct == dcmi::DbusExceptAct::logEventOnly)
+    {
+        exception = static_cast<uint8_t>(dcmi::ExceptActOptions::LogEventOnly);
+    }
+    else if (exceptAct == dcmi::DbusExceptAct::oem)
+    {
+        exception = static_cast<uint8_t>(dcmi::ExceptActOptions::Oem02);
+    }
+    else
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    if (!pcapEnable.value())
     {
         constexpr ipmi::Cc responseNoPowerLimitSet = 0x80;
         return ipmi::response(responseNoPowerLimitSet, reserved1, exception,
-                              *pcapValue, correctionTime, reserved2,
-                              statsPeriod);
+                              pcapValue.value(), pcapCorrectTime.value(),
+                              reserved2, pcapSamplPeriod.value());
     }
-    return ipmi::responseSuccess(reserved1, exception, *pcapValue,
-                                 correctionTime, reserved2, statsPeriod);
+
+    return ipmi::responseSuccess(reserved1, exception, pcapValue.value(),
+                                 pcapCorrectTime.value(), reserved2,
+                                 pcapSamplPeriod.value());
 }
 
 ipmi::RspType<> setPowerLimit(ipmi::Context::ptr& ctx, uint16_t reserved1,
@@ -407,10 +677,7 @@
         return ipmi::responseInvalidCommand();
     }
 
-    // Only process the power limit requested in watts. Return errors
-    // for other fields that are set
-    if (reserved1 || reserved2 || reserved3 || correctionTime || statsPeriod ||
-        exceptionAction != exceptionPowerOff)
+    if (reserved1 || reserved2 || reserved3)
     {
         return ipmi::responseInvalidFieldRequest();
     }
@@ -420,7 +687,60 @@
         return ipmi::responseUnspecifiedError();
     }
 
-    lg2::info("Set Power Cap: {POWERCAP}", "POWERCAP", powerLimit);
+    /*
+     * As defined in table 6-18 of DCMI specification version 1.5.
+     * The Exception action value is mapped as below
+     *  00h - No Action
+     *  01h - Hard Power Off system and log events to SEL
+     *  02h - 10h OEM defined actions
+     *  11h - Log event to SEL only
+     *  12h-FFh Reserved
+     */
+    if (exceptionAction >= 0x12)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    std::string exceptActStr;
+
+    switch (static_cast<dcmi::ExceptActOptions>(exceptionAction))
+    {
+        case dcmi::ExceptActOptions::NoAction:
+        {
+            exceptActStr = dcmi::DbusExceptAct::noAction;
+            break;
+        }
+        case dcmi::ExceptActOptions::HardPowerOff:
+        {
+            exceptActStr = dcmi::DbusExceptAct::hardPowerOff;
+            break;
+        }
+        case dcmi::ExceptActOptions::LogEventOnly:
+        {
+            exceptActStr = dcmi::DbusExceptAct::logEventOnly;
+            break;
+        }
+        default:
+        {
+            exceptActStr = dcmi::DbusExceptAct::oem;
+            break;
+        }
+    }
+
+    if (!dcmi::setPcapExceptAction(ctx, exceptActStr))
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    if (!dcmi::setPcapCorrectTime(ctx, correctionTime))
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    if (!dcmi::setPcapSamplPeriod(ctx, statsPeriod))
+    {
+        return ipmi::responseUnspecifiedError();
+    }
 
     return ipmi::responseSuccess();
 }