Add SetSensorThrehold Command

Support to modify the sensor threshold
`with the ipmitool command.
This piece of code refers to the following code:
https://github.com/openbmc/intel-ipmi-oem/blob/f4d5e05e388aafd06d8320a2f81594de35c77c0d/src/sensorcommands.cpp#L597

Tested: Run `ipmitool sensor thresh Inlet_Temp unc 40`
threshold set ok.

Signed-off-by: Lotus Xu <xuxiaohan@bytedance.com>
Change-Id: I78f315a06c913466649e1ae91d046c3e8c50bbed
diff --git a/sensorhandler.cpp b/sensorhandler.cpp
index e2b2874..350eced 100644
--- a/sensorhandler.cpp
+++ b/sensorhandler.cpp
@@ -607,6 +607,166 @@
                                  resp.upperNonRecoverable);
 }
 
+/** @brief implements the Set Sensor threshold command
+ *  @param sensorNumber        - sensor number
+ *  @param lowerNonCriticalThreshMask
+ *  @param lowerCriticalThreshMask
+ *  @param lowerNonRecovThreshMask
+ *  @param upperNonCriticalThreshMask
+ *  @param upperCriticalThreshMask
+ *  @param upperNonRecovThreshMask
+ *  @param reserved
+ *  @param lowerNonCritical    - lower non-critical threshold
+ *  @param lowerCritical       - Lower critical threshold
+ *  @param lowerNonRecoverable - Lower non recovarable threshold
+ *  @param upperNonCritical    - Upper non-critical threshold
+ *  @param upperCritical       - Upper critical
+ *  @param upperNonRecoverable - Upper Non-recoverable
+ *
+ *  @returns IPMI completion code
+ */
+ipmi::RspType<> ipmiSenSetSensorThresholds(
+    ipmi::Context::ptr& ctx, uint8_t sensorNum, bool lowerNonCriticalThreshMask,
+    bool lowerCriticalThreshMask, bool lowerNonRecovThreshMask,
+    bool upperNonCriticalThreshMask, bool upperCriticalThreshMask,
+    bool upperNonRecovThreshMask, uint2_t reserved, uint8_t lowerNonCritical,
+    uint8_t lowerCritical, uint8_t lowerNonRecoverable,
+    uint8_t upperNonCritical, uint8_t upperCritical,
+    uint8_t upperNonRecoverable)
+{
+    if (reserved)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // lower nc and upper nc not suppported on any sensor
+    if (lowerNonRecovThreshMask || upperNonRecovThreshMask)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // if none of the threshold mask are set, nothing to do
+    if (!(lowerNonCriticalThreshMask | lowerCriticalThreshMask |
+          lowerNonRecovThreshMask | upperNonCriticalThreshMask |
+          upperCriticalThreshMask | upperNonRecovThreshMask))
+    {
+        return ipmi::responseSuccess();
+    }
+
+    constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value";
+
+    const auto iter = ipmi::sensor::sensors.find(sensorNum);
+    if (iter == ipmi::sensor::sensors.end())
+    {
+        return ipmi::responseSensorInvalid();
+    }
+
+    const auto& info = iter->second;
+
+    // Proceed only if the sensor value interface is implemented.
+    if (info.propertyInterfaces.find(valueInterface) ==
+        info.propertyInterfaces.end())
+    {
+        // return with valid mask as 0
+        return ipmi::responseSuccess();
+    }
+
+    constexpr auto warningThreshIntf =
+        "xyz.openbmc_project.Sensor.Threshold.Warning";
+    constexpr auto criticalThreshIntf =
+        "xyz.openbmc_project.Sensor.Threshold.Critical";
+
+    std::string service;
+    boost::system::error_code ec;
+    ec = ipmi::getService(ctx, info.sensorInterface, info.sensorPath, service);
+    if (ec)
+    {
+        return ipmi::responseResponseError();
+    }
+    // store a vector of property name, value to set, and interface
+    std::vector<std::tuple<std::string, uint8_t, std::string>> thresholdsToSet;
+
+    // define the indexes of the tuple
+    constexpr uint8_t propertyName = 0;
+    constexpr uint8_t thresholdValue = 1;
+    constexpr uint8_t interface = 2;
+    // verifiy all needed fields are present
+    if (lowerCriticalThreshMask || upperCriticalThreshMask)
+    {
+
+        ipmi::PropertyMap findThreshold;
+        ec = ipmi::getAllDbusProperties(ctx, service, info.sensorPath,
+                                        criticalThreshIntf, findThreshold);
+
+        if (!ec)
+        {
+            if (lowerCriticalThreshMask)
+            {
+                auto findLower = findThreshold.find("CriticalLow");
+                if (findLower == findThreshold.end())
+                {
+                    return ipmi::responseInvalidFieldRequest();
+                }
+                thresholdsToSet.emplace_back("CriticalLow", lowerCritical,
+                                             criticalThreshIntf);
+            }
+            if (upperCriticalThreshMask)
+            {
+                auto findUpper = findThreshold.find("CriticalHigh");
+                if (findUpper == findThreshold.end())
+                {
+                    return ipmi::responseInvalidFieldRequest();
+                }
+                thresholdsToSet.emplace_back("CriticalHigh", upperCritical,
+                                             criticalThreshIntf);
+            }
+        }
+    }
+    if (lowerNonCriticalThreshMask || upperNonCriticalThreshMask)
+    {
+        ipmi::PropertyMap findThreshold;
+        ec = ipmi::getAllDbusProperties(ctx, service, info.sensorPath,
+                                        warningThreshIntf, findThreshold);
+
+        if (!ec)
+        {
+            if (lowerNonCriticalThreshMask)
+            {
+                auto findLower = findThreshold.find("WarningLow");
+                if (findLower == findThreshold.end())
+                {
+                    return ipmi::responseInvalidFieldRequest();
+                }
+                thresholdsToSet.emplace_back("WarningLow", lowerNonCritical,
+                                             warningThreshIntf);
+            }
+            if (upperNonCriticalThreshMask)
+            {
+                auto findUpper = findThreshold.find("WarningHigh");
+                if (findUpper == findThreshold.end())
+                {
+                    return ipmi::responseInvalidFieldRequest();
+                }
+                thresholdsToSet.emplace_back("WarningHigh", upperNonCritical,
+                                             warningThreshIntf);
+            }
+        }
+    }
+    for (const auto& property : thresholdsToSet)
+    {
+        // from section 36.3 in the IPMI Spec, assume all linear
+        double valueToSet =
+            ((info.coefficientM * std::get<thresholdValue>(property)) +
+             (info.scaledOffset * std::pow(10.0, info.scale))) *
+            std::pow(10.0, info.exponentR);
+        ipmi::setDbusProperty(
+            ctx, service, info.sensorPath, std::get<interface>(property),
+            std::get<propertyName>(property), ipmi::Value(valueToSet));
+    }
+
+    return ipmi::responseSuccess();
+}
+
 /** @brief implements the get SDR Info command
  *  @param count - Operation
  *
@@ -1132,6 +1292,10 @@
                           ipmi::sensor_event::cmdGetSensorThreshold,
                           ipmi::Privilege::User, ipmiSensorGetSensorThresholds);
 
+    // <Set Sensor Thresholds>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdSetSensorThreshold,
+                          ipmi::Privilege::User, ipmiSenSetSensorThresholds);
 #endif
 
     // Common Handers used by both implementation.